MoE架构解析:万亿参数模型如何以稀疏激活实现高效计算?

发布时间:2026/10/17 11:55:33
MoE架构解析:万亿参数模型如何以稀疏激活实现高效计算? 1. 核心认知参数规模和计算量很多人的第一反应都错了1.1 一个让大模型“变轻”的反直觉设计我最初看到“万亿参数”这个词的时候跟大部分人一样第一反应是“这得要多大的算力才能跑起来”。但后来实际接触并拆解了混合专家Mixture of ExpertsMoE架构的模型之后才发现这个直觉完全错了。MoE最有意思的地方恰恰在于它的总参数规模和实际运行时需要的计算量是两个维度的事。用白话解释就是你可以把一个MoE模型想象成一家大型综合医院。医院里挂了上千位专家的门诊牌但一个病人来就诊并不会见遍所有专家。分诊台对应门控网络会根据病人的症状只叫其中最对口的二三位专家出来看诊其他专家继续休息。最后把几位专家的意见汇总起来给出一个综合诊断结果。这就是“万亿参数”模型的核心逻辑模型文件里确实存储着数量庞大的参数但处理任何一个具体的输入时只有一小部分参数被真正加载进计算流程并参与运算。这部分被实际调用的参数规模就是所谓的“激活参数”active parameters。MoE模型的参数量和计算量因此在某种程度上被解耦了这在深度学习架构里是一个相当反直觉但又非常聪明的设计。1.2 闲置参数跑哪去了门控机制的第一眼印象那这些“闲置”的专家参数在推理的时候是不是完全没用也不是。它们更像是“备而不用的专业储备”。假设输入一句话是“如何用Python处理Excel数据”那么MoE模型的某个专家可能擅长代码生成另一个专家可能擅长数据结构两者组合起来能给出很漂亮的方案。而如果你输入的是“巴黎的咖啡馆推荐”被激活的可能完全是另一批专家。这里承担分诊任务的核心部件就是门控网络Router / Gating Network。很多MoE模型在架构上是把Transformer里的前馈网络FFN层替换成一层由多个专家组成的并行模块每个token都会先经过门控网络打分。门控网络本质上也是一个线性层加归一化操作它输出一个长度等于专家数量的分数向量再通过Softmax得到每个专家的权重。然后在训练和推理时只挑权重最高的前2到4个专家参与计算。这就是标题里“实际只用了很小一部分”的直接来源。如果你去算一笔账假设模型有64个专家每个专家是中等规模的FFN每次只路由到2个专家那么实际参与前向计算的参数大约只有全部参数的1/32。算力自然就省下来了。2. MoE的工作机理路由、专家与稀疏激活2.1 门控网络如何决定把token发给谁门控网络的输入通常就是当前token经过自注意力层之后的隐藏状态比如在Transformer解码器里先算完注意力输出然后送入一个线性层线性层的输出维度就是专家数量。这一步要特别注意的是门控网络的输出并不直接等于某个专家“被激活”而是代表每个专家对于当前token的“吸引力分数”。我们一般会对这个分数做两个操作用Softmax把分数压成概率分布保证所有专家权重和等于1。用Top-k比如Top-2筛出分数最高的k个专家剩余专家的权重直接置0。这样做的意义在于模型在标准的前向传播中不需要对全部专家做前向计算只需对选中的k个专家实际调用计算然后按权重加权求和。有一次我调试I/O时把门控网络的布点打印出来发现一个很有趣的现象模型在训练早期门控的分数分布比较乱随着训练推进不同的专家会逐渐对某些特定类型的token表现出明显的“偏爱”。比如有些专家偏代码有些专家偏数学公式有些专家偏中文有些偏英文。这种自组织式的分工不需要任何显式标签完全是靠梯度和正则化压力自己长出来的这是MoE非常迷人的一点。2.2 Top-k选择为什么是“几个”而不是“全部”如果直接把FFN层替换成“专家模块”理论上也可以把每个token分给所有专家然后加权平均所有输出。但这样做毫无意义计算量一下子涨回原来的水平等同于是个更笨重的大模型还失去了稀疏计算带来的速度优势。所以Top-k机制是MoE省算力的关键。k到底取多少合适我看过不少实验大家最常用的值是2。为什么不是1也不是3k1的时候路由选择过于“武断”。一旦门控给某个专家的分数只高出第二名一点点第二名其实也有很强的处理能力直接丢失这部分信息太可惜。k3的时候模型的表达力稍微增强但训练时负载均衡会更难控制通信开销也跟着上涨边际收益递减。k2是多年的实践中一个比较均衡的选择至少保留一个备选专家来兜底又不至于把路由决策的难度拉得太高。所谓“稀疏激活”指的就是这么回事。计算效率体现在不需要把整个MoE层全部计算一遍只需要选择k个专家做计算其他专家相当于“休眠”状态。这里有个很容易误解的点尽管休眠专家本次没有参与计算但在内存中模型的全部参数仍然要被加载。因为下一个token可能选择的是另一批专家。所以MoE模型在推理时的内存占用和“全参模型”是一样的仅仅是FLOPs变小了。2.3 训练时的负载均衡避免所有token都挤同一专家既然门控网络是学习出来的就会存在一个非常实际的训练难题如果不加约束门控网络很快会被训练得“偏心”——总是把大多数token分配给某几个专家其他专家几乎接不到训练样本梯度也少。这种情况俗称“专家坍缩”或“路由崩溃”。为了防止这个问题MoE训练时通常会额外加入一个辅助损失auxiliary loss最经典的做法是计算“负载均衡损失”。它的核心思想非常朴素希望每个专家被选中的次数尽量均匀。负载均衡损失的常见形式是计算每个专家在batch中接收token的比例记为f_i计算门控网络给每个专家分配的平均概率记为P_i累加两者乘积再乘以专家数量作为额外惩罚如果路由完全均匀所有专家平分token这个损失会取到最小值如果路由越偏心损失越大。这个损失项在训练时加到总loss里通过梯度反向传播影响门控网络让路由分配尽量均衡。但这里有个小经验负载均衡损失的系数不能设置太大否则会压制门控网络的选择倾向让所有专家变成“平均主义者”失去专业分工的意义。我试过几个系数发现0.01量级通常是比较稳妥的起点具体还要根据模型规模微调。触发这个问题的场景很常见如果你在训练初期数据分布特别单一比如全部是中文文本而模型里又有一个专家的初始权重让它在中文上输出分布更均匀那这个专家很快会把大部分流量吸走。所以很多人会建议在训练初期就把路由的dropout调大一点或者给专家初始化和门控网络的初始化设置更小的scale能有效降低坍缩概率。3. 自己动手实现一个最小可运行的MoE模块3.1 引入必要的依赖和基础结构讲理论容易飘直接看代码更踏实。我用PyTorch写一个最小化的MoE层适合用来做实验和理解原理。这个实现规模不需要大重点是把“门控选专家”和“稀疏加权合成”这两个核心逻辑跑通。先定义基础依赖import torch import torch.nn as nn import torch.nn.functional as F然后定义专家网络。为了简化我把每个专家设计成两层的FFN输入输出维度都一样方便后续做残差连接。class Expert(nn.Module): def __init__(self, d_model, d_ff): super().__init__() self.w1 nn.Linear(d_model, d_ff) self.w2 nn.Linear(d_ff, d_model) self.act nn.GELU() def forward(self, x): return self.w2(self.act(self.w1(x)))这里的d_model是隐藏层维度d_ff是FFN内部的中间维度。实际模型里的专家可以更复杂比如用多层的Transformer子块但作为Demo两层Linear加GELU已经足够把机制说明白。3.2 定义专家网络和门控路由接着是MoE层的主体包含两个重要成员一个nn.ModuleList用来装所有专家一个nn.Linear用来做门控打分。class MoELayer(nn.Module): def __init__(self, d_model, d_ff, num_experts, top_k, dropout0.1): super().__init__() self.num_experts num_experts self.top_k top_k self.experts nn.ModuleList([Expert(d_model, d_ff) for _ in range(num_experts)]) self.gate nn.Linear(d_model, num_experts) self.dropout nn.Dropout(dropout) def forward(self, x): batch, seq_len, _ x.shape # 把token打平成行向量方便统一处理 x_flat x.view(-1, x.size(-1)) # 门控打分 gate_logits self.gate(x_flat) gate_scores torch.softmax(gate_logits, dim-1) # 选出top-k专家 top_k_scores, top_k_experts torch.topk(gate_scores, self.top_k, dim-1) # 归一化top-k权重 top_k_scores top_k_scores / top_k_scores.sum(dim-1, keepdimTrue) # 初始化输出 out torch.zeros_like(x_flat) # 循环选中的专家逐专家计算 for e_idx in range(self.num_experts): mask (top_k_experts e_idx).any(dim-1) if not mask.any(): continue expert_input x_flat[mask] expert_output self.experts[e_idx](expert_input) # 找出该token在top-k里对应权重 weight top_k_scores[top_k_experts e_idx].unsqueeze(-1) out[mask] expert_output * weight out self.dropout(out) return out.view(batch, seq_len, -1)这段代码有几个容易出错的地方我在调试时踩过坑重点说明一下top_k_scores在选出top-2之后它们的和不一定等于1因为只保留了概率最大的两个分量。如果不重新归一化输出数值的scale会跟模型预期不一致训练时会出现莫名其妙的梯度震荡。所以我在代码里做了除和操作让参与计算的权重和恢复为1。mask (top_k_experts e_idx).any(dim-1)这一行是为了把所有选中了当前专家的token同时计算而不是逐个token循环否则训练速度会极其感人。逐个专家循环看起来笨拙但逻辑直观实际工程里一般会用scatter或者分组矩阵乘法做更高效的处理。这里每个专家只做一次前向计算对于没有选中该专家的token自然不会产生计算量。这就是稀疏激活在代码层面最直接的体现。3.3 计算细节使用gate值加权合成输出上面计算out的时候有一个细节值得展开。每个token的最终输出并不是简单地把选中的专家输出相加而是按照门控网络给出的权重做加权求和。打个比方两个专家给出的输出分别是向量A和向量B门控给它们的权重分别是0.7和0.3那么最终输出是0.7A 0.3B。这个权重本身是由门控网络根据token内容动态算出来的所以模型的表达能力比单专家更强。我一开始写的时候犯过一个很常见的错误直接用scores而不是top_k_scores去加权。这会导致非选中的专家权重也被加入求和。虽然数学上看起来不复杂但对稀疏性约束就失效了本质上退化成了“所有专家都参与”的稠密模型计算开销也没省下来。所以务必记得只对选中专家做加权。另外那个循环绑定权重的写法虽然逻辑是对的但有一处需要确认top_k_scores[top_k_experts e_idx]顺序是否跟expert_input对应。如果同一个batch里有多个token选中同一个专家mask取出来顺序跟原始x_flat是一致的因此权重序列也能对齐。这个我在实践里验证过多次是安全的。当然如果你要追求性能完全可以改成更高效的向量化实现但对于学习性质的Demo保持这种可读性是有价值的。3.4 完整训练小示例我把这个MoE层塞进一个极小的语言模型里做一个玩具级别的训练验证。这里不是为了追求效果主要是让整个流程能跑通让大家看到训练loss能正常下降。class TinyMoEModel(nn.Module): def __init__(self, vocab_size, d_model, d_ff, num_experts, top_k): super().__init__() self.embed nn.Embedding(vocab_size, d_model) self.moe_layer MoELayer(d_model, d_ff, num_experts, top_k) self.lm_head nn.Linear(d_model, vocab_size) def forward(self, x): h self.embed(x) h self.moe_layer(h) logits self.lm_head(h) return logits训练数据我随便构造了一段很短的序列模拟语言模型的下一个词预测任务import math def generate_data(): data torch.randint(0, 100, (16, 32)) return data model TinyMoEModel(vocab_size100, d_model64, d_ff128, num_experts4, top_k2) optimizer torch.optim.Adam(model.parameters(), lr1e-3) for step in range(200): tokens generate_data() logits model(tokens[:, :-1]) loss F.cross_entropy(logits.reshape(-1, 100), tokens[:, 1:].reshape(-1)) optimizer.zero_grad() loss.backward() optimizer.step() if step % 50 0: print(fstep {step}, loss: {loss.item():.4f})如果loss稳定下降说明整条链路是通的。你可以尝试把num_experts从4改成16会发现计算时间并没有线性增长因为每次固定只算top-2个专家专家数量的增加只是多了一堆“备选”不会拖慢太多前向耗时。这恰恰就是MoE规模化的底气。4. 实践中的常见问题与排查技巧4.1 专家坍缩Router Collapse如何规避专家坍缩是MoE训练里最经典的坑表现为某一个或某几个专家获得了绝大多数的路由分数其余专家几乎拿不到token。从指标上看非常明显你可以统计每个专家收到的token数量如果发现某个专家的接收占比超过70%基本就是坍缩了。规避方法我总结下来有三板斧辅助负载均衡损失前面已经说过这是最直接的约束手段。但要注意我建议别把辅助损失直接加在总loss上就不管了可以顺手在日志里打印f_i和P_i的乘积观察到它保持在一个低水平时说明路由是平衡的。更强的dropout对专家内部的隐藏层和门控输出都加dropout强制每个专家不能过度依赖固定特征也就减少了一家的“垄断”机会。初始化调整把门控线性层的scale调小一点让初始logits更平缓路由初始状态就更均匀。一个常见的做法是用nn.init.normal_(self.gate.weight, mean0.0, std0.02)而不是默认的均匀分布。如果已经发生坍缩还有一个临时办法手动给长期接不到token的专家用周边其他专家参数做插值初始化然后调低学习率重新训练一段时间。这是我在某个实验里用过的应急方案效果还行但治标不治本根本解法还是得靠辅助损失和合理初始化。4.2 推理阶段的内存占用为何降不下来前面提到过一个反直觉点MoE虽然省了FLOPs却不省显存。很多人以为模型“参数大但计算少”那么推理时的显存占用也会很小实际试了才发现全部参数都得塞进显存里才能跑。原因不难理解推理过程中每个token自己选择专家前一个token可能选专家3和7后一个token可能选专家11和15。你无法预先知道谁会选谁所以所有专家都必须常驻在内存里随时待命。这就像医院虽然每次只用两三个专家门诊但你不能把其他专家赶回家因为他们随时可能被叫到。因此MoE模型在实际落地推理时往往要配合“专家并行”的分布式策略来做。把不同专家放到不同的GPU上门控结果出来后通过All-to-All通信把token发到对应专家所在的卡上计算完再收回来。这个通信开销在某些场景下会成为新的瓶颈特别是专家数量多、单次token量大的时候。4.3 通信负载与并行策略选择MoE在单卡上做实验很简单但在大规模训练或部署时并行设计直接决定性能上限。我见过不少团队在一开始就把模型铺到很多GPU卡上结果发现每步训练的时间反而比稠密模型还慢。原因是所有专家之间的通信太频繁大量时间花在了all_to_all交换上。一个常规选择是“尽量让每个token的数据在本地多停留一会儿”也就是所谓的local路由变体或者在设计上将部分专家做成本地处理、只有少数跨机路由。实践中会看到几种主要流派专家并行专家分布到不同机器上适用于超大模型。数据并行与专家并行混合每个数据并行组内复制模型和专家减少跨机通信适合专家规模适中的场景。序列并行与专家并行组合在序列维度切分数据让多个token共享一批专家计算提高GPU利用率。我个人的经验是如果你的模型参数量在几十亿量级优先考虑单机多卡如果到了几百亿上千亿再上真正的跨机专家并行。不然过早引入复杂的通信架构排查问题的时间会比训练时间还长。5. 我的实际体会跑过几轮MoE实验之后我最深的感触是MoE并不适合所有场景但它是一种极具“经济性”的扩大模型容量的思路。它对算力和显存的需求是两条不同的曲线参数量在涨但单位token的算力消耗增长远低于参数量的增长速度这也是“万亿参数”模型能落地的根本原因。在实际使用中我最喜欢做的一个小工具是打印每个step的路由分布统计。只要看到专家被调用的频率柱状图就基本能判断模型健康与否。一个均衡健康的MoE模型柱状图应该像理发店门口的三色灯柱一样分布均匀。只要出现一根柱子高高突起就得立刻检查负载均衡损失和门控初始化。如果你正在考虑把模型升级成MoE我的建议是先从8个专家、Top-2开始跑通之后再逐步增加专家数量。不要一上来就堆几百个专家否则通信问题和负载均衡问题会让你寸步难行。等你能熟练地看懂路由统计、熟练地调门控参数之后再去挑战更大的规模会从容得多。最后再分享一个小技巧你在评估MoE模型的推理速度时不要只盯着FLOPs实际吞吐量更受限于内存带宽和通信。尤其在显存带宽较窄的设备上激活参数虽然少但每层都要把专家权重从显存搬到计算单元这个过程本身就有代价。所以对一个具体的部署场景最终还是要以实际跑出来的延迟和吞吐为准。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询