MoE:稀疏激活
一句话定位:MoE(混合专家)把 FFN 层换成「一排专家 + 一位路由器」,每个 token 只激活最相关的少数专家——参数管够、干活按需,671B 的模型只算 37B 的账。本文拆开路由、负载均衡与共享专家,回答「辅助损失为什么是必要的恶」。前置:Attention 与 Transformer——先知道 FFN 在层里的位置。
先看一个「变大不涨价」的模型账本
2024 年 12 月,DeepSeek-V3 发布:总参数 671B——比当时最大的开源稠密模型还大一半;但技术报告写明,每个 token 只激活其中 37B。参数规模翻了几倍,单 token 的计算量却只有同尺寸稠密模型的零头。
矛盾在这:Scaling Law(规模定律)说参数越多越聪明,可参数全激活(稠密模型的做法)意味着计算、显存、成本全跟着参数一起涨——想要更聪明的模型,就得先问问钱包。
能不能「参数管够,干活按需」?
MoE 的答案:把 FFN 层换成一大排「专家」,每个 token 由路由器挑出最相关的几个来干活——其余专家这一轮歇着。参数总量可以继续堆,每个 token 的计算量保持不变。先看它在层里的位置:
图:MoE 层全景——注意力不动,FFN 换成路由器 + 专家池(DeepSeek-V3 为 256 选 8)+ 共享专家;每 token 只算被激活的专家
渐进拆解:挂号处、专科门诊与全科
类比:把 MoE 层想成一家医院。病人(token)先到挂号处(路由器),按症状分诊到最对口的两个科室(top-2:打分取前两名);不需要全院会诊(稠密激活)。医院还保留一个全科门诊(共享专家)——量血压、问病史这些人人都要的项目,不劳专科。科室分得越细(专家越多),医院能接的病种越广(容量越大),但每个病人只看两个科,花费不变。
稀疏激活:动 FFN,不动注意力
Transformer 层里 FFN 才是参数大头——按 Qwen2-7B 官方配置与官方总参数实算,FFN 约占 75%,注意力约占 11%。MoE 化只对 FFN 动手:把一个大全连接层拆成 N 个小的「专家 FFN」,注意力层原样保留。关键手法是拆小:专家的宽度比原 FFN 窄,这样「top-k 个专家的计算」才与原 FFN 相当——参数翻倍靠堆专家数量,计算量靠 top-k 锁死。
这就是「稀疏激活」四个字的含义:参数都在显存里,但每个 token 只让一小部分参与计算。另外并非每层都要换——DeepSeek-V3 只把前 3 层之外的 FFN 换成 MoE,前 3 层保持稠密(技术报告配置)。
「拆小」怎样同时做到参数变大、计算不变?用「动手」节的玩具配置(8 个专家取 2)把这本账画成图:
图:拆小 + top-k 的记账机制——专家宽度减半、数量堆到 8 个(总参数 ×4),每 token 只算被选的 2 个(激活 2.10M,计算 1×)
路由:top-k 打分与它的塌缩病
路由器本身是一个极小的网络,给每个 token 对每个专家打分、取前 k 名(与采样篇的 top-k、长上下文篇 DSA 的 top-k 同名同思想:都是「打分留前 k」——分别挑「生成谁」「看谁」「谁干活」),被选中的输出按分数加权合并——长上下文篇 MoBA 借的正是这套「按分挑块」的路由思想,只是它的门控不带可学习参数。
听起来完备,但训练中有个臭名昭著的病:路由塌缩。开局各专家随机略分高下,被多选的专家得到更多训练、变得「更擅长被选」,于是被选得更多——赢家通吃的正反馈滚起来,最后所有 token 都挤进少数几个专家,其余专家闲成摆设:参数一大堆,有效模型却只剩个小号稠密网络。
负载均衡:两代药方
防塌缩的经典药方是辅助负载均衡损失(Switch Transformer 时代确立):在主损失之外加一条惩罚项,哪个专家被选太多就罚谁,逼着路由把 token 摊匀。说它「必要」,是因为没有它就塌缩——这是用大量实验换来的教训;说它是「恶」,是因为它强行干预了路由的专业化:token 本该跟着「对谁最有利」走,辅助损失却逼它「哪冷门去哪」,专家被迫同质化,主任务的收益被均衡目标啃掉一角。两害相权,此前的主流 MoE 普遍采用这套做法。
直到 DeepSeek-V3 给出辅助损失免除方案:把均衡惩罚改成给每个专家记一个动态偏置——偏置只影响「谁被选中」,不参与梯度、不碰专家本身的学习;哪个专家最近接单太多就下调它的偏置,负载自动摊平,专家的专业化不再被惩罚项扭曲。「去掉会怎样」的完整答案分两半:直接去掉辅助损失——塌缩,训练废掉;聪明地去掉——用不进梯度的偏置完成同样的均衡,V3 证明可行,这也成了新一代 MoE 的起点。严格说 V3 并非零辅助损失:报告里另留一条只防单序列内极端失衡的微量辅助损失,权重小到不干扰专业化。
图:路由塌缩的正反馈与两代药方——辅助损失防住塌缩但压制专业化,DeepSeek-V3 的偏置调节(不进梯度)两全
共享专家:公共知识的专用通道
DeepSeekMoE 提出、自 V2 起大规模应用、V3 沿用的「共享专家隔离」观察到:无论路由怎么挑,总有一部分知识每个 token 都用得上——语法、常见搭配这类「公共课」。把它们塞进路由专家,等于每个专家都要重复学一遍。共享专家隔离的做法:设一两个「人人必过」的共享专家承接公共知识,路由专家只学差异化的长尾——256 个路由专家每 token 激活 8 个,外加 1 个共享专家(DeepSeek-V3 配置)。专家分工更干净,同参数下的有效容量更高。
训练与推理:两张不一样的账
训练侧的账在「均衡」:辅助损失(或偏置调节)盯负载,配上专家并行(专家分放多卡,token 经 all-to-all 通信送到专家所在卡)与容量因子等工程手段——容量因子是经典做法(Switch 时代):给每个专家设「接单上限」,溢出的 token 走残差直通,防单个专家排队拖垮整批;V3 靠均衡改善已不再丢 token。
推理侧的账在「显存与计算的错位」:省的是计算,不省显存——每个 token 只算被选中的专家,但路由逐 token 动态、事先不知道会用到谁,一个专家都不能少装:671B 按 bf16(bfloat16 半精度,每参数 2 字节)折算约 1.3 TB(671×2 字节,从报告参数量直接折算),部署门槛全在这一项。解码时同 batch(一次并行处理的一批 token)里各 token 挑的专家越集中,显存读取越划算——这也是 MoE 推理框架优化的主战场:
图:MoE 训练账(均衡、并行、容量上限)与推理账(显存全驻、计算按需)——省计算不省显存是部署门槛的根源
大白话:MoE 就是一家分科大医院——挂号处按症状分诊(路由 top-k),专科只看分进来的病人(稀疏激活),全科门诊人人必过(共享专家);「辅助损失」是防科室挤破头的行政摊派,副作用是逼专科变全科;DeepSeek-V3 换成排队叫号:只调顺序,不动大夫本事。
技术定义:MoE 层以门控网络(router)对每个 token 计算各专家的亲和分数,取 top-k 个专家的 FFN 输出按门控权重求和(常配共享专家恒激活)。负载均衡通过辅助损失(auxiliary load-balancing loss)或辅助损失免除的动态偏置(auxiliary-loss-free)实现。总参数(total)与激活参数(activated)分别计量——总参数买容量、激活参数付计算,各模型数字见「动手」节的参数账。
动手:算一本 MoE 的参数账
真实模型的「总参数 vs 激活参数」两笔账先摆在一张表里(原始数字出自各技术报告与官方模型卡,占比为自算):
| 模型 | 总参数 | 激活参数 | 激活占比 |
|---|---|---|---|
| DeepSeek-V3 | 671B | 37B | 5.5% |
| Mixtral 8x7B | 46.7B | 12.9B | 27.6% |
不依赖任何库,把「参数 ×4、计算不变」亲手算出来:
def ffn_params(d_model, d_ff):
return 2 * d_model * d_ff # 两层全连接(升维 + 降维);真实模型多为 3 矩阵(SwiGLU),此处取经典两层便于心算
d_model = 512
dense = ffn_params(d_model, 2048) # 稠密基线:单个 FFN
expert = ffn_params(d_model, 1024) # MoE:专家宽度减半
experts, top_k = 8, 2
total = experts * expert # 参数全在
active = top_k * expert # 每 token 只激活 top_k 个
print(f"稠密 {dense/1e6:.2f}M | MoE 总参数 {total/1e6:.2f}M | 每 token 激活 {active/1e6:.2f}M")
print(f"参数比 {total/dense:.1f} 倍,计算比 {active/dense:.1f} 倍")
实测输出:稠密 2.10M、MoE 总参数 8.39M、每 token 激活 2.10M——参数 4 倍,计算 1 倍。换成真实模型验算激活比:DeepSeek-V3 37/671 = 5.5%,Mixtral 12.9/46.7 = 27.6%(总参/激活参出自各自技术报告与官方模型卡,比率为自算)。再看塌缩的代价:8 专家取 2 的层,若路由塌缩到只认 2 个专家,其余 6 个专家每层 6.29M 参数闲置——参数利用率从 8/8 跌到 2/8,这就是负载均衡非管不可的原因。
边界与取舍
MoE 不是免费的
三笔隐性账:
- 显存全驻:省计算不省显存,部署按总参数估
- 路由开销:打分与 token 分发本身要算,批太小、路由太散时专家空转
- 训练门槛:均衡超参、路由器学习率、专家并行通信——微调 MoE 比稠密更容易翻车
显存预算紧、batch 小、只想微调的场景,稠密小模型往往比 MoE 大模型更划算。判断手里是不是 MoE 有个土办法:看模型卡与 config.json——出现 num_experts、num_experts_per_tok(或 n_routed_experts、moe_intermediate_size)这类字段就是,顺手把总参/激活参、专家数、top-k、均衡方式一次看齐。
稀疏激活与稀疏注意力是两码事
名字都带「稀疏」,对象完全不同:长上下文篇的稀疏注意力省的是「token 之间两两打分」的计算,本篇的稀疏激活省的是「FFN 参数参与计算」的份儿——一个动注意力,一个动 FFN,现代大模型(如 DeepSeek-V3.2)两者都用。
坏味道预警:拿「激活参数」当「模型大小」
「37B 模型」这类说法在 MoE 语境里是危险的简写——能力上限由 671B 总参数决定,显存门槛也是 671B 级;激活参数只告诉你单 token 计算多贵。评估「跑不跑得动」看总参数,评估「跑多快」看激活参数,两件事别混。
自测
合上文章先自己答,再展开对照:
负载均衡 auxiliary loss 为什么是「必要的恶」?去掉会怎样?
「必要」:路由有赢家通吃的正反馈——开局略占优的专家被选更多、训练更多、于是更常被选,塌缩到最后少数专家包场,其余参数闲置,MoE 退化成小号稠密模型。辅助损失在主损失外惩罚过载专家,防住塌缩,这是大量实验换来的底线。「恶」:它强行干预路由的专业化——token 本该跟着「对谁最有利」走,惩罚项却逼着它「哪冷门去哪」,专家被摊平、同质化,主任务收益被啃掉一角。「去掉会怎样」分两半:直接去掉——塌缩,训练废掉;聪明地去掉——DeepSeek-V3 的辅助损失免除方案,用只影响「谁被选中」、不进梯度的动态偏置完成负载摊平,专业化不再挨罚(严格说 V3 仍保留一条防单序列内极端失衡的微量辅助损失),新一代 MoE 以此为起点。
MoE 推理时「省什么、不省什么」?为什么部署门槛按总参数算?
省计算不省显存:每个 token 只激活 top-k 个专家(DeepSeek-V3 激活 37B/671B = 5.5%),单 token 的计算量很小;但路由是逐 token 动态的,任何 token 都可能用到任何专家,全部专家的参数必须常驻显存——671B 按 bf16 折算约 1.3 TB。所以「跑不跑得动」看总参数(显存门槛),「跑多快」看激活参数(计算量),拿「激活 37B」当模型大小是危险的简写。共享专家每 token 必过,进一步把公共知识从路由专家里剥离。
参考与延伸
- DeepSeek-AI《DeepSeek-V3 Technical Report》(2024):671B/37B、256 选 8 加共享专家、辅助损失免除的原始出处 — arxiv.org/abs/2412.19437
- Jiang 等《Mixtral of Experts》(2024):8 专家取 2 的经典 MoE(官方模型卡 46.7B/12.9B,论文正文取整写 47B/13B) — arxiv.org/abs/2401.04088
- Dai 等《DeepSeekMoE: Towards Ultimate Expert Specialization》(2024):共享专家隔离与细粒度专家 — arxiv.org/abs/2401.06066
- Fedus 等《Switch Transformers》(2021):辅助负载均衡损失的确立与万亿参数 scaling — arxiv.org/abs/2101.03961
- DeepSeek-V3 官方模型页:总参/激活参数与配置的一手来源 — huggingface.co/deepseek-ai/DeepSeek-V3
下一站:推测解码与 MTP——MoE 把「每 token 计算量」压下来之后,下一个瓶颈是解码一次只能出一个字:能不能一次验多个?