27届大模型面试准备(二十一):MoE 架构全攻略------稀疏激活、路由、负载均衡与专家并行
上一篇《评测体系全攻略》收尾时说过,A 系列前二十篇走完了"架构---训练---对齐---推理---量化---评测"的完整链路,接下来转向更前沿的主题。这一篇是第二十一篇,讲混合专家(Mixture of Experts,MoE)。为什么先讲它?因为 2024 年之后所有你叫得出名字的开源旗舰模型------Mixtral、DeepSeek-V2/V3、Qwen2-MoE、Llama 4、Kimi K2------全部是 MoE 架构。面试里问"你了解 MoE 吗",大部分人能答出"稀疏激活、只激活部分专家",然后就没了。而真正的区分度在后面:路由怎么设计、负载不均衡怎么办、专家并行的 All-to-All 通信开销有多大、为什么 MoE 训练容易崩、为什么 MoE 推理的显存和吞吐特性和稠密模型完全不同。本文按"为什么要 MoE → MoE 层的结构 → 路由算法演进 → 负载均衡 → 细粒度与共享专家 → 专家并行与通信 → 推理部署的坑 → MoE 的固有缺陷"展开,结尾给面试速答和高频追问清单。
一、为什么需要 MoE:Scaling Law 撞上成本墙
1.1 稠密模型的困境
Scaling Law 告诉我们模型效果随参数量增长,但稠密(Dense)模型有一个残酷的性质:参数量翻倍,训练算力翻倍,推理算力也翻倍。每一个 token 的前向计算都要走过全部参数。
设模型参数量为 N,训练 token 数为 D,稠密模型的训练 FLOPs 约为 6ND,推理时每 token 前向约 2N。这意味着:
- 想要 400B 的效果,就得付 400B 的推理成本
- 而推理成本是乘以请求量的,训练是一次性的,推理是永久性的
于是问题变成:能不能让参数量涨、但每个 token 实际用到的参数不涨?
1.2 MoE 的核心思想:解耦总参数与激活参数
稠密模型 vs MoE 的计算路径对比
Dense (70B) MoE (总 400B / 激活 40B)
token token
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Attention │ │ Attention │ (通常不做 MoE)
└──────┬───────┘ └──────┬───────┘
▼ ▼
┌──────────────┐ ┌─────────────────────────┐
│ │ │ Router (gating) │
│ FFN 全部 │ │ 选 Top-2 / 64 专家 │
│ 参数参与 │ └───┬─────────────────┬───┘
│ 计算 │ │ 只激活 2 个 │
│ │ ┌───▼──┐ ┌────┐ ┌───▼──┐
└──────┬───────┘ │ E1 ✓ │ │ E2 │ │ E3 ✓ │ ...
▼ └───┬──┘ └────┘ └───┬──┘
输出 └────加权求和────┘
▼
输出
参数量 = 计算量 参数量 >> 计算量
推理每 token 走 70B 推理每 token 只走 40B
显存 70B 显存仍需 400B(全部专家要驻留)
最关键的一句话总结 :MoE 用显存 换算力。参数全部要放进显存,但每个 token 只用其中一小部分做计算。这句话是回答后面所有推理部署问题的钥匙。
1.3 主流 MoE 模型参数对照
| 模型 | 总参数 | 激活参数 | 专家数 | 每 token 激活 | 共享专家 | 备注 |
|---|---|---|---|---|---|---|
| Mixtral 8x7B | 46.7B | 12.9B | 8 | Top-2 | 无 | MoE 开源破圈之作 |
| Mixtral 8x22B | 141B | 39B | 8 | Top-2 | 无 | |
| DeepSeek-V2 | 236B | 21B | 160 | Top-6 | 2 | 细粒度专家 |
| DeepSeek-V3 | 671B | 37B | 256 | Top-8 | 1 | 无辅助损失负载均衡 |
| Qwen2-57B-A14B | 57B | 14B | 64 | Top-8 | 8 | |
| Llama 4 Scout | 109B | 17B | 16 | Top-1 | 1 | 交替 MoE 层 |
| GPT-OSS-120B | 117B | 5.1B | 128 | Top-4 | 无 | 稀疏度极高 |
从表里能读出两条清晰的演进趋势:
- 专家数从 8 涨到 256:即"细粒度专家化",每个专家变小、数量变多,组合空间指数级增长
- 稀疏度越来越极端:GPT-OSS-120B 激活率只有 4.4%,DeepSeek-V3 是 5.5%,而 Mixtral 8x7B 是 27.6%
面试可以主动抛出的观点:稀疏度是可以调的超参,它本质上是在"专家专业化程度"和"训练稳定性"之间做权衡。稀疏度越高,单位算力能撑起的知识容量越大,但路由学习越困难、训练越不稳定。
二、MoE 层的结构与前向计算
2.1 替换的是 FFN,不是 Attention
标准 Transformer 层 = Attention + FFN。MoE 替换的是 FFN 部分,Attention 保持稠密。
原因很直接:FFN 占了稠密模型约 2/3 的参数量(在 d_ff = 4*d_model 的配置下),是参数大头;而 Attention 涉及 token 之间的交互,如果稀疏化,不同 token 走不同的 Attention 权重会破坏序列建模的一致性,且 KV Cache 会变得无法管理。
2.2 前向计算公式
对输入 token 表示 x ∈ R^d:
1) 路由打分: h = W_g · x (W_g ∈ R^{E×d}, E 为专家数)
2) 选 Top-K: I = TopK(h, k) (返回 k 个专家下标)
3) 归一化权重: g_i = softmax(h_I)_i (只在选中的 k 个上做 softmax)
4) 专家计算: y = Σ_{i∈I} g_i · FFN_i(x)
注意第 3 步的细节------softmax 是在 Top-K 之后做,还是之前做,这是一个高频考点:
- 先 softmax 再 TopK:权重是全局归一化后取 k 个,选中权重之和小于 1
- 先 TopK 再 softmax(主流做法):选中的 k 个权重之和恰好为 1
后者更常见,因为它保证了输出的尺度稳定,不会因为路由置信度低而整体衰减。DeepSeek-V3 用的是 sigmoid 打分加归一化,进一步解耦了专家之间的竞争关系。
2.3 一个最小可运行的 MoE 层
python
import torch
import torch.nn as nn
import torch.nn.functional as F
class Expert(nn.Module):
"""标准 SwiGLU FFN 作为单个专家。"""
def __init__(self, d_model: int, d_ff: int):
super().__init__()
self.w1 = nn.Linear(d_model, d_ff, bias=False) # gate
self.w3 = nn.Linear(d_model, d_ff, bias=False) # up
self.w2 = nn.Linear(d_ff, d_model, bias=False) # down
def forward(self, x):
return self.w2(F.silu(self.w1(x)) * self.w3(x))
class MoELayer(nn.Module):
def __init__(self, d_model, d_ff, n_experts=8, top_k=2,
n_shared=0, capacity_factor=1.25):
super().__init__()
self.n_experts = n_experts
self.top_k = top_k
self.capacity_factor = capacity_factor
self.gate = nn.Linear(d_model, n_experts, bias=False)
self.experts = nn.ModuleList(
[Expert(d_model, d_ff) for _ in range(n_experts)])
# 共享专家:所有 token 都经过,负责通用知识
self.shared = nn.ModuleList(
[Expert(d_model, d_ff) for _ in range(n_shared)])
def forward(self, x):
B, T, D = x.shape
x_flat = x.reshape(-1, D) # [N, D], N = B*T
logits = self.gate(x_flat) # [N, E]
topk_logits, topk_idx = logits.topk(self.top_k, dim=-1)
topk_w = F.softmax(topk_logits, dim=-1) # TopK 之后再 softmax
out = torch.zeros_like(x_flat)
# 按专家分组批处理,避免逐 token 循环
for e in range(self.n_experts):
mask = (topk_idx == e) # [N, k]
if not mask.any():
continue
tok_idx, slot_idx = mask.nonzero(as_tuple=True)
expert_in = x_flat[tok_idx] # [n_e, D]
expert_out = self.experts[e](expert_in)
w = topk_w[tok_idx, slot_idx].unsqueeze(-1)
out.index_add_(0, tok_idx, expert_out * w)
for sh in self.shared: # 共享专家无条件参与
out = out + sh(x_flat)
aux = self._aux_loss(logits, topk_idx)
return out.reshape(B, T, D), aux
def _aux_loss(self, logits, topk_idx):
"""Switch Transformer 式负载均衡损失。"""
probs = F.softmax(logits, dim=-1) # [N, E]
one_hot = F.one_hot(topk_idx, self.n_experts).sum(1).float() # [N, E]
f = one_hot.mean(0) # 实际路由比例
P = probs.mean(0) # 路由概率均值
return self.n_experts * torch.sum(f * P)
这段代码有三个实现要点值得在面试里主动讲:
- 按专家分组而非逐 token 循环 :
mask.nonzero()把 token 按专家聚类,一次性做矩阵乘法。逐 token 循环会让 GPU 利用率跌到个位数 index_add_而不是索引赋值:一个 token 可能被多个专家处理(Top-K),必须累加而不是覆盖- 辅助损失在前向里返回:负载均衡损失是每层都要算的,最后汇总加到总 loss 上
三、路由算法:从 Noisy Top-K 到 Expert Choice
路由器(Router)是 MoE 的灵魂,它决定"哪个 token 交给哪个专家"。演进路线值得完整讲一遍。
3.1 演进脉络
2017 Noisy Top-K Gating (Shazeer, Sparsely-Gated MoE)
└─ 加高斯噪声打破平局,Top-K (k≥2)
└─ 认为 k≥2 才能让梯度传到路由器
▼
2021 Switch Transformer (Fedus et al.)
└─ 大胆用 Top-1,证明 k=1 也能训好
└─ 引入 capacity factor + token drop
▼
2022 Expert Choice (Zhou et al.)
└─ 反转选择方向:专家挑 token,天然负载均衡
▼
2022 BASE / S-BASE
└─ 把路由看成最优传输/线性分配问题,硬约束均衡
▼
2024 DeepSeekMoE + Aux-Loss-Free
└─ 细粒度专家 + 共享专家
└─ 用可学习 bias 替代辅助损失,不干扰主任务梯度
3.2 Token Choice vs Expert Choice
Token Choice (主流) Expert Choice
每个 token 选 k 个专家 每个专家选 c 个 token
t1 ──┐ E1 ──选──> [t1, t3, t7]
t2 ──┼──> E1 (超载! 3个) E2 ──选──> [t2, t3, t5]
t3 ──┘ E3 ──选──> [t4, t6, t8]
t4 ─────> E2 (只有1个)
t5 ─────> E3 每个专家恰好 c 个 token
... 负载天然完美均衡
→ 负载天然不均衡,需要辅助损失 → 但某些 token 可能没人选
→ 每个 token 保证被处理 → 或被超多专家选中
→ 自回归推理友好 → 需要看到整个 batch,
自回归解码时不可用
Expert Choice 的致命问题 :它需要在专家维度上对整个 batch 的 token 做 Top-C 选择,这在训练时可以(整个序列已知),但在自回归解码时,token 是一个个生成的,无法预知未来 token,会造成信息泄漏(future token leakage)。所以生产模型基本都用 Token Choice,Expert Choice 主要用在编码器或非自回归场景。
3.3 各路由方案对比表
| 方案 | 选择方向 | 负载均衡 | 需要辅助损失 | 自回归可用 | 代表模型 |
|---|---|---|---|---|---|
| Noisy Top-K | token→expert | 差 | 是 | 是 | GShard |
| Switch (Top-1) | token→expert | 差 | 是 | 是 | Switch-T |
| Expert Choice | expert→token | 完美 | 否 | 否 | 部分编码器 |
| BASE (最优传输) | 双向匹配 | 完美 | 否 | 否 | BASE Layers |
| Aux-Loss-Free | token→expert | 好 | 否(用 bias) | 是 | DeepSeek-V3 |
| Soft MoE | 加权混合 | 完美 | 否 | 否 | 视觉领域 |
四、负载均衡:MoE 训练最大的工程难题
4.1 为什么会不均衡:马太效应
路由器初始化是随机的,某个专家偶然被多选几次,它就得到更多训练、变得更强、被选中概率更高,其他专家逐渐饿死。这就是专家坍塌(expert collapse),极端情况下 90% 的 token 都涌向两三个专家,剩下的专家参数纯属浪费。
不均衡带来两个后果:
- 效果层面:等效于一个参数量小得多的模型,MoE 白做了
- 系统层面:专家分布在不同 GPU 上,负载最重的那张卡成为瓶颈,其他卡等待,训练吞吐被拖垮
4.2 辅助损失(Auxiliary Loss)
Switch Transformer 的经典形式:
L_aux = α · E · Σ_{i=1}^{E} f_i · P_i
f_i = 被路由到专家 i 的 token 比例(离散,不可导)
P_i = 路由器给专家 i 的平均概率(连续,可导)
α = 权重系数,典型值 0.01
理解这个式子的关键:f_i 不可导,梯度只通过 P_i 回传。当专家 i 过载时 f_i 大,这一项的梯度会压低 P_i,从而减少后续路由到 i 的概率。当所有专家完全均衡时,f_i = P_i = 1/E,L_aux 达到最小值 1。
还有一个常被忽略的 z-loss:
L_z = (1/N) · Σ_n (log Σ_i exp(h_i^{(n)}))²
它约束路由 logits 的绝对值不要发散。MoE 训练中 router logits 爆炸是训练崩溃的常见前兆,尤其在 bf16 下。z-loss 权重一般取 1e-3。
4.3 容量因子与 Token Drop
专家并行下每个专家有固定的 buffer 大小:
capacity = capacity_factor × (tokens_per_batch × top_k / n_experts)
超出容量的 token 会被丢弃(drop),直接走残差连接跳过 MoE 层。
| capacity_factor | 显存 | Token Drop 率 | 适用场景 |
|---|---|---|---|
| 1.0 | 最省 | 高(10%+) | 极端省显存 |
| 1.25 | 中 | 低(1-3%) | 训练常用 |
| 2.0 | 高 | 接近 0 | 微调/小规模 |
| 无限(dropless) | 变长 | 0 | MegaBlocks 方案 |
面试加分点 :现代实现(MegaBlocks、DeepSpeed-MoE 的 dropless 模式)用块稀疏矩阵乘法(block-sparse GEMM)替代固定 buffer,把变长的专家输入组织成分块稀疏矩阵,从而彻底消除 token drop,同时保持 GPU 效率。这是 dropless MoE 的技术本质。
4.4 DeepSeek-V3 的 Aux-Loss-Free 策略
辅助损失有个固有矛盾:它是和主任务 loss 竞争的,权重大了伤效果,权重小了压不住不均衡。
DeepSeek-V3 的解法非常优雅------给每个专家加一个可学习的 bias,只影响选择、不影响权重:
python
# 路由打分
score = sigmoid(W_g @ x) # [E], 用于计算最终加权
# 选择时加 bias
topk_idx = (score + bias).topk(k).indices
# 但加权仍用原始 score,bias 不参与前向输出
weight = normalize(score[topk_idx])
# 训练中每步根据负载调整 bias(不走梯度,直接规则更新)
for e in range(E):
if load[e] > avg_load:
bias[e] -= gamma # 过载则降低被选概率
else:
bias[e] += gamma # 欠载则提高
bias 不参与梯度反传,纯靠规则更新,因此完全不干扰主任务的优化方向。这是 V3 能在 671B 规模上稳定训练的关键工程点之一,也是面试里非常容易讲出彩的细节。
五、细粒度专家与共享专家(DeepSeekMoE)
5.1 细粒度专家:切碎组合空间
传统做法是 8 个大专家选 2 个,组合数 C(8,2) = 28。DeepSeekMoE 的思路是:把每个专家的 d_ff 缩小到 1/m,专家数扩大 m 倍,同时 Top-K 也扩大 m 倍。
| 配置 | 专家数 | 每专家 d_ff | Top-K | 组合数 | 激活参数 |
|---|---|---|---|---|---|
| 传统 | 8 | 4096 | 2 | 28 | 相同 |
| 细粒度 m=4 | 32 | 1024 | 8 | 10,518,300 | 相同 |
激活参数量完全不变,但专家组合的表达空间从 28 涨到千万级。这让每个专家能学到更专精、更正交的知识。代价是路由计算量增加、All-to-All 通信的消息数增加。
5.2 共享专家:隔离通用知识
观察:语法、常识这类通用知识每个专家都得学一遍,造成参数冗余。
DeepSeekMoE 拿出 1 到 2 个共享专家(shared expert),所有 token 无条件经过;剩下的路由专家专注差异化知识。
输入 token
│
┌──────┴──────┐
▼ ▼
共享专家 Router → Top-K 路由专家
(必经) (E1 E5 E9 ...)
│ │
└────── 相加 ────────┘
▼
输出
好处有三个:减少专家间的知识冗余、给路由失误提供兜底(哪怕路由错了,共享专家保证基础能力)、稳定训练早期的梯度。
六、专家并行与 All-to-All 通信
这部分和第十七篇《分布式训练全攻略》直接衔接。
6.1 EP(Expert Parallelism)的通信模式
专家分布在不同 GPU 上,token 需要飞到目标专家所在的卡上计算,再飞回来:
Step 1: 本地路由打分(各卡独立)
Step 2: All-to-All Dispatch ------ 按目标专家把 token 发出去
GPU0 [t1→E2, t2→E0] GPU1 [t3→E0, t4→E3]
│ │
└────── All-to-All ──────┘
▼
GPU0 持有 E0,E1 收到 [t2, t3]
GPU1 持有 E2,E3 收到 [t1, t4]
Step 3: 各卡本地跑专家 FFN
Step 4: All-to-All Combine ------ 结果发回原始 token 所在卡
Step 5: 加权求和 + 残差
每个 MoE 层需要 2 次 All-to-All,这是 MoE 训练最主要的通信开销。All-to-All 的特性是通信量随参与设备数线性增长,且对网络拓扑极其敏感------跨节点(NVLink 之外走 InfiniBand/RoCE)的带宽比节点内低一个数量级。
6.2 并行策略组合
| 并行维度 | 切分对象 | 通信原语 | 在 MoE 中的角色 |
|---|---|---|---|
| DP | batch | All-Reduce | 基础,配 ZeRO 分片优化器 |
| TP | 单层权重矩阵 | All-Reduce | 切 Attention 与共享专家 |
| PP | 层 | P2P Send/Recv | 跨节点扩展 |
| EP | 专家 | All-to-All | MoE 专属 |
| SP | 序列 | All-Gather | 长序列时配合 |
典型的 DeepSeek-V3 配置是 EP × PP × DP 的三维组合,EP 尽量放在节点内(用 NVLink 跑 All-to-All),PP 跨节点。
6.3 通信优化手段
- 计算通信重叠:把 dispatch 拆成多个 chunk,第一个 chunk 通信完就开始算,与后续 chunk 的通信重叠。DeepSeek 的 DualPipe 把这个做到了极致
- 限制跨节点路由(Device-Limited Routing):约束每个 token 最多路由到 M 个节点(V2 用 M=3),把 All-to-All 的跨节点消息数压下来
- 通信量压缩:dispatch 时用 FP8 传输,combine 时用 BF16(因为 combine 涉及累加,精度更敏感)
- 分层 All-to-All:先节点内聚合,再节点间传输,最后节点内分发,减少跨节点消息数
七、推理部署:MoE 的特性与稠密模型完全不同
7.1 显存墙才是主要矛盾
回到第一节那句话:MoE 用显存换算力。DeepSeek-V3 激活只有 37B,但 671B 参数全部要驻留显存:
FP16: 671B × 2 Byte = 1342 GB → 需要 17 张 H100(80G)
INT8: 671B × 1 Byte = 671 GB → 需要 9 张
INT4: 671B × 0.5 B = 336 GB → 需要 5 张
对比稠密的 Llama-70B FP16 只要 140GB,两张卡搞定。所以 MoE 是"大厂友好、个人不友好"的架构:它降低了单 token 的算力成本,但抬高了部署的显存门槛。
7.2 批处理下的"专家激活爆炸"
这是最容易被忽略、也最能体现工程经验的一点:
-
batch=1 时,一个 token 只激活 8/256 个专家,实际读取的权重很少,是标准的 memory-bound 场景,MoE 优势巨大
-
batch=64 时,64 个 token 各选 8 个专家,并集很可能覆盖了几乎全部 256 个专家
batch=1 → 激活专家集合 {E3, E17, E88, ...} 共 8 个 batch=8 → 并集约 50 个 batch=64 → 并集约 220 个(接近全部) batch=256 → 并集 = 全部 256 个
后果:大 batch 下 MoE 的权重读取量退化到接近稠密模型,节省的只是矩阵乘法的 FLOPs,而不是显存带宽。这就解释了为什么 MoE 在高并发服务下的吞吐优势远小于理论值。
应对手段是 Expert Parallel 加大规模专家分片:把专家摊到很多卡上,每张卡只持有少量专家,用 All-to-All 换取每卡显存压力下降,同时靠更大的总 batch 摊薄通信成本。DeepSeek 的推理系统用了 EP144 这种极端配置就是这个道理。
7.3 专家卸载(Expert Offloading)
单卡跑大 MoE 的方案:把不常用的专家放在 CPU 内存或 NVMe,按需加载。
GPU 显存: Attention 权重 + 共享专家 + 热点专家缓存(LRU)
CPU 内存: 全部路由专家
↑ 按 router 预测结果预取
关键优化是预取:利用相邻层路由结果的相关性,在计算第 L 层时提前预测第 L+1 层可能用到的专家并异步加载。Mixtral-Offloading、Fiddler 等工作证明这能把单卡跑 8x7B 的速度做到可用(3 到 5 token/s)。
7.4 MoE 量化的特殊性
和第十九篇《模型量化全攻略》呼应,MoE 量化有三个独有的坑:
- 专家间敏感度差异巨大:高频专家量化误差影响大,冷门专家可以更激进。混合精度方案(热点专家 INT8、冷门专家 INT4)比统一量化效果好
- 共享专家绝对不能激进量化:它参与所有 token 的计算,误差会被无条件放大到每一个 token 上,通常保持 FP16 或 INT8
- Router 必须保持高精度 :router 只有
E × d个参数,占比极小,但量化它会直接改变路由决策,导致完全不同的专家被选中,属于典型的省了芝麻丢了西瓜
八、MoE 的固有缺陷
诚实地讲清楚缺点,是面试里体现判断力的地方。
| 缺陷 | 表现 | 缓解手段 |
|---|---|---|
| 训练不稳定 | router logits 发散、loss spike | z-loss、bf16、router 用 fp32 计算 |
| 微调易过拟合 | 小数据集上 MoE 显著差于稠密 | 冻结专家只调 router/Attention;更强正则 |
| 显存门槛高 | 部署成本高 | 量化、专家卸载、EP 分片 |
| 负载不均衡 | 训练吞吐被最慢卡拖累 | 辅助损失、bias 调整、dropless |
| 通信瓶颈 | All-to-All 占 20-40% 时间 | 重叠、分层通信、限制跨节点路由 |
| 效果收益非线性 | 专家数翻倍收益递减 | 细粒度 + 共享专家 |
关于微调过拟合再多说一句,这是实际项目里最常踩的坑:MoE 的总参数量巨大,在几千条 SFT 数据上全参微调几乎必然过拟合,且路由会退化------所有 token 都涌向少数专家。实践中的做法是用 LoRA(见第十八篇)只在 Attention 和共享专家上加适配器,路由器和路由专家整体冻结。
九、面试速答
Q:一句话解释 MoE,以及它到底省了什么?
A:MoE 把 Transformer 的 FFN 层替换成多个并行的专家网络加一个路由器,每个 token 只激活其中 Top-K 个专家。它省的是每 token 的计算量(FLOPs),不省显存------所有专家参数都要驻留。所以本质是用显存换算力,让模型的知识容量和推理成本解耦。
Q:为什么 MoE 只替换 FFN,不动 Attention?
A:三个原因。第一,FFN 占稠密模型约三分之二的参数,是参数大头,收益最大。第二,Attention 负责 token 之间的信息交互,如果不同 token 走不同的 Attention 权重,序列建模的一致性会被破坏。第三,工程上 KV Cache 的管理会变得极其复杂------每个 token 的 KV 用哪个专家的投影矩阵算出来的,后续注意力还怎么统一计算。
Q:负载不均衡为什么必须解决?辅助损失的原理是什么?
A:不均衡有两层危害。模型层面会出现专家坍塌,大部分 token 涌向少数专家,其他专家参数浪费,等效于一个小得多的模型。系统层面在专家并行下,专家分布在不同 GPU 上,最重负载的卡成为木桶短板,其他卡空转。辅助损失的形式是 α·E·Σ f_i·P_i,其中 f_i 是实际路由比例(不可导),P_i 是路由概率均值(可导),梯度只通过 P_i 回传,过载专家的概率会被压低。均衡时该损失取最小值 1。
Q:DeepSeek-V3 的无辅助损失负载均衡怎么做的?为什么更好?
A:给每个专家加一个可学习的偏置项,这个偏置只参与 Top-K 的选择过程,不参与最终输出的加权计算。训练中每步统计各专家实际负载,过载的降低 bias、欠载的提高 bias,用规则更新而非梯度更新。它更好的原因是:传统辅助损失是加在总 loss 上和主任务竞争的,权重大了伤模型效果、小了压不住不均衡,本质上是个两难;而 bias 完全不进入反向传播,对主任务优化方向零干扰,把负载均衡从优化目标降级成了调度手段。
Q:Top-1 和 Top-2 路由怎么选?
A:早期 Shazeer 认为必须 k 大于等于 2,理由是只有多个专家参与,路由权重之间才有相对比较,梯度才能有效传到 router。Switch Transformer 推翻了这个观点,证明 Top-1 配合合适的辅助损失和初始化也能训好,而且通信量和计算量直接减半。现在的实践是:在细粒度专家架构下用较大的 k(比如 256 选 8),因为单个专家变小了,需要多个组合才能提供足够的表达能力;在粗粒度大专家架构下用小 k。选择依据是激活参数量这个预算约束,而不是 k 本身。
Q:MoE 在推理服务里的吞吐优势为什么没有理论值那么大?
A:因为批处理下的专家激活并集问题。batch=1 时只激活 8 个专家,权重读取量确实只有理论上的一小部分。但 batch 增大后,不同 token 选中的专家并集迅速扩张,batch 到 64 以上时并集几乎覆盖全部专家,此时权重读取量退化到接近稠密模型,只在矩阵乘法的 FLOPs 上有节省。而 LLM 解码阶段本来就是 memory-bound 而非 compute-bound,所以节省 FLOPs 的收益有限。这也是为什么大规模 MoE 服务必须配合大规模专家并行,把专家摊到很多卡上摊薄单卡的权重读取压力。
Q:Expert Choice 路由为什么不能用在自回归生成上?
A:Expert Choice 是反转选择方向,让每个专家从整个 batch 里挑固定数量的 token,这样负载天然完美均衡。但它要求在做选择时能看到 batch 内所有 token 的打分,而自回归解码是逐 token 生成的,当前步无法看到未来 token。如果强行在训练时用 Expert Choice,等于让模型在训练时用到了未来信息,训练和推理行为不一致,属于信息泄漏。所以它只适用于编码器或非自回归场景。
Q:MoE 微调有什么特别注意的?
A:最大的问题是过拟合和路由退化。总参数量巨大而下游数据往往只有几千到几万条,全参微调几乎必然过拟合,同时路由器会快速退化成只用少数几个专家,破坏预训练时学到的专业化分工。实践方案是分层处理:路由器和路由专家整体冻结,只在 Attention 和共享专家上加 LoRA 适配器;如果一定要动路由器,用极小的学习率并保留辅助损失。另外评测时要专门监控专家使用分布的熵,熵大幅下降就是路由退化的信号。
Q:怎么判断一个 MoE 训练是否健康?看哪些指标?
A:至少四个。第一是专家负载分布,用变异系数或熵衡量,理想是接近均匀;第二是 token drop 率,超过 5% 说明容量因子设小了;第三是 router logits 的最大绝对值,持续增长是发散前兆,靠 z-loss 压制;第四是辅助损失本身的数值,Switch 形式的理论下界是 1,长期显著大于 1 说明均衡没做好。此外还要看不同层的路由模式,浅层通常路由较随机、深层专业化更明显,如果所有层都随机说明专家没学出分工。
十、高频追问清单
- MoE 的辅助损失里
f_i不可导,为什么整个损失还能优化负载均衡? - z-loss 具体防止什么问题?为什么 bf16 下更需要它?
- dropless MoE 用块稀疏矩阵乘法实现,它和固定容量方案的性能差距在哪?
- All-to-All 通信量怎么估算?和 All-Reduce 相比在什么规模下更贵?
- 细粒度专家把
d_ff切小,会不会影响单个专家的表达能力?边界在哪? - 共享专家的数量怎么定?多了少了各有什么问题?
- 如果观察到某几个专家从来不被激活,你会怎么排查和处理?
- MoE 模型做知识蒸馏,学生模型该用稠密还是 MoE?
- 上下文并行(CP)和专家并行(EP)能同时用吗?会有什么冲突?
- MoE 的 router 能不能做成基于内容的层次化路由(先选组再选专家)?
- 为什么有些模型(Llama 4)用交替的 MoE 层和稠密层,而不是每层都 MoE?
- 推理时能否根据请求难度动态调整 Top-K?会带来什么问题?
MoE 是当前所有旗舰模型的默认选择,但它带来的不只是架构变化,而是整条训练与推理工程链路的重构。下一篇会讲推理时扩展(test-time scaling)与强化学习新范式------如果说 MoE 解决的是"如何让模型更大而不更贵",那么推理时扩展解决的是"如何让模型在不变大的前提下想得更深",两者正好是当前提升模型能力的两条正交路径。