Skip to content

Forward tpu-inference MoE kernel knobs and surface MoE padding - #5087

Merged
copybara-service[bot] merged 1 commit into
mainfrom
mohit/vllm-moe-forward-kernel-knobs
Sep 2, 2026
Merged

Forward tpu-inference MoE kernel knobs and surface MoE padding#5087
copybara-service[bot] merged 1 commit into
mainfrom
mohit/vllm-moe-forward-kernel-knobs

Conversation

@khatwanimohit

@khatwanimohit khatwanimohit commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Description

Two fixes to the vLLM serving path for MoE models.

  1. RoutedMoE.fused_moe_matmul calls tpu-inference's fused_moe_func without the
    environment-backed knobs that tpu-inference passes on its own serving path, so
    ENABLE_RS_KERNEL, USE_GMM_FUSED_RS_KERNEL, ONEHOT_MOE_PERMUTE_THRESHOLD and
    VLLM_MOE_CHUNK_SIZE are silently ignored when serving a MaxText model. Forward them.

    ONEHOT_MOE_PERMUTE_THRESHOLD matters beyond tuning: it picks the one-hot permute path
    over the SparseCore ragged_gather_reduce kernel, which doesn't fit v5p. Before this,
    any expert_parallelism > 1 died on the first request with
    AssertionError: num_row_partitions=16 must be <= num_simd_lanes=8.

  2. The adapter pads moe_intermediate_size to satisfy GMM_v2's tiling constraint, which
    inflates every expert weight (moe_mlp_tp_size always equals --tensor-parallel-size,
    so TP=4 pads 512->1024 and TP=8 pads 512->2048). It was logged at absl INFO, which vLLM's
    logging doesn't surface, so serving Qwen3.5-35B-A3B quietly used 2x the MoE weights with
    nothing in the logs. Log it at WARNING with the cost.

No behaviour change with no env vars set, and the padding logic itself is untouched.

Tests

Qwen3.5-35B-A3B on v5p-8, bf16 (v5p has no fp8 MXU). Server:

export HF_HOME=... USE_MOE_EP_KERNEL=0 ATTN_BUCKETIZED_NUM_REQS=true \
  ATTN_CUSTOM_NUM_REQS_BUCKETS=4 ONEHOT_MOE_PERMUTE_THRESHOLD=32768 \
  VLLM_MOE_CHUNK_SIZE=256 SLICE_ROPE_CACHE=1 DP_SCHED_BATCH_PREFILL=false \
  NEW_MODEL_DESIGN=1 NUM_PRECOMPILE_WORKERS=8 SKIP_JAX_PRECOMPILE=1 \
  VLLM_ENABLE_V1_MULTIPROCESSING=0

vllm serve Qwen/Qwen3.5-35B-A3B \
  --max-model-len=65536 --max-num-batched-tokens=2048 --max-num-seqs=256 \
  --no-enable-prefix-caching --gpu-memory-utilization=0.9 --tensor-parallel-size=4 \
  --async-scheduling --port=8000 --language-model-only --enable-auto-tool-choice \
  --tool-call-parser=qwen3_coder --reasoning-parser=qwen3 \
  --default-chat-template-kwargs '{"enable_thinking": false}' \
  '--limit-mm-per-prompt={"image": 0, "video": 0}' \
  --hf-overrides '{"architectures": ["MaxTextForCausalLM"]}' \
  "--additional_config={\"sharding\": {\"sharding_strategy\": $SHARDING}, \"maxtext_config\": {\"model_name\": \"qwen3.5-35b-a3b\", \"load_parameters_path\": \"gs://maxtext-model-checkpoints/qwen3.5-35b-a3b/unscanned/0/items\", \"scan_layers\": false, \"weight_dtype\": \"bfloat16\", \"attention\": \"vllm_rpa\", \"enable_nnx\": true, \"pure_nnx_decoder\": true, \"allow_split_physical_axes\": true, \"use_multimodal\": false, \"prefuse_moe_weights\": true}}" \
  --block-size=256 --enable-chunked-prefill

with

# before
SHARDING='{"enable_dp_attention": true, "attn_dp_size": 2}'
# after  (crashed on first request before this PR)
SHARDING='{"enable_dp_attention": false, "tensor_parallelism": 1, "expert_parallelism": 4}'

Client, same for every run:

python tpu-inference/scripts/vllm/benchmarking/agentic_benchmark/benchmark_agentic.py \
  --model Qwen/Qwen3.5-35B-A3B --model-path-or-id Qwen/Qwen3.5-35B-A3B \
  --trace-file gs://wenxindong-vm/rl/mlperf2026/agentic_benchmark/gbs1024_trace_file.jsonl \
  --global-prefix-len 6476 --num-groups 1 --concurrency 1 --group-size 8

240/240 turns in every run, identical output tokens (37,194), so wall clock is comparable.
Native vLLM (MODEL_IMPL_TYPE=vllm, same flags) included for reference.

before after native vLLM
weights/device 33.92 GiB 18.31 GiB 16.95 GiB
wall clock 1450.86 s 1056.98 s 877.64 s
tok/s/chip 838.80 1151.31 1322.14
output tok/s 25.64 35.19 42.38
TPOT avg 282.74 ms 160.73 ms 184.82 ms
TPOT p99 1125.54 ms 603.23 ms 893.00 ms
turn-1 prefill 112,390 ms 144,246 ms 99,449 ms
turns 2+ TTFT 4,923 ms 3,855 ms 4,699 ms

+37% tok/s/chip, and the gap to native goes from 1.58x to 1.15x. Decode is now ahead of
native; what's left is turn-1 prefill, since tensor_parallelism=1 leaves the prefill GEMMs
unsharded. TP=2 / EP=2 is also padding-free (18.12 GiB/device) and is the obvious follow-up
— it was equally unrunnable before this PR.

