-
Notifications
You must be signed in to change notification settings - Fork 34.6k
Expert-parallel token dispatch: each rank trains on its own part of the batch #48518
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
8af1a00
1f0b7bf
4636d42
89fa55c
96769d9
c74c122
1c66acb
38aefbf
e72b117
449acd2
9d17bd7
6f2d9c4
3a8da11
1b07199
7063c45
dc1fdee
f2a3c26
3ab94ff
d188144
b9bf58b
39cdce5
9f86230
930c146
15588e0
51f281d
101bfea
2d9046b
42606af
d8eb1fc
23920e3
e149869
e139e8a
cf74929
0e21b59
18144ab
c9a4216
8f2f9cb
3c8f600
e7be30a
8dbccad
35ed430
79fe11d
0817429
34e63c9
75fff71
acba6bc
69668c3
9cfb2d3
2884e2f
4ce4700
28cb482
a6e36c0
1be3a7f
db78860
a6f5824
b56e1d6
cb02b27
6a86e18
79988c3
5120748
0147840
d604a2a
b169a75
3439765
4f2a6b9
86561b6
56047f4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,7 +20,7 @@ | |
|
|
||
| from ..utils import is_torch_greater_or_equal, logging | ||
| from ..utils.hub import create_and_tag_model_card | ||
| from .configuration_utils import DistributedConfig | ||
| from .configuration_utils import EXPERTS_DISPATCH_STRATEGIES, DistributedConfig | ||
| from .fsdp import apply_fully_sharded_data_parallelism, is_fsdp_managed_module | ||
| from .pipeline_parallel import apply_pipeline_parallelism | ||
| from .tensor_parallel import ( | ||
|
|
@@ -53,6 +53,7 @@ class DistributedMixin: | |
| _ep_plan: dict[str, str] | None = None | ||
| _tp_size = None | ||
| _fsdp_size = None | ||
| _expert_parallel_dispatch = False | ||
| _pp_plan: dict[str, tuple[str, str]] | None = None | ||
| _fsdp_plan: dict[str, str] | None = None | ||
|
|
||
|
|
@@ -156,6 +157,8 @@ def prepare_distribute_model( | |
| raise ValueError("Tensor parallelism and `device_map` are mutually exclusive.") | ||
| if distributed_config.fsdp_size > 1 and not is_torch_greater_or_equal("2.7"): | ||
| raise OSError("FSDP2 requires `torch>=2.7` (distributed checkpoint save/load).") | ||
| if distributed_config.dispatches_tokens and not is_torch_greater_or_equal("2.7"): | ||
| raise OSError("Expert-parallel token dispatch requires `torch>=2.7`.") | ||
|
|
||
| device_map, device_mesh = initialize_distributed_mesh(distributed_config) | ||
|
|
||
|
|
@@ -174,6 +177,7 @@ def maybe_distribute_model( | |
| model._device_mesh = device_mesh | ||
| model._tp_size = distributed_config.tp_size | ||
| model._fsdp_size = distributed_config.fsdp_size | ||
| model._expert_parallel_dispatch = distributed_config.dispatches_tokens | ||
|
|
||
| if distributed_config.pp_size > 1: | ||
| pp_mesh = device_mesh["pp"] if device_mesh.ndim > 1 else device_mesh | ||
|
|
@@ -185,9 +189,40 @@ def maybe_distribute_model( | |
| tp_mesh = device_mesh["tp"] if device_mesh.ndim > 1 else device_mesh | ||
| if isinstance(distributed_config.tp_plan, dict): | ||
| model.tp_plan = distributed_config.tp_plan | ||
| if distributed_config.dispatches_tokens: | ||
| # Every rank trains on its own part of the batch, so only the experts can be sharded across the | ||
| # group: the experts get the dispatch style, the router keeps its global ids and scores, and | ||
| # whatever else the plan shards stays replicated, data-parallel like the rest of the trunk. | ||
| # Replicated parameters inside the experts module keep their gradient all-reduce: FSDP2 treats | ||
| # that module as expert-owned and does not reduce them, and each rank saw different tokens. | ||
| kept = ("grouped_gemm", "moe_tp_experts", "replicated_with_grad_allreduce") | ||
| replicated = sorted( | ||
| name for name, style in model.tp_plan.items() if style not in ("ep_router", *kept) | ||
| ) | ||
|
Comment on lines
+199
to
+201
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. pretty sure default is replicate, so if you pass the ep plan hard coded, it won't follow the one form the config. You don't need any of that in that case no?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's not setting a default, it's dropping entries.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed it's the wrong place to hardcode the style names though. If it moves behind |
||
| if replicated: | ||
| logger.warning( | ||
| f"`experts_dispatch={distributed_config.experts_dispatch!r}` shards only the experts, " | ||
| "so these expert parallel plan " | ||
| f"entries are ignored and their modules stay replicated: {replicated}." | ||
| ) | ||
| # `tp_plan` reads `_ep_plan` under expert parallelism, so that is the plan to rewrite. | ||
| dispatch_style = EXPERTS_DISPATCH_STRATEGIES[distributed_config.experts_dispatch] | ||
| model._ep_plan = { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it is better to do the dispatch of plan at the same place where at this stage of the code, we should only be reading & applying the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if you do that, you can probably check for replicate by just doing
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, it should not be modifying the plan here. @ArthurZucker made the same point in #48518 (comment) and said he would take it, so I left it alone. Happy to move it into the property as you describe if he would rather not.
(a property is read more than once, so it would need |
||
| name: dispatch_style if style == "moe_tp_experts" else style | ||
| for name, style in model.tp_plan.items() | ||
| if style in kept | ||
| } | ||
| model = apply_tensor_parallelism(model, tp_mesh) | ||
|
|
||
| if distributed_config.fsdp_size > 1: | ||
| if distributed_config.dispatches_tokens: | ||
| # Every expert-parallel rank trains on its own part of the batch, so the parameters outside the | ||
| # experts are data-parallel across the whole mesh: FSDP2 shards them across all of it and owns | ||
| # their gradient reduction. The experts stay sharded across `tp` and, if any, across `fsdp`. | ||
| flattened = "_".join(device_mesh.mesh_dim_names) | ||
| trunk_mesh = device_mesh[flattened] if device_mesh.ndim > 1 else device_mesh | ||
| expert_mesh = device_mesh["fsdp"] if device_mesh.ndim > 1 else None | ||
| model = apply_fully_sharded_data_parallelism(model, trunk_mesh, expert_mesh=expert_mesh) | ||
| elif distributed_config.fsdp_size > 1: | ||
| fsdp_mesh = device_mesh["fsdp"] if device_mesh.ndim > 1 else device_mesh | ||
| model = apply_fully_sharded_data_parallelism(model, fsdp_mesh) | ||
| return model | ||
|
|
@@ -250,9 +285,9 @@ def gather_sharded_state_dict_for_save( | |
| if distributed_config is None: | ||
| return state_dict | ||
|
|
||
| if distributed_config.fsdp_size > 1: | ||
| # Also covers the 2-D (fsdp, tp) mesh: every parameter is FSDP-managed, and the full | ||
| # state dict is only materialized on rank 0. | ||
| if distributed_config.fsdp_size > 1 or distributed_config.dispatches_tokens: | ||
| # Also covers the 2-D (fsdp, tp) mesh and token dispatch: every parameter is FSDP-managed, and | ||
| # the full state dict is only materialized on rank 0. | ||
| if not _is_torch_distributed_initialized(): | ||
| raise ValueError( | ||
| "Saving an FSDP-wrapped model requires torch.distributed to be initialized. " | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.