Skip to content

[ROCm][Perf] Fused MoE W4A16 HIP kernel for AMD RDNA3 (gfx1100) - #44075

Merged
AndreasKaratzas merged 12 commits into
vllm-project:mainfrom
JartX:perf/moe_rdna3_w4a16
Jun 6, 2026
Merged

AndreasKaratzas merged 12 commits into
vllm-project:mainfrom
JartX:perf/moe_rdna3_w4a16

Conversation

@JartX

@JartX JartX commented May 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Native HIP kernel for W4A16 MoE on RDNA3 (gfx1100), replacing the Triton
fused_moe_kernel_gptq_awq path. It uses the same dequant + dot primitives as
the dense W4A16 kernel: v_dot2_f32_f16 / v_dot2_f32_bf16, exllama bit-trick
dequant, and 64-bit CAS atomic output.

What it does

  • Fused HIP kernel (csrc/rocm/moe_q_gemm_rdna3.cu): expert routing + W4A16
    GEMM in a single kernel launch. Templated on BLOCK_SIZE_M = {1, 2, 4, 8}.
    The bf16 M=1 fast path skips LDS staging (direct global read, with a
    v_dot2_f32_bf16 opacity trick to defeat InstCombine).
  • output_topk parameter: fuses moe_sum into the w2 kernel — writes
    directly to out[token_id / top_k] via atomics, eliminating a separate kernel
    launch and the intermediate buffer.
  • Decode optimizations: BLOCK_SIZE_M = 1 for small M (eliminates ~75% of
    padding waste); pre-allocated w1/act buffers (eliminates per-layer
    torch.zeros).
  • Extensible dispatch (_try_get_rocm_moe_method()): checks architecture and
    op availability in priority order. New architectures (RDNA4, CDNA) can be added
    as branches. Falls through to Triton WNA16 if no native kernel is available.
  • Build guards: the .cu is only compiled when VLLM_GPU_ARCHES contains
    gfx1100; the torch op is registered under #ifdef VLLM_ROCM_GFX1100; the
    Python side checks hasattr(torch.ops._rocm_C, "moe_gptq_gemm_rdna3").

Benchmarks

Measured on 1× RX 7900 XTX (gfx1100) via EvalScope perf, openqa dataset,
50 requests. Native HIP kernel vs. upstream Triton fused_moe_kernel_gptq_awq
path.

Model: cyankiwi/Qwen3-30B-A3B-Instruct-2507-AWQ-4bit
(MoE, 128 experts / 8 active, W4A16 AWQ).

Throughput (completion tok/s)

max_num_seqs Triton (upstream) HIP (this PR) Speedup
1 82.84 102.01 1.23×
8 186.99 401.94 2.15×
32 348.49 755.62 2.17×

Per-request latency (max_num_seqs = 32)

Metric Triton (upstream) HIP (this PR) Improvement
TPOT (ms) 65.4 31.9 2.05×
Latency avg (s) 65.3 33.4 1.95×
TTFT avg (ms) 14,888 8,634 1.72×

The speedup scales with concurrency: ~1.2× single-stream, growing to ~2.2× under
batch (8–32 seqs), consistent with a kernel that improves batched decode
efficiency rather than single-request latency.

ACC EVAL
vllm serve cyankiwi/Qwen3-30B-A3B-Instruct-2507-AWQ-4bit --gpu-memory-utilization 0.90 --max-model-len 16384 --tensor-parallel-size 1 --port 8000 --enable-log-requests --tool-call-parser hermes --enable-auto-tool-choice --attention-backend TRITON_ATTN --served-model-name QWEN3 


lm_eval --model local-chat-completions --model_args model=QWEN3,base_url=http://localhost:8000/v1/chat/completions,num_concurrent=35,max_retries=3,tokenized_requests=False --tasks gsm8k --batch_size auto --apply_chat_template --output_path ./results/qwen_gsm8k_eval.json

HIP


