Skip to content

[shardformer]: support Qwen3-MoE with expert parallelism - #6450

Open
LiRunGuo wants to merge 2 commits into
hpcaitech:mainfrom
LiRunGuo:feat/qwen3-moe
Open

LiRunGuo wants to merge 2 commits into
hpcaitech:mainfrom
LiRunGuo:feat/qwen3-moe

Conversation

@LiRunGuo

Copy link
Copy Markdown

📌 Checklist before creating the PR

  • I have created an issue for this PR for traceability
  • The title follows the standard format: [doc/gemini/tensor/...]: A concise description
  • I have added relevant tags if possible for us to better distinguish different PRs
  • I have installed pre-commit: pip install pre-commit && pre-commit install (ran the pinned hooks manually instead: black 24.10.0 --line-length=120, isort 5.13.2 --profile black, autoflake 2.3.1, all clean)

🚨 Issue number

Closes the Qwen part of #6180 ("Expert Parallel for qwen/deepseek"; DeepSeek EP already exists).

Depends on #6449: this branch contains that fix as its first commit, because the new test saves a Qwen3-MoE checkpoint with a padded vocab under TP. I will rebase once it is merged; please review only the second commit here.

📝 What does this PR do?

Adds shardformer support for Qwen3-MoE (Qwen3MoeModel, Qwen3MoeForCausalLM, transformers >= 4.51, e.g. Qwen3-30B-A3B / Qwen3-235B-A22B) with MoeHybridParallelPlugin.

feature status
expert parallelism ✅ EPQwen3MoeSparseMoeBlock: all-to-all token dispatch, experts sharded over the EP group, optional TP inside each expert, Qwen3's norm_topk_prob
dense layers (mlp_only_layers, decoder_sparse_step) ✅ kept as they are (TP-sharded if TP is on)
tensor parallelism ✅ attention, router, dense MLP, vocab parallel embedding, lm_head
pipeline parallelism ✅ 1F1B and interleaved; router logits are carried across stages for the load balancing loss, dense layers' None router logits are skipped
sequence parallelism ✅ all_to_all, reusing the Qwen3 attention forward (Qwen3MoeAttention is identical to Qwen3Attention)
ZeRO 1/2 ✅ through the plugin
other SP modes, SP + PP, zero bubble schedule ❌ raise NotImplementedError

The EP block is adapted from EPMixtralSparseMoeBlock; the policy mirrors the Mixtral / Qwen3 policies.

