Repository navigation
🚨 Remove the ep_router expert-parallel style - #49615
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. Pages changed in this PR: |
7d13b07 to
1cbdb8c
Compare
ep_router expert-parallel styleep_router expert-parallel plan
vasqu
left a comment
There was a problem hiding this comment.
Not sure whether to fully remove or keep a BC reference at least? and warn when it is used -> remap to dispatch
1cbdb8c to
9845688
Compare
ep_router expert-parallel planep_router expert-parallel style
|
I think it's best to fully remove it otherwise that will create so much confusion |
| "layers.*.feed_forward.gate_proj": "colwise", | ||
| "layers.*.feed_forward.up_proj": "colwise", | ||
| "layers.*.feed_forward.down_proj": "rowwise", | ||
| "layers.*.feed_forward.router": "ep_router", |
There was a problem hiding this comment.
is is the only file that has ep_router left ?
There was a problem hiding this comment.
No: ep_router was in nine model configs (Beam, DiffusionGemma, Gemma 4, HY-V4, Inkling, Llama 4, Mellum, MiMo-V2-Flash, Qwen3-MoE), and this PR removes it from all of them, modular files included. Llama 4 loses its whole base_model_ep_plan (4bad170). After this PR the only ep_router left in the repo is the error mixin.py raises for a user plan that still names it.
There was a problem hiding this comment.
can we not dispatch on llama4's moe instead?
There was a problem hiding this comment.
not without refactoring llama4's moe: its experts take every token already scaled by its router score (hidden_states only), not (hidden_states, top_k_index, top_k_weights), so there's nothing for dispatch to route. moving it to the standard experts interface is a follow-up
There was a problem hiding this comment.
gotcha, fair enough we still have tp which is the same then for now (would mention this tho!)
There was a problem hiding this comment.
added a comment in the config where the ep plan used to be, a line in the docs, and a line in the breaking changes of the description
vasqu
left a comment
There was a problem hiding this comment.
Mostly agree here, it's better to remove this indeed otherwise we keep some weird references overall
| | Configuration | Result | | ||
| | :--- | :--- | | ||
| | `DistributedConfig(tp_size=4, fsdp_size=2, ep_size=4)` | Each TP group of four shares one batch. Training dispatches tokens, each rank taking a quarter of the batch; inference uses `"all_reduce"`. A masked `ep_plan` uses masking and all-reduce throughout. | | ||
| | `DistributedConfig(tp_size=4, fsdp_size=2, ep_size=4)` | Each TP group of four shares one batch. Training dispatches tokens, each rank taking a quarter of the batch; inference uses `"all_reduce"`; `ep_implementation="all_reduce"` uses it throughout. | |
| | `DistributedConfig(tp_size=1, fsdp_size=8, ep_size=4)` | Dispatch with an independent batch on each rank, no slicing, and experts FSDP-sharded across pairs of ranks. | | ||
| | `DistributedConfig(tp_size=2, fsdp_size=4, ep_size=4)` | Dispatch with a TP pair per batch, each pair slicing its batch in two; two batches per expert group. | | ||
| | `DistributedConfig(tp_size=8, ep_size=8)` | Every rank shares one batch. Training dispatches tokens, each rank taking an eighth of the batch; inference uses `"all_reduce"`. A masked `ep_plan` uses masking and all-reduce throughout. | | ||
| | `DistributedConfig(tp_size=8, ep_size=8)` | Every rank shares one batch. Training dispatches tokens, each rank taking an eighth of the batch; inference uses `"all_reduce"`; `ep_implementation="all_reduce"` uses it throughout. | |
| ## Combining with FSDP2 | ||
|
|
||
| Tensor and expert parallelism shard the weights across `tp`, but the optimizer state and the modules without a rule are still replicated on every rank of the group, which limits how large a model you can train. Add [FSDP2](./fsdp) on a second mesh dimension with `fsdp_size`. With masking and all-reduce, keep `ep_size=tp_size` and pass `ep_plan={"layers.*.mlp.gate": "ep_router", "layers.*.mlp.experts": "moe_tp_experts"}``. | ||
| Tensor and expert parallelism shard the weights across `tp`, but the optimizer state and the modules without a rule are still replicated on every rank of the group, which limits how large a model you can train. Add [FSDP2](./fsdp) on a second mesh dimension with `fsdp_size`. With masking and all-reduce, keep `ep_size=tp_size` and pass `ep_implementation="all_reduce"`. |
There was a problem hiding this comment.
is this still correct we shard across their own meshes tp/ep no?
There was a problem hiding this comment.
right, fixed: they shard across their own meshes, tp and ep
| if {"ep_router", "moe_tp_experts"}.issubset(ep_plan.values()): | ||
| # Legacy masked EP: the EP group is the TP group, every rank keeps every token. | ||
| model = apply_tensor_parallelism(model, tp_mesh, ep_plan) |
There was a problem hiding this comment.
this is a weird artifect ig if we keep moe tp experts on tp plan it still works as expected only adding it to ep plan is properly ignored right?
this is somewhat breaking as well so best to mention this change more explicitly
There was a problem hiding this comment.
yes, in the tp plan it works as before. in the ep plan it only meant something next to ep_router (the masked path), and alone it would now apply nothing and leave the experts unsharded, so it raises and points to ep_dispatch_experts / ep_implementation="all_reduce". added it to the breaking changes in the description, with a test
| if torch.is_grad_enabled() and top_k_weights.requires_grad: | ||
| top_k_weights = _AllReduceBackward.apply(top_k_weights, tp_group) | ||
| if torch.is_grad_enabled() and top_k_weights.requires_grad: | ||
| top_k_weights = _AllReduceBackward.apply(top_k_weights, tp_group) |
There was a problem hiding this comment.
we should use our helper here then depending on what we do on 1 in the stack
There was a problem hiding this comment.
done, it calls _AllReduceBackward.apply unconditionally now, the grad check lives inside it since #49429
| "layers.*.feed_forward.gate_proj": "colwise", | ||
| "layers.*.feed_forward.up_proj": "colwise", | ||
| "layers.*.feed_forward.down_proj": "rowwise", | ||
| "layers.*.feed_forward.router": "ep_router", |
There was a problem hiding this comment.
can we not dispatch on llama4's moe instead?
|
|
||
| Enable expert parallelism with the [`DistributedConfig`] class and the `ep_size` argument. Most MoE models default to [token dispatch](#token-dispatch), so `ep_size` can be set independently of `tp_size`. A few, such as Llama 4, still default to masking and all-reduce (`"ep_router"` and `"moe_tp_experts"` in `model.ep_plan`). Masking is also available on any model with an `ep_plan` override, and requires `ep_size=tp_size` so every rank in an expert group receives the same tokens. | ||
|
|
||
| Enable expert parallelism with the [`DistributedConfig`] class and the `ep_size` argument. MoE models shard their experts with `"ep_dispatch_experts"`. Its implementations pick how tokens reach the experts. [Token dispatch](#token-dispatch) lets `ep_size` differ from `tp_size`. Masking and all-reduce requires `ep_size=tp_size`, so every rank of an expert group holds the same tokens. |
There was a problem hiding this comment.
| Enable expert parallelism with the [`DistributedConfig`] class and the `ep_size` argument. MoE models shard their experts with `"ep_dispatch_experts"`. Its implementations pick how tokens reach the experts. [Token dispatch](#token-dispatch) lets `ep_size` differ from `tp_size`. Masking and all-reduce requires `ep_size=tp_size`, so every rank of an expert group holds the same tokens. | |
| Enable expert parallelism with the [`DistributedConfig`] class and the `ep_size` argument. MoE models shard their experts with `"ep_dispatch_experts"`. Its implementations pick how tokens reach the experts. [Token dispatch](#token-dispatch) lets `ep_size` differ from `tp_size`. A few EP implementations require `ep_size=tp_size`, e.g. `all-reduce`, so every rank of an expert group holds the same tokens. |
thinking of things like deep ep, mega moe etc
There was a problem hiding this comment.
took it, naming both "all_reduce" and "deepgemm_megamoe" (mega moe also hands out slices of the shared batch)
| Providing a plan does not infer parallel sizes: set `tp_size` and `ep_size` explicitly. | ||
|
|
||
| Most MoE models default to `"ep_dispatch_experts"`, which already uses `"all_reduce"` at inference when `ep_size=tp_size`. To use masking and all-reduce in training too, set `ep_size=tp_size` and override both the router and the expert forward rules (the router module name depends on the model, e.g. `mlp.router` on gpt-oss): | ||
| The default `"auto"` implementation already uses `"all_reduce"` at inference when `ep_size=tp_size`. To use masking and all-reduce in training too, set `ep_implementation="all_reduce"`: |
There was a problem hiding this comment.
the already sounds a bit off / confusing no?
There was a problem hiding this comment.
yeah, reworded: "With ep_size=tp_size, the default "auto" implementation runs "all_reduce" at inference and dispatches tokens in training. To mask and all-reduce in training too, set ep_implementation="all_reduce""
| ## Combining with FSDP2 | ||
|
|
||
| Tensor and expert parallelism shard the weights across `tp`, but the optimizer state and the modules without a rule are still replicated on every rank of the group, which limits how large a model you can train. Add [FSDP2](./fsdp) on a second mesh dimension with `fsdp_size`. With masking and all-reduce, keep `ep_size=tp_size` and pass `ep_plan={"layers.*.mlp.gate": "ep_router", "layers.*.mlp.experts": "moe_tp_experts"}``. | ||
| Tensor and expert parallelism shard the weights across their own meshes, `tp` and `ep`, but the optimizer state and the modules without a rule are still replicated on every rank of the group, which limits how large a model you can train. Add [FSDP2](./fsdp) on a second mesh dimension with `fsdp_size`. With masking and all-reduce, keep `ep_size=tp_size` and pass `ep_implementation="all_reduce"`. |
There was a problem hiding this comment.
| Tensor and expert parallelism shard the weights across their own meshes, `tp` and `ep`, but the optimizer state and the modules without a rule are still replicated on every rank of the group, which limits how large a model you can train. Add [FSDP2](./fsdp) on a second mesh dimension with `fsdp_size`. With masking and all-reduce, keep `ep_size=tp_size` and pass `ep_implementation="all_reduce"`. | |
| Tensor and expert parallelism shard the weights across their own meshes, `tp` and `ep`, but the optimizer state and the modules without a rule are still replicated on every rank of the group, which limits how large a model you can train. Add [FSDP2](./fsdp) on another mesh dimension with `fsdp_size`. With masking and all-reduce, keep `ep_size=tp_size` and pass `ep_implementation="all_reduce"`. |
| "model.layers.*.mlp.gate": "ep_router", | ||
| "model.layers.*.mlp.experts": "moe_tp_experts", | ||
| }, | ||
| ep_implementation="all_reduce", |
There was a problem hiding this comment.
ig its technically not necessary? auto should be enough
There was a problem hiding this comment.
in training auto dispatches tokens (all-to-all), and the Trainer doesn't handle dispatch yet (the warning above). this example is the masked + all-reduce training setup, so it needs the explicit "all_reduce". added a comment on that line saying so
| "`moe_tp_experts` is a tensor-parallel style: keep it in `tp_plan`, or name the experts " | ||
| '`ep_dispatch_experts` in `ep_plan`. `DistributedConfig(ep_implementation="all_reduce")` gives the ' | ||
| "masking and all-reduce the old `ep_router` + `moe_tp_experts` plan did." |
There was a problem hiding this comment.
Should we nudge towards a full plan example? grouped_gemm is technically also one slight difference
There was a problem hiding this comment.
merged the two errors into one that spells out the full plan: experts -> ep_dispatch_experts, gate_up_proj / down_proj -> grouped_gemm, and says most moe models default to it so ep_plan can usually be left out
| return module | ||
|
|
||
| def transform_output_post_forward(self, module, output, mesh, source=None): | ||
| def transform_output_post_forward(self, module, output, mesh): |
There was a problem hiding this comment.
do we even need this override anymore?
There was a problem hiding this comment.
the all-reduce itself is still needed: the base transform_output_post_forward returns the output as is, and with tp each rank only computes its shard of every expert, so the outputs are partial sums. but the "any sharded param" guard in it (skip the reduce when the experts are replicated) has no user anymore: every built-in moe_tp_experts plan also has rules that shard gate_up_proj / down_proj, and every tp style shards through distribute_tensor. so i dropped the guard and it always reduces
| "layers.*.feed_forward.gate_proj": "colwise", | ||
| "layers.*.feed_forward.up_proj": "colwise", | ||
| "layers.*.feed_forward.down_proj": "rowwise", | ||
| "layers.*.feed_forward.router": "ep_router", |
There was a problem hiding this comment.
gotcha, fair enough we still have tp which is the same then for now (would mention this tho!)
| self.assertEqual(model.tp_plan, {"model.layers.*.self_attn.q_proj": "colwise"}) | ||
|
|
||
| @parameterized.expand([("ep_router", "model.layers.*.mlp.gate"), ("moe_tp_experts", "model.layers.*.mlp.experts")]) | ||
| def test_legacy_masked_ep_plan_is_refused(self, style, module): |
There was a problem hiding this comment.
moved it to the end of TestExpertParallelLayer in tests/expert_parallel/test_expert_parallel.py
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
e7e8f75 to
78e10dc
Compare
|
[For maintainers] Suggested jobs to run (before merge) run-slow: llama4 |
Depends on #49613.
After #49607 and #49613, no model's EP plan uses the router-masked plan (
ep_routeron the router,moe_tp_expertson the experts).ep_dispatch_expertscovers it:DistributedConfig(ep_implementation="all_reduce")gives the same masking and all-reduce (#49429). So this PR removes theep_routerstyle.Changes
distributed/expert_parallel.py:EpRouterParalleland its"ep_router"registration are removed.distributed/mixin.py:ep_routerraises an error that points toep_dispatch_expertsandep_implementation="all_reduce".ep_planthat namesmoe_tp_experts: withoutep_routerit would apply nothing and leave the experts unsharded.moe_tp_expertskeeps working fromtp_plan.ep_dispatch_expertson the experts,grouped_gemmon their weights).distributed/configuration_utils.py: theep_size=tp_sizecheck of the masked plan is removed.AllReduceEPchecks it.distributed/tensor_parallel.py:moe_tp_expertsloses itsis_expert_parallelflag, which no caller passes. It also loses theinstall_forwardoverride that only passed that flag, and its output always all-reduces: the guard that skipped the reduce for replicated experts has no user, since everymoe_tp_expertsplan shards the expert weights.integrations/tensor_parallel.py: theRouterParallelandEpRouterParallelaliases are removed.ep_routerrule and had no experts rule, so it never applied. Withep_size > 1, attention and the experts stayed replicated.hidden_states, so they cannot useep_dispatch_experts.ep_size > 1now raises the "noep_plan" error. Its experts still shard with tensor parallelism (tp_size), as before. Moving them to the standard experts interface, so EP can dispatch to them, is a follow-up.ep_routerplan examples are replaced withep_implementation="all_reduce".EpRouterParallelleaves the parallel styles table.🚨 Breaking changes
ep_routerstyle is removed. A plan that names it raises an error with the replacement.moe_tp_expertsin anep_planno longer selects masked EP; it raises an error with the replacement. In atp_planit works as before.EpRouterParalleland itsRouterParallelalias are removed.ep_size > 1raises instead of silently replicating; usetp_sizeto shard its experts.Tests
tests/expert_parallelandtests/tensor_parallelon CPU, including both legacy plans being refusedtiktokentests/expert_parallel,tests/tensor_paralleland the TP/EP tests of Mixtral, Qwen3-MoE and Llama 4 on CPU🤖 Generated with Claude Code