MoE 专家并行的 all-to-all:两跳通信与吞吐波动
在 **** 的国产卡集群上(8 卡,单卡 64GB HBM,机内卡间互联 392 GB/s,跨机走 RDMA 以太网,机内无全互连交换)以 BF16 加载 DeepSeek 类 671B 稀疏 MoE(MoE 加 MLA 注意力),专家分散到多卡、跨机排布。decode 阶段吞吐随 EP 规模增大不升反降,或停在一条与单参数调优无关的吞吐上限;把集合通信缓冲区、page-size 这类单参数逐一扫描,改善为 0%。同一套权重换成更低并发的负载,吞吐反而回正,波动比稠密模型剧烈得多。问题落在每层前向的两跳 all-to-all:token 分发到专家所在卡、专家输出再收回来,这两跳的通信量随并发、路由到的专家数与 hidden 维度增长,跨机时还有一条无法隐藏的固定延迟。
专家并行把专家分布到设备
稠密模型每层只有一个 MLP,MoE 每层有多个专家,路由网络给每个 token 选 top-k 个专家。专家并行(EP)把这些专家按设备分摊:每张卡或每个 rank 持有一部分专家,token 按路由结果送到持有对应专家的 rank 上计算。EP 与张量并行、数据并行可以组合,常见做法是 attention 部分走张量并行或数据并行、专家部分走 EP。
一个关键量是「激活参数量」。MoE 每 token 只激活 top-k 个专家,激活参数量远小于总参数量,看起来该省算力。代价在通信:路由把 token 打散到不同设备,每层前向必须先分发、再回收,这部分通信不因「稀疏激活」而消失。
一次前向的两跳 all-to-all
每层 MoE 的前向里,通信分两跳:
- 分发(dispatch):路由网络给出每个 token 命中的 top-k 专家后,把 token 按目标专家所在 rank 分组,通过一次 all-to-all 送到各 rank。每个 rank 收到本卡专家要处理的 token,且这些 token 可能来自任意源 rank。
- 回收(combine):各 rank 的专家算完后,把输出按原 token 顺序送回它来的 rank,再按路由权重加权求和。这是第二跳 all-to-all。
两跳是配套的:分发打乱了 token 与 rank 的对应关系,回收必须按同样规则还原。一个千亿级 MoE 有几十层,每层都付这两跳,层数是乘数。
每 rank 每跳通信量 ≈ 本 rank 发出的 token 数 × top_k × hidden × 每元素字节数
一次前向 两跳 = dispatch + combine,各一次 all-to-all
通信量随什么增长
| 增长项 | 影响 | 说明 |
|---|---|---|
| token 数(并发 × 序列长) | 线性放大 | 每个 token 都要分发与回收,批量越大每层通信量越大 |
| top_k | 线性放大 | 路由到 k 个专家,分发量约为单专家的 k 倍 |
| hidden 维度 | 线性放大 | 每 token 传的是 hidden 维向量 |
| 精度 / 每元素字节数 | 线性放大 | BF16 每元素 2 字节,FP8 降到 1 字节 |
| 专家分散度(跨机跳数) | 抬高固定延迟与链路层级 | 专家摊到越多机器,all-to-all 越容易跨机,固定延迟项上升 |
前三项是通信量的乘数,末一项是延迟项。跨机与机内的差别不在带宽一处:机内全互连一跳可达,跨机要做节点间路由,引入一个与数据量无关的固定延迟,这部分无法靠重叠计算隐藏。
现象
同一处通信约束会以几种形态出现:
- 同为激活参数量级,MoE 的实际吞吐低于预期,随 EP 规模增大不升反降;
- 吞吐随并发变化剧烈:小 batch 时每层通信占比高、吞吐低;把并发抬到一个窗口内,通信被计算重叠掉一部分,吞吐回升,再往上又掉;
- 单参数调优失效:把集合通信缓冲区、page-size 等参数扫描一遍,改善为 0%,指标不随参数收敛;
- 只在某些 batch 或长度窗口下成立:融合 MoE 算子开启后,吞吐收益与内存收益不同时兑现,短请求、长请求与混合负载下的结论不一致;
- 轨迹上 all-to-all 与 expert 计算交替,all-to-all 的等待段在 decode 关键路径上固定出现。
仓库中一条记录给出的量化结果(非本机实测):EP 通信约 61% 可被计算隐藏,其余约 39% 暴露在 decode 关键路径上,跨机固定延迟项约 58 ms/层,帧时间近似为 T_fwd = β · batch + 固定延迟,batch 增大时被固定延迟项钳住。这条记录对应的是「无全互连、跨机多跳」的部署形态。
定位步骤
- 拆算子计时:把
moe.dispatch、moe.combine、moe.expert_compute(grouped GEMM;算子名取自 SGLang profiler,非 vLLM 命名)与 all-reduce 分开抓耗时,看热点落在 dispatch、combine 还是专家计算。三者不拆开时,「MoE 慢」无法定位到哪一跳。 - 算通信占比与可隐藏比例:统计 trace 里 all-to-all 等待段里有多少落在计算重叠窗口之外。暴露比例高说明通信落在关键路径上。
- 看利用率形态:所有 rank 同步低位且 all-to-all 等待固定出现,是通信瓶颈;个别 rank 低位、其余高位,是专家负载不均;rank 都高位却慢,是专家计算本身的算力问题。
- 变并发扫一遍:固定模型与 EP 度,把
--max-running-requests从低到高扫,记录 decode 吞吐与 dispatch 耗时。吞吐随并发非单调、且拐点位置随部署形态移动,是通信在关键路径上的特征。 - 确认命中融合算子路径:核对
resolved_backend(本笔记自设的记录字段,真实凭证是启动日志里那一行),确认moe.dispatch命中融合 MoE 实现,还是回退到逐专家的通用实现。融合路径未命中时,路由、top-k、专家 GEMM 未合并,通信与计算都不在预期实现上。 - 确认是否跨机:记录 all-to-all 是否跨节点,以及跨机跳数。同拓扑内该算子可用、跨机时出现固定延迟,是区分两类问题的分界。
# 示意:DeepSeek 类模型多机部署的并行配置(参数名取自 SGLang,取值按本机规格填)
python -m sglang.launch_server --model-path <model> \
--tp-size 16 \
--dp-size 8 \
--enable-dp-attention \
--moe-a2a-backend deepep \
--mem-fraction-static 0.8
处置:开关与代价
| 动作 | 作用 | 代价 / 前提 |
|---|---|---|
| 调 EP 度 | 专家摊到更多 rank,可减少单卡专家数 | EP 度过大时专家跨机分散,跨机跳数上升,固定延迟项增大,收益反转 |
| 专家分组 / 复制策略 | 减少每层跨机跳数,把专家就近放 | 需重排专家到设备,占用更多显存 |
| 拓扑重构(如双副本布局) | 把跨机 all-to-all 压回机内,越过通信固定延迟构成的上限 | 需成倍的机器与更复杂的部署 |
| 换 a2a 后端 | 用融合的分发/回收实现替代通用集合通信 | 须确认后端在该栈上可用 |
| 调整 dense 与 EP 的配比 | 把 dense MLP 与专家部分的切分重新分配 | 需与 TP 度联调,参数名随框架不同 |
| 精度转 FP8 | 每 token 通信量减半 | 引入量化误差,须核精度 |
| 开 MoE 融合算子 | 路由、top-k、专家 GEMM 合并,减少下发与拷贝 | 收益不总成立,须按负载验证 |
调整顺序:先拆开算子计时,判断瓶颈在 dispatch、combine 还是专家计算;确认是通信撞关键路径后,优先减跨机跳数与调 EP 度这类架构层动作,而不是扫单参数。仓库里那条记录的直接结论是:跨机所有算子参数扫描 0% 改善,减少跨机跳数是本轮唯一有效动作,把 EP 单副本跨机换成机内双副本布局后,decode goodput 从 42% 抬到 74%。单参数调优对通信固定延迟上界无效,这一点在 MoE 上比在稠密模型上更常被验证到。
记录字段
# 示意:一条 MoE 专家并行结论的记录(字段名示意)
model:
model_name:
expert_count: # 专家总数
top_k: # 每 token 路由到的专家数
activated_params: # 激活参数量
precision: # BF16 / FP8 / W8A8
hardware:
card_count:
node_count:
interconnect: # 机内互联带宽 / 跨机链路类型 / 有无全互连
parallelism:
ep_size:
tp_size:
cross_node_hops: # 每层 all-to-all 的跨机跳数
moe_dense_tp_size: # dense 部分与 EP 的配比
a2a_backend: # 如 deepep
path:
moe_dispatch: # moe.dispatch 命中的实现
moe_path_enabled: # 融合 MoE 是否开启
resolved_backend:
workload:
request_pattern: # 短请求 / 长请求 / 混合
max_num_seqs:
perf:
decode_throughput:
itl:
goodput:
comm:
dispatch_time: # 分发耗时占比
combine_time: # 回收耗时占比
exposed_ratio: # all-to-all 暴露在关键路径外的比例
status: # 通信瓶颈 / 专家计算瓶颈 / 负载不均;注明结论在哪种负载下成立
小结
MoE 的专家并行把专家摊到不同设备,每层前向付两跳 all-to-all:分发 token 到专家所在 rank、回收专家的输出。通信量随 token 数、top_k 与 hidden 维度线性增长,跨机时多出一条无法隐藏的固定延迟。遇到「同为激活参数量级、吞吐却低于预期且随并发剧变」时,先把 dispatch、combine 与 expert 计算拆开计时,判断瓶颈落在哪一跳;确认是通信撞关键路径后,优先减少跨机跳数与重排专家分组,单参数调优对通信固定延迟上界无效。