4、RDMA硬件与软件栈:网卡(HCA/RNIC)、驱动、RDMA verbs API
4.1 核心硬件:HCA 与 RNIC 的定位差异
RDMA 传输能力并非普通网卡的附加功能,而是由专用硬件卸载引擎实现的。在硬件层面,业界将支持 RDMA 的网卡分为两类:HCA(Host Channel Adapter,主机通道适配器)与 RNIC(RDMA-enabled Network Interface Card,支持 RDMA 的网络接口卡)。两者在架构起源上有所不同:HCA 源自 InfiniBand 体系,是 IB 网络的原生接口,CPU 仅负责下发工作请求,数据的封装、路由、流控全部由 HCA 硬件完成;RNIC 则是在以太网基础上扩展 RDMA 能力(即 RoCEv2/iWARP),它既要兼容传统以太网帧处理,又要提供 RDMA 语义的硬件卸载。
从软件视角看,两者的核心差异在于队列对(QP)与完成队列(CQ)的实现深度。HCA 对 verbs 语义支持更完整,例如支持原子操作、可靠连接(RC)的硬件重传;RNIC 在 RoCEv2 下同样支持 RC 服务,但受限于以太网 PFC 流控,其拥塞控制依赖额外机制(如 DCQCN)。下表从多 GPU 集群选型角度对比两者关键特性:
| 对比维度 | HCA(InfiniBand) | RNIC(RoCEv2) |
|---|---|---|
| 帧格式 | IB 链路层(LRH/GRH) | 以太网 + IP/UDP 封装 |
| 拥塞控制 | IB 专用 CC(基于 FECN/BECN) | 依赖 DCQCN / 显式拥塞通知 |
| 典型速率 | NDR 400Gb/s,HDR 200Gb/s | 400GbE / 800GbE |
| GPU 直连 | 支持 GPUDirect RDMA(原生) | 支持 GPUDirect RDMA(需网卡支持) |
| 生态成熟度 | 高性能计算传统优势 | 云数据中心更普及 |
在多 GPU 集群中,HCA 与 RNIC 均通过 PCIe Gen5/6 x16 接口与 GPU 位于同一 NUMA 节点,以减少跨 CPU 的延迟。硬件上,两者都包含专用的 DMA 引擎,能够直接访问 GPU 显存中的注册内存区域,无需 CPU 参与数据搬运。
4.2 驱动与 verbs API 的分层协作
RDMA 软件栈的核心设计原则是内核控制面 + 用户态数据面。驱动负责管理硬件资源(QP、CQ、MR 等),而 verbs API 则向应用程序提供统一的编程接口。以最常见的 libibverbs 库为例,其调用链可概括为:
应用进程
│
├─ 调用 ibv_create_qp() / ibv_post_send() / ibv_poll_cq()
│
├─ libibverbs(用户态库)
│ ├─ 直接写硬件 doorbell 寄存器(数据面,无系统调用)
│ └─ 通过 ioctl 或 write() 进入内核(控制面)
│
├─ 内核驱动(mlx5_core / qedr / hns_roce 等)
│ ├─ 管理 QP 状态机(RESET→INIT→RTR→RTS)
│ ├─ 配置硬件地址表与内存翻译表
│ └─ 处理中断与错误事件
│
└─ HCA/RNIC 硬件固件
├─ 执行发送/接收 DMA 操作
├─ 处理 ACK/NACK 与重传
└─ 生成完成队列事件
关键设计在于:发送路径(post_send)完全在用户态完成。应用将工作请求(WR)写入 QP 的发送队列,然后写一次 doorbell 寄存器通知硬件。整个过程不陷入内核,因此单次发送延迟可低至 1~2 微秒。而 QP 的创建、修改、查询等控制操作则必须经过内核,以保证资源隔离与安全性。
4.3 典型 verbs 对象与生命周期
理解 verbs API 的关键是掌握几个核心对象的创建顺序与依赖关系。以下代码展示一个典型的 RC(可靠连接)QP 建立流程:
/* 1. 获取设备列表与设备属性 */
struct ibv_device **dev_list = ibv_get_device_list(NULL);
struct ibv_context *ctx = ibv_open_device(dev_list[0]);
/* 2. 注册内存区域(MR),获取 lkey/rkey */
struct ibv_pd *pd = ibv_alloc_pd(ctx);
struct ibv_mr *mr = ibv_reg_mr(pd, buf, size,
IBV_ACCESS_LOCAL_WRITE |
IBV_ACCESS_REMOTE_WRITE);
/* 3. 创建完成队列(CQ) */
struct ibv_cq *cq = ibv_create_cq(ctx, 128, NULL, NULL, 0);
/* 4. 创建 QP(队列对) */
struct ibv_qp_init_attr attr = {
.qp_type = IBV_QPT_RC,
.send_cq = cq,
.recv_cq = cq,
.cap = { .max_send_wr = 64, .max_recv_wr = 64,
.max_send_sge = 1, .max_recv_sge = 1 }
};
struct ibv_qp *qp = ibv_create_qp(pd, &attr);
/* 5. QP 状态迁移:RESET → INIT → RTR → RTS */
ibv_modify_qp(qp, &init_attr, IBV_QP_STATE | IBV_QP_PKEY_INDEX |
IBV_QP_PORT | IBV_QP_QKEY);
ibv_modify_qp(qp, &rtr_attr, IBV_QP_STATE | IBV_QP_AV |
IBV_QP_PATH_MTU | IBV_QP_DEST_QPN |
IBV_QP_RQ_PSN | IBV_QP_MIN_RNR_TIMER);
ibv_modify_qp(qp, &rts_attr, IBV_QP_STATE | IBV_QP_SQ_PSN |
IBV_QP_TIMEOUT | IBV_QP_RETRY_CNT |
IBV_QP_RNR_RETRY | IBV_QP_ACCESS_FLAGS);
上述流程中,QP 状态机是 verbs 编程最容易出错的部分。RTR(Ready to Receive)需要远端 QP 的 QPN、LID/GID 和 PSN;RTS(Ready to Send)则需配置重传计数与超时参数。多 GPU 场景下,通常使用 ibv_connect_qp() 配合 rdma_cm 库自动交换 QP 信息,避免手动管理连接握手。
4.4 多 GPU 集群中的实际部署案例
以 8 卡 GPU 节点为例,每张 GPU 通过 PCIe Switch 连接一块 HCA 卡,形成 GPU-to-HCA 一对一拓扑。软件配置要点如下:
- 驱动选型:NVIDIA 平台常用 MLNX_OFED 驱动,包含
mlx5_core内核模块与用户态libmlx5库。 - NUMA 亲和性:使用
ibv_get_device_list()后,通过ibv_query_device_ex()获取设备所属 NUMA 节点,确保 QP 创建在 GPU 所在 NUMA 的 CPU 上。 - GPUDirect RDMA:注册 GPU 显存为 MR 时,需使用
cuMemAlloc()分配内存,并通过ibv_reg_mr注册,驱动会自动识别显存物理地址。 - 多 QP 负载均衡:每个 GPU 对应 2~4 个 QP,分别连接到不同远端 GPU,避免单 QP 成为瓶颈。
实际性能测试中,使用 ib_write_bw 工具验证点对点带宽。当 QP 数量从 1 增加到 4 时,聚合带宽可从 180Gb/s 提升至 380Gb/s(接近 HDR 网卡线速),说明多 QP 并行能有效利用网卡多流水线。
4.5 常见误区与排障复盘
误区一:忽略 QP 状态迁移的时序要求。 在 RTR 之前发送 RTS 参数会导致硬件报错。正确做法是:先交换双方 QP 信息(QPN、PSN、LID/GID),再同时执行 RTR 和 RTS 迁移。
误区二:MR 注册时未设置 IBV_ACCESS_REMOTE_WRITE。 多 GPU 场景中,远端 GPU 需要直接写入本地显存,若缺少该权限位,会返回 IBV_WC_REMOTE_ACCESS_ERR 错误。
排障建议: 当遇到 ibv_poll_cq() 返回 IBV_WC_WR_FLUSH_ERR 时,优先检查 QP 是否进入 ERROR 状态。使用 ibv_query_qp() 查看 QP 状态,并配合 ibv_async_event 捕获硬件事件,通常能快速定位是链路层问题还是 verbs 参数错误。
在驱动层面,dmesg 中常见的 mlx5_core: timeout event 提示往往意味着 PCIe 链路不稳定或固件异常。此时应检查 ibv_devinfo -v 输出的端口状态与链路速率,确认是否降速到低速模式。
4.6 性能调优关键参数
verbs 编程中,以下参数对多 GPU 集群吞吐影响显著:
| 参数 | 推荐值 | 影响说明 |
|---|---|---|
| max_send_wr | ≥ 256 | 过小会限制在途 WR 数量,降低流水线效率 |
| max_send_sge | ≥ 4 | 支持多段内存描述,减少内存拷贝 |
| inline_threshold | ≤ 256B | 小消息内联发送,避免额外 DMA 读 |
| cq_moderation | 32~64 | 聚合完成通知,减少 CPU 中断频率 |
| qp_timeout | 14~20 | 影响重传超时,过大拖慢故障恢复 |
实际调优时,建议先用 ibv_async_watch 监控硬件事件,再逐步增大 max_send_wr 与 CQ 深度,观察 ibv_poll_cq() 的批量返回数量。当单次 poll 返回超过 16 个完成项时,说明队列深度设置合理。