Skip to content

[XPU][MiniMax-M3] Add and optimize fp32_router_gemm SYCL kernel for BMG#421

Closed
yangulei wants to merge 3 commits into
vllm-project:mainfrom
yangulei:pr/router-gemm-opt
Closed

[XPU][MiniMax-M3] Add and optimize fp32_router_gemm SYCL kernel for BMG#421
yangulei wants to merge 3 commits into
vllm-project:mainfrom
yangulei:pr/router-gemm-opt

Conversation

@yangulei

@yangulei yangulei commented Jun 16, 2026

Copy link
Copy Markdown

Summary

Adds the fp32_router_gemm SYCL kernel for the MiniMax-M3 MoE router on Intel BMG (Arc Pro B60), and then optimizes it. Two commits, reviewable in isolation:

  1. the SYCL port / kernel add, and
  2. a profile-guided (unitrace) optimization (~20.3× faster: 491,695 → 24,208 ns/call, pinned @ 2.4 GHz, min-of-7 device timing).

Commit 1 — Add fp32_router_gemm SYCL kernel

SYCL port of csrc/libtorch_stable/fp32_router_gemm.cu from vllm-project/vllm#45381 (m3_release). Computes out[m,n] = sum_k mat_a[m,k] * mat_b[n,k] in fp32, where mat_a (activation) is bf16 or fp32 and mat_b (weight) is fp32. Targets the small MoE-router shape (num_tokens ≤ 32, e.g. 256 experts × 3072 hidden): one sub-group per output column, each lane strides over K accumulating one partial sum per token, then a sub-group reduction writes the column.

Registered as torch.ops._xpu_C.fp32_router_gemm.

Commit 2 — Optimize for BMG (~20×)

The original launched only num_experts hardware threads (one SIMD16 sub-group per expert) → 20% occupancy, 93% XVE stall, memory idle. The optimization:

  • K-slicing: split the K reduction across ROUTER_GEMM_NSG sub-groups per expert with an SLM partial-sum reduction → occupancy 20% → 79%.
  • Vectorized loads: aligned_vec<T, VEC> K loads (guarded by hidden % VEC == 0, scalar tail otherwise).
  • Dual-accumulator ILP: 2-way unrolled inner loop with independent partial sums, to break the acc[m] dependency chain (ALUWR stall) and overlap two loads (SBID latency).
  • Tuned NSG=4, VEC=16.

Testing

  • tests/test_fp32_router_gemm.py — 9 passed (bf16 + fp32 activation, vs fp32 torch.matmul). Correctness rel err 1.2e-7.
  • The kernel translation unit compiles cleanly against main headers (csrc/utils.h); the op registration is a self-contained addition to csrc/xpu/ops.h / csrc/xpu/torch_bindings.cpp / tests/register_ops.py and one CMakeLists.txt source line. Builds standalone on main.

Not a duplicate

Checked gh pr list --repo vllm-project/vllm-xpu-kernels --state open — no open PR touches this kernel.

Notes

  • AI assistance (GitHub Copilot CLI) was used. Requires human review of every line before un-drafting.

yangulei and others added 2 commits June 16, 2026 08:57
SYCL port of csrc/libtorch_stable/fp32_router_gemm.cu from vllm-project/vllm#45381
(m3_release). Computes out[m,n] = sum_k mat_a[m,k] * mat_b[n,k] in fp32, where
mat_a (activation) is bf16 or fp32 and mat_b (weight) is fp32. Targets the small
MoE-router shape (num_tokens <= 32, e.g. 256 experts x 3072 hidden): one
sub-group per output column, each lane strides over K accumulating one partial
sum per token, then a sub-group reduction writes the column.

Registered as torch.ops._xpu_C.fp32_router_gemm. Validated on XPU in
tests/test_fp32_router_gemm.py against a fp32 torch.matmul reference (bf16 and
fp32 activation).

Co-authored-by: GitHub Copilot
Signed-off-by: Youlei Yang <youlei.yang@intel.com>
Profile-guided (unitrace) optimization on Intel BMG (Arc Pro B60), pinned
@ 2.4 GHz, min-of-7 device timing.

The original launched only num_experts hardware threads (one SIMD16 sub-group
per expert) -> 20% occupancy, 93% XVE stall, memory idle. Changes:
- Split the K reduction across ROUTER_GEMM_NSG sub-groups per expert with an
  SLM partial-sum reduction (K-slicing) -> occupancy 20% -> 79%.
- Vectorized K loads via aligned_vec<T, VEC> (guarded by hidden % VEC == 0).
- 2-way dual-accumulator inner loop to break the acc[m] dependency chain
  (ALUWR stall) and overlap two loads (SBID latency).
