返回博客

/ DeepSeek

[DeepSeek-4] DeepEP:MoE 专家并行中的 Token 交通系统

从 MoE Dispatch 与 Combine 的路由语义出发,系统拆解 DeepEP V2 的 Rank 级去重、ElasticBuffer、EPHandle、Direct/Hybrid 通信、FP8 传输、SM/QP 资源模型与通信计算重叠。

15 minDeepSeek · DeepEP · Mixture of Experts · Expert Parallelism

截至 2026 年 7 月,DeepEP 主仓库已经切换到 DeepEP V2。它不是完整的训练或推理框架,而是一个面向 Mixture of Experts(MoE)和 Expert Parallelism(EP)的高性能通信库,核心只围绕两个动作展开:

  • dispatch:把 token 发送到其选中专家所在的 GPU;
  • combine:把专家输出送回 token 原来的 GPU,并按照路由权重合并。

这两个名字看起来像普通的 All-to-All,真正困难的地方却在于:每个 token 的目标都不同,每个专家接收的 token 数也不同,通信前后还必须完成动态布局、局部复制、专家对齐和反向路由恢复。

一句话概括:

DeepEP 是一个将 MoE 的动态稀疏 token 路由映射成高效 NVLink/RDMA 数据流的 GPU 通信运行时。

如果说 DeepGEMM 负责“专家怎样算”,那么 DeepEP 负责的就是“token 怎样送过去,算完以后又怎样送回来”。本文从一次 MoE 层的数据流出发,逐步拆解 DeepEP V2 的对象、通信路径与性能取舍。DeepEP 仓库

1. DeepEP 位于 MoE 层的什么位置#

一个典型 MoE 层可以简化为:

Hidden States
      |
      v
Router / Gate
计算 top-k experts
      |
      | topk_idx + topk_weights
      v
+--------------------+
| DeepEP Dispatch    |  将 token 发到专家所在 GPU
+--------------------+
      |
      v
Grouped GEMM / Expert MLP
      |
      v
+--------------------+
| DeepEP Combine     |  将结果送回原 GPU 并加权求和
+--------------------+
      |
      v
MoE Layer Output

因此,DeepEP 不负责 Router 的 Top-K 选择、专家放置与负载均衡,也不负责 Expert GEMM、Attention 或 DP/TP 的梯度同步。它只接管 Router 与 Expert GEMM 之间,以及 Expert GEMM 与 MoE 输出之间的通信和数据重排。

这个边界非常重要。DeepEP 可以缩短 token 搬运时间,却无法修复一个已经严重失衡的路由结果;它可以把数据排成 Grouped GEMM 需要的布局,却不会替框架决定专家应该放在哪张卡上。

2. 为什么普通 All-to-All 还不够#

假设一个 MoE 层采用如下配置:

EP ranks = 16
Experts  = 256
Experts per rank = 16
Top-K = 8

某个 token 可能选中专家 3、19、44、68、121、155、201、243。这些专家可能分布在 6 到 8 张 GPU 上;另一个 token 的目标集合又会完全不同。于是每对 rank 之间的数据量并不相等,每张 GPU 上各个专家收到的 token 数也在动态变化。

若直接使用通用的 all_to_all_single,框架通常需要在通信前后自行完成:

  1. 统计发往每个目标 rank 的 token 数并计算 split size;
  2. 按目标 rank 排序,将 token pack 到连续发送缓冲区;
  3. 执行变长 All-to-All,再对接收数据 unpack;
  4. 按本地 expert 再次排序,准备 Expert GEMM;
  5. 计算结束后反向重排,再执行一次 All-to-All;
  6. 根据原路由恢复 token 顺序并进行 Top-K 加权归约。

通信原语只解决“字节从 rank A 到 rank B”的问题,却不知道某段数据属于哪个 token、哪个 expert,也不知道回程时怎样将 K 份结果合并。DeepEP 的价值正是把布局计算、pack/unpack、通信、元数据传递、专家对齐和路由恢复融合进专用 GPU kernel。

因此更准确的关系是:

NCCL 提供底层 GPU 通信能力,DeepEP 在其上实现具有 MoE 路由语义的 Dispatch/Combine。

3. Dispatch:把稀疏路由变成连续数据流#

Dispatch 的典型输入是:

x:            [num_tokens, hidden]
topk_idx:     [num_tokens, num_topk]
topk_weights: [num_tokens, num_topk]

其中 topk_idx[t, k] 表示第 t 个 token 选择的第 k 个专家。围绕这些索引,Dispatch 大体执行:

expert_id -> EP rank
          -> 统计每个 rank / expert 的接收量
          -> 计算发送与接收 buffer 的位置
          -> pack token 和路由元数据
          -> 通过 NVLink 或 RDMA 传输
          -> 在目标 GPU 上按本地 expert 排列
          -> 生成每个 expert 的 token 计数
          -> 保存 Combine 所需的反向映射

输出不仅是专家输入张量,还包括 Expert GEMM 分组所需的计数或前缀和。换句话说,Dispatch 的输出布局应当直接服务后续 Grouped GEMM,而不是先产生一份通用布局,再交给另一组算子重复排序。

3.1 按目标 rank 去重 token#

一个很关键、也很有 MoE 语义的优化,是在 rank 层面对 token 去重。假设 token A 的两个 Top-K 专家都位于 GPU 5:

Token A
  |-- Expert 81 -> GPU 5
  `-- Expert 86 -> GPU 5

最直接的方法会把完整 hidden state 向 GPU 5 发送两次。DeepEP 可以只跨 rank 传输一次,再在目标 GPU 上展开给两个本地专家:

GPU 0 ---- Token A ----> GPU 5
                           |-- local copy -> Expert 81
                           `-- local copy -> Expert 86

当前实现记录的 rank 接收 token 数正是去重后的数量:同一个 token 即使选择了同一 rank 上的多个专家,也只计数一次。ElasticBuffer 源码

若专家均匀分布、Top-K 选择近似独立,令 EP rank 数为 PP、Top-K 为 KK,则每个 token 访问的不同 rank 数期望为:

E[Rdst]=P(1(11P)K).\mathbb{E}[R_{\mathrm{dst}}] =P\left(1-\left(1-\frac{1}{P}\right)^K\right).

EP=16、Top-K=8 时:

16(1(1516)8)6.45.16\left(1-\left(\frac{15}{16}\right)^8\right)\approx 6.45.

也就是说,一个 token 虽然激活 8 个专家,平均只需要将 hidden state 发往约 6.45 个 rank。若路由策略进一步限制专家组或节点范围,目标 rank 的重合度会更高,这项优化也会更有价值。

4. Combine:沿原路径返回并完成归约#

专家计算完成后,一个 token 对应 K 份输出 y1,y2,,yKy_1,y_2,\ldots,y_K,最终 MoE 输出为:

y=i=1Kwiyi.y=\sum_{i=1}^{K}w_i y_i.

Combine 必须完成四类工作:

  1. 找到每份 expert 输出对应的源 token 和源 rank;
  2. 将结果从 expert 所在 GPU 发回原始 GPU;
  3. 对同一 token 的多个 expert 输出进行归约并乘以 topk_weights
  4. 恢复原始 token 顺序。

所以 Combine 不是简单的反向 All-to-All。它依赖 Dispatch 保存的映射,才能把动态展开后的专家输出重新折叠回原 token。

训练中的前后向也呈现出一种对称性:

Dispatch forward 的 backward  ~= Combine
Combine forward  的 backward  ~= Dispatch

官方示例正是利用这种对应关系组织自动求导路径。README 示例

5. EPHandle:一次 Dispatch 的“回程票”#

EPHandle 是连接 Dispatch 和 Combine 的核心对象。它描述的不只是一个通信请求,而是一次动态路由布局:

token 从哪里来
      -> 被发往哪些 rank
      -> 在目标 buffer 的哪个 slot
      -> 对应哪个 local expert
      -> Combine 时怎样返回和归约

当前 Handle 中包含原始 topk_idx、rank 与本地 expert 的接收前缀和、源 token 到目标 slot 的映射,以及 Hybrid 模式所需的转发元数据。Combine 因而不必重新推导整个路由。

在 Decode 场景中,如果连续步骤的路由布局确实一致,还可以复用缓存 Handle,跳过一部分 layout 计算与 CPU 同步。但这里的前提是“布局一致”,而不是“shape 一致”:新的 token 一旦选中了不同专家,盲目复用旧 Handle 就会产生错误映射。README 示例

6. 从 V1 的两条路径到 V2 的统一接口#

理解已有的 DeepEP 文章和推理框架配置时,仍然需要认识 V1 的两种通信模式。

6.1 Normal:为训练和 Prefill 追求带宽#

Normal,也称 High-throughput 模式,适用于训练、Prefill、大 batch 和 token 数较多的场景。它采用分层通信:节点内走 NVLink/NVSwitch,节点间走 RDMA。

一条典型跨节点路径是:

source GPU
    | NVLink
    v
local forwarding GPU
    | RDMA
    v
remote forwarding GPU
    | NVLink
    v
destination expert GPU