Tasks Version Filter n-shot Metric Value Stderr
gsm8k 3 flexible-extract 5 exact_match 0.868 ± 0.0215
strict-match 5 exact_match 0.840 ± 0.0232

TRITON


Tasks Version Filter n-shot Metric Value Stderr
gsm8k 3 flexible-extract 5 exact_match 0.848 ± 0.0228
strict-match 5 exact_match 0.836 ± 0.0235

Testing

Model: cyankiwi/Qwen3-30B-A3B-Instruct-2507-AWQ-4bit, GPU: 1× RX 7900 XTX
(gfx1100). Output parity checked via EvalScope on openqa, HIP kernel vs. the
Triton WNA16 reference path:

```bash
evalscope eval
--model cyankiwi/Qwen3-30B-A3B-Instruct-2507-AWQ-4bit
--api-url http://localhost:8000/v1
--eval-type openai_api
--datasets openqa
```

@mergify

mergify Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor

Hi @JartX, the pre-commit checks have failed. Please run:

uv pip install pre-commit>=4.5.1
pre-commit install
pre-commit run --all-files

Then, commit the changes and push to your branch.

For future commits, pre-commit will run automatically on changed files before each commit.

Tip

Is mypy failing?
mypy is run differently in CI. If the failure is related to this check, please use the following command to run it locally:
# For mypy (substitute "3.10" with the failing version if needed)
pre-commit run --hook-stage manual mypy-3.10

@JartX
JartX force-pushed the perf/moe_rdna3_w4a16 branch from d061257 to e283487 Compare May 30, 2026 19:56
@AndreasKaratzas

Copy link
Copy Markdown
Member

cc @dllehr-amd Who should review this PR? Is there anyone from the you know familiar with gfx1100?

@JartX
JartX force-pushed the perf/moe_rdna3_w4a16 branch 3 times, most recently from ff3d9e9 to 29d6afd Compare May 30, 2026 22:45
@JartX
JartX force-pushed the perf/moe_rdna3_w4a16 branch from 29d6afd to dd0a25c Compare May 30, 2026 22:47
Comment thread csrc/rocm/moe_q_gemm_rdna3.cu Outdated
@JartX
JartX force-pushed the perf/moe_rdna3_w4a16 branch from dd0a25c to a948f5d Compare May 30, 2026 22:57
Comment thread csrc/rocm/moe_q_gemm_rdna3.cu
@DarkLight1337 DarkLight1337 added the verified Run pre-commit for new contributors without triggering other tests label May 31, 2026
@tjtanaa tjtanaa added the ready ONLY add when PR is ready to merge/full CI is needed label May 31, 2026
@tjtanaa

tjtanaa commented May 31, 2026

Copy link
Copy Markdown
Member

cc @dllehr-amd Who should review this PR? Is there anyone from the you know familiar with gfx1100?

Last time similar PR was reviewed by @mgehre-amd .

It would be great if we have someone from AMD who can review this.
@JartX has been a long-term contributor for Radeon support on vLLM. Moreover, like any opensource PR, as long as the validation provided is sufficient and code path is disjointed and not affecting CDNA code path and CI, and coded with sufficient quality, then we can review ourselves.

I would like to also see the Radeon support being brought up in vLLM as there are in fact many Radeon users based on the reddit local inferencing community.

@tjtanaa

tjtanaa commented May 31, 2026

Copy link
Copy Markdown
Member

@JartX Please provide the lmeval score for the model and make sure to validate with large concurrency to test the batching logic of the kernels.

In lm-eval command, if you do not specify num_concurrent=256 , lm-eval always use batch size=1 . (On radeon since the VRAM is small, you can adjust the num_concurrent based on your GPU VRAM. but make sure it is > 1 )

e.g.

MODEL=deepseek-ai/DeepSeek-V4-Pro
lm_eval --model local-completions --model_args model=$MODEL,base_url=http://0.0.0.0:8001/v1/completions,num_concurrent=256,max_retries=10,max_gen_toks=2048,max_length=1048576,timeout=60000 --batch_size auto --tasks gsm8k --num_fewshot 5 \
  --output_path ./results_deepseekv4pro_validatepr2_numshot5 \
  --log_samples \
