相关链接:
- CANN 组织主页:https://atomgit.com/cann
- hcomm 仓库地址:https://atomgit.com/cann/hcomm
前言
在 CANN(Compute Architecture for Neural Networks)高性能计算生态中,HCOMM (Huawei Communication)作为 HCCL(Heterogeneous Collective Communication Library)的底层通信基础库,承担着为上层集合通信操作提供高效、可靠点对点(Point-to-Point)数据传输服务的重任。现代 AI 集群的硬件环境高度异构,既包含支持 RDMA (Remote Direct Memory Access)的高速网络(如 InfiniBand, RoCE),也存在仅支持传统 Socket(TCP/IP)的通用以太网环境。如何在这两种截然不同的网络协议之上,构建一个性能卓越且接口统一的通信抽象,是 HCOMM 的核心设计挑战。
一、设计目标与核心抽象:Transport 接口
HCOMM 的统一封装策略始于一个清晰的核心抽象------Transport(传输层)接口。该接口定义了所有网络传输后端必须实现的一组基本操作,完全屏蔽了底层协议的差异。
1.1 Transport 接口定义
在 include/transport/transport.h 中,Transport 被定义为一个纯虚基类:
cpp
// include/transport/transport.h
namespace hcomm {
class Transport {
public:
virtual ~Transport() = default;
// 连接到远程节点
virtual Status Connect(const std::string& remote_addr, int port) = 0;
// 断开连接
virtual Status Disconnect() = 0;
// 异步发送数据
virtual Status SendAsync(
const void* data,
size_t length,
std::function<void(Status)> callback
) = 0;
// 异步接收数据
virtual Status RecvAsync(
void* buffer,
size_t length,
std::function<void(Status, size_t)> callback
) = 0;
// 获取本地地址信息
virtual std::string GetLocalAddress() const = 0;
};
}
这个简洁的接口涵盖了建立连接、断开连接以及最重要的异步发送/接收原语。通过强制所有后端实现此接口,HCOMM 确保了上层逻辑可以以完全相同的方式与任何网络进行交互。
1.2 工厂模式与运行时选择
为了在运行时根据环境自动选择最优的传输后端,HCOMM 采用了经典的工厂模式(Factory Pattern)。
cpp
// include/transport/transport_factory.h
namespace hcomm {
class TransportFactory {
public:
static std::unique_ptr<Transport> Create(const TransportOptions& opts) {
// 1. 检查环境变量或配置文件
if (opts.force_socket || !IsRdmaAvailable()) {
HCOM_LOG(INFO) << "Fallback to Socket transport.";
return std::make_unique<SocketTransport>(opts);
}
// 2. 默认优先尝试 RDMA
HCOM_LOG(INFO) << "Using RDMA transport.";
return std::make_unique<RdmaTransport>(opts);
}
private:
static bool IsRdmaAvailable();
};
}
IsRdmaAvailable() 函数会执行一系列探测操作,例如检查 /dev/infiniband/ 目录是否存在、尝试加载 RDMA 内核模块、或查询系统是否安装了必要的用户态库(如 libibverbs)。这种智能的运行时探测机制使得 HCOMM 应用无需重新编译即可在不同网络环境中自适应地选择最佳后端。
二、RDMA 后端实现:RdmaTransport
RDMA 后端 (src/transport/rdma/) 是 HCOMM 性能的关键。它充分利用了 RDMA 的零拷贝(Zero-Copy)和内核旁路(Kernel Bypass)特性。
2.1 核心组件:Queue Pair **(QP)
RDMA 通信的核心是 **Queue Pair **(QP),它由一个发送队列(Send Queue, SQ)和一个接收队列(Recv Queue, RQ)组成。RdmaTransport 在 Connect 阶段会完成 QP 的创建、初始化和与远程 QP 的握手(通过 CM 或 OOB 通道)。
cpp
// src/transport/rdma/rdma_transport.cc
Status RdmaTransport::Connect(const std::string& remote_addr, int port) {
// 1. 创建 Protection Domain (PD)
pd_ = ibv_alloc_pd(context_);
// 2. 注册内存区域 (Memory Region, MR)
// 将用于收发的缓冲区注册到 MR,获取 lkey/rkey
mr_ = ibv_reg_mr(pd_, buffer_, buffer_size_, IBV_ACCESS_LOCAL_WRITE | ...);
// 3. 创建 Completion Queue (CQ)
cq_ = ibv_create_cq(context_, CQ_DEPTH, nullptr, nullptr, 0);
// 4. 创建并初始化 QP
qp_ = CreateAndInitQP(pd_, cq_);
// 5. 与远程节点交换 QP 信息并建立连接
ExchangeQPInfo(remote_addr, port);
ModifyQPToRTR(qp_); // Ready to Receive
ModifyQPToRTS(qp_); // Ready to Send
return SUCCESS;
}
2.2 零拷贝异步 I/O
SendAsync 和 RecvAsync 的实现直接向 QP 的工作队列(Work Queue, WQ)提交 **Work Request **(WR)。
cpp
// src/transport/rdma/rdma_transport.cc
Status RdmaTransport::SendAsync(const void* data, size_t length, Callback cb) {
// 构建 Send Work Request
ibv_sge sge;
sge.addr = reinterpret_cast<uint64_t>(data);
sge.length = length;
sge.lkey = mr_->lkey;
ibv_send_wr wr{};
wr.wr_id = reinterpret_cast<uint64_t>(new Callback(cb)); // 保存回调
wr.sg_list = &sge;
wr.num_sge = 1;
wr.opcode = IBV_WR_SEND;
wr.send_flags = IBV_SEND_SIGNALED;
ibv_send_wr* bad_wr;
int rc = ibv_post_send(qp_, &wr, &bad_wr);
if (rc) { /* handle error */ }
// 启动一个轮询线程或使用事件通知来处理完成队列 (CQ)
StartCqPollingIfNeeded();
return SUCCESS;
}
// 在 CQ 轮询线程中
void RdmaTransport::PollCompletionQueue() {
ibv_wc wc;
while (ibv_poll_cq(cq_, 1, &wc) > 0) {
if (wc.status == IBV_WC_SUCCESS) {
auto* cb = reinterpret_cast<Callback*>(wc.wr_id);
(*cb)(SUCCESS);
delete cb;
}
}
}
整个过程不涉及任何内核上下文切换和内存拷贝,实现了极致的低延迟和高吞吐。
三、Socket 后端实现:SocketTransport
对于不具备 RDMA 能力的环境,SocketTransport (src/transport/socket/) 提供了一个基于 POSIX Socket API 的健壮备选方案。
3.1 异步 I/O 模型:epoll + 线程池
为了克服传统阻塞式 Socket 的性能瓶颈,SocketTransport 采用了 Linux 的 epoll 机制结合线程池来实现高效的异步 I/O。
cpp
// src/transport/socket/socket_transport.cc
class SocketTransport {
private:
int sockfd_;
int epoll_fd_;
std::thread io_thread_; // 专门的 I/O 轮询线程
std::unordered_map<int, PendingRequest> pending_sends_;
std::unordered_map<int, PendingRequest> pending_recvs_;
void IoEventLoop() {
constexpr int MAX_EVENTS = 10;
struct epoll_event events[MAX_EVENTS];
while (running_) {
int nfds = epoll_wait(epoll_fd_, events, MAX_EVENTS, -1);
for (int i = 0; i < nfds; ++i) {
if (events[i].events & EPOLLOUT) {
HandleSendReady(events[i].data.fd);
}
if (events[i].events & EPOLLIN) {
HandleRecvReady(events[i].data.fd);
}
}
}
}
};
3.2 零拷贝优化:sendmsg 与 iovec
即使在 Socket 后端,HCOMM 也尽力减少内存拷贝。它使用 sendmsg/recvmsg 系统调用配合 iovec 结构体,可以直接从用户提供的非连续缓冲区发送数据,避免了在应用层进行数据拼接。
cpp
// src/transport/socket/socket_transport.cc
Status SocketTransport::SendAsync(const void* data, size_t length, Callback cb) {
// 将请求加入待发送队列
pending_sends_[sockfd_] = {data, length, cb};
// 向 epoll 注册可写事件
struct epoll_event ev;
ev.events = EPOLLOUT | EPOLLET;
ev.data.fd = sockfd_;
epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, sockfd_, &ev);
return SUCCESS;
}
void SocketTransport::HandleSendReady(int fd) {
auto& req = pending_sends_[fd];
struct iovec iov = {const_cast<void*>(req.data), req.length};
struct msghdr msg = {};
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
ssize_t sent = sendmsg(fd, &msg, MSG_NOSIGNAL);
if (sent == static_cast<ssize_t>(req.length)) {
// 发送完成,调用回调
req.callback(SUCCESS);
pending_sends_.erase(fd);
} else if (sent > 0) {
// 部分发送,更新缓冲区指针
req.data = static_cast<const char*>(req.data) + sent;
req.length -= sent;
// 保持 EPOLLOUT 事件注册,等待下一次可写
}
// ... 错误处理
}
四、统一内存管理与上层集成
为了进一步简化上层使用并提升性能,HCOMM 在 Transport 之上还提供了统一的内存管理抽象。
4.1 BufferPool 与内存注册
HCOMM 维护一个 BufferPool,用于管理用于网络传输的缓冲区。对于 RDMA 后端,这些缓冲区在分配时就会被自动注册到 MR;而对于 Socket 后端,则只是普通的内存分配。上层应用(如 HCCL)只需从 BufferPool 申请内存,无需关心底层细节。
4.2 与 HCCL 的无缝对接
HCCL 在初始化通信域时,会通过 HCOMM 的工厂创建 Transport 实例,并将其句柄传递给每个通信对。HCCL 的 AllReduce、Broadcast 等集合操作最终会被分解为一系列点对点的 SendAsync/RecvAsync 调用。由于这些调用都通过统一的 Transport 接口,HCCL 的核心逻辑完全无需感知底层是 RDMA 还是 Socket,真正实现了"协议无关"的通信。
五、总结
CANN HCOMM 通过其面向对象的 Transport 接口、智能的运行时后端选择机制,以及针对 RDMA 和 Socket 的深度优化实现,成功地构建了一套强大而灵活的统一封装层。它不仅让上层 HCCL 能够在各种网络环境中无缝运行,还确保了在支持 RDMA 的高性能集群上能够发挥出极致的通信性能,在普通以太网上也能提供稳健可靠的传输服务。
这种"抽象统一,实现各异"的设计理念,是 HCOMM 作为 CANN 通信基石的核心价值所在。它极大地降低了分布式 AI 应用的部署复杂度,为 CANN 生态在多样化硬件基础设施上的广泛适用性提供了坚实保障。
相关链接:
- CANN 组织主页:https://atomgit.com/cann
- hcomm 仓库地址:https://atomgit.com/cann/hcomm