[MI355X][ROCm][AMD] Fuse the DSA indexer QK prologue for GLM-5.x - #14
Open
phucnguyen-ht wants to merge 1 commit into
Open
[MI355X][ROCm][AMD] Fuse the DSA indexer QK prologue for GLM-5.x#14phucnguyen-ht wants to merge 1 commit into
phucnguyen-ht wants to merge 1 commit into
Conversation
The DSA indexer spends five kernel launches per layer per step on pre-processing: LayerNorm on k, RoPE on q and k, FP8 quantization of q, folding the q scale into the indexer weights, and the FP8 k quantization plus paged K-cache write. At decode sizes this is launch-bound. AITER's indexer_qk_rope_quant_and_cache does all five in one launch. Wire it in behind a new default-off flag, VLLM_ROCM_USE_AITER_INDEXER_QK_FUSION; the kernel writes the indexer K cache itself, so the indexer op is built with skip_k_cache_insert=True from the same init-time predicate. Two data-movement optimizations ride along: the rope cos_sin_cache halves are registered once as strided views instead of being re-split every layer every step, and one zero-initialized q_fp8/weights pair is shared by a model's indexer layers. Signed-off-by: moreh <moreh.team@moreh.com.vn> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
phucnguyen-ht
force-pushed
the
feat/rocm-aiter-indexer-qk-fusion
branch
from
August 20, 2026 07:11
9db584e to
da646cd
Compare
4 tasks
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.
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.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
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
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: This change includes AI-generated code.