预计阅读 7 分钟

Expert Parallelism:MoE 模型的并行切分

谁该读这一篇? 在跑 DeepSeek-V3 / Mixtral / Qwen3-MoE 这类 MoE 模型,要榨干 GPU 算力的工程师。 前置阅读: 01-tensor-parallelism.md耗时: 40 分钟 学完能: 1. 讲清 MoE 推理的瓶颈在哪里(AllToAll 通信); 2. 区分 EP / TP / DP 在 MoE 模型上的切法; 3. 解释专家不均衡(routing imbalance)为什么是工程问题; 4. 列出 SGLang 支持的几种 MoE 后端(DeepEP、FlashInfer TRT-LLM MoE 等); 5. 配 DeepSeek-V3 在 8 张 H100 上的最佳并行策略。


1. MoE 推理是什么

Transformer 层结构:
  普通 dense:    X → Attention → MLP → 输出
  MoE:           X → Attention → Router → 选 top-k 个 experts → 加权求和 → 输出
  • 模型总参数大(DeepSeek-V3 671B)。
  • 但每 token 只激活少量参数(DeepSeek-V3 激活 37B)。
  • 训练和推理都要解决"experts 怎么分布、怎么通信"的问题。

2. 三种切法

2.1 TP:把每个 expert 内部切片(同 dense)

GPU 0: 所有 experts 的 1/4 column slice
GPU 1: 所有 experts 的 1/4 column slice
...

每 token 经过 router 后激活 top-2 experts;TP 内部的 column → row → AllReduce 和 dense 一样。

代价:每卡装所有 experts 的部分权重,显存利用不充分。

2.2 EP:把不同 experts 分到不同卡

GPU 0: experts [0, 1, 2, 3, ...]
GPU 1: experts [16, 17, 18, ...]
...

每卡只装一部分 experts,显存大幅省下。 代价:token 路由后要跨卡发送(AllToAll)。

2.3 DP:副本

和 dense 一样,多副本,每副本完整一套。最简单但显存最贵。

实战通常是 EP + TP 混合。


3. AllToAll:MoE 推理的核心通信

Token 1 (GPU 0) ─── router 选了 expert 17 (在 GPU 1) ─── 发到 GPU 1
Token 2 (GPU 0) ─── router 选了 expert  3 (在 GPU 0) ─── 留在 GPU 0
Token 3 (GPU 1) ─── router 选了 expert  5 (在 GPU 0) ─── 发到 GPU 0
...
                  │
                  └── 每 token 跨卡传 hidden state,experts 算完再返回

每层 MoE 有 2 次 AllToAll

  1. dispatch:把 token hidden 发到对应 expert 的卡。
  2. combine:experts 算完输出,把结果发回 token 原卡。

AllToAll 在 NVLink 带宽下(每对卡 600 GB/s)通常不是瓶颈; 跨节点时(IB 400 Gb/s)变成主要瓶颈。


4. SGLang 的 MoE 后端

源码:srt/layers/moe/

主要后端:

后端 文件 强在
DeepEP deepep_waterfill.py DeepSeek 团队开源,AllToAll 优化极致
Fused MoE Triton fused_moe_triton/ 通用,Triton 实现
Cutlass MoE cutlass_moe.py Cutlass 算子
FlashInfer TRT-LLM MoE flashinfer_trtllm_moe.py FlashInfer 集成
EP MoE ep_moe/ Pure EP 实现

DeepEP 是 DeepSeek-V3 / Qwen3-MoE 的最佳搭档;其它后端在不同硬件 / 模型上有用。

切换:

--moe-backend deepep

5. 启动 DeepSeek-V3

典型 8 卡 H100 (1 节点):

python -m sglang.launch_server \
    --model-path deepseek-ai/DeepSeek-V3 \
    --tp 8 \
    --enable-ep-moe \
    --moe-backend deepep \
    --quantization fp8 \
    --max-running-requests 256

或者 16 卡跨 2 节点:

