Skip to content

Commit ec70fbe

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 ec70fbe

2 files changed

Lines changed: 73 additions & 13 deletions

File tree

examples/models/llama/llama_transformer.py

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,12 @@ 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
85+
if args.moe_hidden_dim is not None
86+
else args.hidden_dim
87+
)
8488
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`
8789
multiple_of = args.multiple_of
8890
hidden_dim = 4 * self.dim
8991
hidden_dim = int(2 * hidden_dim / 3)
@@ -95,9 +97,9 @@ def __init__(self, args: ModelArgs):
9597
self.num_experts = args.num_experts
9698

9799
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]
100+
w1_weights = self.w1[expert_indices].transpose(-1, -2) # [T, A, D, hidden]
101+
w3_weights = self.w3[expert_indices].transpose(-1, -2) # [T, A, D, hidden]
102+
w2_weights = self.w2[expert_indices] # [T, A, hidden, D]
101103
x1 = F.silu(torch.einsum("ti,taio -> tao", x, w1_weights))
102104
x3 = torch.einsum("ti, taio -> tao", x, w3_weights)
103105
expert_outs = torch.einsum("tao, taoi -> tai", (x1 * x3), w2_weights)
@@ -110,16 +112,67 @@ def __init__(self, config) -> None:
110112
self.gate = nn.Linear(config.dim, config.num_experts, bias=False)
111113
self.cond_ffn = ConditionalFeedForward(config)
112114
self.dim = config.dim
115+
self.num_activated_experts = config.num_activated_experts
116+
self.score_func = config.moe_score_func
117+
self.route_scale = config.moe_route_scale
118+
if self.score_func not in {"sigmoid", "softmax", "softmax_all"}:
119+
raise ValueError(f"Unsupported MoE routing score function: {self.score_func}")
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+
self.gate.weight.new_zeros(config.num_experts)
132+
if config.moe_use_expert_bias
133+
else None,
134+
)
135+
if config.moe_shared_expert_hidden_dim is not None:
136+
self.shared_expert = FeedForward(
137+
dim=config.dim, hidden_dim=config.moe_shared_expert_hidden_dim
138+
)
139+
else:
140+
self.shared_expert = None
141+
142+
def _route(self, x: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
143+
scores = self.gate(x) # [T, E]
144+
if self.score_func == "sigmoid":
145+
scores = scores.sigmoid()
146+
elif self.score_func == "softmax_all":
147+
scores = scores.softmax(dim=-1)
148+
scores_for_topk = (
149+
(scores + self.expert_bias)
150+
if self.expert_bias is not None
151+
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 = (
162+
expert_weights / normalizer * self.route_scale
163+
)
164+
elif self.score_func == "softmax":
165+
expert_weights = expert_weights.softmax(dim=-1)
166+
return expert_weights, expert_indices
113167

114168
def forward(self, x: torch.Tensor) -> torch.Tensor:
115169
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]
170+
expert_weights, expert_indices = self._route(x)
121171
expert_outs = self.cond_ffn(x, expert_indices)
122-
return torch.einsum("tai,ta -> ti", expert_outs, expert_weights)
172+
out = torch.einsum("tai,ta -> ti", expert_outs, expert_weights)
173+
if self.shared_expert is not None:
174+
out = out + self.shared_expert(x)
175+
return out
123176

124177

125178
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)