[Perf][DSA] Unify q8kv8 sparse-prefill topk_length source with the bf16 path; delete backscan kernel - #36305
Open
zkyue wants to merge 1 commit into
Open
[Perf][DSA] Unify q8kv8 sparse-prefill topk_length source with the bf16 path; delete backscan kernel#36305zkyue wants to merge 1 commit into
zkyue wants to merge 1 commit into
Conversation
… backscan kernel Pass metadata.dsa_cache_seqlens_int32 (the same per-row valid-topk count the bf16 flashmla_sparse path already consumes) to the q8kv8 SM90 sparse prefill kernel unconditionally, replacing the opt-in SGLANG_ENABLE_DSA_Q8KV8_TOPK_LENGTH backscan derivation. The metadata tensor is already computed once per batch; the per-layer backscan Triton kernel, its wrapper and the env flag are deleted. The backscan sweep test is adapted into a metadata-equivalence test (bitwise, incl. all-pad rows). Signed-off-by: zky <kaiyue.zhou@z.ai>
zkyue
requested review from
BBuf,
DarkSharpness,
Fridge003,
HaiShaw,
HydraQYH,
Qiaolin-Yu,
celve,
hebiao064,
ispobock,
merrymercy and
yuan-luo
as code owners
August 25, 2026 09:09
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
The SM90 q8kv8 (FP8) sparse-prefill path currently derives its per-row valid-topk
early-exit count with a dedicated Triton backscan kernel, gated behind
SGLANG_ENABLE_DSA_Q8KV8_TOPK_LENGTH(default OFF) — so upstream users get noearly-exit at all on this path today.
The bf16 sparse path already solved this without any extra kernel or flag: #31128 passes
metadata.dsa_cache_seqlens_int32(seqlens clipped toindex_topk, computed once perbatch) to
flash_mla_sparse_fwdunconditionally. This PR gives the q8kv8 path the sametreatment:
metadata.dsa_cache_seqlens_int32astopk_lengthunconditionally (sameshape-mismatch fallback guard as the bf16 path);
The early-exit win itself was measured by the path's author in #31888: kernel time
−25.9% @ s_q=437, +2.1% → +6.5% tput at adoption shapes (credit @JackChuang — this PR
only changes where
topk_lengthcomes from and makes it the default). Part of theDeepSeek V4 perf effort (#33636, Indexer & top-k; roadmap #23602).
Net: −3 lines (+107/−110), one fewer kernel launch per layer-call, one fewer env knob, and the
q8kv8 path stops being the only sparse path without the early-exit by default.
Why the metadata source is safe (and equivalent)
seqlen < index_topkare tail-padded with-1;dsa_cache_seqlens_int32 = seqlens.clamp(max=index_topk)can only meet or exceed theposition of the last valid index, and
-1entries inside the consumed range aremasked out in-kernel (their KV loads are predicated off) either way. Verified bitwise
below.
pad_dsa_cache_seqlenspads with zeros) hand the kerneltopk_length = 0where the backscan clamped to 1. Verified on the kernel source and onhardware:
num_topk_blocks = 0skips all three warp-specialized loops symmetrically(no barrier is arrived at outside the loops), and both 0 and 1 produce identical
sentinel outputs (
out = 0,max_logits = -inf,lse = +inf).topk_lengthis a device tensor consumed in-kernel; host-side control flow staysidentical across DP ranks (the only host branch is on the tensor's shape, which is
rank-uniform metadata — same guard the bf16 path has carried since [Perf][DSA] Pass topk_length to flash_mla_sparse_fwd in the sparse attention path #31128).
Evidence (H100, three-arm)
Arms: (a)
topk_length=None(today's default), (b) backscan-derived (the deletedopt-in path), (c) metadata-derived (this PR).
Source equivalence, (b) vs (c): bitwise identical —
out,max_logits,lsealltorch.equalacross 24 case-runs (8 shape/pattern combos × 3 seeds; patterns: full rows,ragged, short (≤ topk/4), all-pad-row mixes, plus 2% in-range
-1sprinkle so thebackscan count is strictly smaller than the metadata value on some rows; shapes up to
s_q=2048, topk=2048, s_kv=65536, h ∈ {64,128}, d_qk ∈ {512,576}).
Early-exit numerics, (c) vs (a) — same property the opt-in path already had:
max_logits/lsebitwise identical;outdiffers by at most 1 bf16 ULP on ~0.01%of elements, only in rows where block iterations are actually skipped, fully
deterministic (each arm is run-to-run bitwise stable, and a full-width
topk_lengthtensor is bitwise identical to
None). The bitwise sweeps in #31888 pass because theirlength patterns keep the iteration count unchanged; a trailing all-masked block pair
perturbs the fp32 O-accumulator by ≤1 ULP of the bf16 output. Flagging this explicitly
rather than repeating the "bit-exact vs None" claim.
Timing (caliber: torch.profiler CUDA kernel-time sum per call, kernel only —
wrapper/derivation excluded; backscan's own derivation kernel listed separately;
s_q=4096, topk=2048, s_kv=65536, h=64, d_qk=576, 30 iters after warmup):
(c) preserves the full early-exit win of (b) while deleting the per-layer derivation
kernel.
Note on the wrapper's range validation
sparse_mla_q8kv8_prefill_fwdvalidates0 <= topk_length <= topkwith twotorch.any(...).item()calls — two device→host syncs per layer-call (~70 µs wallmeasured on an otherwise-idle stream). This PR leaves that validation untouched: it is
deliberate, tested API surface
(
test_q8kv8_sparse_prefill_rejects_invalid_topk_length_bounds), and the DSv4 runtimebackend (#32327,
deepseek_v4_backend.py) already passestopk_lengththrough thissame validated wrapper unconditionally. At fully-packed rows (nothing to skip) the sync
overhead is a small wall-time cost (~70 µs against an unchanged 1.85 ms kernel) while
any pad fraction wins 1.9–4.7× kernel time; making the range check sync-free is a
separable wrapper-level discussion affecting both callers, not part of this diff.
Testing
(
test_sparse_mla_q8kv8_prefill_sm90.py27,test_q8kv8_sparse_prefill_backend.py14).test_q8kv8_topk_length_backscan→test_q8kv8_topk_length_metadata_equivalence:asserts bitwise equality of metadata-derived vs exact index-derived counts on
ragged + all-pad + in-range-
-1patterns, and the all-pad sentinel outputs(
out=0,max_logits=-inf,lse=+inf) withtopk_length=0.pre-commit rungreen on all touched files.(
grep -rn "backscan\|SGLANG_ENABLE_DSA_Q8KV8_TOPK_LENGTH\|q8kv8_topk_length" python/ test/ docs/).CI States
Latest PR Test (Base): ❌ Run #32830440901
Latest PR Test (Extra): ❌ Run #32830440631
Latest PR Test (AMD ROCm 7.2): ❌ Run #32830440815