上一篇把单个 MoE Layer 的执行流走了一遍:Router 选 Expert、Token Dispatch 按 Expert 重排、Grouped GEMM 统一调度、Combine 加权合回原始 Token 顺序。整条链路假设所有 Expert 都在同一张 GPU 上。
那篇结尾留了一个问题——如果 Expert 被分散在不同 GPU 上,Token Dispatch 应该怎么组织?
之前把 DDP/ZeRO、TP、PP、SP 和 CP 梳理了一遍。它们分别沿数据、训练状态、张量、模型层和序列等维度拆分。但 Expert Parallelism 有些不同:计算不是按固定规则切分的,而是由 Router 根据运行时输入动态决定。
既然 FSDP 已经可以分片参数,为什么 MoE 还需要专门的 Expert Parallelism?这篇想把这件事想清楚。
8 个 Expert 放不下一张卡,怎么办#
用一组具体数字。4 张 GPU,8 个 Expert,每个 Expert 参数量 2 GB,Router 使用 Top-1。
MoE 结构那篇已经提过,Sparse MoE 的特点是总参数多但每个 Token 只激活少量 Expert。8 个 Expert 合计 16 GB 的参数,但 Top-1 下每个 Token 只用其中一个——2 GB 的活跃 Expert 参数。
两种部署方式。
方案 A:完整复制。 每张 GPU 都保存全部 8 个 Expert。单卡 Expert 参数占用 16 GB,四卡合计 64 GB。每张 GPU 都能本地完成任意 Expert 的计算,不需要跨卡通信。
方案 B:EP = 4。 每张 GPU 保存 2 个 Expert。单卡 Expert 参数占用 4 GB,四卡合计 16 GB。但如果 GPU 0 的 Token 选中了 GPU 3 上的 Expert,就需要跨卡通信才能完成计算。
方案 B 省了单卡 12 GB 的 Expert 参数显存,代价是引入了新问题:Token 和它选中的 Expert 可能不在同一张 GPU 上。
这时候有两个选择。
选择一:把 Expert 参数搬到 Token 所在的 GPU。 2 GB 的参数传输。
选择二:把 Token 的 Hidden State 搬到 Expert 所在的 GPU。 假设 Hidden Size = 4096,BF16(2 Bytes/Element),一个 Token 的 Hidden State 是 \(4096 \times 2 = 8\) KiB。往返(发送输入 + 返回输出)约 16 KiB。
2 GB 和 16 KiB,差了五个数量级。
当然,通信数据量小不直接等于端到端延迟低——通信原语那篇讲过,实际延迟还要看 Bandwidth、Latency 和协议开销。但五个数量级的差距足以说明设计方向:让 Token 去找 Expert,而不是把 Expert 搬到 Token 身边。
这个选择背后有三个设计含义:
- Expert 参数常驻 GPU,避免反复传输大块参数。
- 每个 Expert 可以复用它的参数处理来自不同 GPU 的 Token——参数加载一次,服务多个 Token。
- 计算位置跟随 Expert 的物理位置,不跟随 Token 的来源。计算负载取决于 Router 的运行时选择。
EP 的核心不是简单地把参数切开,而是把 Expert 固定在不同 GPU 上,让 Token 根据 Router 的选择跨 GPU 流动。
完整的数据流:Route、Dispatch、Compute、Combine#
继续用 4 GPU、8 Expert 的布局:
- GPU 0:E0、E1
- GPU 1:E2、E3
- GPU 2:E4、E5
- GPU 3:E6、E7
每张 GPU 初始持有 2 个 Token:
- GPU 0:A、B
- GPU 1:C、D
- GPU 2:E、F
- GPU 3:G、H
这是一种典型的 Token 分片布局。在 EP 与其他并行策略组合时,Token 的初始分布方式可能不同,但本文先用这个最简单的情况。
Router 怎么部署#
一个经常被跳过的问题。
在这个典型实现中,Router 在每张 GPU 上都有一份相同的参数副本。Router 是一个线性层,参数量很小(\(W_r \in \mathbb{R}^{H \times 8}\),只有 \(4096 \times 8 \times 2 = 64\) KiB),复制它的开销可以忽略。
每张 GPU 的 Router 对本地 Token 独立执行路由决策。GPU 0 的 Router 对 Token A 和 B 打分,GPU 1 的 Router 对 Token C 和 D 打分,以此类推。
Router 的输出覆盖全局 8 个 Expert,不只是本地的两个。GPU 0 的 Router 完全可能把 Token A 路由到 E6——虽然 E6 在 GPU 3 上。
需要注意:相同的 Router 参数不意味着不同 GPU 会选出相同的 Expert。Router 的输入是 Token 的 Hidden State,不同 GPU 上的 Token 不同,打分结果自然不同。
训练时,Router 参数的梯度来自各 GPU 本地 Token 的反传,需要通过 AllReduce 或类似机制在 Router 副本之间同步,保持参数一致。
一次完整的 EP Forward#
假设 Router 给出以下路由结果(Top-1):
| Token | 源 GPU | 选中 Expert | 目标 GPU |
|---|---|---|---|
| A | GPU 0 | E6 | GPU 3 |
| B | GPU 0 | E0 | GPU 0 |
| C | GPU 1 | E4 | GPU 2 |
| D | GPU 1 | E7 | GPU 3 |
| E | GPU 2 | E3 | GPU 1 |
| F | GPU 2 | E5 | GPU 2 |
| G | GPU 3 | E1 | GPU 0 |
| H | GPU 3 | E2 | GPU 1 |
这张表里有几个值得注意的地方:
- Token B 和 F 选中了本地 GPU 上的 Expert,不需要跨卡通信。
- Token A 从 GPU 0 要去 GPU 3——跨了三跳(逻辑上,实际拓扑可能不同)。
- GPU 3 要接收两个外来 Token(A 和 D),但它自己的两个 Token(G 和 H)全部去了别的 GPU。
完整的 Forward 分四步。
第一步:Route。 每张 GPU 的 Router 对本地 Token 计算路由,确定每个 Token 应该去哪个 Expert、哪张 GPU。
第二步:Dispatch(第一次 All-to-All)。 根据路由结果,每张 GPU 把需要发往其他 GPU 的 Token Hidden State 打包发送,同时接收来自其他 GPU 的 Token。
All-to-All 之后,每张 GPU 持有的 Token 变了:
- GPU 0:B(本地)、G(来自 GPU 3)→ 分别给 E0、E1
- GPU 1:E(来自 GPU 2)、H(来自 GPU 3)→ 分别给 E3、E2
- GPU 2:C(来自 GPU 1)、F(本地)→ 分别给 E4、E5
- GPU 3:A(来自 GPU 0)、D(来自 GPU 1)→ 分别给 E6、E7
第三步:Expert Compute。 每张 GPU 用本地 Expert 处理收到的 Token。这一步和上一篇讲的单卡 Token Dispatch + Grouped GEMM 完全相同——只不过输入 Token 不再全是本地的,还包含从其他 GPU 发来的。
第四步:Combine(第二次 All-to-All)。 Expert 计算完成后,结果需要送回 Token 的源 GPU。GPU 3 计算出 E6(A) 的结果,要送回 GPU 0;GPU 1 计算出 E3(E) 的结果,要送回 GPU 2。第二次 All-to-All 把输出沿原路返回。
返回后,每张 GPU 恢复持有自己原始 Token 的 MoE 输出。
为什么是 All-to-All 而不是 AllGather#
我第一次看到 EP 数据流时,想的是:为什么不用 AllGather 把所有 Token 收集到每张 GPU 上,让每张 GPU 只计算本地 Expert 对应的部分?
AllGather 确实能工作,但通信量大很多。用上面的例子对比。
AllGather 方案: 每张 GPU 把自己的 2 个 Token 广播给所有其他 GPU。每张 GPU 发出 2 个 Token,被 3 张其他 GPU 接收,跨 GPU 传输 \(2 \times 3 = 6\) 个 Token Payload。四张 GPU 合计跨 GPU 传输 24 份 Token Payload。
All-to-All 方案: 每张 GPU 只发送需要去其他 GPU 的 Token。在上面的路由结果中,Token B 和 F 留在本地不需要传输。实际跨 GPU 传输的 Token 只有 A、C、D、E、G、H——6 个 Token,单方向。返回结果也是 6 个。单次 Dispatch 跨 GPU 传输 6 份 Token Payload。
假设每个 Token Hidden State 为 8 KiB:
- AllGather:\(24 \times 8 = 192\) KiB
- All-to-All(单次 Dispatch):\(6 \times 8 = 48\) KiB
All-to-All 的优势来自选择性:它只传输需要跨 GPU 的 Token,本地 Token 不参与跨卡通信。AllGather 不区分本地和远端,所有数据无差别广播。
但 All-to-All 通信量更少不意味着在任何配置下都更快。AllGather 的通信模式更规整(每张 GPU 发送和接收的数据量相同),某些互连拓扑下可能有更好的带宽利用率。All-to-All 的发送和接收量不对称(取决于路由结果),调度和同步可能更复杂。
通信原语那篇介绍过 AllGather 和 ReduceScatter。All-to-All 和它们的区别:AllGather 让所有 GPU 拿到所有 GPU 的数据,ReduceScatter 带 Reduce 语义。All-to-All 则是每张 GPU 向不同目标发送不同数据——没有 Reduce,也没有全量广播,是一种点对点的多路交换。
关于变长和 Top-K#
上面的例子是 Top-1,每个 Token 只去一个 Expert。如果是 Top-2,每个 Token 会产生两个 Expert 计算任务,通信量相应翻倍。
更值得注意的是:不同 GPU 向同一目标 GPU 发送的 Token 数量可能不同。GPU 0 可能有 0 个 Token 要去 GPU 2,而 GPU 1 有 3 个。这意味着 All-to-All 的 send/recv buffer 大小在各 GPU 之间不对称。
标准的 all_to_all 假设每对 GPU 之间交换固定大小的数据块。MoE 的变长路由可能需要 all_to_all_v(variable-length 版本)或者预先 Padding 到固定大小再传输。Padding 浪费了带宽,但简化了实现;变长版本节省带宽,但需要先通信每对 GPU 之间的数据量。
本地的 Token Permutation 和 Unpermutation(上一篇讲过的 Token Dispatch 重排)在 EP 场景下依然存在。区别是:单卡 MoE 的重排只涉及本地内存搬运,EP 下的重排还伴随跨 GPU 的 All-to-All 通信。
逻辑均衡和物理均衡是两回事#
上一节假设路由结果比较均匀——每张 GPU 最终各处理 2 个 Token。现实没这么理想。
假设 8 个 Expert,但 80% 的 Token 被路由到 E6。E6 所在的 GPU 3 要处理绝大部分 Token,其他三张 GPU 可能只有零星任务。GPU 3 成为计算瓶颈,整个 EP 的吞吐由它决定。
这里有两个不同层面的均衡问题,容易混淆。
Router Load Balancing#
Router 本身的优化目标是任务损失(如语言模型的交叉熵),它不天然关心 Expert 分配是否均匀。如果 E6 对某类 Token 的得分就是高,Router 会把大量 Token 路由到 E6——这可能是合理的专业化分工,也可能是 Router 陷入了局部最优(Router Collapse)。
为了鼓励更均匀的分配,Switch Transformer 引入了辅助负载均衡损失:
$$ L_{\text{total}} = L_{\text{task}} + \alpha \cdot L_{\text{aux}} $$$$ L_{\text{aux}} = N \sum_{i=1}^{N} f_i \cdot P_i $$其中 \(N\) 是 Expert 总数,\(f_i\) 是一个 Batch 中实际被分配到 Expert \(i\) 的 Token 比例,\(P_i\) 是 Router 对 Expert \(i\) 的平均 Softmax 概率。
为什么需要 \(f_i\) 和 \(P_i\) 两项?\(f_i\) 反映 Top-K 选择后的实际分配——这是离散的,梯度传不过去。\(P_i\) 是 Softmax 概率的均值——这是连续的,可以正常反传梯度。把两者相乘,辅助损失通过 \(P_i\) 项提供可微的优化信号,引导 Router 的概率分布更均匀,间接影响实际的 Token 分配。
\(\alpha\) 是平衡系数,通常很小(如 0.01)。太大会干扰主任务的优化方向。
有一点需要注意。辅助损失只是"鼓励"更均匀的分配,不是强制约束。训练收敛后仍然可能存在不均衡——如果某些 Expert 对特定类型的 Token 确实更擅长,Router 在任务损失和辅助损失之间会找到一个平衡点。
E6 非常热门,也不一定意味着它真的最擅长处理那些 Token。可能是 Router 早期偶然偏向了 E6,E6 因此获得了更多梯度更新、变得"更好",进一步吸引更多 Token——一个正反馈循环。MoE 结构那篇讲过这个专家坍塌问题。
Expert Replication:逻辑 Expert 和物理副本#
Router Load Balancing 尝试从训练目标层面减少分配不均。但即使 Router 的逻辑分配完全均匀(每个 Expert 分到的 Token 数量一样多),GPU 之间的计算负载仍然可能不均衡。
为什么?因为逻辑 Expert 到物理 GPU 的映射不一定是均匀的。在 EP = 4、8 Expert 的基本配置下,每张 GPU 放 2 个 Expert,看起来均匀。但如果其中某个 Expert 的计算时间显著长于其他 Expert(比如收到的 Token 数量波动大),对应 GPU 就会成为瓶颈。
一种应对策略是 Expert Replication:把热点 Expert 复制到多张 GPU 上。
保持 Router 的逻辑选择不变——Token X 仍然选择逻辑 Expert E6。但物理上可能有多份 E6:
- E6@GPU 2
- E6@GPU 3
它们使用完全相同的 Expert 参数。
系统因此被分成两个独立的决策:
- Router:选择哪个逻辑 Expert。
- Dispatcher:选择该 Expert 的哪个物理副本。
Dispatcher 可以根据各副本当前的队列长度、网络距离、本地优先等策略选择最合适的物理副本。
复制热点 Expert 不意味着必须复制到所有 GPU。 4 张 GPU,E6 原本在 GPU 3:
- 可以只复制到 GPU 0,总共两份。
- 可以再复制到 GPU 1,总共三份。
- 不一定需要在 GPU 2 上也复制。
副本数量和位置应该由显存预算、负载分布和互连拓扑共同决定。复制一份 E6 到 GPU 0,代价是 GPU 0 多占 2 GB 的 Expert 参数显存,收益是 E6 的流量可以在两张 GPU 之间分担。如果 E6 的热度不足以让一张 GPU 成为瓶颈,复制就是浪费显存。
静态复制和动态复制#
热点 Expert 是运行时 Router 执行后才观察到的。有的 Expert 可能在某个 Batch 很热,下一个 Batch 就不热了。如果基于瞬时观测做复制决策,可能刚复制完 E6,热点就转移到了 E3。
静态复制:通过历史 Profiling 分析 Expert 的平均负载,在部署前确定哪些 Expert 需要复制、复制几份、放在哪里。适合热点稳定的场景。
动态复制:在运行时持续监控 Expert 负载,当某个 Expert 的热度超过阈值时触发复制,热度下降时回收副本。
动态复制需要考虑的因素比想象中多:
- 检测窗口:用多长的历史数据判断热度?太短容易抖动,太长反应迟钝。滑动窗口或指数移动平均(EMA)是常见选择。
- 触发阈值与迟滞:不能一超过阈值就复制、一低于就回收。需要设置迟滞区间(Hysteresis),避免频繁创建和销毁副本。
- 复制代价:传输 Expert 参数到目标 GPU,更新路由映射表,让 Dispatcher 感知新副本。参数传输本身就是一次大块通信。
- 训练时的额外复杂性:如果是训练场景,复制的 Expert 副本各自会收到不同的 Token、产生不同的梯度。这些梯度需要正确归约(类似 DDP 对参数副本的处理),优化器状态也需要同步。仅复制参数而不处理梯度同步会导致副本参数发散。
- 显存预算:复制 Expert 占用目标 GPU 的显存。如果显存已经很紧张,可能没有空间容纳新副本。
动态复制是一种系统优化策略,不是所有 EP 框架都默认支持。在评估是否需要动态复制时,核心判断是:
热点是否持续足够长,使得复制带来的负载分担收益大于复制本身的开销?
Expert Capacity 和 Token Dropping#
MoE 结构那篇提过 Expert Capacity 的概念。这里需要把它和 Load Balancing 严格区分开。
Load Balancing 解决的是负载分布问题——让 Token 尽量均匀地分配给不同 Expert。Expert Capacity 决定的是一次路由中每个 Expert 最多能接收多少 Token。 两者解决不同的问题,不能互相替代。
用具体数字。8 个 Expert,1024 个 Token,Top-1 Routing,Capacity Factor(CF)= 1.25。
理想均匀分配下,每个 Expert 收到 \(1024 / 8 = 128\) 个 Token。Expert Capacity:
$$ C = \left\lceil \frac{T \times K}{N} \times CF \right\rceil = \left\lceil \frac{1024 \times 1}{8} \times 1.25 \right\rceil = 160 $$每个 Expert 最多接收 160 个 Token。比理想均匀值多出 25% 的 Buffer,容纳一定程度的不均衡。
现在假设 E6 收到了 819 个 Token——严重不均衡。
- Capacity:160
- Overflow:\(819 - 160 = 659\)
659 个 Token 被拒绝。这些 Token 的 Expert 计算被跳过——但 Token 本身不会从 Transformer 中消失,Residual Connection 仍然保留原始表示。只是它们没有经过 Expert 的非线性变换。
把 CF 提高到 2.0:
$$ C = \left\lceil \frac{1024}{8} \times 2.0 \right\rceil = 256 $$Overflow 减少到 \(819 - 256 = 563\)。好了一些,但仍然有 563 个 Token 被丢弃。
更关键的是:Capacity 增大缓解了 Overflow,却没有改变 819 个 Token 都被分配给 E6 的事实。 如果 CF 足够大、允许 E6 接收全部 819 个 Token,E6 的计算时间会远超其他 Expert。其他 GPU 等 E6 算完,负载不均衡的问题反而更明显。
如果把 Expert Replication 和 Capacity 结合呢?假设 E6 有 4 份物理副本,819 个 Token 被均匀分摊到每份约 205 个。如果每份物理副本独立具有 160 的 Capacity,4 份合计 640,仍有 \(819 - 640 = 179\) 个 Token Overflow。
但这里有一个实现假设:Capacity 限制是施加在物理副本上还是逻辑 Expert 上?如果限制在逻辑 Expert 层面(整个 E6 最多 160 个 Token),那复制副本本身不改变总 Capacity,只是分担计算。如果限制在物理副本层面(每份副本各自 160),那复制确实扩大了总 Capacity。具体取决于框架实现。
Dropless MoE#
固定 Capacity 的根本问题是:不管 CF 怎么调,总存在路由极端不均衡时大量 Token 被丢弃的风险。
Dropless MoE 的思路是不设固定 Capacity——每个 Expert 接收 Router 分配给它的所有 Token,不做丢弃。代价是每个 Expert 的输入 Token 数量变成了运行时的动态值,无法提前固定 GEMM 的 M 维度。
上一篇讲过 Grouped GEMM 可以处理不同子 GEMM 有不同 M 维度的情况。Dropless MoE 正是依赖这种能力。MegaBlocks 论文提出了基于 Block-sparse 矩阵乘法的实现方案。
Dropless 不代表计算负载自动均衡。 如果 E6 收到 819 个 Token 而其他 Expert 只收到几十个,Dropless 不会丢弃 Token,但 E6 的计算时间仍然远超其他 Expert。Dropless 解决的是信息损失问题(不丢 Token),不解决负载均衡问题。
EP 如何与 TP、DP 组合#
前面一直在单独讨论 EP。实际训练中,EP 通常和 TP、DP 一起使用。TP 切的是单个算子内部的矩阵乘法,DDP/ZeRO 切的是数据。三者切分的维度不同,可以组合。
EP + Expert TP#
假设 64 个 Expert,EP = 4,Expert TP = 4,共 16 张 GPU。
EP = 4 表示 64 个 Expert 被分成 4 组,每组 16 个 Expert。Expert TP = 4 表示每个 Expert 的权重矩阵被进一步切分到 4 张 GPU 上协作计算。
16 张 GPU 形成一个 4 × 4 的二维进程网格:
- 4 个 EP Shard(行),每行负责 16 个逻辑 Expert
- 每个 EP Shard 内 4 张 GPU(列)构成一个 Expert TP Group
当 Token 被路由到 E20 时:
- EP Dispatch(All-to-All)将 Token 送到负责 E20 的 EP Shard(第二行,负责 E16–E31)。
- 该 Shard 内的 4 张 GPU 通过 TP 协作计算 E20——TP 那篇讲的 Column Parallel + Row Parallel。
- EP Combine(All-to-All)将结果送回 Token 的源 GPU。
需要区分几个 Rank 和 Group 的概念:
- EP Rank:这张 GPU 在 EP 维度上的位置(0–3),决定它负责哪些 Expert。
- Expert TP Rank:这张 GPU 在 Expert TP Group 内的位置(0–3),决定它持有 Expert 权重的哪个分片。
- EP Group:4 个 EP Rank 构成一组,执行 All-to-All 通信。
- Expert TP Group:同一 EP Rank 下的 4 张 GPU,执行 AllReduce/ReduceScatter 通信。
- Global Rank:这张 GPU 在所有 16 张 GPU 中的全局编号。
注意:实际框架(如 Megatron)中,Expert TP 和普通 Dense Layer 的 TP 配置可能不同。Dense Layer 可能用 TP = 8,Expert 用 Expert TP = 4。不要把这个简化的正交二维布局当成所有框架唯一的布局方式。
EP + DP#
假设 8 个 Expert,EP = 4,DP = 2,Expert TP = 1,共 8 张 GPU。
每 4 张 GPU 组成一个完整的 EP 模型副本:
- DP 副本 0(GPU 0–3):处理 Batch A
- DP 副本 1(GPU 4–7):处理 Batch B
DP 复制的是完整的逻辑 MoE 模型——而一个逻辑模型本身就由 4 个 EP Rank 共同组成。GPU 0 和 GPU 4 持有相同的 Expert(E0、E1),但处理不同 Batch 的 Token。
训练时,同一个 Expert 在不同 DP 副本上计算不同 Batch 的梯度。这些梯度需要跨 DP 副本归约(类似普通 DDP),然后各副本同步更新 Expert 参数。
三者组合#
在 EP、Expert TP、DP 三个维度互相正交的简化布局下:
$$ \text{World Size} = EP \times \text{Expert TP} \times DP $$例如 \(4 \times 2 \times 2 = 16\) 张 GPU。
这个公式有一个重要前提:三个维度完全正交,每个维度独立切分、不互相依赖。这是为了简化理解的模型。
真实框架的进程组配置更复杂。EP 维度可能与 Dense Layer 的 TP/DP 布局存在重组关系——Dense Layer(如 Attention)和 Expert Layer 可能使用不同的 TP 度数,对应不同的 Process Group 划分。以具体框架的 Process Group 定义为准。
训练和推理:EP 并不总是最佳方案#
讲了这么多 EP 的机制,最后回到一个工程决策问题:什么时候应该用 EP,什么时候应该完整复制 Expert Bank?
训练和推理的目标不同,这会影响 EP 的取舍。
训练更关注:Step Time、Throughput、GPU Utilization、显存中的训练状态(参数 + 梯度 + Optimizer States)。
在线推理更可能关注:P50/P99 Latency、单请求的 Batch Size(可能很小)、GPU 间通信的延迟、显存可行性。
不是说训练一定只看吞吐、推理一定只看延迟——大规模推理服务也关心吞吐,离线训练有时也关心每步时间。但这两类场景的典型约束不同。
一个完整的部署案例#
参数:
- 64 个 Expert,每个 Expert 50 MB
- Expert Bank 总大小:\(64 \times 50 = 3200\) MB = 3.2 GB
- 4 张 GPU,每张 40 GB
- Top-1 Routing
- 每请求 16 个 Token
- 目标:降低 P99 Latency
方案 A:EP = 4。 每张 GPU 16 个 Expert,单卡 Expert 参数 0.8 GB。四卡 Expert 参数合计 3.2 GB。需要根据路由结果执行跨 GPU Dispatch / Combine。
方案 B:完整复制 Expert Bank。 每张 GPU 64 个 Expert,单卡 Expert 参数 3.2 GB。四卡合计 12.8 GB。每张 GPU 可以在本地完成 Expert 计算,无需 EP 的跨 GPU Token 交换。
在这个场景下(Expert Bank 3.2 GB、单卡 40 GB、小 Batch),方案 B 的优势明显——3.2 GB 的参数代价换来零通信延迟。小 Batch 下 All-to-All 的通信 Startup 开销占比高,计算量又不足以摊销。
但这个结论有前提条件。以下三种情况会改变决策。
情况一:Expert Bank 变大,单卡放不下。
如果每个 Expert 500 MB 而不是 50 MB,Expert Bank 就是 32 GB。完整复制需要单卡放下 32 GB Expert 参数 + 其他模型参数 + Activation,40 GB 的 GPU 大概率放不下。EP 此时不是优化而是必要条件。
情况二:Batch Size 增大导致 Activation 显存暴涨。
这一点容易被忽略。即使 Expert Bank 大小不变(3.2 GB),Batch Size 增大会推高 Activation 和 Workspace 的显存占用。
假设单卡 40 GB 的显存分配:
- 其他模型参数(Attention、Embedding 等):10 GB
- Activation / Workspace:28 GB(大 Batch 下)
完整复制方案:\(10 + 3.2 + 28 = 41.2\) GB——超了。
EP = 4 方案:\(10 + 0.8 + 28 = 38.8\) GB——在这个简化模型下可以放下。
实际峰值显存必须通过 Profiling 验证。EP 自身也会引入额外的通信 Buffer 和中间状态,这个简化计算忽略了这些开销。
Batch Size 增大可能通过显存容量约束使 EP 变得必要——这和"大 Batch 有利于摊销通信成本"是两个不同的判断维度。 前者是"放不放得下"的硬约束,后者是"快不快"的软优化。
情况三:吞吐目标和 Expert 计算规模变化。
如果 Token 数量足够大,每个 Expert 收到足够多的 Token,Expert 计算本身就能形成高效的 GEMM(M 维度足够大)。此时 EP 的通信成本相对于计算时间的占比降低,更容易被摊销。但是否更快需要 Benchmark。
决策流程#
我在想这个问题的时候,觉得可以简化成几步:
- 完整模型(包含全部 Expert)能否放入单张 GPU?
- 如果不能,瓶颈在 Expert 参数、Dense 参数还是 Activation?
- 判断 EP 能够减少多少单卡显存——只有 Expert 参数部分受益于 EP。
- 在显存可行的候选部署中,测量通信、计算与端到端延迟。
- 选择满足 P99、Throughput 和资源预算约束的方案。
第 4 步没有捷径——纸面分析可以排除明显不可行的方案,但最终决策需要 Profiling。
写在最后#
回到最初的问题。FSDP/ZeRO 通过分片参数、梯度和优化器状态来降低单卡显存。它们假设所有 GPU 执行相同的模型计算,只是处理不同的数据。
EP 面对的情况不同。MoE 模型的 Expert 参数天然是独立的——不同 Expert 有不同的权重矩阵,不存在"对同一个大矩阵做分片"的简单对应。EP 把不同 Expert 固定在不同 GPU 上,让 Token 根据 Router 的运行时选择跨 GPU 流动。
EP 的重点不是简单减少单 GPU 参数量,而是改变参数与计算任务的组织方式。Expert 保持相对固定,Token 根据路由结果动态流动。它解决了参数容量问题,同时把通信与负载均衡变成了新的系统问题。
并行策略不是越多越好。它们是在存储、通信、计算与调度之间交换成本。
参考资料#
- 从 Dense FFN 到 MoE:Router、Token Dispatch 与 Grouped GEMM
- MoE 结构学习笔记:Expert 与 Gate 的几种关系
- 从 AllReduce 到 Ring:分布式训练中的通信原语
- 从 DDP 到 ZeRO:为什么分布式训练要对参数、梯度和优化器状态分片?
- FSDP 已经能分片参数,为什么还需要 Tensor Parallelism?
- Switch Transformers: Scaling to Trillion Parameter Models with Simple and Efficient Sparsity (Fedus et al., 2021)
- MegaBlocks: Efficient Sparse Training with Mixture-of-Experts (Gale et al., 2022)
- NVIDIA Megatron Core Parallelism Guide
- NVIDIA Megatron-LM MoE Implementation
- Hugging Face Expert Parallelism
- TensorRT-LLM MoE Performance Guide
- MIT 6.S191: Introduction to Deep Learning — Lecture 9: Secrets to Massively Parallel Training. https://introtodeeplearning.com/