Stop hardcoding bf16 for the DSV4 unified_kv buffers - #6
Open
AMD-yanfeiwang wants to merge 1 commit into
Open
Conversation
`DeepSeekV4TokenToKVPool` takes `--kv-cache-dtype` and forwards it to every sub-pool it owns -- the SWA/c4/c128 single pools and the c4 indexer pool -- but `DeepSeekV4UnifiedKVPool` ignored the argument entirely and allocated `torch.bfloat16`. Under `SGLANG_HACK_FLASHMLA_BACKEND=unified_kv_triton` that made `--kv-cache-dtype fp8_e4m3` a no-op for the main KV buffers with nothing in the log to say so: the operator asks for the 584 B/token/layer packed layout (nope fp8 + rope bf16 + fp8 scales) that `DeepSeekV4SingleKVPool` implements and that `pool_configurator._get_bytes_per_full_token` budgets for, and silently gets 1024 B/token/layer instead. On DeepSeek-V4-Pro that is 1.75x the KV bytes the flag implies, both resident and on the wire in PD disaggregation. The unified buffers genuinely cannot be fp8 today: `sparse_attn_v4_paged_prefill` and the non-quantized `sparse_attn_v4_paged_decode` path both reject `unified_kv.dtype != q.dtype`, and the decode fp8 path needs a per-slot `kv_scales` tensor this pool does not own. So keep bf16 as the effective result but make it a decision instead of an accident: `resolve_unified_kv_dtype()` passes 16-bit float dtypes through (an fp16 model now gets fp16 buffers rather than bf16 ones) and warns on anything else, and the pool takes `dtype` as a required argument and rejects what its kernels cannot read. Behavior for the existing `--kv-cache-dtype fp8_e4m3` DSV4 recipes is unchanged apart from the new warning. Not addressed here: `_get_bytes_per_full_token` still budgets the unified layout at the packed 584 B/token/layer figure. It happens not to over-commit today only because the same formula also charges a `swa_ratio * kv_bytes * num_layers` SWA term that the unified pool does not allocate per token (its SWA ring is sized by request slots), and at `--swa-full-tokens-ratio 0.1` the two errors roughly cancel. That coincidence deserves its own fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Problem
DeepSeekV4TokenToKVPooltakes--kv-cache-dtypeand forwards it to every sub-pool it owns — the SWA / c4 / c128 single pools and the c4 indexer pool — butDeepSeekV4UnifiedKVPoolignored the argument entirely:Under
SGLANG_HACK_FLASHMLA_BACKEND=unified_kv_triton(HIP / MI355X) that makes--kv-cache-dtype fp8_e4m3a no-op for the main KV buffers, with nothing in the log to say so. The operator asks for the 584 B/token/layer packed layout (nope fp8 448 + rope bf16 128 + fp8 scales 8) thatDeepSeekV4SingleKVPool.get_bytes_per_token()implements and thatpool_configurator._get_bytes_per_full_token()budgets for, and silently getshead_dim× bf16 = 1024 B/token/layer.On DeepSeek-V4-Pro (61 layers: 30 × ratio-4, 31 × ratio-128) that is 1.75× the KV bytes the flag implies — both resident and on the wire in PD disaggregation:
Why the fix is not "make it fp8"
The unified buffers genuinely cannot be fp8 today:
sparse_attn_v4_paged_prefillrejectsunified_kv.dtype != q.dtype, and requiresqto be fp16/bf16 — there is no fp8 prefill path at all.sparse_attn_v4_paged_decodehas an fp8QUANT_KVpath, but it needs a per-slot fp32kv_scalestensor that this pool neither owns nor populates.So bf16 is currently required, not merely chosen. This PR keeps the effective result but makes it a decision instead of an accident.
Change
resolve_unified_kv_dtype(kv_cache_dtype)— passes 16-bit float dtypes through (an fp16 model now gets fp16 buffers instead of bf16 ones) and warns on anything else, naming both the requested dtype and the real byte cost.DeepSeekV4UnifiedKVPooltakesdtypeas a required keyword argument and raises on a dtype its kernels cannot read, so a future caller cannot reintroduce the silent substitution one level up.Behavior for the existing
--kv-cache-dtype fp8_e4m3DSV4 recipes is unchanged apart from the new warning.Tests
test/registered/unit/mem_cache/test_dsv4_unified_kv_dtype.py(CPU, 10 tests): dtype pass-through and fp8 fallback-with-warning; buffers allocated at the requested dtype (asserted with fp16, so a reintroduced bf16 hardcode still fails);get_buf_infos()item lengths tracking the element size; construction rejected for fp8/fp32; an AST guard that the single construction site passesdtype=resolve_unified_kv_dtype(dtype); and a premise test that pins why the fallback exists — if the prefill kernel ever accepts a non-q.dtypeKV buffer, that test fails andUNIFIED_KV_DTYPESshould be widened rather than the fallback quietly kept.Mutation-checked — each half of the fix reverted in isolation kills exactly its own test and nothing else:
dtype=torch.bfloat16in the allocationtest_buffers_use_the_requested_dtype,test_pool_init_has_no_hardcoded_dtype_literaltest_dtype_argument_is_resolved_not_hardcodedtest_fp8_falls_back_and_says_sotest_unsupported_dtype_is_rejected_at_constructionNot addressed here
_get_bytes_per_full_token()still budgets the unified layout at the packed 584 B/token/layer figure. It happens not to over-commit today only because the same formula also charges aswa_ratio * kv_bytes * num_layers_totalSWA term that the unified pool does not allocate per token (its SWA ring is sized by request slots, not by token capacity), and at--swa-full-tokens-ratio 0.1the two errors roughly cancel. That coincidence deserves its own fix.🤖 Generated with Claude Code
CI States
Latest PR Test (Base): ❌ Run #30981717518
Latest PR Test (Extra): ❌ Run #30981717352