摘要:本文将针对 DeepEP 项目进行深入浅出的功能解析与设计分析,并在此基础上提出一些潜在的优化思路。本报告分为三个主要部分:功能解析、创新设计点、可能的优化方案。为了便于理解,文中会适度引用部分代码片段或函数接口说明。
DeepEP 旨在为 MoE(Mixture of Experts)及其专家并行(Expert-Parallel)场景提供高效的通信库,核心功能包括:
从代码组织来看,DeepEP 的核心由 C++/CUDA 层和 Python 层组成:
C++/CUDA 层(见 csrc/deep_ep.cpp
, csrc/deep_ep.hpp
等)
Buffer
类(deep_ep::Buffer
)来统一管理通信流、FIFO 任务队列、本地和 RDMA 缓冲区,以及 CPU 端用作监控的计数器等。Python 层(见 deep_ep/buffer.py
、deep_ep/utils.py
等)
dispatch
、combine
函数,屏蔽了底层通信细节。Buffer
(Python 版本)的封装类,在初始化时,根据用户参数分配 NVLink 或 RDMA 等所需的通信内存,并与底层 C++/CUDA 组件完成同步。set_num_sms
等配置接口,让使用者可根据集群的 SM 资源进行性能调优。整体而言,DeepEP 代码的核心流程是:
Buffer
对象,并根据需求传入 num_nvl_bytes
, num_rdma_bytes
等参数。Buffer
构造函数中,申请 GPU 上/跨节点的通信内存(比如通过 cudaMalloc
或 NVSHMEM)。sync
等接口,完成进程间、设备间的句柄交换与通信初始化。intranode_dispatch
, internode_dispatch
)中,启动对应的 kernel 执行,将数据在不同 GPU 或节点之间搬运。low_latency_dispatch
与 low_latency_combine
走一条更轻量的 RDMA 通道,牺牲一定带宽以换取显著的端到端延迟优势。下面以 deep_ep::Buffer
类中的部分函数为例,简要概括其功能。
Buffer::Buffer(...)
与 Buffer::~Buffer()
Buffer::Buffer(int rank, int num_ranks, int64_t num_nvl_bytes, int64_t num_rdma_bytes, bool low_latency_mode):
rank(rank), num_ranks(num_ranks), ...
{
// 申请本地或跨节点的 buffer;根据使用模式分配 FIFO、workplace 等
// 与 NVSHMEM、cudaIpcMemHandle 等关联
}
cudaMalloc
、cudaIpcGetMemHandle
、NVSHMEM
等步骤建立了一个节点内/节点间的通信共享内存。moe_recv_counter
、moe_recv_expert_counter
等,便于计算接收的 token 数量。Buffer::~Buffer() noexcept(false) {
// 回收资源,等待可能还在运行的 kernel 完成
// 关闭远端 IPC 句柄 / 释放 NVSHMEM / cudaFree
}
Buffer::sync(...)
此函数主要在初始化阶段调用,用于在进程组内交换 IPC 句柄或 NVSHMEM 唯一 ID:
void Buffer::sync(const std::vector<int> &device_ids, const ...){
// 1. 同步 IPC handles,打开远端显存句柄
// 2. 如果需要 RDMA,则初始化 NVSHMEM 并分配对应内存
// 3. 标记 ready,后续即可执行 dispatch/combine
}
在完成 sync
之前,buffer 尚不可用;一旦完成,is_available()
会返回 true。
dispatch
/ combine
在 deep_ep.cpp
中可以看到多对函数,如 intranode_dispatch
/ intranode_combine
(仅走 NVLink)与 internode_dispatch
/ internode_combine
(同时走 NVLink + RDMA 或纯 RDMA),它们大体做的事情相似:
x
, topk_idx
, num_tokens_per_rank
等)计算需要发送数据的拆分方式。例如 intranode_dispatch
的签名(略简化):
std::tuple<torch::Tensor, ..., std::vector<int>, torch::Tensor, ...>
Buffer::intranode_dispatch(
const torch::Tensor& x,
const std::optional<torch::Tensor>& x_scales,
const std::optional<torch::Tensor>& topk_idx,
...
) {
// 1. 计算要发送给每个 rank 的 token 数
// 2. 发起 NVLink 通信
// 3. 返回接收后的张量
}
此过程中,代码会依赖 cudaStream
来实现异步执行,还可支持在调度流上进行内存申请及事件记录,以满足高性能需求。
low_latency_dispatch
/ low_latency_combine
这是 DeepEP 的一大特色,主要用于推理阶段或需要极端低延迟的场景。在这类函数中:
std::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor, std::optional<EventHandle>, std::optional<std::function<void()>>>
Buffer::low_latency_dispatch(..., bool return_recv_hook) {
// 若 return_recv_hook = true,则只发起发送;调用者可在稍后合适的时间再执行 hook 完成真正接收
}
在代码中,可以看到会构造两个 buffer(偶数与奇数),交替使用,以避免同一时刻需要多次复用同一段内存时产生冲突。
多种通信方式融合
cudaIpcMemHandle
方式直接进行 P2P 读写,省去多次跳转。低时延和高吞吐双管齐下
Hook 机制解耦通信与计算
return_recv_hook=true
),这在自定义 pipeline 并行或 CUDA Graph 场景下具有较高自由度。显式流管理与事件机制
async_finish
参数,以事件进行同步或异步隔离,满足一边传输、一边计算的并行需求。代码可读性与可扩展性
Config
类封装了 chunk size、SM 数量等硬件相关的参数。pybind11
进行接口导出,Python 层更容易被上层深度学习框架集成。进一步的自适应调优
num_max_nvl_chunked_recv_tokens
,num_max_rdma_chunked_send_tokens
等参数进行搜索与测量,以适配实际带宽和延迟特性。减少 CPU 等待的瓶颈
moe_recv_counter
等),这会导致调度上 CPU-GPU 间的同步点。可以尝试用 CUDA Graph 或者类似 GPU 触发回调的机制来取代 CPU 轮询,以减少 CPU 主线程的阻塞开销。支持更多网络拓扑结构
智能调度 + 拆包合并
异构设备或跨版本兼容
DeepEP 项目通过 CUDA 内核、NVLink IPC 与 NVSHMEM RDMA 等多种手段,极大提升了专家并行(MoE)场景下的分发与聚合效率。它在高吞吐和低时延方面都做了专门优化,提供了健全而灵活的接口设计。用户既可在大规模预训练等需要极高带宽的场景下使用常规模式,也可在在线推理等对延迟敏感的场景下选用低时延模式。
通过进一步的自动化调优、异构拓扑支持以及智能化的拆包合并策略,DeepEP 有望更好地适配未来规模更大、结构更复杂的分布式深度学习系统,为高效率的 MoE 模型训练和推理提供更加完备的通信解决方案。