[ROCm][Perf] Enable fused indexer-Q RoPE+quant kernel for DeepSeek/GLM sparse attention - #47335
xaguilar-amd wants to merge 4 commits into
Conversation
|
@xaguilar-amd please provide GSM8K with num-shot 30 as well. It validates the trigger of the sparse mla logic. num-shot Please also provide the vllm serve commands. |
|
Hi @xaguilar-amd, the pre-commit checks have failed. Please run: uv pip install pre-commit>=4.5.1
pre-commit install
pre-commit run --all-filesThen, commit the changes and push to your branch. For future commits, |
|
@tjtanaa thanks for the review! Please find down below new accuracy numbers with num-shot 30: local-completions ({'model': 'zai-org/GLM-5.2-FP8', 'base_url': 'http://localhost:8004/v1/completions', 'num_concurrent': 32, 'max_retries': 10, 'max_gen_toks': 2048, 'tokenizer_backend': 'None', 'tokenized_requests': 'False'}), gen_kwargs: ({}), limit: None, num_fewshot: 30, batch_size: auto
The server command was: export VLLM_ROCM_USE_AITER=1 vllm serve zai-org/GLM-5.2-FP8 |
30a7831 to
0688f99
Compare
|
Hi @xaguilar-amd — I read your ROCm enablement of the fused indexer-Q RoPE, FP8 quantization, and weight-scale fold for DeepSeek/GLM sparse attention. It’s a small code change that depends on careful kernel and backend validation. I’m Daniel, a user researcher with a product research team studying local AI systems for inference engineers. My main question is: what evidence or tooling do you need before reusing an existing fused CUDA-oriented path safely on ROCm? Would you be open to a Zoom conversation of up to 30 minutes when convenient? We’re preparing a prototype and may later provide test units or invite relevant participants as early users or technical advisors. This is research, not sales. |
|
@xaguilar-amd: can you check if this PR is still needed and let's drive it actively forward if it is. |
|
@maeehart the PR is still relevant until the serving migrates to the new implementation in @zyongye could we please try to move this forward? It's just a 2-line change PR. It was already reviewed by TJ and CI is green. Thanks in advance! |
|
@xaguilar-amd can you rerun this and confirm that this is still needed? It's been awhile and a lot of things have moved. |
Signed-off-by: Xavier Aguilar <Xavier.AguilarFruto@amd.com>
…like() Signed-off-by: Xavier Aguilar <xavier.aguilarfruto@amd.com>
Signed-off-by: Xavier Aguilar <xavier.aguilarfruto@amd.com>
0688f99 to
02fdf57
Compare
|
@dllehr-amd Some testing on how this PR interacts with #51315 (AITER fused QK rope+quant+cache), since both touch the DSA indexer prologue. With #51315 in place, its branch shadows this one whenever AITER >=v0.1.15, which is where our pin is. But this PR is still the fallback that matters: without it, if #51315 isn't available the indexer drops all the way to the fully-unfused path, since the ROCm in-place-rope branch is off under Inductor. No conflict — they're consecutive tiers, so this one just sits one tier below: Worth merging both. |
|
Do I understand correctly that with #51315 and with the recent AITER version bump, this PR is not necessary anymore? |
|
yeah I guess, although we could have it as well as a safety net in case that AITER is not in place or whatever, it's just 2 lines of code changed. Your call @maeehart |
|
Yes, I think that it makes sense as a safety net. @dllehr-amd or @tjtanaa, can you check? |
Summary
Enables the existing fused indexer-Q kernel (
fused_indexer_q_rope_quant) onROCm for the DeepSeek Sparse Attention (DSA) indexer used by GLM-5.2 /
DeepSeek-V3.2-style models. On CUDA this kernel already collapses the indexer's
query-side RoPE + FP8 quantization + weight-scale fold into a single launch; on
ROCm the same work was falling back to a chain of small Triton/elementwise
kernels per sparse layer per decode token.
This is a low-risk, 2-line enablement — the kernel is already on vllm and is
architecture-aware. It removes redundant kernels and HBM round-trips on the
ROCm decode path (4 Q-side kernels → 1), yielding a small but consistent decode
improvement with no CUDA-side change and no measured regression.
Kernel-level evidence (torch.profiler, GLM-5.2-FP8, MI325X / gfx942, TP8, rank 0)
Before/after self-CUDA over the same steady-state decode capture. The indexer
decode kernels are the ones with ~21.5k calls (per decoded token × sparse
layers).
Fused away (present in baseline, gone after):
per_token_group_quant_8bit_kerneltriton_poi_fused_3triton_poi_fused_2triton_poi_fused_mul_slice_unsqueeze_view_4Introduced:
_fused_indexer_q_rope_quant_kernel_fused_indexer_q_rope_quant_kernel_0triton_poi_fused_add_copy_index_select_mul_slice_…The Q-side collapses from 4 dedicated kernels → 1 (the two
triton_poi_fused_2/_3RoPE kernels did q and k together; after the change q's RoPE is absorbed and
only a single K-RoPE kernel remains). Net ≈ 170 ms self-CUDA
reclaimed on rank 0 over the capture (≈0.7% of the ~24.9 s total). Untouched:
k_norm,indexer_k_quant_and_cache,_gluon_deepgemm_fp8_paged_mqa_logits,topKPerRowDecode.Accuracy
GLM-5.2-FP8, TP=8 on gfx942
End-to-end performance (GLM-5.2-FP8, TP8, MI325X / gfx942)
vllm bench serve, random dataset,--ignore-eos,--random-range-ratio 0.0.Decode runs under CUDA graphs.
Consistent small improvement across the sweep (~0.3–0.9%), with no credible
regression. *The single
8192/32TPOT dip is run-to-run variance: ITL (1.006x)and throughput (1.005x) both improve at that same point.
Related work / positioning
[ROCm][Perf] DSv3.2: fuse indexer Q-RoPE+quant + K-norm/RoPE/quant/cache)fuses Q and K via an AITER kernel, but explicitly excludes GLM
(
model_type != "glm_moe_dsa") and depends on a companion aiter kernel behindan env flag. This PR covers
glm_moe_dsausing the in-tree Triton kernelwith no aiter dependency and no new flags — complementary, not overlapping.
[ROCm][DSv3.2] Eliminate per-decode FillFunctor launches) removesthe
Fillkernels in the same hot loop; stacks cleanly on top of this change.Risk / compatibility
Low. CUDA behavior is bit-for-bit unchanged; the ROCm change is opt-in via the
same static guard and only alters the previously-fragmented eager path. The
end-to-end gain is intentionally modest (kernel/HBM-traffic reduction under CUDA
graphs), but the change is trivial, carries no measured regression, and compounds
with related sparse-indexer cleanups (#44527). Requires the fused branch to fire
(
is_inplace_rope == False), which is the default undertorch.compile/Inductor.