Repository navigation
[ROCm][Perf] Fuse DSA indexer QK preprocessing with AITER - #53094
sumin-hong wants to merge 1 commit into
Conversation
|
👋 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. 🚀 |
3db257f to
128745a
Compare
Fuse LayerNorm, RoPE, FP8 Q/K quantization, scale folding, and K-cache writes behind a default-off ROCm flag. Add MI3XX shape and parallelism guards, shared outputs, MTP wiring, correctness tests, and a kernel benchmark. Co-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: Sumin Hong <sumin.hong@moreh.io>
128745a to
edcea1e
Compare
|
This pull request has merge conflicts that must be resolved before it can be |
|
Hi @sumin-hong! We noticed that this PR overlaps substantially with #51315, which our team has been developing, has received review, and is now close to merging. Both PRs integrate the same AITER I see that #53094 includes several useful additional integration and correctness improvements beyond the core kernel wiring in #51315. We don’t have a preference about which implementation ultimately gets merged; since #51315 is further along in review, it may be worth merging it first and port the additional improvements later. In either case, we should avoid further parallel work on the shared core. Pinging @dllehr-amd. |
Thanks, @mjkvaak-amd. Your feedback makes sense to us. |
…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>
Purpose
Every DeepSeek-Sparse-Attention layer (DeepSeek-V3.2, GLM-5.x, and their MTP drafts) runs a lightweight indexer whose pre-processing costs five kernel launches per layer per step:
LayerNormonkqandkqqscale into the indexerweightskplus the paged indexer K-cache writeAt decode sizes this is launch-bound: the cost barely moves with token count. AITER already ships a kernel that does all five in one launch (
indexer_qk_rope_quant_and_cache). This PR wires it in behind a new, default-off env flagVLLM_ROCM_USE_AITER_INDEXER_QK_FUSION.The kernel writes the indexer K cache itself, so the sparse-attention indexer op skips its own insert (
skip_k_cache_insert). Two data-movement optimizations ride along: the ropecos_sin_cachehalves are registered once as strided views on the indexer's rotary embedding instead of being re-split every layer every step, and one zero-initializedq_fp8/weightsoutput pair is shared by the indexer layers of a model (allocated next totopk_indices_bufferand sized the same way), with only the first indexer of a forward pass zero-filling it. The change is Python-only: the kernel is already in the AITER version the tree pins.The fused path engages only when all of the following hold, and otherwise falls back silently to today's code:
VLLM_ROCM_USE_AITER=1andVLLM_ROCM_USE_AITER_INDEXER_QK_FUSION=1on_mi3xx()): AITER ships this kernel in its CK build, and the RDNA paths thatSparseAttnIndexer.forward_hipnow also serves have Triton-only AITERrotary_embeddingcustom op is enabledindex_head_dim == 128andqk_rope_head_dim == 64decode_context_parallel_size == 1andprefill_context_parallel_size == 1). The fused kernel is driven byslot_mapping, which isPAD_SLOT_IDon ranks that do not own a token, so those ranks would skip the row and never produce its quantized query — while every CP rank still needs the query to score its own KV shard.Related work
#51315: same kernel, different integration scope
#51315 integrates the same AITER fused indexer kernel and is the direct overlapping effort. The shared core idea should land only once. This PR's material additional scope is:
This PR is ready for maintainer comparison and coordination: maintainers can choose this implementation or carry the safety and integration pieces above into #51315. It should not be interpreted as a second independent fused-kernel feature.
#51728: AITER dependency pin
Current vLLM main still pins AITER
v0.1.19; #51728 proposes moving the pin tov0.1.19.post2. This PR does not duplicate that dependency update. The submitted code follows the currently pinned norm-parameter ABI. If the pin moves first, the fused call must follow the post2 FP32 norm-parameter contract and the op-level and model-accuracy tests must be rerun on that stack before merge.Test Plan
vllm/vllm-openai-rocm:nightly-5a4c8d99242e9e069b604d0e9b969e77f7dd501d.--no-enable-prefix-cachingfor the throughput sweep. Accuracy leaves prefix caching at its default, since GSM8K's few-shot prompts share prefixes; both arms are identical per workload.Serve — GLM-5.2-FP8.
Serve — GLM-5.2-MXFP4.
Speculative decoding. Append this to either serve command:
--speculative-config '{"method":"mtp","num_speculative_tokens":5}'Op test
Kernel benchmark
Accuracy — coherence check. One chat completion, to confirm the model is not producing garbage before spending hours on the rest.
Accuracy — GSM8K
lm_eval --model local-completions --tasks gsm8k \ --model_args "model=<model>,base_url=http://127.0.0.1:$PORT/v1/completions,num_concurrent=64,max_retries=3,tokenized_requests=False,timeout=600"Accuracy — GPQA-Diamond, 198 questions, zero-shot, against the running server:
Accuracy — RULER / NIAH, 500 samples per context length, offline engine:
Serving throughput, for
CONCin 4 8 16 32 64 128 256:Lint
uvx pre-commit run --files $(git diff --name-only HEAD~1 HEAD)The op test scores the fused op and the unfused flow against an fp64 golden of the same math over 20 cases (
num_tokens∈ {1,7,32,257,1023} ×block_size∈ {1,64} ×is_neox∈ {True,False}): the fused path must be no less accurate than the unfused one, and the two must agree to within one fp8 code on every element above 1e-3 of the tensor maximum, for bothq_fp8 * weights_outand the dequantized indexer K cache. Two further cases pin the rows the kernel skips to zero andopcheckthe op's schema, for 22 in total.Test Result
Current main rebase (2026-08-27) — based on vLLM
fd57c4b7af; the conflict-free rebase preserves the submitted patch exactly (git range-diff: equal). The full vLLM pre-commit set passes. The ROCm GPU results below remain from the original image and have not been rerun after this rebase.Op-level equivalence —
22 passedon 8× MI355X (gfx950).Kernel speed — one indexer layer's pre-processing, median of
--repeat 5atblock_size=64,n_head=32(index_n_headsfor both GLM-5.2 checkpoints) and the interleaved RoPE layout GLM-5.x selects, on 8× MI355X (gfx950):pre-commit — all hooks pass (ruff check, ruff format, typos, markdownlint, mypy 3.10, SPDX headers, root lazy imports, forbidden imports,
torch.cudaAPI check, configuration validation, …), exit code 0.Coherence check — both arms answer 391 with a non-empty
content, i.e. the model closed its thinking block and answered:reasoningcontent17 times 23 is **391**.17 times 23 is 391.GSM8K (1319 questions, lm_eval
gsm8k)8× MI355X, TP8. Reported as
strict-matchwith lm_eval's standard error,flexible-extractin brackets.GPQA-Diamond (198 questions, zero-shot)
RULER / NIAH (
niah_single_2, 500 samples per length)Serving throughput, ISL 8192 / OSL 1024
GLM-5.2-FP8, MTP off, 8× MI355X TP8:
GLM-5.2-MXFP4, MTP off, 8× MI355X TP8:
Note: AI assistance was used to rebase, review, and prepare this contribution.
Essential Elements of an Effective PR Description Checklist