Skip to content

[Bug] DSpark speculative decoding cannot start on SM120: decode-dsv4 has no topk=192 instantiation, so verify falls through to the prefill kernel's num_tokens > 64 assert #33985

Description

@ressl

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:

  1. Instantiate decode-dsv4 for topk=192 in FlashInfer.
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions