Repository navigation
[ROCm][Perf] Tiny-dot kernel for the Qwen MoE shared-expert gate - #56789
roberteg16 wants to merge 3 commits into
Conversation
The Qwen MoE shared_expert_gate is a ReplicatedLinear(hidden_size, 1), so it reaches rocm_unquantized_gemm as a 1x1xK GEMM. hipBLASLt answers that with a 64x96x32 macro tile and a SplitK post-pass to produce one scalar. Replace that shape with a fused elementwise-mul + reduction. Gated on gfx1151, the only architecture where it was measured; everything else keeps the BLAS call. benchmarks/kernels/bench_tiny_dot.py on gfx1151, 1x1xK bf16, BLAS -> eager, medians of five invocations since the BLAS arm is not repeatable enough for a single reading: K=1024 12.60 -> 9.16 us K=2048 16.12 -> 8.36 us K=4096 22.94 -> 8.64 us Co-authored-by: Matthias Gehre <matthias.gehre@amd.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Robert Esclapez Garcia <robert.garcia@amd.com>
The eager (x*w).sum(dtype=x.dtype) fallback still costs two launches per call, and the gate runs once per MoE layer per decode step. A single workgroup with fp32 accumulation does it in one, and its cost is flat in K where both other paths grow. Restricted to bias=None and to a K whose elements still fit the workgroup's registers; anything else keeps the eager chain. benchmarks/kernels/bench_tiny_dot.py on gfx1151, 1x1xK bf16, eager -> Triton, medians of five invocations: K=1024 9.16 -> 5.24 us K=2048 8.36 -> 5.22 us K=4096 8.64 -> 5.20 us Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Robert Esclapez Garcia <robert.garcia@amd.com>
The Qwen MoE shared expert scales its output by sigmoid(shared_expert_gate(x)), which on a single token is three launches: mul, sum, sigmoid. An APPLY_SIGMOID constexpr folds all three into the tiny-dot kernel, and allocating the scalar output in the input dtype lets the store do the rounding instead of a separate copy. The call site keeps the eager spelling for multi-token batches, where the gate is a real GEMM, and on anything but gfx1151. It also reads the gate weight directly rather than calling the layer, so it requires an unquantized weight and no bias. benchmarks/kernels/bench_tiny_dot.py --sigmoid on gfx1151, 1x1xK=2048 bf16: eager fused 8.36 -> 10.88 us, the sigmoid costing another launch Triton 5.22 -> 5.26 us, the fold making it free End-to-end numbers for the whole series are in the pull request. Co-authored-by: Matthias Gehre <matthias.gehre@amd.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Robert Esclapez Garcia <robert.garcia@amd.com>
|
👋 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. 🚀 |
|
Not needed any more after #53283. |
Summary
A Qwen MoE block gates its shared expert with
sigmoid(shared_expert_gate(x)), whereshared_expert_gateis aReplicatedLinear(hidden_size, 1). During decode with a single token that is a 1x1xK GEMM, and hipBLASLt answers it with a 64x96x32 macro tile plus a SplitK post-pass in order to produce one scalar. Qwen3.5-35B-A3B has 40 such layers, so this fires 40 times per decode step.This series replaces that shape with a dot product, then folds the surrounding work into the same kernel. On gfx1151 it is worth 3.2% of decode throughput end to end, with prefill and TTFT unchanged. Everything is gated on
on_gfx1151(), the only architecture measured here.What this touches
The dispatcher change fires for any unquantized bf16/fp16
Linear(K, 1)on a single token, which in this tree means theshared_expert_gateinqwen2_moe,qwen3_moe,qwen3_next,qwen3_5andinterns2_mobius. The only other 1-wide projection,qwen3_dspark'sproj, is fp32 and therefore excluded.The call-site change is narrower: it lives in
Qwen2MoeMLP, so it reaches Qwen2-MoE, Qwen3-Next, Qwen3.5-MoE and InternS2 Mobius.qwen3_moehas its ownQwen3MoeMLPwhoseforwardis line-for-line identical; I left it alone because I have not measured it, but folding the two would be the obvious follow-up.Checkpoints without a shared expert are untouched, as is any deployment running with
VLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTS, whereexpert_gateisNone.Commits
Replace the 1x1xK BLAS call with a fused dot on gfx1151(x*w).sum(dtype=x.dtype)instead of BLAS.Serve the tiny scalar projection from a Triton kernelbias=Noneand K within the register budget._tiny_dotholds that choice so callers do not repeat it. Adds the correctness test and the A/B benchmark.Fold the shared_expert_gate sigmoid into the tiny-dot kernelAPPLY_SIGMOIDconstexpr collapses mul + sum + sigmoid into one launch at theQwen2MoeMLPcall site, and the scalar output is allocated in the input dtype so the store does the rounding rather than a separate copy. Extends the benchmark with--sigmoid.End-to-end results
Qwen3.5-35B-A3B-W4A16 on gfx1151 (Strix Halo), concurrency 1, 100-token prompts, 128 output tokens, 10 prompts,
max_num_seqs=1. Three runs per arm, medians below.upstream/mainKernel microbenchmark
benchmarks/kernels/bench_tiny_dot.py, times in µs. Each cell is the median of five separate process invocations, because the BLAS arm is not repeatable enough for a single reading (see below).The Triton kernel is flat in K at roughly 5 µs, being one launch over a single workgroup, while BLAS grows with K. Qwen3.5-35B-A3B uses K=2048.
With
--sigmoid, which is how the gate is actually used, at K=2048 bf16:Folding it into the kernel is what makes the sigmoid free; the other two arms pay another launch for it, so the Triton margin over the eager chain widens from 1.60x to 2.07x.
Accuracy
GSM8K, 5-shot, temperature 0, 400 questions per arm, no invalid responses on either side.
upstream/mainTesting
The new test compares the kernel against the eager reference across K = 32 / 1024 / 2048 / 4096 / 65536 / 131072, both dtypes, with and without the folded sigmoid. The two largest K exercise the multi-warp launches.
Software stack
Measured on ROCm 10.1.0a20260822 (
torch.version.hip7.16), PyTorch 2.13.0, Triton 3.8.0. This is newer than whatdocker/Dockerfile.rocm_basepins for the shipped image (ROCm 7.2.3, PyTorch 2.12, Triton 3.7). On older stack, improvement is even bigger.Not a duplicate
No duplicated PRs identified.
AI assistance
This change was prepared with AI assistance (Claude). Every line was reviewed by the submitter, and the measurements above were run and checked by hand.
Essential Elements of an Effective PR Description Checklist
supported_models.mdandexamplesfor a new model.