专家并行(EP)
DeepSeek-V3 有 671B 参数、256 个路由专家。一个 H100 只有 80 GB HBM。如果每张 GPU 都装完整模型,671B × FP16 2 bytes ≈ 1.3 TB——需要 17 张 H100 才能放下,还不算 KV Cache。但好消息是:每个 token 只激活 37B 参数(~74 GB FP16,一张 H100 刚好装下)。
「每个 token 只用少数专家」这个事实,催生了 DP/TP/PP 之后的一个全新并行维度——EP(Expert Parallelism,专家并行)。它不是 TP 的变种,不是 DP 的特例,它是为 MoE 的稀疏激活专门设计的第五种策略。
本文从「为什么 DP/TP/PP 解决不了 MoE 的存储问题」出发,拆解 EP 的核心机制、All-to-All 通信模式、EP+DP 耦合关系、EPLB 负载均衡、DeepEP 低延迟后端,以及生产环境的选型决策。
一、DP、TP、PP 为什么都解决不了 MoE?
先看三种经典策略面对 MoE 会发生什么。
DP(数据并行):每张 GPU 有完整模型副本。DeepSeek-V3 完整 FP16 副本 = 1.3 TB。单卡装不下,DP 直接出局。
TP(张量并行):把每层的权重矩阵按 hidden 维度切分。Attention 的 QKV projection 可以切,MoE 层的专家 FFN(gate/up projection shape [7168, 2048])也可以切。但 TP 的问题是:每层每个 token 都有一次 All-Reduce。DeepSeek-V3 共 61 层 Transformer,其中后 58 层为 MoE(前 3 层为 Dense FFN,intermediate_size=18432)——仅 MoE 层就有 58 层 × 每层至少 2 次 All-Reduce(up_proj + down_proj 之后各一次)= 116 次 All-Reduce 每次 decode step,加上前 3 层 Dense FFN 的约 6 次,总计约 122 次。在 NVLink 内的 H100 上,单次 All-Reduce 延迟约 5-10μs——122 次就是 0.6-1.2ms,这比一次 decode step 的计算时间(~2-5ms)占比不低,但对 Dense 模型是可以接受的。问题在于:TP 切专家 FFN 并没有利用 MoE 的稀疏性——每个 token 的每个专家 FFN 计算 TP 都要参与 All-Reduce,即使 token 根本不走那个专家。而如果专家没有被切分到每张 GPU(即用 DP 方式保留完整专家),那 TP 就没有解决专家的存储问题。
PP(流水线并行):把 61 层 Transformer(含 58 个 MoE 层)切成几段放到不同 GPU 上。PP 的问题是:它只切层数,不切专家数。每段 GPU 仍然要装下该段内所有层的全部 256 个专家——存储问题没解决,只是换了个维度。
结论:这三种策略都假设「每张 GPU 参与每个 token 的全部计算」,只是分工方式不同。MoE 打破了这一假设——每个 token 只需要少数专家,为什么要让所有 GPU 都参与?
EP 的思路不是「切分所有 GPU 都要用的东西」,而是「让 token 自己选择要去哪张 GPU」。
二、EP 的核心机制:分布、路由、通信
2.1 专家如何分布到 GPU 上
EP 的核心操作:把模型的所有路由专家均匀分配到 EP 组的各张 GPU 上。每张 GPU 持有连续的 1/N 专家权重。
1 | EP=8, 256 个路由专家, 共享专家 = 2 |
连续分布(连续编号的专家在同一张 GPU 上)是 vLLM 和 SGLang 的默认策略,原因有两个:一是路由层的输出(256 维 logits)可以简单地按分段映射到 GPU ID,不需要一张额外的映射表;二是连续分布让 Router 的 weight 和专家 weight 的索引对应关系保持可预测。
共享专家(shared experts,DeepSeek-V3 中有 2 个)不参与 EP 切分——它们处理的是「所有 token 不经过路由都会经过的专家」,所以每张 GPU 都需要完整副本。相较于每个 MoE 层 256 个路由专家的总权重(22.5 GB FP16),2 个共享专家的开销(176 MB)可忽略不计。
2.2 一个 token 的完整 EP 旅程
当一个 token 进入 MoE 层时,完整的 EP 执行流程如下:
1 | 时间线:一个 token 在 EP=8 的 MoE 层中的旅程 |
关键观察:一个 token 的 EP 旅程中,只有 Step 2 和 Step 4 有跨 GPU 通信。Step 1、3、5 都在本 GPU 完成。而且通信的是 token 的 hidden states(7168 维 × FP16 = ~14 KB),不是专家权重。
2.3 Dispatch 和 Combine 的通信量
通信量的精算:
- Dispatch:token 被发送到它路由到的专家所在的 GPU。最坏情况每个 token 的 Top-8 分布在 8 张不同的 GPU 上,数据量 = $\mathrm{batch size} \times \mathrm{top k} \times \mathrm{hidden size} \times \mathrm{dtype size}$。但实际上,一个 token 的 Top-8 通常集中分布在 2-4 张 GPU 上(因为相邻编号的专家在连续 token 上有相似的激活模式)。
- Combine:与 dispatch 对称,从各 GPU 收回专家计算结果,数据量与 dispatch 相同。
- 总通信量(每 token 每 MoE 层): $2 \times \mathrm{top k} \times \mathrm{hidden size} \times 2,\text{bytes}$(FP16)= $2 \times 8 \times 7168 \times 2 \approx 229,\text{KB}$。如果使用 FP8 通信,减半至 $\approx 115,\text{KB}$。
对比 TP 的 All-Reduce:TP=8 的每次 All-Reduce 通信量 $\approx 2 \times \mathrm{hidden size} \times 2,\text{bytes} \times (N-1)/N \approx 2 \times 7168 \times 2 \times 7/8 \approx 25,\text{KB}$。但 TP 每层有多次 All-Reduce(QKV proj、output proj、FFN up、FFN down),总共约 4-6 次 All-Reduce 每层。
EP 的每次通信量比 TP 大(229 KB vs 25 KB),但 EP 只在 MoE 层通信(attention 层无 EP 通信),且 EP 的 combine 可以和最后一轮专家计算并行。
Given:EP 的通信模式是 All-to-All(一对多 + 多对一)→ New:为什么 EP 的通信开销在 MoE 架构下是可接受的——因为它只在需要时才发生,而非每层无差别执行。
三、EP 和 DP 的耦合:不可分割的并行对
3.1 EP 不能独立存在
TP 可以独立配置(TP=8,单节点跑一个模型副本)。PP 可以独立配置。
EP 不行。**EP 需要并发请求驱动多个 rank 同时工作。**原因非常直观:EP 只切了专家的存储和计算,每个 rank 只持有 1/N 的专家——某个请求的 token 路由到了 GPU 2 和 4 的专家,GPU 1 和 3 上如果没有其他请求的 token 在处理,它们就空闲。只有多路请求并发时,各 GPU 上的专家才都有活干,模型才能形成可服务的推理流水线。
这个”并发请求喂饱所有 rank”的能力,在 vLLM 中由 --data-parallel-size 提供。但需要区分两种 DP 概念:
| 传统多实例 DP | vLLM 引擎内 DP(--data-parallel-size) |
|
|---|---|---|
| 概念 | 每个实例有完整模型副本,独立服务 | EP 组内划分的并行维度,rank 间共享专家切分 |
| 通信 | 无需通信 | 有元数据通信(调度、负载均衡) |
| 适用模型 | Dense 和 MoE 均可 | 仅 MoE(parallel.py:859 阻止 Dense 模型使用) |
| 实现方式 | 启动多个 vLLM 实例 + 外部 LB | --data-parallel-size N --enable-expert-parallel |
本文后续讨论的”DP”均指 vLLM 引擎内 DP——它在 EP 语境下与 --data-parallel-size 含义一致。传统多实例 DP 不在 EP 讨论范围内。
3.2 EP 组的大小推导
vLLM 中 EP 组的大小由源码 parallel_state.py 的初始化逻辑决定,而非由用户直接配置:
1 | vLLM EP 组的 rank 编排(parallel_state.py:1894-1901) |
parallel.py:127-128 的注释明确了这一设计意图:“MoE layers will be sharded according to the product of the tensor parallel size and data parallel size.”
实际的 EP rank 数 = TP_SIZE × DP_SIZE(当 CP=1、PP=1 时就等于 WORLD_SIZE)。每张 GPU 持有的专家数 = 总路由专家数 ÷ (TP_SIZE × DP_SIZE)。TP 也在专家维度上生效——如果用 TP=4、DP=1,每卡持有 256/4 = 64 个专家;如果用 TP=1、DP=8,每卡持有 256/8 = 32 个专家。DP 提供了比 TP 更粗粒度的专家切分,且避免了每层 All-Reduce 的开销。
SGLang 的 EP 关系略有不同:ep_size × moe_dp_size = tp_size(见 server_args.py:5273)。SGLang 将 TP 视为总并行度,从中分出 EP 和 DP 两个子维度。这与 vLLM 的「EP = TP × DP」在数学上等价,但概念上 SGLang 将 EP 和 DP 作为 TP 的子划分。
典型配置:
1 | # 8 GPU, DP=8, EP=8(EP 覆盖所有 8 张 GPU,每卡 32 个专家) |
3.3 EP 下的 KV Cache 归属
当一个 token 被 dispatch 到远程 GPU 计算专家后,它的 KV Cache 存放在哪?
答案:KV Cache 始终驻留在 Router 计算发生的那张 GPU 上(即 dispatch 前的 GPU)——token 的 hidden states 被 dispatch 到远程,但 KV 不搬。
token 被 dispatch 到远程 GPU 后,仅执行专家 FFN 计算。计算完成后 combine 回到原始 GPU,attention 层的 K/V 在原始 GPU 上存储和读取。这意味着:
- EP 不影响 KV Cache 的分布。KV Cache 的切分仍由 DP 决定——每个 DP rank 持有自己服务的请求的完整 KV Cache。
- Attention 计算也在原 GPU 完成。MLA 的 QKV 计算和 attention scoring 都发生在原始 GPU,不需要跨 EP rank 搬运 K/V。
- vLLM 中 EP 组内的 DP rank 各自独立管理 KV Cache:每个 DP rank 持有自己服务的请求的完整 KV Cache,EP rank 之间的专家计算不涉及 KV Cache 的搬运或共享。源码中
KVCacheGroupSpec(v1/kv_cache_interface.py:905)用于管理混合注意力架构下不同层组的 KV Cache 规格(如 MHA + MLA 层混合),与 EP 的 rank 分组无关。
这个设计很巧妙:EP 借助了「attention 的计算量和 KV Cache 的大小都远小于专家 FFN 的权重」这个事实。如果 attention 本身也需要跨 GPU 切分(如超长上下文下的 SP),那才需要额外的策略——EP 和 SP 可以叠加使用,但它们在 KV Cache 的管理上是独立的。
Given:DP 是「每张 GPU 有完整模型副本,各处理不同的请求」→ New:EP 在 DP 的基础上把专家权重进一步切分,但 attention 和 KV Cache 仍由 DP 管理,EP 和 DP 各有分工、不可互换。
四、All-to-All 通信:EP 的正反面
4.1 All-to-All 与 All-Reduce 的本质区别
TP 用 All-Reduce(all-reduce:每张 GPU 贡献 1/N 结果,求和后广播回每张 GPU)。通信模式是「所有人的结果,所有人都需要」。
EP 用 All-to-All(dispatch + combine):每张 GPU 把需要远程专家计算的 token 发出去,收回自己需要的专家输出。通信模式是「只有路由到的 GPU 才需要通信」。
| All-Reduce(TP) | All-to-All(EP) | |
|---|---|---|
| 模式 | 全对全归约 | 全对全散射+收集 |
| 触发条件 | 每次矩阵乘之后 | 仅在 MoE 层 |
| 频率 | 每层 4-6 次 | 每 MoE 层 2 次 |
| 通信量(每 token) | ~25 KB × 次数 | ~229 KB × 2(FP16)/ ~115 KB × 2(FP8) |
| 延迟特征 | 与 hidden_size 相关 | 与 top_k × hidden_size 相关 |
| 是否可重叠 | 与下一层重叠(PP 场景) | 与专家计算重叠 |
EP 的 All-to-All 在数据量上打不过 TP 的 All-Reduce——每次通信量更大。但它的优势在于稀疏触发:不是每层都通信(MoE 模型约 50% 的层是 MoE 层,其余是 attention + shared expert),且每次都经过的 attention 层和 LayerNorm 无 EP 通信。
4.2 NCCL 标准 All-to-All 在 EP 场景下的瓶颈
NCCL 的 All-to-All 实现是为通用场景设计的。在 EP 场景下暴露了两个特定瓶颈:
瓶颈 1:decode 阶段的小批量高频率。在 decode 阶段,每个 step 只有 1 个 token(per request),batch 通常在几个到几十个。每次 All-to-All 的数据量 = batch_size × hidden_size / num_gpus。当 batch_size=8、hidden_size=7168、num_gpus=8 时,每张 GPU 的发送量仅约 14 KB。在这个量级,延迟由 kernel launch 开销(~5-10μs)主导,而非带宽。而 DeepSeek-V3 有 58 个 MoE 层,每个 decode step 触发 58 × 2 = 116 次 All-to-All——即使每次仅 5μs 的 launch 开销,累积也达 0.58ms,成为不可忽视的延迟项。
瓶颈 2:dispatch 和 combine 之间的 SM 空转。NCCL 的标准实现是「先完成全部 dispatch,再做专家计算,再等全部 combine 完成」。在此模式中,第一轮 dispatch 的数据到达后 GPU 就开始等待,直到所有 dispatch 都完成。专家计算结束后又等待 combine 完成。SM 利用率远不到 100%。
六、负载均衡:当专家们的工作量不一样
6.1 推理负载天然不均衡
训练中可以用 auxiliary load balancing loss 强制专家均衡——损失函数里加一项惩罚专家负载方差。推理中没法这么做——请求的 token 路由分布完全由输入内容决定。
根据 DeepSeek-V3 技术报告中路由器饱和度的分析,256 个专家在推理时呈长尾激活分布——少数热门专家承载了大部分 token,但绝大多数专家仍然被激活(覆盖面广),极少出现「某专家完全不被使用」的情况。以经验值估算:约 30% 的专家处理了 60% 的 token,其余 70% 的专家处理 40% 的 token。
真正的问题是空间局部性:如果 GPU 3 上的 32 个专家中恰好有 8 个是热门专家,GPU 3 会成为整个 EP 组的瓶颈——其他 GPU 都在等它的专家计算完成。
6.2 EPLB:不停机地迁移专家
SGLang 率先实现了 EPLB(Expert Parallel Load Balancer),vLLM 后续跟进:
1 | EPLB 工作流程: |
关键设计决策:迁移期间不中断推理。这通过双缓冲路由表实现——Router 维护两套映射表,迁移期间的旧请求仍使用旧表,新请求使用新表。类似于 RCU(Read-Copy-Update)。
6.3 冗余专家:以空间换均衡
当迁移频率太高(某些专家持续热门)时,EPLB 的收益被迁移开销吃掉。另一种互补策略是冗余专家——允许特定热门专家在多个 GPU 上有副本。
1 | # vLLM 冗余专家配置 |
256 个路由专家 + 8 个冗余 = 264 个专家分布在 8 张 GPU 上。每卡 33 个专家(比原来多 1 个),但 8 个热门专家被复制到其他 GPU 上——Router 可以将 token 随机路由到副本中的任意一个,分散负载。
多余显存开销:每个冗余专家 ≈ 专家 FFN 权重大小(DeepSeek-V3 中约 88 MB FP16),8 个副本 ≈ 700 MB——对于每个 MoE 层 ~22.5 GB 的专家总权重来说,是 ~3% 的额外开销。
Given:EP 的静态分布(256 专家均匀分到 8 GPU)无法适应推理负载的不均衡 → New:EPLB 动态迁移和冗余专家两种策略如何在延迟和显存之间做权衡。
七、EP 的边界:什么时候该用,什么时候不该用
7.1 EP 的前提条件
EP 不是万能药。它的适用边界非常清晰:
| 条件 | EP 收益 | 原因 |
|---|---|---|
| MoE 模型,专家数 ≥ 64 | ✅ 强烈推荐 | 专家存储瓶颈,EP 是最优解 |
| MoE 模型,专家数 8-64 | ⚠️ 视情况 | 小专家数下 EP 通信占比高 |
| MoE 模型,专家数 < 8 | ❌ 不推荐 | All-to-All 开销 > 存储收益 |
| Dense 模型(LLaMA, Qwen) | ❌ 不需要 | 没有专家,EP 无意义 |
| 单卡能装下所有专家 | ❌ 通常不需要 | EP 不会带来吞吐收益 |
一个反直觉的结论:EP 的价值不在于「让推理更快」,而在于「让 MoE 模型能在多卡上跑」。如果只有 1 张 A100 但 Qwen2.5-72B(Dense 模型)装不下——用 TP(或量化)。如果有 8 张 H100 跑 DeepSeek-V3(MoE 模型)——首选 EP。两者的应用场景完全不同。
Dense 模型需要多副本怎么办? vLLM 的
--data-parallel-size仅用于 MoE + EP 场景(§3.1 中已区分两种 DP)。Dense 模型(LLaMA/Qwen)的多副本部署推荐多实例 DP:启动多个独立的 vLLM 实例,由外部负载均衡器分发请求。各实例之间无通信,不使用--data-parallel-*参数。
7.2 EP vs TP 在 MoE 场景下的决策框架
MoE 模型部署中最常见的困惑:「我用 TP 也能切权重,为什么还要学 EP?」
| 决策维度 | TP | EP |
|---|---|---|
| 切分对象 | 所有层的权重矩阵 | 仅 MoE 层的专家 FFN |
| Attention 层 | 切(head 维度) | 不切 |
| 每层通信 | 是 | 仅 MoE 层 |
| 单层通信量 | 低(多次小 All-Reduce) | 高(2 次大 All-to-All) |
| 总通信量 | 与层数成比例 | 与 MoE 层数成比例 |
| 存储效率 | 降至 1/N(N=TP size) | 专家权重降至 1/N,attention 不变 |
| 关键约束 | 跨 NVLink 延迟 | All-to-All 延迟 |
经验法则:
- MoE 模型优先考虑 EP:先用 EP 解决专家存储(EP=8, 每卡 32 专家),再看 attention 层是否需要 TP(通常 MLA 的 attention 权重很小,单卡可装,不需要 TP)。
- Dense 模型只能用 TP/PP:没有专家,不存在 EP 选项。
- 超大 MoE 模型 EP + TP 组合:当 attention 层的隐藏维度太大以至于单卡装不下 attention 权重时(如将来的 DeepSeek 模型 hidden_size 扩展到 12288 以上),EP + TP 叠加。TP 切 attention,EP 切专家。
7.3 典型配置速查
1 | # 场景 1:单节点 8×H100,DeepSeek-V3(推荐配置) |
八、总结
EP 不是「比 TP 更好的并行策略」。它是在 MoE 成为主流模型架构之后才成为必需品的第五个并行维度——在 Dense 模型时代,DP+TP+PP 覆盖了所有部署场景;MoE 的 256 专家 × 每 token 只用 8 个专家的稀疏激活模式,彻底改变了并行切分的基本假设。
EP 的核心取舍:
- 收益:让 MoE 模型能在多卡上跑,专家权重存储降低到 1/N。对于 DeepSeek-V3 的 256 专家,EP=8 意味着每 GPU 只需存储 32 个专家(而非全部 256 个)。
- 代价:每次 MoE 层引入两次 All-to-All 通信(dispatch + combine),每次 ~115-229 KB per token。负载不均衡导致部分 GPU 过载。
- 应对:DeepEP 用专用化、persistent kernel、FP8 通信、计算-通信重叠来压延迟。EPLB 用动态迁移和冗余副本解决负载均衡。
- 边界:EP 永远与 DP 耦合——没有 DP 的并行请求,EP 无法工作。Dense 模型不需要 EP。
向前看:随着 MoE 模型专家数从 256 增长到 512、1024,EP 的 All-to-All 延迟瓶颈将被进一步放大。FP4 通信(Blackwell 支持)、更激进的计算-通信全重叠、以及专家分组(先按组路由再按专家路由)是下一代 EP 优化的探索方向。