Published on

MoE 混合专家模型

Authors
  • avatar
    Name
    Vegetog
    Twitter

MoE(Mixture of Experts,混合专家)最核心的想法是:准备多组参数,但让每个 token 只使用其中一部分。这样可以增加模型容量,同时控制每个 token 的专家计算量。

本文讨论语言模型中常见的稀疏 MoE:用多个专家 FFN 替代 Transformer 中原来的 FFN。专家通常是一段前馈网络,并不是一个完整的大语言模型。

1. 先看一个 token 的完整路径

进入某一层 MoE 的,是这个 token 当前的隐藏向量 x。它已经包含前面层和 Attention 处理后的上下文信息,不是原始 token ID。

当前 token 的隐藏向量 x
Router 为各个专家打分
选出 Top-k 专家,并得到混合权重
把同一个 x 分别交给选中的专家 FFN
把专家输出按权重相加
得到 MoE 子模块输出 y

这里得到的是 MoE 子模块的输出。完整 Transformer block 还包含归一化、Attention 和残差连接;最终生成词表概率,还要经过后续层和输出头。

每层通常有自己的一组专家和 Router。同一个 token 在不同层可以选不同专家;不同 token 也可以选不同专家。“每次选两个”不是整句话只选两个,也不是整个模型只有两个专家参与工作。Mixtral 论文采用的是每层 8 个专家、每个 token 选 2 个。

2. Router 怎么挑专家

常见 Router 是一个线性映射:把 d 维隐藏向量映射成 N 个专家分数,也称 logits。

x 的形状:       [d]
Router 权重:    [d, N]
logits = xW:    [N]

例如 d=4096、N=8,忽略偏置,Router 只有 4096×8=32768 个参数。4096 和 8 是这个例子的配置,不是 MoE 的固定要求。

一种常见做法是先取 Top-k logits,再对选中的值做 softmax。也可以先对全部 logits 做 softmax,选出 Top-k 后再归一化;在这个设定下,两者得到相同的选中专家权重。不同架构的路由打分和归一化规则可能不同,不能把这一种写法套到所有 MoE。

例如四个专家的概率为:

专家 0:0.12
专家 1:0.48  ← 选中
专家 2:0.08
专家 3:0.32  ← 选中

重新归一化:
w₁ = 0.48 / (0.48 + 0.32) = 0.6
w₃ = 0.32 / (0.48 + 0.32) = 0.4

这些分数表示路由偏好,不能直接解释成专家的正确率或通用能力排名。

3. 选完专家,具体算什么

常见情况下,同一层的专家结构相同、参数不同。以 SwiGLU FFN 为例,可以写成:

Eᵢ(x) = [SiLU(xW_gate,ᵢ) ⊙ (xW_up,ᵢ)] W_down,ᵢ

其中 ⊙ 表示逐元素相乘。两条投影把 d 维向量映射到中间维度 m,再投影回 d 维。

注意两个“gate”不是一回事:Router 决定把 token 分给谁;SwiGLU 内部的 gate projection 负责专家内部的特征变换。

沿用上面的权重,输出是:

y = 0.6 × E₁(x) + 0.4 × E₃(x)

两个专家接收同一个 x,分别计算;不会先经过专家 1,再把结果送给专家 3。假设为了演示把输出缩成二维:

E₁(x) = [2, 4]
E₃(x) = [8, 1]
y = [0.6×2 + 0.4×8, 0.6×4 + 0.4×1]
  = [4.4, 2.8]

未选中的专家无需为这个 token 计算 FFN,但仍可能处理同一批次中的其他 token。

4. 总参数量与激活参数量怎么算

先用一个简化模型:有 L 个 MoE 层,每层 N 个等大的路由专家,每个专家 Pₑ 个参数,每个 token 每层选 k 个。用 Pₛ 表示只计一份的其余参数,按常用架构统计口径估算:

总参数量   ≈ Pₛ + L × N × Pₑ
激活参数量 ≈ Pₛ + L × k × Pₑ

因此,只能对路由专家部分乘 k/N,不能直接用“总参数量 × k/N”。如果模型还有始终执行的共享专家,估算时也要把它们计入始终参与的部分。模型卡中的激活参数是规模口径,也不表示一次 embedding 查表实际读取了整张表。

Mixtral 8×7B 为什么不是 56B

8×7B 是模型规格名称,不表示把八个完整 7B 模型拼起来。Attention、Embedding 等并没有随每个专家复制八份。

按 Mixtral 的配置自行复算:d=4096,m=14336,L=32。一个无偏置 SwiGLU 专家有三个矩阵,参数量为:

Pₑ = 3 × d × m
   = 176,160,768 ≈ 0.176B

全模型所有路由专家:32 × 8 × Pₑ ≈ 45.10B
每 token 选中的专家:32 × 2 × Pₑ ≈ 11.27B

再计入 Attention、Embedding、输出头、Norm 和 Router 等,得到约 46.7B 总参数、12.9B 激活参数。这里不是“单层的一个专家有 7B”,也不需要假设“7B 固定拆成 5.6B FFN 和 1.4B Attention”。配置来自 Mixtral 论文的架构表,具体数值由上述矩阵尺寸推算。

Qwen3-235B-A22B 中的 A22B

Qwen3 官方介绍,该模型总参数约 235B,每 token 激活约 22B;每个 MoE 层有 128 个专家,激活 8 个。22/235≈9.4%,可以说激活参数约为总参数的十分之一。

但它不等于推理速度提高十倍,也不等于只需十分之一显存。注意 8/128=6.25% 与 22/235 也不相同,因为总参数中还有非路由专家部分。

5. 为什么少算了,显存仍然很大

在所有权重常驻 GPU 的部署中,没有被当前 token 选中的专家,仍然需要存储。以 BF16、每个参数 2 字节粗算,46.7B 参数仅权重就约 93.4 GB,235B 参数约 470 GB;这里使用十进制 GB,还没有加 KV cache、激活和运行时缓冲区。

“所有专家必须放在同一张显卡里”则不对。专家可以分布在多张 GPU,也可以通过量化减少存储,或者卸载到 CPU 内存并按需搬运。MoE offloading 研究讨论了后一类方案。节省 GPU 常驻空间会引入传输和调度方面的取舍。

实际推理时间还受 Attention、权重读取、批大小、专家负载、kernel 效率和跨卡通信影响。分布式专家并行的一条常见路径是:

按专家整理 token → 把隐藏向量发送到专家所在 GPU
→ 批量执行专家 FFN → 返回结果 → 恢复 token 顺序并加权合并

因此,激活参数量适合帮助理解计算规模,不能直接替代端到端延迟或吞吐测试。

6. 专家坍缩:不是简单的“好专家太好用”

训练中,Router 可能长期把大量 token 分给少数专家。热门专家得到更多训练机会,冷门专家很少获得更新,偏斜可能继续强化。结果既浪费模型容量,也可能让部分设备过载。

更准确的说法是路由或负载失衡。不能仅凭“被选得多”就认定某个专家更好,也不能把专家预先当成固定的数学、编程、写作部门。

常见处理方式有三类,但并不是所有模型都必须同时使用:

  1. 负载均衡辅助损失:根据专家接收 token 的比例、Router 概率等统计量,对过度集中的分配施加约束。优化的是使用分布,而不是直接惩罚回答正确的专家。
  2. 专家容量限制:规定一次 batch 或路由分组内,一个专家最多处理多少 token。超额 token 可能被丢弃该专家分支或重新路由,取决于实现;不是整个训练期间累计调用到上限就永久停用。
  3. 路由噪声:在训练时扰动路由分数,增加探索机会,减轻早期选择过早固化。不是把所谓好专家的参数或算力强行转移给差专家。

Switch Transformer提供了 Top-1 路由下的容量与均衡损失例子;早期稀疏 MoE 论文介绍了 noisy top-k gating。具体策略应以目标模型的实现为准。

以 Top-1 为例,一组有 T=1024 个 token、N=8 个专家、capacity factor=1.25,则每个专家的容量约为:

C = ceil((T / N) × 1.25) = 160

这里限制的是这一组 token。Top-k 会产生更多 token—专家分配,不能不加说明地照搬 Top-1 的容量公式。

7. 为什么要增加专家数量

在专家尺寸和 k 固定时,增加 N 可以增加总参数容量,而每个 token 的专家 FFN 计算量大致保持不变。代价是更大的权重存储,以及可能增加的路由、通信和负载管理成本。

“专家组合更多”可以作为直觉,但组合数量不是独立知识数量,更不是效果保证。能否从更多专家中获益,还取决于训练数据、路由和优化是否有效。

阅读一个 MoE 模型时,可以先核对四件事:哪几层使用 MoE、每层有多少专家、每 token 选几个、总参数和激活参数分别是多少。再讨论显存与速度,就不容易把模型容量和实际执行成本混在一起。

参考资料

本文由个人学习笔记整理,数值算例用于解释机制,不包含新的 GPU 性能实测。