这类设计利用了节点内与节点间带宽的不对称性,在节点内聚合或转发跨节点流量,使多张 GPU 和多张 NIC 更充分地并行工作。V1 文档将其称为 asymmetric-domain bandwidth forwarding。V1 文档

6.2 Low Latency:为 Decode 压缩固定开销#

Decode 中,每轮每个请求通常只产生一个 token。此时消息更小,kernel launch、CPU 同步、RDMA atomic 和 pack/unpack 的固定开销会比峰值带宽更重要。

V1 Low-latency kernel 因此主要采用更直接的 RDMA 路径,减少节点内转发阶段与主机控制。它还提供 receiving hook,使网络接收可以在后台推进,并与另一个 micro-batch 的 Attention 或 Expert GEMM 重叠;官方文档称这部分后台网络传输不占用计算侧 SM。V1 文档

V1 的场景划分因而十分清楚:

场景主要目标V1 模式
Training吞吐与带宽利用率Normal
Prefill大消息带宽Normal
Decode延迟与 TPOTLow Latency

6.3 V2:ElasticBuffer 统一两套 API#

V1 对外暴露 Buffer.dispatch()Buffer.combine() 以及独立的 low_latency_dispatch()low_latency_combine()。V2 将其统一为:

ElasticBuffer.dispatch()
ElasticBuffer.combine()

训练、Prefill 与 Decode 使用同一套接口;内部再根据拓扑、消息规模、通信资源和 Handle 缓存选择执行方式。初始化时需要声明最坏情况下的容量:

ElasticBuffer(
    group,
    num_max_tokens_per_rank=...,
    hidden=...,
    num_topk=...,
    use_fp8_dispatch=...,
)

这些参数共同决定通信 buffer 与 workspace 的预留规模。统一 API 并不表示所有场景使用同一个固定 kernel,而是将策略选择下沉到了运行时。README 示例

7. V2 的底层变化:NCCL Gin 与设备侧通信#

V1 的跨节点能力主要依赖 NVSHMEM。V2 主路径改为 NCCL Gin Backend,并能够复用已有 NCCL communicator。NCCL Device API 允许 CUDA kernel 直接发起通信操作,减少 host 控制路径,也更便于将索引、搬运与通信推进组合在同一执行流程中。DeepEP 仓库

调用栈可以粗略表示为:

PyTorch
   |
   v
DeepEP ElasticBuffer
   |
   v
