先说一个反直觉的事实:你正在用的不少大模型,宣传口径已经冲到了几千亿甚至上万亿参数,但每生成一个 token,真正参与计算的参数可能只有总量的十分之一到二十分之一。这不是营销号胡吹,而是 MoE(Mixture of Experts,混合专家)架构在起作用。MoE 把模型里那个巨大的前馈网络拆成几十上百个独立的“专家”,再让门控网络决定每个 token 只去找少数几位专家处理,于是模型可以做到参数巨大无比,单 token 的算力开销却没有跟着膨胀。
这篇文章适合三类人看:在研究大模型架构的算法工程师、在做推理服务部署的后端工程师,以及想弄明白“万亿参数模型为什么跑得比想象中快”的所有技术爱好者。我会从最基础的参数构成讲起,再到门控路由、训练难点、分布式部署,最后聊一些我自己踩过的坑。读完你不仅能看懂 MoE 的原理,还能直接把它引入到自己的训练和部署方案里。
1. “万亿参数”的错觉:稠密模型的算力账单
1.1 稠密 Transformer 里的参数都去哪了
在 MoE 成为主流之前,绝大多数大模型都是“稠密”架构。所谓稠密,就是每一层 Transformer 从头到尾是一条完整路径:输入 token 经过注意力机制后,一定会进入那个唯一的、巨大的前馈网络(FFN)。FFN 通常由两个线性层夹一个激活函数组成,负责对特征做高维非线性变换,把注意力层提取到的信息真正“消化”成更高层次的表达。
在稠密模型里,FFN 是绝对参数大户,通常占到单层参数的三分之二到四分之三。以一个常见的 700 亿参数稠密模型来说,大约 500 亿左右的参数都躺在 FFN 里。无论进来的 token 内容多简单,都必须完整地算过一遍全部 FFN 权重。所以这类模型每处理一个 token,计算量是恒定的:总参数多少,就得算多少。
这个局面非常棘手。你想让模型更聪明,就得加参数;参数一加,训练成本和推理成本等比上涨。在固定预算下,模型规模和推理成本基本是零和游戏。行业里很早就有人琢磨:能不能让参数变多,但每个 token 的算力不变?这就是 MoE 最原始的出发点。
1.2 稀疏激活:计算量与参数量从此分家
MoE 的思路本质上是在和上面的零和游戏对着干:把每层唯一的 FFN 替换成一组可以分别工作的 FFN“专家”。专家各自有各自的权重,互不共享。token 进入这一层时,不会经过全部专家,而是由一个轻量级的门控网络决定它去访问哪些专家。
于是模型的总参数量变成了“全部专家的权重之和”,但前向计算只会激活被选中的少数几个专家。这里要反复强调一个核心结论:参数量和计算量并不是一回事了。模型规模表征的是容量与知识储备,单 token 计算量表征的是真实算力成本。我们在新闻里看到的“万亿参数”,说的是权重总量;而模型跑一次前向实际用的算力,只看激活参数规模。
很多人第一次接触 MoE 时的最大困惑就在这里:模型明明有万亿参数,为什么跑起来不比千亿稠密模型慢?答案是,它的前向计算路径本质上仍然是一个千亿甚至百亿量级的网络,只是把那些没被选中的专家权重“晾”在了显存里。
1.3 一个具体的激活比例计算示例
我直接算一笔账,把比例讲清楚。假设有一个总参数 1.6 万亿的 MoE 模型,其中公共参数(Embedding、注意力等)大约 2000 亿,其余 1.4 万亿放在 64 个专家里,每个专家平均 218 亿左右,门控采用 Top-2 路由。每个 token 前向时:
- 公共参数全部参与:2000 亿;
- 两个专家参与:约 440 亿;
- 单 token 激活合计约 2440 亿,占总参数的 15% 左右。
| 项目 | 参数量(亿) | 是否参与计算 |
|---|---|---|
| 公共参数 | 2000 | 是 |
| 每个专家 | 218 | 仅被选中的参与 |
| 单 token 实际激活 | 約 2440 | 是 |
| 单 token 激活占比 | 约 15% | — |
对比一下稠密模型:如果拿一个 1600 亿参数的稠密模型,单 token 激活比例是 100%。同样量级的 MoE 模型,单 token 计算成本直接降到稠密模型的六分之一左右,但知识容量依然可以接近那个万亿规模。
这笔账算清楚之后,很多现象就有了解释。比如为什么一些总参数达到千亿级别的 MoE 模型,在量化和分片之后能部署到比同参数稠密模型小得多的资源预算下;又比如为什么很多 API 的定价没有按模型总参数线性上涨。真正按 token 消耗的算力,不是总参数。
2. MoE 架构的心脏:门控网络与 Top-K 路由
2.1 门控网络:给每个 token 分配专家
门控网络是 MoE 层内部最重要的调度器。它接收 token 当前的隐含层向量,输出对该 token 与每个专家匹配程度的打分。打分经 softmax 转成概率之后,模型只看排名前几位,决定 token 实际交给哪些专家处理。
核心实现并不复杂,伪代码级别的 PyTorch 版本大概是这样的:
import torch import torch.nn as nn import torch.nn.functional as F class Router(nn.Module): def __init__(self, hidden_dim, num_experts, top_k): super().__init__() self.gate = nn.Linear(hidden_dim, num_experts) self.top_k = top_k def forward(self, x): logits = self.gate(x) # [N, num_experts] probs = F.softmax(logits, dim=-1) top_k_probs, top_k_indices = probs.topk(self.top_k, dim=-1) return top_k_probs, top_k_indices门控网络通常只有一层线性映射,参数量在几十万到几百万之间,放在动辄千亿的参数池里可以忽略不计。也正因为它轻量,才能几乎不增加额外开销地完成每个 token 的路由决策。
有一段时间我把门控理解成“一个额外的小模型”,后来发现这个理解不够准确。它更接近一个坐标选择器:输入是高维特征向量,输出是对每个专家的亲和度排序。它本身不承载专业知识,只决定“派谁去干活”。
2.2 Top-K 机制为什么通常选 Top-2
既然有了打分,一个很自然的问题是:到底让 token 选几个专家?实践中 Top-2 是最主流的配置,原因是它在“质量”和“成本”之间正好卡在最稳的折中点。
- Top-1:计算量最小,token 只进一个专家,但路由决策容错很低。如果门控给出了错误排序,这个 token 就只能被一个不合适的专家处理,后面没有纠偏机会。
- Top-2:计算量约为 Top-1 的两倍,但两个专家输出做加权合并后,特征表达更稳定,路由误判的影响被明显稀释。
- Top-K 更大(Top-4、Top-8):表达能力更强,但激活参数等比上涨,通信成本也跟着涨,MoE 相对稠密模型的性价比优势会被慢慢吃掉。
在真实训练中,Top-2 是“质量与成本”非常稳的选择。很多模型即使把专家数量推到上百个,依然保留 Top-2 路由。我自己在实验中的体会也是:Top-2 做基线,效果稳定,问题定位也方便;真要追求表达上限再试 Top-4,但要提前做好算力上升的心理准备。
2.3 路由矩阵与负载均衡
每次前向计算,门控网络会产出一张路由矩阵,记录每个 token 被分到哪个或哪两个专家。矩阵统计信息非常关键:各个专家接收的 token 数量分布,直接暴露了路由是否健康。
理想状况是所有专家接到的 token 数量大致均等,这样每个专家的权重都能获得充分训练。但如果不加干预,门控网络几乎必然走向“少数专家忙死、多数专家闲死”的状态。原因是早期只要某个专家稍微占优,它就会拿到更多梯度,变得更强,也就会被更多 token 选中,循环往复。
为了对抗这种正反馈,MoE 训练里一般会用负载均衡损失对门控打分施加压力,同时设置专家容量上限。这两个旋钮我会在第 4 章展开。这里先记住一个关键结论:路由分布是 MoE 训练时必须时刻盯住的健康指标,不能只看 loss。
3. 前向计算的一次完整旅程:token 如何穿越专家层
3.1 从输入到门控打分
把一个 token 在 MoE 层里的路径完整走一遍。token 先进入注意力和残差结构,得到一个隐含向量。这个向量随后被复制成两份:一份送进门控网络,等着拿路由结果;一份保留在当前层,准备交给被选中的专家。
由于每个 token 的专家分配可能完全不同,整个计算过程可以看作先把一批 token 的“去向”全部决定好,再按专家统批计算。这也是为什么 MoE 在单条请求上的表现不一定看起来很快:门控环节有序列化调度开销,但当大批请求同时进入时,token 会在不同专家之间形成天然并行,整体吞吐会非常可观。
在推理服务里,我不会用“单条请求延迟”来评估 MoE 模型,而是看“并发批量请求下的吞吐量”。单看前者,你可能会误解 MoE“不过如此”;看后者,才能感受到它真正的优势。
3.2 专家前向计算与加权合并
被选中的两个专家各自计算 FFN 变换,产出两个隐含向量。之后,门控网络给出的概率得分会作为权重,把两个向量做加权求和,得到这一层的最终输出。这就像两位专家分别给出建议,调度员按信任度做加权汇总。
理论上,每个专家都在学习属于自己的特征变换,而门控的加权合成为 token 提供了更丰富的表达空间。计算本身依然是矩阵乘法,额外增加的只是路由和合并环节的少量操作,量级都很小。
这也是 MoE 的“条件计算”本质:不是每个 token 都走同样的网络路径,而是根据 token 内容动态改变计算路线。简单 token 可能集中到少数专家,复杂 token 则激活差异更大的专家组合。这样的动态性,是稠密架构里不存在的。
3.3 为什么 MoE 的推理速度没有被万亿参数拖垮
到这里,最核心的疑问基本解开了。MoE 模型每前向一次,只激活被选中的少数专家;其余专家的权重仅仅驻留在显存里,不产生任何计算。所以单 token 的有效计算量 = 公共参数 + Top-K 专家参数,与总参数规模没有直接关系。
这也是为什么某些开源社区里的千亿级 MoE 模型,在推理吞吐上能明显快于同参数规模的稠密模型,甚至逼近一个体量小得多的稠密模型。加上 Batch 推理时不同 token 被路由到不同专家的特性,每个专家都在同时服务自己的那部分 token,模型整体吞吐很容易被压满。
不过这里要泼一盆冷水:推理速度不被拖垮,不等于部署成本低。所有专家权重都占显存,这部分成本一点都省不了。关于显存的账,我会在第 5 章细说。
4. 训练 MoE 最容易翻车的三个地方
4.1 路由崩溃:模型为什么偷懒
MoE 训练第一大坑就是路由崩溃。按理想设计,每个 token 应该根据内容特性被灵活分配,所有专家都能分到活。但实际训练中,门控网络很容易演变成一个“偷懒调度器”:所有 token 都涌向一开始就比较强的少数专家,其他专家拿不到梯度,越来越弱,最终彻底失去被选中的机会。
可以这么理解:门控决策没有显式的奖励信号,只能从 token 经过专家之后的最终 loss 间接感知“这次路由好不好”。这个信号非常稀疏,对门控来说偏向延迟反馈,所以模型倾向于走捷径——既然某个专家积累了优势,多把它派几次,loss 下降更快。
这个问题如果不解决,专家数量再多也是一纸空谈,模型实际退化成一个少数专家参与的弱模型,MoE 的“混合”意义完全消失。
4.2 辅助损失与专家容量:两个关键旋钮
针对路由崩溃,主流的解法有两个。
第一个是负载均衡辅助损失。训练时,除了正常任务损失,还要计算每个专家被路由到的 token 比例与路由概率的乘积,累积成一个损失加进总损失。路由越不均匀,这个损失越大,门控网络在优化时就会被拉回来,变得更愿意“平均分配”。
第二个是专家容量。给每个专家设置一个单项任务中可以处理的最大 token 数。超过容量的 token 会被视为溢出,不进入该专家。容量设小,负载更平均,但会损失训练信号;容量设大,允许一定不均匀以保存表达效率。
| 问题 | 解法一 | 解法二 |
|---|---|---|
| 路由崩溃 | 负载均衡辅助损失 | 专家容量限制 |
| 核心原理 | 惩罚不均匀路由 | 硬性限制单专家负载 |
| 代价 | 可能压掉门控判别力 | 容量过小浪费训练信号 |
实际操作中,我的经验是训练早期把辅助损失系数调得稍大,逼着所有专家尽快进入工作状态;训练稳定后再逐步降低系数,以免“平均化压力”压过门控对真实语义的判别力。这个系数需要结合验证集表现来调,不能照搬默认值。
4.3 通信瓶颈与训练不稳定
MoE 一进入分布式训练,通信问题立刻显现。token 被路由到其他 GPU 上的专家时,你需要把它的向量从当前 GPU 打包发过去,等专家算完再传回来,这个传输叫 all-to-all。专家分布得越散,单个 token 跨卡次数越多,通信总量越大。
训练稳定性方面,MoE 比同规模的稠密模型更敏感。学习率稍微偏大,前期就可能出现 loss 飙高;一批样本里的 token 分布不均匀,也会导致不同专家训练进度差异巨大。所以预热学习率、合理设置 token 掩码、用小批次起步,成了必要的保护措施。
这里还有一个容易被忽略的点:Batch 内的 token 如果都路由到同一个专家,那这个专家的大部分计算就浪费了;如果分散得特别均匀,跨卡通信又非常高。实际工程中,MoE 的 batch 大小和专家数需要一起调,不能孤立地看吞吐。
5. 分布式部署中的真实经验:专家并行与显存账本
5.1 大模型训练不是只靠一种并行
MoE 模型天然要求“专家并行”这个概念。数据并行解决“多卡同步梯度、加快训练”的问题,每个 GPU 都存模型全量副本,这对 MoE 来说不可行——专家权重在每张卡上重复驻留,显存浪费极大,而且路由跨卡通信会变得不可控。
张量并行解决单卡放不下某个大矩阵的问题,把专家内部的大 FFN 拆到多卡计算。专家并行才是 MoE 的专属武器:不同专家分布在不同的 GPU 上,token 按路由结果跨卡访问,训练和部署都依赖这张专家分布表。
现实中的大 MoE 训练很少只用一种并行,而是混合在一起。比如数据并行负责 batch 切分,张量并行处理 attention 公共部分的大矩阵,专家并行安置专家权重,再通过分层调度减少跨卡通信。这个“混合并行”设计,是 MoE 工程里真正拉开团队差距的地方。
5.2 专家并行与 all-to-all 通信的代价
专家并行的核心操作是一次 all-to-all 通信。把一批 token 的路由矩阵和向量打包,发到对应的目标 GPU;每个 GPU 把自己负责的专家算完后,再把结果按路由关系回传给原 GPU。这个过程伴随两次跨卡数据传输。
通信量与两个因素成正比:batch 大小和路由分散度。batch 越大、token 越是均匀打散到所有专家,每次传输的数据量就越大,GPU 互联带宽很容易变成瓶颈。这也是为什么专家数量不能无限增加:专家多了,模型总容量更大,但跨卡传输的碎片化开销会让收益曲线迅速变平。
业界常见的 64 到 128 个专家的配置,不是算出来的理论最优,而是在实测多轮之后大家不约而同停下来的经验拐点。再往上加,通信成本会吃掉专家数量带来的容量红利。
5.3 显存占用与推理服务的实际配置
这里要强调一个新手最容易搞错的点:MoE 省的是计算,不省显存。所有专家权重必须常驻显存,模型是多少参数,显存就要按多少权重来准备,哪怕某个 token 只激活 2 个专家。
我部署过 MoE 推理服务,权重分片是最基本的动作:把不同专家分到不同推理进程,公共部分尽量每个节点保留一份,专家部分按路由动态调用。请求进入后,先走公共注意力层,再路由到目标专家节点完成计算。配合动态 batch 调度,把同一个 batch 中路由到相同专家集合的请求聚合起来服务,能显著减少跨卡次数。
| 部署环节 | 关键动作 | 优先级 |
|---|---|---|
| 权重存放 | 专家按 ID 分片,公共层每节点保留 | 高 |
| 显存规划 | 按总参数规划,不是按激活参数 | 高 |
| 请求调度 | 按路由结果聚合到同一专家节点 | 中 |
| 通信优化 | 使用高带宽 GPU 互联网络 | 中 |
每次上线 MoE 模型,显存规划是我第一件要确认的事。宁可把余量留大一点,也不要卡着总参数量的下限去开服务,否则线上流量一波动,显存溢出的概率极高。
6. MoE 适合什么场景,以及我踩过的坑
6.1 MoE 的参数-算力解耦到底意味着什么
说完了原理和工程,回到一个更实用的问题:MoE 到底适合什么场景?
MoE 最大的价值,是把“模型的知识容量”和“单次推理的算力成本”分开。如果你需要模型知识量很大、能力很全面,又对单位请求的成本和吞吐量非常敏感,MoE 几乎是当前最现实的选择。高并发在线服务、海量 token 生成,稀疏激活带来的吞吐优势非常明显。
它尤其适合两类场景:一是公司内部的通用对话助手,知识面越广越好,但每次调用的成本直接关系到毛利率;二是内容生成类业务,比如批量总结、结构化抽取、代码辅助,这类任务并发量大,单请求延迟容忍度较高,MoE 的吞吐优势可以压得很足。
6.2 什么时候不要用 MoE
但我不建议无脑上 MoE。如果你的场景是单请求延迟极敏感、请求规模很小、显存又极度受限,MoE 的优势会被通信和权重驻留成本抵消。同样在单卡资源下跑一个小规模稠密模型,可能比跑一个同等显存需求的 MoE 模型更省心。
还有一个很多人忽视的坑:微调 MoE 模型比微调稠密模型更容易破坏原有能力。我做了个小规模实验,直接对某个 MoE 模型做全参微调,只调了几个 epoch,评估集就掉点。最终定位到问题是门控分布被训练数据带偏,不是专家本身的质量下降。后来改成冻结路由权重、只微调专家,效果才稳住。
如果你要微调 MoE,我强烈建议把路由部分当作“高危区”来对待。要么冻结,要么用很小的学习率去调,别让它随着微调数据自由漂移。
6.3 给新手的实践建议与技巧
如果你正准备把 MoE 引入自己的项目,我最后给几个具体建议:
- 从小规模开始。先搭 16 个专家、Top-2 的模型,跑通训练,观察专家 token 分布与 loss 曲线,再放大规模。
- 把负载均衡损失打印出来看。它和 loss 一样是需要长期监控的训练指标,不能等到训练结束才发现路由崩了。
- 评估推理性能时,一定用批量并发指标,不要用单条请求延迟。
- 微调 MoE 时,默认先把路由部分冻结;确有需要,再考虑对路由做低学习率微调。
- 显存预算按总参数算,别按激活参数算,否则上线第一天就会爆显存。
我自己在真正把 MoE 模型部署到生产环境之后,最大的感受是:它把“大模型很贵”这个固有印象修正了一下。万亿参数听起来吓人,但当你知道它只是把权重摆在那儿、每次只请几位专家出山工作时,这个数量级反而变得亲切起来。
最后再分享一个小技巧:上线之前,拿真实流量里的路由分布看一眼。如果发现某几个专家被高频选中、另一些专家几乎闲置,这不一定代表模型坏了,但至少说明当前数据分布和训练时差异很大,值得回到训练阶段重新校准。路由分布,永远是 MoE 系统里最值得信赖的一盏信号灯。