| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | # SPDX-FileCopyrightText: Songlin Yang, Yu Zhang |
| 4 | # mypy: ignore-errors |
| 5 | # |
| 6 | # This file contains code copied from the flash-linear-attention project. |
| 7 | # The original source code was licensed under the MIT license and included |
| 8 | # the following copyright notice: |
| 9 | # Copyright (c) 2023-2025, Songlin Yang, Yu Zhang |
| 10 | # ruff: noqa: E501 |
| 11 | |
| 12 | import torch |
| 13 | |
| 14 | from vllm.third_party.flash_linear_attention.ops.op import exp |
| 15 | from vllm.triton_utils import tl, triton |
| 16 | |
| 17 | |
| 18 | def token_stride(x: torch.Tensor) -> int: |
| 19 | """Token stride (elements) of a ``[B, T, H, D]`` or ``[B, T, H]`` tensor. |
| 20 | |
| 21 | The recurrent kernel walks tokens with this stride and addresses heads |
| 22 | densely inside a token, so each token's ``[H, D]`` (or ``[H]``) block must |
| 23 | be contiguous, tokens must not overlap, and with ``B > 1`` sequence ``n`` |
| 24 | must start at token ``n * T`` (dense batch). Column slices of a wider |
| 25 | per-token projection buffer satisfy this and are consumed in place. |
| 26 | """ |
| 27 | st = x.stride() |
| 28 | assert x.dim() in (3, 4) and st[-1] == 1, (x.shape, st) |
| 29 | assert x.dim() == 3 or st[2] == x.shape[3], (x.shape, st) |
| 30 | assert st[1] >= x.shape[2] * (x.shape[3] if x.dim() == 4 else 1), (x.shape, st) |
| 31 | assert x.shape[0] == 1 or st[0] == x.shape[1] * st[1], (x.shape, st) |
| 32 | return st[1] |
| 33 | |
| 34 | |
| 35 | @triton.heuristics( |
| 36 | { |
| 37 | "USE_INITIAL_STATE": lambda args: args["h0"] is not None, |
| 38 | "IS_VARLEN": lambda args: args["cu_seqlens"] is not None, |
| 39 | "IS_CONTINUOUS_BATCHING": lambda args: args["ssm_state_indices"] is not None, |
| 40 | "IS_SPEC_DECODING": lambda args: args["num_accepted_tokens"] is not None, |
| 41 | } |
| 42 | ) |
| 43 | @triton.jit(do_not_specialize=["N", "T"]) |
| 44 | def fused_recurrent_gated_delta_rule_fwd_kernel( |
| 45 | q, |
| 46 | k, |
| 47 | v, |
| 48 | g, |
| 49 | beta, |
| 50 | o, |
| 51 | h0, |
| 52 | ht, |
| 53 | cu_seqlens, |
| 54 | ssm_state_indices, |
| 55 | num_accepted_tokens, |
| 56 | a_log, |
| 57 | g_bias, |
| 58 | scale, |
| 59 | N: tl.int64, # num of sequences |
| 60 | T: tl.int64, # num of tokens |
| 61 | B: tl.constexpr, |
| 62 | H: tl.constexpr, |
| 63 | HV: tl.constexpr, |
| 64 | K: tl.constexpr, |
| 65 | V: tl.constexpr, |
| 66 | BK: tl.constexpr, |
| 67 | BV: tl.constexpr, |
| 68 | stride_init_state_token: tl.constexpr, |
| 69 | stride_final_state_token: tl.constexpr, |
| 70 | stride_indices_seq: tl.constexpr, |
| 71 | stride_indices_tok: tl.constexpr, |
| 72 | # Token strides of q/k/v/beta (elements), see `token_stride`. |
| 73 | stride_q_t, |
| 74 | stride_k_t, |
| 75 | stride_v_t, |
| 76 | stride_beta_t, |
| 77 | USE_INITIAL_STATE: tl.constexpr, # whether to use initial state |
| 78 | INPLACE_FINAL_STATE: tl.constexpr, # whether to store final state inplace |
| 79 | IS_BETA_HEADWISE: tl.constexpr, # whether beta is headwise vector or scalar, |
| 80 | USE_QK_L2NORM_IN_KERNEL: tl.constexpr, |
| 81 | IS_VARLEN: tl.constexpr, |
| 82 | IS_CONTINUOUS_BATCHING: tl.constexpr, |
| 83 | IS_SPEC_DECODING: tl.constexpr, |
| 84 | IS_KDA: tl.constexpr, |
| 85 | SIGMOID_BETA: tl.constexpr, # beta holds raw logits; sigmoid at fp32 load |
| 86 | COMPUTE_GATE: tl.constexpr, # g holds raw logits; KDA gate computed in-kernel |
| 87 | SAFE_GATE: tl.constexpr, # bounded gate variant (only branch implemented) |
| 88 | LOWER_BOUND: tl.constexpr, |
| 89 | ): |
| 90 | i_k, i_v, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2) |
| 91 | i_n, i_hv = i_nh // HV, i_nh % HV |
| 92 | i_h = i_hv // (HV // H) |
| 93 | if IS_VARLEN: |
| 94 | bos, eos = ( |
| 95 | tl.load(cu_seqlens + i_n).to(tl.int64), |
| 96 | tl.load(cu_seqlens + i_n + 1).to(tl.int64), |
| 97 | ) |
| 98 | all = T |
| 99 | T = eos - bos |
| 100 | else: |
| 101 | bos, eos = i_n * T, i_n * T + T |
| 102 | all = B * T |
| 103 | |
| 104 | if T == 0: |
| 105 | # no tokens to process for this sequence |
| 106 | return |
| 107 | |
| 108 | o_k = i_k * BK + tl.arange(0, BK) |
| 109 | o_v = i_v * BV + tl.arange(0, BV) |
| 110 | |
| 111 | p_q = q + bos * stride_q_t + i_h * K + o_k |
| 112 | p_k = k + bos * stride_k_t + i_h * K + o_k |
| 113 | p_v = v + bos * stride_v_t + i_hv * V + o_v |
| 114 | if IS_BETA_HEADWISE: |
| 115 | p_beta = beta + bos * stride_beta_t + i_hv * V + o_v |
| 116 | else: |
| 117 | p_beta = beta + bos * stride_beta_t + i_hv |
| 118 | |
| 119 | if not IS_KDA: |
| 120 | p_g = g + bos * HV + i_hv |
| 121 | else: |
| 122 | p_gk = g + (bos * HV + i_hv) * K + o_k |
| 123 | |
| 124 | # Per-head gate amplitude, hoisted out of the token loop (COMPUTE_GATE). |
| 125 | if COMPUTE_GATE: |
| 126 | b_a_log = tl.exp(tl.load(a_log + i_h).to(tl.float32)) |
| 127 | |
| 128 | p_o = o + ((i_k * all + bos) * HV + i_hv) * V + o_v |
| 129 | |
| 130 | mask_k = o_k < K |
| 131 | mask_v = o_v < V |
| 132 | mask_h = mask_v[:, None] & mask_k[None, :] |
| 133 | |
| 134 | b_h = tl.zeros([BV, BK], dtype=tl.float32) |
| 135 | if USE_INITIAL_STATE: |
| 136 | if IS_CONTINUOUS_BATCHING: |
| 137 | if IS_SPEC_DECODING: |
| 138 | i_t = tl.load(num_accepted_tokens + i_n).to(tl.int64) - 1 |
| 139 | else: |
| 140 | i_t = 0 |
| 141 | # Load state index and check for invalid entries |
| 142 | state_idx = tl.load(ssm_state_indices + i_n * stride_indices_seq + i_t).to( |
| 143 | tl.int64 |
| 144 | ) |
| 145 | # Skip if state index is invalid (NULL_BLOCK_ID=0) |
| 146 | if state_idx <= 0: |
| 147 | return |
| 148 | p_h0 = h0 + state_idx * stride_init_state_token |
| 149 | else: |
| 150 | p_h0 = h0 + bos * HV * V * K |
| 151 | p_h0 = p_h0 + i_hv * V * K + o_v[:, None] * K + o_k[None, :] |
| 152 | b_h += tl.load(p_h0, mask=mask_h, other=0).to(tl.float32) |
| 153 | |
| 154 | for i_t in range(0, T): |
| 155 | b_q = tl.load(p_q, mask=mask_k, other=0).to(tl.float32) |
| 156 | b_k = tl.load(p_k, mask=mask_k, other=0).to(tl.float32) |
| 157 | b_v = tl.load(p_v, mask=mask_v, other=0).to(tl.float32) |
| 158 | |
| 159 | if USE_QK_L2NORM_IN_KERNEL: |
| 160 | b_q = b_q / tl.sqrt(tl.sum(b_q * b_q) + 1e-6) |
| 161 | b_k = b_k / tl.sqrt(tl.sum(b_k * b_k) + 1e-6) |
| 162 | b_q = b_q * scale |
| 163 | # [BV, BK] |
| 164 | if not IS_KDA: |
| 165 | b_g = tl.load(p_g).to(tl.float32) |
| 166 | b_h *= exp(b_g) |
| 167 | else: |
| 168 | b_gk = tl.load(p_gk).to(tl.float32) |
| 169 | if COMPUTE_GATE: |
| 170 | # Replicates kda_gate_fwd_kernel's SAFE_GATE branch |
| 171 | # bit-for-bit (same tl.exp, same fp32 math; the intermediate |
| 172 | # gate value this replaces was stored/reloaded as fp32, |
| 173 | # which is lossless): y = lb / (1 + exp(-exp(A)*(g+bias))). |
| 174 | b_gk += tl.load(g_bias + i_h * K + o_k, mask=mask_k, other=0.0).to( |
| 175 | tl.float32 |
| 176 | ) |
| 177 | b_gk = LOWER_BOUND / (1.0 + tl.exp(-(b_a_log * b_gk))) |
| 178 | b_h *= exp(b_gk[None, :]) |
| 179 | # [BV] |
| 180 | b_v -= tl.sum(b_h * b_k[None, :], 1) |
| 181 | if IS_BETA_HEADWISE: |
| 182 | b_beta = tl.load(p_beta, mask=mask_v, other=0).to(tl.float32) |
| 183 | else: |
| 184 | b_beta = tl.load(p_beta).to(tl.float32) |
| 185 | # Matches torch's `x.float().sigmoid()` pre-computation bit-for-bit |
| 186 | # on the input side (bf16->fp32 is exact); only the sigmoid impl itself |
| 187 | # can differ by <=1 ULP. |
| 188 | if SIGMOID_BETA: |
| 189 | b_beta = tl.sigmoid(b_beta) |
| 190 | b_v *= b_beta |
| 191 | # [BV, BK] |
| 192 | b_h += b_v[:, None] * b_k[None, :] |
| 193 | # [BV] |
| 194 | b_o = tl.sum(b_h * b_q[None, :], 1) |
| 195 | tl.store(p_o, b_o.to(p_o.dtype.element_ty), mask=mask_v) |
| 196 | |
| 197 | # keep the states for multi-query tokens |
| 198 | if INPLACE_FINAL_STATE: |
| 199 | # Load state index and check for invalid entries |
| 200 | final_state_idx = tl.load( |
| 201 | ssm_state_indices + i_n * stride_indices_seq + i_t |
| 202 | ).to(tl.int64) |
| 203 | # Only store if state index is valid (not NULL_BLOCK_ID=0) |
| 204 | if final_state_idx > 0: |
| 205 | p_ht = ht + final_state_idx * stride_final_state_token |
| 206 | p_ht = p_ht + i_hv * V * K + o_v[:, None] * K + o_k[None, :] |
| 207 | tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), mask=mask_h) |
| 208 | else: |
| 209 | p_ht = ht + (bos + i_t) * stride_final_state_token |
| 210 | p_ht = p_ht + i_hv * V * K + o_v[:, None] * K + o_k[None, :] |
| 211 | tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), mask=mask_h) |
| 212 | |
| 213 | p_q += stride_q_t |
| 214 | p_k += stride_k_t |
| 215 | p_o += HV * V |
| 216 | p_v += stride_v_t |
| 217 | if not IS_KDA: |
| 218 | p_g += HV |
| 219 | else: |
| 220 | p_gk += HV * K |
| 221 | p_beta += stride_beta_t |
| 222 | |
| 223 | |
| 224 | def fused_recurrent_gated_delta_rule_fwd( |
| 225 | q: torch.Tensor, |
| 226 | k: torch.Tensor, |
| 227 | v: torch.Tensor, |
| 228 | g: torch.Tensor, |
| 229 | beta: torch.Tensor, |
| 230 | scale: float, |
| 231 | initial_state: torch.Tensor, |
| 232 | inplace_final_state: bool = True, |
| 233 | cu_seqlens: torch.Tensor | None = None, |
| 234 | ssm_state_indices: torch.Tensor | None = None, |
| 235 | num_accepted_tokens: torch.Tensor | None = None, |
| 236 | use_qk_l2norm_in_kernel: bool = False, |
| 237 | ) -> tuple[torch.Tensor, torch.Tensor]: |
| 238 | B, T, H, K, V = *k.shape, v.shape[-1] |
| 239 | HV = v.shape[2] |
| 240 | N = B if cu_seqlens is None else len(cu_seqlens) - 1 |
| 241 | BK, BV = triton.next_power_of_2(K), min(triton.next_power_of_2(V), 32) |
| 242 | NK, NV = triton.cdiv(K, BK), triton.cdiv(V, BV) |
| 243 | assert NK == 1, "NK > 1 is not supported yet" |
| 244 | num_stages = 3 |
| 245 | num_warps = 1 |
| 246 | |
| 247 | o = q.new_empty(NK, *v.shape) |
| 248 | if inplace_final_state: |
| 249 | final_state = initial_state |
| 250 | else: |
| 251 | final_state = q.new_empty(T, HV, V, K, dtype=initial_state.dtype) |
| 252 | |
| 253 | stride_init_state_token = initial_state.stride(0) |
| 254 | stride_final_state_token = final_state.stride(0) |
| 255 | |
| 256 | if ssm_state_indices is None: |
| 257 | stride_indices_seq, stride_indices_tok = 1, 1 |
| 258 | elif ssm_state_indices.ndim == 1: |
| 259 | stride_indices_seq, stride_indices_tok = ssm_state_indices.stride(0), 1 |
| 260 | else: |
| 261 | stride_indices_seq, stride_indices_tok = ssm_state_indices.stride() |
| 262 | |
| 263 | grid = (NK, NV, N * HV) |
| 264 | fused_recurrent_gated_delta_rule_fwd_kernel[grid]( |
| 265 | q=q, |
| 266 | k=k, |
| 267 | v=v, |
| 268 | g=g, |
| 269 | beta=beta, |
| 270 | o=o, |
| 271 | h0=initial_state, |
| 272 | ht=final_state, |
| 273 | cu_seqlens=cu_seqlens, |
| 274 | ssm_state_indices=ssm_state_indices, |
| 275 | num_accepted_tokens=num_accepted_tokens, |
| 276 | scale=scale, |
| 277 | N=N, |
| 278 | T=T, |
| 279 | B=B, |
| 280 | H=H, |
| 281 | HV=HV, |
| 282 | K=K, |
| 283 | V=V, |
| 284 | BK=BK, |
| 285 | BV=BV, |
| 286 | stride_init_state_token=stride_init_state_token, |
| 287 | stride_final_state_token=stride_final_state_token, |
| 288 | stride_indices_seq=stride_indices_seq, |
| 289 | stride_indices_tok=stride_indices_tok, |
| 290 | stride_q_t=token_stride(q), |
| 291 | stride_k_t=token_stride(k), |
| 292 | stride_v_t=token_stride(v), |
| 293 | stride_beta_t=token_stride(beta), |
| 294 | IS_BETA_HEADWISE=beta.ndim == v.ndim, |
| 295 | USE_QK_L2NORM_IN_KERNEL=use_qk_l2norm_in_kernel, |
| 296 | INPLACE_FINAL_STATE=inplace_final_state, |
| 297 | IS_KDA=False, |
| 298 | SIGMOID_BETA=False, |
| 299 | a_log=None, |
| 300 | g_bias=None, |
| 301 | COMPUTE_GATE=False, |
| 302 | SAFE_GATE=True, |
| 303 | LOWER_BOUND=-5.0, |
| 304 | num_warps=num_warps, |
| 305 | num_stages=num_stages, |
| 306 | ) |
| 307 | o = o.squeeze(0) |
| 308 | return o, final_state |
| 309 | |
| 310 | |
| 311 | @triton.jit |
| 312 | def fused_recurrent_gated_delta_rule_packed_decode_kernel( |
| 313 | mixed_qkv, |
| 314 | a, |
| 315 | b, |
| 316 | A_log, |
| 317 | dt_bias, |
| 318 | o, |
| 319 | h0, |
| 320 | ht, |
| 321 | ssm_state_indices, |
| 322 | scale, |
| 323 | stride_mixed_qkv_tok: tl.constexpr, |
| 324 | stride_a_tok: tl.constexpr, |
| 325 | stride_b_tok: tl.constexpr, |
| 326 | stride_init_state_token: tl.constexpr, |
| 327 | stride_final_state_token: tl.constexpr, |
| 328 | stride_indices_seq: tl.constexpr, |
| 329 | H: tl.constexpr, |
| 330 | HV: tl.constexpr, |
| 331 | K: tl.constexpr, |
| 332 | V: tl.constexpr, |
| 333 | BK: tl.constexpr, |
| 334 | BV: tl.constexpr, |
| 335 | SOFTPLUS_THRESHOLD: tl.constexpr, |
| 336 | USE_QK_L2NORM_IN_KERNEL: tl.constexpr, |
| 337 | SPLIT_BATCH_HEAD_GRID: tl.constexpr, |
| 338 | ): |
| 339 | if SPLIT_BATCH_HEAD_GRID: |
| 340 | i_v, i_hv, i_n = tl.program_id(0), tl.program_id(1), tl.program_id(2) |
| 341 | else: |
| 342 | i_v, i_nh = tl.program_id(0), tl.program_id(1) |
| 343 | i_n, i_hv = i_nh // HV, i_nh % HV |
| 344 | i_h = i_hv // (HV // H) |
| 345 | |
| 346 | o_k = tl.arange(0, BK) |
| 347 | o_v = i_v * BV + tl.arange(0, BV) |
| 348 | mask_k = o_k < K |
| 349 | mask_v = o_v < V |
| 350 | mask_h = mask_v[:, None] & mask_k[None, :] |
| 351 | |
| 352 | state_idx = tl.load(ssm_state_indices + i_n * stride_indices_seq).to(tl.int64) |
| 353 | p_o = o + (i_n * HV + i_hv) * V + o_v |
| 354 | |
| 355 | # Skip if state index is invalid (NULL_BLOCK_ID=0) |
| 356 | if state_idx <= 0: |
| 357 | zero = tl.zeros([BV], dtype=tl.float32).to(p_o.dtype.element_ty) |
| 358 | tl.store(p_o, zero, mask=mask_v) |
| 359 | return |
| 360 | |
| 361 | p_h0 = h0 + state_idx * stride_init_state_token |
| 362 | p_h0 = p_h0 + i_hv * V * K + o_v[:, None] * K + o_k[None, :] |
| 363 | b_h = tl.load(p_h0, mask=mask_h, other=0).to(tl.float32) |
| 364 | |
| 365 | p_mixed = mixed_qkv + i_n * stride_mixed_qkv_tok |
| 366 | q_off = i_h * K + o_k |
| 367 | k_off = (H * K) + i_h * K + o_k |
| 368 | v_off = (2 * H * K) + i_hv * V + o_v |
| 369 | b_q = tl.load(p_mixed + q_off, mask=mask_k, other=0).to(tl.float32) |
| 370 | b_k = tl.load(p_mixed + k_off, mask=mask_k, other=0).to(tl.float32) |
| 371 | b_v = tl.load(p_mixed + v_off, mask=mask_v, other=0).to(tl.float32) |
| 372 | |
| 373 | if USE_QK_L2NORM_IN_KERNEL: |
| 374 | b_q = b_q / tl.sqrt(tl.sum(b_q * b_q) + 1e-6) |
| 375 | b_k = b_k / tl.sqrt(tl.sum(b_k * b_k) + 1e-6) |
| 376 | b_q = b_q * scale |
| 377 | |
| 378 | a_val = tl.load(a + i_n * stride_a_tok + i_hv).to(tl.float32) |
| 379 | b_val = tl.load(b + i_n * stride_b_tok + i_hv).to(tl.float32) |
| 380 | A_log_val = tl.load(A_log + i_hv).to(tl.float32) |
| 381 | dt_bias_val = tl.load(dt_bias + i_hv).to(tl.float32) |
| 382 | x = a_val + dt_bias_val |
| 383 | softplus_x = tl.where(x <= SOFTPLUS_THRESHOLD, tl.log(1.0 + tl.exp(x)), x) |
| 384 | g_val = -tl.exp(A_log_val) * softplus_x |
| 385 | beta_val = tl.sigmoid(b_val) |
| 386 | |
| 387 | b_h *= exp(g_val) |
| 388 | b_v -= tl.sum(b_h * b_k[None, :], 1) |
| 389 | b_v *= beta_val |
| 390 | b_h += b_v[:, None] * b_k[None, :] |
| 391 | b_o = tl.sum(b_h * b_q[None, :], 1) |
| 392 | tl.store(p_o, b_o.to(p_o.dtype.element_ty), mask=mask_v) |
| 393 | |
| 394 | p_ht = ht + state_idx * stride_final_state_token |
| 395 | p_ht = p_ht + i_hv * V * K + o_v[:, None] * K + o_k[None, :] |
| 396 | tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), mask=mask_h) |
| 397 | |
| 398 | |
| 399 | def fused_recurrent_gated_delta_rule_packed_decode( |
| 400 | mixed_qkv: torch.Tensor, |
| 401 | a: torch.Tensor, |
| 402 | b: torch.Tensor, |
| 403 | A_log: torch.Tensor, |
| 404 | dt_bias: torch.Tensor, |
| 405 | scale: float, |
| 406 | initial_state: torch.Tensor, |
| 407 | out: torch.Tensor, |
| 408 | ssm_state_indices: torch.Tensor, |
| 409 | use_qk_l2norm_in_kernel: bool = False, |
| 410 | ) -> tuple[torch.Tensor, torch.Tensor]: |
| 411 | if mixed_qkv.ndim != 2: |
| 412 | raise ValueError( |
| 413 | f"`mixed_qkv` must be a 2D tensor (got ndim={mixed_qkv.ndim})." |
| 414 | ) |
| 415 | if mixed_qkv.stride(-1) != 1: |
| 416 | raise ValueError("`mixed_qkv` must be contiguous in the last dim.") |
| 417 | if a.ndim != 2 or b.ndim != 2: |
| 418 | raise ValueError( |
| 419 | f"`a` and `b` must be 2D tensors (got a.ndim={a.ndim}, b.ndim={b.ndim})." |
| 420 | ) |
| 421 | if a.stride(-1) != 1 or b.stride(-1) != 1: |
| 422 | raise ValueError("`a`/`b` must be contiguous in the last dim.") |
| 423 | if A_log.ndim != 1 or dt_bias.ndim != 1: |
| 424 | raise ValueError("`A_log`/`dt_bias` must be 1D tensors.") |
| 425 | if A_log.stride(0) != 1 or dt_bias.stride(0) != 1: |
| 426 | raise ValueError("`A_log`/`dt_bias` must be contiguous.") |
| 427 | if ssm_state_indices.ndim != 1: |
| 428 | raise ValueError( |
| 429 | f"`ssm_state_indices` must be 1D for packed decode (got ndim={ssm_state_indices.ndim})." |
| 430 | ) |
| 431 | if not out.is_contiguous(): |
| 432 | raise ValueError("`out` must be contiguous.") |
| 433 | |
| 434 | dev = mixed_qkv.device |
| 435 | if ( |
| 436 | a.device != dev |
| 437 | or b.device != dev |
| 438 | or A_log.device != dev |
| 439 | or dt_bias.device != dev |
| 440 | or initial_state.device != dev |
| 441 | or out.device != dev |
| 442 | or ssm_state_indices.device != dev |
| 443 | ): |
| 444 | raise ValueError("All inputs must be on the same device.") |
| 445 | |
| 446 | B = mixed_qkv.shape[0] |
| 447 | if a.shape[0] != B or b.shape[0] != B: |
| 448 | raise ValueError( |
| 449 | "Mismatched batch sizes: " |
| 450 | f"mixed_qkv.shape[0]={B}, a.shape[0]={a.shape[0]}, b.shape[0]={b.shape[0]}." |
| 451 | ) |
| 452 | if ssm_state_indices.shape[0] != B: |
| 453 | raise ValueError( |
| 454 | f"`ssm_state_indices` must have shape [B] (got {tuple(ssm_state_indices.shape)}; expected ({B},))." |
| 455 | ) |
| 456 | |
| 457 | if initial_state.ndim != 4: |
| 458 | raise ValueError( |
| 459 | f"`initial_state` must be a 4D tensor (got ndim={initial_state.ndim})." |
| 460 | ) |
| 461 | if initial_state.stride(-1) != 1: |
| 462 | raise ValueError("`initial_state` must be contiguous in the last dim.") |
| 463 | HV, V, K = initial_state.shape[-3:] |
| 464 | if a.shape[1] != HV or b.shape[1] != HV: |
| 465 | raise ValueError( |
| 466 | f"`a`/`b` must have shape [B, HV] with HV={HV} (got a.shape={tuple(a.shape)}, b.shape={tuple(b.shape)})." |
| 467 | ) |
| 468 | if A_log.numel() != HV or dt_bias.numel() != HV: |
| 469 | raise ValueError( |
| 470 | f"`A_log` and `dt_bias` must have {HV} elements (got A_log.numel()={A_log.numel()}, dt_bias.numel()={dt_bias.numel()})." |
| 471 | ) |
| 472 | if out.shape != (B, 1, HV, V): |
| 473 | raise ValueError( |
| 474 | f"`out` must have shape {(B, 1, HV, V)} (got out.shape={tuple(out.shape)})." |
| 475 | ) |
| 476 | |
| 477 | qkv_dim = mixed_qkv.shape[1] |
| 478 | qk_dim = qkv_dim - HV * V |
| 479 | if qk_dim <= 0 or qk_dim % 2 != 0: |
| 480 | raise ValueError( |
| 481 | f"Invalid packed `mixed_qkv` last dim={qkv_dim} for HV={HV}, V={V}." |
| 482 | ) |
| 483 | q_dim = qk_dim // 2 |
| 484 | if q_dim % K != 0: |
| 485 | raise ValueError(f"Invalid packed Q size {q_dim}: must be divisible by K={K}.") |
| 486 | H = q_dim // K |
| 487 | if H <= 0 or HV % H != 0: |
| 488 | raise ValueError( |
| 489 | f"Invalid head config inferred from mixed_qkv: H={H}, HV={HV}." |
| 490 | ) |
| 491 | |
| 492 | BK = triton.next_power_of_2(K) |
| 493 | if triton.cdiv(K, BK) != 1: |
| 494 | raise ValueError( |
| 495 | f"Packed decode kernel only supports NK=1 (got K={K}, BK={BK})." |
| 496 | ) |
| 497 | BV = min(triton.next_power_of_2(V), 32) |
| 498 | num_stages = 3 |
| 499 | num_warps = 1 |
| 500 | |
| 501 | stride_mixed_qkv_tok = mixed_qkv.stride(0) |
| 502 | stride_a_tok = a.stride(0) |
| 503 | stride_b_tok = b.stride(0) |
| 504 | stride_init_state_token = initial_state.stride(0) |
| 505 | stride_final_state_token = initial_state.stride(0) |
| 506 | stride_indices_seq = ssm_state_indices.stride(0) |
| 507 | |
| 508 | NV = triton.cdiv(V, BV) |
| 509 | # CUDA limits grid Y/Z dimensions to 65535. |
| 510 | split_batch_head_grid = B * HV > 65535 |
| 511 | grid = (NV, HV, B) if split_batch_head_grid else (NV, B * HV) |
| 512 | fused_recurrent_gated_delta_rule_packed_decode_kernel[grid]( |
| 513 | mixed_qkv=mixed_qkv, |
| 514 | a=a, |
| 515 | b=b, |
| 516 | A_log=A_log, |
| 517 | dt_bias=dt_bias, |
| 518 | o=out, |
| 519 | h0=initial_state, |
| 520 | ht=initial_state, |
| 521 | ssm_state_indices=ssm_state_indices, |
| 522 | scale=scale, |
| 523 | stride_mixed_qkv_tok=stride_mixed_qkv_tok, |
| 524 | stride_a_tok=stride_a_tok, |
| 525 | stride_b_tok=stride_b_tok, |
| 526 | stride_init_state_token=stride_init_state_token, |
| 527 | stride_final_state_token=stride_final_state_token, |
| 528 | stride_indices_seq=stride_indices_seq, |
| 529 | H=H, |
| 530 | HV=HV, |
| 531 | K=K, |
| 532 | V=V, |
| 533 | BK=BK, |
| 534 | BV=BV, |
| 535 | SOFTPLUS_THRESHOLD=20.0, |
| 536 | USE_QK_L2NORM_IN_KERNEL=use_qk_l2norm_in_kernel, |
| 537 | SPLIT_BATCH_HEAD_GRID=split_batch_head_grid, |
| 538 | num_warps=num_warps, |
| 539 | num_stages=num_stages, |
| 540 | ) |
| 541 | return out, initial_state |
| 542 | |
| 543 | |
| 544 | class FusedRecurrentFunction(torch.autograd.Function): |
| 545 | @staticmethod |
| 546 | def forward( |
| 547 | ctx, |
| 548 | q: torch.Tensor, |
| 549 | k: torch.Tensor, |
| 550 | v: torch.Tensor, |
| 551 | g: torch.Tensor, |
| 552 | beta: torch.Tensor, |
| 553 | scale: float, |
| 554 | initial_state: torch.Tensor, |
| 555 | inplace_final_state: bool = True, |
| 556 | cu_seqlens: torch.Tensor | None = None, |
| 557 | ssm_state_indices: torch.Tensor | None = None, |
| 558 | num_accepted_tokens: torch.Tensor | None = None, |
| 559 | use_qk_l2norm_in_kernel: bool = False, |
| 560 | ): |
| 561 | o, final_state = fused_recurrent_gated_delta_rule_fwd( |
| 562 | q=q.contiguous(), |
| 563 | k=k.contiguous(), |
| 564 | v=v.contiguous(), |
| 565 | g=g.contiguous(), |
| 566 | beta=beta.contiguous(), |
| 567 | scale=scale, |
| 568 | initial_state=initial_state, |
| 569 | inplace_final_state=inplace_final_state, |
| 570 | cu_seqlens=cu_seqlens, |
| 571 | ssm_state_indices=ssm_state_indices, |
| 572 | num_accepted_tokens=num_accepted_tokens, |
| 573 | use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel, |
| 574 | ) |
| 575 | |
| 576 | return o, final_state |
| 577 | |
| 578 | |
| 579 | def fused_recurrent_gated_delta_rule( |
| 580 | q: torch.Tensor, |
| 581 | k: torch.Tensor, |
| 582 | v: torch.Tensor, |
| 583 | g: torch.Tensor, |
| 584 | beta: torch.Tensor = None, |
| 585 | scale: float = None, |
| 586 | initial_state: torch.Tensor = None, |
| 587 | inplace_final_state: bool = True, |
| 588 | cu_seqlens: torch.Tensor | None = None, |
| 589 | ssm_state_indices: torch.Tensor | None = None, |
| 590 | num_accepted_tokens: torch.Tensor | None = None, |
| 591 | use_qk_l2norm_in_kernel: bool = False, |
| 592 | ) -> tuple[torch.Tensor, torch.Tensor]: |
| 593 | r"""Args: |
| 594 | q (torch.Tensor): |
| 595 | queries of shape `[B, T, H, K]`. |
| 596 | k (torch.Tensor): |
| 597 | keys of shape `[B, T, H, K]`. |
| 598 | v (torch.Tensor): |
| 599 | values of shape `[B, T, HV, V]`. |
| 600 | GVA is applied if `HV > H`. |
| 601 | g (torch.Tensor): |
| 602 | g (decays) of shape `[B, T, HV]`. |
| 603 | beta (torch.Tensor): |
| 604 | betas of shape `[B, T, HV]`. |
| 605 | scale (Optional[int]): |
| 606 | Scale factor for the RetNet attention scores. |
| 607 | If not provided, it will default to `1 / sqrt(K)`. Default: `None`. |
| 608 | initial_state (Optional[torch.Tensor]): |
| 609 | Initial state of shape `[N, HV, V, K]` for `N` input sequences. |
| 610 | For equal-length input sequences, `N` equals the batch size `B`. |
| 611 | Default: `None`. |
| 612 | inplace_final_state: bool: |
| 613 | Whether to store the final state in-place to save memory. |
| 614 | Default: `True`. |
| 615 | cu_seqlens (torch.Tensor): |
| 616 | Cumulative sequence lengths of shape `[N+1]` used for variable-length training, |
| 617 | consistent with the FlashAttention API. |
| 618 | ssm_state_indices (Optional[torch.Tensor]): |
| 619 | Indices to map the input sequences to the initial/final states. |
| 620 | num_accepted_tokens (Optional[torch.Tensor]): |
| 621 | Number of accepted tokens for each sequence during decoding. |
| 622 | |
| 623 | Returns: |
| 624 | o (torch.Tensor): |
| 625 | Outputs of shape `[B, T, HV, V]`. |
| 626 | final_state (torch.Tensor): |
| 627 | Final state of shape `[N, HV, V, K]`. |
| 628 | |
| 629 | Examples:: |
| 630 | >>> import torch |
| 631 | >>> import torch.nn.functional as F |
| 632 | >>> from einops import rearrange |
| 633 | >>> from fla.ops.gated_delta_rule import fused_recurrent_gated_delta_rule |
| 634 | # inputs with equal lengths |
| 635 | >>> B, T, H, HV, K, V = 4, 2048, 4, 8, 512, 512 |
| 636 | >>> q = torch.randn(B, T, H, K, device='cuda') |
| 637 | >>> k = F.normalize(torch.randn(B, T, H, K, device='cuda'), p=2, dim=-1) |
| 638 | >>> v = torch.randn(B, T, HV, V, device='cuda') |
| 639 | >>> g = F.logsigmoid(torch.rand(B, T, HV, device='cuda')) |
| 640 | >>> beta = torch.rand(B, T, HV, device='cuda').sigmoid() |
| 641 | >>> h0 = torch.randn(B, HV, V, K, device='cuda') |
| 642 | >>> o, ht = fused_gated_recurrent_delta_rule( |
| 643 | q, k, v, g, beta, |
| 644 | initial_state=h0, |
| 645 | ) |
| 646 | # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required |
| 647 | >>> q, k, v, g, beta = map(lambda x: rearrange(x, 'b t ... -> 1 (b t) ...'), (q, k, v, g, beta)) |
| 648 | # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected |
| 649 | >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.int32) |
| 650 | >>> o_var, ht_var = fused_gated_recurrent_delta_rule( |
| 651 | q, k, v, g, beta, |
| 652 | initial_state=h0, |
| 653 | cu_seqlens=cu_seqlens |
| 654 | ) |
| 655 | |
| 656 | """ |
| 657 | if cu_seqlens is not None and q.shape[0] != 1: |
| 658 | raise ValueError( |
| 659 | f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." |
| 660 | f"Please flatten variable-length inputs before processing." |
| 661 | ) |
| 662 | if scale is None: |
| 663 | scale = k.shape[-1] ** -0.5 |
| 664 | else: |
| 665 | assert scale > 0, "scale must be positive" |
| 666 | if beta is None: |
| 667 | beta = torch.ones_like(q[..., 0]) |
| 668 | o, final_state = FusedRecurrentFunction.apply( |
| 669 | q, |
| 670 | k, |
| 671 | v, |
| 672 | g, |
| 673 | beta, |
| 674 | scale, |
| 675 | initial_state, |
| 676 | inplace_final_state, |
| 677 | cu_seqlens, |
| 678 | ssm_state_indices, |
| 679 | num_accepted_tokens, |
| 680 | use_qk_l2norm_in_kernel, |
| 681 | ) |
| 682 | return o, final_state |