NCCL Gin Device Backend
   |
   +-- NVLink / NVSwitch
   `-- RDMA

DeepEP 并没有取代 NCCL:NCCL 负责底层 peer 间的数据传输能力,DeepEP 负责把动态 MoE 路由翻译成可高效执行的通信计划。

8. Direct 与 Hybrid:拓扑决定数据怎样走#

V2 保留了两种内部通信形态。

8.1 Direct#

当 EP group 位于单个 scale-up domain 内、不需要独立 scale-out 层级时,rank 直接与目标 peer 通信:

GPU A ----------> GPU B

典型场景是单个 NVLink/NVSwitch domain 内的 EP。Direct 模式倾向于使用更少的 Queue Pair(QP),避免细粒度消息中 doorbell ringing 本身成为显著开销。

8.2 Hybrid#

当通信同时包含节点内 scale-up 和节点间 scale-out 时,路径被拆成:

Local NVLink
     |
     v
Cross-node RDMA
     |
     v
Remote NVLink

当前源码把 num_scaleout_ranks > 1 视为 Hybrid 模式,并为跨节点转发维护 channel linked list、token forward metadata,以及不同的发送与接收 buffer。ElasticBuffer Runtime

Hybrid 模式则更倾向于为各个 channel 和通知路径准备独立 QP,以推动大量细粒度 token 流并发前进。这说明 DeepEP 优化的并不是少量整齐的大包,而是目的地持续变化、大小不均的稀疏数据流。

9. ElasticBuffer:不只是预分配 Tensor#

ElasticBuffer 是 DeepEP V2 的通信运行时。它管理的不只是数据内存,还包括:

  • send/recv buffer 与 Dispatch/Combine workspace;
  • 路由元数据和 Handle 生命周期;
  • NCCL Gin context 与 RDMA QP;
  • 独立 communication stream;
  • Direct/Hybrid 模式状态与通信资源配置。

“Elastic”目前更多体现为统一接口和可扩展内存布局。官方代码说明当前物理存储仍主要是 GPU memory,CPU/GPU 混合物理内存属于后续扩展方向。ElasticBuffer 源码

由于 MoE 的接收量动态变化,buffer 必须按 num_max_tokens_per_rank、hidden size、Top-K、EP size 和数据类型为最坏情况留出空间。V2 官方也明确指出,它的 buffer 占用高于 V1。DeepEP 仓库

10. EventOverlap:让通信只在真正依赖处阻塞#

DeepEP 通常在独立 communication stream 上运行。例如:

recv_x, *rest, event = buffer.dispatch(
    x,
    topk_idx=topk_idx,
    topk_weights=topk_weights,
    async_with_compute_stream=True,
)
 
# 在 compute stream 上执行与 recv_x 无关的工作
 
event.current_stream_wait()
# 从这里开始才能安全使用 recv_x

依赖关系因此变为:

Communication stream: Dispatch ------------------+
                                                   v
Compute stream:       Independent work -------- Wait -> Expert GEMM

而不是从 Dispatch 发起时就阻塞整个默认 stream。真正的 overlap 仍要求框架找到可并行的计算,并正确管理 buffer 生命周期;设置 async_with_compute_stream=True 只建立了异步执行的可能性,不会凭空创造重叠窗口。README 示例

11. SM 与 QP:通信也要做资源预算#

通信 kernel 并非“免费”运行。它会占用 SM、寄存器、shared memory 和调度槽位;分配过多计算资源虽然更容易打满 NVLink/RDMA,却可能挤压 Expert GEMM。

更多通信 SM
  -> 更容易达到网络峰值
  -> 与 Expert GEMM 竞争更强
 
更少通信 SM
  -> 更适合通信计算重叠
  -> 可能无法打满链路

V1 常通过 benchmark auto-tuning,再手工设置类似 Buffer.set_num_sms(24) 的参数。V2 根据 RDMA/NVLink 带宽、专家数、Top-K、单 SM 读写能力、Direct/Hybrid 模式,以及是否希望与计算重叠建立理论模型,并通过:

get_theoretical_num_sms()
get_theoretical_num_qps()

选择通信 kernel 的 SM 数和 RDMA QP 数。ElasticBuffer 源码

模型隐含的前提是 Router 负载大体均衡;当少数专家过热时,理论带宽模型无法消除等待最慢 expert/rank 的尾部延迟。因此,自动资源选择仍需与真实路由分布一起验证。

12. 为什么 FP8 Dispatch 特别有效#

Dispatch 传输的主体是 hidden state。若每个 token 访问的不同目标 rank 数为 RdstR_{\mathrm{dst}},通信量可以近似写成:

VdispatchT×Rdst×H×B,V_{\mathrm{dispatch}} \approx T\times R_{\mathrm{dst}}\times H\times B,

其中 TT 是 token 数,HH 是 hidden size,BB 是每个元素的字节数。BF16 的 B=2B=2,FP8 的 B=1B=1,因此 FP8 Dispatch 理论上可以将 hidden state 主体流量降低接近一半。

这并不意味着整个 MoE 层都必须使用 FP8。Expert 输出和 Combine 可以继续采用 BF16,以降低多专家加权归约中的精度风险。官方 V2 benchmark 使用的正是“FP8 Dispatch + BF16 Combine”配置。README 示例

13. 怎样阅读官方 V2 性能数字#

官方 V2 benchmark 的代表性配置是:

8K tokens/rank
Hidden = 7168
Top-K = 8
FP8 Dispatch
BF16 Combine

部分结果如下:

拓扑DispatchCombine通信 SM
SM90,CX7,EP 8x290 GB/s81 GB/s12
SM90,CX7,EP 8x461 GB/s61 GB/s6
SM100,单节点 EP8726 GB/s740 GB/s64
SM100,单节点 EP8,少 SM643 GB/s675 GB/s24

官方总结称,V2 相比 V1 的峰值性能最高提升约 1.3 倍,通信 SM 用量最高减少约 4 倍,并将规模扩展到 EP2048。DeepEP 仓库

这里的 GB/s 是逻辑瓶颈带宽,可能将本地 rank 流量与经过不同层级的有效数据量计入指标,不能直接等同于一张 NIC 的物理线速。更有意义的阅读方式是同时观察:

  • 相同拓扑、shape 和数据类型下的端到端延迟;
  • 每 rank 的 token 数与路由分布;
  • 通信使用的 SM/QP 数及其对 GEMM 的影响;
  • Dispatch 和 Combine 是否真正与计算重叠;
  • 最慢 rank 的尾延迟,而不只是平均逻辑带宽。

14. DeepEP 解决不了哪些问题#

14.1 Expert 负载不均衡#

如果 Expert 7 收到 5000 个 token,而 Expert 8 只收到 200 个,即使通信无限快,Expert 7 的 GEMM 仍会成为 straggler。DeepEP 不能替代 auxiliary loss、expert replication、dynamic placement 或 Expert Parallel Load Balancer(EPLB)。

14.2 错误或低效的网络拓扑#

DeepEP 高度依赖 GPU peer access、NVLink/NVSwitch、RDMA,以及 NIC-GPU 的 PCIe 亲和性。GID、MTU、QP、service level、adaptive routing 与流量隔离等配置也会直接影响表现。官方说明 V2 主要在 InfiniBand 上完成测试,理论上兼容 RoCE,并建议通过 virtual lane 隔离 EP 与其他通信流量。README 示例

14.3 不足的 buffer 容量#

MoE 接收 token 数不固定。若 num_max_tokens_per_rank 只按平均负载设置,一次路由波动就可能越过容量上限;若完全按极端上界预留,显存成本又可能过高。容量规划必须结合 batch、Top-K、路由约束、专家热度和并发策略共同完成。

14.4 框架层缺少可重叠工作#

DeepEP 可以暴露异步 event、控制通信 SM,却不能保证调度器一定有独立计算可插入。如果 Attention、Dispatch、Expert GEMM 和 Combine 之间全是严格串行依赖,再好的异步 API 也不会自动转化为端到端收益。

15. DeepEP 与 DP、TP、EP 怎样配合#

假设系统使用 DP16、TP1、EP16

Attention: 每个 DP rank 处理自己的 token
    |
Router:    为每个 token 选择 Top-K experts
    |
DeepEP:    在 EP16 group 内执行 Dispatch
    |
Experts:   每张卡计算本地 experts
    |
DeepEP:    在 EP16 group 内执行 Combine

这里:

  • TP1 表示 expert 内部没有 Tensor Parallel 通信;
  • EP16 表示每个 MoE 层产生一次 Dispatch 和一次 Combine;
  • DP16 是否需要 AllReduce,取决于训练/推理阶段以及 DP、EP group 的组织方式;
  • DeepEP 只优化 EP group 内的 token 交换,不处理 DP AllReduce。

两机 16 卡的 EP16 同时涉及节点内 NVLink/NVSwitch 与节点间 RDMA,是 Hybrid 路径的典型目标。若一个 EP4 group 完全位于单机内部,则 EP 通信只经过节点内互联,不会使用服务器网卡。

16. 从源码理解 DeepEP V2#

V2 kernel 通过轻量 JIT 模块在运行时编译,安装阶段不需要预编译所有 CUDA kernel。仓库同时提供 PTX/SASS dump、PTXAS 检查和 LineInfo 等调试选项。源码目录

阅读时可以沿着一条完整数据流前进:

README ElasticBuffer example
        |
        v
deep_ep/buffers/elastic.py       Python API、参数检查、Handle 管理
        |
        v
tests/elastic/test_ep.py         shape、正确性、性能与调用顺序
        |
        v
csrc/elastic/buffer.hpp          C++ runtime、Direct/Hybrid 状态
        |
        +--> csrc/indexing/      token 索引与布局计算
        |
        +--> csrc/kernels/       Dispatch/Combine 通信 kernel
        |
        `--> csrc/jit/           kernel 编译与缓存