- Tuned NSG=4, VEC=16.

Result: 491,695 -> 24,208 ns/call (~20.3x). Correctness rel err 1.2e-7.
Tested: tests/test_fp32_router_gemm.py (9 passed).

Note: builds on the in-flight MiniMax-M3 XPU enablement (csrc/xpu/utils.h and
the _xpu_C kernel sources are not yet on main); this draft PR is for review of
the kernel optimization. AI assistance (GitHub Copilot CLI) was used.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Youlei Yang <youlei.yang@intel.com>
@jikunshang

Copy link
Copy Markdown
Member

can you add some benchmark to compare torch.matmul and this new kernel?

@yangulei yangulei changed the title [XPU][MiniMax-M3] Optimize fp32_router_gemm for BMG (~20x) [XPU][MiniMax-M3] Add and optimize fp32_router_gemm SYCL kernel for BMG Jun 17, 2026
@yangulei

Copy link
Copy Markdown
Author

Thanks @jikunshang. Added benchmark/benchmark_fp32_router_gemm.py (commit b58de2e) comparing this kernel against torch.matmul(mat_a.float(), mat_b.t()) (the fp32 reference it replaces). Numbers on Arc Pro B60 (BMG), freq pinned @ 2.4 GHz, MiniMax-M3 router shape N=256, K=3072.

Wall-clock per call (triton.testing.do_bench, includes dispatch + the .float() cast for torch)

act M torch.matmul (us) this kernel (us) kernel/torch
bf16 1 26.8 33.6 0.80x
bf16 8 31.9 41.3 0.77x
bf16 16 31.4 50.3 0.62x
bf16 32 31.4 69.4 0.45x
fp32 1 24.1 38.1 0.63x
fp32 16 29.1 58.5 0.50x
fp32 32 30.5 85.8 0.36x

(max_abs vs the fp32 ref stays ~1e-4 for bf16, exact for fp32.)

Device-time per call (unitrace -d, kernel-only, the fairer apples-to-apples), M=32

path bf16 fp32
torch.matmul gemm 12.97us + bf16->fp32 cast 3.11us = 16.1us gemm 12.8us
this kernel 24.2us 45.6us

Honest takeaway

torch.matmul (oneDNN GEMM) is faster than this kernel on XPU for the router shapes — ~1.5x for bf16, ~3.6x for fp32 in device time — even though this PR already makes the custom kernel ~20x faster than the original port (491us -> 24us). At M=32 the kernel hits ~17% FP32 MFU vs oneDNN's ~31%.

So this optimization is a big win relative to the existing custom kernel, but it does not beat torch.matmul here. Options I'd like your guidance on:

  1. Keep the kernel (parity with the upstream CUDA path vllm-project/vllm#45381) with this optimization, or
  2. Switch the XPU router to torch.matmul and drop the custom kernel.

Happy to do either. The original CUDA kernel presumably beat cuBLAS for this shape on NVIDIA; on XPU oneDNN's small-GEMM path is clearly stronger.

Adds benchmark/benchmark_fp32_router_gemm.py comparing the SYCL kernel against
the torch.matmul(mat_a.float(), mat_b.t()) fp32 reference it replaces, across
the MiniMax-M3 router shapes (M in {1,4,8,16,32}, N=256, K=3072; bf16 and fp32
activation). Reports per-call latency, speedup, and max abs error.

Addresses review feedback on vllm-project#421.

AI assistance (GitHub Copilot CLI) was used.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Youlei Yang <youlei.yang@intel.com>
yangulei added a commit to yangulei/vllm-fork that referenced this pull request Jun 17, 2026
The fp32 router fast-path (GateLinear tier 2) was CUDA-only and backed by
the custom fp32_router_gemm kernel. On Intel GPU, oneDNN's torch.matmul
outperforms a custom kernel for the MiniMax-M3 router shapes (N=256/128,
K=3072/6144, M<=32) -- ~1.5x for bf16 and ~3.6x for fp32 in device time --
so enable the fast-path on XPU and dispatch it to torch.matmul instead of
adding a new custom kernel.

See vllm-project/vllm-xpu-kernels#421 for the benchmark and discussion.

AI assistance (GitHub Copilot CLI) was used for this change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Youlei Yang <youlei.yang@intel.com>
@yangulei

Copy link
Copy Markdown
Author

Close as the the GateLinear already dispatched to oneDNN fp32 GEMM by default which is optimal.

@yangulei yangulei closed this Jun 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants