Skip to content

Commit ce21f3a

Browse files
committed
[distributed] Allow context parallelism together with expert parallelism
Under cp the ranks of an ep group hold the slices of several batches, which token dispatch already handles: each rank sends the tokens of its own slice. The expert gradient divides by dp_size, which includes cp_size, so the reduced expert gradient is still the gradient of the full-sequence loss.
1 parent 2b71a64 commit ce21f3a

3 files changed

Lines changed: 11 additions & 10 deletions

File tree

‎src/transformers/distributed/configuration_utils.py‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,9 @@ class DistributedConfig:
6363
Number of devices that split each sequence (context parallelism). The ranks of a cp group get the same
6464
batch, and each keeps a contiguous slice of every sequence, so the sequence length must be divisible by
6565
`cp_size`. With `cp_size > 1`, FSDP2 shards over `dp_size` ranks: the ranks of a cp group hold parts of
66-
the same batch, so their gradients are partial sums of the same batch. Can be combined with `tp_size`, in
67-
which case the ranks of a tp group hold the same batch and the same slice. Defaults to 1.
66+
the same batch, so their gradients are partial sums of the same batch. Can be combined with `tp_size` and
67+
`ep_size`, in which case the ranks of a tp group hold the same batch and the same slice, while each rank
68+
of an ep group dispatches the tokens of its own slice. Defaults to 1.
6869
cp_implementation (`str`, *optional*, defaults to `"ulysses"`):
6970
How attention sees the whole sequence while each rank holds a slice of it. A name in
7071
`transformers.distributed.context_parallel.cp_implementations`. `"ulysses"`: an all-to-all gives each
@@ -194,12 +195,11 @@ def _validate_mesh_config(self):
194195
if self.cp_size > 1:
195196
if not is_torch_greater_or_equal("2.10"):
196197
raise OSError("Context parallelism requires `torch>=2.10` (`DeviceMesh._unflatten`).")
197-
unsupported = [name for name in ("pp_size", "ep_size") if getattr(self, name) > 1]
198-
if unsupported:
198+
if self.pp_size > 1:
199199
raise ValueError(
200200
f"Context parallelism (`cp_size={self.cp_size}`) is not supported yet with "
201-
f"{', '.join(f'`{name}={getattr(self, name)}`' for name in unsupported)}. Use "
202-
"`DistributedConfig(tp_size=N, cp_size=M)` or `DistributedConfig(fsdp_size=N, cp_size=M)`."
201+
f"`pp_size={self.pp_size}`. Use `DistributedConfig(tp_size=N, cp_size=M)`, "
202+
"`DistributedConfig(fsdp_size=N, cp_size=M)` or `DistributedConfig(ep_size=N, cp_size=M)`."
203203
)
204204

205205
if self.dp_size > 1 and self.pp_size > 1:

‎src/transformers/distributed/fsdp.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,10 +202,10 @@ def apply_fully_sharded_data_parallelism(
202202
for module_name, module in model.named_modules():
203203
if ep_plan.get(module_name) == "ep_dispatch_experts":
204204
fully_shard(module, mesh=expert_mesh, reshard_after_forward=True, **fsdp_policy_kwargs)
205-
# an EP group holds ep_size / tp_size distinct batches
205+
# an EP group holds ep_size / tp_size distinct batches, or cp slices of them under context parallelism
206206
# the efsdp reduce the sums on efsdp_size copies of that expert.
207-
# The expert gradient therefore covers ep_size / tp_size * efsdp_size = fsdp_size batches
208-
# not efsdp_size batches (the relationship is efsdp_size = fsdp_size * tp_size / ep_size)
207+
# The expert gradient therefore covers ep_size / tp_size * efsdp_size = fsdp_size * cp_size batches
208+
# not efsdp_size batches (the relationship is efsdp_size = fsdp_size * cp_size * tp_size / ep_size)
209209
# With HSDP, the all-reduce across replicas adds dp_replicate_size times more: dp_size batches.
210210
module.set_gradient_divide_factor(float(distributed_config.dp_size))
211211
# FSDP2 applies a divide factor other than the group size through PREMUL_SUM, which non-NCCL backends

‎src/transformers/distributed/utils.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,8 @@ class TransformersDeviceMesh:
205205
parameters over `fsdp` and `cp` together (`fsdp_cp`): the ranks of a cp group hold parts of the same batch, so their
206206
gradients are partial sums of that batch. `fsdp_cp` is an axis of the dense root mesh because FSDP2 composes the
207207
mesh it shards over with the mesh the parameters are already sharded on (`tp`), and torch only composes meshes
208-
built from the same root.
208+
built from the same root. On the expert mesh a cp slice plays the role of a batch: each rank dispatches the tokens
209+
of its own slice only, so no token is sent twice and `efsdp * ep` still covers `fsdp * cp * tp`.
209210
"""
210211

211212
def __init__(self, dense_mesh: DeviceMesh, expert_mesh: DeviceMesh, dense_root_mesh: DeviceMesh | None = None):

0 commit comments

Comments
 (0)