本文目录
Mixture-of-Experts(MoE)模型可以拥有非常大的总参数量,却让每个 token 只经过少数几个专家。它用稀疏激活扩大模型容量,使“模型能容纳多少知识”与“每个 token 做多少计算”不再严格同步。
但在推理系统里,没被当前 token 激活的专家权重仍要存放;被激活的专家还可能位于另一张 GPU,token 必须先发过去、算完再传回来。于是 Dense 模型中相对规整的矩阵乘,变成了路由、重排、all-to-all、许多大小不同的 expert GEMM 与结果合并。
MoE 优化的核心不是单独追求某个 kernel 更快,而是在模型容量、GPU 计算、显存和网络通信之间取得平衡。
从 Dense FFN 到专家集合
Transformer block 通常包含 Attention 和 Feed-Forward Network(FFN)。Dense 模型的 FFN 对所有 token 使用同一组参数:
\[\operatorname{FFN}(x)=W_2\sigma(W_1x)\]MoE 层准备 $E$ 组 FFN 参数 $E_1, E_2,\ldots,E_E$,router 根据当前 token 的隐藏状态 $x$ 产生专家分数:
\[s=W_rx\]再选择 Top-$k$ 个专家,并按路由权重合并输出:
\[y=\sum_{i\in\operatorname{TopK}(s)}p_i(x)E_i(x)\]例如 8 个专家、Top-2 路由:
token x
│
▼
router scores: [0.02, 0.41, 0.03, 0.01, 0.36, 0.06, 0.07, 0.04]
│
├─► expert 1 ─► output 1 ─┐
└─► expert 4 ─► output 4 ─┴─► weighted sum
一层选中的专家不代表后续层也相同。同一 token 在不同 MoE layer 会根据新的隐藏状态重新路由;“某个专家永久负责数学”也不是架构规定,而只是可能从数据中形成的统计现象。
总参数、激活参数与显存是三件事
描述 MoE 常出现两个参数量:
- 总参数量:所有 Attention、共享层和全部 experts 的参数;
- 激活参数量:一个 token 在一次 forward 中实际经过的参数近似量。
若每个 token 只选择少量 experts,激活计算可以远小于总容量。但除非做权重 offload,所有专家仍必须分布在设备集合中。不能用“每 token 激活 37B”推断“只需存 37B 权重”。
推理显存至少包括:
attention + embedding + shared FFN weights
+ local expert weights
+ KV Cache
+ router / dispatch metadata
+ permuted token buffers
+ grouped GEMM workspace
+ communication buffers
+ CUDA Graph / allocator reserve
实际部署容量应该从 checkpoint 的本地 shard 和运行时 profile 得到,而不是只把激活参数乘以 dtype 字节数。
一个 token 为什么要跨 GPU
单卡放不下全部专家时,常用 Expert Parallel(EP):每个 rank 只持有一部分 experts。假设 8 个专家分布在 4 张 GPU,每张两个:
GPU 0: expert 0, 1
GPU 1: expert 2, 3
GPU 2: expert 4, 5
GPU 3: expert 6, 7
GPU 0 上的一批 token 经过 router 后,可能分别选择专家 1、4、7。完整数据路径为:
hidden states on source ranks
│
▼
router: top-k expert ids + weights
│
▼
count / prefix sum / permute by destination
│
▼
dispatch all-to-all
│
▼
tokens grouped by local expert
│
▼
expert up/gate/down projections
│
▼
combine all-to-all
│
▼
inverse permutation + weighted sum
Top-2 路由时,一个 token 通常会产生两份 expert 输入;若两个专家位于不同 rank,就会去往两个目的地。通信量因而大致随 token 数、hidden size、dtype 与 Top-$k$ 增长。
Dispatch 并不是一次普通 all-to-all
普通 collective 的每个 rank 发送量往往规则;MoE 路由由数据决定,不同 expert 收到的 token 数不同。dispatch 前需要先知道每个目的地的大小,并把原 token 顺序重排成按 rank/expert 连续的布局。
一个简化过程是:
tokens: t0 t1 t2 t3 t4
expert ids: 3 1 3 0 6
histogram: expert0=1, expert1=1, expert3=2, expert6=1
prefix sum: calculate each expert's output offset
permute: [t3] [t1] [t0,t2] [t4]
dispatch: send each segment to expert owner
combine 阶段再沿相反方向把 expert outputs 发回源 rank,按原 token 位置恢复顺序,并应用路由权重。任何 offset、count 或逆置换错误都可能让输出形状正常、token 内容却被串位,因此通信库不仅要快,还必须严格验证元数据。
DeepEP 这类专用库提供的正是 MoE dispatch/combine kernel,并区分高吞吐与低延迟路径、支持低精度传输和通信/计算重叠。具体接口在演进,文章不应把某个版本的峰值带宽当作所有集群的保证。
Expert GEMM 为什么经常不够大
Dense FFN 可以把 batch 中全部 token 送进一组大矩阵乘。MoE 将 token 分散给许多专家;若一轮只有少量 token,每个 expert 可能只收到个位数行,GEMM 太小,Tensor Core 和 kernel 启动都难以充分利用。
Grouped GEMM 将多个形状不同、权重不同的专家矩阵乘放进一个调度:
expert 0: [m0, hidden] @ W0
expert 1: [m1, hidden] @ W1
expert 2: [m2, hidden] @ W2
...
它减少逐专家 kernel launch,并能更好安排 GPU work。但 $m_i$ 仍由 router 和当前 batch 决定:某专家收到 300 token、另一个只有 2 token 时,负载倾斜依然存在。
增大 batch 会让 expert GEMM 更饱满,也会增加在线排队和 KV 占用。因此“提升 MFU”不能脱离 TTFT/TPOT 的 SLO。
负载不均会拖慢整个集体通信
同步 MoE layer 的下一阶段通常要等所有相关 rank 完成。若一个热点 expert 收到远多于平均值的 token:
- 持有它的 rank 需要接收更多数据;
- 该 expert GEMM 更大;
- combine 返回更晚;
- 其他 rank 即使已经完成,也只能等待 straggler。
设一轮总 expert assignment 数为 $N_a$,expert $i$ 收到 $n_i$ 个 token,一个直观指标是:
\[\text{imbalance ratio} =\frac{\max_i n_i}{N_a/E}\]值为 1 表示完全均匀;值越大,最热 expert 相对平均负载越高。还应观察 P95/P99 expert load、每 rank 收发字节和层级耗时,因为平均分布可能掩盖偶发热点。
训练时常用辅助负载均衡损失、容量因子或其他路由方法。DeepSeek-V3 报告了 auxiliary-loss-free balancing 策略;这是模型训练设计,不是 serving 引擎可以任意替换的路由公式。推理必须尊重 checkpoint 的 router 与 Top-$k$ 语义。
Capacity、padding 与 token dropping
为了让专家 batch 形状规则,一些训练系统为每个 expert 设置容量:
\[C=\left\lceil \text{capacity factor}\times\frac{N_a}{E} \right\rceil\]少于 $C$ 的专家可以 pad,多于 $C$ 的 assignment 可能被丢弃或重路由。这在训练中能控制固定 buffer 与 collective shape,但推理时丢 token 可能改变输出质量。
现代 serving 实现也可以使用动态 shape、提前计算 counts 或专用通信 buffer,避免为了整齐而丢弃 token。不能看到“capacity factor”就默认所有 MoE 推理都会丢 token;应以模型和 runtime 的实际策略为准。
Expert 放置决定跨节点流量
节点内 NVLink/NVSwitch 与跨节点网络的带宽、延迟差距明显。若常被共同选择的 experts 分散在不同节点,Top-$k$ assignment 会产生更多 RDMA 流量。
可用策略包括:
- 将 EP group 尽量限制在高速互联域;
- 按模型定义的 expert group 做拓扑感知放置;
- 复制热点 expert,用路由负载均衡副本;
- 根据线上统计周期性重平衡 expert placement;
- 将 Attention 的 DP/TP 与 MoE 的 EP 组合,减少不必要通信。
DeepSeek 开源的 EPLB 展示了 expert placement/load balancing 的实现思路,并尝试让同组 experts 位于同一节点。复制 expert 会增加权重显存,重新放置也有迁移和稳定性成本,不能只看通信下降。
TP、EP、DP 应如何组合
Tensor Parallel
把单个矩阵/attention heads 切到多个 rank。对 dense layer 很常见,但每层有 all-reduce/reduce-scatter 等通信。若 MoE 仍使用 TP,单个 expert 权重也会跨 rank 切分。
Expert Parallel
每个 rank 持有完整的部分 experts,token 通过 all-to-all 找到专家。它减少单 expert 内部的 TP 计算通信,却引入路由型 dispatch/combine。
Data Parallel
复制共享部分或整个模型处理不同请求。MoE 系统可以让 Attention 走 DP、experts 走 EP;这需要在每层完成 token 的重新分布和状态管理。
Pipeline Parallel
按层切模型,降低单 stage 权重占用,但带来 pipeline bubble、microbatch 和跨 stage KV/激活管理。
没有一个固定组合适合所有模型。选择时需要同时看:
- 总权重是否单节点可放;
- expert 数能否整除 EP size;
- Attention/共享层是否需要 TP;
- 节点内和节点间拓扑;
- prefill 与 decode 的 batch 形状;
- KV Cache 需要留多少显存。
Prefill 与 Decode 需要不同通信取向
Prefill 一轮处理多个输入 token,expert GEMM 较大,更偏吞吐;通信库可以使用较大 buffer 和高吞吐协议,并尝试与其他计算重叠。
Decode 每个请求每轮只有一个新 token,即使 continuous batching 后,expert batch 仍可能较小,更关心 collective 启动延迟、CPU 同步和小消息效率。一个只在 8k-token batch 上达到高带宽的 dispatch kernel,不一定改善在线 decode。
评测 MoE 通信时必须把两种形状分开:
| 阶段 | token batch | 常见关注点 |
|---|---|---|
| Prefill | 大、变化明显 | 吞吐带宽、重叠、buffer 容量 |
| Decode | 小、频繁 | 启动延迟、小消息、同步开销 |
通信与计算重叠的边界
理想流水线希望在一部分 token 做 expert GEMM 时,同时 dispatch 另一部分,或在 combine 时执行独立的 Attention。但重叠需要真正独立的依赖和资源:
- 后续 expert GEMM 不能在输入到达前开始;
- 通信 kernel 也可能占用 SM,与 GEMM 争抢;
- 同一 CUDA stream 上的操作不会自动并行;
- 跨节点 NIC、NVLink 和 HBM 带宽可能相互影响;
- 为流水线切得太碎会增加 launch 与同步。
性能分析要看时间线上的 overlap,而不是看到两个 stream 就宣称通信被“完全隐藏”。专用通信库减少 SM 占用的目的之一,就是给并发计算留出执行资源。
量化与 offload 并非独立开关
专家权重量化
MoE 总权重大,INT4/FP8 等量化能显著降低容量和权重带宽。但 expert GEMM 的形状、量化 scale 布局和硬件支持决定是否有高效 kernel;解量化开销也可能在小 batch 下突出。
低精度 dispatch
将 token hidden states 以 FP8 等格式传输可减少通信字节,但需要 scale、量化/反量化和误差验证。通信少了不代表端到端一定更快。
Expert offload
把冷 expert 权重放在 CPU/主机内存,命中时再传到 GPU,可以用延迟换容量。路由分布稳定、热点集中时可做缓存;若 expert 选择分散或快速变化,PCIe 传输会阻塞整个 layer。
每项优化都应分别做质量与性能消融,不应把“FP8、offload、EP 改大”同时打开后只报告一个总 speedup。
应该观察哪些指标
普通 LLM serving 的 TTFT、TPOT、吞吐、goodput 和 KV 使用量仍然需要;MoE 还应增加:
router:
tokens per expert / per rank
max-to-mean imbalance
top-k overlap / routing distribution
communication:
dispatch latency and bytes
combine latency and bytes
intra-node vs inter-node traffic
synchronization / wait time
compute:
expert GEMM m-shape distribution
grouped GEMM latency
padding or dropped assignments
attention vs MoE layer time
memory:
expert weights
communication and permutation buffers
KV Cache and workspace
只看 GPU utilization 可能误判:通信 kernel、等待或频繁小 GEMM 都可能让设备显示忙碌,却没有提高输出 token 数。
一条可解释的调优路径
- 确认模型语义:Top-$k$、shared experts、路由权重、是否允许 dropping;
- 验证正确性:单卡/参考实现对比分布式输出,特别检查 permutation 和 combine;
- 拆解时间:区分 Attention、router、dispatch、expert GEMM 和 combine;
- 检查倾斜:观察每层 expert/rank 负载,而非只看全局平均;
- 匹配拓扑:先避免不必要的跨节点 assignment,再调整 TP/EP/DP;
- 优化 kernel 与重叠:针对 prefill/decode 分别选择路径;
- 最后加入量化/offload:重新验证质量、显存和 SLO。
每轮只改变一组变量,并保存模型 revision、引擎 commit、GPU/NIC 拓扑和流量分布。MoE 对 topology 与 batch 极其敏感,脱离条件的固定 tok/s 或“某精度必然 1.8 倍”没有可迁移性。
小结
MoE 的稀疏性发生在“每个 token 激活哪些 experts”,不是“未激活权重自动从系统消失”。总权重决定容量压力,Top-$k$ 决定计算与通信量,路由分布决定每个 rank 的实际负载,而最慢 rank 往往决定整层延迟。
沿着 router、permute、dispatch、grouped GEMM、combine 和 inverse permutation 逐段观察,才能知道瓶颈究竟是显存、网络、小矩阵还是负载倾斜。MoE 推理最终是一项软硬件联合优化:模型路由、并行拓扑、通信库和 serving 调度缺一不可。
参考资料
觉得有帮助?
分享给同样关注系统性能的人。