| tee lmeval_deepseekv4pro_validatepr2_numshot5.log

JartX added 2 commits June 2, 2026 02:06
The AMD CI image (e.g. mi325) exposes csrc/ + CMakeLists.txt under
/vllm-workspace for building but installs the vllm python package as a
wheel, so vllm/_custom_ops.py and compressed_tensors_moe.py are not in
the source tree. _find_repo_root() still matched /vllm-workspace via
CMakeLists.txt + csrc/, so the python static-analysis tests raised
FileNotFoundError instead of skipping.

Add _read_source_or_skip() which skips the test when the target file is
absent. The C++/CMake guard checks still run (those files are present);
only the python-source checks skip where the python tree isn't shipped.

Signed-off-by: JartX <sagformas@epdcenter.es>
Instead of skipping the _custom_ops.py / compressed_tensors_moe.py guard
checks when the python source tree is absent, resolve them from the
installed vllm package via vllm.__file__. This makes the checks actually
run in CI (e.g. AMD mi325, which ships the wheel and only checks out
csrc/ + CMakeLists.txt for building) and verifies the code that is truly
imported at runtime.

C/CMake guard checks still read from the repo tree (csrc/, CMakeLists.txt
exist only in a source checkout); only those skip when the tree is
unavailable.

Signed-off-by: JartX <sagformas@epdcenter.es>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd suggest following the modular kernel / oracle style and create a new expert class for your rdna3 w4a16 kernel.

oracle and is_supported_config will handle the platform / config triage, and other quantization methods that support w4a16 can also benefit from your kernel if applicable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hi @BowenBao thanks for answer :)

I Appreciate the suggestion. Keeping the dispatch in compressed_tensors_moe was a deliberate call, and I'd lean toward keeping it scoped for this PR unless you feel strongly.

The intent is to keep the RDNA path self-contained and easy to follow. This kernel targets the RDNA consumer line (RDNA3 today, very likely RDNA4 and onward), and the plan is for each arch to have its own simple, dedicated trace. This also follows earlier guidance we got to keep these paths easily traceable and cleanly separated. A contained dispatch lets me do that without threading RDNA-specific gating into the shared wNa16 oracle, which is otherwise CUDA/CDNA-oriented. I'd rather keep RDNA and CDNA (MI2xx/MI3xx) as cleanly separated worlds, since an RDNA fused kernel has very different tradeoffs from the CDNA parts.

It's also fully gated and hermetic. RDNA3 code can't leak into non-gfx1100 builds (I added tests to enforce that), so there's zero blast radius on the CUDA/CDNA side.

So I'd suggest landing it as-is with the contained dispatch. That said, if you'd like, I'm very happy to work through together how to make the RDNA separation even cleaner. Either way works for me. What do you think?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Synced offline with @JartX, I'd recommend following the oracle and expert class convention that vllm is heading, which keeps RDNA logic contained and managed in the respective expert class. #43693 is related and is merging CompressedTensorsWNA16MarlinMoEMethod and CompressedTensorsWNA16MoEMethod. That said, if you are to refactor the PR it will depend on #43693 to land first.

Another option is to merge this as is and refactor later after #43693 lands.

I'll let other folks chime in as for preferences, cc @AndreasKaratzas , @tjtanaa , @mgoin , @bnellnm .

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks @BowenBao for summing it up. Both work for me, but I'd lean slightly toward merging as-is now and doing the oracle/expert refactor as a follow-up once #43693 lands. The kernel is already validated, tested and benchmarked, so this gets it into users' hands now. RDNA stays its own separate path either way, and I'm committed to the expert-class refactor right after #43693 merges. Happy to open a tracking issue for that follow-up so it doesn't fall through the cracks. That said, I'll defer to whatever you all prefer :)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm fine with either approach. #43693 already depends on another PR to land first so it might be awhile before it lands.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks @bnellnm for letting me continue with the PR. I'll add it to my watcher. As soon as PR #43693 changes status to merged, I'll do the refactor PR. 😊

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@BowenBao @JartX From my understanding, when we moved to the expert class and oracle, we won't have the rocm_moe.py file?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hi @tjtanaa, correct — after the refactor in #44460, the RDNA3 dispatch moves into its own expert class under the standard pattern, so rocm_moe.py would go away. For now it stays as-is per the merge-now/refactor-later agreement with @BowenBao and @bnellnm .

