Enable FSDP2 + expert parallelism via a 2-D (fsdp, tp) device mesh - #48516
Open
qgallouedec wants to merge 8 commits into
Open
Enable FSDP2 + expert parallelism via a 2-D (fsdp, tp) device mesh#48516qgallouedec wants to merge 8 commits into
qgallouedec wants to merge 8 commits into
Conversation
DistributedConfig(tp_size=E, fsdp_size=D, enable_expert_parallel=True) builds a 2-D mesh: experts are sharded across tp, everything else is fully sharded across fsdp. The Trainer mirrors both dimensions into accelerate's ParallelismConfig, averages the expert gradients over fsdp (FSDP2 only reduces what it shards), computes the gradient norm across parameters on different meshes, and gathers the DTensor state dict on save.
…d-mesh norm, single collective save FSDP2 composes over the tp-sharded experts and shards them across fsdp as well, so nothing is replicated over fsdp and the Trainer-side gradient averaging never ran; remove it and describe the actual layout. The gradient norm is now one get_total_norm per mesh, each reduced over its own mesh. save_model runs save_pretrained on every rank so its gather is collective and only rank 0 writes; the FSDP branch of the gather (full state dict on rank 0 only) now also covers the 2-D mesh. ParallelismConfig keeps a user-supplied config and only claims what the model was loaded with; pipeline parallelism is rejected together with tp/fsdp; optimizer checkpoints are refused for models sharded at load time since they cannot be resumed.
…ng per parameter Fused/foreach AdamW cannot span parameters on different meshes, but it can run per mesh: 189 ms/step per-parameter vs 41 ms fused per mesh group on Qwen3-30B-A3B at tp=4 x fsdp=2.
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
Member
Author
|
Merge-order note! Found by merging both into #48204: the non-EP forward crashes otherwise. Whichever lands second should add the gate. |
Contributor
CI recapDashboard: View test results in Grafana |
This was referenced Sep 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #48205 (which carries #48208); the diff to review is this branch against
fix-ep-training-gradients.Expert parallelism shards only the experts. Everything else and its optimizer state is replicated on every EP rank, so the model size you can train is bounded by what one rank can hold of the dense part plus its Adam state.
DistributedConfig(tp_size=E, fsdp_size=D, enable_expert_parallel=True)now builds a 2-D(fsdp, tp)mesh and theTrainertrains it end to end. Until nowtp_size > 1withfsdp_size > 1raisedFSDP+TP is not supported yet.Correctness
Trainer-level parity (fp32, 8 GPUs, 6 steps,
max_grad_norm=1.0active, same total batch of 8): per-step loss and gradient norm of every arm against a single-process run of the same tiny MoE (16 experts).tp_size=8tp_size=4, fsdp_size=2tp_size=2, fsdp_size=4This exercises the batch split across
fsdp, FSDP2's reduction of the expert gradients, the mesh-aware norm and clipping, the per-parameter optimizer, the loss normalization across ranks, andsave_model. Driver and comparison scripts in the details ⬇️.fp32 gradient certification against a single-GPU reference (real Qwen3-30B-A3B weights, first N layers): every parameter's gradient, relative max-abs difference.
tp_size=4)tp_size=2, fsdp_size=2)Loss curves (OLMoE-1B-7B, bf16 full fine-tuning, tulu-3 data, same total batch of 8 x 1024 tokens):
bf16 runs with different reduction orders drift by a few percent per step (peak memory: 64.5 GB single GPU, 12.5 GB EP, 9.5 GB and 8.7 GB for the two 2-D layouts); the fp32 tables above are the exact check.
Throughput and memory
Qwen3-30B-A3B full fine-tuning, bf16, 8xH100, sequence length 2048, per-device batch 1, sdpa, AdamW:
tp_size=8tp_size=4, fsdp_size=2tp_size=2, fsdp_size=4The 2-D configurations pay FSDP2's all-gather/reduce-scatter of the experts across
fsdp. Splitting the optimizer param groups by mesh (instead of stepping per parameter as #48208 did) is worth 189 -> 41 ms per step attp_size=4, fsdp_size=2on this model, which is where most of the +15-25% over the umbrella branch's numbers comes from.At scale (installed stack: this branch + accelerate/peft/trl from main, 64 H100,
trl.SFTTrainer, huggingface/trl#6869's script unchanged): GLM-4.5-Air 110B full fine-tuning withtp_size=32, fsdp_size=2, 20 steps at ~3 s/step steady state, loss 3.9 -> 1.2, thensave_modelwrites the 200 GB checkpoint (5 safetensors shards) through the collective gather.Known limitations
tp_sizedoubles as the EP size.Trainerrefuses to write optimizer checkpoints for them.(dp_shard, tp)mesh from theParallelismConfignext to the model's; it only reads sizes and ranks from it, but that is a second set of communicators per rank.Trainer-level parity driver (single process vs EP vs 2-D through the Trainer, same total batch)
parity_train.pycompare_parity.pycompare_saves.py