main
py 1,377 lines 38.6 KB
Raw
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
13 import torch
14
15 from vllm.third_party.flash_linear_attention.ops.chunk_delta_h import (
16 chunk_gated_delta_rule_fwd_h,
17 )
18 from vllm.third_party.flash_linear_attention.ops.cumsum import chunk_local_cumsum
19 from vllm.third_party.flash_linear_attention.ops.index import prepare_chunk_indices
20 from vllm.third_party.flash_linear_attention.ops.l2norm import l2norm_fwd
21 from vllm.third_party.flash_linear_attention.ops.op import exp2, log
22 from vllm.third_party.flash_linear_attention.ops.solve_tril import solve_tril
23 from vllm.third_party.flash_linear_attention.ops.utils import FLA_CHUNK_SIZE, is_amd
24 from vllm.triton_utils import tl, triton
25 from vllm.utils.math_utils import RCP_LN2, cdiv, next_power_of_2
26
27 from .fused_recurrent import (
28 fused_recurrent_gated_delta_rule_fwd_kernel,
29 token_stride,
30 )
31
32 BT_LIST_AUTOTUNE = [32, 64, 128]
33 NUM_WARPS_AUTOTUNE = [2, 4, 8, 16] if is_amd else [4, 8, 16, 32]
34
35
36 def fused_recurrent_kda_fwd(
37 q: torch.Tensor,
38 k: torch.Tensor,
39 v: torch.Tensor,
40 g: torch.Tensor,
41 beta: torch.Tensor,
42 scale: float,
43 initial_state: torch.Tensor,
44 inplace_final_state: bool = True,
45 cu_seqlens: torch.Tensor | None = None,
46 ssm_state_indices: torch.Tensor | None = None,
47 num_accepted_tokens: torch.Tensor | None = None,
48 use_qk_l2norm_in_kernel: bool = False,
49 out: torch.Tensor | None = None,
50 sigmoid_beta: bool = False,
51 a_log: torch.Tensor | None = None,
52 g_bias: torch.Tensor | None = None,
53 compute_gate: bool = False,
54 lower_bound: float | None = -5.0,
55 ) -> tuple[torch.Tensor, torch.Tensor]:
56 B, T, H, K, V = *k.shape, v.shape[-1]
57 HV = v.shape[2]
58 N = B if cu_seqlens is None else len(cu_seqlens) - 1
59 BK, BV = next_power_of_2(K), min(next_power_of_2(V), 8)
60 NK, NV = cdiv(K, BK), cdiv(V, BV)
61 assert NK == 1, "NK > 1 is not supported yet"
62 num_stages = 3
63 num_warps = 1
64
65 if compute_gate:
66 assert a_log is not None and g_bias is not None, (
67 "compute_gate requires a_log and g_bias"
68 )
69 assert lower_bound is not None, (
70 "compute_gate implements the bounded (safe_gate) branch only"
71 )
72 a_log = a_log.reshape(-1).contiguous()
73 g_bias = g_bias.reshape(-1).contiguous()
74
75 if out is None:
76 o = torch.empty(k.shape, dtype=k.dtype, device=k.device)
77 else:
78 # Caller-provided output buffer; must be layout-compatible with the
79 # tensor the kernel indexes (contiguous, same shape/dtype as k).
80 assert out.shape == k.shape and out.dtype == k.dtype
81 assert out.is_contiguous()
82 o = out
83 if inplace_final_state:
84 final_state = initial_state
85 else:
86 final_state = q.new_empty(T, HV, V, K, dtype=initial_state.dtype)
87
88 stride_init_state_token = initial_state.stride(0)
89 stride_final_state_token = final_state.stride(0)
90
91 if ssm_state_indices is None:
92 stride_indices_seq, stride_indices_tok = 1, 1
93 elif ssm_state_indices.ndim == 1:
94 stride_indices_seq, stride_indices_tok = ssm_state_indices.stride(0), 1
95 else:
96 stride_indices_seq, stride_indices_tok = ssm_state_indices.stride()
97
98 grid = (NK, NV, N * HV)
99 fused_recurrent_gated_delta_rule_fwd_kernel[grid](
100 q=q,
101 k=k,
102 v=v,
103 g=g,
104 beta=beta,
105 o=o,
106 h0=initial_state,
107 ht=final_state,
108 cu_seqlens=cu_seqlens,
109 ssm_state_indices=ssm_state_indices,
110 num_accepted_tokens=num_accepted_tokens,
111 scale=scale,
112 N=N,
113 T=T,
114 B=B,
115 H=H,
116 HV=HV,
117 K=K,
118 V=V,
119 BK=BK,
120 BV=BV,
121 stride_init_state_token=stride_init_state_token,
122 stride_final_state_token=stride_final_state_token,
123 stride_indices_seq=stride_indices_seq,
124 stride_indices_tok=stride_indices_tok,
125 stride_q_t=token_stride(q),
126 stride_k_t=token_stride(k),
127 stride_v_t=token_stride(v),
128 stride_beta_t=token_stride(beta),
129 IS_BETA_HEADWISE=beta.ndim == v.ndim,
130 USE_QK_L2NORM_IN_KERNEL=use_qk_l2norm_in_kernel,
131 INPLACE_FINAL_STATE=inplace_final_state,
132 IS_KDA=True,
133 SIGMOID_BETA=sigmoid_beta,
134 a_log=a_log,
135 g_bias=g_bias,
136 COMPUTE_GATE=compute_gate,
137 SAFE_GATE=True,
138 LOWER_BOUND=lower_bound if lower_bound is not None else -5.0,
139 num_warps=num_warps,
140 num_stages=num_stages,
141 )
142
143 return o, final_state
144
145
146 def fused_recurrent_kda(
147 q: torch.Tensor,
148 k: torch.Tensor,
149 v: torch.Tensor,
150 g: torch.Tensor,
151 beta: torch.Tensor = None,
152 scale: float = None,
153 initial_state: torch.Tensor = None,
154 inplace_final_state: bool = True,
155 use_qk_l2norm_in_kernel: bool = True,
156 cu_seqlens: torch.Tensor | None = None,
157 ssm_state_indices: torch.LongTensor | None = None,
158 num_accepted_tokens: torch.Tensor | None = None,
159 out: torch.Tensor | None = None,
160 sigmoid_beta: bool = False,
161 a_log: torch.Tensor | None = None,
162 g_bias: torch.Tensor | None = None,
163 compute_gate: bool = False,
164 lower_bound: float | None = -5.0,
165 **kwargs,
166 ) -> tuple[torch.Tensor, torch.Tensor]:
167 if cu_seqlens is not None and q.shape[0] != 1:
168 raise ValueError(
169 f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`."
170 f"Please flatten variable-length inputs before processing."
171 )
172 if scale is None:
173 scale = k.shape[-1] ** -0.5
174
175 # q/k/v/beta are consumed in place with an explicit token stride, so
176 # column slices of the fused projection buffer need no copy; layouts the
177 # kernel cannot address fail loudly in `token_stride`.
178 o, final_state = fused_recurrent_kda_fwd(
179 q=q,
180 k=k,
181 v=v,
182 g=g.contiguous(),
183 beta=beta,
184 scale=scale,
185 initial_state=initial_state,
186 inplace_final_state=inplace_final_state,
187 cu_seqlens=cu_seqlens,
188 ssm_state_indices=ssm_state_indices,
189 num_accepted_tokens=num_accepted_tokens,
190 use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel,
191 out=out,
192 sigmoid_beta=sigmoid_beta,
193 a_log=a_log,
194 g_bias=g_bias,
195 compute_gate=compute_gate,
196 lower_bound=lower_bound,
197 )
198 return o, final_state
199
200
201 @triton.heuristics({"IS_VARLEN": lambda args: args["cu_seqlens"] is not None})
202 @triton.autotune(
203 configs=[
204 triton.Config({"BK": BK}, num_warps=num_warps, num_stages=num_stages)
205 for BK in [32, 64]
206 for num_warps in [1, 2, 4, 8]
207 for num_stages in [2, 3, 4]
208 ],
209 key=["BC"],
210 )
211 @triton.jit(do_not_specialize=["T"])
212 def chunk_kda_scaled_dot_kkt_fwd_kernel_intra_sub_inter(
213 q,
214 k,
215 g,
216 beta,
217 A,
218 Aqk,
219 scale,
220 cu_seqlens,
221 chunk_indices,
222 T,
223 H: tl.constexpr,
224 K: tl.constexpr,
225 BT: tl.constexpr,
226 BC: tl.constexpr,
227 BK: tl.constexpr,
228 NC: tl.constexpr,
229 IS_VARLEN: tl.constexpr,
230 ):
231 i_t, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2)
232 i_b, i_h = i_bh // H, i_bh % H
233 i_i, i_j = i_c // NC, i_c % NC
234 if IS_VARLEN:
235 i_n, i_t = (
236 tl.load(chunk_indices + i_t * 2).to(tl.int32),
237 tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32),
238 )
239 bos, eos = (
240 tl.load(cu_seqlens + i_n).to(tl.int32),
241 tl.load(cu_seqlens + i_n + 1).to(tl.int32),
242 )
243 T = eos - bos
244 else:
245 bos, eos = i_b * T, i_b * T + T
246
247 if i_t * BT + i_i * BC >= T:
248 return
249 if i_i <= i_j:
250 return
251
252 q += (bos * H + i_h) * K
253 k += (bos * H + i_h) * K
254 g += (bos * H + i_h) * K
255 A += (bos * H + i_h) * BT
256 Aqk += (bos * H + i_h) * BT
257
258 p_b = tl.make_block_ptr(
259 beta + bos * H + i_h, (T,), (H,), (i_t * BT + i_i * BC,), (BC,), (0,)
260 )
261 b_b = tl.load(p_b, boundary_check=(0,))
262
263 b_A = tl.zeros([BC, BC], dtype=tl.float32)
264 b_Aqk = tl.zeros([BC, BC], dtype=tl.float32)
265 for i_k in range(tl.cdiv(K, BK)):
266 p_q = tl.make_block_ptr(
267 q, (T, K), (H * K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)
268 )
269 p_k = tl.make_block_ptr(
270 k, (T, K), (H * K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)
271 )
272 p_g = tl.make_block_ptr(
273 g, (T, K), (H * K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)
274 )
275 b_kt = tl.make_block_ptr(
276 k, (K, T), (1, H * K), (i_k * BK, i_t * BT + i_j * BC), (BK, BC), (0, 1)
277 )
278 p_gk = tl.make_block_ptr(
279 g, (K, T), (1, H * K), (i_k * BK, i_t * BT + i_j * BC), (BK, BC), (0, 1)
280 )
281
282 o_k = i_k * BK + tl.arange(0, BK)
283 m_k = o_k < K
284 # [BK,]
285 b_gn = tl.load(g + (i_t * BT + i_i * BC) * H * K + o_k, mask=m_k, other=0)
286 # [BC, BK]
287 b_g = tl.load(p_g, boundary_check=(0, 1))
288 b_k = tl.load(p_k, boundary_check=(0, 1)) * exp2(b_g - b_gn[None, :])
289 # [BK, BC]
290 b_gk = tl.load(p_gk, boundary_check=(0, 1))
291 b_kt = tl.load(b_kt, boundary_check=(0, 1))
292 # [BC, BC]
293 b_ktg = b_kt * exp2(b_gn[:, None] - b_gk)
294 b_A += tl.dot(b_k, b_ktg)
295
296 b_q = tl.load(p_q, boundary_check=(0, 1))
297 b_qg = b_q * exp2(b_g - b_gn[None, :]) * scale
298 b_Aqk += tl.dot(b_qg, b_ktg)
299
300 b_A *= b_b[:, None]
301
302 p_A = tl.make_block_ptr(
303 A, (T, BT), (H * BT, 1), (i_t * BT + i_i * BC, i_j * BC), (BC, BC), (1, 0)
304 )
305 tl.store(p_A, b_A.to(A.dtype.element_ty), boundary_check=(0, 1))
306 p_Aqk = tl.make_block_ptr(
307 Aqk, (T, BT), (H * BT, 1), (i_t * BT + i_i * BC, i_j * BC), (BC, BC), (1, 0)
308 )
309 tl.store(p_Aqk, b_Aqk.to(Aqk.dtype.element_ty), boundary_check=(0, 1))
310
311
312 @triton.heuristics({"IS_VARLEN": lambda args: args["cu_seqlens"] is not None})
313 @triton.autotune(
314 configs=[triton.Config({}, num_warps=num_warps) for num_warps in [1, 2, 4, 8]],
315 key=["BK", "BT"],
316 )
317 @triton.jit(do_not_specialize=["T"])
318 def chunk_kda_scaled_dot_kkt_fwd_kernel_intra_sub_intra(
319 q,
320 k,
321 g,
322 beta,
323 A,
324 Aqk,
325 scale,
326 cu_seqlens,
327 chunk_indices,
328 T,
329 H: tl.constexpr,
330 K: tl.constexpr,
331 BT: tl.constexpr,
332 BC: tl.constexpr,
333 BK: tl.constexpr,
334 IS_VARLEN: tl.constexpr,
335 ):
336 i_t, i_i, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2)
337 i_b, i_h = i_bh // H, i_bh % H
338 if IS_VARLEN:
339 i_n, i_t = (
340 tl.load(chunk_indices + i_t * 2).to(tl.int32),
341 tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32),
342 )
343 bos, eos = (
344 tl.load(cu_seqlens + i_n).to(tl.int32),
345 tl.load(cu_seqlens + i_n + 1).to(tl.int32),
346 )
347 T = eos - bos
348 else:
349 bos, eos = i_b * T, i_b * T + T
350
351 if i_t * BT + i_i * BC >= T:
352 return
353
354 o_i = tl.arange(0, BC)
355 o_k = tl.arange(0, BK)
356 m_k = o_k < K
357 m_A = (i_t * BT + i_i * BC + o_i) < T
358 o_A = (bos + i_t * BT + i_i * BC + o_i) * H * BT + i_h * BT + i_i * BC
359
360 p_q = tl.make_block_ptr(
361 q + (bos * H + i_h) * K,
362 (T, K),
363 (H * K, 1),
364 (i_t * BT + i_i * BC, 0),
365 (BC, BK),
366 (1, 0),
367 )
368 p_k = tl.make_block_ptr(
369 k + (bos * H + i_h) * K,
370 (T, K),
371 (H * K, 1),
372 (i_t * BT + i_i * BC, 0),
373 (BC, BK),
374 (1, 0),
375 )
376 p_g = tl.make_block_ptr(
377 g + (bos * H + i_h) * K,
378 (T, K),
379 (H * K, 1),
380 (i_t * BT + i_i * BC, 0),
381 (BC, BK),
382 (1, 0),
383 )
384 b_q = tl.load(p_q, boundary_check=(0, 1))
385 b_k = tl.load(p_k, boundary_check=(0, 1))
386 b_g = tl.load(p_g, boundary_check=(0, 1))
387
388 p_b = beta + (bos + i_t * BT + i_i * BC + o_i) * H + i_h
389 b_k = b_k * tl.load(p_b, mask=m_A, other=0)[:, None]
390
391 p_kt = k + (bos + i_t * BT + i_i * BC) * H * K + i_h * K + o_k
392 p_gk = g + (bos + i_t * BT + i_i * BC) * H * K + i_h * K + o_k
393
394 for j in range(0, min(BC, T - i_t * BT - i_i * BC)):
395 b_kt = tl.load(p_kt, mask=m_k, other=0).to(tl.float32)
396 b_gk = tl.load(p_gk, mask=m_k, other=0).to(tl.float32)
397 b_ktg = b_kt[None, :] * exp2(b_g - b_gk[None, :])
398 b_A = tl.sum(b_k * b_ktg, 1)
399 b_A = tl.where(o_i > j, b_A, 0.0)
400 b_Aqk = tl.sum(b_q * b_ktg, 1)
401 b_Aqk = tl.where(o_i >= j, b_Aqk * scale, 0.0)
402 tl.store(A + o_A + j, b_A, mask=m_A)
403 tl.store(Aqk + o_A + j, b_Aqk, mask=m_A)
404 p_kt += H * K
405 p_gk += H * K
406
407
408 def chunk_kda_scaled_dot_kkt_fwd(
409 q: torch.Tensor,
410 k: torch.Tensor,
411 gk: torch.Tensor | None = None,
412 beta: torch.Tensor | None = None,
413 scale: float | None = None,
414 cu_seqlens: torch.Tensor | None = None,
415 chunk_indices: torch.Tensor | None = None,
416 chunk_size: int = FLA_CHUNK_SIZE,
417 output_dtype: torch.dtype = torch.float32,
418 ) -> tuple[torch.Tensor, torch.Tensor]:
419 r"""Compute beta * K * K^T.
420
421 Args:
422 q (torch.Tensor):
423 The query tensor of shape `[B, T, H, K]`.
424 k (torch.Tensor):
425 The key tensor of shape `[B, T, H, K]`.
426 beta (torch.Tensor):
427 The beta tensor of shape `[B, T, H]`.
428 gk (torch.Tensor):
429 The cumulative sum of the gate tensor of shape `[B, T, H, K]` applied to the key tensor. Default: `None`.
430 scale (float):
431 Scale applied to the query-key products. Default: `None`.
432 cu_seqlens (torch.Tensor):
433 The cumulative sequence lengths of the input tensor.
434 Default: None
435 chunk_indices (torch.Tensor):
436 Precomputed chunk indices for `cu_seqlens`. Default: `None`.
437 chunk_size (int):
438 The chunk size. Default: 64.
439 output_dtype (torch.dtype):
440 The dtype of the output tensor. Default: `torch.float32`
441
442 Returns:
443 beta * K * K^T of shape `[B, T, H, BT]` where `BT` is the chunk size.
444
445 """
446 B, T, H, K = k.shape
447 assert K <= 256
448 BT = chunk_size
449 if chunk_indices is None and cu_seqlens is not None:
450 chunk_indices = prepare_chunk_indices(cu_seqlens, BT)
451 NT = cdiv(T, BT) if cu_seqlens is None else len(chunk_indices)
452
453 BC = min(16, BT)
454 NC = cdiv(BT, BC)
455 BK = max(next_power_of_2(K), 16)
456 A = torch.zeros(B, T, H, BT, device=k.device, dtype=output_dtype)
457 Aqk = torch.zeros(B, T, H, BT, device=k.device, dtype=output_dtype)
458 grid = (NT, NC * NC, B * H)
459 chunk_kda_scaled_dot_kkt_fwd_kernel_intra_sub_inter[grid](
460 q=q,
461 k=k,
462 g=gk,
463 beta=beta,
464 A=A,
465 Aqk=Aqk,
466 scale=scale,
467 cu_seqlens=cu_seqlens,
468 chunk_indices=chunk_indices,
469 T=T,
470 H=H,
471 K=K,
472 BT=BT,
473 BC=BC,
474 NC=NC,
475 )
476
477 grid = (NT, NC, B * H)
478 chunk_kda_scaled_dot_kkt_fwd_kernel_intra_sub_intra[grid](
479 q=q,
480 k=k,
481 g=gk,
482 beta=beta,
483 A=A,
484 Aqk=Aqk,
485 scale=scale,
486 cu_seqlens=cu_seqlens,
487 chunk_indices=chunk_indices,
488 T=T,
489 H=H,
490 K=K,
491 BT=BT,
492 BC=BC,
493 BK=BK,
494 )
495 return A, Aqk
496
497
498 @triton.heuristics(
499 {
500 "STORE_QG": lambda args: args["qg"] is not None,
501 "STORE_KG": lambda args: args["kg"] is not None,
502 "IS_VARLEN": lambda args: args["cu_seqlens"] is not None,
503 }
504 )
505 @triton.autotune(
506 configs=[
507 triton.Config({}, num_warps=num_warps, num_stages=num_stages)
508 for num_warps in [2, 4, 8]
509 for num_stages in [2, 3, 4]
510 ],
511 key=["H", "K", "V", "BT", "BK", "BV", "IS_VARLEN"],
512 )
513 @triton.jit(do_not_specialize=["T"])
514 def recompute_w_u_fwd_kernel(
515 q,
516 k,
517 qg,
518 kg,
519 v,
520 beta,
521 w,
522 u,
523 A,
524 gk,
525 cu_seqlens,
526 chunk_indices,
527 T,
528 H: tl.constexpr,
529 K: tl.constexpr,
530 V: tl.constexpr,
531 BT: tl.constexpr,
532 BK: tl.constexpr,
533 BV: tl.constexpr,
534 STORE_QG: tl.constexpr,
535 STORE_KG: tl.constexpr,
536 IS_VARLEN: tl.constexpr,
537 DOT_PRECISION: tl.constexpr,
538 ):
539 i_t, i_bh = tl.program_id(0), tl.program_id(1)
540 i_b, i_h = i_bh // H, i_bh % H
541 if IS_VARLEN:
542 i_n, i_t = (
543 tl.load(chunk_indices + i_t * 2).to(tl.int32),
544 tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32),
545 )
546 bos, eos = (
547 tl.load(cu_seqlens + i_n).to(tl.int32),
548 tl.load(cu_seqlens + i_n + 1).to(tl.int32),
549 )
550 T = eos - bos
551 else:
552 bos, eos = i_b * T, i_b * T + T
553 p_b = tl.make_block_ptr(beta + bos * H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,))
554 b_b = tl.load(p_b, boundary_check=(0,))
555
556 p_A = tl.make_block_ptr(
557 A + (bos * H + i_h) * BT, (T, BT), (H * BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)
558 )
559 b_A = tl.load(p_A, boundary_check=(0, 1))
560
561 for i_v in range(tl.cdiv(V, BV)):
562 p_v = tl.make_block_ptr(
563 v + (bos * H + i_h) * V,
564 (T, V),
565 (H * V, 1),
566 (i_t * BT, i_v * BV),
567 (BT, BV),
568 (1, 0),
569 )
570 p_u = tl.make_block_ptr(
571 u + (bos * H + i_h) * V,
572 (T, V),
573 (H * V, 1),
574 (i_t * BT, i_v * BV),
575 (BT, BV),
576 (1, 0),
577 )
578 b_v = tl.load(p_v, boundary_check=(0, 1))
579 b_vb = (b_v * b_b[:, None]).to(b_v.dtype)
580 b_u = tl.dot(b_A, b_vb, input_precision=DOT_PRECISION)
581 tl.store(p_u, b_u.to(p_u.dtype.element_ty), boundary_check=(0, 1))
582
583 for i_k in range(tl.cdiv(K, BK)):
584 p_w = tl.make_block_ptr(
585 w + (bos * H + i_h) * K,
586 (T, K),
587 (H * K, 1),
588 (i_t * BT, i_k * BK),
589 (BT, BK),
590 (1, 0),
591 )
592 p_k = tl.make_block_ptr(
593 k + (bos * H + i_h) * K,
594 (T, K),
595 (H * K, 1),
596 (i_t * BT, i_k * BK),
597 (BT, BK),
598 (1, 0),
599 )
600 b_k = tl.load(p_k, boundary_check=(0, 1))
601 b_kb = b_k * b_b[:, None]
602
603 p_gk = tl.make_block_ptr(
604 gk + (bos * H + i_h) * K,
605 (T, K),
606 (H * K, 1),
607 (i_t * BT, i_k * BK),
608 (BT, BK),
609 (1, 0),
610 )
611 b_gk = tl.load(p_gk, boundary_check=(0, 1))
612 b_kb *= exp2(b_gk)
613 if STORE_QG:
614 p_q = tl.make_block_ptr(
615 q + (bos * H + i_h) * K,
616 (T, K),
617 (H * K, 1),
618 (i_t * BT, i_k * BK),
619 (BT, BK),
620 (1, 0),
621 )
622 p_qg = tl.make_block_ptr(
623 qg + (bos * H + i_h) * K,
624 (T, K),
625 (H * K, 1),
626 (i_t * BT, i_k * BK),
627 (BT, BK),
628 (1, 0),
629 )
630 b_q = tl.load(p_q, boundary_check=(0, 1))
631 b_qg = b_q * exp2(b_gk)
632 tl.store(p_qg, b_qg.to(p_qg.dtype.element_ty), boundary_check=(0, 1))
633 if STORE_KG:
634 last_idx = min(i_t * BT + BT, T) - 1
635
636 o_k = i_k * BK + tl.arange(0, BK)
637 m_k = o_k < K
638 b_gn = tl.load(
639 gk + ((bos + last_idx) * H + i_h) * K + o_k, mask=m_k, other=0.0
640 )
641 b_kg = b_k * exp2(b_gn - b_gk)
642
643 p_kg = tl.make_block_ptr(
644 kg + (bos * H + i_h) * K,
645 (T, K),
646 (H * K, 1),
647 (i_t * BT, i_k * BK),
648 (BT, BK),
649 (1, 0),
650 )
651 tl.store(p_kg, b_kg.to(p_kg.dtype.element_ty), boundary_check=(0, 1))
652
653 b_w = tl.dot(b_A, b_kb.to(b_k.dtype))
654 tl.store(p_w, b_w.to(p_w.dtype.element_ty), boundary_check=(0, 1))
655
656
657 def recompute_w_u_fwd(
658 k: torch.Tensor,
659 v: torch.Tensor,
660 beta: torch.Tensor,
661 A: torch.Tensor,
662 q: torch.Tensor | None = None,
663 gk: torch.Tensor | None = None,
664 cu_seqlens: torch.Tensor | None = None,
665 chunk_indices: torch.Tensor | None = None,
666 ) -> tuple[torch.Tensor, torch.Tensor]:
667 B, T, H, K, V = *k.shape, v.shape[-1]
668 BT = A.shape[-1]
669 BK = 64
670 BV = 64
671
672 if chunk_indices is None and cu_seqlens is not None:
673 chunk_indices = prepare_chunk_indices(cu_seqlens, BT)
674 NT = cdiv(T, BT) if cu_seqlens is None else len(chunk_indices)
675
676 w = torch.empty_like(k)
677 u = torch.empty_like(v)
678 kg = torch.empty_like(k) if gk is not None else None
679 recompute_w_u_fwd_kernel[(NT, B * H)](
680 q=q,
681 k=k,
682 qg=None,
683 kg=kg,
684 v=v,
685 beta=beta,
686 w=w,
687 u=u,
688 A=A,
689 gk=gk,
690 cu_seqlens=cu_seqlens,
691 chunk_indices=chunk_indices,
692 T=T,
693 H=H,
694 K=K,
695 V=V,
696 BT=BT,
697 BK=BK,
698 BV=BV,
699 DOT_PRECISION="ieee",
700 )
701 return w, u, None, kg
702
703
704 @triton.heuristics({"IS_VARLEN": lambda args: args["cu_seqlens"] is not None})
705 @triton.autotune(
706 configs=[
707 triton.Config({"BK": BK, "BV": BV}, num_warps=num_warps, num_stages=num_stages)
708 for BK in [32, 64]
709 for BV in [64, 128]
710 for num_warps in [2, 4, 8]
711 for num_stages in [2, 3, 4]
712 ],
713 key=["BT"],
714 )
715 @triton.jit(do_not_specialize=["T"])
716 def chunk_gla_fwd_kernel_o(
717 q,
718 v,
719 g,
720 h,
721 o,
722 A,
723 cu_seqlens,
724 chunk_indices,
725 scale,
726 T,
727 H: tl.constexpr,
728 K: tl.constexpr,
729 V: tl.constexpr,
730 BT: tl.constexpr,
731 BK: tl.constexpr,
732 BV: tl.constexpr,
733 IS_VARLEN: tl.constexpr,
734 ):
735 i_v, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2)
736 i_b, i_h = i_bh // H, i_bh % H
737 if IS_VARLEN:
738 i_tg = i_t
739 i_n, i_t = (
740 tl.load(chunk_indices + i_t * 2).to(tl.int32),
741 tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32),
742 )
743 bos, eos = (
744 tl.load(cu_seqlens + i_n).to(tl.int32),
745 tl.load(cu_seqlens + i_n + 1).to(tl.int32),
746 )
747 T = eos - bos
748 NT = tl.cdiv(T, BT)
749 else:
750 NT = tl.cdiv(T, BT)
751 i_tg = i_b * NT + i_t
752 bos, eos = i_b * T, i_b * T + T
753
754 m_s = tl.arange(0, BT)[:, None] >= tl.arange(0, BT)[None, :]
755
756 b_o = tl.zeros([BT, BV], dtype=tl.float32)
757 for i_k in range(tl.cdiv(K, BK)):
758 p_q = tl.make_block_ptr(
759 q + (bos * H + i_h) * K,
760 (T, K),
761 (H * K, 1),
762 (i_t * BT, i_k * BK),
763 (BT, BK),
764 (1, 0),
765 )
766 p_g = tl.make_block_ptr(
767 g + (bos * H + i_h) * K,
768 (T, K),
769 (H * K, 1),
770 (i_t * BT, i_k * BK),
771 (BT, BK),
772 (1, 0),
773 )
774 p_h = tl.make_block_ptr(
775 h + (i_tg * H + i_h) * K * V,
776 (V, K),
777 (K, 1),
778 (i_v * BV, i_k * BK),
779 (BV, BK),
780 (1, 0),
781 )
782
783 # [BT, BK]
784 b_q = tl.load(p_q, boundary_check=(0, 1))
785 b_q = (b_q * scale).to(b_q.dtype)
786 # [BT, BK]
787 b_g = tl.load(p_g, boundary_check=(0, 1))
788 # [BT, BK]
789 b_qg = (b_q * exp2(b_g)).to(b_q.dtype)
790 # [BV, BK]
791 b_h = tl.load(p_h, boundary_check=(0, 1))
792 # [BT, BV]
793 if i_k >= 0:
794 b_o += tl.dot(b_qg, tl.trans(b_h).to(b_qg.dtype))
795 p_v = tl.make_block_ptr(
796 v + (bos * H + i_h) * V,
797 (T, V),
798 (H * V, 1),
799 (i_t * BT, i_v * BV),
800 (BT, BV),
801 (1, 0),
802 )
803 p_o = tl.make_block_ptr(
804 o + (bos * H + i_h) * V,
805 (T, V),
806 (H * V, 1),
807 (i_t * BT, i_v * BV),
808 (BT, BV),
809 (1, 0),
810 )
811 p_A = tl.make_block_ptr(
812 A + (bos * H + i_h) * BT, (T, BT), (H * BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)
813 )
814 # [BT, BV]
815 b_v = tl.load(p_v, boundary_check=(0, 1))
816 # [BT, BT]
817 b_A = tl.load(p_A, boundary_check=(0, 1))
818 b_A = tl.where(m_s, b_A, 0.0).to(b_v.dtype)
819 b_o += tl.dot(b_A, b_v, allow_tf32=False)
820 tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1))
821
822
823 def chunk_gla_fwd_o_gk(
824 q: torch.Tensor,
825 v: torch.Tensor,
826 g: torch.Tensor,
827 A: torch.Tensor,
828 h: torch.Tensor,
829 o: torch.Tensor,
830 scale: float,
831 cu_seqlens: torch.Tensor | None = None,
832 chunk_indices: torch.Tensor | None = None,
833 chunk_size: int = FLA_CHUNK_SIZE,
834 ):
835 B, T, H, K, V = *q.shape, v.shape[-1]
836 BT = chunk_size
837
838 if chunk_indices is None and cu_seqlens is not None:
839 chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size)
840 NT = cdiv(T, BT) if cu_seqlens is None else len(chunk_indices)
841
842 def grid(meta):
843 return (cdiv(V, meta["BV"]), NT, B * H)
844
845 chunk_gla_fwd_kernel_o[grid](
846 q=q,
847 v=v,
848 g=g,
849 h=h,
850 o=o,
851 A=A,
852 cu_seqlens=cu_seqlens,
853 chunk_indices=chunk_indices,
854 scale=scale,
855 T=T,
856 H=H,
857 K=K,
858 V=V,
859 BT=BT,
860 )
861 return o
862
863
864 @triton.heuristics(
865 {
866 "HAS_BIAS": lambda args: args["g_bias"] is not None,
867 "IS_VARLEN": lambda args: args["cu_seqlens"] is not None,
868 }
869 )
870 @triton.autotune(
871 configs=[
872 triton.Config({"BD": BD}, num_warps=num_warps)
873 for BD in [32, 64]
874 for num_warps in [2, 4, 8]
875 ],
876 key=["H", "D", "BT", "IS_VARLEN"],
877 )
878 @triton.jit(do_not_specialize=["T"])
879 def kda_gate_cumsum_fwd_kernel(
880 g,
881 A,
882 y,
883 g_bias,
884 cu_seqlens,
885 chunk_indices,
886 cumsum_scale,
887 beta,
888 threshold,
889 SAFE_GATE: tl.constexpr,
890 LOWER_BOUND: tl.constexpr,
891 T,
892 H: tl.constexpr,
893 D: tl.constexpr,
894 BT: tl.constexpr,
895 BD: tl.constexpr,
896 HAS_BIAS: tl.constexpr,
897 IS_VARLEN: tl.constexpr,
898 ):
899 i_d, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2)
900 i_b, i_h = i_bh // H, i_bh % H
901 if IS_VARLEN:
902 i_n, i_t = (
903 tl.load(chunk_indices + i_t * 2).to(tl.int32),
904 tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32),
905 )
906 bos, eos = (
907 tl.load(cu_seqlens + i_n).to(tl.int32),
908 tl.load(cu_seqlens + i_n + 1).to(tl.int32),
909 )
910 T = eos - bos
911 else:
912 bos = i_b * T
913
914 p_g = tl.make_block_ptr(
915 g + (bos * H + i_h) * D,
916 (T, D),
917 (H * D, 1),
918 (i_t * BT, i_d * BD),
919 (BT, BD),
920 (1, 0),
921 )
922 p_y = tl.make_block_ptr(
923 y + (bos * H + i_h) * D,
924 (T, D),
925 (H * D, 1),
926 (i_t * BT, i_d * BD),
927 (BT, BD),
928 (1, 0),
929 )
930
931 b_g = tl.load(p_g, boundary_check=(0, 1)).to(tl.float32)
932 if HAS_BIAS:
933 o_d = i_d * BD + tl.arange(0, BD)
934 b_bias = tl.load(g_bias + i_h * D + o_d, mask=o_d < D, other=0.0).to(tl.float32)
935 b_g = b_g + b_bias[None, :]
936
937 b_a = tl.load(A + i_h).to(tl.float32)
938 b_a = tl.exp(b_a) if SAFE_GATE else -tl.exp(b_a)
939 if SAFE_GATE:
940 # y = lower_bound * sigmoid(exp(A) * (g + g_bias)), bounded to
941 # (lower_bound, 0) for safe-gate checkpoints.
942 b_gate = LOWER_BOUND / (1.0 + tl.exp(-(b_a * b_g)))
943 else:
944 b_g_scaled = b_g * beta
945 b_softplus = tl.where(
946 b_g_scaled > threshold,
947 b_g,
948 (1.0 / beta) * log(1.0 + tl.exp(b_g_scaled)),
949 )
950 b_gate = b_a * b_softplus
951
952 # Out-of-bounds rows (load returns 0, but softplus/bias can still make
953 # b_gate non-zero) participate in the dot product. They only contribute to
954 # out-of-bounds output rows, which are masked away by `boundary_check` on
955 # the store, so visible output matches unfused gate + chunk-local cumsum.
956 o_t = tl.arange(0, BT)
957 m_cumsum = tl.where(o_t[:, None] >= o_t[None, :], 1.0, 0.0)
958 b_y = tl.dot(m_cumsum, b_gate, allow_tf32=False) * cumsum_scale
959 tl.store(p_y, b_y.to(p_y.dtype.element_ty), boundary_check=(0, 1))
960
961
962 def fused_kda_gate_chunk_cumsum(
963 raw_g: torch.Tensor,
964 A_log: torch.Tensor,
965 g_bias: torch.Tensor | None = None,
966 beta: float = 1.0,
967 threshold: float = 20.0,
968 cu_seqlens: torch.Tensor | None = None,
969 chunk_indices: torch.Tensor | None = None,
970 chunk_size: int = FLA_CHUNK_SIZE,
971 output_dtype: torch.dtype | None = torch.float,
972 safe_gate: bool = False,
973 lower_bound: float = -5.0,
974 ) -> torch.Tensor:
975 if cu_seqlens is not None:
976 assert raw_g.shape[0] == 1, (
977 "Only batch size 1 is supported when cu_seqlens are provided"
978 )
979 B, T, H, D = raw_g.shape
980 if chunk_indices is None and cu_seqlens is not None:
981 chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size)
982 NT = cdiv(T, chunk_size) if cu_seqlens is None else len(chunk_indices)
983
984 A_log = A_log.reshape(-1)
985 if g_bias is not None:
986 g_bias = g_bias.reshape(-1)
987 y = torch.empty_like(raw_g, dtype=output_dtype or raw_g.dtype)
988
989 def grid(meta):
990 return (cdiv(meta["D"], meta["BD"]), NT, B * H)
991
992 kda_gate_cumsum_fwd_kernel[grid](
993 g=raw_g,
994 A=A_log,
995 y=y,
996 g_bias=g_bias,
997 cu_seqlens=cu_seqlens,
998 chunk_indices=chunk_indices,
999 # RCP_LN2 folds in the natural-log -> log2 conversion so downstream
1000 # exp2-based kernels reproduce exp(g). Keep this in sync with the
1001 # `use_exp2=True` path in `_chunk_kda_fwd_with_cumulative_g`.
1002 cumsum_scale=RCP_LN2,
1003 beta=beta,
1004 threshold=threshold,
1005 SAFE_GATE=safe_gate,
1006 LOWER_BOUND=lower_bound,
1007 T=T,
1008 H=H,
1009 D=D,
1010 BT=chunk_size,
1011 )
1012 return y
1013
1014
1015 def _chunk_kda_fwd_with_cumulative_g(
1016 q: torch.Tensor,
1017 k: torch.Tensor,
1018 v: torch.Tensor,
1019 g: torch.Tensor,
1020 beta: torch.Tensor,
1021 scale: float,
1022 initial_state: torch.Tensor,
1023 output_final_state: bool,
1024 cu_seqlens: torch.Tensor | None = None,
1025 chunk_indices: torch.Tensor | None = None,
1026 chunk_size: int = FLA_CHUNK_SIZE,
1027 ):
1028 # `g` must already be chunk-local cumulatively-summed AND scaled by
1029 # RCP_LN2 (so the downstream exp2-based kernels reproduce exp(g)).
1030 # Use `chunk_kda_fwd` or `chunk_kda_with_fused_gate_fwd` instead of
1031 # calling this helper directly unless that invariant is upheld.
1032 # the intra Aqk is kept in fp32
1033 # the computation has very marginal effect on the entire throughput
1034 A, Aqk = chunk_kda_scaled_dot_kkt_fwd(
1035 q=q,
1036 k=k,
1037 gk=g,
1038 beta=beta,
1039 scale=scale,
1040 cu_seqlens=cu_seqlens,
1041 chunk_indices=chunk_indices,
1042 chunk_size=chunk_size,
1043 output_dtype=torch.float32,
1044 )
1045 A = solve_tril(A=A, cu_seqlens=cu_seqlens, output_dtype=k.dtype)
1046 w, u, _, kg = recompute_w_u_fwd(
1047 k=k,
1048 v=v,
1049 beta=beta,
1050 A=A,
1051 gk=g,
1052 cu_seqlens=cu_seqlens,
1053 chunk_indices=chunk_indices,
1054 )
1055 del A
1056 h, v_new, final_state = chunk_gated_delta_rule_fwd_h(
1057 k=kg,
1058 w=w,
1059 u=u,
1060 gk=g,
1061 initial_state=initial_state,
1062 output_final_state=output_final_state,
1063 cu_seqlens=cu_seqlens,
1064 chunk_indices=chunk_indices,
1065 use_exp2=True,
1066 )
1067 del w, u, kg
1068 o = chunk_gla_fwd_o_gk(
1069 q=q,
1070 v=v_new,
1071 g=g,
1072 A=Aqk,
1073 h=h,
1074 o=v,
1075 scale=scale,
1076 cu_seqlens=cu_seqlens,
1077 chunk_indices=chunk_indices,
1078 chunk_size=chunk_size,
1079 )
1080 del Aqk, v_new, h
1081 return o, final_state
1082
1083
1084 def chunk_kda_fwd(
1085 q: torch.Tensor,
1086 k: torch.Tensor,
1087 v: torch.Tensor,
1088 g: torch.Tensor,
1089 beta: torch.Tensor,
1090 scale: float,
1091 initial_state: torch.Tensor,
1092 output_final_state: bool,
1093 cu_seqlens: torch.Tensor | None = None,
1094 ):
1095 chunk_size = FLA_CHUNK_SIZE
1096 chunk_indices = (
1097 prepare_chunk_indices(cu_seqlens, chunk_size)
1098 if cu_seqlens is not None
1099 else None
1100 )
1101 g = chunk_local_cumsum(
1102 g,
1103 chunk_size=chunk_size,
1104 cu_seqlens=cu_seqlens,
1105 chunk_indices=chunk_indices,
1106 )
1107 # KDA evaluates cumulative gate decays with exp2. Convert from natural-log
1108 # space so exp(x) is preserved as exp2(x / ln(2)).
1109 g = g * RCP_LN2
1110 return _chunk_kda_fwd_with_cumulative_g(
1111 q=q,
1112 k=k,
1113 v=v,
1114 g=g,
1115 beta=beta,
1116 scale=scale,
1117 initial_state=initial_state,
1118 output_final_state=output_final_state,
1119 cu_seqlens=cu_seqlens,
1120 chunk_indices=chunk_indices,
1121 chunk_size=chunk_size,
1122 )
1123
1124
1125 def chunk_kda_with_fused_gate_fwd(
1126 q: torch.Tensor,
1127 k: torch.Tensor,
1128 v: torch.Tensor,
1129 raw_g: torch.Tensor,
1130 beta: torch.Tensor,
1131 A_log: torch.Tensor,
1132 g_bias: torch.Tensor | None,
1133 scale: float,
1134 initial_state: torch.Tensor,
1135 output_final_state: bool,
1136 cu_seqlens: torch.Tensor | None = None,
1137 safe_gate: bool = False,
1138 lower_bound: float = -5.0,
1139 ):
1140 chunk_size = FLA_CHUNK_SIZE
1141 chunk_indices = (
1142 prepare_chunk_indices(cu_seqlens, chunk_size)
1143 if cu_seqlens is not None
1144 else None
1145 )
1146 g = fused_kda_gate_chunk_cumsum(
1147 raw_g,
1148 A_log=A_log,
1149 g_bias=g_bias,
1150 cu_seqlens=cu_seqlens,
1151 chunk_indices=chunk_indices,
1152 chunk_size=chunk_size,
1153 safe_gate=safe_gate,
1154 lower_bound=lower_bound,
1155 )
1156 return _chunk_kda_fwd_with_cumulative_g(
1157 q=q,
1158 k=k,
1159 v=v,
1160 g=g,
1161 beta=beta,
1162 scale=scale,
1163 initial_state=initial_state,
1164 output_final_state=output_final_state,
1165 cu_seqlens=cu_seqlens,
1166 chunk_indices=chunk_indices,
1167 chunk_size=chunk_size,
1168 )
1169
1170
1171 def chunk_kda(
1172 q: torch.Tensor,
1173 k: torch.Tensor,
1174 v: torch.Tensor,
1175 g: torch.Tensor,
1176 beta: torch.Tensor,
1177 scale: float = None,
1178 initial_state: torch.Tensor = None,
1179 output_final_state: bool = False,
1180 use_qk_l2norm_in_kernel: bool = False,
1181 cu_seqlens: torch.Tensor | None = None,
1182 **kwargs,
1183 ):
1184 if scale is None:
1185 scale = k.shape[-1] ** -0.5
1186
1187 if use_qk_l2norm_in_kernel:
1188 q = l2norm_fwd(q.contiguous())
1189 k = l2norm_fwd(k.contiguous())
1190
1191 o, final_state = chunk_kda_fwd(
1192 q=q,
1193 k=k,
1194 v=v.contiguous(),
1195 g=g.contiguous(),
1196 beta=beta.contiguous(),
1197 scale=scale,
1198 initial_state=initial_state.contiguous(),
1199 output_final_state=output_final_state,
1200 cu_seqlens=cu_seqlens,
1201 )
1202 return o, final_state
1203
1204
1205 def chunk_kda_with_fused_gate(
1206 q: torch.Tensor,
1207 k: torch.Tensor,
1208 v: torch.Tensor,
1209 raw_g: torch.Tensor,
1210 beta: torch.Tensor,
1211 A_log: torch.Tensor,
1212 g_bias: torch.Tensor | None,
1213 scale: float | None = None,
1214 initial_state: torch.Tensor | None = None,
1215 output_final_state: bool = False,
1216 use_qk_l2norm_in_kernel: bool = False,
1217 cu_seqlens: torch.Tensor | None = None,
1218 safe_gate: bool = False,
1219 lower_bound: float = -5.0,
1220 **kwargs,
1221 ):
1222 """Run chunk KDA from raw gate projection using fused gate+cumsum."""
1223 if scale is None:
1224 scale = k.shape[-1] ** -0.5
1225
1226 if use_qk_l2norm_in_kernel:
1227 q = l2norm_fwd(q.contiguous())
1228 k = l2norm_fwd(k.contiguous())
1229
1230 o, final_state = chunk_kda_with_fused_gate_fwd(
1231 q=q,
1232 k=k,
1233 v=v.contiguous(),
1234 raw_g=raw_g.contiguous(),
1235 beta=beta.contiguous(),
1236 A_log=A_log,
1237 g_bias=g_bias,
1238 scale=scale,
1239 initial_state=initial_state.contiguous() if initial_state is not None else None,
1240 output_final_state=output_final_state,
1241 cu_seqlens=cu_seqlens,
1242 safe_gate=safe_gate,
1243 lower_bound=lower_bound,
1244 )
1245 return o, final_state
1246
1247
1248 @triton.autotune(
1249 configs=[
1250 triton.Config({"BT": bt}, num_warps=nw, num_stages=ns)
1251 for bt in BT_LIST_AUTOTUNE
1252 for nw in NUM_WARPS_AUTOTUNE
1253 for ns in [2, 3]
1254 ],
1255 key=["H", "D"],
1256 )
1257 @triton.jit
1258 def kda_gate_fwd_kernel(
1259 g,
1260 A,
1261 y,
1262 g_bias,
1263 beta: tl.constexpr,
1264 threshold: tl.constexpr,
1265 SAFE_GATE: tl.constexpr,
1266 LOWER_BOUND: tl.constexpr,
1267 T,
1268 H,
1269 D: tl.constexpr,
1270 BT: tl.constexpr,
1271 BD: tl.constexpr,
1272 HAS_BIAS: tl.constexpr,
1273 ):
1274 i_t, i_h = tl.program_id(0), tl.program_id(1)
1275 n_t = i_t * BT
1276
1277 b_a = tl.load(A + i_h).to(tl.float32)
1278 b_a = tl.exp(b_a) if SAFE_GATE else -tl.exp(b_a)
1279
1280 stride_row = H * D
1281 stride_col = 1
1282
1283 g_ptr = tl.make_block_ptr(
1284 base=g + i_h * D,
1285 shape=(T, D),
1286 strides=(stride_row, stride_col),
1287 offsets=(n_t, 0),
1288 block_shape=(BT, BD),
1289 order=(1, 0),
1290 )
1291
1292 y_ptr = tl.make_block_ptr(
1293 base=y + i_h * D,
1294 shape=(T, D),
1295 strides=(stride_row, stride_col),
1296 offsets=(n_t, 0),
1297 block_shape=(BT, BD),
1298 order=(1, 0),
1299 )
1300
1301 b_g = tl.load(g_ptr, boundary_check=(0, 1)).to(tl.float32)
1302
1303 if HAS_BIAS:
1304 n_d = tl.arange(0, BD)
1305 bias_mask = n_d < D
1306 b_bias = tl.load(g_bias + i_h * D + n_d, mask=bias_mask, other=0.0).to(
1307 tl.float32
1308 )
1309 b_g = b_g + b_bias[None, :]
1310
1311 if SAFE_GATE:
1312 # y = lower_bound * sigmoid(exp(A) * (g + g_bias)), bounded to
1313 # (lower_bound, 0) for safe-gate checkpoints.
1314 b_y = LOWER_BOUND / (1.0 + tl.exp(-(b_a * b_g)))
1315 else:
1316 # softplus(x, beta) = (1/beta) * log(1 + exp(beta * x))
1317 # When beta * x > threshold, use linear approximation x
1318 # Use threshold to switch to linear when beta*x > threshold
1319 g_scaled = b_g * beta
1320 use_linear = g_scaled > threshold
1321 sp = tl.where(use_linear, b_g, (1.0 / beta) * log(1.0 + tl.exp(g_scaled)))
1322 b_y = b_a * sp
1323
1324 tl.store(y_ptr, b_y.to(y.dtype.element_ty), boundary_check=(0, 1))
1325
1326
1327 def fused_kda_gate(
1328 g: torch.Tensor,
1329 A: torch.Tensor,
1330 head_k_dim: int,
1331 g_bias: torch.Tensor | None = None,
1332 beta: float = 1.0,
1333 threshold: float = 20.0,
1334 safe_gate: bool = False,
1335 lower_bound: float | None = -5.0,
1336 ) -> torch.Tensor:
1337 """Forward pass for KDA gate:
1338 input g: [..., H*D]
1339 param A: [H] or [1, 1, H, 1]
1340 beta: softplus beta parameter (softplus branch only)
1341 threshold: softplus threshold parameter (softplus branch only)
1342 safe_gate: when False (default) compute y = -exp(A)*softplus(g+g_bias);
1343 when True compute the bounded y = lower_bound*sigmoid(exp(A)*(g+g_bias))
1344 lower_bound: floor for the safe_gate branch (default -5.0)
1345 return : [..., H, D]
1346 """
1347 orig_shape = g.shape[:-1]
1348
1349 g = g.view(-1, g.shape[-1])
1350 T = g.shape[0]
1351 HD = g.shape[1]
1352 H = A.numel()
1353 assert H * head_k_dim == HD
1354
1355 y = torch.empty_like(g, dtype=torch.float32)
1356
1357 def grid(meta):
1358 return (cdiv(T, meta["BT"]), H)
1359
1360 kda_gate_fwd_kernel[grid](
1361 g,
1362 A,
1363 y,
1364 g_bias,
1365 beta,
1366 threshold,
1367 safe_gate,
1368 lower_bound if lower_bound is not None else -5.0,
1369 T,
1370 H,
1371 head_k_dim,
1372 BD=next_power_of_2(head_k_dim),
1373 HAS_BIAS=g_bias is not None,
1374 )
1375
1376 y = y.view(*orig_shape, H, head_k_dim)
1377 return y