MoE
可视化实验室
通过动画与交互深入理解 Mixture of Experts 核心机制。从路由到负载均衡,从稀疏激活到推理优化,把 MoE 黑箱拆成你能看见的零件。
MoE 架构概览 (Architecture)
混合专家模型用稀疏激活替代全连接:每个 token 只激活少数专家,以更少的计算量达到更大的模型容量。
说明
稠密模型中每个 token 经过所有参数。MoE 层将 FFN 替换为多个并行 FFN(专家),门控网络为每个 token 选择 Top-K 个专家。总参数量大但每次前向传播只用一小部分。
面试要点
- 稀疏激活:总参数 N,每次仅激活 N×(K/num_experts)
- DeepSeek-V3:256 专家,每次激活 8,总 671B 仅用 37B
- GLM-5.2:744B 参数 MoE,消费级 GPU 可推理
- 关键优势:参数容量 ↑↑,计算成本 ↑
class MoELayer(nn.Module):
def __init__(self, d_model, n_experts, top_k):
self.gate = nn.Linear(d_model, n_experts)
self.experts = nn.ModuleList([
FFN(d_model) for _ in range(n_experts)
])
self.top_k = top_k
def forward(self, x):
# 门控网络打分
logits = self.gate(x) # [B,S,E]
# Top-K 选择
topk_val, topk_idx = logits.topk(self.top_k, dim=-1)
# 加权求和
return dispatch(x, topk_idx, topk_val)
专家路由 (Expert Routing)
门控网络根据 token 的表示决定路由到哪些专家。路由质量直接决定 MoE 模型的性能。
说明
门控网络是一个线性层 G(x)=W_g·x,输出每个专家的分数。Softmax 归一化后选择 Top-K。不同 token 倾向于不同专家——这形成了隐式的专家分工。
面试要点
- 门控函数:G(x) = Softmax(W_g · x),简单线性层
- 专家特化:训练后专家会自然分工(语法/语义/代码等)
- 噪声门控:G(x) = Softmax(W·x + ε),ε~N(0,σ²) 促进探索
- 路由稳定性:是 MoE 训练的最大挑战
def gating(x, n_experts, top_k, noise=False):
# x: [batch, seq, d_model]
logits = W_gate @ x # [batch, seq, n_experts]
if noise:
logits += torch.randn_like(logits) * softplus(W_noise)
# Top-K 选择
weights, indices = logits.softmax(-1).topk(top_k)
weights = weights / weights.sum(dim=-1, keepdim=True)
return weights, indices
Top-K 选择 (Top-K Selection)
每个 token 只选择得分最高的 K 个专家。K 越大,模型质量越高但计算量也越大。
说明
门控网络输出所有专家的分数后,Top-K 选择保留最高的 K 个。选中专家的权重经归一化后用于加权求和。未选中的专家不参与计算,实现稀疏激活。
面试要点
- K=1:最稀疏,最快,但可能路由坍塌
- K=2:最常用,GPT-4 / Mixtral 标准配置
- K=8:DeepSeek-V3 选择,质量更高
- 权衡:K↑ → 质量↑ 速度↓ 通信↑
# 门控分数 → Top-K 选择
gate_scores = softmax(gate(x), dim=-1)
# [batch, seq, n_experts]
topk_weights, topk_indices = gate_scores.topk(
k=K, dim=-1
)
# 重新归一化选中的权重
topk_weights = topk_weights / topk_weights.sum(
dim=-1, keepdim=True
)
# 只计算选中的 K 个专家
for i in range(K):
expert_idx = topk_indices[..., i]
output += topk_weights[..., i] * expert(expert_idx, x)
负载均衡 (Load Balancing)
如果不加约束,门控网络可能总是把 token 路由到少数专家,导致其他专家闲置。辅助损失强制均衡分配。
说明
辅助损失 L_aux = α·n_experts·Σ(f_i·P_i),其中 f_i 是专家 i 收到的 token 比例,P_i 是平均门控概率。当分配均匀时 L_aux 最小。这个简单的损失项是 MoE 训练的关键。
面试要点
- 路由坍塌:不加约束时 80% token 去同一个专家
- 辅助损失:L_aux = α·N·Σ(f_i·P_i),α≈0.01
- Expert Capacity:每个专家设上限 capacity = C·avg
- DeepSeek 方案:无辅助损失的偏置调整
def load_balancing_loss(gate_probs, expert_mask):
# gate_probs: [tokens, n_experts] softmax probs
# expert_mask: [tokens, n_experts] 0/1 selected
N = gate_probs.size(1)
# f_i: 每个专家实际收到的比例
f = expert_mask.mean(dim=0)
# P_i: 每个专家的平均门控概率
P = gate_probs.mean(dim=0)
# 辅助损失
aux_loss = N * (f * P).sum()
return aux_loss
容量与溢出 (Capacity & Overflow)
每个专家有处理上限。当分配的 token 超过容量时,多余 token 被丢弃或传递给下一层。
说明
Expert Capacity = (tokens/Batch) × capacity_factor × top_k。capacity_factor 通常 1.0-1.5。超出容量的 token 被标记为 overflow,直接透传不经过专家处理。这是 MoE 训练中信息丢失的主要来源。
面试要点
- 容量因子:1.0 = 刚好,1.25 = 宽松,1.5 = 很少溢出
- Token 丢弃:溢出 token 直接透传,信息不经过 FFN
- 影响:溢出率高 → 模型质量下降
- 权衡:容量↑ → 显存↑ 但溢出↓
def dispatch_with_capacity(x, indices, weights, capacity):
# capacity = ceil(tokens * top_k / n_experts * factor)
expert_counts = zeros(n_experts)
overflow_count = 0
for token in x:
for expert in selected_experts(token):
if expert_counts[expert] < capacity:
dispatch(token, expert)
expert_counts[expert] += 1
else:
# 溢出:直接透传
overflow_count += 1
稀疏激活 (Sparse Activation)
MoE 的核心优势:用更大的总参数量获得更好的性能,但每次前向传播只激活一小部分参数。
说明
DeepSeek-V3 总参数 671B 但每次推理只激活 37B。这意味着模型的"知识容量"接近 671B 模型,但"计算成本"接近 37B 稠密模型。稀疏激活让大模型在消费级硬件上可运行。
面试要点
- 激活比:激活参数/总参数,DeepSeek-V3 ≈ 5.5%
- 等效稠密模型:37B 激活 ≈ 70B 稠密模型性能
- 显存挑战:需加载全部参数,即使不激活
- 推理优化:按需加载专家(Expert Offloading)
# DeepSeek-V3 示例
total_params = 671e9 # 671B
active_params = 37e9 # 37B
n_experts = 256
top_k = 8
activation_ratio = active_params / total_params
# ≈ 5.5%
# 每个专家平均参数
per_expert = total_params / n_experts
# ≈ 2.6B per expert
# 每次推理实际计算
flops = top_k * per_expert
# ≈ 21B FLOPs (远小于 671B)
MoE 训练 (Training)
MoE 训练比稠密模型更难:需要处理路由不稳定、负载不均、专家利用率等问题。
说明
MoE 训练面临独特挑战:路由坍塌(所有 token 选同一专家)、专家利用率不均、训练不稳定。解决方案包括辅助损失、专家容量、噪声注入、初始化策略等。DeepSeek 提出了无辅助损失的偏置调整方案。
面试要点
- 路由坍塌:训练初期门控网络偏向少数专家
- 辅助损失:α=0.01,强制均衡分配
- 初始化:门控网络需较小初始化方差
- 数据并行:Expert Parallelism 需 All-to-All 通信
def train_step(model, batch, aux_weight=0.01):
# 前向传播(含门控路由)
output, gate_probs, expert_mask = model(batch)
# 主任务损失
lm_loss = cross_entropy(output, targets)
# 负载均衡辅助损失
aux_loss = load_balancing_loss(
gate_probs, expert_mask
)
# 总损失
total_loss = lm_loss + aux_weight * aux_loss
total_loss.backward()
optimizer.step()
return lm_loss, aux_loss
推理优化 (Inference)
MoE 推理的核心挑战:总参数巨大但每次只用一小部分。专家卸载和按需加载是关键优化。
说明
MoE 推理时,专家可以卸载到 CPU/磁盘,只在需要时加载到 GPU。DeepSeek-V3 的 256 专家中,每次只需 8 个在 GPU 上。通过预测下一层路由,可以提前预取专家,实现计算与 IO 的重叠。
面试要点
- 专家卸载:GPU 存热专家,CPU/SSD 存冷专家
- 预取:预测下一层路由,提前加载专家
- 量化:FP8/INT4 量化减少专家大小
- 批处理:合并同专家请求提升吞吐
class ExpertPool:
def __init__(self, experts, gpu_capacity):
self.gpu = {} # GPU 上的热专家
self.cpu = {} # CPU 上的冷专家
self.lru = OrderedDict()
def get(self, expert_id):
if expert_id in self.gpu:
return self.gpu[expert_id]
# 从 CPU 加载到 GPU
if len(self.gpu) >= capacity:
evict_lru()
self.gpu[expert_id] = self.cpu[expert_id].cuda()
return self.gpu[expert_id]