Tests

  • tests/test_shardformer/test_model/test_shard_qwen3_moe.py (4 GPUs, plus an 8-GPU largedist variant like Mixtral's):
    • training against a single-GPU reference for 11 (ZeRO, EP, PP, TP, SP) combinations, on a model with GQA and both dense and sparse layers; the sharded checkpoint is reloaded with Qwen3MoeModel.from_pretrained and compared.
    • Qwen3MoeForCausalLM loss including the router aux loss for EP=4, PP=4, TP=4 and EP=2×PP=2 (the reference is computed on the same micro batches, since the aux loss is not linear in the batch).
  • tests/test_moe/test_qwen3_moe_layer.py (4 GPUs): fp32 comparison of the EP block with the transformers block — output, router logits, input grad and every parameter grad — for 1 and 2 experts per rank and both norm_topk_prob values.

A note on test sensitivity

The model-level test (same design as test_shard_mixtral.py) cannot detect wrong expert math: at the default initializer_range the expert outputs are ~1e-4 against a residual stream of ~1, so e.g. flipping norm_topk_prob in the EP block still passes it (max output error 0.0156 with or without the mutation, i.e. bf16 noise). The layer test uses a larger init and fails on that mutation on every rank, so I added it and did not skip it. test_mixtral_layer.py is currently skipped as "tested in corresponding shardformer" — the same blind spot applies to Mixtral.

Results (4x H200, torch 2.5.1+cu124, transformers 4.51.3)

test result
test_shard_qwen3_moe.py::test_qwen3_moe (11 model configs + 4 causal LM configs) passes
test_qwen3_moe_layer.py (ep 2/4 × norm_topk_prob on/off) passes
layer test with norm_topk_prob flipped in the EP block fails on all ranks (as intended)

Sharding check: with ep=4 each rank holds 1 of the 4 experts in each of the 3 sparse layers.

Also found (upstream transformers, not changed here)

In transformers 4.51.3, Qwen3MoeModel.forward collects the router logits of dense layers (None) and load_balancing_loss_func then fails when output_router_logits=True and mlp_only_layers is non-empty. The pipeline / SP forward in this PR skips them; the causal LM test uses only sparse layers so that the transformers reference can compute the aux loss.

💥 Checklist before requesting a review

  • I have linked my PR to an issue (instruction)
  • My issue clearly describes the problem/feature/proposal, with diagrams/charts/table/code if possible
  • I have performed a self-review of my code
  • I have added thorough tests.
  • I have added docstrings for all the functions/methods I implemented

⭐️ Do you enjoy contributing to Colossal-AI?

  • 🌝 Yes, I do.
  • 🌚 No, I don't.

`MoECheckpointIO._model_sharder` is a copy of
`HybridParallelCheckpointIO._model_sharder` without the
`to_unpadded_tensor` step, so `booster.save_model(..., shard=True)` with
`MoeHybridParallelPlugin` writes padded parameters. With tensor parallelism,
`VocabParallelEmbedding1D` pads the vocab to a multiple of
`make_vocab_size_divisible_by * tp_size`, so the saved embedding has the
padded vocab size and cannot be loaded by transformers:

    size mismatch for weight: copying a param with shape torch.Size([1024, 8])
    from checkpoint, the shape in current model is torch.Size([1000, 8]).

This affects any MoE model whose vocab is not a multiple of 64 * tp_size,
e.g. Qwen3 (151936). The existing test uses Mixtral's default vocab (32000),
which is never padded, and tp_size=1.

Unpad the parameters like HybridParallelCheckpointIO does, and run the MoE
checkpoint test with a padded vocab and tp_size=2 as well.
Closes the Qwen part of hpcaitech#6180.

Add a shardformer policy for `Qwen3MoeModel` / `Qwen3MoeForCausalLM`
(transformers >= 4.51) that works with `MoeHybridParallelPlugin`:

- expert parallelism: `EPQwen3MoeSparseMoeBlock`, adapted from the Mixtral EP
  block (all-to-all token dispatch, experts sharded across the EP group,
  optional TP inside each expert), with Qwen3's `norm_topk_prob`. Dense layers
  (`mlp_only_layers` / `decoder_sparse_step`) are kept as they are.
- tensor parallelism for attention, router and dense MLP layers, vocab
  parallel embedding.
- pipeline parallelism (1F1B and interleaved) with the router logits carried
  across stages for the load balancing loss; router logits of dense layers
  are skipped.
- sequence parallelism (all_to_all), reusing the Qwen3 attention forward,
  since Qwen3MoeAttention is identical to Qwen3Attention.
- ZeRO 1/2 through the plugin.

Not supported yet (raise NotImplementedError): other SP modes, SP together
with PP, and the zero bubble schedule.

Tests:
- tests/test_shardformer/test_model/test_shard_qwen3_moe.py: training
  (loss and weights) against a single-GPU reference for 11 combinations of
  EP / TP / PP / SP / ZeRO with a model that has dense and sparse layers and
  GQA, sharded checkpoint reloaded by `from_pretrained`, and the causal LM
  loss including the router aux loss for EP / PP / TP.
- tests/test_moe/test_qwen3_moe_layer.py: fp32 layer-level check of the EP
  block (output, router logits and every gradient) against the transformers
  block, for 1 and 2 experts per rank and both `norm_topk_prob` values.
  The model-level test cannot catch wrong expert math, because the expert
  outputs are tiny compared to the residual stream at the default init; the
  layer test uses a larger init and catches e.g. a flipped `norm_topk_prob`.
@LiRunGuo
LiRunGuo requested a review from a team as a code owner September 22, 2026 15:26
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.

1 participant