Agent 工程学习站 做棵大树 出品 · beatree.cn 免费 · 无需登录 · 进度存本机
I 版 · 大模型原理 第 03 章 Sparse Activation
I · 大模型原理 03 / 22 进阶

MoE 与稀疏激活

Sparse Activation
预计阅读 24 分钟
难度 进阶
关键词 MoE · 路由
本机状态 未读

一个 671B 总参数的模型,为什么在成本上能和几十 B 的稠密模型打平?答案藏在两个数字之间的那条缝里:总参数量与激活参数量。理解这条缝,你就能解释模型选型里最反直觉的那些现象。

编辑部注 · 本章最容易答错的是一道看似简单的推理题:稀疏激活省的是算力还是显存?请务必读到最后一节。

模型参数有两个口径,混用会让人得出完全相反的结论。第一个是总参数量——模型文件有多大,要占多少显存去装。第二个是激活参数量——每生成一个 token,实际参与计算的有多少。稠密模型里这两个数字相等;MoE(混合专家)把它们拆开了。

一、两个数字之间的缝

设一个 MoE 模型有 256 个专家,每个专家约 2.6B 参数,每层路由激活其中 8 个。

口径稠密模型MoE 模型工程后果
总参数量与激活量相同所有专家之和,例如 671B决定显存下限:权重得装得下
激活参数量同上每 token 只走 top-k 个专家,例如 37B决定单 token 算力:接近一个 37B 稠密模型
每 token FLOPs与总参数成正比与激活参数成正比吞吐高、单价低
跨设备通信少(同层并行即可)多(专家分布在不同设备,需 All-to-All)延迟与实现复杂度上升
训练稳定性较稳路由易坍缩,需负载均衡损失训练工程难度更高

一句话概括 MoE 的交易:用显存和通信换算力效率。参数总量上去了(容量变大),但每个 token 的计算量没跟着上去。

MoE 不是让模型变小,而是让「不参与计算的参数」变得便宜。本刊编辑部

二、路由是怎么工作的

MoE 层内部:先选路,再计算 token x [d] 门控 / 路由 softmax(x·W_g) 取 top-k 专家 17 权重 0.61 激活 专家 03 权重 0.39 激活 专家 88 权重 0 专家 129 权重 0 ⋮ 其余 252 个专家不参与本 token 计算 加权求和 0.61·E17(x) + 0.39·E03(x) 为什么 MoE 的成本结构这么特殊 容量看总数:256 个专家 = 671B 参数的知识容量 → 需要显存装下全部权重。 算力看激活:每 token 只跑 2 个专家 ≈ 37B 的算力 → 单 token 成本接近 37B 稠密模型。 代价:专家分散在多卡上,每次路由都要跨设备搬运 token(All-to-All 通信)。
图 1 MoE 层用一个小门控网络给每个 token 选 top-k 个专家,只跑选中的那几个,最后按权重加权。注意「权重 0」的专家并非从显存里消失——它们仍占着地方,只是不参与这一 token 的计算。

三、省的是算力,不是显存

这是本章最重要的一句话,也是最容易答错的地方:

常见错误答案

错误说法:「MoE 因为只激活部分专家,所以显存需求也降低了,能在小显存上部署大模型。」

为什么错:路由是运行时根据输入动态决定的。你无法预知下一个 token 会选哪两个专家,所以所有专家的权重都必须常驻显存(或在需要时快速换入,代价极高)。MoE 压缩的是每 token 的 FLOPs,不是权重的存储体积。

正确说法:MoE 让「参数容量」和「单 token 算力」解耦——你可以拥有 671B 的知识容量,却只付 37B 的算力账单;但显存下限仍然由 671B 决定。

由此衍生出几个工程结论:

  • MoE 的部署门槛是显存,不是算力。 这就是为什么 MoE 模型通常只在云端大规模集群上跑,很难本地化。
  • MoE 的延迟优势不如吞吐优势明显。 单请求延迟受 All-to-All 通信影响,可能反而比同激活量的稠密模型更慢;但吞吐(单位时间处理 token 数)会好很多。
  • MoE 对 batch 敏感。 batch 越大,专家利用率越高,单位成本越低。这解释了为什么小 batch 场景下 MoE 的性价比优势会缩水。

四、动手:三十行感受一次路由

moe_router.pypython
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 算力」和「显存占用」分别是什么关系。

五、自测

