Summary
On SM120 (RTX PRO 6000 Blackwell), DSpark speculative decoding for
DeepSeek-V4-Flash can never start. Every launch dies during CUDA graph capture
with:
tvm.error.InternalError: Check failed: num_tokens > 64 (50 vs. 64) :
Decode (num_tokens <= 64) must go through sparse_mla_sm120_decode_dsv3_2
or sparse_mla_sm120_decode_dsv4; got num_tokens=50
The assertion can never be satisfied, because speculative verify submits
batch x (gamma + 1) tokens, which is below 64 by construction.
Root cause
FlashInfer instantiates sparse_mla_sm120_decode_dsv4 only for a fixed
(num_heads, topk) table (_DECODE_DSV4_DISPATCH). For 64 heads that is
{128, 512, 1024}.
I instrumented _flash_mla_flashinfer in
python/sglang/kernels/ops/attention/flash_mla_sm120.py and logged every call
over a full startup. The split is clean:
| topk |
_decode_dsv4_dispatchable |
calls |
| 128 |
True |
4644 |
| 192 |
False |
4 |
Normal decode always arrives with topk=128 and dispatches to the decode
kernel. DSpark's draft attention arrives with topk=192, which has no
instantiation, so _decode_dsv4_dispatchable returns False and the call falls
through to module.sparse_mla_sm120_paged_attention, the prefill orchestrator,
whose first check is num_tokens > 64.
Every other dispatch input is fine: model_type=_MODEL_TYPE_DSV4, d_qk=512,
kv_pbs=64 after the existing page-split, num_heads=64.
What is not the cause
- Not the CUDA graph ladder.
--cuda-graph-max-bs-decode of 1, 2 and 10
all fail identically, only with different token counts (6, 12, 50).
- Not gamma.
--speculative-dspark-block-size 5 matches the checkpoint's
own dspark_block_size: 5 in config.json.
- Not the attention backend.
SGLANG_SM120_FLASHMLA_BACKEND=triton clears
capture but aborts on the first real prefill.
Reproduction
sglang serve --model-path <DeepSeek-V4-Flash-0731> \
--tp-size 4 --kv-cache-dtype fp8_e4m3 \
--max-running-requests 10 --cuda-graph-max-bs-decode 10 \
--speculative-algorithm DSPARK --speculative-dspark-block-size 5
Hardware: 4x RTX PRO 6000 Blackwell (SM120), PCIe-only, driver 610.57.04,
CUDA 13.3. SGLang 0.0.0.dev1+ga358374ae. The same code path is present on
main today (flash_mla_sm120.py, the _sparse_mla_sm120_paged_attention
call after the B <= _FI_DECODE_MAX_TOKENS block).
Fix
Two options:
- Instantiate
decode-dsv4 for topk=192 in FlashInfer.
- Have the SGLang shim pad the sparse index up to the next instantiated width
and bound the real length with topk_length, which the kernel already
accepts.
I implemented (2) and will open a PR. Measured on the hardware above:
| Metric |
before |
after |
| single-stream decode, natural text |
80.8 tok/s |
223.7 tok/s |
| aggregate decode, 10 concurrent |
429.2 tok/s |
465.2 tok/s |
| acceptance rate |
n/a |
0.62 (accept len 4.10) |
Correctness check: padding with two different values (first index vs last
index of the row) produces byte-identical output across five deterministic
fixtures, which confirms topk_length bounds the read and the padding is never
consumed.
Summary
On SM120 (RTX PRO 6000 Blackwell), DSpark speculative decoding for
DeepSeek-V4-Flash can never start. Every launch dies during CUDA graph capture
with:
The assertion can never be satisfied, because speculative verify submits
batch x (gamma + 1)tokens, which is below 64 by construction.Root cause
FlashInfer instantiates
sparse_mla_sm120_decode_dsv4only for a fixed(num_heads, topk)table (_DECODE_DSV4_DISPATCH). For 64 heads that is{128, 512, 1024}.I instrumented
_flash_mla_flashinferinpython/sglang/kernels/ops/attention/flash_mla_sm120.pyand logged every callover a full startup. The split is clean:
_decode_dsv4_dispatchableNormal decode always arrives with
topk=128and dispatches to the decodekernel. DSpark's draft attention arrives with
topk=192, which has noinstantiation, so
_decode_dsv4_dispatchablereturns False and the call fallsthrough to
module.sparse_mla_sm120_paged_attention, the prefill orchestrator,whose first check is
num_tokens > 64.Every other dispatch input is fine:
model_type=_MODEL_TYPE_DSV4,d_qk=512,kv_pbs=64after the existing page-split,num_heads=64.What is not the cause
--cuda-graph-max-bs-decodeof 1, 2 and 10all fail identically, only with different token counts (6, 12, 50).
--speculative-dspark-block-size 5matches the checkpoint'sown
dspark_block_size: 5inconfig.json.SGLANG_SM120_FLASHMLA_BACKEND=tritonclearscapture but aborts on the first real prefill.
Reproduction
Hardware: 4x RTX PRO 6000 Blackwell (SM120), PCIe-only, driver 610.57.04,
CUDA 13.3. SGLang
0.0.0.dev1+ga358374ae. The same code path is present onmaintoday (flash_mla_sm120.py, the_sparse_mla_sm120_paged_attentioncall after the
B <= _FI_DECODE_MAX_TOKENSblock).Fix
Two options:
decode-dsv4fortopk=192in FlashInfer.and bound the real length with
topk_length, which the kernel alreadyaccepts.
I implemented (2) and will open a PR. Measured on the hardware above:
Correctness check: padding with two different values (first index vs last
index of the row) produces byte-identical output across five deterministic
fixtures, which confirms
topk_lengthbounds the read and the padding is neverconsumed.