Skip to content

[ROCm][Perf] Use AITER tgemm for DeepSeek V4 compressors - #48727

Closed
Fangzhou-Ai wants to merge 10 commits into
vllm-project:mainfrom
Fangzhou-Ai:afz/dsv4-aiter-tgemm-compressors
Closed

[ROCm][Perf] Use AITER tgemm for DeepSeek V4 compressors#48727
Fangzhou-Ai wants to merge 10 commits into
vllm-project:mainfrom
Fangzhou-Ai:afz/dsv4-aiter-tgemm-compressors

Conversation

@Fangzhou-Ai

@Fangzhou-Ai Fangzhou-Ai commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Use AITER tuned BF16 GEMMs for the DeepSeek V4 main and optional indexer
fused_wkv_wgate compressor projections on gfx950.

The optimized path is intentionally limited to FULL CUDA-graph execution with
M <= 4, where M is the compressor projection row count. Larger shapes,
PIECEWISE graphs, and eager execution retain the original FP32-output
torch.mm path.

This changes the two compressor score outputs from FP32 to BF16 only on that
optimized path. AITER remains opt-in through VLLM_ROCM_USE_AITER=1; users can
retain the original compressor path with VLLM_ROCM_USE_AITER_LINEAR=0.

Implementation

  • Cache hardware, AITER, compressor, and weight-dtype eligibility at
    initialization so non-dynamic predicates are not repeated per layer and
    forward pass.
  • Route eligible BF16 projections to AITER tgemm only during FULL graph
    capture/replay when M <= 4.
  • Check the dynamic row count before the CUDA-graph runtime mode so larger
    batches also skip that repeated context lookup.
  • Override only the existing attn_gemm_parallel_execute input-GEMM hook.
    Reuse its inherited path and existing attention boundary for M > 4,
    PIECEWISE graphs, and eager execution.
  • Reuse the shared DeepSeek V4 forward implementation; no ROCm-specific graph
    decorator, capture hook, or alternate attention forward is added.
  • Call the polymorphic _fused_wqa_wkv_gemm() helper so FP8 preshuffled
    projections remain correct.
  • Add focused coverage for runtime mode, row count, cached eligibility, input
    dtype, both compressor weights, and the inherited tuple contract.

M <= 4 is the range where the current DSV4 AITER table has tuned entries for
both compressor projections. In the same-base serving sweep, unrestricted
tgemm improved CONC=4, was neutral at CONC=8, and regressed throughput by
8.23%, 10.91%, and 7.61% at CONC=16, 32, and 64 respectively. The bounded path
preserves the low-concurrency optimization without those larger-batch
regressions.

An earlier all-runtime-mode version was also evaluated and rejected. At
CONC=4 it reached 154.462 output tok/s, 8.99% below its supplied 169.714 tok/s
baseline, because PIECEWISE execution exposed 132 per-forward tgemm dispatches
to the CPU. Keeping the optimization inside FULL graphs removes that repeated
dispatch overhead.

The unrestricted CONC=128 run also encountered an HSA memory-access fault when
the indexer projection selected its M=128 ASM split-K kernel. Separately,
ROCm/aiter#4494 tracks an ASM split-K
semaphore deadlock under graph capture. That upstream report supports avoiding
the larger captured ASM path for now, but it does not prove that its reported
deadlock caused the HSA fault observed here.

Duplicate-work check

No issue number was supplied, so an issue-specific lookup was not applicable.
Open-PR searches for DeepSeek V4 AITER tgemm, compressor tgemm, and
DeepSeek V4 compressor ROCm found no other PR implementing this change.

The closest open work is materially different:

  • #23712 integrates AITER
    tgemm into generic linear layers; these compressor projections use direct
    torch.mm calls and are not routed through that integration.
  • #46699 adds an opt-in HIP
    path that fuses the broader compressor, normalization, RoPE, quantization,
    and cache-storage pipeline; this PR only replaces two BF16 projection GEMMs.

Tests

  • .venv/bin/python -m pytest tests/kernels/attention/test_rocm_triton_attn_dsv4.py -q
    — 49 passed.
  • .venv/bin/pre-commit run --files vllm/models/deepseek_v4/amd/rocm.py tests/kernels/attention/test_rocm_triton_attn_dsv4.py
    — all hooks passed, including Ruff, mypy, and DCO sign-off checks.

Performance and model evaluation

