《缩放定律》说"越大越好"。但参数量一大,每个 token 都要过一遍全部参数—— 推理成本直接线性爆炸。MoE(Mixture of Experts,混合专家)的说法是: 参数量可以大,但每个 token 只看其中几个专家就够了。
Transformer 的每一层(一个 Block)由两部分组成:注意力和前馈层(FFN)。 我们在《Transformer 自注意力》那章算过:FFN 占了整层三分之二的参数。
所以想让参数量变大、又不让计算量跟着变大,唯一值得动刀的地方就是 FFN。 原因也简单:FFN 对每个 token 各算各的,正好拆得开;注意力要让所有 token 互相交换信息,拆成专家收益小。
普通模型(叫「稠密」模型:每个 token 都要用到全部参数)把这一个大 FFN 用满。 MoE 换成 N 个小的(叫"专家"),每个 token 只挑其中 k 个走。 N 是专家总数,k 是每个 token 用几个——两个数不是一回事。
总参数决定模型能装多少知识(显存要装下全部)。
每个 token 用到的参数(active parameters)决定它要花多少计算(速度看这个)。
比如 Qwen3-30B-A3B(30.5B 总 / 3.3B 用上):总参数 300 亿,但每个 token 只用其中 30 亿。
它的知识容量高于用到的参数、又达不到总参数——每个专家只见到分给它的那部分 token,
学到的比同样大的稠密模型少(所以「有效参数」落在中间;这是本章的说法,不是论文指标)。
官方博客的对照是:它以约十分之一的「每个 token 用到的参数」超过了 QwQ-32B
(一个 32B 的稠密模型;Qwen3 博客),
推理速度则和 3B 的稠密模型差不多。
总参数是容量上限,不是等值兑换。
图 · 总参数是整库,每个 token 只借其中一小块
MoE 像一家有很多专科医生的医院:分诊台(路由器)看完你这个病人,只把你送去最对口的两位医生。
全医院的知识都在,但你这一个病人只花两位医生的时间。
这个类比管到「怎么分工」为止:医生的专科是事先定的,MoE 的专家不是——哪个专家擅长什么,是训练中自己长出来的。
互动 · 动刀的地方只有 FFN:把 1 个大 FFN 换成 8 个小的
怎么决定哪个 token 去哪个专家?靠一个叫路由器(Router)的小矩阵 Wr: 它给每个 token 算一组分数,取最高的 k 个,再用 softmax 归一化成权重 g。
路由器是可以训练的,而且它只占极少的参数——Wr 是一个 d×N 的矩阵 (d 是 token 向量的长度,N 是专家个数,k 是每个 token 用几个)。 下面沙盘里每个 token 带着一个 x(它的特征向量,这里是 6 个数), 路由分数就是 Wr · x(x 的每一项乘 Wr 相应一列再相加), 再取分数最高的 k 个——每一步怎么算,后面的数学节会逐格走一遍。
沙盘里,左边每一行是一个 token,右边每一行是一个专家;每条线 = 这个 token 被送去了那个专家, 线的粗细就是它的权重 g。
沙盘里的 token 特征是按 8 个专家方向手工造的,所以它们一定会落到不同专家—— 这里演示的是路由怎么算,不是专家怎么学会分工。 真实模型里专家到底专在哪?研究者事后检查过 Mixtral 的路由:没有出现明显的「这个专家管数学、那个管生物」, 更像是在词法、句法层面分了工(Mixtral 论文第 5 节)。
互动 · 亲手路由一批 token
上面的沙盘看起来一切正常。但 MoE 有一个致命的自增强陷阱:富者越富。
解法是往损失函数里加一项:负载均衡损失(Load Balancing Loss)。 它惩罚"分配不均",逼路由器把 token 摊开。
互动 · 加上负载均衡损失,看路由怎么被掰回来
专家负载分布
损失随训练下降
互动 · 富者越富:拖「自我强化」强度,看负载什么时候塌成一个专家
| 版本 | 关键改动 | 解决了什么 |
|---|---|---|
| 最初的 MoE 1991 |
层内并列多个专家,用一个小网络(门控)来挑 | 理论上更省算力,但训练极不稳定 |
| 稀疏门控 MoE 2017 |
第一次把稀疏门控真的堆到 137B 参数 | 参数量大幅涨、算力几乎不涨;但通信开销巨大 |
| GShard 2020 |
top-2 token-choice 门控(token 选专家)+ 自动分片(把模型切到上千张卡上) | 第一次把 MoE 做到 600B 级并跑通分布式训练 |
| Switch Transformer 2021 |
top-1 路由(只选一个专家)+ 简化负载均衡损失 | 第一次把 MoE 训到 1.6 万亿参数(并用半精度 bfloat16 稳住训练) |
| Expert Choice 2022 |
反过来:专家选 token(expert-choice) | 天然负载均衡。但每个 token 会被不同数量的专家处理;而且专家要拿一整批 token 来挑,生成时后面的 token 还没出来,不能直接用于自回归生成 |
| Mixtral 8×7B 2023 |
top-2,共 8 个专家 | 第一个用 Apache-2.0 许可(一种允许商用的开源协议)发布、被社区广泛验证的 MoE 大模型 |
| DeepSeekMoE 2024 |
细粒度专家(专家更多更小)+ 共享专家(几个专家所有 token 都走) | 共享专家负责通用知识,路由专家负责专门知识, 大幅减少"每个专家都得重复学一遍常识"的浪费 |
| DeepSeek-V3 / R1 2024–25 |
671B 总参数 / 37B 用上;无辅助损失的路由(不额外加一项惩罚)+ 通信优化 | 证明 MoE 在超大规模上不仅省算力,效果也能打赢同级别的稠密模型 |
标准 MoE 有个隐性浪费:像"这个词是名词""这句话是疑问句"这类所有 token 都需要的常识,
会因为路由分散而在每一个专家里都被重复学一遍。
DeepSeekMoE 的解法是留出几个共享专家——不管路由结果如何,所有 token 都会过它们。
于是专门知识交给路由专家,通用知识交给共享专家,不再重复劳动。
互动 · 三个真实机型:总参数和每个 token 用到的参数差了多少
| 资源 | 稠密模型 | MoE(总参数 8×,每 token 用 1×) |
|---|---|---|
| 每个 token 的计算量 | 1× | 约 1×(只走 k 个专家)—— 这是 MoE 唯一真正省的东西 |
| 显存占用 | 1× | 约 8×(所有专家都要装在显存里,即使某个 token 不用) |
| 训练时的通信 | 低 | 很高。专家分散在不同 GPU 上,token 要在显卡之间发送再收回来 |
| 训练稳定性 | 稳 | 需要精心调负载均衡、路由温度和容量因子(后两样后面讲) |
| 知识容量 | 1× | 约 8×(总参数量决定容量) |
| 推理延迟 | 取决于参数量 | 计算快,但要处理动态路由,一次要算的 token 很少(batch 小)时反而可能更慢 |
MoE 省算力,但很费显存和通信。
所以它特别适合"推理量巨大、但显存相对宽裕"的场景——
比如在线服务的基础模型。反过来,如果你要在手机或边缘设备上部署,
MoE 是最糟糕的选择:装不下,而且动态路由的开销完全掩盖了计算量的节省。
互动 · 省了什么、没省什么:两笔账摆在一起(以 8 专家 top-1 为例)
前面说「路由器给每个 token 打分,取 top-k,再 softmax 加权」。 这十几个字,读的人脑子里常常是空的——分数从哪算出来、挑中之后是几个什么数、 几个专家的答案又怎么变成一个。一张图走完这三步,这一章的公式你就全看懂了。 (图上那个「路由温度 T」藏在 softmax 里:分数先除以 T 再归一化——T 越小,高分的优势被放得越大。)
核心大图 · 拖 top-k 和温度:看这个 token 被打了几分、被谁处理、最后怎么合起来
互动 · 每个符号管图上的哪一块(卡片下面那张是「均衡损失为什么是个碗」的那一步)
把「温度」从 1 拖到 0.2:分最高的那个专家的权重会迅速吃掉全部,
输出几乎就等于它自己——这就是 top-1 的极端。
再把温度拖到 3:被选中的两个专家权重变得接近,输出成了两者的平均。
同一个 token、同一组分数,只因为温度变了,它走的路就变了——
这就是为什么「路由温度」是 MoE 要专门调的一个超参数。
| 坑 | 说明 | 什么时候真的会痛 → 换什么 |
|---|---|---|
| ① 专家坍塌 | 富者越富的自增强循环:被选中的专家学得更好,于是更容易被选中。解法是负载均衡损失, 但均衡得太狠又会让路由失去区分能力——所有专家变得一样,等于没分 | 训练数据分布单一、或 λ 怎么调都不对时 → 调大 λ、或让共享专家分摊通用知识(DeepSeekMoE 的做法) |
| ② 容量因子与 token 丢弃 | 专家硬件上容量有限,超出容量的 token 只能被丢弃:这些 token 对专家参数的梯度是 0 (token 本身经残差——原样加回上一层——直通下一层,见 《归一化》) | 负载天生偏、数据长尾(大部分数据集中在少数类型,其余类型各只有一点点)时最痛 → 把容量因子调大(费显存),或看第 4 节 expert-choice 那种反过来的路由 |
| ③ batch 小的时候不划算 | MoE 的优势建立在"不同 token 走不同专家、可以并行"上。 一次要算的 token 很少时,它们可能都挤到同一个专家,并行度丢失,反而比稠密模型慢 | 端侧、单条请求、实时交互 → 直接用每个 token 计算量和它差不多的稠密模型 |
| ④ 微调更难 | LoRA(一种常见的省显存微调法)之类的方法在 MoE 上不那么好使——该调路由器还是该调专家? 专家之间的负载还会随微调数据分布漂移,目前仍是开放问题 | 要频繁换任务微调 → 得连专家一起调、并盯住负载平衡;想省事就用稠密模型 |
| 看的数 | 意味着什么 | 代价 |
|---|---|---|
| 专家越多越小(细粒度) | 组合空间更大、更灵活 | 通信和路由开销上升 |
| top-k 越大 | 每个 token 用的专家越多,质量通常更好 | 每个 token 用到的参数变多,算力跟着涨 |
| 比例(总参数 ÷ 用到的参数) | 常见区间是 3.6:1 到 18:1 | 比例越大,显存和通信的账越贵 |
互动 · 拖「容量因子」:设小了,有多少 token 会被直接丢掉
这些工程细节底下,只压着一个赌注。这一节把它挖出来。
回到第 2 节那张路由沙盘,点一下「让专家 0 一开始就领先」那个按钮—— 专家 0 的柱子立刻比别的长出一大截,读数报出「专家坍塌」。
再把 token 数量留在默认的 24 个,把「每个 token 走几个专家」拖到 top-1: 连线的条数少了,专家 0 的份额还会更高(49% → 54%)。 那个瞬间你看到的就是赌注塌掉的样子——流量全挤向一个专家,MoE 白做了,实际退化成一个小得多的稠密网络。
然后走到第 3 节,把 λ 从 0.01 拖到 0.05,点「▶ 开始训练」—— 负载柱会被压成一条平线。那条平线就是那场拉扯:均衡的力量和分工的力量互相抵消, MoE 的全部工程难度都挂在这个滑块上。如果你刚才没想指着某个东西说这句话,回去再拖一次。
路由器在做的事,和负载均衡损失在做的事,各是一行。把鼠标移到公式里的符号上, 对应的滑块会亮起来。
鼠标停在 λ 上,看它指向第几节的那个滑块;f 是每个专家实际分到的比例,P 是路由器给它的平均概率,悬停它们也能看到各自指哪。
然后把 λ 从 0.01 拖到 0.05——右边那条均衡项曲线会掉到贴着虚线。
那条虚线是「N 个专家各拿 1/N」时 N·Σf² 的最小值 1.0(还没乘 λ);曲线贴着它,正说明负载摊匀了。
注意这条曲线只画了 N·Σf² 这一项,不是模型的总损失,也还没乘 λ:λ 拖大,这一项确实贴近最小值 1.0,
但路由被逼着不看内容了,模型整体反而变差——这才是「不能一直拖大」的原因。
| λ 太大 | λ 刚好(≈0.01) | λ 太小或为 0 |
|---|---|---|
| 路由器被逼着不看内容、均匀分配。每个专家拿到一样的流量, 但也就变成了 8 个相同的小 FFN——等于没有专家 | token 较均匀地分到各个专家,同时没有一个专家垄断流量。 这就是要的那个点 | 富者越富的自增强循环:被选中的专家学得更好 → 更容易被选中 → 最后只剩一两个专家在干活(专家坍塌) |
λ 是一个
下面的 A–F 是本站拆解任何一个方法时通用的六个问题,不是 MoE 专属的。
| 暗线 | 这一章的回答 |
|---|---|
| A 信息流动 | 输入 (n_token, d_model)(n_token 个 token,每个是一条 d_model 长的向量),输出还是 (n_token, d_model)——
MoE 不改变张量的形状。它改变的是那条路径:稠密 FFN 里每个 token 走同一套参数, MoE 里每个 token 走一条自己选的专家路径。 形状不变、计算图变成随 token 而变的动态图——这就是 MoE 所有工程麻烦的源头: batch 里没有两个 token 保证走同一条路,所以你没法把它们简单拼成一次大批量矩阵乘 |
| B 什么被牺牲了 | 牺牲的不是精度,是稠密模型那份「每个参数都为每个 token 干活」的确定性。
同时丢掉三样: |
| C 参数账本 | 看三个真实机型: Mixtral 8×7B:8 个专家、top-2,总参数 46.7B,每个 token 用 12.9B(约 3.6:1) DeepSeek-V3:256 个专家、top-8,总参数 671B,每个 token 用 37B(约 18:1) Qwen3-30B-A3B:就是第 1 节那台(比例约 9:1) 「每个 token 用到的参数」才是 FLOPs(计算量)的账,「总参数」才是 |
| D 跑在什么上 | 卡在「搬数据」上,不是卡在 MoE 省的正是 FLOPs(计算量),可它把省下的钱全花在了搬运上:专家分布在不同卡上时, 每个 token 要被 两两互送(all-to-all)到它专家所在的那张卡,算完再搬回来—— 最贵的动作是「把 token 从一张卡搬到另一张卡」,不是乘加。 更反讽的是:每张卡分到的 token 变少了,一次搬进来的数据只够算几下—— 算力再快也得等数据送到,GPU 反而更容易被「数据进出的速度」(内存带宽)卡住。 完整的搬运 vs 计算账在 《硬件与算力账本》 |
| E 它假设了什么 | 假设「知识是可分块的、输入决定了该用哪一块」。
这条假设成立时,MoE 就是白捡的算力;不成立时(所有 token 其实都需要同一套知识),
路由学不出分工、专家全长成一个样,MoE 退化成稠密网络还多背一堆参数。 第 2 节那个沙盘演示的是这条假设成立时的样子(token 特征是我们按专家方向造的); 真实模型里它成不成立,要看数据 |
| F 违背了哪个直觉 | 「参数越多越慢」是错的。常识里 671B 的模型该比 37B 慢一个数量级,
可 MoE 让它跑出 37B 的速度。 第二个反直觉:「更大的模型必然要花更多算力」也不完全对—— MoE 把「容量」和「速度」解耦了,代价是它们各自需要的东西(显存 vs 通信) 不再是同一笔账 |
MoE = 把 FFN 换成多个专家,每个 token 只走 top-k 个。
它唯一真正省的是计算量(每个 token 用到的参数),总参数(知识容量)一点没省,反而更费显存和通信。
核心难点是路由会让富者越富——所以必须加负载均衡损失,
但均衡过度又会毁掉专家的区分度。
它的工程含义很明确:适合显存宽裕、推理量巨大的服务场景,不适合端侧部署。
取舍那笔账,《缩放定律》算过;数据从哪来,看《预训练数据工程》。
上面讲的都是「够用」的版本。想往下挖,这里有三个入口—— 它们不是必修内容,是给想再往前走一步的读者准备的。