| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | # SPDX-FileCopyrightText: Songlin Yang, Yu Zhang |
| 4 | # |
| 5 | # This file contains code copied from the flash-linear-attention project. |
| 6 | # The original source code was licensed under the MIT license and included |
| 7 | # the following copyright notice: |
| 8 | # Copyright (c) 2023-2025, Songlin Yang, Yu Zhang |
| 9 | # ruff: noqa: E501 |
| 10 | |
| 11 | import torch |
| 12 | |
| 13 | from .chunk_delta_h import chunk_gated_delta_rule_fwd_h |
| 14 | from .chunk_o import chunk_fwd_o |
| 15 | from .chunk_scaled_dot_kkt import chunk_scaled_dot_kkt_fwd |
| 16 | from .cumsum import chunk_local_cumsum |
| 17 | from .l2norm import l2norm_fwd |
| 18 | from .solve_tril import solve_tril |
| 19 | from .utils import FLA_CHUNK_SIZE, SUPPRESS_LEVEL, input_guard |
| 20 | from .wy_fast import recompute_w_u_fwd |
| 21 | |
| 22 | |
| 23 | def chunk_gated_delta_rule_fwd( |
| 24 | q: torch.Tensor, |
| 25 | k: torch.Tensor, |
| 26 | v: torch.Tensor, |
| 27 | g: torch.Tensor, |
| 28 | beta: torch.Tensor, |
| 29 | scale: float, |
| 30 | initial_state: torch.Tensor, |
| 31 | output_final_state: bool, |
| 32 | cu_seqlens: torch.Tensor | None = None, |
| 33 | chunk_indices: torch.Tensor | None = None, |
| 34 | chunk_offsets: torch.Tensor | None = None, |
| 35 | core_attn_out: torch.Tensor | None = None, |
| 36 | ): |
| 37 | g = chunk_local_cumsum( |
| 38 | g, chunk_size=FLA_CHUNK_SIZE, cu_seqlens=cu_seqlens, chunk_indices=chunk_indices |
| 39 | ) |
| 40 | # obtain WY representation. u is actually the new v. |
| 41 | A = chunk_scaled_dot_kkt_fwd( |
| 42 | k=k, |
| 43 | beta=beta, |
| 44 | g=g, |
| 45 | cu_seqlens=cu_seqlens, |
| 46 | chunk_indices=chunk_indices, |
| 47 | output_dtype=torch.float32, |
| 48 | ) |
| 49 | A = solve_tril( |
| 50 | A=A, cu_seqlens=cu_seqlens, chunk_indices=chunk_indices, output_dtype=k.dtype |
| 51 | ) |
| 52 | w, u = recompute_w_u_fwd( |
| 53 | k=k, |
| 54 | v=v, |
| 55 | beta=beta, |
| 56 | A=A, |
| 57 | g_cumsum=g, |
| 58 | cu_seqlens=cu_seqlens, |
| 59 | chunk_indices=chunk_indices, |
| 60 | ) |
| 61 | h, v_new, final_state = chunk_gated_delta_rule_fwd_h( |
| 62 | k=k, |
| 63 | w=w, |
| 64 | u=u, |
| 65 | g=g, |
| 66 | initial_state=initial_state, |
| 67 | output_final_state=output_final_state, |
| 68 | cu_seqlens=cu_seqlens, |
| 69 | chunk_indices=chunk_indices, |
| 70 | chunk_offsets=chunk_offsets, |
| 71 | ) |
| 72 | o = chunk_fwd_o( |
| 73 | q=q, |
| 74 | k=k, |
| 75 | v=v_new, |
| 76 | h=h, |
| 77 | g=g, |
| 78 | scale=scale, |
| 79 | cu_seqlens=cu_seqlens, |
| 80 | chunk_indices=chunk_indices, |
| 81 | core_attn_out=core_attn_out, |
| 82 | ) |
| 83 | if SUPPRESS_LEVEL < 3: |
| 84 | return g, o, A, final_state, None, None, None |
| 85 | elif SUPPRESS_LEVEL >= 3: |
| 86 | return g, o, A, final_state, w, h, v_new |
| 87 | |
| 88 | |
| 89 | class ChunkGatedDeltaRuleFunction(torch.autograd.Function): |
| 90 | @staticmethod |
| 91 | @input_guard |
| 92 | @torch.amp.custom_fwd(device_type="cuda") |
| 93 | def forward( |
| 94 | ctx, |
| 95 | q: torch.Tensor, |
| 96 | k: torch.Tensor, |
| 97 | v: torch.Tensor, |
| 98 | g: torch.Tensor, |
| 99 | beta: torch.Tensor, |
| 100 | scale: float, |
| 101 | initial_state: torch.Tensor, |
| 102 | output_final_state: bool, |
| 103 | cu_seqlens: torch.Tensor | None = None, |
| 104 | chunk_indices: torch.Tensor | None = None, |
| 105 | chunk_offsets: torch.Tensor | None = None, |
| 106 | use_qk_l2norm_in_kernel: bool = False, |
| 107 | core_attn_out: torch.Tensor | None = None, |
| 108 | ): |
| 109 | if use_qk_l2norm_in_kernel: |
| 110 | q = l2norm_fwd(q) |
| 111 | k = l2norm_fwd(k) |
| 112 | |
| 113 | g, o, A, final_state, w, h, v_new = chunk_gated_delta_rule_fwd( |
| 114 | q=q, |
| 115 | k=k, |
| 116 | v=v, |
| 117 | g=g, |
| 118 | beta=beta, |
| 119 | scale=scale, |
| 120 | initial_state=initial_state, |
| 121 | output_final_state=output_final_state, |
| 122 | cu_seqlens=cu_seqlens, |
| 123 | chunk_indices=chunk_indices, |
| 124 | chunk_offsets=chunk_offsets, |
| 125 | core_attn_out=core_attn_out, |
| 126 | ) |
| 127 | ctx.scale = scale |
| 128 | ctx.use_qk_l2norm_in_kernel = use_qk_l2norm_in_kernel |
| 129 | if core_attn_out is not None: |
| 130 | assert not torch.is_grad_enabled(), ( |
| 131 | "core_attn_out buffer reuse is only supported for inference" |
| 132 | ) |
| 133 | assert q.dtype == o.dtype, "Incompatible dtype for inplace computation" |
| 134 | return o.to(q.dtype), final_state |
| 135 | |
| 136 | |
| 137 | @torch.compiler.disable |
| 138 | def chunk_gated_delta_rule( |
| 139 | q: torch.Tensor, |
| 140 | k: torch.Tensor, |
| 141 | v: torch.Tensor, |
| 142 | g: torch.Tensor, |
| 143 | beta: torch.Tensor, |
| 144 | scale: float = None, |
| 145 | initial_state: torch.Tensor = None, |
| 146 | output_final_state: bool = False, |
| 147 | cu_seqlens: torch.Tensor | None = None, |
| 148 | chunk_indices: torch.Tensor | None = None, |
| 149 | chunk_offsets: torch.Tensor | None = None, |
| 150 | use_qk_l2norm_in_kernel: bool = False, |
| 151 | core_attn_out: torch.Tensor | None = None, |
| 152 | ): |
| 153 | r""" |
| 154 | Args: |
| 155 | q (torch.Tensor): |
| 156 | Queries of shape `[B, T, H, K]`. |
| 157 | k (torch.Tensor): |
| 158 | Keys of shape `[B, T, H, K]`. |
| 159 | v (torch.Tensor): |
| 160 | Values of shape `[B, T, H, V]`. |
| 161 | g (torch.Tensor): |
| 162 | (forget) Gating tensor (in log space!) of shape `[B, T, H]`. |
| 163 | beta (torch.Tensor): |
| 164 | Betas of shape `[B, T, H]`. |
| 165 | scale (Optional[int]): |
| 166 | Scale factor for the RetNet attention scores. |
| 167 | If not provided, it will default to `1 / sqrt(K)`. Default: `None`. |
| 168 | initial_state (Optional[torch.Tensor]): |
| 169 | Initial state of shape `[N, H, V, K]` for `N` input sequences. |
| 170 | For equal-length input sequences, `N` equals the batch size `B`. |
| 171 | Default: `None`. |
| 172 | output_final_state (Optional[bool]): |
| 173 | Whether to output the final state of shape `[N, H, V, K]`. Default: `False`. |
| 174 | cu_seqlens (torch.Tensor): |
| 175 | Cumulative sequence lengths of shape `[N+1]` used for variable-length training, |
| 176 | consistent with the FlashAttention API. |
| 177 | Returns: |
| 178 | o (torch.Tensor): |
| 179 | Outputs of shape `[B, T, H, V]`. |
| 180 | final_state (torch.Tensor): |
| 181 | Final state of shape `[N, H, V, K]` if `output_final_state=True` else `None`. |
| 182 | |
| 183 | Examples:: |
| 184 | >>> import torch |
| 185 | >>> import torch.nn.functional as F |
| 186 | >>> from einops import rearrange |
| 187 | >>> from fla.ops.gated_delta_rule import chunk_gated_delta_rule |
| 188 | # inputs with equal lengths |
| 189 | >>> B, T, H, K, V = 4, 2048, 4, 512, 512 |
| 190 | >>> q = torch.randn(B, T, H, K, dtype=torch.bfloat16, device='cuda') |
| 191 | >>> k = F.normalize(torch.randn(B, T, H, K, dtype=torch.bfloat16, device='cuda'), p=2, dim=-1) |
| 192 | >>> v = torch.randn(B, T, H, V, dtype=torch.bfloat16, device='cuda') |
| 193 | >>> beta = torch.rand(B, T, H, dtype=torch.bfloat16, device='cuda').sigmoid() |
| 194 | >>> g = F.logsigmoid(torch.rand(B, T, H, dtype=torch.bfloat16, device='cuda')) |
| 195 | >>> h0 = torch.randn(B, H, V, K, dtype=torch.bfloat16, device='cuda') |
| 196 | >>> o, ht = chunk_gated_delta_rule( |
| 197 | q, k, v, g, beta, |
| 198 | initial_state=h0, |
| 199 | output_final_state=True |
| 200 | ) |
| 201 | # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required |
| 202 | >>> q, k, v, beta, g = map(lambda x: rearrange(x, 'b t ... -> 1 (b t) ...'), (q, k, v, beta, g)) |
| 203 | # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected |
| 204 | >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.int32) |
| 205 | >>> o_var, ht_var = chunk_gated_delta_rule( |
| 206 | q, k, v, g, beta, |
| 207 | initial_state=h0, |
| 208 | output_final_state=True, |
| 209 | cu_seqlens=cu_seqlens |
| 210 | ) |
| 211 | """ |
| 212 | assert q.dtype == k.dtype == v.dtype |
| 213 | assert q.dtype != torch.float32, ( |
| 214 | "ChunkGatedDeltaRuleFunction does not support float32. Please use bfloat16." |
| 215 | ) |
| 216 | assert len(beta.shape) == 3, "beta must be of shape [B, T, H]." |
| 217 | if cu_seqlens is not None: |
| 218 | if q.shape[0] != 1: |
| 219 | raise ValueError( |
| 220 | f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." |
| 221 | f"Please flatten variable-length inputs before processing." |
| 222 | ) |
| 223 | if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: |
| 224 | raise ValueError( |
| 225 | f"The number of initial states is expected to be equal to the number of input sequences, " |
| 226 | f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}." |
| 227 | ) |
| 228 | if scale is None: |
| 229 | scale = k.shape[-1] ** -0.5 |
| 230 | o, final_state = ChunkGatedDeltaRuleFunction.apply( |
| 231 | q, |
| 232 | k, |
| 233 | v, |
| 234 | g, |
| 235 | beta, |
| 236 | scale, |
| 237 | initial_state, |
| 238 | output_final_state, |
| 239 | cu_seqlens, |
| 240 | chunk_indices, |
| 241 | chunk_offsets, |
| 242 | use_qk_l2norm_in_kernel, |
| 243 | core_attn_out, |
| 244 | ) |
| 245 | return o, final_state |