The side-by-side comparison uses the branch's exact parent,
821717118fc26667dd474b9b0ab81d29259dfc5c, as the baseline and
aaa3fee24014237015e6b9578f28c7f21b8034bc as the PR revision. Both sides used fresh servers in the same
refreshed container, editable environment, model weights, and server
configuration.

Settings: DeepSeek-V4-Pro, TP=8, gpu-memory-utilization=0.8, random
8K-input/1K-output requests, random-range-ratio=0.8, seed=0, ignore-eos,
2 * CONC warmups, and 10 * CONC measured requests. Each fresh server
received one discarded CONC=4 preconditioning pass before its recorded sweep.
The baseline and PR used pairwise-identical input/output token-length arrays.

CONC Requests Input / output tokens Output tok/s, baseline → PR (Δ) Mean TPOT ms, baseline → PR (Δ) Mean TTFT ms, baseline → PR (Δ) Mean E2E ms, baseline → PR (Δ)
4 40 294,050 / 36,805 185.450 → 193.110 (+4.13%) 20.566 → 19.729 (+4.07%) 461.732 → 467.166 (-1.18%) 19,366.683 → 18,600.765 (+3.95%)
8 80 584,809 / 74,069 318.593 → 321.202 (+0.82%) 23.829 → 23.708 (+0.51%) 755.531 → 711.015 (+5.89%) 22,854.103 → 22,682.563 (+0.75%)
16 160 1,175,032 / 146,426 558.039 → 557.572 (-0.08%) 26.960 → 26.986 (-0.09%) 718.614 → 744.293 (-3.57%) 25,388.323 → 25,433.095 (-0.18%)
32 320 2,352,062 / 296,143 832.600 → 839.829 (+0.87%) 35.983 → 35.807 (+0.49%) 1,300.083 → 1,188.915 (+8.55%) 34,625.384 → 34,343.454 (+0.81%)
64 640 4,727,544 / 589,927 1,095.010 → 1,098.108 (+0.28%) 54.877 → 54.966 (-0.16%) 2,354.723 → 2,112.954 (+10.27%) 52,980.846 → 52,827.137 (+0.29%)
128 1,280 9,454,961 / 1,177,736 1,340.538 → 1,404.579 (+4.78%) 89.562 → 85.772 (+4.23%) 4,231.753 → 3,707.103 (+12.40%) 86,711.682 → 82,711.240 (+4.61%)

The output-throughput geometric-mean gain across the six concurrency points is
+1.78%. CONC=128 completed all 1,280 requests without the HSA fault seen
with unrestricted tgemm.

For throughput, delta is (PR / baseline - 1) * 100; for latency, delta is
(baseline - PR) / baseline * 100. Positive values therefore indicate
improvement.

Final-head GSM8K, 5-shot greedy, 1,319 examples:

  • strict match: 1,267/1,319 (96.0576%)
  • flexible extract: 1,266/1,319 (95.9818%)

Both scores match the prior baseline exactly.

AI assistance disclosure

OpenAI Codex assisted with review-comment analysis, simplifying the
implementation, running tests and model evaluations, benchmarking, and drafting
this description. The human submitter reviewed every changed line and can
understand and defend the change end-to-end.

@mergify mergify Bot added deepseek Related to DeepSeek models nvidia rocm Related to AMD ROCm labels Jul 15, 2026
@mergify mergify Bot added the v1 label Jul 15, 2026
@github-project-automation github-project-automation Bot moved this to Todo in AMD Jul 15, 2026
@Fangzhou-Ai
Fangzhou-Ai marked this pull request as ready for review July 15, 2026 11:10

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

Comment thread vllm/models/deepseek_v4/amd/rocm.py Outdated
Comment thread vllm/models/deepseek_v4/amd/rocm.py Outdated
Comment thread vllm/platforms/rocm.py Outdated
@tjtanaa

tjtanaa commented Jul 15, 2026

Copy link
Copy Markdown
Member
  • Enter a platform warmup hook around uncaptured CUDA graph warmups. The
    default hook is a no-op; ROCm scopes the target graph mode in its additional
    forward context so AITER can initialize the tuned GEMMs before capture.

Can you explain AITER's behaviour in more details? What are initialized? Why can't we expect it is just a regular function where you call the tgemm like regular function during graph capture?

@tjtanaa

tjtanaa commented Jul 15, 2026

Copy link
Copy Markdown
Member

Another thing, there is a thread on slack discussing whether it will cause accuracy issue by swapping from FP32 accumulation to BF16 accumulation
https://vllm-dev.slack.com/archives/C0AVB34LZT6/p1777654100621619 , I would like to get @zyongye opinion on this.

