一个 671B 总参数的模型,为什么在成本上能和几十 B 的稠密模型打平?答案藏在两个数字之间的那条缝里:总参数量与激活参数量。理解这条缝,你就能解释模型选型里最反直觉的那些现象。
编辑部注 · 本章最容易答错的是一道看似简单的推理题:稀疏激活省的是算力还是显存?请务必读到最后一节。
模型参数有两个口径,混用会让人得出完全相反的结论。
第一个是 总参数量(Total Parameters)——模型文件有多大,要占多少显存去装。第二个是 激活参数量(Active Parameters)——每生成一个 token,实际参与计算的有多少。稠密模型(Dense Model)里这两个数字相等;混合专家(Mixture of Experts)把它们拆开了。
一、两个数字之间的缝
设一个 MoE 模型有 256 个专家(Expert),每个专家约 2.6B 参数,每层路由稀疏激活(Sparse Activation)其中 8 个。
| 口径 | 稠密模型 | MoE 模型 | 工程后果 |
|---|---|---|---|
| 总参数量 | 与激活量相同 | 所有专家之和,例如 671B | 决定显存下限:权重得装得下 |
| 激活参数量 | 同上 | 每 token 只走 top-k 个专家,例如 37B | 决定单 token 算力:接近一个 37B 稠密模型 |
| 每 token FLOPs | 与总参数成正比 | 与激活参数成正比 | 吞吐高、单价低 |
| 跨设备通信 | 少(同层并行即可) | 多(专家分布在不同设备,需 All-to-All) | 延迟与实现复杂度上升 |
| 训练稳定性 | 较稳 | 路由易坍缩,需负载均衡损失 | 训练工程难度更高 |
一句话概括 MoE 的交易:用显存和通信换算力效率。参数总量上去了(容量变大),但每个 token 的计算量没跟着上去——由门控网络(Gating Network)在运行时挑出少数专家来算。
MoE 不是让模型变小,而是让「不参与计算的参数」变得便宜。本刊编辑部
二、路由是怎么工作的
三、省的是算力,不是显存
这是本章最重要的一句话,也是最容易答错的地方:
错误说法:「MoE 因为只激活部分专家,所以显存需求也降低了,能在小显存上部署大模型。」
为什么错:路由是运行时根据输入动态决定的。你无法预知下一个 token 会选哪两个专家,所以所有专家的权重都必须常驻显存(或在需要时快速换入,代价极高)。MoE 压缩的是每 token 的 FLOPs,不是权重的存储体积。
正确说法:MoE 让「参数容量」和「单 token 算力」解耦——你可以拥有 671B 的知识容量,却只付 37B 的算力账单;但显存下限仍然由 671B 决定。
由此衍生出几个工程结论:
- MoE 的部署门槛是显存,不是算力。 这就是为什么 MoE 模型通常只在云端大规模集群上跑,很难本地化。
- MoE 的延迟优势不如吞吐优势明显。 单请求延迟受 All-to-All 通信 影响,可能反而比同激活量的稠密模型更慢;但吞吐(单位时间处理 token 数)会好很多。
- MoE 对 batch 敏感。 batch 越大,专家利用率越高,单位成本越低。这解释了为什么小 batch 场景下 MoE 的性价比优势会缩水(也更容易出现路由坍缩(Routing Collapse),需要负载均衡损失(Load Balancing Loss)来对抗)。
四、专家怎么分布与并行
前面讲的是「单个 MoE 层里发生了什么」,这一节把镜头拉到整个集群——671B 的权重,物理上到底放在哪、怎么分工。这决定了一个 MoE 模型能不能上线、以及它为什么难本地化。
第一个事实:总参数太大,单卡根本装不下。 671B 参数按 fp16 算约 1.3 TB,远超单张 GPU 的显存(80GB 或更小)。所以专家必须被切开放到多张卡上——这就是专家并行(Expert Parallelism)(EP):不同专家住在不同设备,门控网络选出专家后,token 要被送到对应设备上算。
第二个事实:把专家分开,就引出了通信这个新代价。 一个 token 被选中的两个专家可能分布在两张不同的卡上,于是每层都要做一次 All-to-All 通信:把各卡的 token 按「目标专家」重新分发,算完再送回原卡。这张图就是这一节要讲的「分布 + 通信」:
由此引出三个可直接用于选型与面试的结论:
- 专家并行度和通信开销成正比。 专家分得越散(并行度越高),单卡显存越省,但每层 All-to-All 的固定开销越高。部署时要在「显存装得下」和「通信扛得住」之间找平衡点,不是越高越好。
- 这解释了「MoE 延迟不如吞吐」的深层原因。 单请求延迟被跨卡搬运拖累,但高并发下大量 token 可以「凑成一批」一起分发,通信被摊薄,吞吐反而高。所以 MoE 适合高并发、可批量的云端,不适合低并发、强延迟敏感的本地。
- 「未激活的专家」不是可以卸载的。 有人会问:能不能把没选到的专家换出显存、省显存?答案是否定的——路由是运行时动态决定的,你无法预知下一个 token 会选谁;换入换出的代价远高于常驻。所以显存下限始终由总参数量决定,专家并行只是把这份「必须常驻」的总量分摊到多张卡,而不是削减它。
这一节补上了 MoE 成本结构的最后一块拼图:容量看总数、算力看激活、显存看总数、通信看并行度。记住这四句,MoE 相关的面试题基本都能兜住。
五、动手:三十行感受一次路由
import numpy as np
def softmax(x, axis=-1):
m = x.max(axis=axis, keepdims=True)
e = np.exp(x - m)
return e / e.sum(axis=axis, keepdims=True)
class MoELayer:
def __init__(self, d_model=16, n_experts=8, top_k=2, seed=0):
rng = np.random.default_rng(seed)
self.top_k = top_k
# 门控权重 [d_model, n_experts]
self.Wg = rng.normal(size=(d_model, n_experts)) / np.sqrt(d_model)
# 每个专家的权重 [n_experts, d_model, d_model]
self.experts = rng.normal(size=(n_experts, d_model, d_model)) / np.sqrt(d_model)
def forward(self, x):
"""x: [n_tokens, d_model]"""
gates = softmax(x @ self.Wg) # [n, n_experts]
idx = np.argsort(-gates, axis=1)[:, :self.top_k] # [n, top_k]
out = np.zeros_like(x)
for t in range(x.shape[0]):
acc = np.zeros(x.shape[1])
w_sum = gates[t, idx[t]].sum() # 重归一化,保证权重和为 1
for e in idx[t]:
acc += (gates[t, e] / w_sum) * (x[t] @ self.experts[e])
out[t] = acc
# 专家利用率统计 —— 这是训练稳定性的关键观察指标
used, counts = np.unique(idx, return_counts=True)
self.last_usage = dict(zip(used.tolist(), counts.tolist()))
return out
layer = MoELayer()
x = np.random.default_rng(1).normal(size=(64, 16))
y = layer.forward(x)
print('输出形状:', y.shape) # (64, 16),与输入一致
print('专家使用次数:', layer.last_usage) # 观察是否集中在少数专家身上- 把
n_experts从 8 调到 64,观察last_usage的长尾分布——你会看到少数专家被反复选中,这就是「路由坍缩」; - 加上一个负载均衡项:统计每个专家的使用次数,对过度使用的专家施加惩罚,再跑一次;
- 计算「激活参数量占比」:
top_k / n_experts,并说明它与「每 token 算力」和「显存占用」分别是什么关系。
六、常见误区与追问
6.1 误区:MoE 因为只激活部分专家,所以显存也省了
错在哪:把「不计算」等同于「不占显存」。为什么自然:直觉上「没用到的东西就不用准备」,符合常识。正确做法:路由是运行时按输入动态决定的,你无法预知下一个 token 会走哪个专家,所以所有专家权重必须常驻显存。MoE 压的是每 token 的 FLOPs,不是权重的存储体积。判据:被问「未激活专家能不能卸载到磁盘省显存」时,答案是否定的——除非你能接受极慢的换入换出,否则显存下限仍由总参数量(671B)决定。
6.2 误区:拿总参数量去和稠密模型比成本
错在哪:用 671B 的总参数去断言「这个模型比 70B 稠密贵一个量级」。为什么自然:总参数确实大一个量级,直觉成立。正确做法:比单 token 成本要看激活参数量(37B),比容量/显存才看总参数(671B)。判据:比成本比激活,比容量比总参;一句话说清「这模型单 token 算力≈一个 37B 稠密,但显存门槛≈一个 671B 稠密」。混用两个口径必然得出相反结论。
6.3 误区:稀疏激活 ≈ 稀疏注意力
错在哪:把「稀疏」两个字当成同一种技术。为什么自然:中文都叫稀疏,容易并类。正确做法:MoE 的稀疏是参数/专家层面的稀疏激活(每个 token 只走少数专家),稀疏注意力是注意力矩阵层面的稀疏(只算部分位置对)。两者解决不同瓶颈、属于不同技术线,不能互相替代。判据:问「它改的是哪一步」——MoE 改的是 FFN/专家选择,稀疏注意力改的是 QKᵀ 的 [n,n] 计算。
6.4 误区:路由坍缩只是训练不稳,不影响上线
错在哪:认为坍缩只是训练期现象,推理时无所谓。为什么自然:推理时路由照常工作,输出看起来正常。正确做法:坍缩意味着少数专家承担了绝大多数 token,其余专家形同虚设——等于你付了 671B 显存,只用了约 37B 的有效容量。这是纯粹的成本浪费,且会让容量上限被悄悄锁死。判据:上线前统计各专家在真实流量上的使用次数分布;若基尼系数高(少数专家占比超 80%),说明已坍缩,需要回炉训练加负载均衡损失。
6.5 误区:MoE 延迟一定比同激活量稠密模型更低
错在哪:把「吞吐高」等同于「延迟低」。为什么自然:单位时间处理更多 token,听起来更快。正确做法:单请求延迟受 All-to-All 跨设备通信 拖累,往往比同激活量的稠密模型更慢;MoE 的优势在吞吐(高并发下单位成本更低),不在单请求延迟。判据:高并发、可批量 → 用 MoE 划算;低并发、强延迟敏感、要本地化 → 稠密模型反而更合适。
6.6 误区:每个专家是一个「领域专家」
错在哪:按字面把 expert 理解成「懂金融的专家」「懂代码的专家」。为什么自然:中文「专家」自带领域含义。正确做法:专家只是层内的一个前馈子网络,门控网络按 token 的表示动态选 top-k,并不按主题分工;同一个专家在不同 token 上可能扮演不同角色。判据:想验证,就看消融实验——打乱专家分配标签,模型表现基本不变,说明专家没有稳定的「主题身份」。
七、自测
八、小结
| 你要能回答的问题 | 一句话答案 |
|---|---|
| MoE 省什么 | 省每 token 的计算量(FLOPs),不省显存 |
| MoE 贵在哪 | 显存下限高、跨设备 All-to-All 通信、训练需要负载均衡 |
| 什么时候 MoE 划算 | 高并发、高吞吐的云端推理场景 |
| 什么时候不划算 | 低并发、强延迟敏感、本地化部署 |
| 专家并行(EP)是什么 | 把专家摊到多张卡,用 All-to-All 通信换单卡显存——并行度越高,通信越贵 |
| 与 KV Cache 的关系 | 完全独立:KV Cache 管的是历史复用,MoE 管的是参数复用 |
一句话记住成本结构:容量看总数、算力看激活、显存看总数、通信看并行度。
下一章换个更贴近日常的问题:既然输出是采样出来的,那「同一个问题问两次答案不同」到底该不该修,以及怎么修。
九、参考与延伸
本章机制部分只讲到「够用」为止。想往下深挖,下面按「先看图、再看代码、最后读论文」的顺序排好了。全站不做原文转载,这里只登记链接与「为什么值得读」。
先看图(建立直觉)
- Switch Transformers(arXiv:2101.03961) —— 先只看它的 Figure 1:token → 门控 → top-k 专家 → 加权,MoE 层最经典的那张结构图就在这里。对着它再看本章第二节的图 1,「未选中的专家仍然占着显存、只是不参与计算」会立刻变具体;负载均衡损失与专家容量这两个概念也出自这篇,想深挖再读正文。
再看代码(动手实现)
- 动手学深度学习(中文,zh.d2l.ai) —— 配套可跑代码,把注意力、路由、专家选择都实现了一遍。把本章的 MoE 路由脚本照着敲一遍,比看十遍都记得牢。
最后读论文(对齐一手定义)
- Mixtral of Experts(arXiv:2401.04088) —— 开源 MoE 模型的一手技术报告。重点看它怎么设置每 token 激活的专家数(top-k),以及稀疏激活比例怎么影响成本;本章「激活参数量只占总量一小部分」在它身上有具体数字。
- DeepSeek-V3 Technical Report(arXiv:2412.19437) —— 本章那组「671B 总参 / 37B 激活」的数字就出自这里。它用的是 DeepSeekMoE + MLA,并提出 auxiliary-loss-free 的负载均衡策略——不加辅助损失也能压住坍缩。想知道工业界怎么在保住吞吐的前提下平衡专家负载,看它第 4 节。
◇ 面试官会怎么问
共 6 条 · 其中 2 条高频 · 先自己答一遍,再展开对照
为什么 MoE 的总参数量和激活参数量不是一回事? 高频
总参数量是所有专家参数之和——决定模型需要
要多少显存/内存装下它;
激活参数量是单个 token 实际参与运算的那部分参数——决定算力开销。
所以一个总参数 671B 的模型,单 token 激活可能只有 37B 量级,算力像小模型,容量像大模型,这就是它便宜的原因。
追问链
- 那显存能不能按激活参数量来估?
- 路由不均(专家负载倾斜)会带来什么工程问题?
MoE 和稠密模型分别适合什么场景?
MoE 适合:预算充足、追求单位算力下的质量、有大规模并发可以摊薄通信与调度开销、以及显存资源相对宽裕的服务端部署。
稠密模型适合:显存受限(端侧、单机小卡)、并发低且延迟敏感(MoE 的专家并行会引入额外通信延迟)、需要极简部署与稳定吞吐的场景。
换句话说:MoE 换来的是"用容量换质量",代价是显存占用、通信开销与调度复杂度。
MoE 的负载不均衡是什么问题?会怎么表现?
常规处理是加负载均衡损失(鼓励路由分布均匀)、设置专家容量上限与溢出丢弃策略、以及在工程侧做专家并行的重排布。对使用方的启示:同一个模型在不同任务分布下的实际吞吐可能差异很大,压测要用真实流量而不是随机文本。
客户要求「把 671B 的 MoE 模型部署到两台 80GB 的机器上」,你会怎么回应? 高频
正确回应是给出可选项:
- 换成激活量相近的稠密小模型;
- 用能跑下的量化版 MoE,并明确精度风险;
- 若一定要这份容量,接受多机多卡 + 专家并行带来的 All-to-All 通信开销,并说清延迟代价。
追问链
- 如果客户愿意把机器加到 16 台,还缺什么才能跑起来?
- 专家并行的 All-to-All 主要伤害吞吐还是延迟?
一个 256 专家、每 token top-8 的 MoE,把 top-8 改成 top-4,单 token 算力和显存下限各怎么变?
因为这两件事由不同口径决定:激活参数占比从
8/256 ≈ 3.1% 变成 4/256 ≈ 1.6%,砍掉的是每 token 的 FLOPs;但路由是运行时才决定的,你无法预知下一个 token 会走哪几个专家,所以全部 256 个专家的权重必须常驻显存。判据:被问「调 top-k 能省显存吗」,答案是不能;要省显存只能动总参数量或做量化。
追问链
- 那调小 top-k 会带来什么副作用?
- 如果只想省显存,有哪些可用手段?
MoE 省的 FLOPs 和 KV Cache 省的重复计算会叠加吗?叠加之后瓶颈会挪到哪里?
KV Cache 省的是重复计算(同样的前缀不再重算),MoE 省的是单位 token 的参数量(不激活全部专家);两者降低的都是「算多少」。但 MoE 同时引入两个新成本:显存下限由总参数量决定,以及跨设备搬运 token 的 All-to-All 通信。
于是高并发场景下,瓶颈常常从算力挪到显存容量与通信带宽;反过来,小 batch 时专家利用率低,MoE 的性价比优势会缩水甚至变成负担。
判据:把两项优化叠在一起之前,先确认新的瓶颈不是显存或通信。
追问链
- 多大的 batch 规模下,MoE 才开始划算?
- 如果 MoE 只在云端划算,本地化部署该怎么选型?
结论先行(一句话给出取舍)→ 机制(为什么是这样,涉及哪条链路)→ 代价或边界(这么做放弃了什么)→ 你的实践或数字(真实场景里怎么落地)。 只讲机制不讲代价,是背题;只讲取舍没有数字,是空谈。