- Published on
Mixture of Experts (MoE) 架构论文深度解析:从 GShard 到 DeepSeek-MoE
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 引言
- MoE 架构基础:Sparse Gating 与 Expert 选择
- GShard:大规模分布式 MoE 训练
- Switch Transformer:单一 Expert 路由的效率
- Mixtral 8x7B:开源 MoE 的创新
- DeepSeek-MoE:Fine-grained Expert Segmentation
- MoE 模型比较分析表
- 路由策略与负载均衡
- MoE 训练中的注意事项与不稳定性解决
- 失败案例与调试
- 结语
- 参考资料

引言
大规模语言模型(LLM)的性能提升,传统上一直与参数量的增加直接挂钩。然而,Dense 模型中参数翻倍,训练和推理的计算量(FLOPs)也会随之成比例增加。Mixture of Experts(MoE) 架构正是突破这一局限的核心策略。它让模型的总参数量大幅增加,但对每个输入 token,只激活全部参数中的少数专家(Expert),从而将计算成本维持在一定水平。
MoE 的最初构想可以追溯到 1991 年 Jacobs 等人的论文,但直到 2017 年 Shazeer 等人在 "Outrageously Large Neural Networks" 中提出 Sparsely-Gated MoE 之后,它才开始具备现代形态。此后,Google 的 GShard(2020)实现了 600B 参数规模的分布式 MoE 训练,Switch Transformer(2021)用单一专家路由把效率推向极致。Mistral AI 的 Mixtral 8x7B(2024)为开源 MoE 模型树立了新标准,DeepSeek 的 DeepSeek-MoE(2024)则通过细粒度专家分割(Fine-grained Expert Segmentation)与共享专家隔离(Shared Expert Isolation),将专家特化推向极致。
本文将分析各篇论文的核心贡献,并结合代码比较路由策略、负载均衡与训练稳定性技巧,同时讨论 MoE 训练中实际会遇到的不稳定性问题与调试策略。
MoE 架构基础:Sparse Gating 与 Expert 选择
门控网络的原理
MoE 层的核心是 门控网络(Gating Network)。给定输入 token x,门控网络会输出针对 N 个专家各自的概率分布。
这里 W_g 是可学习的门控权重矩阵,噪声项则在训练初期帮助专家探索。MoE 层的最终输出,是由所选 Top-k 专家的加权和计算得出的。
E_i(x) 是第 i 个专家网络(通常为 FFN)的输出。
import torch
import torch.nn as nn
import torch.nn.functional as F
class BasicMoEGating(nn.Module):
"""Sparsely-Gated MoE 的基础门控网络"""
def __init__(self, d_model: int, num_experts: int, top_k: int = 2,
noise_std: float = 1.0):
super().__init__()
self.top_k = top_k
self.noise_std = noise_std
self.gate = nn.Linear(d_model, num_experts, bias=False)
self.noise_gate = nn.Linear(d_model, num_experts, bias=False)
def forward(self, x: torch.Tensor):
# x: (batch_size, seq_len, d_model)
logits = self.gate(x)
# 训练时加入噪声(促进探索)
if self.training:
noise = F.softplus(self.noise_gate(x))
logits = logits + torch.randn_like(logits) * noise * self.noise_std
# 选择 Top-k 专家
top_k_logits, top_k_indices = torch.topk(logits, self.top_k, dim=-1)
top_k_gates = F.softmax(top_k_logits, dim=-1)
return top_k_gates, top_k_indices
Sparse Activation 与计算效率
Dense 模型中,所有参数在每个 token 上都会被激活,而 Sparse MoE 中只有 Top-k 专家会被激活。由于 N 个专家中只有 k 个参与运算,模型容量可以扩大 N 倍,而计算量则维持在 k 倍的水平。
举例来说,Mixtral 8x7B 在 8 个专家中激活 2 个。总参数量约为 47B,但激活参数量约为 13B,能以与 Dense 13B 模型相近的推理成本,取得高得多的性能。
Capacity Factor 与 token 丢弃
每个专家能处理的 token 数上限,由 容量因子(Capacity Factor, CF) 来控制。在理想的均匀分配下,每个专家会处理 T/N 个 token,乘以 CF 后得到实际容量。
CF 为 1.0 时,假设是完全均匀分配。实际中常用 1.25 到 1.5 之间的值。CF 太低,token 会被丢弃(溢出)导致信息损失;CF 太高,则会浪费内存。
class MoELayer(nn.Module):
"""基础 Sparse MoE 层(含 Capacity Factor)"""
def __init__(self, d_model: int, d_ff: int, num_experts: int,
top_k: int = 2, capacity_factor: float = 1.25):
super().__init__()
self.num_experts = num_experts
self.top_k = top_k
self.capacity_factor = capacity_factor
self.gate = BasicMoEGating(d_model, num_experts, top_k)
# 每个专家都是独立的 FFN
self.experts = nn.ModuleList([
nn.Sequential(
nn.Linear(d_model, d_ff),
nn.GELU(),
nn.Linear(d_ff, d_model)
) for _ in range(num_experts)
])
def forward(self, x: torch.Tensor):
B, T, D = x.shape
gates, indices = self.gate(x)
expert_capacity = int(self.capacity_factor * T / self.num_experts)
output = torch.zeros_like(x)
tokens_dropped = 0
for i in range(self.num_experts):
# 分配给第 i 个专家的 token 掩码
expert_mask = (indices == i).any(dim=-1) # (B, T)
# 丢弃超出容量的 token
token_count = expert_mask.sum(dim=-1)
for b in range(B):
if token_count[b] > expert_capacity:
overflow = token_count[b] - expert_capacity
tokens_dropped += overflow.item()
if not expert_mask.any():
continue
expert_input = x[expert_mask]
expert_output = self.experts[i](expert_input)
gate_values = gates[indices == i]
output[expert_mask] += gate_values.unsqueeze(-1) * expert_output
return output, tokens_dropped
GShard:大规模分布式 MoE 训练
论文概述
GShard(Lepikhin 等,2020)是 Google 发表的论文,利用 MoE 在 2048 个 TPU v3 上,用 4 天时间训练出了 600B 参数规模的多语言机器翻译模型。其核心贡献有三点。
- Top-2 门控与随机路由:第一个专家以 Top-1 方式确定性地选出,第二个专家则按门控概率成比例地随机选出
- 本地分组分发(Local Group Dispatching):把 token 分成 G 个本地组,并行独立处理,从而降低通信成本
- 自动分片(Auto-sharding):借助 XLA 编译器的自动分区,抽象掉分布式训练的复杂性
GShard 的随机路由
GShard 的路由策略颇为独特。Top-1 专家以确定性方式选出门控值最高的专家,而第二个专家则按其余专家的门控概率成比例地随机采样。这种方式让利用率较低的专家也有机会被分配到 token,从而促进专家间的负载均衡与探索(exploration)。
class GShardRouter(nn.Module):
"""GShard Top-2 路由器(含随机路由)"""
def __init__(self, d_model: int, num_experts: int,
capacity_factor: float = 2.0):
super().__init__()
self.num_experts = num_experts
self.capacity_factor = capacity_factor
self.gate = nn.Linear(d_model, num_experts, bias=False)
def forward(self, x: torch.Tensor):
# x: (batch_size, seq_len, d_model)
B, T, D = x.shape
logits = self.gate(x)
probs = F.softmax(logits, dim=-1)
# Top-1:确定性选择
top1_probs, top1_indices = probs.max(dim=-1)
# Top-2:概率性选择(从其余专家中采样)
mask = torch.zeros_like(probs)
mask.scatter_(-1, top1_indices.unsqueeze(-1), 1.0)
remaining_probs = probs * (1 - mask)
remaining_probs = remaining_probs / (remaining_probs.sum(dim=-1, keepdim=True) + 1e-8)
# 按概率采样第二个专家
top2_indices = torch.multinomial(
remaining_probs.view(-1, self.num_experts), 1
).view(B, T)
top2_probs = probs.gather(-1, top2_indices.unsqueeze(-1)).squeeze(-1)
# 重新归一化门控值
total = top1_probs + top2_probs
top1_gates = top1_probs / total
top2_gates = top2_probs / total
return (top1_gates, top1_indices, top2_gates, top2_indices)
本地分组分发
GShard 把整个 batch 的 token 均匀切分为 G 个本地组。每组包含 S = N/G 个 token,各组内独立完成专家分发。这样可以把门控运算加速 O(G) 倍,每个专家的本地容量则设为 C = 2N/(G x E)。
Switch Transformer:单一 Expert 路由的效率
论文概述
Switch Transformer(Fedus 等,2021)是 Google Brain 发表的论文,颠覆了关于 MoE 路由的常识。此前普遍认为必须激活 Top-2 以上的专家训练才会稳定,而 Switch Transformer 证明了仅用 Top-1 单一专家路由 也能取得优异性能。
Top-1 路由的三大优势
- 减少路由器运算:只需选择一个专家,门控运算随之简化
- 提升 batch 效率:由于每个 token 只分配给一个专家,在相同 Capacity Factor 下可以把 batch 大小翻倍
- 降低通信成本:分布式训练时,token 只需发送到一个专家所在设备
Switch Transformer 的路由器实现
class SwitchRouter(nn.Module):
"""Switch Transformer 路由器(Top-1 单一专家)"""
def __init__(self, d_model: int, num_experts: int,
capacity_factor: float = 1.25):
super().__init__()
self.num_experts = num_experts
self.capacity_factor = capacity_factor
self.gate = nn.Linear(d_model, num_experts, bias=False)
def forward(self, x: torch.Tensor):
B, T, D = x.shape
logits = self.gate(x)
probs = F.softmax(logits, dim=-1)
# Top-1 选择(单一专家)
gate_values, expert_indices = probs.max(dim=-1) # (B, T)
# 基于 Capacity 的 token 丢弃
capacity = int(self.capacity_factor * T / self.num_experts)
# 追踪各专家分配到的 token 数
expert_counts = torch.zeros(B, self.num_experts, device=x.device)
drop_mask = torch.zeros(B, T, dtype=torch.bool, device=x.device)
for t in range(T):
for b in range(B):
eid = expert_indices[b, t].item()
if expert_counts[b, eid] >= capacity:
drop_mask[b, t] = True # 丢弃超出容量的 token
else:
expert_counts[b, eid] += 1
# 把被丢弃 token 的门控值设为 0
gate_values = gate_values * (~drop_mask).float()
return gate_values, expert_indices, drop_mask
Simplified Load Balancing Loss
Switch Transformer 提出了一种简化的负载均衡辅助损失(auxiliary loss)。
这里 N 是专家数,f_i 是分配给第 i 个专家的 token 比例,P_i 是第 i 个专家的平均路由器概率,alpha 是超参数(论文中推荐为 0.01)。该损失会促使 f_i 和 P_i 都趋近于 1/N,也就是让专家们均匀地获得 token。
def switch_load_balancing_loss(router_probs: torch.Tensor,
expert_indices: torch.Tensor,
num_experts: int,
alpha: float = 0.01) -> torch.Tensor:
"""Switch Transformer 的 Simplified Load Balancing Loss
Args:
router_probs: 路由器概率 (B, T, num_experts)
expert_indices: 所选专家索引 (B, T)
num_experts: 专家数
alpha: 均衡系数
"""
# f_i:分配给各专家的 token 比例
one_hot = F.one_hot(expert_indices, num_experts).float()
f = one_hot.mean(dim=[0, 1]) # (num_experts,)
# P_i:各专家的平均路由器概率
P = router_probs.mean(dim=[0, 1]) # (num_experts,)
# 均衡损失:alpha * N * sum(f_i * P_i)
loss = alpha * num_experts * (f * P).sum()
return loss
bfloat16 混合精度训练
Switch Transformer 的另一项重要贡献,是用 bfloat16 混合精度训练大规模 Sparse 模型的技巧。由于路由器的 softmax 运算在数值上可能不稳定,因此只让路由器部分保持 float32,其余部分用 bfloat16 处理。这样既节省了内存,又保证了训练稳定性。
Mixtral 8x7B:开源 MoE 的创新
论文概述
Mixtral 8x7B(Jiang 等,2024)是 Mistral AI 发布的 Sparse MoE 语言模型。它基于与 Mistral 7B 相同的架构,但把每层的 FFN(前馈网络)替换成 8 个专家,并采用 Top-2 路由。
架构细节
- 总参数量:约 46.7B(8 个专家 x 7B FFN + 共享 Attention)
- 激活参数量:约 12.9B(每个 token 激活 2 个专家)
- 上下文长度:32K token
- 注意力机制:Grouped Query Attention(GQA)+ Sliding Window Attention(SWA)
Mixtral 的 Top-2 路由
与 GShard 不同,Mixtral 的 Top-2 路由是确定性的。对每个 token,选出门控值最高的 2 个专家,再用归一化后的门控值对这两个专家的输出做加权和。
专家特化模式分析
Mixtral 论文中一个有趣的发现是专家特化模式。研究者分析了 The Pile 验证集多个领域中各专家的激活频率,结果显示专家的特化更多依据 句法模式(syntactic pattern),而非主题(topic)。特定专家并不是只专精于代码、数学或自然语言中的某一种,而是根据 token 序列的结构特征被反复选中。
基准测试性能
Mixtral 8x7B 仅凭 13B 激活参数,就在大多数基准测试中达到了与 LLaMA 2 70B 相当甚至更优的性能,在数学与代码生成基准测试中尤其突出,在部分领域甚至达到 GPT-3.5 的水平。
DeepSeek-MoE:Fine-grained Expert Segmentation
论文概述
DeepSeek-MoE(Dai 等,2024)为了在 MoE 架构中把专家特化推向极致,提出了两项核心策略。
- Fine-grained Expert Segmentation:把专家细分为更小的单元,使灵活组合成为可能
- Shared Expert Isolation:把部分专家隔离为共享专家,集中承担公共知识,减少路由专家之间的重复
Fine-grained Expert Segmentation 的原理
如果说传统 MoE 是从 N 个专家中激活 K 个,那么 DeepSeek-MoE 则在相同参数预算下,把专家数分割为 m 倍(mN 个),并激活 m 倍数量的专家(mK 个)。具体做法是把每个专家 FFN 的中间维度(intermediate hidden dimension)除以 m。
总参数量和计算量保持不变,但由于可以实现更精细的专家组合,知识得以被更精确地分解并学习,每个专家也能达到更高水平的特化。
Shared Expert Isolation
DeepSeek-MoE 把部分专家指定为 共享专家(Shared Expert),使其对所有 token 始终保持激活。共享专家负责学习与上下文无关、普遍需要的知识(通用语言结构、基本语法等),其余路由专家则可以专注于各自的专业领域。这样一来,路由专家之间的知识重复得以减少。
class DeepSeekMoELayer(nn.Module):
"""DeepSeek-MoE 层(Fine-grained Segmentation + Shared Expert)"""
def __init__(self, d_model: int, d_ff: int, num_routed_experts: int,
num_shared_experts: int, top_k: int, segmentation_factor: int = 4):
super().__init__()
self.num_routed = num_routed_experts
self.num_shared = num_shared_experts
self.top_k = top_k
# Fine-grained:把 FFN 中间维度除以 segmentation_factor
fine_d_ff = d_ff // segmentation_factor
# 共享专家(始终激活)
self.shared_experts = nn.ModuleList([
nn.Sequential(
nn.Linear(d_model, d_ff),
nn.SiLU(),
nn.Linear(d_ff, d_model)
) for _ in range(num_shared_experts)
])
# 路由专家(细分后的尺寸)
self.routed_experts = nn.ModuleList([
nn.Sequential(
nn.Linear(d_model, fine_d_ff),
nn.SiLU(),
nn.Linear(fine_d_ff, d_model)
) for _ in range(num_routed_experts)
])
# 门控网络(用于路由专家)
self.gate = nn.Linear(d_model, num_routed_experts, bias=False)
def forward(self, x: torch.Tensor):
B, T, D = x.shape
# 1. 共享专家输出(应用于所有 token)
shared_output = sum(expert(x) for expert in self.shared_experts)
# 2. 选择路由专家并计算输出
logits = self.gate(x)
probs = F.softmax(logits, dim=-1)
top_k_probs, top_k_indices = torch.topk(probs, self.top_k, dim=-1)
top_k_gates = top_k_probs / top_k_probs.sum(dim=-1, keepdim=True)
routed_output = torch.zeros_like(x)
for k in range(self.top_k):
expert_idx = top_k_indices[:, :, k] # (B, T)
gate_val = top_k_gates[:, :, k] # (B, T)
for i in range(self.num_routed):
mask = (expert_idx == i)
if mask.any():
inp = x[mask]
out = self.routed_experts[i](inp)
routed_output[mask] += gate_val[mask].unsqueeze(-1) * out
return shared_output + routed_output
性能比较
DeepSeek-MoE 2B 用比 GShard 2.9B 少 1.5 倍的专家参数,达到了相当的性能。DeepSeek-MoE 16B 则以约 40% 的计算量,达到了与 LLaMA 2 7B 相当的性能,从实证上验证了 Fine-grained Segmentation 的效果。
MoE 模型比较分析表
Dense vs MoE 比较
| 特性 | Dense 模型 | Sparse MoE 模型 |
|---|---|---|
| 激活参数 | 全部参数 | 仅 Top-k 专家参数 |
| 计算量(FLOPs) | 与参数量成正比 | 与激活参数量成正比 |
| 内存占用 | 与参数量大小成正比 | 需要全部参数占用内存(推理时) |
| 训练稳定性 | 相对稳定 | 需要负载均衡,可能不稳定 |
| 分布式训练 | 数据/模型并行 | 需额外的 Expert 并行 |
| 推理延迟 | 可预测 | 存在路由开销 |
| 扩展效率 | 线性增长 | 亚线性计算增长,效率更高 |
MoE 变体模型比较
| 特性 | GShard(2020) | Switch Transformer(2021) | Mixtral 8x7B(2024) | DeepSeek-MoE(2024) |
|---|---|---|---|---|
| 路由方式 | Top-2(随机 2nd) | Top-1 | Top-2(确定性) | Top-K(细分) |
| 专家数 | 2048(分布式) | 128+ | 8 per layer | 64 routed + 2 shared |
| 激活专家数 | 2 | 1 | 2 | 6 routed + 2 shared |
| 总参数量 | 600B | 1.6T(最大) | 46.7B | 16.4B / 145B |
| 激活参数量 | - | - | 12.9B | 2.8B / 22.2B |
| 专家细分 | 标准 | 标准 | 标准 | Fine-grained |
| 共享专家 | 无 | 无 | 无 | 有 |
| 负载均衡 | Auxiliary loss + CF | Simplified loss | 隐式 | Auxiliary loss |
| 训练精度 | float32 | bfloat16 混合 | bfloat16 | bfloat16 |
| 主要贡献 | 大规模分布式 MoE | Top-1 路由效率 | 开源 MoE LLM | 专家特化极致化 |
路由策略与负载均衡
路由策略谱系
MoE 路由策略大致可分为三种范式。
1. Token-Choice Routing:由 token 选择专家的方式(GShard、Switch、Mixtral)
最常见的方式,把每个 token 的隐藏状态输入门控网络,选出 Top-k 专家。直观且实现简单,但可能出现负载不均衡。
2. Expert-Choice Routing:由专家选择 token 的方式(Expert Choice,2022)
每个专家自行挑选要处理的 token。由于专家容量固定,能保证完美的负载均衡,但特定 token 可能不被任何专家选中,也可能被多个专家重复选中。
3. Shared + Routed Hybrid:共享专家与路由专家并用的方式(DeepSeek-MoE)
公共知识由共享专家处理,专业知识由路由专家负责。路由专家之间的重复得以减少,专家特化随之提升。
负载均衡的重要性
MoE 训练中最常见的问题之一是 专家崩溃(Expert Collapse)。训练初期,如果某个专家的表现哪怕只是略优于其他专家,路由器就会给它分配更多 token,这又会进一步强化该专家的训练,形成 positive feedback loop。最终会陷入只有少数专家被使用、其余专家得不到训练的状态。
def compute_expert_utilization(expert_indices: torch.Tensor,
num_experts: int) -> dict:
"""专家利用率监控工具
Args:
expert_indices: 所选专家索引 (B, T) 或 (B, T, K)
num_experts: 专家总数
Returns:
利用率指标字典
"""
flat = expert_indices.flatten()
counts = torch.bincount(flat, minlength=num_experts).float()
total = flat.numel()
# 各专家的 token 比例
fractions = counts / total
# 利用率指标
max_fraction = fractions.max().item()
min_fraction = fractions.min().item()
ideal_fraction = 1.0 / num_experts
# 不均衡比例(越接近 1 越均匀)
balance_ratio = min_fraction / (max_fraction + 1e-8)
# 非激活专家数
dead_experts = (counts == 0).sum().item()
return {
"balance_ratio": balance_ratio,
"max_load": max_fraction,
"min_load": min_fraction,
"ideal_load": ideal_fraction,
"dead_experts": dead_experts,
"load_std": fractions.std().item(),
}
Auxiliary Loss 的效果与局限
Auxiliary loss(辅助损失)是防止专家崩溃最常用的技巧。但 alpha 超参数的设置颇为棘手。
- alpha 过小时:均衡效果不足,存在专家崩溃风险
- alpha 过大时:路由质量下降,因为会强行把 token 均匀分配给所有专家,导致专家特化被削弱
- Switch Transformer 推荐值:alpha = 0.01
- 实务范围:在 0.001 ~ 0.1 之间摸索
MoE 训练中的注意事项与不稳定性解决
训练不稳定性的原因
相比 Dense 模型,MoE 模型的训练往往更不稳定,主要原因如下。
1. 路由器的离散决策(Discrete Decision)
Top-k 选择本质上是不可微的离散运算。虽然通过 softmax 使其近似可微,但门控值的微小变动仍可能剧烈改变专家选择。这是训练不稳定性的根本原因之一。
2. 专家间的非对称训练
热门专家会获得更多梯度、训练得更快,而冷门专家则因梯度不足而停滞。这种不对称会随着时间推移而加剧。
3. 路由器 softmax 的数值不稳定性
在大规模 logit 值下,softmax 运算在数值上可能不稳定,在 float16 或 bfloat16 精度下这一问题会更加严重。
稳定化技巧
Router z-loss:Switch Transformer 提出的 z-loss,通过抑制路由器 logit 的规模来提升数值稳定性。
def router_z_loss(router_logits: torch.Tensor) -> torch.Tensor:
"""Router z-loss:通过抑制路由器 logit 的规模来提升稳定性
Args:
router_logits: 路由器的原始 logit (B, T, num_experts)
"""
# log-sum-exp 的平方
log_z = torch.logsumexp(router_logits, dim=-1) # (B, T)
z_loss = (log_z ** 2).mean()
return z_loss
class StableMoETrainer:
"""用于稳定 MoE 训练的 Trainer 示例"""
def __init__(self, model, optimizer, alpha_balance=0.01,
alpha_z=0.001):
self.model = model
self.optimizer = optimizer
self.alpha_balance = alpha_balance
self.alpha_z = alpha_z
def train_step(self, input_ids, labels):
self.optimizer.zero_grad()
# Forward pass
logits, router_logits, expert_indices = self.model(input_ids)
# Task loss
task_loss = F.cross_entropy(
logits.view(-1, logits.size(-1)), labels.view(-1)
)
# Load balancing loss
router_probs = F.softmax(router_logits, dim=-1)
balance_loss = switch_load_balancing_loss(
router_probs, expert_indices, self.model.num_experts
)
# Router z-loss
z_loss = router_z_loss(router_logits)
# Total loss
total_loss = task_loss + self.alpha_balance * balance_loss + self.alpha_z * z_loss
total_loss.backward()
# 梯度裁剪(在 MoE 训练中尤为重要)
torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=1.0)
self.optimizer.step()
return {
"task_loss": task_loss.item(),
"balance_loss": balance_loss.item(),
"z_loss": z_loss.item(),
"total_loss": total_loss.item(),
}
混合精度训练策略
Switch Transformer 揭示的一个重要经验是:在 MoE 模型中,路由器必须保持高精度(float32)。路由器的 softmax 运算若在 bfloat16 下进行,数值误差会累积,可能导致训练发散。
推荐策略如下。
- 路由器门控:保持 float32
- 专家 FFN:可用 bfloat16
- Attention 层:可用 bfloat16
- 损失计算:保持 float32
失败案例与调试
案例 1:专家崩溃(Expert Collapse)
症状:训练过程中,特定 1~2 个专家处理了超过 90% 的 token,其余专家的门控概率趋近于 0。
原因:Auxiliary loss 的 alpha 值过小,或学习率过高,导致路由器无法摆脱初始偏置。
解决方法:
- 尝试把 alpha 值从 0.01 提高到 0.1
- 路由器权重不用 Xavier 初始化,改用更小的标准差(0.01)初始化
- 加入 Jitter noise 以强制探索
- 每一步都监控专家利用率,以便及早发现
案例 2:因 token 丢弃导致的性能下降
症状:在 Capacity Factor 较低的情况下,有超过 10% 的 token 被丢弃,导致 perplexity 无法改善。
原因:CF 过低,或 token 过度集中在特定专家上。
解决方法:
- 把 CF 从 1.0 提高到 1.5(在内存允许范围内)
- 强化 Auxiliary loss,使 token 分布更均匀
- 改变 Token dropping 策略:从基于「position」改为基于「probability」,只丢弃低概率 token
案例 3:分布式训练中的 All-to-All 通信瓶颈
症状:增加专家数量后,训练吞吐量(throughput)反而下降。
原因:在 Expert Parallelism 中,All-to-All 通信耗时超过了计算耗时。
解决方法:
- 缩小 Expert Parallelism 组的规模,增加 Data Parallelism
- 增大每个专家的 token batch 大小,改善计算与通信的比例
- 应用 GShard 的本地分组分发,限制通信范围
案例 4:推理时的内存暴涨
症状:MoE 模型推理时,所需 GPU 内存比 Dense 模型多出 N 倍以上。
原因:Sparse MoE 无论训练还是推理,都需要把全部专家参数加载进内存。虽然激活参数不多,但全部参数都常驻内存。
解决方法:
- Expert Offloading:把非激活专家移到 CPU/NVMe,按需加载
- Expert Quantization:把非激活专家量化为 INT8/INT4
- Expert Pruning:移除利用率低的专家,并重新训练路由器
调试清单
MoE 训练中必须监控的指标如下。
- 各专家的 token 分配比例:应保持均匀(理想情况下为 1/N)
- 非激活专家数:应为 0
- token 丢弃率:建议维持在 5% 以下
- 路由器熵:过低会导致专家崩溃,过高则会导致随机路由
- Auxiliary loss 趋势:应单调下降
- 各专家的梯度范数:专家间偏差过大意味着训练不均衡
结语
MoE 架构已经稳固确立为 LLM 扩展的核心策略。GShard 证明了大规模分布式 MoE 训练的可行性,Switch Transformer 验证了单一专家路由的效率。Mixtral 8x7B 为开源社区提供了实用的 MoE 模型,DeepSeek-MoE 则通过 Fine-grained Expert Segmentation 与 Shared Expert Isolation,把专家特化推向极致。
各篇论文的核心经验总结如下。
- GShard:借助随机路由与本地分组分发,在数千台设备上训练 MoE 成为可能
- Switch Transformer:Top-1 路由可以比 Top-2 更简单,同时依然高效;路由器的数值稳定性是关键
- Mixtral 8x7B:即使只用少量专家(8 个),也能构建出实用的 MoE LLM
- DeepSeek-MoE:把专家细分并隔离共享专家,可以在相同计算预算下取得更高性能
MoE 研究仍在持续演进。DeepSeek-V2 中出现了与 Multi-head Latent Attention(MLA)结合的 MoE 架构,DeepSeek-V3 则以 671B 参数中仅激活 37B 的结构,开辟了效率的新境界。未来,路由策略的改进、训练稳定性的提升、推理优化等方向预计仍将持续发展。
参考资料
- GShard: Scaling Giant Models with Conditional Computation and Automatic Sharding (arXiv:2006.16668)
- Switch Transformers: Scaling to Trillion Parameter Models with Simple and Efficient Sparsity (arXiv:2101.03961)
- Mixtral of Experts (arXiv:2401.04088)
- DeepSeekMoE: Towards Ultimate Expert Specialization in Mixture-of-Experts Language Models (arXiv:2401.06066)
- Mixture-of-Experts with Expert Choice Routing (arXiv:2202.09368)
- A Survey on Mixture of Experts (arXiv:2407.06204)
- Hugging Face Blog: Mixture of Experts Explained
- DeepSeek-V2: A Strong, Economical, and Efficient Mixture-of-Experts Language Model (arXiv:2405.04434)