Repository navigation
[Fix] kvcache: Skip the reserved padding slot in the zero-RoPE MLA KV writer - #47013
Open
JackChuang wants to merge 1 commit into
Open
JackChuang wants to merge 1 commit into
JackChuang wants to merge 1 commit into
Conversation
… writer set_mla_kv_buffer_kernel_norope wrote every loc, including the padding slot 0 that the RoPE writers skip since sgl-project#36003. Padding rows can carry NaN K since sgl-project#42689, and the FP8 TileLang sparse decode reads row 0 for unused top-k entries, so models with qk_rope_head_dim = 0 (GLM-5.3-Flash) decoded NaN logits with an FP8 KV cache. Skip the slot in the kernel and in the torch-native fallback, and run the reserved-slot tests with rope_dim = 0.
JackChuang
requested review from
BBuf,
DarkSharpness,
HaiShaw,
HydraQYH,
celve and
yuan-luo
as code owners
October 28, 2026 07:30
This was referenced Oct 28, 2026
Closed
Collaborator
|
/tag-and-rerun-ci |
Collaborator
|
/rerun-test test/registered/e2e/models/test_glm53_flash_h200_low_latency.py test/registered/e2e/models/test_glm53_flash_h200_high_throughput.py Targeted validation for |
Contributor
|
🚀 |
Collaborator
|
/rerun-failed-ci |
BBuf
approved these changes
Oct 30, 2026
BBuf
left a comment
Collaborator
There was a problem hiding this comment.
LGTM. Zero-RoPE writes now preserve the reserved slot; wait for related CUDA/AMD kernel CI and H200 model checks.
Collaborator
|
/rerun-test test/registered/e2e/models/test_glm53_flash_h200_low_latency.py test/registered/e2e/models/test_glm53_flash_h200_high_throughput.py Targeted validation for |
Collaborator
|
/tag-and-rerun-ci |
Contributor
|
🚀 |
Collaborator
|
/tag-and-rerun-ci |
3 of 5 tasks
bvolpato
added a commit
to bvolpato/sglang
that referenced
this pull request
Nov 3, 2026
…g DSA The page says TileLang DSA with FP8 KV is not a valid CUDA combination. PR sgl-project#42957 made the server accept this pair when both backends are tilelang, so that reason is out of date. The pair is still not qualified. On 4x B200 with real weights, main scored 81.50% on sgl-eval GSM8K, and 16.5% of the answers ended in a run of "!" at the token limit. The page now gives this status and links PR sgl-project#47013, which reports the same output on H200. The deployment panel does not change, because its reason is still correct on main. (cherry picked from commit da27037)
3 of 5 tasks
Contributor
This branch has not been deployed
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.
Summary
Enables: correct output for GLM-5.3-Flash with an FP8 KV cache and the TileLang DSA backends on current main. Since #42689, about half of its answers under concurrent requests turn into gibberish (a run of
!up to the token limit), and GSM8K drops to 50.0%; with this PR it scores 98.0%.Since #36003 the MLA KV writers skip the slot reserved for padding rows (slot 0). The zero-RoPE writer
set_mla_kv_buffer_kernel_norope, which every GLM-5.3-Flash KV write goes through (qk_rope_head_dim = 0), still writes it. This PR makes it skip the slot the same way.Motivation
Padding rows (CUDA graph capture and padded replays) write to
out_cache_loc = 0with undefined inputs. Since #42689, per-token FP8 quantization keeps BF16 NaNs instead of clamping them, so the zero-RoPE writer can now store NaN rows in slot 0. The FP8 TileLang sparse decode maps unused top-k entries to row 0, and masking the scores does not stop a NaN (-inf + NaN,0 * NaN), so prompts shorter thanindex_topkdecode NaN logits.#45988 hardens that reader by masking every KV load in the FP8 decode kernel, which makes that kernel slower: at GLM-5.3-Flash TP4 shapes (16 heads per GPU, top-k 2048) it takes 1.3× as long for one decode token and 2.7–3.3× as long for 16 to 256 (see Speed Tests). This PR stops the write instead, so slot 0 stays clean for every kernel that reads it and the decode kernel is unchanged. The two fixes are independent: with both applied, GSM8K scores 98.5% in the setup below.
The NaN rows come from padded outputs that are left uninitialized, such as those of the KDA/GDN recurrent kernel, which #46225 zeroes. This PR keeps slot 0 clean whatever the padding rows hold, as the RoPE writers already do, so it does not rely on every kernel zeroing its padded outputs; the two can land in either order.
The same skip is part of the DCP rework in #39117 / #40433, which also adds DCP ownership to this kernel. This PR lands only the non-DCP part, so main stops producing NaN now; #40433 can rebase by adding its DCP arguments on top. cc @kpham-sgl @Fridge003 @BBuf
Modifications
python/sglang/kernels/ops/kvcache/mla_buffer.py:set_mla_kv_buffer_kernel_noropetakesreserved_skip_indexand masks its store exactly likeset_mla_kv_buffer_kernel;_set_mla_kv_buffer_implpasses it. The torch-native fallbackset_mla_kv_buffer_naivenow skips the slot for zero RoPE too, so the two paths stay bit-identical.test/registered/kernels/ops/kvcache/test_set_mla_kv_buffer.py: the two existing Triton reserved-slot tests also run withrope_dim=0. The skip case fails on main and passes with this PR.What changes: only writes aimed at the reserved slot are dropped, as the RoPE writers already do; real token rows are written as before. It reaches every MLA model with
qk_rope_head_dim = 0(GLM-5.3-Flash, BF16 or FP8 KV); the KV allocators never hand out slot 0 and the pool zero-initializes it, so the slot now stays zero. DCP handling in this kernel is unchanged (#36886, #40433).Accuracy Tests
GLM-5.3-Flash on 4x H200 with the server arguments of the H200 CI tests (
test_glm53_flash_h200_*.py), at TP4 and with an FP8 KV cache. Prefill CUDA graphs are off; decode CUDA graphs stay on. The eval uses the sampling settings of those CI tests. The MTP 5/1/6 rows add the speculative flags oftest_glm53_flash_h200_low_latency.py(--speculative-algorithm EAGLE --speculative-num-steps 5 --speculative-eagle-topk 1 --speculative-num-draft-tokens 6).f9257d6c7c)!)Speed Tests and Profiling
Kernel time of the zero-RoPE writer (
set_mla_kv_buffer_tritonwith an empty RoPE part) on H200, timed with CUDA graph replay:The rows are BF16 with 512 values; FP8 rows of 512 to 656 bytes behave the same. Without padding rows, this PR is at most 0.07 µs slower than main at any size from 1 to 8,192 rows. With padding rows it is faster, because main writes all of them to the same slot.
End to end, with a BF16 KV cache (on main the FP8 output is not valid) and otherwise the setup of the accuracy tests. Rows with 1 request or at saturation use
bench_serving --dataset-name random --random-range-ratio 1(fixed prompt and output lengths); the rows at 16 and 64 concurrent requests use prompts of about 8k tokens. Medians:The reader-side alternative (#45988) slows the FP8 decode kernel instead.
tilelang_sparse_fwdwith an FP8 KV cache at GLM-5.3-Flash TP4 shapes (16 heads,d_v512, top-k 2048, every index valid), H200:End to end with an FP8 KV cache and the same workloads, medians. Prefill uses the same kernel, so TTFT grows too:
CI
test_set_mla_kv_buffer.pypasses, including the new zero-RoPE cases, andpre-commit runpasses on the changed files. On H200, in the per-PR NVIDIA suites (base-a and base-b on 1 and 2 GPUs, base-c on 4 and 8 GPUs) and the five CPU suites, every test that fails with this PR also fails the same way on the parent commit (535aeae2a7). The H200 GLM-5.3-Flash tests (BF16 KV cache, TileLang DSA), whose KV writes all go through the changed kernel, pass with and without this PR: GSM8K 97.3% with this PR vs 97.2% on main with EAGLE (test_glm53_flash_h200_low_latency.py), and 96.9% vs 97.4% without it (test_glm53_flash_h200_high_throughput.py). Not run here: the PD disaggregation, Mooncake and NIXL tests andhicache/test_pp_with_hicache.py; Blackwell GPUs; ROCm.Checklist
Review and Merge Process
/tag-and-rerun-ci,/tag-run-ci-label,/rerun-failed-ciCI States
Latest PR Test (Base): ❌ Run #40650688282
Latest PR Test (Extra): ❌ Run #40650688166
Latest PR Test (AMD ROCm 10): ❌ Run #40650688212