Fix expert parallelism through Trainer - #48208
Conversation
|
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. |
e79ef7a to
ab27829
Compare
f3e0be8 to
894690a
Compare
ab27829 to
1b718f6
Compare
894690a to
a2bfd50
Compare
ArthurZucker
left a comment
There was a problem hiding this comment.
LGTM for TP, for trainer, @SunMarc can you approve?
| def _has_mixed_mesh_grads(self, model) -> bool: | ||
| from torch.distributed.tensor import DTensor | ||
|
|
||
| grads = [p.grad for p in model.parameters() if p.grad is not None] |
There was a problem hiding this comment.
detecting ep plan or local flags form the tp plan might be better / faster / cashable to waste less?
There was a problem hiding this comment.
+1 on this. Or we could try to cache de result and store the result somewhere, either on the model or on the trainer.
There was a problem hiding this comment.
Cached it on the trainer in d0e4af5 (resolved at the first clip, sharding is static for the run).
Went with the cache rather than reading the tp plan so the Trainer stays out of plan semantics; the optimizer-side check runs once at construction so it needed nothing.
| return AdamW, ctx.optimizer_kwargs | ||
|
|
||
|
|
||
| def _has_mixed_dtensor_params(model) -> bool: |
There was a problem hiding this comment.
This is basically the same function as the mixed_mesh one no ?
| def _has_mixed_mesh_grads(self, model) -> bool: | ||
| from torch.distributed.tensor import DTensor | ||
|
|
||
| grads = [p.grad for p in model.parameters() if p.grad is not None] |
There was a problem hiding this comment.
+1 on this. Or we could try to cache de result and store the result somewhere, either on the model or on the trainer.
1b718f6 to
0f3bd58
Compare
a2bfd50 to
ba4a3c3
Compare
7398af0 to
1a958cd
Compare
ba4a3c3 to
3019094
Compare
|
@bot /style |
|
Style fix bot fixed some files and pushed the changes. |
1a958cd to
b52614e
Compare
352c82f to
967df85
Compare
Loading a model with DistributedConfig(tp_size=N, enable_expert_parallel=True) and handing it to Trainer fails on main in three places: 1. maybe_distribute_model never assigns model._tp_size, so the Trainer builds no ParallelismConfig and accelerate wraps the DTensor model in DDP: ValueError: Your model contains DTensor parameters, which is incompatible with DDP. 2. _get_grad_norm calls clip_grad_norm_ over the full parameter set, and _foreach_norm cannot span a mix of DTensor (experts) and plain parameters. 3. The fused/foreach AdamW kernels cannot span that mix either. Set _tp_size where the mesh is recorded, compute the gradient norm (and clip) per-gradient with replication-aware discounting when parameters live on different meshes, and fall back to per-parameter AdamW stepping for mixed parameter sets. With this and #48205, expert-parallel full fine-tuning through Trainer runs end-to-end and tracks a single-GPU control step by step (OLMoE-1B-7B, tp=4: 12.13 -> 12.45 -> 11.52 -> ... -> 11.04 vs 12.13 -> 12.45 -> 11.51 -> ... -> 11.09).
b52614e to
e0b2e71
Compare
967df85 to
f464638
Compare
CI recapDashboard: View test results in Grafana |
…ributions (huggingface#48205) * Fix NaN gradients in expert-parallel training: mask uninitialized grouped_mm rows Under EP, sentinel token-expert slots sit beyond offsets[-1] and torch._grouped_mm leaves those output rows (fwd output and bwd d_input) uninitialized. The forward relied on a single post-mask plus a single pre-mask, letting NaN/Inf from uninitialized memory transit the activation and down-projection backward. The gate product's backward (act_fn(gate) * up) turns 0 x Inf into NaN (torch.autograd anomaly mode names this exact Mul), and it escapes into finite gradients: full fine-tuning of any EP-sharded MoE produced nan grad_norm on the second step (the first step survives only because freshly-allocated CUDA memory happens to be zeroed) and the loss collapsed to 0. Mask the sentinel-tail rows after each grouped GEMM instead. Full fine-tuning of OLMoE-1B-7B under ep=4 now matches the single-GPU loss trajectory. * Fix wrong gradients for all non-expert parameters in expert-parallel training Under EP the router hook zeroes the routing scores of non-local experts, so in backward each rank's score gradient covers only the slots of its local experts, and nothing sums the per-rank partial gradients: the gate weights and, through the gate's input, every parameter upstream of each MoE block receive gradients missing the contributions that flow through remote experts. The existing _AllReduceBackward on the experts' hidden input covers the dispatch branch, and the top_k_weights branch is explicitly skipped when is_expert_parallel -- but under EP it is exactly as partial as under TP-MoE. Measured against a single-GPU reference (OLMoE-1B-7B, one batch, fp32 so rounding noise vanishes): before the fix, 3/179 parameters agree (relative max-abs errors 0.3-2.5 on attention, norms, embeddings and router gates, 10-100x above the run-to-run noise floor; only the last layer's experts and the final norm -- the parameters backward reaches before crossing an expert block -- are correct). After the fix: 179/179 agree, max relative error 2.7e-5. Fix: allreduce-sum the score gradient in the EP router hook, before the non-local mask (each slot has exactly one owning rank, so the sum is exact). * Trim comments * Gate the router-score backward all-reduce on grad mode; drop the post-mask superseded by the per-mm masks * Fix expert parallelism through Trainer (huggingface#48208)
Stacked on #48200, this PR makes expert parallelism reachable through
Trainer, and without the gradient fixes there, the training it unlocks is wrong.Loading a model with
DistributedConfig(tp_size=N, enable_expert_parallel=True)and handing it toTrainerfails on main before the first step completes, in three places:maybe_distribute_modelnever assignsmodel._tp_size(the attribute is declared but dead), so theTrainerbuilds noParallelismConfigand accelerate wraps the DTensor model in DDP:ValueError: Your model contains DTensor parameters, which is incompatible with DDP._foreach_normcannot span a parameter set that mixes DTensors (the experts) and plain tensors (everything else).The fix: assign
_tp_sizewhere the mesh is recorded; compute the gradient norm (and clip) per-gradient with replication-aware discounting when parameters live on different meshes; fall back to per-parameter AdamW stepping for mixed parameter sets.Validation
Together with #48205 (which fixes the gradients EP computes), expert-parallel full fine-tuning through
Trainerruns end-to-end and tracks a single-GPU control step by step (OLMoE-1B-7B,tp_size=4, 4×H100, identical data):Reproduction (each of the three failures appears on main as the previous one is fixed; on this branch it trains)