[ROCm][MLA][Perf] Fuse decode QK-RoPE + Q-concat + KV-concat + KV-cache write for sparse MLA - #47757
[ROCm][MLA][Perf] Fuse decode QK-RoPE + Q-concat + KV-concat + KV-cache write for sparse MLA#47757xaguilar-amd wants to merge 5 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 |
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>
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?
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.