分析对象:NVIDIA-DOCA/gpunetio 开源仓库中的
examples/gpunetio_verbs_write_lat结论先行:该测试使用了六项能力中的 3 项------Verbs CPU 控制路径、GPUNetIO CPU 控制路径、RDMA Verbs 单边(one-sided)GPU 数据路径。
一、六项能力是什么
这六项能力对应仓库 README 中 "Open vs Full" 对比表的六个维度,覆盖了一个 GPUNetIO 应用的完整生命周期:
| # | 能力 | 属于 | 作用 |
|---|---|---|---|
| 1 | Verbs CPU 控制路径 | 控制面 | 在 CPU 上创建和管理 RDMA 资源:设备、PD、MR、CQ、QP,以及 QP 状态机推进(INIT→RTR→RTS) |
| 2 | GPUNetIO CPU 控制路径 | 控制面 | 在 CPU 上建立 GPU handler(doca_gpu_create)、分配/导出 GPU 显存、把 QP/CQ 镜像导出到 GPU 显存 |
| 3 | RDMA Verbs 单边 GPU 数据路径 | 数据面 | CUDA kernel 内直接发起 WRITE/READ/ATOMIC,指定远端地址,远端无需软件参与 |
| 4 | RDMA Verbs 双边 GPU 数据路径 | 数据面 | CUDA kernel 内执行 SEND/RECV 消息语义,接收端需预先 post RECV |
| 5 | 以太网 GPU 数据路径 | 数据面 | CUDA kernel 直接收发二层报文(rxq/txq),仅完整 SDK 提供 |
| 6 | DMA GPU 数据路径 | 数据面 | CUDA kernel 驱动 DMA 引擎做显存拷贝,仅完整 SDK 提供 |
二、write_lat 测试的工作机制
这是一个 GDAKI 版的 ib_write_lat:两台机器通过 RDMA WRITE 乒乓(ping-pong) 测量往返延迟。
控制面阶段(CPU,初始化)
examples/verbs_common.cpp 中的典型调用序列:
c
ibv_open_device(); // 打开 RDMA 设备上下文 ← 能力 1
ibv_alloc_pd(); // 分配保护域 ← 能力 1
ibv_reg_mr(); // 注册内存区域 ← 能力 1
doca_gpu_create(); // 建立 GPU handler ← 能力 2
doca_verbs_qp_attr_create();// 创建 QP(doca_verbs 实现) ← 能力 1
doca_verbs_qp_modify(); // INIT→RTR→RTS 状态机 ← 能力 1
doca_gpu_verbs_export_qp(); // 把 QP/CQ 导出到 GPU 显存 ← 能力 2
注意能力 1 与能力 2 的分工:标准 verbs 资源(device/PD/MR)和 doca_verbs 对象(QP/CQ 属性与状态机)属于 Verbs CPU 控制路径 ;而"让 GPU 能用这些队列"的所有操作(GPU handler、显存管理、队列导出)属于 GPUNetIO CPU 控制路径。两者在初始化阶段是交织使用的,缺一不可。
数据面阶段(GPU,测试主体)
Client 和 Server 都启动 CUDA kernel (这是它与 put_bw 的区别),每个线程执行乒乓循环(gpunetio_verbs_write_lat_kernel.cu):
Client 第 N 轮:
1. 写本地 post_buf 最后一个字节为序号 N
2. kernel 内准备 RDMA WRITE WQE:把 post_buf 写到远端 poll_buf
→ doca_gpu_dev_verbs_wqe_prepare_write(qp, wqe_ptr, wqe_idx,
MLX5_OPCODE_RDMA_WRITE, ...)
3. kernel 内提交并敲 doorbell
→ doca_gpu_dev_verbs_submit() 或 submit_bf()(BlueFlame)
4. 自旋轮询本地 poll_buf,等待 Server 写回的字节变成 N
Server 第 N 轮:
1. 自旋轮询本地 poll_buf,等 Client 写来的字节变成 N
2. 同样用 RDMA WRITE 把数据写回 Client 的 poll_buf
三轮延迟数据的取得依靠两个 GPU 侧动作:
- 接收感知 = 轮询显存 :
while (local_poll_buf[idx] != rcnt);------ 因为是单边 WRITE,数据由网卡硬件直接落进本地显存,接收方 GPU 线程只需盯着显存里的 flag 字节,不需要任何 RECV WQE; - 发送完成确认 = 轮询 CQE :
doca_gpu_dev_verbs_poll_cq_collapsed_at(qp, wqe_idx - 1),kernel 直接读 GPU 显存中的完成队列项。
整个数据面只出现 MLX5_OPCODE_RDMA_WRITE 一种操作码------典型的单边语义 :发起方在 WQE 中指定远端地址(dst + dst_buf_mkey),对端零软件参与。
三、逐项判定
✔ 使用了的能力(3 项)
1. Verbs CPU 控制路径 ------ 使用。
设备打开(ibv_open_device)、PD 分配、MR 注册(ibv_reg_mr,包括 post_buf 和 poll_buf 两块显存 MR)、QP/CQ 创建与 RC 连接状态机(doca_verbs_qp_modify 推进 INIT→RTR→RTS)。没有这些,QP 根本不存在。
2. GPUNetIO CPU 控制路径 ------ 使用。
doca_gpu_create 建立 GPU handler、分配 GPU 显存、以及关键的 doca_gpu_verbs_export_qp/cq 把 QP/CQ 的可执行镜像导出到 GPU 显存------这是 CPU 控制面向 GPU 数据面"交棒"的动作。
3. RDMA Verbs 单边 GPU 数据路径 ------ 使用,且是测试主体。
CUDA kernel 内的 wqe_prepare_write(RDMA_WRITE)→ submit/submit_bf(GPU 直敲 doorbell 或 BlueFlame)→ poll_cq_collapsed_at(GPU 轮询 CQE),构成完整的 kernel 内发-收闭环。延迟数字正是这条路径性能的度量。
✘ 未使用的能力(3 项)
4. RDMA Verbs 双边 GPU 数据路径 ------ 未使用。
全程没有 SEND/RECV 操作码,没有 receive queue 的 post 动作。接收方感知数据靠轮询显存 flag(单边 WRITE 的天然特性),而非消息匹配。
5. 以太网 GPU 数据路径 ------ 未使用。
代码中没有任何 doca_eth / rxq / txq 对象;通信走的是 RDMA(IB 或 RoCE),虽然 RoCE 底层跑在以太网上,但 GPUNetIO 能力分类中的"以太网数据路径"特指二层报文收发队列,与本测试无关。
6. DMA GPU 数据路径 ------ 未使用。
没有 doca_dma 相关调用;数据搬运由网卡 RDMA 引擎完成,不涉及 GPU 驱动的 DMA 拷贝引擎。
四、为什么恰好是这三项------回到 Open vs Full 表
| 能力 | write_lat 是否需要 | 开源版是否提供 |
|---|---|---|
| Verbs CPU 控制路径 | ✔ | ✔(开源 C++) |
| GPUNetIO CPU 控制路径 | ✔ | ✔(开源 C++) |
| RDMA 单边 GPU 数据路径 | ✔ | ✔(.cuh 与完整 SDK 相同) |
| RDMA 双边 GPU 数据路径 | ✘ | ✘ |
| 以太网 GPU 数据路径 | ✘ | ✘ |
| DMA GPU 数据路径 | ✘ | ✘ |
两列完美对齐------这不是巧合。开源版裁剪功能时保留的正是 GDAKI over RDMA 单边通信 这条最小闭环,而 write_lat / write_bw / put_bw 三个示例就是被精心挑选来证明这条闭环完整可用的基准测试:它们只依赖开源版提供的能力,因此可以在完全不安装完整 DOCA SDK 的环境中编译、运行、测出有效延迟数据。
五、总结
回答最初的问题:write_lat 延迟测试 = Verbs CPU 控制路径(建队列)+ GPUNetIO CPU 控制路径(交给 GPU)+ RDMA 单边 GPU 数据路径(kernel 内 WRITE 乒乓),三者分别对应应用的"建资源、交棒、跑数据"三个阶段;双边、以太网、DMA 三条 GPU 数据路径因语义和场景不符,均未被触及。