Original

torch.mm(..., out_dtype=torch.float32)

This PR

tgemm.mm(..., otype=torch.bfloat16)

@zyongye

zyongye commented Jul 15, 2026

Copy link
Copy Markdown
Member

Another thing, there is a thread on slack discussion whether it will cause accuracy issue by swapping from FP32 accumulation to BF16 accumulation https://vllm-dev.slack.com/archives/C0AVB34LZT6/p1777654100621619 , I would like to get @zyongye opinion on this.

Original

torch.mm(..., out_dtype=torch.float32)

This PR

tgemm.mm(..., otype=torch.bfloat16)

I personally don't think we can turn this on by default. But we can keep both path and let user choose if needed.

@mergify

mergify Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @Fangzhou-Ai.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase label Jul 17, 2026
@Fangzhou-Ai
Fangzhou-Ai force-pushed the afz/dsv4-aiter-tgemm-compressors branch from 7e2509c to 546d394 Compare July 22, 2026 03:11
@mergify mergify Bot removed the needs-rebase label Jul 22, 2026
@mergify

mergify Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @Fangzhou-Ai.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify

mergify Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @Fangzhou-Ai.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase label Jul 30, 2026
@mergify mergify Bot added the mrv2 Model Runner V2 specific label Jul 31, 2026
@Fangzhou-Ai
Fangzhou-Ai force-pushed the afz/dsv4-aiter-tgemm-compressors branch from b7e1551 to 76cbbc9 Compare August 6, 2026 01:37
@mergify mergify Bot removed the needs-rebase label Aug 6, 2026
@Fangzhou-Ai

Copy link
Copy Markdown
Contributor Author

Hi @tjtanaa @zyongye I have made a couple of changes to make this PR much simpler, the CUDA graph changes are removed and some per-forward functional calls are cached for better performance. Currently the only concern would be the fp32-->bf16 conversion, although from my test I didn't see any precision regression, do you guys have any suggestions on it?

Fangzhou-Ai and others added 9 commits August 7, 2026 00:05
Use tuned BF16 GEMMs for the two compressed-attention projections in full CUDA graphs. Scope the warmup signal to a ROCm platform hook and compute indexer weights in the AMD implementation, leaving the shared model and NVIDIA behavior unchanged.

Co-authored-by: OpenAI Codex <noreply@openai.com>

Signed-off-by: fai <fangzhouai@gmail.com>
Signed-off-by: tjtanaa <tunjian.tan@embeddedllm.com>
Assisted-by: OpenAI Codex
Signed-off-by: Fangzhou Ai <fangzhou.ai@amd.com>
Call AITER tgemm directly during full graph capture and remove the platform warmup-context plumbing. Preserve ROCm preshuffled FP8 projections through the polymorphic projection helper.

Assisted-by: OpenAI Codex

Signed-off-by: Fangzhou Ai <fangzhou.ai@amd.com>
Select tgemm directly from cached static eligibility, BF16 input dtype, and the V1 FULL graph mode. This removes the per-layer capture-state query while leaving V1 uBatch and V2 on the existing fallback.

Assisted-by: OpenAI Codex
Signed-off-by: Fangzhou Ai <fangzhou.ai@amd.com>
Route all eligible BF16 compressor projections through AITER tgemm without inspecting CUDA graph runtime mode. This applies consistently to V1, V2, eager, FULL, and PIECEWISE execution.

Assisted-by: OpenAI Codex
Signed-off-by: Fangzhou Ai <fangzhou.ai@amd.com>
Keep metadata-dependent DeepSeek V4 attention work in an eager segment for PIECEWISE breakable graphs. FULL captures, V2 outer captures, and eager execution continue to run the tgemm path directly.

Assisted-by: OpenAI Codex
Signed-off-by: Fangzhou Ai <fangzhou.ai@amd.com>
Route eligible compressor projections through AITER tgemm at the existing input-GEMM hook, preserving the shared metadata eager boundary without a model-specific CUDA graph decorator.

Assisted-by: OpenAI Codex <noreply@openai.com>

Signed-off-by: Fangzhou Ai <fangzhou.ai@amd.com>
Keep AITER tgemm inside FULL graph capture while routing PIECEWISE and eager execution through the shared input-GEMM path. This avoids replaying per-layer tgemm dispatch eagerly and keeps fresh tgemm outputs from crossing the breakable attention boundary.