# 节点 1, rank 0-7
python -m sglang.launch_server \
    --model-path deepseek-ai/DeepSeek-V3 \
    --tp 16 \
    --enable-ep-moe \
    --dist-init-addr <head_node_ip>:5000 \
    --nnodes 2 --node-rank 0

# 节点 2, rank 8-15
python -m sglang.launch_server \
    --model-path deepseek-ai/DeepSeek-V3 \
    --tp 16 \
    --enable-ep-moe \
    --dist-init-addr <head_node_ip>:5000 \
    --nnodes 2 --node-rank 1

跨节点用 NCCL TCP 或 RDMA。


6. 专家不均衡(Expert Imbalance)

router 选的 top-k experts 不是均匀分布的:某些 expert 被频繁选中,造成"热点 expert"所在的 GPU 排队。

业务影响:

  • TPOT 抖动。
  • 单卡瓶颈。

工程对策:

6.1 EPLB(Expert Parallel Load Balancing)

源码:srt/eplb/

思路:

  • 监测 expert 使用频率。
  • 动态把热门 expert 复制到多卡(增加冗余备份)。
  • 冷 expert 合并到一卡。

启用:

--enable-eplb \
--eplb-rebalance-num-iterations 1000

每 N 步重新评估 expert 分布。

6.2 静态副本

最热的几个 expert 提前部署成 N 副本:

--ep-num-redundant-experts 2

每个 expert 最多 2 副本,router 选时按负载分流。


7. AllToAll 优化

DeepEP 的几个核心 trick:

  • NVLink + RDMA 异构通信:节点内 NVLink,节点间 RDMA,编排成统一原语。
  • Token sorting:对要发到同一目标 GPU 的 token 排序,减少零散 send。
  • Fine-grained pipeline:dispatch 和 expert 计算重叠。

效果:DeepEP AllToAll 比 NCCL ncclAllToAll 快 1.5-2×。


8. EP 和 TP 的混合

DeepSeek-V3 推荐 EP=8 + TP=1 的"宽 EP":

8 GPU, EP=8:
  GPU 0: experts [0..31]
  GPU 1: experts [32..63]
  ...
  GPU 7: experts [224..255]
非 MoE 部分(attention 等): TP=8 切分

attention 用 TP,MoE 用 EP,路由分两套通信。 eplb/ 系列文件实现这个混合。


9. 监控指标

sglang:moe_expert_load{expert_id="x", ...}     # 每 expert 被选次数
sglang:moe_alltoall_latency_ms                 # AllToAll 时延
sglang:moe_imbalance_ratio                     # 最忙 expert / 平均

健康范围:

  • imbalance_ratio < 2.0
  • alltoall_latency < 30% 单层耗时

10. 排障

现象 排查
MoE 启动 OOM EP 太低(单卡装太多 expert);增 EP
TPOT 突增 某 expert 热点;查 expert_load
AllToAll 慢 NCCL / RDMA 配置;NVLink 是否正确启用
router routing 不准 检查 fp8 量化是否影响 router head

11. 小结

  • MoE 推理瓶颈在 AllToAll 通信 + expert 不均衡。
  • 三种并行:TP(切 expert 内部)、EP(按 expert 分布)、DP(副本)。
  • SGLang 支持 DeepEP / Fused Triton / Cutlass / FlashInfer TRT-LLM 多种后端。
  • DeepSeek-V3 推荐 EP=8 + DeepEP 后端 + EPLB 均衡。
  • 监控 imbalance_ratio 和 alltoall_latency。

12. 自检

  1. EP 和 TP 在 MoE 上的取舍?为什么 DeepSeek-V3 推荐 EP?
