[ROCm][Bugfix] Keep the MLA query in bf16 for AITER Gluon fp8 KV decode - #50563
ZhengGong-amd wants to merge 2 commits into
Conversation
AiterMLAImpl inherits supports_quant_query_input = True from MLACommonImpl,
so with an fp8 KV cache the common MLA layer pre-quantizes the decode query
before calling the backend. For fewer than 16 heads the backend dispatches to
AITER's Gluon MLA kernel, whose fp8 KV regime (bh16bn128) is a bf16-query /
fp8-KV kernel: it folds the KV dequant scale into the QK temperature and
asserts a bf16 query. The result on gfx950 is that any small-head model with
--kv-cache-dtype fp8 dies during engine init with
RuntimeError: Worker failed with error
'q_nope/q_pe must be bf16, got torch.float8_e4m3fn/torch.float8_e4m3fn'
Kimi-K3 TP8 hits this: 96 MLA heads over 8 ranks is 12 heads per rank.
Leave the query unquantized for that combination, the same way TritonMLAImpl
already does for its own fp8 KV path. Head counts of 16 and above are
unaffected and keep using the fp8 query the asm kernels expect.
Signed-off-by: Zheng Gong <zgong@amd.com>
|
👋 Hi! Thank you for contributing to the vLLM project. 💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in PRs do not trigger a full CI run by default. Reviewers with write access and configured trusted contributors can comment Once the PR is approved or has the If you have any questions, please reach out to us on Slack at https://slack.vllm.ai. Agent GuidelinesIMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban. 🚀 |
fc5da58 to
6e6a889
Compare
… an env var
VLLM_K3_MLA_MERGE_ACROSS_BACKENDS=0 restores the pre-1755c10c behaviour, where a differing
indexes_kv_by_block_stride forces MLA layers into separate KV-cache groups. Default is
unchanged, so this is a no-op unless the variable is set.
This exists to bisect the merge itself, which is now the leading suspect for the fault that
needs both DSpark and the Mooncake tier:
DSpark off, tier on clean, concurrency 1/8/16 at 900 s each
DSpark on, tier off clean 420 s, acceptance 1.18
DSpark on, tier on HSA_STATUS_ERROR_EXCEPTION 0x1016 at 106 s
DSpark on, tier on, acceptance 1 clean 460 s
DSpark on, tier on, acceptance 8 fault at 121 s
The merge is the only structural thing enabling DSpark changes about the store. Its own
per-group logging shows the draft's five MLA layers joining the target's MLA group:
DSpark off segments per group={0: 23, 1: 23, 2: 23, 3: 24}
DSpark on segments per group={0: 23, 1: 23, 2: 23, 3: 29}
Excluding those five layers from the store while leaving them in the group was tried first
(branch yichaozhu/k3-dspark-no-draft-offload) and did not help: 93 segments registered, group 3
addressing 24 of its 29 layers, and it still faulted at 116 s. So if the merge is implicated it
is through sharing the group -- the block-id namespace and the block table -- rather than
through the draft's bytes being in the store's value.
Setting this to 0 costs 1.65x KV capacity, because the draft's 5 layers are then padded up to a
24-layer bucket ("Add 19 padding layers, may waste at most 380.00% KV cache memory"), and the
native 1M context stops fitting at all. The bisect therefore has to lower max-model-len, and the
control arm has to lower it identically so that memory pressure is not the variable under test.
Enabling an fp8 KV cache would buy the capacity back and let the merge be tested at the full 1M
shape, but that needs four pieces this fork does not have -- ROCm/aiter#4480 (still open),
vllm-project#50563, arguably vllm-project#50619 for the DSpark fp8 verify step, and fp8 added to
AiterMLABackend.supported_kv_cache_dtypes, which currently lists only auto/float16/bfloat16 --
and it would change the target's attention kernel, which is the thing being held fixed. Matching
max-model-len between the two arms answers the same question without any of that.
|
Closing this in favour of #51011, which is the right fix. This PR assumed fp8 KV decode would keep going through the Gluon kernel, and
Under that routing my change is not merely redundant, it is harmful: the #51011 also shows my validation was too weak to justify the claim I made. I The companion AITER change (ROCm/aiter#4480) is being narrowed to what still |
Purpose
Fixes fp8 KV cache on the ROCm AITER MLA backend for models with fewer than 16
MLA heads per rank.
AiterMLAImplinheritssupports_quant_query_input = TruefromMLACommonImpl, so with an fp8 KV cache the common MLA layer pre-quantizes thedecode query
(
mla_attention.pyif fp8_attention and self.impl.supports_quant_query_input:).For fewer than 16 heads the backend dispatches decode to AITER's Gluon MLA
kernel. Its fp8 KV regime (
bh16bn128) is a bf16-query / fp8-KV kernel: itfolds the KV dequant scale into the QK temperature and asserts a bf16 query. So
the pre-quantized query is rejected and the engine dies during init:
Kimi-K3 TP8 hits this: 96 MLA heads over 8 ranks is 12 heads per rank.
Fix
Leave the query unquantized for that combination, exactly as
TritonMLAImplalready does for its own fp8 KV path
(
triton_mla.py):Head counts of 16 and above are untouched and keep using the fp8 query the asm
kernels expect. bf16 KV caches are untouched at every head count.
Test Plan
Serve Kimi-K3 TP8 on gfx950 with
--kv-cache-dtype fp8and drive concurrentdecode, comparing against the same run without the flag.
Load generator: N concurrent
/v1/completionsrequests attemperature=0,each with a unique ~8192-token prompt and
max_tokens=256. Identical protocolon both sides: one discarded warmup pass, then five timed repetitions of 64
requests at concurrency 32; the first timed repetition was a cold outlier on
both sides and is excluded.
Test Result
Without this change the server never reaches startup; it fails engine init with
the
q_nope/q_pe must be bf16error above. With it,Application startup complete, and 64/64 requests succeed in every repetition.Capacity, from the engine's own accounting (deterministic across reboots):
Throughput at ISL 8192 / OSL 256 / concurrency 32:
fp8 is 3.5% faster at the median, but the ranges overlap, so this is within
run-to-run variance and is not claimed as a speedup. The benefit of fp8 KV on
this model is the +45% capacity; MLA is only ~10% of GPU time at this shape.
Greedy output is byte-identical between fp8 and bf16 for the shared prompts.
(Capacity grows +45% rather than 2x because only Kimi-K3's 24 full-attention
layers use the MLA cache; the other 69 are KDA and are not quantized.)
Kernel-level numerics for the fp8 regime at batch > 1 were validated separately
against an fp32 reference over the dequantized cache; see the companion AITER
PR.
Companion PR
This change alone is not sufficient: AITER's
mla_gluonalso assertsbatch_size == 1in thebh16bn128regime, so with only this PR the serverfails with
mla_gluon[bh16bn128] requires batch_size=1, got 512.The two are mutually dependent for fp8 KV to work and independently harmless,
since fp8 KV on small-head MLA is currently broken in every combination. Merge
order does not matter.
Related
Kimi-K3 TP8 stack (illegal memory access at ~337K context). It is not
addressed here: that report's KV pool works out to roughly 2.10M slots
(337,128 tokens at 16.08% usage), which is below the 3.73M-slot int32
row-base limit fixed in the companion AITER PR, so the two are distinct.
onto the 16-head persistent asm kernel, but is restricted to bf16 query and
bf16 KV, so it does not overlap with this fp8 path.
Found and validated with ROCm Hyperloom, an agentic system that auto-optimizes LLM inference workloads on AMD GPUs.