本章自测第 1 题是高频陷阱题
Q1MoE 通过稀疏激活省下了什么?
C。路由是运行时决定的,无法预知下一个 token 会走哪个专家,所以全部权重必须常驻。MoE 解耦的是「容量」与「算力」,不是「容量」与「显存」。另外它完全不改变注意力的平方复杂度——那是另一条独立的技术路线(稀疏注意力 / 线性注意力)要解决的问题。
Q2为什么 MoE 模型在 batch 很小的时候性价比优势会缩水?
B。MoE 的效率来自「把大量 token 一次性分发到各专家,让每个专家都有活干」。batch 小的时候,每个专家分到的 token 可能只有一两个,算力利用率大幅下降,而 All-to-All 通信的固定开销不会等比例减少。这是「MoE 适合高吞吐、不适合低并发」的物理原因。
Q3路由坍缩(routing collapse)指的是什么现象?
D。训练早期若某些专家偶然表现更好,门控会更偏向它们,形成正反馈,最终少数专家承担绝大部分 token,其余专家形同虚设——等于花了 671B 的显存,只用了 37B 的有效容量。标准解法是加负载均衡损失(auxiliary load balancing loss)强制使用率均匀。

六、小结

你要能回答的问题一句话答案
MoE 省什么省每 token 的计算量(FLOPs),不省显存
MoE 贵在哪显存下限高、跨设备 All-to-All 通信、训练需要负载均衡
什么时候 MoE 划算高并发、高吞吐的云端推理场景
什么时候不划算低并发、强延迟敏感、本地化部署
与 KV Cache 的关系完全独立:KV Cache 管的是历史复用,MoE 管的是参数复用

下一章换个更贴近日常的问题:既然输出是采样出来的,那「同一个问题问两次答案不同」到底该不该修,以及怎么修。

面试官会怎么问

共 3 条 · 其中 1 条高频 · 先自己答一遍,再展开对照

为什么 MoE 的总参数量和激活参数量不是一回事? 高频
MoE 把前馈层(FFN)拆成很多个专家,每个 token 只被路由到其中 Top-K 个专家参与计算。于是:
总参数量是所有专家参数之和——决定模型需要
要多少显存/内存装下它;
激活参数量是单个 token 实际参与运算的那部分参数——决定算力开销。
所以一个总参数 671B 的模型,单 token 激活可能只有 37B 量级,算力像小模型,容量像大模型,这就是它便宜的原因。

追问链

  1. 那显存能不能按激活参数量来估?
  2. 路由不均(专家负载倾斜)会带来什么工程问题?
加分点:能说清"显存看总参数、算力看激活参数、通信看专家并行",这三句话就完整了。
MoE 和稠密模型分别适合什么场景?
这道题答错会直接扣分,因为它考察的是工程选型直觉。
MoE 适合:预算充足、追求单位算力下的质量、有大规模并发可以摊薄通信与调度开销、以及显存资源相对宽裕的服务端部署。
稠密模型适合:显存受限(端侧、单机小卡)、并发低且延迟敏感(MoE 的专家并行会引入额外通信延迟)、需要极简部署与稳定吞吐的场景。
换句话说:MoE 换来的是"用容量换质量",代价是显存占用、通信开销与调度复杂度。
加分点:主动提 MoE 的代价(通信、负载均衡、显存),而不是只夸它便宜。
MoE 的负载不均衡是什么问题?会怎么表现?
门控网络倾向于把 token 集中路由到少数"表现好"的专家,导致部分专家过载、部分闲置。表现是:训练时出现"专家坍缩"(大部分专家学不到东西),推理时吞吐被少数热专家的算力上限卡住,整体并行效率下降。
常规处理是加负载均衡损失(鼓励路由分布均匀)、设置专家容量上限与溢出丢弃策略、以及在工程侧做专家并行的重排布。对使用方的启示:同一个模型在不同任务分布下的实际吞吐可能差异很大,压测要用真实流量而不是随机文本。
答这类题的通用结构

结论先行(一句话给出取舍)→ 机制(为什么是这样,涉及哪条链路)→ 代价或边界(这么做放弃了什么)→ 你的实践或数字(真实场景里怎么落地)。 只讲机制不讲代价,是背题;只讲取舍没有数字,是空谈。

本章收尾 · Wrap up
掌握度自评
点一下给自己打分;低于 3 分建议加入复习队列
个人笔记 · Notes