答案 **TP**:每卡装所有 experts 的列切片。优势:与 dense 部分统一,AllReduce 通信简单。劣势:每卡都装全部 expert,**显存利用差**(每卡都背 256 expert × 1/4 大小)。 **EP**:每卡装一部分 experts 完整。优势:显存按 expert 数严格均分。劣势:token routing 跨卡 → AllToAll 通信。 DeepSeek-V3 推 EP 因为:(a) **expert 多(256)**,TP 时每卡显存被 sparse 权重塞满;(b) **激活稀疏(top-2)**,AllToAll 只传 2/256 的 token;(c) **DeepEP** 把 AllToAll 优化到接近 NVLink 峰值;(d) **EPLB** 解决负载均衡。 实战 EP=8 + DeepEP 比 TP=8 吞吐高 30-50%。
  1. AllToAll 在一层 MoE 里发生几次?分别在哪?
答案 **2 次**: (1) **Dispatch**:每 token 经 router 选 top-k experts,把 token hidden state 发到对应 expert 所在的卡。 (2) **Combine**:experts 算完输出后,把结果发回 token 原卡,按 router weight 加权求和。 实现:每 token 产生 k 个目标位置(top-k),dispatch 把每 token 的 hidden 复制 k 份发出去;combine 把 k 个输出合回原位置。 DeepEP 把这两步 fuse 进通信 kernel,与 expert 计算 overlap,实测占总时延 ~25%(不 fuse 时 ~50%)。
  1. Expert 不均衡的工程对策有哪些?
答案 (1) **EPLB**([`srt/eplb/`](../sglang/python/sglang/srt/eplb/)):周期性统计 expert 使用频次,动态重排——热门 expert **复制**到多卡(增加冗余备份让 router 选时分流),冷门 expert 合并到一卡。 (2) **静态冗余**:`--ep-num-redundant-experts 2` 给最热的几个 expert 提前部署成 2 副本。 (3) **Router 损失项**:训练时加 load-balance loss 让 router 倾向均匀(这是训练侧的事,推理时已固定)。 (4) **批内打散**:调度时把同 expert 高频请求分散到不同 batch,避免单次 forward 内 expert 极度倾斜。 监控 `moe_imbalance_ratio`(最忙 / 平均),健康 < 2.0,> 3.0 必须干预。
  1. DeepEP 比 NCCL AllToAll 快的核心 trick?
答案 (1) **异构通信原语**:节点内用 NVLink(双向 600 GB/s),节点间用 RDMA(IB 400 Gb/s),编排成统一 dispatch + combine API,避免 NCCL 走通用路径的开销。 (2) **Token sorting**:dispatch 前对要发到同一目标 GPU 的 token 排序合并,把零散小包打成大包,减少 send 次数。 (3) **Fine-grained pipeline**:dispatch 的 IO 与 expert 计算 overlap——上一 batch 在算时下一 batch 已在搬。 (4) **避免 padding**:NCCL ncclAllToAll 假设每对 send/recv 同 size,MoE 实际每对 size 不等需要 padding;DeepEP 用 ragged 通信省 padding 字节。 实测 DeepEP 比 ncclAllToAll 快 1.5-2×,与 expert 计算重叠后 MoE 层时延降 40%。
  1. 跨节点跑 DeepSeek-V3 用 NCCL TCP 还是 RDMA?为什么?
答案 **必须 RDMA**(IB 或 RoCE)。 原因:DeepSeek-V3 跨节点 AllToAll 流量大(每 token 几 KB × top-2 × layer 数 × batch),TCP over Ethernet(10/25 Gbps)撑不住,TPOT 直接秒级。 IB 400 Gb/s 让跨节点 AllToAll 跟节点内 NVLink 在一个量级(仍慢 2-3×,但可接受)。 配置:NCCL 自动识别 IB;`NCCL_IB_HCA` / `NCCL_SOCKET_IFNAME` 指定网卡;启动看日志 "NET/IB: ..." 确认走 IB 而非 socket。 没有 IB 的环境:DeepSeek-V3 严格说跑不出生产性能;强行跑就用单节点 + EP=8 + 接受装得下的 prompt 长度上限。

13. 下一步

上游源码:sglang/python/sglang/srt/layers/moe/