Skip to content

Commit 2bd83bd

Browse files
EP: local params around the implementation call in ep_forward; empty-rows case in AllToAllEP
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent b8c0ec3 commit 2bd83bd

2 files changed

Lines changed: 29 additions & 27 deletions

File tree

‎docs/source/en/expert_parallelism.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,15 @@ The expert forward rule in `ep_plan` selects how tokens reach the experts:
6565
6666
### Adding an implementation
6767

68-
An implementation subclasses `EPImplementation` and registers under the name `ep_implementation` selects. It is built once per experts module, so it can keep state such as communication buffers. Calling it with the batch this rank holds, with global expert ids, returns the weighted expert output for each of its tokens. It runs this rank's experts with `run_experts`, on the rows, local expert ids and weights it brought them.
68+
An implementation subclasses `EPImplementation` and registers under the name `ep_implementation` selects. It is built once per experts module, so it can keep state such as communication buffers. Calling it with the batch this rank holds, with global expert ids, returns the weighted expert output for each of its tokens. It runs this rank's experts with `experts_forward`, on the rows, local expert ids and weights it brought them.
6969

7070
```py
7171
from transformers.distributed.expert_parallel import EPImplementation, EPImplementationInterface
7272

7373
class MyEP(EPImplementation):
7474
def __call__(self, hidden_states, top_k_index, top_k_weights):
7575
tokens, expert_ids, weights = ... # bring this rank's experts the rows routed to them
76-
expert_output = self.run_experts(tokens, expert_ids, weights)
76+
expert_output = self.experts_forward(tokens, expert_ids, weights)
7777
return ... # the weighted expert output for each token of the batch this rank holds
7878

7979
EPImplementationInterface.register("my_ep", MyEP)

‎src/transformers/distributed/expert_parallel.py‎

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ class EPImplementation:
204204
reuses).
205205
206206
Calling it with `(hidden_states, top_k_index, top_k_weights)`, the batch this rank holds (global expert ids),
207-
returns the weighted expert output for each of its tokens. It runs this rank's experts with `run_experts`, on
207+
returns the weighted expert output for each of its tokens. It runs this rank's experts with `experts_forward`, on
208208
whatever rows, local expert ids and weights it brought them.
209209
210210
Gradients must reach every rank that holds a token, routing weights included, so a collective that autograd does not
@@ -214,8 +214,7 @@ class EPImplementation:
214214
# Set by implementations that only run when every rank of the EP group holds the same batch, e.g. `AllReduceEP`.
215215
needs_shared_batch = False
216216

217-
def __init__(self, experts, ep_mesh, tp_mesh):
218-
self.experts = experts
217+
def __init__(self, experts: nn.Module, ep_mesh: DeviceMesh, tp_mesh: DeviceMesh):
219218
self.experts_forward = experts.forward # the experts' own forward, on local expert ids
220219
self.num_local_experts = experts.num_experts
221220
self.ep_mesh = ep_mesh
@@ -231,16 +230,6 @@ def __init__(self, experts, ep_mesh, tp_mesh):
231230
def __call__(self, hidden_states, top_k_index, top_k_weights):
232231
raise NotImplementedError
233232

234-
def run_experts(self, tokens, expert_ids, weights):
235-
"""This rank's experts on `tokens`, routed to `expert_ids` (local ids) with `weights`."""
236-
with _use_local_dtensor_params(self.experts):
237-
if tokens.shape[0] == 0:
238-
# Keep the empty rows connected to backward, so the reverse exchange and FSDP's reduction still run.
239-
return self.experts_forward(
240-
tokens.sum(dim=0, keepdim=True), expert_ids.new_zeros((1, 1)), weights.new_ones((1, 1))
241-
)[:0]
242-
return self.experts_forward(tokens, expert_ids, weights)
243-
244233

245234
class _AllToAllState(NamedTuple):
246235
top_k_weights: torch.Tensor
@@ -281,7 +270,14 @@ class AllToAllEP(EPImplementation):
281270

282271
def __call__(self, hidden_states, top_k_index, top_k_weights):
283272
tokens, expert_ids, weights, state = self._dispatch_tokens(hidden_states, top_k_index, top_k_weights)
284-
return self._combine_tokens(self.run_experts(tokens, expert_ids, weights), state)
273+
if tokens.shape[0] == 0:
274+
# Keep the empty rows connected to backward, so the reverse exchange and FSDP's reduction still run.
275+
expert_output = self.experts_forward(
276+
tokens.sum(dim=0, keepdim=True), expert_ids.new_zeros((1, 1)), weights.new_ones((1, 1))
277+
)[:0]
278+
else:
279+
expert_output = self.experts_forward(tokens, expert_ids, weights)
280+
return self._combine_tokens(expert_output, state)
285281

286282
def _dispatch_tokens(self, hidden_states, top_k_index, top_k_weights):
287283
"""Each (token, expert) pair to the expert's rank: the rows this rank's experts run on, their local expert ids
@@ -376,7 +372,7 @@ def __call__(self, hidden_states, top_k_index, top_k_weights):
376372
top_k_weights = _AllReduceBackward.apply(top_k_weights, ep_group)
377373
off_rank = (top_k_index // self.num_local_experts) != self.ep_mesh.get_local_rank()
378374
local_index = torch.where(off_rank, self.num_local_experts, top_k_index % self.num_local_experts)
379-
expert_output = self.run_experts(hidden_states, local_index, top_k_weights.masked_fill(off_rank, 0.0))
375+
expert_output = self.experts_forward(hidden_states, local_index, top_k_weights.masked_fill(off_rank, 0.0))
380376
return _AllReduceForward.apply(expert_output, ep_group)
381377

382378

@@ -387,7 +383,7 @@ class AutoEP(EPImplementation):
387383
back to the host, so decode captures in a CUDA graph.
388384
"""
389385

390-
def __init__(self, experts, ep_mesh, tp_mesh):
386+
def __init__(self, experts: nn.Module, ep_mesh: DeviceMesh, tp_mesh: DeviceMesh):
391387
super().__init__(experts, ep_mesh, tp_mesh)
392388
self.all_to_all = AllToAllEP(experts, ep_mesh, tp_mesh)
393389
# Only built where it can run: an EP group whose ranks hold different batches has no all-reduce path.
@@ -415,21 +411,23 @@ def should_use_local_tensors(self, module):
415411

416412
def install_forward(self, module, ep_mesh, *, tp_mesh, ep_implementation="auto"):
417413
"""`tp_mesh` is the group of ranks holding the same batch (size 1 without TP)."""
418-
ep_implementations = EPImplementationInterface()
419-
if ep_implementation not in ep_implementations:
414+
implementations = EPImplementationInterface()
415+
if ep_implementation not in implementations:
420416
raise ValueError(
421417
f"`ep_implementation={ep_implementation!r}` names no registered implementation: use one of "
422-
f"{', '.join(repr(name) for name in ep_implementations)}, or register an `EPImplementation` with "
423-
"`EPImplementationInterface.register`."
418+
f"{', '.join(repr(name) for name in implementations)}, or register an `EPImplementation` "
419+
"with `EPImplementationInterface.register`."
424420
)
425-
implementation = ep_implementations[ep_implementation](module, ep_mesh, tp_mesh)
421+
implementation = implementations[ep_implementation](module, ep_mesh, tp_mesh)
426422

427423
def ep_forward(hidden_states, top_k_index, top_k_weights):
428424
if isinstance(hidden_states, DTensor):
429425
hidden_states = hidden_states.to_local()
430426
if isinstance(top_k_weights, DTensor):
431427
top_k_weights = top_k_weights.to_local()
432-
return implementation(hidden_states, top_k_index, top_k_weights).to(hidden_states.dtype)
428+
with self.context_around_forward(module, ep_mesh):
429+
output = implementation(hidden_states, top_k_index, top_k_weights)
430+
return output.to(hidden_states.dtype)
433431

434432
module.forward = ep_forward
435433
return module
@@ -464,11 +462,15 @@ def transform_output_post_forward(self, module, output, mesh, source=None):
464462

465463

466464
def apply_expert_parallelism(
467-
model: nn.Module, ep_mesh: DeviceMesh, tp_mesh: DeviceMesh, plan: dict[str, str], ep_implementation: str = "auto"
465+
model: nn.Module,
466+
ep_mesh: DeviceMesh,
467+
tp_mesh: DeviceMesh,
468+
plan: dict[str, str],
469+
ep_implementation: str = "auto",
468470
):
469471
"""
470-
Shard experts on EP; `ep_dispatch_experts` modules reach them through the `ep_implementation` registered in
471-
`EPImplementationInterface`.
472+
Shard experts on EP; `ep_dispatch_experts` modules reach them through the `EPImplementation` that
473+
`ep_implementation` names in `EPImplementationInterface`.
472474
"""
473475
modules = dict(model.named_modules())
474476
parameters = dict(model.named_parameters())

0 commit comments

Comments
 (0)