Skip to content

Commit 98ba4de

Browse files
JacobSzwejbkafacebook-github-bot
authored andcommitted
More flexible MoE support mobile focused (#21047)
Summary: title Reviewed By: digantdesai Differential Revision: D96376371
1 parent abbcb06 commit 98ba4de

2 files changed

Lines changed: 71 additions & 13 deletions

File tree

examples/models/llama/llama_transformer.py

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,10 @@ class ConditionalFeedForward(nn.Module):
8080
def __init__(self, args: ModelArgs):
8181
super().__init__()
8282
self.dim = args.dim
83-
hidden_dim = args.hidden_dim
83+
hidden_dim = (
84+
args.moe_hidden_dim if args.moe_hidden_dim is not None else args.hidden_dim
85+
)
8486
if hidden_dim is None:
85-
# If hidden_dim is not explicitly set in the ModelArgs,
86-
# then calculate implicitly based on dim and also multiple of `args.multiple_of`
8787
multiple_of = args.multiple_of
8888
hidden_dim = 4 * self.dim
8989
hidden_dim = int(2 * hidden_dim / 3)
@@ -95,9 +95,9 @@ def __init__(self, args: ModelArgs):
9595
self.num_experts = args.num_experts
9696

9797
def forward(self, x: torch.Tensor, expert_indices: torch.Tensor) -> torch.Tensor:
98-
w1_weights = self.w1[expert_indices].transpose(-1, -2) # [T, A, D, D]
99-
w3_weights = self.w3[expert_indices].transpose(-1, -2) # [T, A, D, D]
100-
w2_weights = self.w2[expert_indices] # [T, A, D, D]
98+
w1_weights = self.w1[expert_indices].transpose(-1, -2) # [T, A, D, hidden]
99+
w3_weights = self.w3[expert_indices].transpose(-1, -2) # [T, A, D, hidden]
100+
w2_weights = self.w2[expert_indices] # [T, A, hidden, D]
101101
x1 = F.silu(torch.einsum("ti,taio -> tao", x, w1_weights))
102102
x3 = torch.einsum("ti, taio -> tao", x, w3_weights)
103103
expert_outs = torch.einsum("tao, taoi -> tai", (x1 * x3), w2_weights)
@@ -110,16 +110,67 @@ def __init__(self, config) -> None:
110110
self.gate = nn.Linear(config.dim, config.num_experts, bias=False)
111111
self.cond_ffn = ConditionalFeedForward(config)
112112
self.dim = config.dim
113+
self.num_activated_experts = config.num_activated_experts
114+
self.score_func = config.moe_score_func
115+
self.route_scale = config.moe_route_scale
116+
if self.score_func not in {"sigmoid", "softmax", "softmax_all"}:
117+
raise ValueError(
118+
f"Unsupported MoE routing score function: {self.score_func}"
119+
)
120+
if self.score_func != "sigmoid" and self.route_scale != 1.0:
121+
raise ValueError("MoE route scaling is only supported with sigmoid routing")
122+
if not 0 < self.num_activated_experts <= config.num_experts:
123+
raise ValueError(
124+
"The number of activated experts must be between one and the "
125+
"total number of experts"
126+
)
127+
self.expert_bias: torch.Tensor | None
128+
# Citrine C3: initialize the buffer on the gate's device.
129+
self.register_buffer(
130+
"expert_bias",
131+
(
132+
self.gate.weight.new_zeros(config.num_experts)
133+
if config.moe_use_expert_bias
134+
else None
135+
),
136+
)
137+
if config.moe_shared_expert_hidden_dim is not None:
138+
self.shared_expert = FeedForward(
139+
dim=config.dim, hidden_dim=config.moe_shared_expert_hidden_dim
140+
)
141+
else:
142+
self.shared_expert = None
143+
144+
def _route(self, x: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
145+
scores = self.gate(x) # [T, E]
146+
if self.score_func == "sigmoid":
147+
scores = scores.sigmoid()
148+
elif self.score_func == "softmax_all":
149+
scores = scores.softmax(dim=-1)
150+
scores_for_topk = (
151+
(scores + self.expert_bias) if self.expert_bias is not None else scores
152+
)
153+
_, expert_indices = torch.topk(
154+
scores_for_topk, self.num_activated_experts, dim=-1
155+
)
156+
expert_weights = scores.gather(1, expert_indices)
157+
if self.score_func == "sigmoid":
158+
normalizer = expert_weights.sum(dim=-1, keepdim=True).clamp_min(
159+
torch.finfo(expert_weights.dtype).tiny
160+
)
161+
expert_weights = expert_weights / normalizer * self.route_scale
162+
elif self.score_func == "softmax":
163+
expert_weights = expert_weights.softmax(dim=-1)
164+
return expert_weights, expert_indices
113165

114166
def forward(self, x: torch.Tensor) -> torch.Tensor:
115167
x = x.view(-1, self.dim)
116-
# T = num_tokens, E = num_experts, D = hidden dim, A = activated experts
117-
# x: [T, D]
118-
scores = self.gate(x) # [T, E]
119-
expert_weights, expert_indices = torch.topk(scores, 2, dim=-1) # [T, A], [T, A]
120-
expert_weights = expert_weights.softmax(dim=-1) # [T, A]
168+
expert_weights, expert_indices = self._route(x)
121169
expert_outs = self.cond_ffn(x, expert_indices)
122-
return torch.einsum("tai,ta -> ti", expert_outs, expert_weights)
170+
out = torch.einsum("tai,ta -> ti", expert_outs, expert_weights)
171+
if self.shared_expert is not None:
172+
out = out + self.shared_expert(x)
173+
return out
123174

124175

125176
class TransformerBlock(nn.Module):

examples/models/llama/model_args.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
from dataclasses import dataclass
33
from enum import Enum
44
from functools import partial
5-
from typing import Any, Dict, Optional
5+
from typing import Any, Dict, Literal, Optional
66

77
import torch.nn.functional as F
88

9+
MoEScoreFunc = Literal["sigmoid", "softmax", "softmax_all"]
10+
911

1012
class ActFn(Enum):
1113
SILU = "silu"
@@ -71,6 +73,11 @@ class ModelArgs:
7173
moe: bool = False # True to enable the MoE (Mixture of Experts)
7274
num_experts: int = 8 # Number of experts
7375
num_activated_experts: int = 2 # Number of experts to activate
76+
moe_hidden_dim: Optional[int] = None
77+
moe_shared_expert_hidden_dim: Optional[int] = None
78+
moe_use_expert_bias: bool = False
79+
moe_score_func: MoEScoreFunc = "softmax"
80+
moe_route_scale: float = 1.0
7481
attention_type: str = "mha" # Attention type, registered in attention.py
7582
use_q_gate: bool = (
7683
False # Use q-gated projection in attention (Qwen3.5 full attention)

0 commit comments

Comments
 (0)