Repository navigation
[#20548][perf] DFlash / MLA DSpark: fuse the decode-step drafter bookkeeping into Triton kernels - #20550
Draft
lucifer1004 wants to merge 2 commits into
Draft
[#20548][perf] DFlash / MLA DSpark: fuse the decode-step drafter bookkeeping into Triton kernels#20550lucifer1004 wants to merge 2 commits into
lucifer1004 wants to merge 2 commits into
Conversation
…r bookkeeping into Triton kernels TLLM_DFLASH_FUSED_PROLOGUE=1 runs the draft block-table refresh, the gen-request bookkeeping in prepare_1st_drafter_inputs, the single-latent context write, and the MLA drafter's block fixup and block-latent pool write as single Triton kernels, bit-exact with the torch code they replace. TLLM_DFLASH_OVERLAP_CONTEXT_PROJECTION=1 forks the gen tokens' context projection onto an aux stream ahead of sample/accept. Signed-off-by: Zihua Wu <13583761+lucifer1004@users.noreply.github.com>
This was referenced Oct 19, 2026
…xup builder too With TLLM_DFLASH_FUSED_PROLOGUE=1 the block decode builds its fixup with _build_mla_block_fixup_fused, so the branch check counts either builder (and requires the fused one when the env is on); the numeric comparison is unchanged. Signed-off-by: Zihua Wu <13583761+lucifer1004@users.noreply.github.com>
longlee0622
added a commit
to longlee0622/TensorRT-LLM
that referenced
this pull request
Oct 27, 2026
…latent drafter bookkeeping Replaces chains of small eager ops in the one-model spec-decode step with Triton kernels that produce the same values (new spec_tail_kernels.py): * Strict acceptance of a uniform-width draft (SpecWorkerBase ._sample_and_accept_draft_tokens_base, every one-model worker) is one launch; the synthetic forced-acceptance override still runs after it. Ragged verification keeps its path. * The DFlash draft-KV block-table decode (blocks per request, clamped and divided offsets) is one launch. * For the generation requests of a DFlash step (context requests may precede them) whose drafter context is a paged single-latent cache (the MLA DSpark drafter on the draft KV manager's pool), the per-request bookkeeping runs in one kernel -- bonus token, query and RoPE positions, destination pages / offsets / write mask of the context latents, ctx_len advance with the dummy slot emptied, the advertised context length, seq_lens -- and one kernel stores the context latents of every drafter layer. The same kernel places the block's own positions in the pages (DraftBlockPlacement), which the MLA drafter's block decode takes instead of re-gathering the page table. Every drafter's dflash_forward takes the placement as an optional block_placement argument; drafters that append the block themselves ignore it. Takes effect by default: strict acceptance for every non-ragged one-model verification; the block-table decode and the paged-latent preparation for DFlash/DSpark drafters bound to the draft KV manager's pool, the latter for single-latent drafters other than LiLiCorr (every loaded DFlash drafter owns the fc + hidden_norm target projection), decided when the drafter context binds to the pool, on steps with captured target features drafting from all K+1 target tokens. Replaces TLLM_SPEC_TAIL_FUSION of the reference branch; no switch. Not ported from the reference set: the next-step token and block gather-id kernels, the V un-absorb into the o_proj layout, the block-latent scatter and the vocab-sharded embedding lookup (a few launches each per step). Evidence (reference branch, Rubin): part of the Tier-0 GEN bundle, helix16 b1 12.23 -> 11.45 ms (-6.4%, job 773254); dep16 b8 integ1 + Tier-0 40.11 -> 34.71 ms (incl. integ1 changes); not isolated. The reference's tail fusion covers about the -0.2 ms/step that upstream PR NVIDIA#20550 measures for the same drafter bookkeeping (status 2026-10-19). Platform: N. Tests: tests/unittest/_torch/speculative/test_spec_tail_kernels.py (strict acceptance and block-table decode vs the eager chains, the latter with an all-negative row, an all-zero row and about half negative or zero entries; the worker's paged-latent preparation vs its eager path: drafter inputs, ctx_len, seq_lens, pool contents bit for bit from a nonzero pool, with every entry outside the step's destinations unchanged and every destination written, block placement vs _build_mla_block_fixup, incl. dummy slots, the max_ctx ceiling and contexts that outrun their pages), registered in l0_b200. GB300 results (NSC LHR, base image + this tree's Python): new tests pass, jobs 316358 (single GPU, 41 passed) and 316359 (TP2/TP4, 13 passed); tests/unittest/_torch/speculative regression 316234 has the same failure set as base 316233 (pre-existing HF-download failures) plus none of its own. OCI AGA review-fix reruns: jobs 1369108, 1390012. Review fixes, NSC LHR GB300 (main-f298722651 image + this tree's Python): job 416485, speculative unit suites 79 passed, multi-GPU 18 passed, hw_agnostic DFlash/DSpark suites 582 passed with the same 5 HF-download failures as the unchanged PR tip (control job 416484). Signed-off-by: Jonas Li <6110159+longlee0622@users.noreply.github.com>
longlee0622
added a commit
to longlee0622/TensorRT-LLM
that referenced
this pull request
Oct 27, 2026
…latent drafter bookkeeping Replaces chains of small eager ops in the one-model spec-decode step with Triton kernels that produce the same values (new spec_tail_kernels.py): * Strict acceptance of a uniform-width draft (SpecWorkerBase ._sample_and_accept_draft_tokens_base, every one-model worker) is one launch; the synthetic forced-acceptance override still runs after it. Ragged verification keeps its path. * The DFlash draft-KV block-table decode (blocks per request, clamped and divided offsets) is one launch. * For the generation requests of a DFlash step (context requests may precede them) whose drafter context is a paged single-latent cache (the MLA DSpark drafter on the draft KV manager's pool), the per-request bookkeeping runs in one kernel -- bonus token, query and RoPE positions, destination pages / offsets / write mask of the context latents, ctx_len advance with the dummy slot emptied, the advertised context length, seq_lens -- and one kernel stores the context latents of every drafter layer. The same kernel places the block's own positions in the pages (DraftBlockPlacement), which the MLA drafter's block decode takes instead of re-gathering the page table. Every drafter's dflash_forward takes the placement as an optional block_placement argument; drafters that append the block themselves ignore it. Takes effect by default: strict acceptance for every non-ragged one-model verification; the block-table decode and the paged-latent preparation for DFlash/DSpark drafters bound to the draft KV manager's pool, the latter for single-latent drafters other than LiLiCorr (every loaded DFlash drafter owns the fc + hidden_norm target projection), decided when the drafter context binds to the pool, on steps with captured target features drafting from all K+1 target tokens. Replaces TLLM_SPEC_TAIL_FUSION of the reference branch; no switch. Not ported from the reference set: the next-step token and block gather-id kernels, the V un-absorb into the o_proj layout, the block-latent scatter and the vocab-sharded embedding lookup (a few launches each per step). Evidence (reference branch, Rubin): part of the Tier-0 GEN bundle, helix16 b1 12.23 -> 11.45 ms (-6.4%, job 773254); dep16 b8 integ1 + Tier-0 40.11 -> 34.71 ms (incl. integ1 changes); not isolated. The reference's tail fusion covers about the -0.2 ms/step that upstream PR NVIDIA#20550 measures for the same drafter bookkeeping (status 2026-10-19). Platform: N. Tests: tests/unittest/_torch/speculative/test_spec_tail_kernels.py (strict acceptance and block-table decode vs the eager chains, the latter with an all-negative row, an all-zero row and about half negative or zero entries; the worker's paged-latent preparation vs its eager path: drafter inputs, ctx_len, seq_lens, pool contents bit for bit from a nonzero pool, with every entry outside the step's destinations unchanged and every destination written, block placement vs _build_mla_block_fixup, incl. dummy slots, the max_ctx ceiling and contexts that outrun their pages), registered in l0_b200. GB300 results (NSC LHR, base image + this tree's Python): new tests pass, jobs 316358 (single GPU, 41 passed) and 316359 (TP2/TP4, 13 passed); tests/unittest/_torch/speculative regression 316234 has the same failure set as base 316233 (pre-existing HF-download failures) plus none of its own. OCI AGA review-fix reruns: jobs 1369108, 1390012. Review fixes, NSC LHR GB300 (main-f298722651 image + this tree's Python): job 416485, speculative unit suites 79 passed, multi-GPU 18 passed, hw_agnostic DFlash/DSpark suites 582 passed with the same 5 HF-download failures as the unchanged PR tip (control job 416484). Signed-off-by: Jonas Li <6110159+longlee0622@users.noreply.github.com>
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.
Description
Fixes #20548.
Each decode step of DFlash / DSpark with an MLA drafter runs a long chain of small torch kernels around the drafter. This PR adds two opt-in switches. Both are read per call, so a CUDA graph keeps what was read at capture, and both are bit-exact with the code they replace.
TLLM_DFLASH_FUSED_PROLOGUE=1dflash.py: these groups now run as one Triton kernel each, instead of chains of small torch kernels:_refresh_ctx_block_tables: page counts and decoded table;prepare_1st_drafter_inputs: bonus token, query and context positions, embedding ids;ctx_lenadvance with the clamp, dummy-slot reset and advertised context length;_store_context_kv_pagedscatter used by prefill.modeling_dspark.py(MLADSparkForCausalLM):index_put;TLLM_DFLASH_OVERLAP_CONTEXT_PROJECTION=1ctx_len; nothing between the fork and the join writes either.Performance, Rubin, TP8 across 2 nodes (MNNVL), Kimi K3 with an MLA DSpark drafter, CUDA graphs, single request, decode step time. Measured with the same change on our integration branch, against the same stack without it:
Accepted tokens per step, accuracy and needle checks were unchanged.
End to end, the overlap switch is within noise on this configuration. In a single-GPU microbenchmark (Rubin single GPU, 200-SM engineering sample, whole worker step under CUDA graph replay), it saved 53-84 µs on top of the fusion, bounded by the length of the sample / accept window it hides behind. It is kept as a separate switch so it can be measured on configurations with a longer window. I am happy to drop it from this PR if reviewers prefer.
Test Coverage
tests/unittest/_torch/speculative/test_dflash_fused_prologue.py(any CUDA GPU; added tol0_b200.yml). Each fused kernel is checked bitwise against the torch code it replaces:_refresh_ctx_block_tables, on a strided view of the offsets tensor;_store_context_kv_paged;dflash_noise_block_embedding;ctx_lenbookkeeping vs. the torch store,_advance_ctx_lenanddflash_allocated_ctx_limit;_build_mla_block_fixup(all fields), and the block-latent pool write on vs. off.tests/unittest/_torch/speculative/hw_agnostic/test_kimi_k3_dspark_semantics.py::test_mla_dspark_block_decode_matches_unabsorbed_referencecounts which fixup builder the paged block decode used. WithTLLM_DFLASH_FUSED_PROLOGUE=1the fused builder runs instead, so the check now accepts either builder (and requires the fused one when the env is on); the numeric comparison is unchanged.test_dflash_fused_prologue.py: 17 passed (17 failed on main, as expected).hw_agnostic -k "dflash or dspark": the same pass / fail set as main (the existing failures there need the flash-attn package).TLLM_DFLASH_FUSED_PROLOGUE=1 TLLM_DFLASH_OVERLAP_CONTEXT_PROJECTION=1: one extra failure, the builder count above (a path assertion before the numeric check, not a numeric mismatch). Fixed in the test by the latest commit; the rerun is pending.test_dspark_cute_dsl_attention.py/test_dspark_cute_dsl_rmsnorm_rope.pyskip on SM107 (their skipif allows SM100 / SM103 only); pending on SM100 / SM103.TRITON_INTERPRET=1on CPU: the new test passes (17); mutating any of four kernels makes 11 of them fail.DSparkWorkerwithMLADSparkForCausalLMat Kimi K3 per-rank shapes (5 layers, 8 heads, page 32, block 7):PR Checklist