Repository navigation
[ROCm][MLA][Perf] Fuse decode QK-RoPE + Q-concat + KV-concat + KV-cache write for sparse MLA - #47757
xaguilar-amd wants to merge 8 commits into
Conversation
|
This pull request has merge conflicts that must be resolved before it can be |
1fc69d5 to
6f7e010
Compare
|
@xaguilar-amd can you use the existing AITER triton kernel |
|
This pull request has merge conflicts that must be resolved before it can be |
d3afdaa to
9887ba0
Compare
maeehart
left a comment
There was a problem hiding this comment.
pre-commit failed on mypy, same three errors on 3.10 through 3.13. tests/kernels/core/test_fused_qk_rope_concat_and_cache_mla.py:290 passes a SimpleNamespace into fused_qk_rope_concat_and_cache, which expects ROCMAiterMLASparseImpl. The other two are vllm/models/deepseek_v32/amd/rocm.py:235 and :262 reading cos_sin_cache after rotary_emb became nn.Module | None on MLAAttention. Can you type the test helper, add the same rotary_emb assert you already have in attention.py to the AMD executor, and push?
|
This pull request has merge conflicts that must be resolved before it can be |
23a1b00 to
833902f
Compare
|
This pull request has merge conflicts that must be resolved before it can be |
9b1987a to
55fbb49
Compare
|
This pull request has merge conflicts that must be resolved before it can be |
KV-concat, and fp8 KV-cache write into the single AITER kernel fused_qk_rope_concat_and_cache_mla, implemented at a model level, no FX-graph pass. Signed-off-by: Xavier Aguilar <xavier.aguilarfruto@amd.com>
Signed-off-by: Xavier Aguilar <xavier.aguilarfruto@amd.com>
Signed-off-by: Xavier Aguilar <xavier.aguilarfruto@amd.com>
… path The new rotary_emb: nn.Module | None on the base MLAAttention broke mypy (3.10-3.13): guard rotary_emb before use in the fused decode branch, narrow forward_context.slot_mapping to dict, ignore the impl attr assignment, and assert rotary_emb in the DeepseekV32 (nvidia) subclass that inherits the type. co-authored by Claude Signed-off-by: Xavier Aguilar <xavier.aguilarfruto@amd.com>
Signed-off-by: Aakif Nawaz <aakif.nawaz@amd.com> Signed-off-by: Aakif Nawaz <aakif.nawaz@amd.com>
Making rotary_emb `nn.Module | None` on the base MLAAttention left three call sites unnarrowed. Assert it before use in the DeepseekV32 AMD executor and in the sparse-MHA branch, and cast the duck-typed impl in the kernel test to ROCMAiterMLASparseImpl. Clean under mypy 1.20.2 on Python 3.10 through 3.13. Signed-off-by: Jin Tao <jin.tao@amd.com> Co-authored-by: Cursor <cursoragent@cursor.com>
eedbf6b to
89f32e6
Compare
…se branch Brings the changes tested on the tmp/v0.28.0 image series into moreh/v0.28.0. Content is exactly that of the fork PRs #16 to #22 (all against this branch or its tmp copy): #16 [ROCm][Perf] Fuse DSA indexer QK preprocessing with AITER (upstream vllm-project#53094) #17 [ROCm][Perf] Shard DSA prefill indexer rows across TP ranks (upstream vllm-project#56944) #18 [ROCm] Skip redundant sparse index remap on non-indexer layers (backport of upstream vllm-project/vllm vllm-project#51309) #19 [ROCm][MLA][Perf] Fuse decode QK-RoPE + Q-concat + KV-concat + KV-cache write for sparse MLA (backport of upstream vllm-project#47757) #20 [ROCm][Perf] Dispatch DSA prefill indexer logits to aiter's HIP kernel on gfx950 #21 [ROCm][Perf] Use AITER tuned GEMM for the MoE router gate (backport of upstream vllm-project#50535) #22 [ROCm][Bugfix] Add `positions` to unified_mla_attention_with_output_fake Verified as image vllm-forks:0.28.0-pr16-21-a4-9-260917 (this branch minus #22) built with aiter moreh/v0.1.19 + PR #4 + PR #9 on MI355X. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ITER On a stock build, Kimi-K3 MLA decode runs two kernels per layer per step: the fp8 KV-cache write (`vllm::concat_and_cache_mla_kernel`) and the `[ql_nope | q_pe]` concat plus that query's static fp8 quant, which the `mla_decode_concat_quant_fp8` custom op emits as a single `triton_poi_fused__to_copy_cat_clamp_mul_reciprocal_view` kernel. Collapse both into one AITER `fused_qk_rope_concat_and_cache_mla` launch. Measured on MI355X at TP=8, plain `vllm serve` on the stock upstream nightly with no speculative decoding and no compilation-config override: 288 -> 144 launches and 1351.8 -> 682.8 us per rank over 6 decode steps, i.e. 9.39 -> 4.74 us per MLA layer per decode step. End-to-end serving throughput is unchanged within measurement noise and this change does not claim a throughput win. K3 MLA is NoPE, while the kernel always applies RoPE and takes the cos/sin caches as required arguments. Feed it an identity rotation (cos=1, sin=0) so the rotation is a no-op -- the approach ROCm/ATOM uses for K3. The cache is a single row: the `_opt` decode kernel clamps `pos` into `[0, cos_cache.size(0))`, so real positions pass through untouched and K3's 1M max_position_embeddings does not cost ~268 MB to express a no-op. AITER's general decode kernel does not clamp, so the head counts that would select it are excluded. Kept entirely in `vllm/models/kimi_k3/amd/mla.py`, mirroring the q/kv RMSNorm fusion this wrapper already owns (vllm-project#52494); no shared MLA code is touched, so this carries no dependency on vllm-project#47757 and cannot affect any other model or backend. Everything that is fixed at load time -- platform, KV dtype, shapes, DCP/PCP/HiSparse, head padding and the W_UK variant -- is resolved once in `_fused_qk_prep_supported()`, so the only per-batch condition is that the batch is decode-only; anything else falls through to the stock `MLAAttention` path. Signed-off-by: Rita Brugarolas Brufau <rita.brugarolasbrufau@amd.com> Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Rita Brugarolas Brufau <rita.brugarolasbrufau@amd.com>
|
This pull request has merge conflicts that must be resolved before it can be |
Summary
On ROCm, the sparse-MLA decode path currently runs the per-layer Q/KV preparation
as several back-to-back ops: main RoPE on
q_pe/k_pe, the queryconcat (
ql_nope+ RoPE'dq_pe), the KV concat, and the fp8 KV-cache write.This PR collapses all of them into the single AITER kernel
fused_qk_rope_concat_and_cache_mla.The fusion is implemented at the model level (eager op dispatch from the MLA
layer), not as an FX graph-rewrite pass since vLLM is moving away from the
graph-pass fusion mechanism, and several models already adopt the model-level
eager approach. Implementing it this way keeps this change aligned with the
current direction, and makes it easily portable to that new model-level mechanism.
The fusion is gated and falls back cleanly to the existing split-kernel path when
it is not applicable (non-ROCm, non-gfx9, no AITER, non-fp8 KV cache, the
fp8_ds_mlalayout).Before:
After:
Three coordinated decision sites, all keyed off one impl flag
(
ROCMAiterMLASparseImpl.use_fused_qk_rope_cache):mla.py) — when fusing, skip the in-place main RoPE and pass raw(pre-RoPE)
q_pe/k_pedown, threadingpositionsas a real argument.ROCMAiterMLASparseImpl.do_kv_cache_update) — no-op when fusing(the write is folded into the fused kernel; the sparse path routes all tokens
through
forward_mqa, sonum_mqa_tokens == q.size(0)and the fused callwrites the full batch's KV, prefill included).
MLAAttention.forward_impl) — after theW_UKbmm, call the fusedkernel to produce the pre-fused
qand write the cache.Why
positionsis a real argument (not a forward-context stash)positionsis threaded throughMLAAttention.forward→ the opaque custom op(
unified_mla_attention_with_output) / directforward_impl→ the fused kernel,as an optional trailing tensor argument. An earlier stash into
forward_context.additional_kwargsinside the wrapper'sforwardproved notto be torch.compile / CUDA-graph safe.
Compatibility / no-effect cases
(
DeepseekV4ROCMAiterMLAAttention/DeepseekV4ROCMAiterMLASparseBackend,registry tag
ROCM_FLASHMLA_SPARSE_DSV4) with its ownforward_mqaand cachewrite — it never touches
MLAAttention,ROCMAiterMLASparseImpl,do_kv_cache_update, or the fused flag.are the intended beneficiaries.
forward; base MLA pathuntouched.
fp8_ds_mlaall miss the gate → byte-for-byteunchanged.
MLAAttentionlayers, so thepositionsargument covers them too.positionsadded as an optional trailing param (Tensor?) onMLAAttention.forward,forward_impl, andunified_mla_attention_with_output— backward compatible for every existing caller.
_aiter_ops.pyregistration is@if_aiter_supported(no-op); thenew op body lazy-imports AITER only when dispatched.
Test plan
Unit test (ROCm, gfx942)
pytest tests/kernels/core/test_fused_qk_rope_concat_and_cache_mla.py -v
Two tests cover the fused kernel:
concat_and_cache_mla) on acontiguous
ql_nope, acrossis_neox(True/False), seq lengths, head counts(16/128), and block sizes (1/64):
q_out==concat(ql_nope, RoPE(q_pe))q_out==scaled_fp8_quant(concat(...), q_scale)— verifies the kernel'sfolded q-side quant matches vLLM's downstream convention (tested with a
non-unit
q_scaleso a divide-vs-multiply mistake can't alias).kv_cache==concat_and_cache_mla(kv_c, RoPE(k_pe))(fp8).ql_nope: drivesROCMAiterMLASparseImpl.fused_qk_rope_concat_and_cacheandchecks fp8
q_out+ KV parity. This guards the.contiguous()fix — the rawkernel silently produces wrong
q_outfor a stridedql_nope.All configs pass.
Accuracy GLM-5.2-FP8 (gfx942, and TP=8)
Performance uplift GLM-5.2-FP8 (gfx942, TP=8, 3 repeats per experiment)
Serving recipe:
Accuracy Deepseek-V3.2 (gfx942, TP=8)
Performance uplift DeepSeek-V3.2 (gfx942, TP=8)
the small perf drop on mc32 and mc64 is most likely run variance noise and not a real perf. regression, since TPOT and ITL disagree. I can re-run the experiments a few times and provide averages if needed.
Serving recipe:
Related work
This PR was created with the help of AI.