Repository navigation
[Perf][DSA]: Cut MTP decode overhead with live-length-bounded replay metadata and a greedy-draft argmax exchange - #46630
Draft
mattteochen wants to merge 3 commits into
Draft
mattteochen wants to merge 3 commits into
mattteochen wants to merge 3 commits into
Conversation
The DSA replay metadata kernels (decode, target verify, draft extend) launch 1 + rows * cdiv(width, 128) programs, where width is the page-table width, i.e. --context-length. At a 1M-token context that is 8193 programs per row, almost all of which load the row's KV length and exit, so the cost scales with the configured context instead of the live one. Add bounded variants (kernels/ops/attention/dsa_metadata_bounded.py): each request gets min(width blocks, budget // requests, 256) programs that stride over its live column blocks, read from seq_lens on device. The grid depends only on (requests, width, budget), so the launch stays CUDA-graph safe, and the written elements and values are identical to the unbounded kernels. Target verify loads each request's req_to_token block once for its next_n rows; the static-length draft extend takes the length as a scalar, so the replay no longer launches a torch.full for it. DSAMetadataManagementMixin swaps in the bounded kernels on CUDA with index_kpool 1 and no KPool metadata fusion. SGLANG_DSA_BOUNDED_REPLAY_METADATA (default on) is the kill switch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DeepseekSparseAttnMultiStepBackend's replay precomputes the decode metadata into a scratch page table as wide as --context-length, then copies its full width into every step backend (fused_metadata_copy_multi for steps 0-2, fused_metadata_copy for the rest). Only the live prefix is meaningful. When the bounded replay metadata is active, write every step backend's graph metadata straight from req_to_token with fused_dsa_decode_metadata_multi (one launch for up to four steps, same program budget as the other bounded launches), then run the per-step tail of the replay. Values match the precompute + copy on the live prefix. Draft-decode steps that reuse the draft-extend indexer top-k never run the paged-MQA indexer, so their DeepGEMM schedule refresh is dead work; skip it for those steps. Unseeded steps keep the old refresh steps. draft_decode_metadata_writable gates the path per captured bs (bounded metadata on, not FlashMLA decode, bs captured by every step backend, matching table presence, shapes and strides); otherwise the existing precompute + copy replay runs unchanged. No new flag: SGLANG_DSA_BOUNDED_REPLAY_METADATA=0 turns it off with the bounded kernels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ead of gathering logits A greedy topk=1 EAGLE draft consumes only the per-row argmax of its LM-head logits, yet every draft step (and the draft extend) all-gathers rows x vocab logits, casts them to fp32 and runs an argmax. Instead, each TP rank reduces its own vocab shard to one candidate per (rank, 8192-column split), written as 4 fp32 lanes (value bits hi/lo, global column, first NaN column) into its own slot of a zeroed [rows, tp * splits * 4] buffer. The TP group's sum all-reduce exchanges them (one nonzero contributor per slot, every lane an integer below 2^24, so the result is exact on any backend), and a one-warp finalize picks the global argmax with the gathered path's tie and NaN rules. Tokens, topk_p and positions are bit-identical. All decisions are host-side and fixed per captured shape, so the path runs inside the draft decode and draft extend CUDA graphs. - kernels/ops/speculative/topk1.py: shard partials, draft-decode postprocess and draft-extend argmax from partials. - LogitsProcessor: next_token_argmax_partials output; static eligibility (CUDA, TP all-gather, no DP / attn-TP / all-to-all LM head, no softcapping); enable_draft_argmax_exchange() opt-in; the draft_argmax_only forward flag. It falls back to the gather when the LM head is not a plain vocab-parallel shard or the all-reduce would run on NCCL. - GroupCoordinator.device_all_reduce_method mirrors all_reduce's CUDA dispatch. - EagleDraftWorker opts in its own draft LM-head processors at init (a processor that returns False keeps its logits path) and sets the flag around draft decode and draft extend (not under rejection sampling). SGLANG_SPEC_DRAFT_ARGMAX_EXCHANGE=0 restores the gather. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5 tasks done
3 of 5 tasks
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.
Motivation
With MTP on DSA models (GLM-5.x, DeepSeek-V3.2), every decode step pays costs that scale with
--context-lengthand the vocab rather than with the live work:rows x vocablogits only to take an argmax.Modifications
kernels/ops/attention/dsa_metadata_bounded.py).min(budget / rows, 256)programs, which stride over its live column blocks.DeepseekSparseAttnMultiStepBackendwrites each step backend's graph metadata straight fromreq_to_token, in one bounded launch for up to 4 steps. This replaces the precompute + full-width copies.topk_pand positions are bit-identical, inside the draft-decode and draft-extend CUDA graphs.LogitsProcessors opt in throughenable_draft_argmax_exchange().Flags (kill switches, both default on):
SGLANG_DSA_BOUNDED_REPLAY_METADATA=0turns off the first two commits.SGLANG_SPEC_DRAFT_ARGMAX_EXCHANGE=0turns off the third.Related:
_fused_draft_extend_tail_eligiblereadsnext_token_logits, which isNonewhen the exchange is engaged, so it should decline whennext_token_argmax_partialsis set.Accuracy Tests
GSM8K on GB300 with GLM-5.2 NVFP4: full test set (1319 questions), 5-shot, greedy, 512 new tokens.
/generate, with the same prompts and answer extraction assglang.test.few_shot_gsm8k. One question is sent first, so both arms read the same 5-shot prefix KV from the radix cache.Speed Tests and Profiling
Setup: GB300, GLM-5.2 NVFP4. The 2026-10-26 SGLang nightly image (
lmsysorg/sglang:nightly-dev-cu13-20261026-8252e3af, SGLang8252e3af63) is the main arm; the other arm is this branch on top of it.End to end:
--dataset-name random), greedy, real acceptance, default 1M context.TP4 (one tray), EAGLE 4/1/5:
TP8 (two trays, one NVL72 rack), EAGLE 5/1/6:
Kernels. Times are CUDA-graph replays in us. The metadata kernels are timed with a cold L2.
Replay metadata, 1M-token table, 5 verify rows per request. Unbounded → bounded:
MTP draft-decode metadata. Precompute + copies (plus, at 5/1/6, the step-3 schedule refresh) → one launch:
Draft step (LM head + top-1). Logits gather → partials exchange; vocab 154880, hidden 6144, FlashInfer all-reduce, max over ranks:
Reproduce
Checklist
New tests:
test/registered/kernels/ops/attention/test_dsa_metadata_bounded.pyandtest_dsa_draft_decode_metadata.py: bit-identity against the unbounded kernels and against precompute + copies at a 1M-token table; CUDA-graph replay with new lengths; backend selection; the one-launch gate; the schedule-refresh steps.test/registered/kernels/ops/speculative/test_draft_topk1_partials.py: partials + sum all-reduce against the gathered logits, covering ties, NaN, inf and padding, with TP 2-8 emulated on one GPU.test/registered/kernels/ops/speculative/test_draft_argmax_exchange_tp.py: a real TP group, covering an exact all-reduce,LogitsProcessorforward and the CUDA graph.test/registered/unit/spec/test_draft_argmax_exchange.py: the opt-in protocol.Results on GB300:
next_token_argmax_partials=Noneto the fakeLogitsProcessorOutputintest_eagle_draft_sampling.py.🤖 Generated with Claude Code
CI States
Latest PR Test (Base): ❌ Run #40379582163
Latest PR Test (Extra): ❌ Run #40379582127
Latest PR Test (AMD ROCm 10): ❌ Run #40379581904