Skip to content

[XPU][MoE] Tune Triton fused MoE for Intel XPU - #53065

Draft
pmanczak wants to merge 1 commit into
vllm-project:mainfrom
pmanczak:pmanczak/moe-autotune-xpu
Draft

[XPU][MoE] Tune Triton fused MoE for Intel XPU#53065
pmanczak wants to merge 1 commit into
vllm-project:mainfrom
pmanczak:pmanczak/moe-autotune-xpu

Conversation

@pmanczak

@pmanczak pmanczak commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Purpose

The Triton fused MoE kernel runs on Intel XPU but has no tuned configs there, so
it always falls back to get_default_config() and logs "Using default MoE
config. Performance might be sub-optimal!". This PR makes
benchmarks/kernels/benchmark_moe.py usable on XPU and ships tuned configs for
Intel Arc Pro B70.

Changes

  • Route CUDA graph capture, default device and cache clearing through
    torch.accelerator / current_platform instead of hardcoded torch.cuda.
  • Add --no-ray. Ray keys its parallelism on the "GPU" resource, which XPU
    does not register, so ray.remote is now applied dynamically rather than at
    class definition and the same worker class serves both paths.
  • Add an XPU search space (num_warps up to 32, num_stages from 3, smaller
    BLOCK_SIZE_K, grf_mode) and prune_xpu_search_space(), which drops tiles
    wider than the routed row count since those are pure padding and the slowest
    to compile. Adapted from intel-xpu-backend-for-triton.
  • Keep grf_mode in sort_config(). No plumbing is needed for it, as **config
    already forwards extra keys into the launch, the same way ROCm passes
    waves_per_eu.
  • Ship tuned configs for E=64, N=1024 and E=64, N=512.

Results

Kernel time from benchmark_moe.py, Intel Arc Pro B70, bf16,
allenai/OLMoE-1B-7B-0924. Baseline is the built-in heuristic config, tuned is
the shipped JSON.

E=64, N=1024 (tp=1):

batch baseline (us) tuned (us) speedup
1 331.56 234.54 1.41x
2 650.32 386.58 1.68x
4 1150.53 674.60 1.71x
8 1740.73 992.18 1.75x
16 2333.32 1302.85 1.79x
24 2540.36 1411.62 1.80x
32 2628.54 1449.64 1.81x
48 1734.96 1499.30 1.16x
64 1750.65 1506.59 1.16x
96 1842.48 1550.00 1.19x
128 4789.70 1585.79 3.02x
256 2229.38 1737.90 1.28x
512 3140.05 2183.39 1.44x
1024 11349.81 3594.97 3.16x
1536 14507.93 4985.24 2.91x
2048 18100.66 6417.00 2.82x
3072 25021.64 9352.32 2.68x
4096 32101.63 12095.05 2.65x

E=64, N=512 (tp=2):

batch baseline (us) tuned (us) speedup
1 157.82 130.36 1.21x
16 1160.66 688.16 1.69x
128 2353.28 845.63 2.78x
512 1635.36 1180.34 1.39x
2048 8910.22 3443.63 2.59x
4096 15361.95 6500.82 2.36x

Geometric mean 1.86x and 1.91x respectively.

Two things drive the larger wins. The heuristic escalates BLOCK_SIZE_M with
batch size, which is wrong for a 64-expert layer at topk=8 where the rows an
expert receives stay small; that makes its cost curve non-monotonic (batch 128
costs more than batch 256, for half the tokens) while the tuned curve is
monotonic. And half the winners sit outside the default space: num_warps=16
wins at 9 of 18 batch sizes, where the default space only explores 4 and 8.

Every shipped config was checked against an fp32 reference; relative L2 error is
4.1e-3, which is the bf16 output quantization step rather than kernel error.

End to end, allenai/OLMoE-1B-7B-0924 on one B70 with --moe-backend triton,
the shipped configs supplied through VLLM_TUNED_CONFIG_FOLDER and the baseline
being the same run without them:

workload baseline tuned speedup
generate, 32 prompts x 64 tokens 2.107 s 1.280 s 1.65x
lm_eval arc_challenge, 4687 requests 67 s 57 s 1.18x

Accuracy is unchanged: arc_challenge scores acc 0.4727 and acc_norm 0.4829 on
both. Greedy continuations are not bit-identical, since a different reduction
order moves logits by less than a bf16 step and flips near-ties, but the
identical loglikelihood scores show behaviour is unaffected.

The space leaves out BLOCK_SIZE_N=32 and num_warps=32. Neither won at any of
the 31 tuned batch sizes across E=64, N=1024, E=64, N=512 and E=40, N=512
(Granite 3.1 3b-a800m), and on a Mixtral-shaped kernel (E=8, N=14336, K=4096, topk=2) they stay far off the best even though that is the corner where wide
tiles get the most rows per expert:

value M=128 M=1024 M=4096
BLOCK_SIZE_N=32 +46% +85% +90%
num_warps=32 +44% +54% +17%
BLOCK_SIZE_M=256 (kept) +112% +17% +7%

BLOCK_SIZE_M=256 is kept because its margin keeps shrinking with batch size on
that shape, and prune_xpu_search_space() already drops it whenever the routed
row count is small. The space is 360 candidates rather than 720, which roughly
halves tuning time.

Test plan

  • benchmark_moe.py --model allenai/OLMoE-1B-7B-0924 --tp-size 1 --no-ray --tune
    completes on XPU and writes a config JSON
  • serving that model on B70 with --moe-backend triton picks the shipped config
    up (the "default MoE config" warning is gone) and generates 1.65x faster at
    identical arc_challenge accuracy
  • CUDA and ROCm paths are unchanged: the Ray path still wraps the worker with
    ray.remote(num_gpus=1), and pruning is only reached under
    current_platform.is_xpu()

Make benchmarks/kernels/benchmark_moe.py runnable on XPU, add an XPU
search space plus tile pruning, and ship tuned fused MoE configs for
Intel Arc Pro B70.

The XPU path had no tuned configs, so it always fell back to
get_default_config() and logged the sub-optimal-config warning. Tuning
recovers 1.16-3.16x of fused MoE kernel time (geometric mean 1.86x on
E=64/N=1024 and 1.91x on E=64/N=512, OLMoE-1B-7B, bf16). The heuristic
escalates BLOCK_SIZE_M with batch size, which is wrong for a 64-expert
layer at topk=8 where the rows an expert receives stay small, and that
makes its cost curve non-monotonic; the tuned curve is not.

Three supporting changes. The benchmark hardcoded CUDA for graph
capture, default device and cache clearing, now routed through
torch.accelerator and current_platform. Ray keys its parallelism on the
"GPU" resource, which XPU does not register, so ray.remote is applied
dynamically instead of at class definition and --no-ray runs shapes
serially in one process. sort_config() learns to keep grf_mode, without
which the tuner would select a GRF mode and then drop it when writing
the JSON.

The search space is adapted from intel-xpu-backend-for-triton, which
autotunes these kernels per launch rather than from a config file.
BLOCK_SIZE_N=32 and num_warps=32 are left out: across four shapes,
including a Mixtral-sized one, they never won and never came within 17%
of the best.

Signed-off-by: pmanczak <pawel.manczak@intel.com>
@mergify mergify Bot added performance Performance-related issues intel-gpu Related to Intel GPU labels Aug 20, 2026
@pmanczak
pmanczak force-pushed the pmanczak/moe-autotune-xpu branch 5 times, most recently from 181ba82 to a883afd Compare August 20, 2026 12:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

intel-gpu Related to Intel GPU performance Performance-related issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant