Conversation
…SM120 FlashInfer path
FlashInfer's dsv4 sparse decode API disambiguates decode-vs-prefill by
query rank. On SM120 (GB10) with speculative decoding (DSpark), the
flattened 3-D [tokens, heads, 512] query for next_n > 1 batches is
misrouted to the varlen prefill kernel, whose SM120 build asserts
num_tokens > 64 ("Decode ... must go through sparse_mla_sm120_decode_dsv4").
Four fixes to DeepseekV4FlashInferSM120Attention:
- Pass a 4-D [batch, next_n, heads, 512] query (and matching output
view) for spec decode batches so FlashInfer routes them to the
decode kernels.
- Reshape the companion sparse indices/lens tensors to
[batch, next_n, ...] alongside the 4-D query (FlashInfer validates
their layout against the query).
- Route <= 64-token prefill segments (the DSpark draft's k-token pass,
short chunked-prefill tails) through per-request decode-form
[1, q_len, ...] calls, since the SM120 sparse prefill kernel asserts
num_tokens > 64.
- Skip empty prefill chunks (zero-token spans in query_start_loc):
the FlashInfer sparse kernel crashes reshaping 0 elements.
Validated on 2x DGX Spark (GB10, SM121) TP=2 serving
DeepSeek-V4-Flash-0731 with DSpark speculative decoding.
Signed-off-by: pavelzak <pavel.zakharov@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
👋 Hi! Thank you for contributing to the vLLM project. 💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in PRs do not trigger a full CI run by default. Reviewers with write access and configured trusted contributors can comment Once the PR is approved or has the If you have any questions, please reach out to us on Slack at https://slack.vllm.ai. Agent GuidelinesIMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban. 🚀 |
Three correctness fixes for the SM120/SM121 (consumer Blackwell / GB10) serving path, ported from open upstream PRs: 1. MXFP4 persistent-kernel constraint must cover sm120/sm121 (upstream vllm-project#52850): has_native_mxfp() is cuda_capability_geq(10,0), so gating the is_persistent constraint on the sm100 family alone leaves consumer Blackwell requiring a constraint nothing sets and every native-MXFP matmul raises. Gate on >=100 instead. Adds test_swizzle_mxfp4_sets_persistent_for_native_mxfp. 2. DSpark spec-decode reshape guard in the SM120 FlashInfer sparse decode (upstream vllm-project#52499): flashinfer's dsv4 decode API expects [batch, q_len_per_request, ...] queries; flattened spec-decode batches misroute to the varlen prefill kernel which asserts num_tokens > 64. Reshape to [num_decodes, next_n, ...] when tokens exceed requests, and handle empty/small (<=64) prefill chunks via the uniform decode-form call. 3. Ragged decode batches take the padded indexer path (upstream vllm-project#52500): requires_padding can be computed False for ragged warmup/mixed batches (SM120 TP=2: 8 tokens over 6 seqs), where the uniform reshape would crash. Also pad when num_decode_tokens does not divide the decode batch. Verified: all four files compile; pre-commit clean. test_flashmla_sparse.py failures are pre-existing (no compiled extensions in this venv; identical 4 failures with changes stashed). Co-authored-by: OMP Agent <noreply@omp.local> Signed-off-by: Naadir Jeewa <naadir@randomvariable.co.uk>
|
Adoption data point: this fix has been independently ported into a third-party GB10 (DGX Spark) serving fork and is running there — see randomvariable/vllm@07011d8 ("port consumer-Blackwell correctness trio from upstream", 2026-08-19), which cherry-picked this PR together with #52500. Also referenced by the test-coverage PR #52815 for the adjacent dispatch-width helpers. |
|
Data point from a 2x DGX Spark (GB10/SM121) serving DeepSeek-V4-Flash-0731 with DSpark (next_n=6), TP=2,
Verified in-container: Suggested fix: force the copy where it's cheap, e.g. extra_sparse_indices = extra_sparse_indices.reshape(num_decodes, next_n, -1).contiguous()
swa_indices = swa_indices.reshape(num_decodes, next_n, -1).contiguous()This is a no-op on the already-contiguous inputs this PR was validated against, which is presumably why it passed the original testing. |
|
We serve DSpark k=5 on 2x GB10 with If this PR is the remaining |
Three correctness fixes for the SM120/SM121 (consumer Blackwell / GB10) serving path, ported from open upstream PRs: 1. MXFP4 persistent-kernel constraint must cover sm120/sm121 (upstream vllm-project#52850): has_native_mxfp() is cuda_capability_geq(10,0), so gating the is_persistent constraint on the sm100 family alone leaves consumer Blackwell requiring a constraint nothing sets and every native-MXFP matmul raises. Gate on >=100 instead. Adds test_swizzle_mxfp4_sets_persistent_for_native_mxfp. 2. DSpark spec-decode reshape guard in the SM120 FlashInfer sparse decode (upstream vllm-project#52499): flashinfer's dsv4 decode API expects [batch, q_len_per_request, ...] queries; flattened spec-decode batches misroute to the varlen prefill kernel which asserts num_tokens > 64. Reshape to [num_decodes, next_n, ...] when tokens exceed requests, and handle empty/small (<=64) prefill chunks via the uniform decode-form call. 3. Ragged decode batches take the padded indexer path (upstream vllm-project#52500): requires_padding can be computed False for ragged warmup/mixed batches (SM120 TP=2: 8 tokens over 6 seqs), where the uniform reshape would crash. Also pad when num_decode_tokens does not divide the decode batch. Verified: all four files compile; pre-commit clean. test_flashmla_sparse.py failures are pre-existing (no compiled extensions in this venv; identical 4 failures with changes stashed). Co-authored-by: OMP Agent <noreply@omp.local> Signed-off-by: Naadir Jeewa <naadir@randomvariable.co.uk>
Three correctness fixes for the SM120/SM121 (consumer Blackwell / GB10) serving path, ported from open upstream PRs: 1. MXFP4 persistent-kernel constraint must cover sm120/sm121 (upstream vllm-project#52850): has_native_mxfp() is cuda_capability_geq(10,0), so gating the is_persistent constraint on the sm100 family alone leaves consumer Blackwell requiring a constraint nothing sets and every native-MXFP matmul raises. Gate on >=100 instead. Adds test_swizzle_mxfp4_sets_persistent_for_native_mxfp. 2. DSpark spec-decode reshape guard in the SM120 FlashInfer sparse decode (upstream vllm-project#52499): flashinfer's dsv4 decode API expects [batch, q_len_per_request, ...] queries; flattened spec-decode batches misroute to the varlen prefill kernel which asserts num_tokens > 64. Reshape to [num_decodes, next_n, ...] when tokens exceed requests, and handle empty/small (<=64) prefill chunks via the uniform decode-form call. 3. Ragged decode batches take the padded indexer path (upstream vllm-project#52500): requires_padding can be computed False for ragged warmup/mixed batches (SM120 TP=2: 8 tokens over 6 seqs), where the uniform reshape would crash. Also pad when num_decode_tokens does not divide the decode batch. Verified: all four files compile; pre-commit clean. test_flashmla_sparse.py failures are pre-existing (no compiled extensions in this venv; identical 4 failures with changes stashed). Co-authored-by: OMP Agent <noreply@omp.local> Signed-off-by: Naadir Jeewa <naadir@randomvariable.co.uk>
FlashInfer 0.6.17 ships SM12x DSV4 decode cubins for top_k in
{128, 512, 1024}, not DSpark k=5's aligned width 192. Snap the
non-causal SWA index list up to the next cubin so init and the
kernel see the same supported shape; the logical window stays 128.
Also reshape spec-decode queries to [batch, next_n, heads, 512] and
route <=64-token prefill segments through decode-form calls so
FlashInfer does not misroute DSpark drafts to the SM120 prefill
kernel (vllm-project#52499).
Co-authored-by: Cursor Grok 4.6
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Three correctness fixes for the SM120/SM121 (consumer Blackwell / GB10) serving path, ported from open upstream PRs: 1. MXFP4 persistent-kernel constraint must cover sm120/sm121 (upstream vllm-project#52850): has_native_mxfp() is cuda_capability_geq(10,0), so gating the is_persistent constraint on the sm100 family alone leaves consumer Blackwell requiring a constraint nothing sets and every native-MXFP matmul raises. Gate on >=100 instead. Adds test_swizzle_mxfp4_sets_persistent_for_native_mxfp. 2. DSpark spec-decode reshape guard in the SM120 FlashInfer sparse decode (upstream vllm-project#52499): flashinfer's dsv4 decode API expects [batch, q_len_per_request, ...] queries; flattened spec-decode batches misroute to the varlen prefill kernel which asserts num_tokens > 64. Reshape to [num_decodes, next_n, ...] when tokens exceed requests, and handle empty/small (<=64) prefill chunks via the uniform decode-form call. 3. Ragged decode batches take the padded indexer path (upstream vllm-project#52500): requires_padding can be computed False for ragged warmup/mixed batches (SM120 TP=2: 8 tokens over 6 seqs), where the uniform reshape would crash. Also pad when num_decode_tokens does not divide the decode batch. Verified: all four files compile; pre-commit clean. test_flashmla_sparse.py failures are pre-existing (no compiled extensions in this venv; identical 4 failures with changes stashed). Co-authored-by: OMP Agent <noreply@omp.local> Signed-off-by: Naadir Jeewa <naadir@randomvariable.co.uk>
Three correctness fixes for the SM120/SM121 (consumer Blackwell / GB10) serving path, ported from open upstream PRs: 1. MXFP4 persistent-kernel constraint must cover sm120/sm121 (upstream vllm-project#52850): has_native_mxfp() is cuda_capability_geq(10,0), so gating the is_persistent constraint on the sm100 family alone leaves consumer Blackwell requiring a constraint nothing sets and every native-MXFP matmul raises. Gate on >=100 instead. Adds test_swizzle_mxfp4_sets_persistent_for_native_mxfp. 2. DSpark spec-decode reshape guard in the SM120 FlashInfer sparse decode (upstream vllm-project#52499): flashinfer's dsv4 decode API expects [batch, q_len_per_request, ...] queries; flattened spec-decode batches misroute to the varlen prefill kernel which asserts num_tokens > 64. Reshape to [num_decodes, next_n, ...] when tokens exceed requests, and handle empty/small (<=64) prefill chunks via the uniform decode-form call. 3. Ragged decode batches take the padded indexer path (upstream vllm-project#52500): requires_padding can be computed False for ragged warmup/mixed batches (SM120 TP=2: 8 tokens over 6 seqs), where the uniform reshape would crash. Also pad when num_decode_tokens does not divide the decode batch. Verified: all four files compile; pre-commit clean. test_flashmla_sparse.py failures are pre-existing (no compiled extensions in this venv; identical 4 failures with changes stashed). Co-authored-by: OMP Agent <noreply@omp.local> Signed-off-by: Naadir Jeewa <naadir@randomvariable.co.uk>
|
@pavelzak This PR shouldn't be needed after flashinfer-ai/flashinfer#4802, could you help double-check? |
Reported by @kitch2400 on vllm-project#52499: under CUDA graph capture with C128A layers, c128a_global_decode_topk_indices can be a slice of a larger, alignment-padded workspace buffer kept fixed-size for graph safety. reshape() onto that slice can produce a tensor whose shape matches but whose layout doesn't satisfy flashinfer's CHECK_INPUT_AND_TYPE contiguity check (eidx.IsContiguous() at sparse_mla_sm120.cu), because reshape() only guarantees a valid *view* when the source strides permit one - it doesn't force copy-to-contiguous in every non-trivial case. .contiguous() makes the copy explicit and unconditional. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C7YveGPhnT8v6qrAEucmVW Signed-off-by: pavelzak <pavel.zakharov@gmail.com>
|
Thanks all, three good data points here. @kitch2400 — confirmed and fixed. @maci0 — right, if your window+k already lands in the dispatch table (192, post-flashinfer#4380) you won't hit the @lucifer1004 — looked at #4802. It removes the hard T≤64 cutoff and makes topk dispatch continuous, which should obsolete two of this PR's four changes: the empty-chunk skip and the ≤64-token-reroute-to-decode-form (both exist purely to route around flashinfer's old hard cutoffs). The 4-D query reshape + matching index/length reshape, though, fixes how vLLM constructs the call for a next_n>1 batch — a flat 3-D query is shape-ambiguous regardless of what flashinfer's dispatch table can serve — so I'd expect that half to still be needed post-#4802. I don't have a #4802 build to verify against yet (it's unmerged); happy to re-test and trim this PR down to just the shape-construction fix once it lands and ships in a release. For now, on any currently-released flashinfer, all four changes are still required. |
|
@lucifer1004 following up with a more careful answer — I traced #4802's actual diff rather than just its summary. #4802 doesn't touch the part of this PR that matters most. Its changes to And that function's own fallback is arguably wrong for our case, independent of #4802. On current flashinfer main, when you pass it a 3-D query, it does Where I'd expect #4802 to genuinely help: the empty-chunk skip and the ≤64-token-reroute-to-decode-form in this PR, which exist to route around flashinfer's old hard T≤64 prefill cutoff — #4802's continuous envelope should make both unnecessary once it ships. So: keep the 4-D reshape (core fix, orthogonal to #4802); the other two pieces are candidates to drop in a follow-up once #4802 merges and releases. Happy to split this PR that way if it's easier to review — let me know. |
|
Thanks for the careful trace of #4802 — you're right that Where I disagree is the inference that There's also an empirical data point against the "wrong grouping" theory: @maci0 ran DeepSeek-V4-Flash-0731 with DSpark k=5 on GB10 (sm_121a) against current main + a contiguity-only fix (#53574) — no reshape — and greedy output stayed coherent. If the token grouping were wrong, spec-decode output would be garbage, not coherent. On the original crash this PR addressed (flat 3-D misrouted to the varlen prefill kernel, So my suggestion: once #4802 lands, could you re-run your original repro (C128A + TP=2 + long context + graph capture) on current main? If it still crashes, let's move that to an issue with the exact stack and I'll take it from the flashinfer side. |
|
@lucifer1004 ran the actual repro. Built flashinfer from git Two things confirmed empirically:
So: you were right, and I was wrong about the reshape being load-bearing. On flashinfer post-#4802, none of this PR's four changes look necessary anymore — the shape-construction fix isn't needed (per above), and the empty-chunk-skip / ≤64-reroute were already expected to fall away per the continuous envelope. Given #4802 hasn't shipped in a release yet, currently-released flashinfer (0.6.18.post1 and earlier) still needs this PR — so I'll leave it open for now. Once a release containing #4802 ships, I'll re-verify against that release specifically and close this. Thanks for pushing on this; test script is straightforward to share if useful for your own tracking. |
|
@pavelzak thanks for the careful #4802 split. Agreed:
On our 2x GB10 path (DSpark k=5, top_k 192 after flashinfer#4380) we still do not hit the old |
Purpose
Fix DeepSeek-V4 sparse MLA speculative decoding (DSpark) on SM120/SM121 (GB10 / DGX Spark) with the
FLASHINFER_MLA_SPARSE_DSV4backend.FlashInfer's
trtllm_batch_decode_sparse_mla_dsv4API disambiguates decode-vs-prefill by query rank. With speculative decoding (next_n > 1tokens per request),DeepseekV4FlashInferSM120Attentionpasses a flattened 3-D[tokens, heads, 512]query, which FlashInfer's normalizer misroutes to the varlen prefill kernel. The SM120 prefill build assertsnum_tokens > 64and the server crashes with the cryptic"Decode ... must go through sparse_mla_sm120_decode_dsv4"assertion.Four related fixes, all confined to
DeepseekV4FlashInferSM120Attention:[batch, next_n, heads, 512]query (and matching output view) for spec decode batches so FlashInfer routes them to the decode kernels.[batch, next_n, ...]alongside the 4-D query (FlashInfer validates their layout against the query).[1, q_len, ...]calls (the DSpark draft's k-token pass and short chunked-prefill tails hit the samenum_tokens > 64prefill assert).query_start_loc— the FlashInfer sparse kernel crashes reshaping 0 elements. (FlashMLA-side counterpart of the same class of bug: [Bugfix][DSv4] Skip zero-query-len prefill chunks in FlashMLA sparse prefill (complements #49059) #51489.)Not a duplicate: #51538 (merged) fixes the SM120 top-k specialization selection and adds an init-time check, but the decode call on
mainstill passes flat 3-D queries for spec batches, and the prefill chunk loop still has neither the ≤64-token routing nor the empty-chunk skip. #41834 (SM12x DSV4 enablement) does not touchflashinfer_sparse.pyspec-decode shapes.Test Plan
python -m py_compile vllm/models/deepseek_v4/nvidia/flashinfer_sparse.pyTest Result
num_tokens > 64assertion.AI assistance was used for this PR (rebasing/porting the fixes from a v0.26.0-based production branch onto
main); every changed line was reviewed and validated end-to-end by the submitter on the hardware above.