The padding warning on the before config, where previously nothing was logged:

WARNING:absl:Padding moe_intermediate_size from 512 to 1024 to match MLP MoE requirements
(moe_mlp_tp_size=4, 2*num_lanes=256). This multiplies the MoE weights and MoE FLOPs by 2x.

Note --enable-expert-parallel on the CLI doesn't affect the JAX mesh — tpu-inference reads
expert parallelism only from additional_config, which is why it's set there above.

Checklist

Before submitting this PR, please make sure (put X in square brackets):

  • I have performed a self-review of my code. For an optional AI review, add the gemini-review label.
  • I have necessary comments in my code, particularly in hard-to-understand areas.
  • I have run end-to-end tests tests and provided workload links above if applicable.
  • I have made or will make corresponding changes to the doc if needed, including adding new documentation pages to the relevant Table of Contents (toctree directive) as explained in our documentation.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request updates the vLLM adapter to log a warning when padding moe_intermediate_size due to its memory and FLOP costs. It also forwards environment-backed kernel configuration variables from tpu_inference to fused_moe_func in moe.py to ensure consistent kernel behavior when serving MaxText models through vLLM. The review feedback suggests using getattr with fallback defaults when accessing these environment variables to prevent potential AttributeError crashes with older versions of the tpu_inference package.

Comment thread src/maxtext/layers/moe.py
@codecov

codecov Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 20.00000% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...t/integration/vllm/maxtext_vllm_adapter/adapter.py 0.00% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

@khatwanimohit
khatwanimohit force-pushed the mohit/vllm-moe-forward-kernel-knobs branch from 7d00759 to 82364f1 Compare September 1, 2026 18:23
@khatwanimohit
khatwanimohit force-pushed the mohit/vllm-moe-forward-kernel-knobs branch from 82364f1 to 6c57031 Compare September 1, 2026 20:30
When a MaxText model is served through vLLM, RoutedMoE.fused_moe_matmul calls
tpu-inference's fused_moe_func with only the required arguments. tpu-inference's
own serving path passes several environment-backed knobs as well, so the two
paths run the same kernel with different configurations, and env vars that
operators set on the vLLM command line are silently ignored on the MaxText path.

Forward the four environment-backed knobs: ENABLE_RS_KERNEL,
USE_GMM_FUSED_RS_KERNEL, ONEHOT_MOE_PERMUTE_THRESHOLD and VLLM_MOE_CHUNK_SIZE.
ONEHOT_MOE_PERMUTE_THRESHOLD matters beyond tuning: it selects the one-hot
permute path over the SparseCore ragged_gather_reduce kernel, which is what lets
expert parallelism run at all on TPU generations whose SparseCore has fewer SIMD
lanes than that kernel needs. On v5p, expert_parallelism > 1 previously failed
with "num_row_partitions=16 must be <= num_simd_lanes=8".

Also make two silent sharding surprises visible in the vLLM adapter.

Raise the MoE padding message from absl INFO to WARNING and include its cost.
vLLM's logging configuration does not surface absl INFO records, so the padding
was invisible: serving Qwen3.5-35B-A3B at moe_mlp_tp_size=4 pads
moe_intermediate_size 512 -> 1024 and doubles the MoE weights (measured
33.92 GiB/device instead of 16.95 GiB/device), and at moe_mlp_tp_size=8 it
quadruples them, with no indication in the logs.

Warn when --enable-expert-parallel is passed but the mesh has no expert shards.
The native tpu-inference model paths derive use_ep from
parallel_config.enable_expert_parallel, while the mesh takes expert parallelism
only from additional_config's sharding_strategy and MaxText derives use_ep from
the mesh. The same command line therefore runs expert-parallel MoE natively and
tensor-parallel MoE under MaxText, which is the configuration that triggers the
padding above. The warning points at expert_parallelism as the fix.

Bump the post-training pins to pick these up. USE_GMM_FUSED_RS_KERNEL does not
exist in the previously pinned tpu-inference (7ecc401e, Jul 27), and neither
does the use_gmm_fused_rs_kernel argument of fused_moe_func, so the change above
needs a newer commit to import at all:

  tpu-inference  7ecc401e -> b67ae5f8 (main)
  vllm           0ba2aa35 -> d626108b (tpu-inference .buildkite/vllm_lkg.version)

Both the old and new tpu-inference pin jax==0.11.0, jaxlib==0.11.0 and
libtpu==0.0.44, which already match tpu_post_train_overrides.txt, so the jax
version in CI is unchanged.

The post-training lock is regenerated with seed-env using the same JAX seed
commit recorded for it in docs (52d5cb38). Regeneration needs one fix to run:
base requirements ask for an unpinned llguidance, which now resolves to 1.8.0,
while vLLM requires >=1.7.0,<1.8.0 in both the old and new commits. Bound it to
<1.8.0. The functionally required outcome of the regeneration is
tokamax>=0.0.13 (up from 0.0.12, where the GMM_v2 kernel lives) and
huggingface-hub>=1.29.0; the lock is installed with --resolution=lowest and the
GitHub deps with --no-deps, so these floors are exactly what CI installs.
@khatwanimohit
khatwanimohit force-pushed the mohit/vllm-moe-forward-kernel-knobs branch from 6c57031 to 76cd098 Compare September 1, 2026 20:47
@copybara-service
copybara-service Bot merged commit da46b9e into main Sep 2, 2026
74 of 76 checks passed
@copybara-service
copybara-service Bot deleted the mohit/vllm-moe-forward-kernel-knobs branch September 2, 2026 18:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants