Skip to content

Commit 79df454

Browse files
committed
Add an opt-in route_norm flag for the legacy MoE gate
top1gating leaves the chosen expert's softmax probability in the combine weight. top2gating and topkgating rescale the kept weights so they sum to 1. Making those agree changes the MoE output for existing checkpoints, so route_norm defaults to None and each gate keeps its historical rule. True or False overrides all three gates. Auxiliary loss is unchanged. Signed-off-by: ARAVINDHAN T <arvindhant01@gmail.com>
1 parent 991ccf0 commit 79df454

4 files changed

Lines changed: 274 additions & 27 deletions

File tree

‎deepspeed/moe/layer.py‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ class MoE(nn.Module):
3333
use_tutel (bool, optional): default=False, whether to use Tutel optimizations (if installed).
3434
enable_expert_tensor_parallelism (bool, optional): default=False, whether to use tensor parallelism for experts
3535
top2_2nd_expert_sampling (bool, optional): default=True, whether to perform sampling for 2nd expert
36+
route_norm (bool, optional): default=None. None keeps the historical combine weights: k=1 leaves the chosen
37+
expert's softmax probability, and k>=2 rescales the kept weights so they sum to 1. True rescales for every k.
38+
False leaves the raw softmax probabilities for every k. True or False changes the MoE output relative to a
39+
checkpoint trained with the other setting. The auxiliary load-balancing loss is computed before this step
40+
and does not change. This is the legacy gate. It is not expert_parallel.route_norm, which configures the
41+
AutoEP router.
3642
"""
3743

3844
def __init__(self,
@@ -50,7 +56,8 @@ def __init__(self,
5056
use_rts: bool = True,
5157
use_tutel: bool = False,
5258
enable_expert_tensor_parallelism: bool = False,
53-
top2_2nd_expert_sampling: bool = True) -> None:
59+
top2_2nd_expert_sampling: bool = True,
60+
route_norm: Optional[bool] = None) -> None:
5461

5562
super(MoE, self).__init__()
5663

@@ -72,7 +79,7 @@ def __init__(self,
7279
experts = Experts(expert, self.num_local_experts, self.expert_group_name)
7380
self.deepspeed_moe = MOELayer(TopKGate(hidden_size, num_experts, k, capacity_factor, eval_capacity_factor,
7481
min_capacity, noisy_gate_policy, drop_tokens, use_rts, None,
75-
top2_2nd_expert_sampling),
82+
top2_2nd_expert_sampling, route_norm),
7683
experts,
7784
self.expert_group_name,
7885
self.ep_size,

‎deepspeed/moe/sharded_moe.py‎

Lines changed: 78 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,13 @@ def _sparse_decode(expert_output: Tensor, slots: Tensor, gates: Tensor, num_toke
232232
return combined.to(expert_output.dtype)
233233

234234

235+
def _route_norm_enabled(route_norm: Optional[bool], legacy_default: bool) -> bool:
236+
"""None keeps that gate's historical combine-weight rule. True or False overrides every gate."""
237+
if route_norm is None:
238+
return legacy_default
239+
return route_norm
240+
241+
235242
def top1gating(logits: Tensor,
236243
capacity_factor: float,
237244
min_capacity: int,
@@ -241,8 +248,13 @@ def top1gating(logits: Tensor,
241248
use_rts: bool = True,
242249
ep_group: Union[torch.distributed.ProcessGroup, None] = None,
243250
sparse_routes: bool = False,
244-
use_tutel: bool = False) -> Tuple[Tensor, Tensor, Tensor, Tensor]:
245-
"""Implements Top1Gating on logits."""
251+
use_tutel: bool = False,
252+
route_norm: Optional[bool] = None) -> Tuple[Tensor, Tensor, Tensor, Tensor]:
253+
"""Implements Top1Gating on logits.
254+
255+
route_norm=None keeps the historical weights: the chosen expert's softmax probability, with no
256+
second normalization. The auxiliary loss is computed from those probabilities either way.
257+
"""
246258
if noisy_gate_policy == 'RSample':
247259
logits_w_noise = logits + gumbel_rsample(logits.shape, device=logits.device)
248260
# everything is in fp32 in this function
@@ -319,16 +331,25 @@ def top1gating(logits: Tensor,
319331

320332
if sparse_routes:
321333
gates1_s = (gates * mask1).sum(dim=1)
334+
# Historical top-1 does not renormalize. An explicit True makes a kept token's weight 1.
335+
# A dropped token sums to 0, and 0 / eps stays 0.
336+
if _route_norm_enabled(route_norm, False):
337+
denom_s = torch.clamp(gates1_s, min=torch.finfo(gates1_s.dtype).eps)
338+
gates1_s = gates1_s / denom_s
322339
locations1_s = torch.sum(locations1 * mask1, dim=1)
323340
return (l_aux, capacity, num_experts, indices1_s.to(torch.int32).unsqueeze(0),
324341
locations1_s.to(torch.int32).unsqueeze(0), gates1_s.unsqueeze(0), exp_counts)
325342

326343
# Store the capacity location for each token
327344
locations1_s = torch.sum(locations1 * mask1, dim=1)
328345

329-
# Normalize gate probabilities
346+
# Keep the assigned expert's softmax probability. route_norm rescales a kept row to 1.
330347
mask1_float = mask1.float()
331348
gates = gates * mask1_float
349+
if _route_norm_enabled(route_norm, False):
350+
denom_s = torch.sum(gates, dim=1, keepdim=True)
351+
denom_s = torch.clamp(denom_s, min=torch.finfo(denom_s.dtype).eps)
352+
gates = gates / denom_s
332353

333354
locations1_sc = _one_hot_to_float(locations1_s, capacity)
334355
combine_weights = einsum("se,sc->sec", gates, locations1_sc)
@@ -344,8 +365,13 @@ def top2gating(logits: Tensor,
344365
drop_tokens: bool = True,
345366
ep_group: Union[torch.distributed.ProcessGroup, None] = None,
346367
top2_2nd_expert_sampling: bool = True,
347-
sparse_routes: bool = False) -> Tuple[Tensor, Tensor, Tensor, Tensor]:
348-
"""Implements Top2Gating on logits."""
368+
sparse_routes: bool = False,
369+
route_norm: Optional[bool] = None) -> Tuple[Tensor, Tensor, Tensor, Tensor]:
370+
"""Implements Top2Gating on logits.
371+
372+
route_norm=None keeps the historical weights: the two kept probabilities are rescaled to sum to 1.
373+
The auxiliary loss is computed before that rescaling.
374+
"""
349375
# everything is in fp32 in this function
350376
gates = F.softmax(logits, dim=1)
351377

