Conversation
On SM120/SM121 any DeepSeek-V4 prompt longer than SM120_DECODE_MAX_TOKENS (64) aborts the server: Check failed: (ok) is false: Unsupported sparse-MLA prefill configuration: model=DSV4 num_heads=8 topk=128 page_block_size=64 topk_extra=512 extra_page_block_size=128 _flash_mla_sm120_prefill splits the main KV cache from the SWA pool's page_block_size down to 64, because the FlashInfer SM120 kernels only instantiate pbs=64. It does not do the same for the DSv4 secondary (C4A/C128A) cache, which stays at 128. dispatch_dsv4_dual accepts extra_page_block_size of 64 or 2 only -- in both the fulltile fast path and the general DISPATCH_BY_NH_PBSX branch -- so it returns false and the TVM_FFI_ICHECK in sparse_mla_sm120.cu fires. Everything else about the call already matched: num_heads=8, topk=128 and main page_block_size=64 are all supported. Only the extra cache was wrong. _split_kv_pages_to_64 is already generic in src_pbs and the extra cache uses the same 584-byte-per-token footer layout, so this only needs the helper to be called for it. The helper's persistent output and mask buffers are keyed per device and shared, however, so a second call within one forward would alias the first: add a key_suffix so the extra cache gets its own buffers. Without it the extra split silently overwrites half the main cache's split pages. The decode path is deliberately untouched -- decode accepts extra_page_block_size=128 and has always worked. Verified on 8x RTX PRO 6000 Blackwell (SM120, TP=8) with DeepSeek-V4.1-Flash: - prompts of 71..911 tokens now serve; previously every prompt >64 aborted - the split is bitwise neutral: materialising the same extra-cache contents at pbs=128 (split) and pbs=64 (no split) yields bitwise-identical kernel output - dual-cache prefill agrees with the torch reference (cos=0.99975) - new unit test covers the footer round-trip for pbs=256 and pbs=128, the buffer namespacing, and touched-page masking
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
On SM120/SM121 (RTX PRO 6000 Blackwell, GB10/DGX Spark), serving DeepSeek-V4 with the default FlashInfer sparse-MLA backend aborts on any prompt longer than 64 tokens:
flash_mla_with_kvcache_sm120()routes a forward to_flash_mla_sm120_prefillwheneverq.shape[0] > SM120_DECODE_MAX_TOKENS(64). That function splits the main KV cache from the SWA pool's page size down to 64 via_split_kv_pages_to_64, because the FlashInfer SM120 kernels only instantiatepage_block_size=64. It does not do the same for the DSv4 secondary (C4A/C128A) cache, which stays at 128.On the kernel side,
dispatch_dsv4_dualinsparse_mla_sm120_prefill.cuacceptsextra_page_block_sizeof 64 or 2 only — in both thefulltilefast path and the generalDISPATCH_BY_NH_PBSXbranch. It returnsfalse, and theTVM_FFI_ICHECK(ok)insparse_mla_sm120.cufires.Every other parameter already matched:
num_heads=8✅,topk=128✅, mainpage_block_size=64✅. Only the extra cache was wrong.The decode path is unaffected and is deliberately left alone — it accepts
extra_page_block_size=128and has always worked. That is why short prompts succeed and only prefill fails.Continues the page-split work in sgl-project#32320, sgl-project#35116 and sgl-project#29927.
Modifications
_split_kv_pages_to_64was already generic insrc_pbs(assert src_pbs % 64 == 0) and the extra cache uses the identical 584-byte-per-token footer layout, so the fix is small:Call the existing helper for the extra cache in
_flash_mla_sm120_prefill, deriving the source page size from the tensor shape (handling the 3D, NHD and HND forms the binding documents) and skipping the work when it is already 64.Add a
key_suffixparameter to_split_kv_pages_to_64. The helper's persistent output and touched-page mask buffers are keyed per device (flash_mla_sm120_split:{dev},flash_mla_sm120_mask:{dev}) and are shared, so a second call within one forward aliases the first.This second part is not cosmetic. With a shared key the extra-cache split reuses the main cache's buffer and silently overwrites it — measured at 512 of 1024 main-cache tokens corrupted in the added test's configuration.
key_suffixdefaults to"", so the existing main-cache call site and its buffer key names are byte-identical to today.Token indices are invariant under the split (
dst_page = src_page*ratio + sub⇒dst_page*64 + off == src_page*src_pbs + sub*64 + off), so no index remapping is needed — the same property the main-cache split already relies on.Why split in Python rather than instantiate
extra_page_block_size=128in FlashInfer: splitting to 64 is valid against every released FlashInfer wheel and fixes users today. A new C++ template instantiation would require a FlashInfer release before it helps anyone and would leave all current wheels broken. Happy to follow up on the FlashInfer side as well.Accuracy Tests
All runs on 8× RTX PRO 6000 Blackwell (SM120), TP=8, DeepSeek-V4.1-Flash.
1. The page split is bitwise neutral. The same logical extra-cache contents were materialised twice — as 16 pages of
pbs=128(takes the new split path) and as 32 pages ofpbs=64(no split) — and fed to the same kernel:So the split contributes exactly zero numerical difference.
2. Dual-cache prefill agrees with the in-tree reference.
_flash_mla_sm120_prefillvs the pure-PyTorch_sm120_sparse_decode_fwd, 128 tokens / 8 heads / topk=128 / extra_topk=512:FlashInfer sits further from the torch reference than Triton does, but per (1) that gap is a pre-existing property of the kernel and not of this change — identical inputs produce bitwise-identical output whether or not the split runs.
3. The unsplit path still reproduces the bug. Bypassing the extra-cache split in the same harness reproduces the original error exactly:
4. End-to-end. Sweeping prompt length across the 64-token boundary against a live server (server-reported
prompt_tokens):Before this patch every
prompt_tokens > 64row aborted the server. A full serving sweep (1k/1k,1k/8k,8k/1kat concurrency 32/64/128) also runs clean, including 8k prompts that exercise multi-page chunked prefill.5. New unit test.
test/registered/kernels/ops/attention/test_flash_mla_sm120_page_split.py— this file previously had no test coverage. It covers:pbs=256(regression guard for the existing main-cache path) andpbs=128(the extra cache)key_suffixisolating the output/mask buffers, so the extra split cannot clobber the main splitkey_suffix=""preserving the historic buffer key namestouched_indicesmasking still restricting copies to referenced pagesIt exercises only the Triton page-copy path, so it runs on any CUDA GPU and needs no SM120 hardware.
Speed Tests and Profiling
There is no meaningful before/after throughput comparison for this path: before the patch, every DSv4 prefill of more than 64 tokens aborted the server, so the baseline does not run.
On cost: the extra-cache split reuses the same touched-page masking introduced in sgl-project#32320, so only source pages actually referenced by
extra_indicesare copied rather than the whole pool. The additional persistent buffer isnum_dst_pages * 37440bytes, grow-only, allocated once per device.Checklist