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/s400GbE / 800GbE
GPU 直连支持 GPUDirect RDMA(原生)支持 GPUDirect RDMA(需网卡支持)
生态成熟度高性能计算传统优势云数据中心更普及

在多 GPU 集群中,HCA 与 RNIC 均通过 PCIe Gen5/6 x16 接口与 GPU 位于同一 NUMA 节点,以减少跨 CPU 的延迟。硬件上,两者都包含专用的 DMA 引擎,能够直接访问 GPU 显存中的注册内存区域,无需 CPU 参与数据搬运。

GPU 显存 注册内存区域 GPU 显存 注册内存区域 HCA / RNIC 硬件卸载引擎 HCA / RNIC 硬件卸载引擎 驱动 + verbs API ibverbs / librdmacm 驱动 + verbs API ibverbs / librdmacm DMA 写 DMA 写 完成通知 完成通知 RDMA 网络链路 InfiniBand 或 RoCEv2 以太网 用户态:libibverbs / libfabric / UCX 内核态:驱动 + 内核 verbs 接口 GPU 显存 HCA/RNIC 驱动/verbs DMA 数据路径 网络传输

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_moderation32~64聚合完成通知,减少 CPU 中断频率
qp_timeout14~20影响重传超时,过大拖慢故障恢复

实际调优时,建议先用 ibv_async_watch 监控硬件事件,再逐步增大 max_send_wr 与 CQ 深度,观察 ibv_poll_cq() 的批量返回数量。当单次 poll 返回超过 16 个完成项时,说明队列深度设置合理。