@@ -399,16 +425,17 @@ def top2gating(logits: Tensor,
399425
locations1_s = torch.sum(locations1 * mask1, dim=1)
400426
locations2_s = torch.sum(locations2 * mask2, dim=1)
401427

402-
# Normalize gate probabilities
428+
# Normalize gate probabilities. route_norm=None keeps this rescaling; False leaves the raw softmax values.
403429
mask1_float = mask1.float()
404430
mask2_float = mask2.float()
405431
gates1_s = einsum("se,se->s", gates, mask1_float)
406432
gates2_s = einsum("se,se->s", gates, mask2_float)
407-
denom_s = gates1_s + gates2_s
408-
# Avoid divide-by-zero
409-
denom_s = torch.clamp(denom_s, min=torch.finfo(denom_s.dtype).eps)
410-
gates1_s /= denom_s
411-
gates2_s /= denom_s
433+
if _route_norm_enabled(route_norm, True):
434+
denom_s = gates1_s + gates2_s
435+
# Avoid divide-by-zero. A dropped token is 0 / eps and stays 0.
436+
denom_s = torch.clamp(denom_s, min=torch.finfo(denom_s.dtype).eps)
437+
gates1_s /= denom_s
438+
gates2_s /= denom_s
412439

413440
if sparse_routes:
414441
# Routes evicted by the capacity limit are flagged with a negative expert index.
@@ -440,8 +467,13 @@ def topkgating(
440467
ep_group: Union[torch.distributed.ProcessGroup, None] = None,
441468
drop_policy: str = "probs",
442469
sparse_routes: bool = False,
470+
route_norm: Optional[bool] = None,
443471
) -> Tuple[Tensor, Tensor, Tensor, Tensor]:
444-
"""Implements TopKGating on logits."""
472+
"""Implements TopKGating on logits.
473+
474+
route_norm=None keeps the historical weights: the kept probabilities are rescaled to sum to 1.
475+
The auxiliary loss is computed before that rescaling.
476+
"""
445477

446478
# everything is in fp32 in this function
447479
# gating decisions
@@ -494,11 +526,12 @@ def topkgating(
494526
capacity = new_capacity
495527
locations = torch.cumsum(mask, dim=0) - 1
496528

497-
# normalize gates
529+
# normalize gates. route_norm=None keeps this rescaling; False leaves the raw softmax values.
498530
gates_masked = gates * mask
499-
gates_s = torch.sum(gates_masked, dim=-1, keepdim=True)
500-
denom_s = torch.clamp(gates_s, min=torch.finfo(gates_masked.dtype).eps)
501-
gates_masked = gates_masked / denom_s
531+
if _route_norm_enabled(route_norm, True):
532+
gates_s = torch.sum(gates_masked, dim=-1, keepdim=True)
533+
denom_s = torch.clamp(gates_s, min=torch.finfo(gates_masked.dtype).eps)
534+
gates_masked = gates_masked / denom_s
502535

503536
if locations is None:
504537
raise ValueError(f"Locations is not set: {locations}")
@@ -539,6 +572,10 @@ class TopKGate(Module):
539572
size of model embedding dimension
540573
num_experts (int):
541574
number of experts in model
575+
route_norm (bool, optional):
576+
None keeps the historical combine weights (no renorm for k=1, renorm for k>=2).
577+
True renormalizes the kept weights for every k. False leaves the raw softmax values.
578+
The auxiliary loss does not use this flag.
542579
"""
543580

544581
wg: torch.nn.Linear
@@ -554,7 +591,8 @@ def __init__(self,
554591
drop_tokens: bool = True,
555592
use_rts: bool = True,
556593
ep_group: Union[torch.distributed.ProcessGroup, None] = None,
557-
top2_2nd_expert_sampling: bool = True) -> None:
594+
top2_2nd_expert_sampling: bool = True,
595+
route_norm: Optional[bool] = None) -> None:
558596
super().__init__()
559597

560598
self.wg = torch.nn.Linear(model_dim, num_experts, bias=False)
@@ -570,6 +608,8 @@ def __init__(self,
570608
self.drop_tokens = drop_tokens
571609
self.use_rts = use_rts
572610
self.top2_2nd_expert_sampling = top2_2nd_expert_sampling
611+
# None is forwarded so each gating function keeps its own historical default.
612+
self.route_norm = route_norm
573613

574614
def _set_ep_group(self, ep_group):
575615
assert self.ep_group is None, 'Attempting to override an existing ep_group'
@@ -591,22 +631,36 @@ def forward(self,
591631
logits = torch.nn.functional.linear(input_fp32, weight=self.wg.weight.float(), bias=None)
592632

593633
if self.k == 1:
594-
gate_output = top1gating(logits, self.capacity_factor if self.training else self.eval_capacity_factor,
595-
self.min_capacity, used_token, self.noisy_gate_policy if self.training else None,
596-
self.drop_tokens, self.use_rts, self.ep_group, sparse_routes, use_tutel)
634+
gate_output = top1gating(logits,
635+
self.capacity_factor if self.training else self.eval_capacity_factor,
636+
self.min_capacity,
637+
used_token,
638+
self.noisy_gate_policy if self.training else None,
639+
self.drop_tokens,
640+
self.use_rts,
641+
self.ep_group,
642+
sparse_routes,
643+
use_tutel,
644+
route_norm=self.route_norm)
597645

598646
elif self.k == 2:
599-
gate_output = top2gating(logits, self.capacity_factor if self.training else self.eval_capacity_factor,
600-
self.min_capacity, self.drop_tokens, self.ep_group, self.top2_2nd_expert_sampling,
601-
sparse_routes)
647+
gate_output = top2gating(logits,
648+
self.capacity_factor if self.training else self.eval_capacity_factor,
649+
self.min_capacity,
650+
self.drop_tokens,
651+
self.ep_group,
652+
self.top2_2nd_expert_sampling,
653+
sparse_routes,
654+
route_norm=self.route_norm)
602655
else:
603656
gate_output = topkgating(logits,
604657
self.k,
605658
self.capacity_factor if self.training else self.eval_capacity_factor,
606659
self.min_capacity,
607660
self.drop_tokens,
608661
self.ep_group,
609-
sparse_routes=sparse_routes)
662+
sparse_routes=sparse_routes,
663+
route_norm=self.route_norm)
610664

611665
if self.wall_clock_breakdown:
612666
self.timers(TOPK_GATE_TIMER).stop()

‎docs/_tutorials/mixture-of-experts.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ Updated with MoE Layers
6363
self.fc4 = nn.Linear(84, 10)
6464
```
6565

66+
`MoE(..., route_norm=...)` is optional and defaults to `None`. That default keeps the historical combine weights: `k=1` uses the chosen expert's softmax probability, and `k>=2` rescales the kept weights so they sum to 1. `True` rescales for every `k`, and `False` leaves the raw softmax probabilities for every `k`. Either explicit value changes the layer output relative to a checkpoint trained with the other setting. The auxiliary load-balancing loss is unchanged. This constructor argument is the legacy gate. It is not the AutoEP config key `expert_parallel.route_norm`.
67+
6668
### Pyramid-Residual MoE
6769

6870
Recently, we proposed a novel [Pyramid-Residual MoE](https://arxiv.org/abs/2201.05596) (PR-MoE) model architecture. To create such an MoE model, the users need to do two additional things:

0 commit comments

Comments
 (0)