Assisted-by: OpenAI Codex <noreply@openai.com>
Signed-off-by: Fangzhou Ai <fangzhou.ai@amd.com>
@Fangzhou-Ai
Fangzhou-Ai force-pushed the afz/dsv4-aiter-tgemm-compressors branch from 4f70b30 to 4c13bdd Compare August 7, 2026 02:14
@Fangzhou-Ai

Fangzhou-Ai commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Rebased and force-pushed the simplified implementation on the refreshed base.

  • AITER tgemm is now limited to FULL CUDA-graph execution. PIECEWISE and eager
    execution use the inherited FP32 torch.mm path; no model-specific capture
    hook or alternate attention forward remains.
  • Refreshed-image CONC=4 (TP=8, 40 requests, 8k/1k): 193.688 output tok/s,
    19.666 ms TPOT, 469.880 ms TTFT, and 18,545.889 ms mean E2E. The previously
    supplied matched branch/baseline runs were 184.982 vs 169.714 output tok/s
    (+8.99%) and 20.589 vs 22.058 ms TPOT (-6.66%).
  • GSM8K 5-shot greedy, 1,319 examples: 1,266/1,319 strict (95.9818%) and
    1,265/1,319 flexible (95.9060%). The prior baseline was 1,267/1,319 and
    1,266/1,319 respectively.

These are intermediate measurements. The final refreshed-base side-by-side
sweep supersedes the older performance comparison above; its complete table is
in the PR description and the latest results comment.

Validation: 47 focused tests passed, and all pre-commit hooks passed on the two
changed files. The PR body contains the full commands, comparisons, duplicate
check, and AI-assistance disclosure.

Use AITER tgemm only for FULL graph batches up to four tokens, where both DeepSeek V4 compressor projections have tuned entries. Larger batches retain the original FP32-output GEMM, avoiding serving regressions and the M=128 ASM graph failure.

Assisted-by: OpenAI Codex <noreply@openai.com>

Signed-off-by: Fangzhou Ai <fangzhou.ai@amd.com>
@Fangzhou-Ai

Fangzhou-Ai commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Updated the PR description with the final same-base side-by-side sweep. Positive
deltas indicate improvement.

CONC Output tok/s, baseline → PR (Δ) TPOT ms, baseline → PR (Δ) TTFT ms, baseline → PR (Δ) E2E ms, baseline → PR (Δ)
4 185.450 → 193.110 (+4.13%) 20.566 → 19.729 (+4.07%) 461.732 → 467.166 (-1.18%) 19,366.683 → 18,600.765 (+3.95%)
8 318.593 → 321.202 (+0.82%) 23.829 → 23.708 (+0.51%) 755.531 → 711.015 (+5.89%) 22,854.103 → 22,682.563 (+0.75%)
16 558.039 → 557.572 (-0.08%) 26.960 → 26.986 (-0.09%) 718.614 → 744.293 (-3.57%) 25,388.323 → 25,433.095 (-0.18%)
32 832.600 → 839.829 (+0.87%) 35.983 → 35.807 (+0.49%) 1,300.083 → 1,188.915 (+8.55%) 34,625.384 → 34,343.454 (+0.81%)
64 1,095.010 → 1,098.108 (+0.28%) 54.877 → 54.966 (-0.16%) 2,354.723 → 2,112.954 (+10.27%) 52,980.846 → 52,827.137 (+0.29%)
128 1,340.538 → 1,404.579 (+4.78%) 89.562 → 85.772 (+4.23%) 4,231.753 → 3,707.103 (+12.40%) 86,711.682 → 82,711.240 (+4.61%)

Output-throughput geometric-mean gain: +1.78%. The M <= 4 routing removes
the previous CONC=16/32/64 regressions, and CONC=128 completed 1,280/1,280
requests without the prior HSA fault.

Final-head GSM8K, 5-shot greedy, 1,319 examples:

  • strict match: 1,267/1,319 (96.0576%)
  • flexible extract: 1,266/1,319 (95.9818%)

Both scores match the prior baseline exactly.

@Fangzhou-Ai
Fangzhou-Ai marked this pull request as draft August 7, 2026 06:36
@Fangzhou-Ai

Copy link
Copy Markdown
Contributor Author

I will close this PR for now as a conversion from fp32 to bf16 can cause some precision issue under some corner cases.

@github-project-automation github-project-automation Bot moved this from Todo to Done in AMD Aug 16, 2026
@github-project-automation github-project-automation Bot moved this to Done in NVIDIA Aug 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deepseek Related to DeepSeek models mrv2 Model Runner V2 specific nvidia rocm Related to AMD ROCm v1

Projects

Status: Done
Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants