模型架构 · MoE
学习舱 / 模型架构 / MoE

MoE:稀疏激活

整理中 深度 · 应用 更新 2026-09-22

前置 Attention

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 被换掉,注意力不动 注意力层 与稠密模型相同 ① 路由器 给每个专家打分 ② 选 top-2 只激活最相关的 2 个 ③ 共享专家 每个 token 必过 专家池(示意:真实模型数十到数百个专家) 专家 1 ✓ 专家 2(歇) 专家 3(歇) 专家 4 ✓ …(歇) 专家 256(歇) 每 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:参数 ×4,计算不变 稠密 FFN(基线) 单层宽 2048 参数 2.10M · 计算 1× 拆成 8 个窄专家 每个宽 1024:比原 FFN 减半 专家 1 ✓ 专家 2 ✓ 专家 3 专家 4 专家 5 专家 6 专家 7 专家 8 top-2:只有被选中的 2 个在算 每 token 只进 2 个 激活参数 2.10M 计算 1×,与稠密 FFN 相当 其余 6 个这一轮歇着 显存里 8 个参数都在 总账:总参数 8.39M(×4),每 token 激活 2.10M(1×)——参数 ×4 靠堆专家数量,计算不变靠 top-k 锁死

图:拆小 + 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 并非零辅助损失:报告里另留一条只防单序列内极端失衡的微量辅助损失,权重小到不干扰专业化。

路由塌缩与两代药方 病:路由塌缩(赢家通吃) 开局略占优的专家 → 被选更多 → 训练更多 → 更常被选 → 循环滚大:最后少数专家包场,其余闲置 药方一代:辅助负载均衡损失 主损失外加一条惩罚:谁被选爆了罚谁 有效:防住塌缩(Switch Transformer 起普及) 代价:压制专业化,与主目标打架 药方二代:偏置调节(V3 起) 每专家记一个动态偏置:接单多就降偏置 偏置只影响「选谁」,不进梯度、不碰专家学习 效果:负载照样摊平,专业化不挨罚 「必要的恶」终章:恶不可怕,可怕的是没有替代——偏置方案一出,它就功成身退 类比:辅助损失像行政摊派(为均衡硬派单);偏置调节像排队叫号(只调叫号顺序,不动大夫水平) 读法:上下是「问题与两代答案」;判断新 MoE 论文的均衡设计,先问「惩罚进不进梯度」

图:路由塌缩的正反馈与两代药方——辅助损失防住塌缩但压制专业化,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 的两张账:训练盯均衡,推理盯错位 训练侧:防塌缩、摊负载 · 辅助损失或动态偏置:盯住各专家接单量 · 专家并行:专家分放多卡,token 走 all-to-all · 容量因子(经典做法):设接单上限;V3 已不丢 token 核心指标:各专家负载方差(越平越健康) 推理侧:显存与计算的错位 · 显存:全部专家常驻——671B×2 字节≈1.3 TB · 计算:每 token 只算激活的 5.5%(37B/671B) · 批内 token 各挑各的专家,命中率影响访存 · 估部署门槛看总参数,估速度看激活参数 核心指标:显存驻留量 × 每 token 激活计算 一句话:训练怕「有人累死有人闲」,推理怕「全家都得住着、干活只叫两个」 1.3 TB 折算口径:671B 参数 × bf16 每参数 2 字节 = 1342 GB,原始参数量出自技术报告 读法:两张账的指标不同——别用训练侧的「负载均衡」回答推理侧的「显存」问题

图:MoE 训练账(均衡、并行、容量上限)与推理账(显存全驻、计算按需)——省计算不省显存是部署门槛的根源

大白话:MoE 就是一家分科大医院——挂号处按症状分诊(路由 top-k),专科只看分进来的病人(稀疏激活),全科门诊人人必过(共享专家);「辅助损失」是防科室挤破头的行政摊派,副作用是逼专科变全科;DeepSeek-V3 换成排队叫号:只调顺序,不动大夫本事。

技术定义:MoE 层以门控网络(router)对每个 token 计算各专家的亲和分数,取 top-k 个专家的 FFN 输出按门控权重求和(常配共享专家恒激活)。负载均衡通过辅助损失(auxiliary load-balancing loss)或辅助损失免除的动态偏置(auxiliary-loss-free)实现。总参数(total)与激活参数(activated)分别计量——总参数买容量、激活参数付计算,各模型数字见「动手」节的参数账。

动手:算一本 MoE 的参数账

真实模型的「总参数 vs 激活参数」两笔账先摆在一张表里(原始数字出自各技术报告与官方模型卡,占比为自算):

模型总参数激活参数激活占比
DeepSeek-V3671B37B5.5%
Mixtral 8x7B46.7B12.9B27.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 不是免费的

三笔隐性账:

  1. 显存全驻:省计算不省显存,部署按总参数估
  2. 路由开销:打分与 token 分发本身要算,批太小、路由太散时专家空转
  3. 训练门槛:均衡超参、路由器学习率、专家并行通信——微调 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 计算量」压下来之后,下一个瓶颈是解码一次只能出一个字:能不能一次验多个?

本节点大纲与自测题(3 个子点 · 1 问)

大纲

  • 稀疏激活原理与 FFN 的 MoE 化
  • 路由:top-k、负载均衡、共享专家
  • 训练与推理阶段 MoE 的差异

自测——答不上来就是缺口

  • 负载均衡 auxiliary loss 为什么是「必要的恶」?去掉会怎样?