Repository navigation
Conversation
6 tasks done
6 tasks done
Contributor
|
This pull request has merge conflicts that must be resolved before it can be |
GLM-5.3-Flash decodes each KDA layer with three Triton kernels (conv update, recurrence, gated RMSNorm). FlashInfer's fused_kda_decode does all three in one CuTe DSL launch per layer (packed_fused_kda_decode for draft-verify rows), as Kimi K3 already uses. - Plain decode steps, the decode rows of mixed steps and draft-verify rows run the FlashInfer kernels. In a mixed step the conv skips the decode rows (null slots) and the prefill kernel sees only the builder's prefill tail, as the GDN layer peels its decode rows; FlashKDA's prepare kernel no longer scans the decode rows. - Selection follows Kimi K3: additional_config kda_decode_backend / kda_spec_decode_backend (--kda-decode-backend) in auto / flashinfer / triton. auto uses FlashInfer on SM10x when a static check accepts the layer: 8/12/16/24/32/48/64/96 local heads; draft-verify rows 12-96 heads and the SD conv-state layout. - Requires FlashInfer 0.7.3: flashinfer-ai/flashinfer#7132 adds 16/64 heads (TP4, TP1/DP attention) and the DS conv-state layout. - The fused kernel applies o_norm, so with it _forward returns normed output for every step type (a breakable graph replays the code after _forward as captured, whatever the live step). - FlashInfer's [3, width, P] conv weight and fp32 norm weight are built from the fused conv1d parameter and o_norm in process_weights_after_loading and refilled in place on reload. Pure decode steps run the fused kernel in one FULL CUDA graph; eager (PIECEWISE) mixed steps also enqueue fewer kernels, and the decode-row peel keeps one-token rows out of the chunked prefill kernel. Error vs an fp64 reference equals the Triton path's. End-to-end serving results (GB300 and GB200) are in the PR description. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Ao Tang <aot@nvidia.com>
kda_prefill_backend=flashinfer (--kda-prefill-backend flashinfer, already a CLI choice; GLM-5.3 rejected it) runs the chunked KDA prefill with FlashInfer's recurrent_kda(backend="cute-dsl"): chunk preparation and the recurrence in one kernel, from the raw gate and beta logits, reading and writing the recurrent state pool in place through ssm_state_indices. The gather / scatter of the FlashKDA path and its final-state buffer go away. - FlashInfer has no has-initial-state flag, so fresh sequences' slots are zeroed first (one index_fill; other rows zero the unused null slot). - A step whose prefill rows total one token uses the recurrent kernel. - The slot indices are passed contiguous: with speculative decoding they are column 0 of the [N, 1 + num_spec] block table, which FlashInfer rejects. - Steps with draft-verify rows write the prefill rows to their own buffer and scatter them like FlashKDA's; no FlashKDA workspace is involved. - Opt-in: auto stays FlashKDA. Prefix-cache checkpoints are not produced with this backend (as for Kimi K3). Supported on SM100 / SM103 / SM107 (SM107 from flashinfer-ai/flashinfer#6201, in FlashInfer 0.7.3). Error vs an fp64 reference (H64, 8 x 8192 prefill tokens): 4.2e-3, vs 5.1e-3 for FlashKDA. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Ao Tang <aot@nvidia.com>
suiyoubi
force-pushed
the
aot/glm53-upstream-fi-kda-main
branch
from
November 4, 2026 15:34
81a449c to
e35dc06
Compare
suiyoubi
marked this pull request as ready for review
November 4, 2026 15:40
suiyoubi
requested review from
AndreasKaratzas,
DarkLight1337,
hmellor and
ywang96
as code owners
November 4, 2026 15:40
suiyoubi
force-pushed
the
aot/glm53-upstream-fi-kda-main
branch
from
November 4, 2026 18:42
e35dc06 to
eb06372
Compare
Contributor
|
This pull request has merge conflicts that must be resolved before it can be |
suiyoubi
force-pushed
the
aot/glm53-upstream-fi-kda-main
branch
from
November 4, 2026 19:34
eb06372 to
6a0ad6d
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
This PR moves GLM-5.3-Flash's KDA (Kimi Delta Attention) layers onto FlashInfer kernels:
fused_kda_decode);packed_fused_kda_decode);recurrent_kda(backend="cute-dsl")).Kimi K3 already uses these kernels through the same
vllm.utils.flashinferwrappers and the samekda_decode_backend/kda_spec_decode_backend/kda_prefill_backendoptions. The model has 34 KDA layers(64 heads of 128). Today each decode step runs three Triton kernels per layer, and mixed steps send their decode
rows through the chunked prefill kernel.
auto; activates where FlashInfer supports the layer.--kda-prefill-backend flashinfer.Important
Requires FlashInfer 0.7.3. This PR assumes the FlashInfer pin bump to 0.7.3 (a separate PR) has landed.
0.7.3 contains flashinfer-ai/flashinfer#7132 (16/64 heads in
fused_kda_decode/packed_fused_kda_decode, andthe DS conv-state layout) and #6201 (SM107 prefill). GLM-5.3-Flash's 64-head (TP1 / DP attention) and 16-head
(TP4) ranks, and the DS layout that NIXL P/D needs, rely on #7132. With the current pin (0.7.2.post1) the static
support checks accept configurations that 0.7.2 rejects at its first call, so this PR should merge after the bump.
Claims
FULL_AND_PIECEWISE, KDA captured), so the GPU times above arewhat serving pays; in eager (PIECEWISE, mixed/prefill) steps the host enqueue also drops, 3.0 → 0.95 ms per
34 layers.
6.96 → 2.72 ms). Most of it is the decode-row peel.
at H64 or a few at H16, mostly from the call's host time (about 95 µs vs FlashKDA's 18 µs on VR200, where
FlashInfer's kernel is faster). This is why it stays opt-in.
+13.5% / +14.8% on GB200, which is KV-capacity bound;
and mixed steps send their decode rows to that kernel instead of the chunked prefill kernel (the decode-row peel).
low concurrency, mostly from the call's host time (Follow-ups). This is why it stays opt-in.
including disaggregated P/D; performance numbers for VR200 are not included here.
states) are bit-identical to the Triton path.
Validation
Unit tests
tests/models/glm5next/test_kda_flashinfer_decode.py(new):reference, with null and padding slots;
tests/models/glm5next/test_kda_flashinfer_prefill.py(new):1-token and >16-token sequences;
Existing:
tests/models/glm5next/(one test updated for the_forwardsignature) andtests/models/kimi_k3/test_kda.py.The strided-slot-index test fails without the prefill fix (VR200, the same test file run against the prefill commit
without
.contiguous()) and passes with it:Accuracy (GSM8K 40-shot, 1319 questions, greedy, 6 runs per arm, agg DEP2, mnbt 32768)
Greedy runs differ by about ±1 point from batch-dependent numerics, so 6 runs per arm were taken; the difference is
+0.03 points (t = 0.06). Reference from earlier VR200 work: 91.40%.
MTP (speculative decoding, 2 draft tokens)
GSM8K 40-shot as above with
--speculative-config '{"method":"mtp","num_speculative_tokens":2}', agg DEP2, VR200,nightly c50aca1. Covers the draft-verify path (
packed_fused_kda_decode) and prefill/decode with spec-decodingmetadata.
Accuracy is within the ±1-point spread of greedy runs seen above, and draft acceptance is unchanged. The PR arm's
logs show the FlashInfer prefill and the packed decode (3 tokens per row) in use.
Serving with MTP (sa-bench 8K/1K random, agg DEP2, C128 and C256, VR200) also completed every request in both arms,
with the same draft acceptance.
This check found a bug in an earlier revision: under speculative decoding the builder's non-spec slot indices are a
strided column of the [N, 1 + num_spec] block table, which FlashInfer's prefill rejects. Warmup failed with
ValueError: backend='cute-dsl' does not support this recurrent_kda prefill contract. The prefill now passescontiguous indices, and a unit test covers it.
Serving (GB300 and GB200)
Native srt-slurm sa-bench, ISL 8192 / OSL 1024, random data, Dynamo frontend, on Lyris (GB300 and GB200, 4 GPUs per
node). Settings:
max-num-batched-tokens32768;Base: nightly 8e2beaa. PR: the same image + this branch's
kda.py+ the final flashinfer-ai/flashinfer#7132files, with
--kda-prefill-backend flashinfer. Single runs; all points completed every request. The same recipeswere also run on VR200.
queue and every step is a mixed step. That is where the decode-row peel pays most.
requests queue (mean TTFT 58 s for base at C512). The PR's gain there is admission: faster mixed steps cut mean TTFT
58 → 42 s (C512) and 176 → 145 s (C1024), while per-token decode time is slightly higher with the larger running
batch. Hence +14.8% at C1024 rather than about +25%.
rank, experts over 2 GPUs). A TP2 prefill does not work with a TP1-per-rank decode for this model: the hybrid KDA
block size follows the per-rank KDA state, and NIXL rejects the mismatched blocks (8576 vs 4352).
147 → 169 ms, C8 652 → 681 ms). These steps prefill one or a few sequences at 16 heads. On GB300 the FlashInfer
prefill call costs about 190 µs of host time vs FlashKDA's 50–56 µs (34 layers per step), and at 1–4 sequences its
kernel is also 7–13% slower; this is why the FlashInfer prefill stays opt-in. Decode alone was not run on GB300/GB200.
Details
Decode.
flashinfer_fused_kda_decodeonce per layer: conv + SiLU with the conv-state update,the recurrence, and the gated RMSNorm, writing the layer output buffer. Slot ≤ 0 rows are null rows.
flashinfer_packed_fused_kda_decodewith the existingspec_state_indices_tensor/num_accepted_tokens. Mixed steps with verify rows keep the Triton spec path.kernel. The conv then skips those rows as null slots, so the builder's existing causal-conv metadata stays
valid. The prefill kernel uses the builder's prefill tail (
prefill_query_start_loc/prefill_state_indices/
prefill_has_initial_state), as the GDN layer already does. FlashKDA's prepare kernel thus no longer scanshundreds of one-token sequences, and gather / scatter no longer move their states. With prefix-cache
checkpoints the step keeps today's path.
--kda-decode-backend(additional_config.kda_decode_backend) andadditional_config.kda_spec_decode_backendtakeauto/flashinfer/triton._is_flashinfer_kda_decode_supported): CUDA SM10x, D=128, conv width4, bf16 activations and conv state, fp32 or bf16 recurrent state, a bounded gate, and the head counts and
layouts of FlashInfer with [Frontend] Support embeddings in the run_batch API #7132 (single-token rows: 8/12/16/24/32/48/64/96 heads, SD or DS; draft-verify rows:
12–96 heads, SD only, at most 7 draft tokens).
autofalls back to Triton when the check fails;flashinferraises._is_flashinfer_kda_prefill_supported: SM100 / SM103 / SM107,D=128, bf16 activations, fp32 or bf16 state, bounded gate) and raises at startup when it fails.
o_norm, so with the FlashInfer backend_forwardreturns normedoutput for every step type. In PIECEWISE (mixed/prefill) steps, breakable CUDA graphs run
_forwardeagerly but the code after it isreplayed as captured, so whether to norm must not depend on the step type.
Prefill (opt-in).
--kda-prefill-backend flashinfer(already a CLI choice, previously rejected for GLM-5.3) callsrecurrent_kda(backend="cute-dsl")with the raw gate and beta logits.ssm_state_indices, so there is no gather,scatter or final-state buffer.
index_fill_; rows with state zero the unused null slot.(
kda_prefill_checkpoint_alignmentis None).Numerics. FlashInfer's kernels are not bit-identical to Triton/FlashKDA (different summation orders). Against an
fp32/fp64 reference they are as close or closer:
GSM8K above is the end-to-end check.
Limitations / follow-ups.
release that has it before this PR leaves draft.
wrapper costs more per call than its cached kernel launch; a cheaper FlashInfer launch path would help low
concurrency.
and a pipelined bf16-state decode kernel.
tests/models/glm5nextruns on H200 (SM90), where these tests skip or exercise FlashInfer's kernels onlywhere FlashInfer supports them. SM10x coverage needs a Blackwell lane.
--kda-prefill-backend flashinfer, TTFT at low concurrency is higher(GB300 TP4: C1 143 → 163 ms, C8 603 → 650 ms). Most of the cost is the prefill call's host time, paid 34 times per
eager prefill step: on GB300 about 190 µs on FlashInfer's two-kernel route (2 x sequences x heads <= SMs) and
110 µs on its one-kernel route, vs FlashKDA's 50–56 µs; at 1–4 sequences the kernel is also 7–13% slower than
FlashKDA's. A prototype that routes such steps to FlashKDA brings C1 TTFT back to base, with unchanged throughput,
but reserves FlashKDA's workspace (less KV cache) and ties the cutover to FlashInfer's routing rule. It is left out
of this PR; the host overhead is reported to FlashInfer, whose fix would make it unnecessary.
Pull Request Checklist
I used vLLM's
/pr-checklistskill. (Mandatory for agents, optional for humans).AI assistance was used during the creation of this PR (Claude Code); the submitter reviewed every line.
Design Fit
Testing and Validation
Code Quality and Style
Pull Request Contents
Need help on this PR? Tag
@codesmith-botwith what you need.Need help on this PR? Tag
@codesmith-botwith what you need.