Skip to content

Commit 33e0ee3

Browse files
authored
[None][feat] Enable flashifner gdn decoding kernel for qwen3.5 (#13645)
Signed-off-by: nv-guomingz <137257613+nv-guomingz@users.noreply.github.com>
1 parent 4cc2d8a commit 33e0ee3

7 files changed

Lines changed: 332 additions & 312 deletions

File tree

tensorrt_llm/_torch/modules/fla/chunk.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ def chunk_gated_delta_rule(
148148
Scale factor for the RetNet attention scores.
149149
If not provided, it will default to `1 / sqrt(K)`. Default: `None`.
150150
initial_state (Optional[torch.Tensor]):
151-
Initial state of shape `[N, H, K, V]` for `N` input sequences.
151+
Initial state of shape `[N, H, V, K]` (K innermost, matching the
152+
state pool) for `N` input sequences.
152153
For equal-length input sequences, `N` equals the batch size `B`.
153154
Default: `None`.
154155
initial_state_indices (Optional[torch.Tensor]):
@@ -159,7 +160,7 @@ def chunk_gated_delta_rule(
159160
`initial_state` in-place. Callers are responsible for ensuring the
160161
selected slots are safe to update without aliasing races.
161162
output_final_state (Optional[bool]):
162-
Whether to output the final state of shape `[N, H, K, V]`. Default: `False`.
163+
Whether to output the final state of shape `[N, H, V, K]`. Default: `False`.
163164
cu_seqlens (torch.LongTensor):
164165
Cumulative sequence lengths of shape `[N+1]` used for variable-length training,
165166
consistent with the FlashAttention API.
@@ -171,7 +172,7 @@ def chunk_gated_delta_rule(
171172
o (torch.Tensor):
172173
Outputs of shape `[B, T, H, V]` if `head_first=False` else `[B, H, T, V]`.
173174
final_state (torch.Tensor):
174-
Final state of shape `[N, H, K, V]` if `output_final_state=True` else `None`.
175+
Final state of shape `[N, H, V, K]` if `output_final_state=True` else `None`.
175176
176177
Examples::
177178
>>> import torch

tensorrt_llm/_torch/modules/fla/chunk_delta_h.py

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -104,21 +104,23 @@ def chunk_gated_delta_rule_fwd_kernel_h_blockdim64(
104104
ht = ht + i_h * K * V
105105

106106
# load initial state
107+
# Pool layout [slots, HV, V, K], K innermost: logical block (K, V) but K has
108+
# stride 1, V has stride K, order=(0, 1) so Triton treats K as innermost.
107109
if USE_INITIAL_STATE:
108-
p_h0_1 = tl.make_block_ptr(h0, (K, V), (V, 1), (0, i_v * BV), (64, BV),
109-
(1, 0))
110+
p_h0_1 = tl.make_block_ptr(h0, (K, V), (1, K), (0, i_v * BV), (64, BV),
111+
(0, 1))
110112
b_h1 += tl.load(p_h0_1, boundary_check=(0, 1)).to(tl.float32)
111113
if K > 64:
112-
p_h0_2 = tl.make_block_ptr(h0, (K, V), (V, 1), (64, i_v * BV),
113-
(64, BV), (1, 0))
114+
p_h0_2 = tl.make_block_ptr(h0, (K, V), (1, K), (64, i_v * BV),
115+
(64, BV), (0, 1))
114116
b_h2 += tl.load(p_h0_2, boundary_check=(0, 1)).to(tl.float32)
115117
if K > 128:
116-
p_h0_3 = tl.make_block_ptr(h0, (K, V), (V, 1), (128, i_v * BV),
117-
(64, BV), (1, 0))
118+
p_h0_3 = tl.make_block_ptr(h0, (K, V), (1, K), (128, i_v * BV),
119+
(64, BV), (0, 1))
118120
b_h3 += tl.load(p_h0_3, boundary_check=(0, 1)).to(tl.float32)
119121
if K > 192:
120-
p_h0_4 = tl.make_block_ptr(h0, (K, V), (V, 1), (192, i_v * BV),
121-
(64, BV), (1, 0))
122+
p_h0_4 = tl.make_block_ptr(h0, (K, V), (1, K), (192, i_v * BV),
123+
(64, BV), (0, 1))
122124
b_h4 += tl.load(p_h0_4, boundary_check=(0, 1)).to(tl.float32)
123125

124126
# main recurrence
@@ -215,26 +217,26 @@ def chunk_gated_delta_rule_fwd_kernel_h_blockdim64(
215217
b_k = tl.load(p_k, boundary_check=(0, 1))
216218
b_h4 += tl.dot(b_k, b_v_new)
217219

218-
# epilogue
220+
# epilogue — write final state back to pool-layout [slots, HV, V, K].
219221
if STORE_FINAL_STATE or USE_INDEXED_STATE:
220-
p_ht = tl.make_block_ptr(ht, (K, V), (V, 1), (0, i_v * BV), (64, BV),
221-
(1, 0))
222+
p_ht = tl.make_block_ptr(ht, (K, V), (1, K), (0, i_v * BV), (64, BV),
223+
(0, 1))
222224
tl.store(p_ht, b_h1.to(p_ht.dtype.element_ty), boundary_check=(0, 1))
223225
if K > 64:
224-
p_ht = tl.make_block_ptr(ht, (K, V), (V, 1), (64, i_v * BV),
225-
(64, BV), (1, 0))
226+
p_ht = tl.make_block_ptr(ht, (K, V), (1, K), (64, i_v * BV),
227+
(64, BV), (0, 1))
226228
tl.store(p_ht,
227229
b_h2.to(p_ht.dtype.element_ty),
228230
boundary_check=(0, 1))
229231
if K > 128:
230-
p_ht = tl.make_block_ptr(ht, (K, V), (V, 1), (128, i_v * BV),
231-
(64, BV), (1, 0))
232+
p_ht = tl.make_block_ptr(ht, (K, V), (1, K), (128, i_v * BV),
233+
(64, BV), (0, 1))
232234
tl.store(p_ht,
233235
b_h3.to(p_ht.dtype.element_ty),
234236
boundary_check=(0, 1))
235237
if K > 192:
236-
p_ht = tl.make_block_ptr(ht, (K, V), (V, 1), (192, i_v * BV),
237-
(64, BV), (1, 0))
238+
p_ht = tl.make_block_ptr(ht, (K, V), (1, K), (192, i_v * BV),
239+
(64, BV), (0, 1))
238240
tl.store(p_ht,
239241
b_h4.to(p_ht.dtype.element_ty),
240242
boundary_check=(0, 1))
@@ -277,7 +279,9 @@ def chunk_gated_delta_rule_fwd_h(
277279
"Indexed chunk state updates require inplace_indexed_state_update=True."
278280
)
279281
store_final_state_in_kernel = output_final_state and not use_indexed_state
280-
final_state = (k.new_empty(N, H, K, V, dtype=torch.float32)
282+
# Kernel writes final state in [V, K] layout (K innermost) to match the
283+
# pool layout. Allocate accordingly so the tensor's shape reflects memory.
284+
final_state = (k.new_empty(N, H, V, K, dtype=torch.float32)
281285
if store_final_state_in_kernel else None)
282286

283287
v_new = torch.empty_like(u) if save_new_value else None

tensorrt_llm/_torch/modules/fla/flashinfer_chunk.py

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,9 @@
1717
``use_qk_l2norm_in_kernel`` parameter on ``flashinfer.chunk_gated_delta_rule``
1818
is currently a dead arg, see ``flashinfer/gdn_prefill.py:317-356``).
1919
* Pre-gather and post-scatter of indexed SSM state (FlashInfer requires
20-
packed ``[num_seqs, H, D, D]`` fp32 initial/output state).
21-
* State layout: TRT-LLM's Triton uses ``[N, H, K, V]`` ordering of the last
22-
two dims; FlashInfer's prefill kernel uses ``[N, H, V, K]`` (per docstring
23-
in ``flashinfer/gdn_prefill.py``: "The final state layout is ``[N, H, V, K]``";
24-
confirmed by ``flashinfer/tests/gdn/test_prefill_delta_rule.py`` which
25-
transposes the last two dims to compare with the reference implementation).
26-
The wrapper transposes initial_state on the way in and output_state on the
27-
way out so callers see TRT-LLM's ``[N, H, K, V]`` everywhere.
20+
packed ``[num_seqs, H, V, K]`` fp32 initial/output state). TRT-LLM's GDN
21+
state pool uses the same ``[N, H, V, K]`` logical layout, so the adapter
22+
casts/gathers/scatters without transposing the last two dims.
2823
2924
This module is only imported when ``TLLM_USE_FLASHINFER_GDN_PREFILL=1`` is set
3025
at process start; do not import it lazily inside hot paths.
@@ -35,8 +30,8 @@
3530
import torch
3631

3732
from tensorrt_llm._torch.modules.fla.fused_state_io import (
38-
gather_cast_transpose_kv_to_fp32_vk,
39-
transpose_cast_scatter_fp32_vk_to_kv,
33+
cast_scatter_fp32_vk_to_vk,
34+
gather_cast_vk_to_fp32_vk,
4035
)
4136
from tensorrt_llm._torch.modules.fla.l2norm import l2norm_fwd
4237

@@ -107,12 +102,10 @@ def chunk_gated_delta_rule(
107102
q3 = l2norm_fwd(q3)
108103
k3 = l2norm_fwd(k3)
109104

110-
# --- Step 4: gather initial state and convert layout -----------------
111-
# TRT-LLM stores SSM state with last two dims as (K, V); FlashInfer expects
112-
# (V, K). Fuse gather + cast-to-fp32 + transpose + contiguous into a single
113-
# Triton kernel so we get one HBM pass and one launch instead of three
114-
# (``[indices]`` gather, ``.to(fp32)`` cast, ``.transpose.contiguous()`` copy).
115-
gathered_init = gather_cast_transpose_kv_to_fp32_vk(initial_state, initial_state_indices)
105+
# --- Step 4: gather initial state and cast dtype ---------------------
106+
# TRT-LLM's GDN kernels and FlashInfer both use [N, H, V, K] state layout.
107+
# Fuse gather + cast-to-fp32 + contiguous into a single Triton kernel.
108+
gathered_init = gather_cast_vk_to_fp32_vk(initial_state, initial_state_indices)
116109

117110
# --- Step 5+6: call FlashInfer with pre-allocated output/state buffers
118111
# FI 0.6.10 accepts `output=` / `output_state=`; pre-allocating skips its
@@ -160,26 +153,24 @@ def chunk_gated_delta_rule(
160153
)
161154
out_state = None
162155

163-
# --- Step 7: convert state layout back, scatter / return -----------
164-
# Fuse transpose + cast (fp32 → initial_state.dtype) + optional indexed
165-
# scatter into a single Triton pass, mirroring Step 4. The non-inplace
166-
# branch allocates a fresh (K, V) buffer and writes every row; the inplace
167-
# branch writes only the slots named by ``initial_state_indices`` and
168-
# leaves the rest of ``initial_state`` untouched.
156+
# --- Step 7: cast state back, scatter / return ---------------------
157+
# Fuse cast (fp32 -> initial_state.dtype) + optional indexed scatter into a
158+
# single Triton pass, mirroring Step 4. The inplace branch writes only the
159+
# slots named by ``initial_state_indices`` and leaves the rest untouched.
169160
if inplace_indexed_state_update:
170-
transpose_cast_scatter_fp32_vk_to_kv(out_state, initial_state, initial_state_indices)
161+
cast_scatter_fp32_vk_to_vk(out_state, initial_state, initial_state_indices)
171162
final_to_return: Optional[torch.Tensor] = None
172163
elif output_final_state:
173164
num_seqs_out, num_h_out, v_out, k_out = out_state.shape
174165
final_to_return = torch.empty(
175166
num_seqs_out,
176167
num_h_out,
177-
k_out,
178168
v_out,
169+
k_out,
179170
dtype=initial_state.dtype,
180171
device=out_state.device,
181172
)
182-
transpose_cast_scatter_fp32_vk_to_kv(out_state, final_to_return, None)
173+
cast_scatter_fp32_vk_to_vk(out_state, final_to_return, None)
183174
else:
184175
final_to_return = None
185176

tensorrt_llm/_torch/modules/fla/fused_recurrent.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ def fused_recurrent_gated_delta_rule_fwd_kernel(
7474

7575
b_h = tl.zeros([BK, BV], dtype=tl.float32)
7676
if USE_INITIAL_STATE:
77-
p_h0 = h0 + i_nh * K * V + o_k[:, None] * V + o_v[None, :]
77+
# Pool layout [N, HV, V, K] with K innermost: offset v*K + k.
78+
p_h0 = h0 + i_nh * V * K + o_k[:, None] + o_v[None, :] * K
7879
b_h += tl.load(p_h0, mask=mask_h, other=0).to(tl.float32)
7980

8081
for _ in range(0, T):
@@ -110,7 +111,8 @@ def fused_recurrent_gated_delta_rule_fwd_kernel(
110111
p_beta += HV * (V if IS_BETA_HEADWISE else 1)
111112

112113
if STORE_FINAL_STATE:
113-
p_ht = ht + i_nh * K * V + o_k[:, None] * V + o_v[None, :]
114+
# Pool layout [N, HV, V, K].
115+
p_ht = ht + i_nh * V * K + o_k[:, None] + o_v[None, :] * K
114116
tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), mask=mask_h)
115117

116118

@@ -137,7 +139,8 @@ def fused_recurrent_gated_delta_rule_fwd(
137139

138140
o = q.new_empty(NK, *v.shape)
139141
if output_final_state:
140-
final_state = q.new_empty(N, HV, K, V, dtype=torch.float32)
142+
# Pool layout: [N, HV, V, K] (K innermost).
143+
final_state = q.new_empty(N, HV, V, K, dtype=torch.float32)
141144
else:
142145
final_state = None
143146

@@ -240,19 +243,19 @@ def fused_recurrent_gated_delta_rule(
240243
Scale factor for the RetNet attention scores.
241244
If not provided, it will default to `1 / sqrt(K)`. Default: `None`.
242245
initial_state (Optional[torch.Tensor]):
243-
Initial state of shape `[N, HV, K, V]` for `N` input sequences.
246+
Initial state of shape `[N, HV, V, K]` for `N` input sequences.
244247
For equal-length input sequences, `N` equals the batch size `B`.
245248
Default: `None`.
246249
output_final_state (Optional[bool]):
247-
Whether to output the final state of shape `[N, HV, K, V]`. Default: `False`.
250+
Whether to output the final state of shape `[N, HV, V, K]`. Default: `False`.
248251
cu_seqlens (torch.LongTensor):
249252
Cumulative sequence lengths of shape `[N+1]` used for variable-length training,
250253
consistent with the FlashAttention API.
251254
Returns:
252255
o (torch.Tensor):
253256
Outputs of shape `[B, T, HV, V]`.
254257
final_state (torch.Tensor):
255-
Final state of shape `[N, HV, K, V]` if `output_final_state=True` else `None`.
258+
Final state of shape `[N, HV, V, K]` if `output_final_state=True` else `None`.
256259
Examples::
257260
>>> import torch
258261
>>> import torch.nn.functional as F
@@ -265,7 +268,7 @@ def fused_recurrent_gated_delta_rule(
265268
>>> v = torch.randn(B, T, HV, V, device='cuda')
266269
>>> g = F.logsigmoid(torch.rand(B, T, HV, device='cuda'))
267270
>>> beta = torch.rand(B, T, HV, device='cuda').sigmoid()
268-
>>> h0 = torch.randn(B, HV, K, V, device='cuda')
271+
>>> h0 = torch.randn(B, HV, V, K, device='cuda')
269272
>>> o, ht = fused_gated_recurrent_delta_rule(
270273
q, k, v, g, beta,
271274
initial_state=h0,
@@ -387,8 +390,9 @@ def fused_recurrent_gated_delta_rule_update_fwd_kernel(
387390
idx = tl.load(h0_indices + i_n)
388391
# Add bounds checking for idx
389392
if idx >= 0: # Assuming negative indices are invalid
390-
p_h0 = (h0_source + idx * HV * K * V + i_hv * K * V +
391-
o_k[:, None] * V + o_v[None, :])
393+
# Pool layout [slots, HV, V, K], K innermost (stride 1).
394+
p_h0 = (h0_source + idx * HV * V * K + i_hv * V * K + o_k[:, None] +
395+
o_v[None, :] * K)
392396
b_h += tl.load(p_h0, mask=mask_h, other=0).to(tl.float32)
393397

394398
# Prepare intermediate state cache variables if enabled
@@ -427,12 +431,12 @@ def fused_recurrent_gated_delta_rule_update_fwd_kernel(
427431
# store intermediate states if enabled
428432
if CACHE_INTERMEDIATE_STATES:
429433
if cache_idx >= 0:
430-
# Compute cache pointer for this step
431-
step_offset = step_idx * HV * K * V
434+
# Match the SSM pool layout [slots, HV, V, K] with K innermost.
435+
step_offset = step_idx * HV * V * K
432436
cache_ptr = (intermediate_states_buffer +
433-
cache_idx * cache_steps * HV * K * V +
434-
step_offset + i_hv * K * V + o_k[:, None] * V +
435-
o_v[None, :])
437+
cache_idx * cache_steps * HV * V * K +
438+
step_offset + i_hv * V * K + o_k[:, None] +
439+
o_v[None, :] * K)
436440
tl.store(cache_ptr,
437441
b_h.to(cache_ptr.dtype.element_ty),
438442
mask=mask_h)
@@ -447,12 +451,12 @@ def fused_recurrent_gated_delta_rule_update_fwd_kernel(
447451
p_beta += HV * (V if IS_BETA_HEADWISE else 1)
448452

449453
# Store final state back to h0_source with bounds checking
450-
# ssm states
454+
# ssm states (pool layout [slots, HV, V, K], K innermost).
451455
if not DISABLE_STATE_UPDATE:
452456
idx = tl.load(h0_indices + i_n)
453457
if idx >= 0: # Add bounds checking
454-
p_h0 = (h0_source + idx * HV * K * V + i_hv * K * V +
455-
o_k[:, None] * V + o_v[None, :])
458+
p_h0 = (h0_source + idx * HV * V * K + i_hv * V * K + o_k[:, None] +
459+
o_v[None, :] * K)
456460
tl.store(p_h0, b_h.to(p_h0.dtype.element_ty), mask=mask_h)
457461

458462

0 commit comments

Comments
 (0)