其中 tests/elastic/test_ep.py 是最佳入口:它把 Python API、数据 shape、Handle 生命周期、Dispatch/Combine 对称性和性能指标放在同一上下文中。直接从最底层 kernel 开始读,反而容易看见大量地址与同步细节,却不知道它们在维护哪一种上层路由语义。

结语:优化的不是 All-to-All,而是动态路由全流程#

DeepEP 的核心价值并非单独提高某次 All-to-All 的峰值带宽,而是将一整条动态路由链路作为一个系统问题处理:

Dynamic routing
  + rank-level token deduplication
  + layout and pack/unpack
  + expert alignment
  + FP8 transport
  + hierarchical NVLink/RDMA
  + communication-compute overlap
  + reverse-route recovery
  + SM/QP resource control

对 MoE 系统而言,最值得借鉴的也正是这些边界清晰的原则:Dispatch/Combine 应当理解路由语义;节点内和节点间通信需要分层设计;传输布局要与 Grouped GEMM 联合设计;通信 kernel 必须接受计算资源预算;Prefill 与 Decode 需要不同的优化目标;可以缓存的路由元数据应尽量留在设备侧,避免重复计算与 CPU 同步。

最终,DeepEP 做的事情可以落回一个非常具体的比喻:Router 决定每个 token 要拜访哪些专家,Expert GEMM 完成专家计算,而 DeepEP 负责修建道路、合并同路乘客、安排换乘,并确保每份结果拿着正确的回程票回到原位。