Skip to content

[#20551][perf] MLA DSpark: run each drafter layer's kv path beside its q path - #20552

Draft
lucifer1004 wants to merge 3 commits into
NVIDIA:mainfrom
lucifer1004:upstream/dspark-mla-qkv-streams
Draft

lucifer1004 wants to merge 3 commits into
NVIDIA:mainfrom
lucifer1004:upstream/dspark-mla-qkv-streams

Conversation

@lucifer1004

@lucifer1004 lucifer1004 commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Fixes #20551. Stacked on #20550, so this branch also contains that PR's commit. Review only the last commit, "MLA DSpark: run each drafter layer's kv path beside its q path".

Note: #19275 moves cute_dsl_kernels/blackwell/dspark_rmsnorm_rope.py to attention/kernels/blackwell/. Whichever of the two lands second updates the one DSparkRMSNormRoPEKernel import in modeling_dspark.py.

In MLADSparkForCausalLM.dflash_forward, each drafter layer runs its q path and its kv path one after another on one stream, although they are independent until attention. With TLLM_DSPARK_MLA_SPLIT_QKV_STREAMS=1, read per call and applied on the kernel block-decode paths only:

  • after input_layernorm, the layer forks to a lazily created aux stream;
  • the aux stream runs kv_a_proj_with_mqa -> RMSNorm/RoPE -> block write into the context pool;
  • the main stream runs q_a_proj -> q_a_layernorm -> q_b_proj -> rope_into -> k absorption;
  • the main stream joins before _mla_paged_attention, which skips its own block write (block_written=True).

Everything the aux stream reads is produced before the fork, and everything it writes is read only after the join. The next layer's fork orders the aux stream after this layer's main-stream work.

On the CuTe DSL block-decode variant, the RMSNorm/RoPE kernel stores the block latents straight into the pool at the fixup's pages / offsets, without a staging buffer and scatter. That kernel (_RMSNormRoPEPoolWriteKernel) is DSparkRMSNormRoPEKernel with only the store index changed. Other variants keep the staged write.

Two seams (_split_qkv_use_aux_stream, _split_qkv_direct_pool_write) let the two halves be measured separately.

Performance, Rubin, TP8 across 2 nodes (MNNVL), Kimi K3 with an MLA DSpark drafter, CUDA graphs, single request, decode step time, on top of #20550. This is a cross-job comparison, so the differences are close to cross-node noise; on a later stack, turning the switch off cost about +0.06 ms per step.

2K 32K 64K 96K 128K 393K
split q / kv streams -0.053 ms -0.038 ms -0.025 ms -0.037 ms -0.023 ms -0.018 ms

Accepted tokens per step, accuracy and needle checks were unchanged.

In a single-GPU microbenchmark (Rubin single GPU, 200-SM engineering sample, drafter forward under CUDA graph replay), it saves 56-68 µs per step at B = 1 / 4. Almost all of that comes from the two streams; the direct pool write alone saves 6-19 µs but adds about nothing on top of the streams, because it is hidden behind the q path.

Test Coverage

PR Checklist

  • PR description clearly explains what and why.
  • PR follows the TRT-LLM coding guidelines to the best of my knowledge.
  • Test cases are provided for new code paths.
  • No API changes, no new dependencies, no CODEOWNERS changes.
  • Please check this after reviewing the above items as appropriate for this PR.

…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>
…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>
…ide its q path

TLLM_DSPARK_MLA_SPLIT_QKV_STREAMS=1: in the kernel block-decode paths,
every drafter layer runs kv_a -> RMSNorm/RoPE -> pool write on an aux
stream while the q path runs on the main stream, joined before attention.
On the CuTe DSL variant the RMSNorm/RoPE kernel stores the block latents
straight into the pool.

Signed-off-by: Zihua Wu <13583761+lucifer1004@users.noreply.github.com>
@lucifer1004
lucifer1004 force-pushed the upstream/dspark-mla-qkv-streams branch from fee0fb7 to 77ad5ad Compare October 19, 2026 03:58

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Performance]: MLA DSpark drafter runs each layer's q and kv paths serially on one stream

1 participant