@BowenBao BowenBao left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM on quant side per follow-up refactors.

@JartX

JartX commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

@bnellnm @BowenBao @tjtanaa @AndreasKaratzas
Follow-up refactor tracked in #44460 — will migrate to the oracle/expert class pattern once #44570 lands, as agreed with @BowenBao and @bnellnm

@JartX

JartX commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

@bnellnm changed! to #44570 Could you please approve the PR if you agree !? many thanks! :)

if quant_method_name in (
"CompressedTensorsWNA16MarlinMoEMethod",
"CompressedTensorsWNA16MoEMethod",
"CompressedTensorsWNA16RDNA3MoEMethod",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There's another check for CompressedTensorsWNA16MoEMethod above (at around ~490). Does that need to be updated with the new class?

@JartX JartX Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@bnellnm many thanks for the review!

It is a good question, thanks for flagging it. I traced again it and I don't think that block needs the new class. Here's why:

CompressedTensorsWNA16RDNA3MoEMethod subclasses the non-Marlin CompressedTensorsWNA16MoEMethod and inherits its create_weights unchanged, which hardcodes load_full_w2=False on the w2 scale. intermediate_size_full is only ever consumed inside the Marlin/auto-GPTQ create_weights, gated on act-order (load_full_w2 = actorder and group_size != -1). On the non-Marlin path load_full_w2 is always False, so the w2 gets sharded normally by intermediate_size_per_partition and intermediate_size_full is never read.

@amd-xavierwang

amd-xavierwang commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

@JartX HI! I have some similar work in progress which serves all of RDNA family: #43389.

Quickly benchmarking shows the following on gfx1100(vllm serve, 200 prompts, sharegpt, gpu-utilization=0.9):

Model 1. cyankiwi/Qwen3-30B-A3B-Instruct-2507-AWQ-4bit (compressed-tensors, symmetric)

Metric HIP Kernel (#44075) Triton rewrite (#43389)
Benchmark duration (s) 55.95 62.62
Total input tokens 43560 43560
Total generated tokens 44559 44394
Request throughput (req/s) 3.57 3.19
Output token throughput (tok/s) 796.35 708.93
Peak output token throughput (tok/s) 1715.00 1615.00
Peak concurrent requests 200.00 200.00
Total token throughput (tok/s) 1574.84 1404.54
Mean TTFT (ms) 9958.49 1949.21
Median TTFT (ms) 9952.42 1107.50
P99 TTFT (ms) 18131.55 5058.90
Mean TPOT (ms) 226.35 153.53
Median TPOT (ms) 117.00 114.30
P99 TPOT (ms) 791.12 406.19
Mean ITL (ms) 95.95 102.42
Median ITL (ms) 66.57 97.46
P99 ITL (ms) 797.38 409.37

50 prompts, max_num_seqs=1

Metric HIP Kernel (#44075) Triton rewrite (#43389)
Benchmark duration (s) 111.11 201.67
Total input tokens 12332 12332
Total generated tokens 10349 10349
Request throughput (req/s) 0.45 0.25
Output token throughput (tok/s) 93.15 51.32
Peak output token throughput (tok/s) 103.00 55.00
Total token throughput (tok/s) 204.14 112.46
Mean TTFT (ms) 61564.61 107434.62
Median TTFT (ms) 63739.98 110339.05
Mean TPOT (ms) 9.85 19.10
Median TPOT (ms) 9.83 19.19

Model 2: Qwen/Qwen3-30B-A3B-AWQ (AWQ, asymmetric zeros). This PR doesn't affect this at all, changing only Compressed-tensor pass.

Metric this PR (#44075, essentially upstream/nothing changed) Triton rewrite (#43389)
Benchmark duration (s) 187.89 64.78
Total input tokens 43560 43560
Total generated tokens 44697 44697
Request throughput (req/s) 1.06 3.09
Output token throughput (tok/s) 237.89 690.03
Peak output token throughput (tok/s) 531.00 1531.00
Peak concurrent requests 200.00 200.00
Total token throughput (tok/s) 469.72 1362.51
Mean TTFT (ms) 17789.06 1981.39
Median TTFT (ms) 18522.95 1149.14
P99 TTFT (ms) 28339.27 5313.10
Mean TPOT (ms) 465.81 162.86
Median TPOT (ms) 386.72 120.68
P99 TPOT (ms) 979.98 450.58
Mean ITL (ms) 329.72 106.72
Median ITL (ms) 339.69 101.46
P99 ITL (ms) 988.43 424.99

Since your native HIP kernel beats, I think it makes sense to extends this kernel to asymmetric awq and symetric GPTQ models, which are currently dispatched differently through vllm/model_executor/layers/quantization/moe_wna16.py. I understand there is ongoing refactoring #44570 and you want to push this first. When you come back later to refactor, can you make sure this extends to awq/gptq models as well?

@JartX

JartX commented Jun 6, 2026

Copy link
Copy Markdown
Contributor Author

Many thanks @amd-xavierwang

Thanks a lot for the detailed benchmarks and the comparison, really appreciate you taking the time to run both configurations. Great data to have. And thanks for the kind words about the kernel, means a lot coming from someone at AMD.
Regarding extending the HIP kernel to asymmetric AWQ and symmetric GPTQ: absolutely, that’s on my radar. I’ll tackle it after the oracle/expert refactor lands #44460, since that will give us the right structure to plug in additional quant paths cleanly. Once that’s in place, extending to AWQ/GPTQ will be the natural next step.

@tjtanaa

It has been validated by 2 AMD members, both dispatch and performance.

It is getting closer :)

@tjtanaa tjtanaa left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM. Then we will need to quickly proceed with PR #44460

@AndreasKaratzas
AndreasKaratzas merged commit 062b05f into vllm-project:main Jun 6, 2026
179 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in AMD Jun 6, 2026
@amd-xavierwang

Copy link
Copy Markdown
Contributor

Many thanks @amd-xavierwang

Thanks a lot for the detailed benchmarks and the comparison, really appreciate you taking the time to run both configurations. Great data to have. And thanks for the kind words about the kernel, means a lot coming from someone at AMD. Regarding extending the HIP kernel to asymmetric AWQ and symmetric GPTQ: absolutely, that’s on my radar. I’ll tackle it after the oracle/expert refactor lands #44460, since that will give us the right structure to plug in additional quant paths cleanly. Once that’s in place, extending to AWQ/GPTQ will be the natural next step.

@tjtanaa

It has been validated by 2 AMD members, both dispatch and performance.

It is getting closer :)

I am happy that I can help! This PR is great work and I understand the toughness developing native HIP kernels.. Everyone knows native kernel is the ultimate efficient solution, and yet I chose a much easier path as I am not proficient developing HIP kernels right now.

Can't wait to collaborate in the future!!

nkzhenhua pushed a commit to nkzhenhua/vllm that referenced this pull request Jun 24, 2026
…-project#44075)

Signed-off-by: JartX <sagformas@epdcenter.es>
Co-authored-by: TJian <tunjian.tan@embeddedllm.com>
philippesic pushed a commit to philippesic/vllm-semantic-cache that referenced this pull request Jul 19, 2026
…-project#44075)

Signed-off-by: JartX <sagformas@epdcenter.es>
Co-authored-by: TJian <tunjian.tan@embeddedllm.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/build ready ONLY add when PR is ready to merge/full CI is needed rocm Related to AMD ROCm verified Run pre-commit for new contributors without triggering other tests

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

7 participants