0. DOCA GPUNetIO(概述)
本文档提供 DOCA GPUNetIO API 的概述与配置说明。
原文:https://networking-docs.nvidia.com/doca/archive/3-5-0/doca-gpunetio(DOCA 3.5.0 归档版本,页面最后更新:2026 年 9 月 1 日)
简介
DOCA 各库的质量状态列于此处。
DOCA GPUNetIO 实现了对网络数据包的实时 GPU 处理,非常适用于以下应用领域:
- 信号处理(Signal processing)
- 网络安全(Network security)
- 信息采集(Information gathering)
- 输入重建(Input reconstruction)
传统方法通常依赖以 CPU 为中心(CPU-centric)的模型:CPU 与网卡(NIC)协调,借助 GPUDirect RDMA 将数据包接收到 GPU 内存中;随后 CPU 通知 GPU 上的 CUDA kernel 来处理这些数据包。然而在低功耗平台上,这种对 CPU 的依赖可能成为瓶颈,限制 GPU 性能并增加延迟。
DOCA GPUNetIO 通过提供以 GPU 为中心(GPU-centric)的解决方案应对这一挑战,将 CPU 从关键路径中移除。它结合了多项 NVIDIA 技术,为网络数据包处理提供了一种高效且可扩展的方法。
与 DOCA GPUNetIO 集成的技术包括:
- GPUDirect RDMA------实现网卡与 GPU 内存之间的直接数据包传输,消除不必要的内存拷贝
- GPUDirect Async Kernel-Initiated(GDAKI) ------允许 CUDA kernel 在无需 CPU 介入的情况下控制网络操作
- 当与 RDMA 协议配合使用时,GDAKI 也被称为 IBGDA
- GDRCopy 库------允许 CPU 直接访问 GPU 内存
- NVIDIA BlueField DMA 引擎------支持由 GPU 触发的内存拷贝
以下是以 CPU 为中心(CPU-centric)方式的示例示意图:
【图示:CPU-centric 方式------(1) 网卡(Network Card)接收数据包(Receive Packets);(2) 数据包经 PCIe 通过 DMA 传输(DMA transfer over PCIe)进入 GPU 内存(GPU memory);(3) CPU 进程(CPU process)解除阻塞、通知 GPU 处理(Unblock GPU processing);(4) CUDA 处理(CUDA Processing)对 GPU 内存中的数据包进行处理(Process Packets)。】
以下是以 GPU 为中心(GPU-centric)方式的示例示意图:
【图示:GPU-centric 方式------(1) CUDA 处理(CUDA Processing)直接通过网卡接收数据包(Receive Packets);(2) 数据包经 PCIe 通过 DMA 传输进入 GPU 内存;(3) GPU 处理数据包(Process Packets);(4) GPU 直接发送数据包(Send Packets)。整个流程无需 CPU 参与。】
DOCA GPUNetIO 的关键特性包括:
- GPUDirect Async Kernel-Initiated(GDAKI)
- GDAKI 网络通信------GPU CUDA kernel 可以控制网络通信来发送或接收数据
- GPU 可控制以太网通信(Ethernet/IP/UDP/TCP/ICMP)
- GPU 可控制 RDMA 通信(支持 InfiniBand 或 RoCE)
- 应用关键路径中无需 CPU 介入
- GDAKI 网络通信------GPU CUDA kernel 可以控制网络通信来发送或接收数据
- GPUDirect RDMA
- 实现与 GPU 内存之间的直接数据传输,无需经由 CPU 进行中转拷贝
- DMA 引擎控制
- CUDA kernel 可使用 BlueField 的 DMA 引擎发起内存拷贝
- 用于低延迟通信的信号量(Semaphore)
- 支持 CUDA kernel 之间、或 CUDA kernel 与 CPU 线程之间的高效消息传递
- 智能内存分配
- 分配对齐的 GPU 内存缓冲区,优化内存访问
- 借助 GDRCopy 库分配可从 CPU 访问的 GPU 内存缓冲区
- 精确发送调度(Accurate Send Scheduling)
- 基于用户定义的时间戳,对以太网数据包的发送提供精确控制
使用 DOCA GPUNetIO 的 NVIDIA 应用包括:
- Aerial 5G SDK------用于超低延迟的 5G 网络操作
- NIXL------NVIDIA Inference Xfer Library(NIXL)旨在加速 AI 推理框架(如 NVIDIA Dynamo)中的点对点通信
- Holoscan 高级网络操作符(Advanced Network Operator)------为边缘 AI 环境中的实时数据处理提供支持
- NVQLink------基于 Holoscan sensor bridge operator 的新型 GPU RoCE 收发器
- UCX------通过 GPUNetIO 函数实现的全新 GDAKI 模块
- NCCL------通过 GPUNetIO GPU 通信启用的 GIN 传输
- NVSHMEM------用于 GPU 通信的 GPUNetIO 传输,替代原有的 IBGDA 传输
有关 DOCA GPUNetIO 的更多信息,请参阅以下 NVIDIA 博客文章:
- Inline GPU Packet Processing with NVIDIA DOCA GPUNetIO
- Unlocking GPU-Accelerated RDMA with NVIDIA DOCA GPUNetIO
- Realizing the Power of Real-time Network Processing with NVIDIA DOCA GPUNetIO
相对此前版本的变更
3.4.0 中的变更
- GPUNetIO Verbs:
- 在
doca_gpu_dev_verbs_submit_bf函数中通过DOCA_GPUNETIO_VERBS_GPU_CODE_OPT_BF_UNRELIABLEcode_opt 模板参数引入了新的不可靠(unreliable)BlueFlame 模式 - 改进了
gpunetio_verbs_write_lat示例 - 新增扩展原子 WQE:
doca_gpu_dev_verbs_wqe_prepare_atomic_ext - 在 poll_cq 函数中引入了更轻量的 fence:
doca_gpu_dev_verbs_fence_acquire_nvidia_nic - 对 CUDA 设备函数
doca_gpu_dev_verbs_get及相关的gpunetio_verbs_get_bw示例进行了多项增强 - 引入独立的
doca_gpu_dev_verbs_mcst函数,以便在 Ampere 或更早架构 GPU 上启用 MCST
- 在
- 所有 GPUNetIO Verbs 带宽示例现在均以 Gbps 为单位返回性能数据
通用性能与最佳实践
请遵循以下准则,以最大化性能并简化 DOCA GPUNetIO 的开发工作。
构建配置
- 使用 release 构建 :始终在
meson.build文件中设置buildtype = 'release'来编译应用和示例。与默认的 debug 模式相比,这能确保最大吞吐量和最小延迟。
硬件与系统设置
dmabuf的驱动选择 :安装nvidia-open驱动以使用dmabuf。如果环境依赖闭源 NVIDIA 驱动(cuda-drivers),系统则不支持dmabuf,请改用nvidia-peermem。- 优化 PCIe 拓扑:为获得峰值吞吐量,确保 GPU 与网卡之间为 PIX 或 PXB 拓扑(通过单个 PCIe 桥连接)。避免 NODE 或 SYS 拓扑,因为跨 NUMA 节点或 SMP 互连路由数据会降低效率。
- 扩大 BAR1 容量 :如果应用无法将内存缓冲区映射到网卡,请使用
nvidia-smi -q检查 GPU 的 BAR1 容量。在系统 BIOS 中启用 Resizable BAR 以分配足够的空间。 - 不支持 GPUDirect RDMA 的系统 :对于不支持 GPUDirect RDMA 的硬件(如 DGX Spark),请使用
DOCA_GPU_MEM_TYPE_CPU_GPU内存类型,并为发送操作启用 CPU 代理(CPU proxy)模式。
编程准则
- CUDA 上下文初始化 :在调用 DOCA GPUNetIO 函数(如
doca_gpu_create)之前,确保目标设备上已存在活跃的 CUDA 上下文。可通过调用cudaFree(0)来有效初始化该上下文。 - 优化执行作用域:使用高层以太网 API 时,选择尽可能宽的执行作用域(Warp 或 Block),而非 Thread 作用域。这能最小化原子操作上的竞争,并显著提高门铃(doorbell)敲响效率。
- 严格的指针管理 :严格区分内存访问边界以防止段错误(segmentation fault)。
memptr_gpu仅可在 CUDA kernel 内使用,memptr_cpu仅可用于 CPU 侧管理。
权限
- Root 权限要求 :所有以太网示例和应用都依赖 DOCA Flow,因此必须以
sudo或 root 权限执行。 - 非 root 替代方案 :Verbs、RDMA 和 DMA 操作可以在无 root 权限的情况下运行。要启用此功能,请为 NVIDIA 驱动配置选项
NVreg_RegistryDwords="PeerMappingOverride=1;"。
导航中心
请选择与您当前目标匹配的路径:
| 目标 | 描述 | 链接 |
|---|---|---|
| 搭建我的系统 | 安装软件包、配置网卡固件(ConnectX/BlueField)、验证 PCIe 拓扑 | 安装与设置 |
| 了解概念 | 理解 GDAKI、CPU 与 GPU 控制路径的对比、GPUNetIO 内存模型 | 架构与设计 |
| 开始编码 | 查阅以太网、RDMA、Verbs 和 DMA 的 CPU 与 GPU 函数 | API 参考 |
| 运行演示 | 按照构建说明操作并执行提供的示例应用 | 示例指南 |
1. GPUNetIO 安装与设置
原文:https://networking-docs.nvidia.com/doca/archive/3-5-0/gpunetio-installation-and-setup(DOCA 3.5.0 归档版本,页面最后更新:2026 年 9 月 1 日)
DOCA GPUNetIO 包含在 doca-all 软件包中,该包可从 DOCA 下载门户获取,支持所有受支持的操作系统。
要安装所需的 DOCA GPUNetIO 组件,请使用您操作系统的包管理器。
-
对于 Ubuntu/Debian:
textapt install doca-all doca-sdk-gpunetio libdoca-sdk-gpunetio-dev -
对于 RHEL:
textyum install doca-all doca-sdk-gpunetio doca-sdk-gpunetio-devel
为在构建任何 DOCA GPUNetIO 示例或应用时获得最佳性能,必须在 meson.build 文件中将 buildtype 设置为 release(例如 buildtype = 'release')。以默认的 debug 模式构建会导致性能显著下降。
要运行 DOCA GPUNetIO 应用,系统必须同时配置一块 GPU 和一块网卡(NVIDIA® ConnectX® 或 NVIDIA® BlueField®),二者均通过 PCIe 连接到系统。
【图示:主机系统(Host System)中,CPU 运行 DOCA GPUNetIO 应用(DOCA GPUNetIO Application),GPU 与 ConnectX/BlueField 网卡通过 PCIe 与主机相连。】
系统的内部硬件拓扑应当对 GPUDirect RDMA 友好,以最大化 GPU 与网卡之间的内部吞吐量。要验证 GPU 与网卡之间的连接类型:
text
$ nvidia-smi topo -m
GPU0 NIC0 NIC1 CPU Affinity NUMA Affinity GPU NUMA ID
GPU0 X NODE NODE 12-23,36-47 1 N/A
NIC0 NODE X PIX
NIC1 NODE PIX X
Legend:
X = Self
SYS = Connection traversing PCIe as well as the SMP interconnect between NUMA nodes (e.g., QPI/UPI)
NODE = Connection traversing PCIe as well as the interconnect between PCIe Host Bridges within a NUMA node
PHB = Connection traversing PCIe as well as a PCIe Host Bridge (typically the CPU)
PXB = Connection traversing multiple PCIe bridges (without traversing the PCIe Host Bridge)
PIX = Connection traversing at most a single PCIe bridge
NV# = Connection traversing a bonded set of # NVLinks
NIC Legend:
NIC0: mlx5_0
NIC1: mlx5_1
图例中文说明:
X= 设备自身SYS= 连接跨越 PCIe 以及 NUMA 节点之间的 SMP 互连(如 QPI/UPI)NODE= 连接跨越 PCIe 以及同一 NUMA 节点内 PCIe 主桥(Host Bridge)之间的互连PHB= 连接跨越 PCIe 以及一个 PCIe 主桥(通常是 CPU)PXB= 连接跨越多个 PCIe 桥(但不跨越 PCIe 主桥)PIX= 连接至多跨越单个 PCIe 桥NV#= 连接跨越由 # 条 NVLink 组成的绑定链路
为最大化 GPU 与网卡之间的吞吐量,系统应采用具有专用 PCIe 连接的 **PIX(或 PXB)**拓扑。如果 GPU 和网卡位于同一 PCIe 主桥和同一 NUMA 节点上,PHB 拓扑也是可以接受的,尽管性能可能因平台而异。为获得最佳性能,建议避免 NODE 和 SYS 拓扑------尽管应用仍能正常运行,但它们可能对性能产生负面影响。
DOCA GPUNetIO 已在裸机系统和 Docker 容器中完成充分测试。对虚拟化环境的支持目前仍被视为实验性质。
网卡配置
ConnectX 网卡
确保 ConnectX 固件与当前 DOCA 版本兼容。NVIDIA 建议使用 ConnectX-6 Dx 或更高版本的适配器。
-
启动 MST:
$ sudo mst start -
检查 MST 状态以获取 MST 设备标识符:
$ sudo mst status -v示例输出:
MST modules: ------------ MST PCI module is not loaded MST PCI configuration module loaded PCI devices: ------------ DEVICE_TYPE MST PCI RDMA NET NUMA ConnectX6DX(rev:0) /dev/mst/mt4125_pciconf0.1 b5:00.1 mlx5_1 net-ens6f1 0 ConnectX6DX(rev:0) /dev/mst/mt4125_pciconf0 b5:00.0 mlx5_0 net-ens6f0 0 -
配置 ConnectX 网卡:
-
对于以太网(Ethernet)传输,运行以下命令,并将
<mst_device>替换为实际的 MST 设备名(例如/dev/mst/mt4125_pciconf0):mlxconfig -d <mst_device> s KEEP_ETH_LINK_UP_P1=1 KEEP_ETH_LINK_UP_P2=1 KEEP_IB_LINK_UP_P1=0 KEEP_IB_LINK_UP_P2=0 # 仅当应用使用精确发送调度(Accurate Send Scheduling)功能时才需要执行下面这条 mlxconfig -d <mst_device> --yes set ACCURATE_TX_SCHEDULER=1 REAL_TIME_CLOCK_ENABLE=1以下示例假设适配器为双端口。如果是单端口,则只有 P1 选项适用。
-
对于 InfiniBand 传输,运行:
mlxconfig -d <mst_device> s KEEP_ETH_LINK_UP_P1=0 KEEP_ETH_LINK_UP_P2=0 KEEP_IB_LINK_UP_P1=1 KEEP_IB_LINK_UP_P2=1 # 精确发送调度功能无法与 InfiniBand 一起使用以下示例假设适配器为双端口。如果是单端口,则只有 P1 选项适用。
-
-
执行冷重启以应用更改:
ipmitool power cycle
BlueField 网卡
要将 NVIDIA BlueField-2 或 BlueField-3 与 DOCA GPUNetIO 配合使用,DPU 必须处于 NIC 模式,以便将内部的 ConnectX 暴露给主机应用。
-
启动 MST:
$ sudo mst start -
检查 MST 状态以获取 MST 设备标识符:
$ sudo mst status -v示例输出:
MST modules: ------------ MST PCI module is not loaded MST PCI configuration module loaded PCI devices: ------------ DEVICE_TYPE MST PCI RDMA NET NUMA BlueField3(rev:1) /dev/mst/mt41692_pciconf0.1 9f:00.1 mlx5_1 net-ens6f1np1 1 BlueField3(rev:1) /dev/mst/mt41692_pciconf0 9f:00.0 mlx5_0 net-ens6f0np0 1 -
配置 BlueField 网卡:
-
对于以太网传输:
sudo mlxconfig -d /dev/mst/mt41692_pciconf0 --yes set LINK_TYPE_P1=2 LINK_TYPE_P2=2 INTERNAL_CPU_MODEL=1 INTERNAL_CPU_PAGE_SUPPLIER=1 INTERNAL_CPU_ESWITCH_MANAGER=1 INTERNAL_CPU_IB_VPORT0=1 INTERNAL_CPU_OFFLOAD_ENGINE=DISABLED # 仅当应用使用精确发送调度功能时才需要执行下面这条 sudo mlxconfig -d /dev/mst/mt41692_pciconf0 --yes set ACCURATE_TX_SCHEDULER=1 REAL_TIME_CLOCK_ENABLE=1 -
对于 InfiniBand 传输:
sudo mlxconfig -d /dev/mst/mt41692_pciconf0 --yes set LINK_TYPE_P1=1 LINK_TYPE_P2=1 INTERNAL_CPU_MODEL=1 INTERNAL_CPU_PAGE_SUPPLIER=1 INTERNAL_CPU_ESWITCH_MANAGER=1 INTERNAL_CPU_IB_VPORT0=1 INTERNAL_CPU_OFFLOAD_ENGINE=DISABLED # 精确发送调度功能无法与 InfiniBand 一起使用
-
-
执行冷重启以应用更改:
ipmitool power cycle -
以太网模式的验证命令示例:
sudo mlxconfig -d /dev/mst/mt41692_pciconf0 q LINK_TYPE_P1 LINK_TYPE_P2 INTERNAL_CPU_MODEL INTERNAL_CPU_PAGE_SUPPLIER INTERNAL_CPU_ESWITCH_MANAGER INTERNAL_CPU_IB_VPORT0 INTERNAL_CPU_OFFLOAD_ENGINE ACCURATE_TX_SCHEDULER REAL_TIME_CLOCK_ENABLE示例输出(以太网模式):
LINK_TYPE_P1 ETH(2) LINK_TYPE_P2 ETH(2) INTERNAL_CPU_MODEL EMBEDDED_CPU(1) INTERNAL_CPU_PAGE_SUPPLIER EXT_HOST_PF(1) INTERNAL_CPU_ESWITCH_MANAGER EXT_HOST_PF(1) INTERNAL_CPU_IB_VPORT0 EXT_HOST_PF(1) INTERNAL_CPU_OFFLOAD_ENGINE DISABLED(1) ACCURATE_TX_SCHEDULER True(1) REAL_TIME_CLOCK_ENABLE True(1)
PCIe 配置
在某些 x86 系统上,必须禁用访问控制服务(Access Control Services,ACS),以确保网卡与 GPU 之间的直接通信------无论它们位于同一融合加速器 DPU 上,还是位于系统中不同的 PCIe 插槽上。推荐的解决方案是通过 PCIe 桥的 BIOS(如 Supermicro 或 HPE)禁用 ACS 控制。也可以通过命令行禁用它,但效果可能不如 BIOS 选项。
以下 lspci -tvvv 输出展示了一个典型的系统拓扑:
$ lspci -tvvv
...+-[0000:b0]-+-00.0 Intel Corporation Device 09a2
| +-00.1 Intel Corporation Device 09a4
| +-00.2 Intel Corporation Device 09a3
| +-00.4 Intel Corporation Device 0998
| \-02.0-[b1-b6]----00.0-[b2-b6]--+-00.0-[b3]--+-00.0 Mellanox Technologies MT42822 BlueField-2 integrated ConnectX-6 Dx network controller
| | +-00.1 Mellanox Technologies MT42822 BlueField-2 integrated ConnectX-6 Dx network controller
| | \-00.2 Mellanox Technologies MT42822 BlueField-2 SoC Management Interface
| \-01.0-[b4-b6]----00.0-[b5-b6]----08.0-[b6]----00.0 NVIDIA Corporation Device 20b8
需要关注的 PCIe 交换机地址是 b2:00.0(DPU 的入口点)。ACSCtl 的所有值必须都为负(关闭):
setpci -s b2:00.0 ECAP_ACS+0x6.w=0000
验证设置是否已正确应用:
$ sudo lspci -s b2:00.0 -vvvv | grep -i ACSCtl
ACSCtl: SrcValid- TransBlk- ReqRedir- CmpltRedir- UpstreamFwd- EgressCtrl- DirectTrans-
更多信息请参阅相关页面(PCIe ACS 说明 等)。
如果应用仍然没有报告收到任何数据包,请尝试禁用 IOMMU。在某些系统上,可以在 BIOS 的北桥(NorthBridge)配置中找到 VT-d 或 IOMMU 选项,将其设置为 Disable 并保存。系统可能还需要在内核选项中添加 intel_iommu=off 或 amd_iommu=off,可以通过 grub 命令行完成:
$ sudo vim /etc/default/grub
# GRUB_CMDLINE_LINUX_DEFAULT="iommu=off intel_iommu=off <更多选项>"
$ sudo update-grub
$ sudo reboot
GPU 配置
CUDA 依赖
DOCA GPUNetIO 组件依赖于 CUDA。CPU 侧共享库与 GPU 侧数据路径组件的依赖要求有所不同:
- CPU 共享库(
libdoca_gpunetio.so) :该库依赖于libcuda.so(CUDA Driver API)。由于它不使用 CUDA Runtime API,因此不受运行时相关版本问题的影响。 - GPU 数据路径组件 :数据路径函数以头文件和静态库两种形式交付,二者要求不同:
- 纯头文件 API(GPUNetIO Ethernet、GPUNetIO Verbs):这些是内联函数。由于它们与您的应用一起编译,因此非常灵活,可与任何较新的 CUDA 版本(如 CUDA 12.x 或 13.x)一起使用。
- 静态库 API(GPUNetIO DMA、CommCh、RDMA):该库使用 CUDA 13.0 预构建。因此,任何使用该静态库中函数的应用都必须使用 CUDA 13.0 或更新版本构建。
一般建议尽可能使用 CUDA 12.6 或更新版本,以利用新特性。
为减少应用初始启动延迟,强烈建议启用 NVIDIA 驱动持久模式(persistence mode):
bash
nvidia-smi -pm 1
GDRCopy 安装
为在不使用 CUDA API 的情况下实现 CPU 对 GPU 内存的直接访问,DOCA 需要 GDRCopy 内核模块和库。
-
安装必要的软件包:
sudo apt install -y check kmod -
克隆 GDRCopy 仓库:
git clone https://github.com/NVIDIA/gdrcopy.git /opt/mellanox/gdrcopy -
构建 GDRCopy:
cd /opt/mellanox/gdrcopy && make -
加载 GDRCopy 内核模块:
./insmod.sh -
检查
gdrdrv和nvidia-peermem模块是否已加载:lsmod | egrep gdrdrv示例输出:
gdrdrv 24576 0 nvidia 55726080 4 nvidia_uvm,nvidia_peermem,gdrdrv,nvidia_modeset -
导出 GDRCopy 库路径:
textexport LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/opt/mellanox/gdrcopy/src -
确保 CUDA 库路径已在环境变量中:
textexport PATH="/usr/local/cuda/bin:${PATH}" export LD_LIBRARY_PATH="/usr/local/cuda/lib:/usr/local/cuda/lib64:${LD_LIBRARY_PATH}" export CPATH="$(echo /usr/local/cuda/targets/{x86_64,sbsa}-linux/include | sed 's/ /:/'):${CPATH}"
GDRCopy 是可选的。如果未安装,DOCA GPUNetIO 将无法使用 DOCA_GPU_MEM_TYPE_GPU_CPU 标志分配内存。如果未检测到 GDRCopy,DOCA GPUNetIO 会记录警告消息。
如果您的应用不需要 GDRCopy,可以安全地忽略相关警告消息。要使用 GDRCopy,请确保其安装路径包含在 LD_LIBRARY_PATH 环境变量中,或通过 GDRCOPY_PATH_L 环境变量指定。
GPU 内存映射
为使网卡能够使用 GPU 内存发送和接收数据包,必须使用内存映射机制。DOCA 支持两种方法:
dmabuf(默认方法):映射 GPU 内存的首选现代方法。nvidia-peermem(回退方法) :当dmabuf不可用或失败时使用的传统方法。
使用 dmabuf
这是映射 GPU 内存的主要方法。该方法的前提条件是:
- Linux 内核版本 6.2 或更高
libibverbs版本 1.14.44 或更高- CUDA 工具包:
- 版本 12.5 或更早:必须带
-m=kernel-open标志安装(即开源 NVIDIA 驱动模式) - 版本 12.6 或更新:默认启用开放内核(open kernel)模式
- 版本 12.5 或更早:必须带
请确保系统安装了 nvidia-open 驱动。如果显示的是 cuda-drivers,则说明 NVIDIA 驱动是以闭源版本安装的,此时无法使用 dmabuf。
使用 nvidia-peermem
当 dmabuf 不可用时使用此方法。它需要加载 nvidia-peermem 内核模块(随 CUDA 工具包一起安装):
sudo modprobe nvidia-peermem
实现与回退逻辑
推荐的实现方式是先尝试获取 dmabuf 文件描述符;如果失败,应用应回退到 nvidia-peermem 方法。
以下代码片段演示了如何通过 DOCA mmap 使用 dmabuf 进行 GPU 内存映射,包括回退逻辑:
c
/* 从 CUDA 获取 GPU 内存缓冲区的 dmabuf 文件描述符 */
result = doca_gpu_dmabuf_fd(gpu_dev, gpu_buffer_addr, gpu_buffer_size, &(dmabuf_fd));
if (result != DOCA_SUCCESS) {
/* 如果 dmabuf 失败,回退到 nvidia-peermem 传统方法 */
doca_mmap_set_memrange(gpu_buffer_mmap, gpu_buffer_addr, gpu_buffer_size);
} else {
/* 使用 dmabuf 创建 DOCA mmap */
doca_mmap_set_dmabuf_memrange(gpu_buffer_mmap, dmabuf_fd, gpu_buffer_addr, 0, gpu_buffer_size);
}
处理 dmabuf 失败
doca_gpu_dmabuf_fd 失败(即示例中的 if 分支)很可能表明 NVIDIA 驱动未处于开源模式。
随后调用 doca_mmap_start 时,DOCA 将尝试映射 GPU 内存。如果未设置 dmabuf,它会自动回退到传统的 nvidia-peermem 方法。此时会记录以下警告消息:
[DOCA][WRN][linux_devx_adapter.cpp:374] devx adapter 0x5566a16018e0: Registration using dmabuf is not supported, falling back to legacy registration
如果您的应用可以依赖 nvidia-peermem 而并非严格要求 dmabuf,则可以安全地忽略此警告消息。
示例实现
- GPUNetIO Ethernet 示例使用带
dmabuf的 DOCA mmap,并以nvidia-peermem作为回退(遵循上述代码示例中的逻辑)。 - GPUNetIO Verbs 示例展示了另一种基于 verbs 的方法:使用
ibv_reg_dmabuf_mr(用于dmabuf),并以ibv_reg_mr作为回退。
GPU BAR1 容量
每当一个 GPU 缓冲区被映射到网卡时(例如与发送或接收队列关联的缓冲区),都会占用一部分 GPU BAR1 映射空间。因此,检查 BAR1 映射空间是否足够容纳 DOCA GPUNetIO 应用尝试映射的所有字节非常重要。可以使用 nvidia-smi 验证 GPU 的 BAR1 映射空间:
$ nvidia-smi -q
==============NVSMI LOG==============
.....
Attached GPUs : 1
GPU 00000000:CA:00.0
Product Name : NVIDIA A100 80GB PCIe
Product Architecture : Ampere
Persistence Mode : Enabled
.....
BAR1 Memory Usage
Total : 131072 MiB
Used : 1 MiB
Free : 131071 MiB
默认情况下,某些 GPU(如 RTX 型号)的 BAR1 容量可能非常小:
$ nvidia-smi -q | grep -i bar -A 3
BAR1 Memory Usage
Total : 256 MiB
Used : 6 MiB
Free : 250 MiB
如果 BAR1 容量不足,DOCA GPUNetIO 应用可能会因 DOCA mmap 无法将 GPU 内存缓冲区映射到网卡而报错退出(例如 Failed to start mmap DOCA Driver call failure)。要解决此问题,必须从 BIOS 增大 GPU BAR1。系统应启用 "Resizable BAR" 选项。更多信息请参阅 NVIDIA 论坛帖子。
无 Root 权限运行
所有使用以太网的 DOCA GPUNetIO 示例和应用都依赖 DOCA Flow,因此必须以 sudo 或 root 权限执行。
不过,如果在 NVIDIA 驱动中启用了特定选项,Verbs、RDMA 和 DMA 示例可以在无 sudo 权限的情况下运行:
-
为 NVIDIA 驱动创建配置文件:
cat <<EOF | sudo tee /etc/modprobe.d/nvidia.conf options nvidia NVreg_RegistryDwords="PeerMappingOverride=1;" EOF -
执行冷重启以确保更改生效。
-
使用以下命令验证配置是否已应用:
$ grep RegistryDwords /proc/driver/nvidia/params您应看到以下输出,确认设置已生效:
RegistryDwords: "PeerMappingOverride=1;"
DGX Spark
由于硬件拓扑限制,DGX Spark 不支持 GPUDirect RDMA。不过,DOCA GPUNetIO 应用仍可通过使用 CPU 固定内存(DOCA_GPU_MEM_TYPE_CPU_GPU)替代 GPU 内存,在此类系统上执行。这同时适用于队列分配和数据包/数据内存。
以太网配置
使用 DOCA Ethernet 创建 Rx 或 Tx 队列时,必须使用 doca_eth_rxq_gpu_data_path.h 和 doca_eth_txq_gpu_data_path.h 中的 setter 函数,将队列分配到 CPU-GPU 共享内存上:
c
doca_error_t doca_eth_rxq_gpu_set_rq_mem_type(struct doca_eth_rxq *eth_rxq, enum doca_gpu_mem_type rq_mem_type);
doca_error_t doca_eth_txq_gpu_set_sq_mem_type(struct doca_eth_txq *eth_txq, enum doca_gpu_mem_type sq_mem_type);
对于 Tx 队列,还必须启用 CPU 代理模式来处理发送:
c
doca_error_t doca_eth_txq_gpu_set_uar_on_cpu(struct doca_eth_txq *eth_txq);
数据路径上 CPU 代理的实现可参见 gpunetio_simple_send_sample。
Verbs RDMA 配置
类似的方法也适用于 GPUNetIO Verbs 应用,区别在于应用负责使用 DOCA_GPU_MEM_TYPE_CPU_GPU 内存类型显式分配队列内存(QP UMEM)。
在发送侧,同样支持 CPU 代理模式。实现细节请参阅以下 GPUNetIO Verbs 示例:
gpunetio_verbs_put_bw_samplegpunetio_verbs_put_counter_bw_samplegpunetio_verbs_write_bw_samplegpunetio_verbs_write_lat_sample
2. GPUNetIO 架构与设计
原文:https://networking-docs.nvidia.com/doca/archive/3-5-0/gpunetio-architecture-and-design(DOCA 3.5.0 归档版本,页面最后更新:2026 年 9 月 1 日)
架构
一个 DOCA GPUNetIO 网络应用可分为两个基本阶段:
- 配置阶段(CPU):CPU 负责所有初始设置工作,例如设备配置、内存分配以及启动 CUDA kernel。
- 数据路径阶段(GPU):GPU 与网卡直接交互,执行高速数据包处理功能。
DOCA GPUNetIO 提供了一系列构建模块,用于创建完全在 GPU 上运行的完整数据路径流水线,通常与 DOCA Ethernet、DOCA RDMA Verbs、DOCA RDMA 或 DOCA DMA 等其他库组合使用。
设置与组件模型
在设置阶段,基于 CPU 的应用必须:
- 在 CPU 上准备所有必需的对象(如队列、上下文)。
- 为这些对象导出 GPU 专用的句柄(handle)。
- 启动一个 CUDA kernel,将对象的 GPU 句柄作为参数传递给它,使 kernel 能在数据路径阶段操作这些对象。
正是这种"CPU 设置、GPU 运行"(CPU-setup, GPU-run)的模型,决定了 DOCA GPUNetIO 由以下几个不同的组件构成:
libdoca_gpunetio.so(CPU 控制路径):包含控制路径函数的共享库。CPU 应用使用这些函数来准备 GPU、分配内存和配置对象。libdoca_gpunetio_device.a(GPU 数据路径------静态库):包含 GPUNetIO RDMA、GPUNetIO DMA 和 GPUNetIO CommCh 数据路径函数的静态库。这些函数由 GPU 在 CUDA kernel 内部调用。doca_gpunetio_dev_*.cuh(GPU 数据路径------头文件):一组为 GPUNetIO Ethernet 和 GPUNetIO Verbs 提供内联数据路径函数的头文件。它们被直接编译进应用的 CUDA kernel 中。
下图展示了典型流程:
【图示:典型流程------配置阶段(Configuration Phase)在 CPU 上依次执行:设置 DOCA 网络设备(Setup DOCA network device)→ 设置 DOCA GPU 设备(Setup DOCA GPU device)→ 创建队列、流规则和内存(Create queues, flows, memory)→ 将 DOCA 对象导出到 GPU(Export DOCA objects on GPU)→ 启动 CUDA Kernel(Launch CUDA Kernel)。进入数据路径阶段(Datapath Phase)后 CPU 空闲(CPU free);GPU CUDA kernel 执行"接收数据包(Receive packets)→ 处理数据包(Process packets)→ 共享结果(Share results)",以及"准备数据包(Prepare packets)→ 发送数据包(Send Packets)"。】
库链接
- CPU 共享库的 pkgconfig 文件是
doca-gpunetio.pc。 - GPU 静态库(
libdoca_gpunetio_device.a)没有 pkgconfig 文件。如果您的应用需要这些 CUDA device 函数,必须显式链接该库。- 默认路径:
/opt/mellanox/doca/lib/x86_64-linux-gnu/libdoca_gpunetio_device.a
- 默认路径:
面向不同协议的 GDAKI 数据路径
DOCA GPUNetIO 提供 GPU GDAKI(GPU Direct Access Kernel Interface)函数,用于控制由其他 DOCA 库创建的各种传输和协议对象。本节说明 DOCA GPUNetIO 与这些其他库之间的对应关系。
以太网 GDAKI 通信
要启用基于以太网传输的 GPU 加速通信,应用必须组合使用三个 DOCA 库:
- DOCA GPUNetIO:提供 GPU 专用句柄和数据路径函数。
- DOCA Ethernet:创建和管理底层的 TX/RX 队列。
- DOCA Flow:将数据包引导(steer)到正确的 GPU 管理的队列。
控制路径阶段:初始 CPU 配置
在 GPU 上发生任何数据路径操作之前,CPU 必须首先配置所有必需的资源:
- 为网卡创建 DOCA Core 设备句柄。
- 为 GPU 创建 DOCA GPUNetIO 设备句柄。
- 使用 DOCA Ethernet 库:
- 创建所需的发送队列(TXQ)和/或接收队列(RXQ)。
- 将这些队列句柄的数据路径设置为 GPU。
- 导出代表这些队列的 GPU 专用句柄。
- 使用 DOCA Flow 库创建并安装流引导规则,将所需类型的数据包引导到新建的 DOCA Ethernet 接收队列。
数据路径阶段:GPU Kernel 执行
配置阶段完成后,应用可以启动一个 CUDA kernel,将以太网队列的 GPU 句柄作为输入参数传入。这样,DOCA GPUNetIO CUDA device 函数就能在 kernel 内部直接操作这些队列。
所有 GPUNetIO Ethernet CUDA device 函数均以 inline 函数形式提供在以下头文件中:
doca_gpunetio_dev_eth_rxq.cuhdoca_gpunetio_dev_eth_txq.cuh
这些函数分为两种不同的 API:
- 低层 API :提供对基本
mlx5元素的细粒度控制,例如提交工作队列元素(WQE)、敲响网卡门铃(doorbell)、轮询完成队列元素(CQE)。 - 高层 API :提供更复杂的、预封装好的函数,实现以下高级特性:
- 共享发送 QP(Shared Send QP):允许单个发送队列被不同的 CUDA 线程、warp 或 block 安全地并发访问。
- 协作接收 QP(Cooperative Receive QP):允许单个线程、warp 内所有线程或 block 内所有线程协作,从单个接收队列并行接收数据包。
- 内存一致性(MCST):针对 Hopper 之前架构 GPU 的特性,用于管理接收侧的内存映射。
两种 API 都支持 CPU 代理(CPU proxy)模式------这是为无法从 GPU 直接敲响门铃的系统准备的回退机制。
有关高层和低层 GPUNetIO Ethernet API 的使用示例,请参阅《GPUNetIO 示例指南》。
示例用例与延伸阅读
以太网 GPU 通信的示例,请参阅 DOCA GPU Packet Processing 应用指南(doca_gpu_packet_processing)以及相关示例(doca_gpunetio_simple_send、doca_gpunetio_simple_receive、doca_gpunetio_send_wait_time)。
要深入理解底层以太网发送和接收的结构、对象与函数,请参阅 DOCA Ethernet 库文档。
当使用多个队列和/或信号量接收以太网流量时的示例示意图:
【图示:多队列接收架构------数据包(Packet)经 DOCA Flow 规则分流到以太网接收队列 0 和队列 1;CUDA kernel"接收方"(Receiver)中,CUDA Block 0 和 Block 1 分别执行接收(Receive)和分发(Dispatch),将结果写入各自的 GPU 信号量(Semaphore GPU,含 Item 0、Item 1......);CUDA kernel"处理方"(Processing)轮询信号量(Poll semaphore)→ 数据包处理(Packet processing)→ 产生输出(Produce Output)→ 分发(Dispatch)到 CPU 信号量(Semaphore CPU);CPU 线程(CPU Thread)轮询信号量(Poll semaphore)并上报统计信息(Report stats)。】
将接收到的数据包分发给另一个 CUDA kernel 并非必需。更简单的场景可以由单个 CUDA kernel 同时接收和处理数据包:
【图示:单 kernel 简化场景------数据包经 DOCA Flow 规则进入以太网接收队列 0 和队列 1;CUDA kernel"接收方"中,Block 0 和 Block 1 各自执行接收(Receive)与处理(Process),直接把结果写入 CPU 信号量(Semaphore CPU);CPU 线程轮询多个信号量(Poll semaphores)并上报统计信息(Report stats)。】
RDMA Verbs GDAKI 通信(IBGDA)
DOCA GPUNetIO 为使用 DOCA RDMA 和 DOCA RDMA Verbs 库创建的对象提供 GPU 数据路径函数。这使得基于 RDMA 传输协议(IB 或 RoCE)的 GPU 通信成为可能。
DOCA GPUNetIO 与 DOCA RDMA
该方式使用高层 DOCA RDMA 库,它抽象了大部分底层 mlx5 和 IBVerbs 细节。GPUNetIO 的 CUDA 数据路径函数同样采用类似风格的较高层 API。
关键特征:
- 为通用 RDMA 操作(Write、Send、Read、Recv)提供高层 API。
- 以闭源 CUDA 静态库(
libdoca_gpunetio_device.a)形式交付。 - 不包含内置的共享队列管理。应用必须手动管理来自不同 CUDA 线程对队列的同时访问。
- 最适合执行基本 RDMA 操作的较简单 GDAKI 应用,因为它不需要深入了解 IBVerbs 或
mlx5细节。
Weak 模式与 Strong 模式
部分 RDMA GPU 函数提供两种操作模式:
- Weak 模式 :由应用负责计算队列中的下一个可用位置。
- 辅助函数(如
doca_gpu_rdma_get_info)提供下一个可用位置和队列大小掩码(用于索引回绕)。 - 开发者必须指定确切的队列描述符编号,确保不跳过任何描述符。
- 更复杂,但性能更好,并允许开发者针对 GPU 内存合并访问(coalescing)进行优化。
- 辅助函数(如
- Strong 模式 :GPU 函数自动将 RDMA 操作入队到下一个可用位置。
- 管理更简单,开发者无需跟踪位置。
- 可能因原子操作引入额外延迟,且不保证连续的操作使用连续的内存位置。
注意:所有 strong 模式函数都在 CUDA block 级别运作。不能从两个不同的 CUDA block 同时访问同一个 RDMA 队列。
配置与使用
- 使用 DOCA Core 为网卡创建设备句柄。
- 使用 DOCA GPUNetIO 为 GPU 卡创建 GPU 设备句柄。
- 使用 DOCA RDMA:
- 创建发送和/或接收队列句柄。
- 将队列句柄的数据路径设置为 GPU。
- 导出代表这些队列的 GPU 句柄。
配置完成后,启动 CUDA kernel,将 RDMA 队列的 GPU 句柄作为输入参数传入。在 kernel 中使用 doca_gpunetio_dev_rdma.cuh 中定义的函数(以 doca_gpu_dev_rdma_* 开头)进行 RDMA 通信。
示例用例
GPUNetIO RDMA 函数的示例请参阅 doca_gpunetio_rdma_client_server_write 示例。
要深入理解 RDMA 操作,请参阅 DOCA RDMA 文档。
DOCA GPUNetIO 与 DOCA Verbs
该方式使用更低层的 DOCA RDMA Verbs 库。GPUNetIO Verbs 的 CUDA 数据路径函数以 inline 函数形式提供在 doca_gpunetio_dev_verbs_*.cuh 头文件中。
这些函数分为两种不同的 API:
- 低层 API :直接操作基本的 RDMA
mlx5元素,例如提交工作队列元素(WQE)、敲门铃、轮询完成队列(CQE)。同时支持单边操作(Read、Write、Atomic)和双边操作(Send、Recv)。 - 高层 API :实现常见模式的更复杂辅助函数:
- 共享 QP(Shared QP):允许单个 QP 被不同的 CUDA 线程或 warp 安全地并发访问。
- 组合操作(Combined Operations) :用于串联多个操作的构建模块(例如
put_signal,将一个 RDMA Write 与一个原子 Fetch-and-Add 组合)。 - 内存一致性(MCST):针对 Hopper 之前架构 GPU 的特性,用于管理 RDMA Get 或接收侧的内存映射。
- ConnectX-8 可靠门铃(reliable doorbell)特性:无需更新 DBREC。
两种 API 都支持 CPU 代理模式------这是为无法从 GPU 直接敲门铃的系统准备的回退机制。samples/doca_gpunetio/verbs_high_level.cpp 文件提供了辅助函数(如 doca_gpu_verbs_create_qp_hl()),可简化这些 Verbs QP 的 CPU 侧设置。
GPUNetIO Verbs API 目前处于实验阶段。请报告使用中遇到的任何问题,以帮助改进代码质量和健壮性。
配置与使用
- 使用 DOCA Core 为网卡创建设备句柄。
- 使用 DOCA GPUNetIO 为 GPU 卡创建 GPU 设备句柄。
- 使用 DOCA RDMA Verbs:
- 创建发送和/或接收队列句柄。
- 将队列句柄的数据路径设置为 GPU。
- 导出代表这些队列的 GPU 句柄。
配置完成后,启动 CUDA kernel,将 Verbs 队列的 GPU 句柄作为输入参数传入。
示例用例
GPUNetIO Verbs 函数的示例请参阅 doca_gpunetio_verbs_* 系列示例。
要深入理解 Verbs 操作,请参阅 DOCA RDMA Verbs 文档。
DMA GDAKI 内存拷贝
要启用使用 DMA 引擎、由 GPU 触发的内存拷贝,应用需要 DOCA GPUNetIO 和 DOCA DMA 库。
初始 CPU 配置阶段
- 使用 DOCA Core 为网卡创建设备句柄。
- 使用 DOCA GPUNetIO 为 GPU 卡创建 GPU 设备句柄。
- 使用 DOCA DMA:
- 创建 DMA 队列句柄。
- 将队列句柄的数据路径设置在 GPU 上。
- 导出代表这些队列的 GPU 句柄。
GPU 上的数据路径阶段
完成配置阶段后,启动 CUDA kernel,将 DMA 队列的 GPU 句柄作为输入参数传入。这使得 DOCA GPUNetIO CUDA device 函数能够在 CUDA kernel 内部工作。
DMA 内存拷贝请使用 doca_gpunetio_dev_dma.cuh 中定义的函数。
示例用例
从 CUDA kernel 触发 DMA 内存拷贝的示例,请参阅 doca_gpunetio_dma_memcpy 示例。
要深入理解 DMA 操作,请参阅 DOCA DMA 文档。
3. GPUNetIO API 参考
原文:https://networking-docs.nvidia.com/doca/archive/3-5-0/gpunetio-api-reference(DOCA 3.5.0 归档版本,页面最后更新:2026 年 9 月 1 日)
本节详细介绍 CPU 和 GPU 上与 DOCA GPUNetIO 主 API 相关的具体结构与操作。GPUNetIO 头文件包括:
doca_gpunetio.h------用于创建 GPU 句柄、分配 GPU 内存等的 CPU 函数doca_gpunetio_dev_eth_rxq.cuh------管理 DOCA Ethernet 接收队列的开源 GPU 函数doca_gpunetio_dev_eth_txq.cuh------管理 DOCA Ethernet 发送队列的开源 GPU 函数doca_gpunetio_dev_verbs_*.cuh------管理 DOCA RDMA Verbs 对象的开源 GPU 函数doca_gpunetio_dev_buf.cuh------管理 DOCA 缓冲区数组的 GPU 函数doca_gpunetio_dev_sem.cuh------管理 DOCA GPUNetIO 信号量的 GPU 函数doca_gpunetio_dev_rdma.cuh------管理 DOCA RDMA 队列的 GPU 函数doca_gpunetio_dev_dma.cuh------管理 DOCA DMA 队列的 GPU 函数
本节列出 DOCA GPUNetIO 的主要函数。所有与 GPUNetIO 组合使用的 DOCA Core、Ethernet、Verbs、RDMA 和 DMA 对象,都有一个 GPU 导出函数,用于获取该对象的 GPU 句柄。
CPU 函数
本节列出只能在 CPU 上使用的 DOCA GPUNetIO 函数。
doca_gpu_mem_type
该枚举列出了可使用 GPUNetIO 分配的所有可能的内存类型。
c
enum doca_gpu_mem_type {
DOCA_GPU_MEM_TYPE_GPU = 0,
DOCA_GPU_MEM_TYPE_GPU_CPU = 1,
DOCA_GPU_MEM_TYPE_CPU_GPU = 2,
};
关于命名语法,DOCA_GPU_MEM_TYPE_ 前缀之后的字符串表示 <内存驻留位置>_<谁可以访问>:
DOCA_GPU_MEM_TYPE_GPU------内存驻留在 GPU 上,且仅 GPU 可访问DOCA_GPU_MEM_TYPE_GPU_CPU------内存驻留在 GPU 上,CPU 也可访问DOCA_GPU_MEM_TYPE_CPU_GPU------内存驻留在 CPU 上,GPU 也可访问
DOCA_GPU_MEM_TYPE_GPU_CPU 内存类型的典型用法是从 CPU 向 GPU 发送通知(例如,CUDA kernel 周期性地检查 CPU 设置的退出条件是否满足)。
doca_gpu_create
这是 GPUNetIO 应用必须调用的第一个函数,用于在 GPU 设备上创建句柄。该函数初始化一个指向 struct doca_gpu * 类型内存结构的指针。
c
doca_error_t doca_gpu_create(const char *gpu_bus_id, struct doca_gpu **gpu_dev);
gpu_bus_id------应用中要使用的 GPU 设备的<PCIe总线>:<设备>.<功能号>地址gpu_dev [out]------指向该 GPU 设备的 GPUNetIO 句柄
要获取 PCIe 地址,用户可以使用 lspci 或 nvidia-smi 命令。
doca_gpu_mem_alloc
该 CPU 函数分配不同种类的内存。
c
doca_error_t doca_gpu_mem_alloc(struct doca_gpu *gpu_dev, size_t size, size_t alignment, enum doca_gpu_mem_type mtype, void **memptr_gpu, void **memptr_cpu)
gpu_dev------GPUNetIO 设备句柄size------要分配的内存区域大小(字节)alignment------要使用的内存地址对齐。若为 0,则使用默认对齐mtype------要分配的内存类型memptr_gpu [out]------GPU 指针;如果内存分配在 GPU 上或对 GPU 可见,用于从 GPU 修改该内存memptr_cpu [out]------CPU 指针;如果内存分配在 CPU 上或对 CPU 可见,用于从 CPU 修改该内存。如果内存仅 GPU 可用,可以为 NULL
注意 :务必在正确的设备上使用正确的指针!如果应用试图从 CPU 使用
memptr_gpu地址访问内存,将导致段错误。
doca_gpu_semaphore_create
创建 DOCA GPUNetIO 信号量的新实例。信号量由一组条目(item)组成,每个条目默认包含:一个状态标志、数据包数量、以及 doca_gpu_buf_arr 中某个 doca_gpu_buf 的索引。
例如,GPUNetIO 信号量可用于这样的应用:一个 CUDA kernel 负责在与以太网接收队列对象 doca_gpu_eth_rxq 关联的 doca_gpu_buf_arr 数组中接收数据包(参见"doca_gpu_dev_eth_rxq_receive_*"一节),并将数据包信息分发给第二个 CUDA kernel 进行处理。
GPUNetIO 信号量的另一种用法是在不同实体之间交换数据,例如两个 CUDA kernel 之间,或一个 CUDA kernel 与一个 CPU 线程之间。这种场景的原因可能是 CUDA kernel 需要把数据包处理的结果提供给 CPU,由 CPU 汇总统计报告。因此,可以为信号量中的每个条目关联一个自定义的应用定义结构。这样,信号量就可以用作消息传递对象。
【图示:信号量结构------信号量(Semaphore)包含 Item 0、Item 1、Item 2......,每个条目含状态(Status)、数据包数量(Number of packets)、DOCA 缓冲区索引(DOCA buffer index);右侧为可选的应用定义区域(Optional application-defined),每个条目可对应若干自定义字段(Custom field)。】
通过信号量通信的各实体必须按照以下逻辑采用轮询/更新(poll/update)机制:
- 更新方(Update) :
- 填充信号量的下一个条目(数据包信息和/或自定义的应用定义信息)。
- 将状态标志设置为 READY。
- 轮询方(Poll) :
- 等待下一个条目的状态标志等于
READY。 - 读取并处理信息。
- 将状态标志设置为
DONE。
- 等待下一个条目的状态标志等于
c
doca_error_t doca_gpu_semaphore_create(struct doca_gpu *gpu_dev, struct doca_gpu_semaphore **semaphore)
gpu_dev------GPUNetIO 句柄semaphore [out]------与 GPU 设备关联的 GPUNetIO 信号量句柄
doca_gpu_semaphore_set_memory_type
该函数定义信号量分配的内存类型。
c
doca_error_t doca_gpu_semaphore_set_memory_type(struct doca_gpu_semaphore *semaphore, enum doca_gpu_mem_type mtype)
semaphore------GPUNetIO 信号量句柄mtype------用于分配自定义信息结构的内存类型- 如果应用仅在 CUDA kernel 之间共享数据包信息,建议使用
DOCA_GPU_MEM_GPU内存类型 - 如果应用需要从 CUDA kernel 向 CPU 共享信息(例如上报统计信息或流水线计算的输出),建议使用
DOCA_GPU_MEM_CPU_GPU内存类型
- 如果应用仅在 CUDA kernel 之间共享数据包信息,建议使用
doca_gpu_semaphore_set_items_num
该函数定义信号量中的条目数量。
c
doca_error_t doca_gpu_semaphore_set_items_num(struct doca_gpu_semaphore *semaphore, uint32_t num_items)
semaphore------GPUNetIO 信号量句柄num_items------要分配的条目数量
doca_gpu_semaphore_set_custom_info
该函数将一个应用特定的结构关联到信号量条目,如"doca_gpu_semaphore_create"一节所述。
c
doca_error_t doca_gpu_semaphore_set_custom_info(struct doca_gpu_semaphore *semaphore, uint32_t nbytes, enum doca_gpu_mem_type mtype)
semaphore------GPUNetIO 信号量句柄nbytes------要关联的自定义信息结构的大小mtype------用于分配自定义信息结构的内存类型- 如果应用仅在 CUDA kernel 之间共享数据包信息,建议使用
DOCA_GPU_MEM_GPU内存类型 - 如果应用需要从 CUDA kernel 向 CPU 共享信息(例如上报统计信息或流水线计算的输出),建议使用
DOCA_GPU_MEM_CPU_GPU内存类型
- 如果应用仅在 CUDA kernel 之间共享数据包信息,建议使用
doca_gpu_semaphore_get_status
从 CPU 查询信号量条目的状态。如果信号量以 DOCA_GPU_MEM_GPU 分配,此函数将导致段错误。
c
doca_error_t doca_gpu_semaphore_get_status(struct doca_gpu_semaphore *semaphore_cpu, uint32_t idx, enum doca_gpu_semaphore_status *status)
semaphore_cpu------GPUNetIO 信号量 CPU 句柄idx------信号量条目索引status [out]------输出的信号量状态
doca_gpu_semaphore_get_custom_info_addr
从 CPU 检索与信号量条目关联的自定义信息结构的地址。如果信号量或自定义信息以 DOCA_GPU_MEM_GPU 分配,此函数将导致段错误。
c
doca_error_t doca_gpu_semaphore_get_custom_info_addr(struct doca_gpu_semaphore *semaphore_cpu, uint32_t idx, void **custom_info)
semaphore_cpu------GPUNetIO 信号量 CPU 句柄idx------信号量条目索引custom_info [out]------输出的信号量自定义信息地址
doca_gpu_verbs_export_qp
doca_gpu_verbs_export_qp 函数从 DOCA RDMA Verbs QP 对象创建一个 GPUNetIO 句柄。它接受一个 DOCA RDMA Verbs QP 作为输入,并返回一个在 CPU 上分配的 DOCA GPUNetIO Verbs QP 对象(struct doca_gpu_verbs_qp)。要在 CUDA kernel 中使用该对象,应用必须使用 doca_gpu_verbs_get_qp_dev 函数提取 GPU 设备句柄(struct doca_gpu_dev_verbs_qp)。
c
doca_error_t doca_gpu_verbs_export_qp(struct doca_gpu *gpu_dev, struct doca_dev *dev, struct doca_verbs_qp *qp, enum doca_gpu_dev_verbs_nic_handler nic_handler, void *gpu_qp_umem_dev_ptr, struct doca_verbs_cq *cq_sq, struct doca_verbs_cq *cq_rq, struct doca_gpu_verbs_qp **qp_out);
gpu_dev:GPUNetIO 设备句柄dev:DOCA 设备句柄qp:DOCA RDMA Verbs QP 句柄nic_handler:NIC 句柄类型gpu_qp_umem_dev_ptr:指向 UMEM 的 GPU 内存指针cq_sq和cq_rq:与 QP 中发送队列和接收队列关联的 CQ
注意 :
cq_sq或cq_rq任一者可以为 NULL,但二者不能同时为 NULL。
qp_out:CPU 内存中的 DOCA GPUNetIO Verbs QP 句柄
doca_gpu_verbs_get_qp_dev
从 DOCA GPUNetIO Verbs QP 对象中提取 GPU 设备句柄(struct doca_gpu_dev_verbs_qp)。
c
doca_error_t doca_gpu_verbs_get_qp_dev(struct doca_gpu_verbs_qp *qp, struct doca_gpu_dev_verbs_qp **qp_gpu);
qp:DOCA GPUNetIO Verbs QP 句柄qp_gpu:GPU 内存中的 DOCA GPUNetIO Verbs QP GPU 设备句柄
doca_gpu_verbs_unexport_qp
取消导出先前导出的 DOCA GPUNetIO Verbs QP 对象(struct doca_gpu_verbs_qp)。
c
doca_error_t doca_gpu_verbs_unexport_qp(struct doca_gpu *gpu_dev, struct doca_gpu_verbs_qp *qp);
gpu_dev:GPUNetIO 设备句柄qp:CPU 内存中的 DOCA GPUNetIO Verbs QP 句柄
doca_gpu_verbs_bridge_export_qp
doca_gpu_verbs_bridge_export_qp 函数根据应用定义的参数创建 DOCA GPUNetIO Verbs QP 对象,充当 IBVerbs/mlx5 对象与 DOCA GPUNetIO 之间的桥梁。这使得应用可以使用 IBVerbs 和 mlx5 命令创建 QP、CQ、UAR 等所需对象,然后将相关信息传递给此 DOCA GPUNetIO 函数。
该函数返回一个在 CPU 上分配的 DOCA GPUNetIO Verbs QP 对象(struct doca_gpu_verbs_qp)。要在 CUDA kernel 中使用该对象,应用必须使用 doca_gpu_verbs_get_qp_dev 函数提取 GPU 设备句柄(struct doca_gpu_dev_verbs_qp)。
应用有责任确保所有传入的参数都已正确创建和设置。
c
doca_error_t doca_gpu_verbs_bridge_export_qp(struct doca_gpu *gpu_dev, uint32_t sq_qpn, void *sq_wqe_addr, uint16_t sq_wqe_num, uint32_t *sq_dbrec, uint64_t *sq_db, size_t uar_size, uint32_t sq_cqn, void *sq_cqe_addr, uint32_t sq_cqe_num, uint32_t *sq_cq_dbrec, uint32_t rq_qpn, void *rq_wqe_addr, uint16_t rq_wqe_num, uint32_t *rq_dbrec, uint32_t rcv_wqe_size, uint32_t rq_cqn, void *rq_cqe_addr, uint32_t rq_cqe_num, uint32_t *rq_cq_dbrec, enum doca_gpu_dev_verbs_nic_handler nic_handler, struct doca_gpu_verbs_qp **qp_out);
gpu_dev:GPUNetIO 设备句柄sq_qpn:发送 QP 队列编号sq_wqe_addr:发送 QP 的 WQE 缓冲区内存地址sq_wqe_num:发送 QP 的 WQE 数量sq_dbrec:发送 QP 门铃记录(Doorbell Record)地址sq_db:发送 QP 门铃(Doorbell)地址uar_size:UAR 大小sq_cqn:发送 CQ 编号sq_cqe_addr:发送 CQ 的 CQE 缓冲区内存地址sq_cqe_num:发送 CQ 的 CQE 数量qp_out:CPU 内存中的 DOCA GPUNetIO Verbs QP 句柄
DOCA PE
导出给 GPUNetIO 使用的 DOCA Ethernet Txq 上下文,可以在 CPU 侧通过 DOCA PE 进行跟踪,以检查发送数据包时是否出错,或在 GPU 上使用任何 doca_gpu_dev_eth_txq_*_enqueue_* 函数发送数据包后检索通知信息。示例可参见 DOCA GPU packet processing 应用中的 ICMP 流量处理。
导出给 GPUNetIO 使用的 DOCA Comch Producer 或 Consumer 上下文,仍必须附加到 CPU 上的 DOCA PE,以确保远程 producer 和 consumer 的连接与断开得到正确处理。
GPU 函数------以太网(Ethernet)
本节列出可在 GPU CUDA kernel 内用于以太网 GDAKI 网络操作的 DOCA GPUNetIO 函数。
doca_gpunetio_eth_def.h:包含常量、枚举和结构定义。doca_gpunetio_dev_eth_common.cuh:提供 Txq 和 Rxq 头文件共用的 CUDA 工具函数。doca_gpunetio_dev_eth_txq.cuh:提供以太网发送操作的 CUDA 函数,包括高层(带共享 QP 特性)和低层(提交 WQE、敲 DB、轮询 CQ 等)函数。doca_gpunetio_dev_eth_rxq.cuh:提供以太网接收操作的 CUDA 函数。
实验性 API:新的 GPUNetIO Ethernet API 处于实验阶段。请报告使用中遇到的任何问题,您的反馈对提升代码质量和健壮性至关重要。
私有函数 :使用 GPUNetIO Ethernet 头文件时,请避免调用以doca_priv_*开头的函数。这些是仅供公开 API 内部使用的内部函数。
调试宏 :doca_gpunetio_eth_def.h文件包含宏#define DOCA_GPUNETIO_ETH_ENABLE_DEBUG 0。该默认设置会禁用调试打印(包括 CQE 错误)。要启用错误消息,请将该宏设置为1。
执行作用域与共享队列
高层 GPUNetIO Ethernet API 的执行作用域(thread、warp 或 block)通过 doca_gpu_dev_eth_exec_scope 枚举作为模板参数指定。它告诉函数有多少线程参与该操作。
- 发送(Txq):高层发送 API 支持共享队列特性。这允许属于不同作用域的不同线程并发访问同一个 Txq 而不产生竞态条件。
- 接收(Rxq):高层接收 API 不支持共享队列特性。如果某个线程、warp 或 block 正在使用一个 Rxq,其他线程、warp 或 block 不能并行使用同一个 Rxq。
以下小节详细说明发送执行作用域。
Thread 作用域(DOCA_GPUNETIO_ETH_EXEC_SCOPE_THREAD)
- 每个线程作为独立实体运作。
- 每个线程提交一个或多个 WQE,并负责自己的 submit(敲门铃)。
- 性能:由于原子操作上的高度竞争,这是最慢的方式。
【图示:Thread 作用域------CUDA 线程 X、Y、Z 各自独立执行:保留 WQE(Reserve WQE,原子操作)→ 提交发送 WQE(Post Send WQE)→ 如有需要提交额外 WQE(Post extra-WQE if needed)→ 设置 WQE 就绪(Set WQE ready to be used)→ 携带 WQE 索引提交门铃(Submit DB with wqe index,原子操作)。】
Warp 作用域(DOCA_GPUNETIO_ETH_EXEC_SCOPE_WARP)
- warp 内所有线程都必须调用该函数。
- 每个线程将 WQE 提交到不同的位置,但只有第一个线程(
lane_idx 0)执行 submit 操作。 - 性能:将竞争降低到每 warp 一次。
【图示:Warp 作用域------Lane 0 线程为整个 warp 保留 WQE(Reserve WQEs for the WARP,原子操作);Lane 0 到 Lane 31 各线程分别提交发送 WQE;Lane 0 如有需要提交额外 WQE、设置所有 WQE 就绪(Set all WQEs ready to be used)、并携带 WQE 索引提交门铃(Submit DB,原子操作)。】
Block 作用域(DOCA_GPUNETIO_ETH_EXEC_SCOPE_BLOCK)
- block 内所有线程都必须调用该函数。
- 每个线程将 WQE 提交到不同的位置,但只有第一个线程(
threadIdx 0)执行 submit 操作。 - 性能:将竞争降低到每 block 一次。
【图示:Block 作用域------与 Warp 作用域类似,由 block 内第一个线程统一保留 WQE、收尾并提交门铃,其余线程仅各自提交发送 WQE。】
敲 DB 与 CPU 代理(Ring DB and CPU Proxy)
作为模板参数传入的 doca_gpu_dev_eth_nic_handler 枚举决定由谁负责敲响网卡门铃。
可用的模式有:
DOCA_GPUNETIO_ETH_NIC_HANDLER_AUTO:自动检测最佳的敲门铃方式。DOCA_GPUNETIO_ETH_NIC_HANDLER_CPU_PROXY:启用 CPU 代理模式。GPU 的 submit 函数将信息提供给 CPU,由 CPU 敲响门铃。DOCA_GPUNETIO_ETH_NIC_HANDLER_GPU_SM_DB:启用常规 GDAKI 模式,由 CUDA 线程直接敲响门铃。
CPU 代理模式 :启用 CPU_PROXY 模式后,CPU 线程必须循环调用
doca_eth_txq_gpu_cpu_proxy_progress,以检测 GPU 的信息并敲响门铃。GPUNetIO Ethernet Simple Send 示例演示了该特性。
内存一致性(MCST)算法
在接收操作期间,网卡通过 PCIe 将数据包数据写入应用映射的内存。对于 Hopper 之前架构的 GPU(参见 CUGPUDirectRDMAWritesOrdering),当 CUDA kernel 向 GPU 内存接收数据时,必须确保内存一致性(MCST)。
DOCA GPUNetIO 提供两种启用 MCST 算法的方式:
-
CPU 配置阶段 :创建 DOCA Ethernet Rxq 时,使用
doca_eth_rxq_gpu_enable_mcst_qp函数创建一个专用于 MCST 算法的内部队列。ccudaGetDeviceProperties(&prop, cuda_id); // 如果是 Hopper 之前的 GPU,__CUDA_ARCH__ < 900 if (prop.major < 9) doca_eth_rxq_gpu_enable_mcst_qp(rxq->eth_rxq_cpu); -
GPU 数据路径阶段 :将
doca_gpu_dev_eth_mcst_mode枚举作为模板参数传给 recv 函数。
接收------高层函数:doca_gpu_dev_eth_rxq_recv
该函数在 CUDA kernel 中接收以太网数据包,并提供按线程(per-thread)、按 warp(per-warp)或按 block(per-block)作用域的不同版本。
c++
template <enum doca_gpu_dev_eth_exec_scope exec_scope = DOCA_GPUNETIO_ETH_EXEC_SCOPE_THREAD,
enum doca_gpu_dev_eth_mcst_mode mcst_mode = DOCA_GPUNETIO_ETH_MCST_DISABLED,
enum doca_gpu_dev_eth_nic_handler nic_handler = DOCA_GPUNETIO_ETH_NIC_HANDLER_AUTO,
bool enable_attributes = false>
__device__ inline doca_error_t doca_gpu_dev_eth_rxq_recv(struct doca_gpu_eth_rxq *rxq,
uint32_t max_rx_pkts,
uint64_t timeout_ns,
volatile uint64_t *out_first_pkt_idx,
uint32_t *out_pkt_num,
struct doca_gpu_dev_eth_rxq_attr *out_attr);
参数:
rxq:以太网接收队列 GPU 句柄。max_rx_pkts:要接收的最大数据包数量。函数保证返回的数据包数量 ≤ 该值。timeout_ns:返回前等待数据包的纳秒数。out_first_pkt_idx [out]:收到的第一个数据包的索引。对于 block 或 warp 作用域,该变量必须位于共享内存或全局内存中,以便所有线程可见。out_pkt_num [out]:实际收到的数据包数量。对于 block 或 warp 作用域,该变量必须位于共享内存或全局内存中。out_attr [out]:如果enable_attributes为 true,该结构将被填充每个数据包的属性。应用必须确保该数组足够大,能够容纳max_rx_pkts个数据包的属性。
注意事项
- 如果
max_rx_pkts和timeout_ns都设置为 0,函数将会挂起(hang)。 - 对于 BLOCK 或 WARP 作用域,
max_rx_pkts的值至少应等于作用域内的线程数(例如blockDim.x或warpSize),因为每个线程都会尝试至少接收一次。 - 此函数不支持共享 QP 特性。对
rxq的访问必须由调用它的线程、warp 或 block 独占,任何其他作用域都不能并行操作同一个rxq。
数据包索引与循环缓冲区
在 CPU 上创建 Rxq 时,其 GPU 内存被划分为固定大小的步幅(stride,基于 max_packet_size)。doca_gpu_dev_eth_rxq_recv 函数保证这些步幅被新数据包顺序填充。
函数返回以下输出参数以标识接收到的数据包:
out_first_packet_idx:第一个被填充的步幅(数据包)的索引。out_pkt_num:接收到的数据包总数。
例如,如果 out_first_packet_idx 为 X 且 out_pkt_num 为 Y,则函数已接收 Y 个数据包,填充了从索引 X 到 (X + Y - 1) 的 GPU 内存缓冲区步幅。
输出参数 out_first_packet_idx 和 out_pkt_num 必须对作用域内的所有线程可见(例如,warp 和 block 作用域下通过 CUDA 共享内存)。
接收缓冲区被视为循环缓冲区。一旦最后一个步幅被填满,队列会回绕到第一个步幅。
应用有责任在数据包被队列回绕覆盖之前将其消费掉。这需要合理规划 Rxq GPU 缓冲区的大小,并扩展到多个接收队列。
接收:doca_gpu_dev_eth_rxq_get_pkt_addr
该工具函数检索特定步幅中特定数据包的内存地址。
c++
__device__ inline uint64_t doca_gpu_dev_eth_rxq_get_pkt_addr(struct doca_gpu_eth_rxq *rxq, uint64_t packet_idx);
参数:
rxq:以太网接收队列 GPU 句柄。packet_idx:数据包所在步幅的索引。
发送------高层函数:doca_gpu_dev_eth_txq_send
该函数从 CUDA kernel 发送以太网数据包,提供按线程、按 warp 或按 block 作用域的版本。它启用共享队列特性,允许并发访问同一个 Txq。
c++
template <enum doca_gpu_dev_eth_resource_sharing_mode resource_sharing_mode = DOCA_GPUNETIO_ETH_RESOURCE_SHARING_MODE_GPU,
enum doca_gpu_dev_eth_sync_scope sync_scope = DOCA_GPUNETIO_ETH_SYNC_SCOPE_GPU,
enum doca_gpu_dev_eth_nic_handler nic_handler = DOCA_GPUNETIO_ETH_NIC_HANDLER_AUTO,
enum doca_gpu_dev_eth_exec_scope exec_scope = DOCA_GPUNETIO_ETH_EXEC_SCOPE_THREAD>
__device__ static inline void doca_gpu_dev_eth_txq_send(struct doca_gpu_eth_txq *txq,
uint64_t addr,
uint32_t mkey,
size_t size,
enum doca_gpu_eth_send_flags flags,
doca_gpu_dev_eth_ticket_t *out_ticket)
参数:
txq:以太网发送队列 GPU 句柄。addr:要发送的数据包的内存地址。mkey:数据包的内存密钥(memory key)。size:数据包大小(字节)。flags:来自doca_gpu_eth_send_flags枚举的发送标志。out_ticket:返回数据包被提交到发送队列中的 WQE 索引(位置)。
发送------高层函数:doca_gpu_dev_eth_txq_wait_send
该函数将"精确发送调度"(Accurate Send Scheduling,按时间戳等待)特性与发送操作相结合。作用域内的第一个线程提交一个"按时间等待"(wait on time)屏障,然后作用域内所有线程提交各自的发送。此函数同样启用共享队列特性。
c++
template <enum doca_gpu_dev_eth_resource_sharing_mode resource_sharing_mode = DOCA_GPUNETIO_ETH_RESOURCE_SHARING_MODE_GPU,
enum doca_gpu_dev_eth_sync_scope sync_scope = DOCA_GPUNETIO_ETH_SYNC_SCOPE_GPU,
enum doca_gpu_dev_eth_nic_handler nic_handler = DOCA_GPUNETIO_ETH_NIC_HANDLER_AUTO,
enum doca_gpu_dev_eth_exec_scope exec_scope = DOCA_GPUNETIO_ETH_EXEC_SCOPE_THREAD>
__device__ static inline void doca_gpu_dev_eth_txq_wait_send(struct doca_gpu_eth_txq *txq,
const uint64_t wait_on_time_ts,
uint64_t addr,
uint32_t mkey,
size_t size,
enum doca_gpu_eth_send_flags flags,
doca_gpu_dev_eth_ticket_t *out_ticket);
参数:
txq:以太网发送队列 GPU 句柄。wait_on_time_ts:等待时间戳屏障。addr:数据包内存地址。mkey:数据包内存密钥。size:数据包大小(字节)。flags:来自doca_gpu_eth_send_flags枚举的发送标志。out_ticket:返回数据包被提交位置的 WQE 索引。
发送------低层函数:doca_gpu_dev_eth_txq_wqe_prepare_send
该低层函数提交一个发送 WQE。它会被高层函数调用,但应用也可以直接调用它。
c++
__device__ __inline__ static doca_error_t doca_gpu_dev_eth_txq_wqe_prepare_send(
const struct doca_gpu_eth_txq *txq,
struct doca_gpu_dev_eth_txq_wqe *wqe_ptr,
const uint16_t wqe_idx,
const uint64_t addr,
const uint32_t mkey,
const uint32_t nbytes,
const enum doca_gpu_eth_send_flags flags);
参数:
txq:以太网发送队列 GPU 句柄。wqe_ptr:WQE 的内存指针(可通过doca_gpu_dev_eth_txq_get_wqe_ptr获取)。wqe_idx:WQE 的索引。addr:数据包内存地址。mkey:数据包内存密钥。nbytes:数据包大小(字节)。flags:来自doca_gpu_eth_send_flags枚举的发送标志。
发送------低层函数:doca_gpu_dev_eth_txq_wqe_prepare_wait_time
该低层函数提交一个"按时间等待"(wait on time)WQE 以创建时间屏障。它会被 doca_gpu_dev_eth_txq_wait_send 调用,但也可以直接调用。
c++
__device__ __inline__ static doca_error_t doca_gpu_dev_eth_txq_wqe_prepare_wait_time(
const struct doca_gpu_eth_txq *txq,
struct doca_gpu_dev_eth_txq_wqe *wqe_ptr,
const uint16_t wqe_idx,
const uint64_t wait_on_time_ts,
const enum doca_gpu_eth_send_flags flags);
参数:
txq:以太网发送队列 GPU 句柄。wqe_ptr:WQE 的内存指针(可通过doca_gpu_dev_eth_txq_get_wqe_ptr获取)。wqe_idx:WQE 的索引。wait_on_time_ts:时间戳屏障。时间戳可以在 GPU 上计算(通过doca_gpu_dev_eth_txq_calculate_timestamp)或在 CPU 上计算(通过doca_eth_txq_calculate_timestamp)。flags:来自doca_gpu_eth_send_flags枚举的发送标志。
发送------低层函数:doca_gpu_dev_eth_txq_submit
该函数"敲响门铃",通知网卡执行自上次 submit 以来提交的所有 WQE。它支持共享队列特性:内部原子操作允许多个线程并行调用它,而门铃寄存器只会由拥有最大 WQE 索引的线程更新。它还允许通过模板参数在 GPU 和 CPU 代理(CPU Proxy)句柄之间进行选择。
c++
template <enum doca_gpu_dev_eth_resource_sharing_mode resource_sharing_mode = DOCA_GPUNETIO_ETH_RESOURCE_SHARING_MODE_GPU,
enum doca_gpu_dev_eth_sync_scope sync_scope = DOCA_GPUNETIO_ETH_SYNC_SCOPE_GPU,
enum doca_gpu_dev_eth_nic_handler nic_handler = DOCA_GPUNETIO_ETH_NIC_HANDLER_AUTO>
__device__ static inline void doca_gpu_dev_eth_txq_submit(struct doca_gpu_eth_txq *txq, uint64_t prod_index);
参数:
txq:以太网发送队列 GPU 句柄。prod_index:要写入网卡门铃寄存器的生产者索引(WQE 索引)。
发送------低层函数:...update_dbr、...ring_db 和 ...submit_proxy
在特殊情况下,这些函数可以替代 doca_gpu_dev_eth_txq_submit------例如当应用有明确固定的发送模式、不需要用于共享队列访问的原子操作时。典型的调用顺序是先 doca_gpu_dev_eth_txq_update_dbr(更新 DBREC),再 doca_gpu_dev_eth_txq_ring_db(更新门铃寄存器)。
c++
template <enum doca_gpu_dev_eth_sync_scope sync_scope = DOCA_GPUNETIO_ETH_SYNC_SCOPE_GPU>
__device__ static inline void doca_gpu_dev_eth_txq_update_dbr(struct doca_gpu_eth_txq *txq, uint32_t prod_index);
参数:
txq:以太网发送队列 GPU 句柄。prod_index:要写入网卡 DBREC 的 WQE 索引。
c++
template <enum doca_gpu_dev_eth_sync_scope sync_scope = DOCA_GPUNETIO_ETH_SYNC_SCOPE_GPU>
__device__ static inline void doca_gpu_dev_eth_txq_ring_db(struct doca_gpu_eth_txq *txq, uint64_t prod_index);
参数:
txq:以太网发送队列 GPU 句柄。prod_index:要写入网卡门铃寄存器的 WQE 索引。
doca_gpu_dev_eth_txq_ring_db 适用于 GPU 直接敲门铃。若要通过 CPU 代理敲门铃,则必须使用 doca_gpu_dev_eth_txq_submit_proxy:
c++
template <enum doca_gpu_dev_eth_resource_sharing_mode resource_sharing_mode = DOCA_GPUNETIO_ETH_RESOURCE_SHARING_MODE_GPU,
enum doca_gpu_dev_eth_sync_scope sync_scope = DOCA_GPUNETIO_ETH_SYNC_SCOPE_GPU>
__device__ static inline void doca_gpu_dev_eth_txq_submit_proxy(struct doca_gpu_eth_txq *txq, uint64_t prod_index);
参数:
txq:以太网发送队列 GPU 句柄。prod_index:要写入 CPU 代理共享内存的 WQE 索引。
发送------低层函数:轮询完成(Poll Completion)
这些函数允许等待发送操作完成。只有在提交 WQE 时使用了 DOCA_GPUNETIO_ETH_SEND_FLAG_NOTIFY 标志,才会生成完成事件(CQE)。
doca_gpu_dev_eth_txq_poll_completion 等待一定数量的 CQE。它以线程作用域运作,但支持共享队列特性(每个线程锁定 num_cqe 个槽位进行轮询)。
c++
template <enum doca_gpu_dev_eth_cq_poll_mode cqe_poll = DOCA_GPUNETIO_ETH_CQ_POLL_ALL,
enum doca_gpu_dev_eth_resource_sharing_mode resource_sharing_mode = DOCA_GPUNETIO_ETH_RESOURCE_SHARING_MODE_GPU,
enum doca_gpu_dev_eth_sync_scope sync_scope = DOCA_GPUNETIO_ETH_SYNC_SCOPE_GPU>
__device__ static inline doca_error_t doca_gpu_dev_eth_txq_poll_completion(struct doca_gpu_eth_txq *txq,
const uint32_t num_cqe,
enum doca_gpu_dev_eth_wait_flags wait_mode,
uint32_t *num_completed);
参数:
txq:以太网发送队列 GPU 句柄。num_cqe:要等待的 CQE 数量。wait_mode:以阻塞或非阻塞模式等待(标志来自doca_gpu_dev_eth_wait_flags)。num_completed:检测到的 CQE 数量。
当 num_cqe > 1 时,将 cqe_poll 模板设置为 DOCA_GPUNETIO_ETH_CQ_POLL_ALL 可以降低延迟,因为函数将只轮询最新的 CQE(num_cqe - 1),而不是顺序轮询每一个。
doca_gpu_dev_eth_txq_poll_completion_at 允许应用轮询特定位置的 CQE。它不支持共享队列特性。
c++
template <enum doca_gpu_dev_eth_resource_sharing_mode resource_sharing_mode = DOCA_GPUNETIO_ETH_RESOURCE_SHARING_MODE_GPU,
enum doca_gpu_dev_eth_sync_scope sync_scope = DOCA_GPUNETIO_ETH_SYNC_SCOPE_GPU>
__device__ static inline doca_error_t doca_gpu_dev_eth_txq_poll_completion_at(struct doca_gpu_eth_txq *txq,
const uint64_t cqe_idx,
enum doca_gpu_dev_eth_wait_flags wait_mode);
参数:
txq:以太网发送队列 GPU 句柄。cqe_idx:要轮询的 CQE 索引。wait_mode:以阻塞或非阻塞模式等待(标志来自doca_gpu_dev_eth_wait_flags)。
GPU 函数------Verbs
GPUNetIO Verbs 数据路径函数以 inline 实现的形式定义在名为 doca_gpunetio_dev_verbs_* 的头文件中。
头文件
doca_gpunetio_verbs_def.h:包含常量、枚举和结构定义。doca_gpunetio_dev_verbs_qp.cuh:提供与 QP 交互的 CUDA 函数,包括 WQE 创建和敲响网卡门铃。doca_gpunetio_dev_verbs_cq.cuh:提供与 CQ 交互的 CUDA 函数,包括 CQE 轮询和校验。doca_gpunetio_dev_verbs_onesided.cuh:在 CUDA 线程和 warp 级别启用带共享 QP 特性的单边操作(如put)。doca_gpunetio_dev_verbs_twosided.cuh:在 CUDA 线程和 warp 级别启用带共享 QP 特性的双边操作(如send/recv)。doca_gpunetio_dev_verbs_counter.cuh:通过带共享 QP 特性的 RDMA 原子操作,提供计数器式操作的 CUDA 函数。这要求启用 "Core Direct" 作为 QP 属性。
实验性 API:GPUNetIO Verbs API 目前处于实验阶段。请报告使用中遇到的任何问题,您的反馈对提升代码质量和健壮性至关重要。
私有函数 :使用 GPUNetIO Verbs 头文件时,请避免调用以doca_priv_*开头的函数。这些是仅供公开 API 内部使用的内部函数。
调试宏 :doca_gpunetio_verbs_def.h文件包含宏#define DOCA_GPUNETIO_VERBS_ENABLE_DEBUG 0。该默认设置会禁用调试打印(包括 CQE 错误)。要启用错误消息,请将该宏设置为1。
执行作用域与共享队列
高层 GPUNetIO Verbs API 可以通过将 doca_gpu_dev_verbs_exec_scope 枚举的值作为模板参数指定,以线程(thread)或 warp 作用域执行。它告诉函数有多少线程参与该操作。
高层 API 还支持共享队列特性,允许来自不同作用域的不同线程并发访问同一个队列而不产生竞态条件。
Thread 作用域(DOCA_GPUNETIO_VERBS_EXEC_SCOPE_THREAD)
- 每个线程作为独立实体运作,提交 WQE 并敲响门铃。
- 由于原子操作上的高度竞争,这是最慢的方式。
Warp 作用域(DOCA_GPUNETIO_VERBS_EXEC_SCOPE_WARP)
- warp 内所有线程都必须调用该函数。
- 每个线程将 WQE 提交到不同的位置,但只有第一个线程(
lane_idx 0)执行 submit(敲门铃)。 - 这将竞争降低到每 warp 一次。
敲 DB 与 CPU 代理(Ring DB and CPU Proxy)
作为模板参数传入的 doca_gpu_dev_verbs_nic_handler 枚举决定由谁负责敲响门铃。
可用的模式有:
DOCA_GPUNETIO_VERBS_NIC_HANDLER_AUTO:自动检测最佳的敲门铃方式。DOCA_GPUNETIO_VERBS_NIC_HANDLER_CPU_PROXY:启用 CPU 代理模式。GPU 的 submit 函数将信息提供给 CPU,由 CPU 敲响门铃。DOCA_GPUNETIO_VERBS_NIC_HANDLER_GPU_SM_DB:启用常规 GDAKI 模式,由 CUDA 线程直接敲响门铃。DOCA_GPUNETIO_VERBS_NIC_HANDLER_GPU_SM_BF:实验性的 BlueFlame 敲门铃模式(当前版本中可能失败)。
CPU 代理模式 :启用 CPU_PROXY 模式后,CPU 线程必须循环调用
doca_gpu_verbs_cpu_proxy_progress,以检测 GPU 的信息并敲响门铃。大多数 GPUNetIO Verbs 示例都可以通过命令行选项启用此模式。
内存一致性(MCST)算法
在 RDMA Read 或接收(Receive)操作期间,网卡通过 PCIe 将数据写入应用映射的内存。对于 Hopper 之前架构的 GPU(参见 CUGPUDirectRDMAWritesOrdering),当 CUDA kernel 向 GPU 内存接收数据时,必须确保内存一致性(MCST)。
DOCA GPUNetIO Verbs 应用可以通过设置 doca_gpu_dev_verbs_mcst_mode mcst_mode 模板参数,在 GPU 上启用 MCST 算法。该参数在 doca_gpu_dev_verbs_get、doca_gpu_dev_verbs_get_wait 和 doca_gpu_dev_verbs_recv_wait 等函数中可用。
gpunetio_verbs_twosided_bw 示例通过检查 CUDA 架构演示了这一点:
c
#if __CUDA_ARCH__ < 900
doca_gpu_dev_verbs_recv_wait<DOCA_GPUNETIO_VERBS_RESOURCE_SHARING_MODE_GPU, DOCA_GPUNETIO_VERBS_NIC_HANDLER_AUTO, DOCA_GPUNETIO_VERBS_MCST_ENABLED>(
qp, doca_gpu_dev_verbs_addr{.addr = (uint64_t)dump_flag, .key = dump_flag_mkey});
#else
doca_gpu_dev_verbs_recv_wait<DOCA_GPUNETIO_VERBS_RESOURCE_SHARING_MODE_GPU, DOCA_GPUNETIO_VERBS_NIC_HANDLER_AUTO, DOCA_GPUNETIO_VERBS_MCST_DISABLED>(
qp, doca_gpu_dev_verbs_addr{.addr = 0, .key = 0});
#endif
队列类型(Queue Type)
许多函数(例如 submit 门铃、轮询 CQ)可应用于 QP 的发送队列(Send Queue)或接收队列(Receive Queue)。使用来自 doca_gpu_dev_verbs_qp_type 枚举的模板参数指定要使用的队列:
DOCA_GPUNETIO_VERBS_QP_SQ:使用输入 QP 的发送队列。DOCA_GPUNETIO_VERBS_QP_RQ:使用输入 QP 的接收队列。
ConnectX-8 可靠门铃(Reliable Doorbell)
ConnectX-8 可靠门铃特性通过允许应用跳过 QP 门铃记录(QP DBREC)更新来优化发送数据路径。这减少了 GPU 为发送 WQE(Write、Read、Send 等)敲响门铃所需执行的操作数量。
实现步骤如下:
- 验证支持 :使用
doca_verbs_device_attr_get_is_send_dbr_mode_supported()检查设备是否支持该特性。 - 启用特性 :使用
doca_verbs_qp_init_attr_set_send_dbr_mode()设置新的 QP 属性。 - 处理 GPU 数据路径 :如果已启用,使用 GPUNetIO 句柄模式
DOCA_GPUNETIO_VERBS_NIC_HANDLER_GPU_SM_NO_DBR。
gpunetio_verbs_write_lat 和 gpunetio_verbs_put_counter_lat 示例演示了该特性。可通过命令行选项 -r 1 启用。
头文件:doca_gpunetio_dev_verbs_qp.cuh
该头文件提供用于 QP 操作的函数,分为三类:
doca_gpu_dev_verbs_wqe_prepare_*:在 QP 内存上准备并提交各种 RDMA WQE(Write、Read、Send、Recv、Atomic、Wait、Dump)。doca_gpu_dev_verbs_submit:为 SQ 和 RQ 更新网卡寄存器以提交门铃(常规、CPU 代理和 BlueFlame)。doca_gpu_dev_verbs_wait_*:等待 CQE 到达 SQ 或 RQ 的完成队列。
详细说明请参阅头文件。示例可参见 doca_gpunetio_verbs_write_bw 和 doca_gpunetio_verbs_write_lat 示例。
头文件:doca_gpunetio_dev_verbs_cq.cuh
该头文件提供用于操作完成队列(CQ)的函数。具体而言,doca_gpu_dev_verbs_poll_* 函数用于轮询 CQE,等待所连接 QP 中 WQE 的完成。
头文件:doca_gpunetio_dev_verbs_onesided.cuh
该头文件在线程和 warp 级别提供支持共享 QP 的单边 RDMA 操作函数。
doca_gpu_dev_verbs_put:提交一个 RDMA Write WQE 并 submit。在 WARP 作用域中,只有第一个线程执行 submit。doca_gpu_dev_verbs_put_signal:与put类似,但额外增加一个 RDMA 原子 Fetch-and-Add。在 THREAD 作用域中,每个线程提交一个 Write、一个 Atomic 并执行 submit;在 WARP 作用域中,每个线程提交一个 Write,但只有第一个线程提交 Atomic 并执行 submit。doca_gpu_dev_verbs_get:提交一个 RDMA Read WQE 并 submit。在 WARP 作用域中,只有第一个线程执行 submit。可选地提交一个 Dump WQE 以确保数据一致性(例如,确保 CQE 到达后 Read 数据已在内存中,参见CUGPUDirectRDMAWritesOrdering)。
详细说明请参阅头文件。示例可参见 doca_gpunetio_verbs_put_bw、doca_gpunetio_verbs_put_signal_bw 和 doca_gpunetio_verbs_put_signal_lat 示例。
头文件:doca_gpunetio_dev_verbs_twosided.cuh
该头文件在线程和 warp 级别提供支持共享 QP 的双边 RDMA 操作函数。
doca_gpu_dev_verbs_send:提交一个 RDMA Send WQE 并 submit。在 WARP 作用域中,只有第一个线程执行 submit。doca_gpu_dev_verbs_recv:提交一个 RDMA Receive WQE 并 submit。在 WARP 作用域中,只有第一个线程执行 submit。
双边通信的要求流程:
- 对端 A 提交一个或多个 RDMA Receive WQE。
- 对端 A 通知对端 B 它已准备好接收。
- 对端 B 等待该通知。
- 对端 B 提交一个或多个 RDMA Send WQE。
通知可以通过 RDMA Write 或 Atomic 发送。使用 RDMA Write 的示例可参见 doca_gpunetio_verbs_twosided_bw 示例。
头文件:doca_gpunetio_dev_verbs_counter.cuh
该头文件提供使用计数器特性的共享 QP 函数。它允许主 QP 上的 CQE 到达时触发伴随 QP(companion QP)上的 WQE。这要求通过 doca_verbs_qp_init_attr_set_core_direct_master() 启用 Core Direct。
doca_gpu_dev_verbs_put_counter:在主 QP 上提交一个 RDMA Write,并在伴随 QP 上提交一个 RDMA Atomic FetchAdd。该 Atomic 仅在 Write 的 CQE 到达后才执行,从而实现缓冲区重用。doca_gpu_dev_verbs_submit_multi_qps:在单次调用中同时提交主 QP 和伴随 QP。
详细说明请参阅头文件。示例可参见 doca_gpunetio_verbs_put_counter_bw 和 doca_gpunetio_verbs_put_counter_lat 示例。
GPU 函数------RDMA
本节列出只能在 GPU 上、在 CUDA kernel 内使用以执行 RDMA 操作的 DOCA GPUNetIO 函数。这些函数提供 strong 和 weak 两种模式。
c++
__device__ doca_error_t doca_gpu_dev_rdma_get_info(struct doca_gpu_dev_rdma *rdma, uint32_t connection_index, uint32_t *curr_position, uint32_t *mask_max_position)
rdma------RDMA 队列 GPU 句柄connection_index------使用 RDMA CM 时,必须指定连接索引。默认为 0curr_position------队列中的下一个可用位置mask_max_position------队列中总位置数的掩码
c++
__device__ doca_error_t doca_gpu_dev_rdma_recv_get_info(struct doca_gpu_dev_rdma_r *rdma_r, uint32_t *curr_position, uint32_t *mask_max_position)
rdma_r------RDMA 接收队列 GPU 句柄curr_position------队列中的下一个可用位置mask_max_position------队列中总位置数的掩码
doca_gpu_dev_rdma_write_*
要从 CUDA kernel 将数据 RDMA 写入远程内存位置,DOCA GPUNetIO 提供 strong 和 weak 两种模式将操作入队到 RDMA 队列。两种模式的作用域均为单个 CUDA 线程。
c++
__device__ doca_error_t doca_gpu_dev_rdma_write_strong(struct doca_gpu_dev_rdma *rdma,
uint32_t connection_index,
struct doca_gpu_buf *remote_buf, uint64_t remote_offset,
struct doca_gpu_buf *local_buf, uint64_t local_offset,
size_t length, uint32_t imm,
const enum doca_gpu_dev_rdma_write_flags flags)
rdma------RDMA 队列 GPU 句柄connection_index------使用 RDMA CM 时,必须指定连接索引。默认为 0remote_buf------来自 DOCA GPU 缓冲区数组的远程 DOCA 缓冲区,数据将写入其中remote_offset------写入远程缓冲区的偏移量(字节)local_buf------来自 DOCA GPU 缓冲区数组的本地 DOCA 缓冲区,从中取数据用于写入local_offset------从本地缓冲区取数据的偏移量(字节)length------要写入的字节数imm------立即数(uint32_t)flags------doca_gpu_dev_rdma_write_flags枚举中的标志之一
c++
__device__ doca_error_t doca_gpu_dev_rdma_write_weak(struct doca_gpu_dev_rdma *rdma,
uint32_t connection_index,
struct doca_gpu_buf *remote_buf, uint64_t remote_offset,
struct doca_gpu_buf *local_buf, uint64_t local_offset,
size_t length, uint32_t imm,
const enum doca_gpu_dev_rdma_write_flags flags,
uint32_t position);
- 前 9 个参数同上
position------RDMA 操作在队列中放置的位置。范围:0 -mask_max_position
doca_gpu_dev_rdma_read_*
要从 CUDA kernel 从远程内存位置 RDMA 读取数据,DOCA GPUNetIO 提供 strong 和 weak 两种模式将操作入队到 RDMA 队列。两种模式的作用域均为单个 CUDA 线程。
c++
__device__ doca_error_t doca_gpu_dev_rdma_read_strong(struct doca_gpu_dev_rdma *rdma,
uint32_t connection_index,
struct doca_gpu_buf *remote_buf, uint64_t remote_offset,
struct doca_gpu_buf *local_buf, uint64_t local_offset,
size_t length,
const uint32_t flags_bitmask)
rdma------RDMA 队列 GPU 句柄connection_index------使用 RDMA CM 时,必须指定连接索引。默认为 0remote_buf------来自 DOCA GPU 缓冲区数组的远程 DOCA 缓冲区,从中读取数据remote_offset------从远程缓冲区读取数据的偏移量(字节)local_buf------来自 DOCA GPU 缓冲区数组的本地 DOCA 缓冲区,用于存放读到的远程数据local_offset------数据存入本地缓冲区的偏移量(字节)length------要读取的字节数flags_bitmask------必须为 0;保留供将来使用
c++
__device__ doca_error_t doca_gpu_dev_rdma_read_weak(struct doca_gpu_dev_rdma *rdma,
uint32_t connection_index,
struct doca_gpu_buf *remote_buf, uint64_t remote_offset,
struct doca_gpu_buf *local_buf, uint64_t local_offset,
size_t length,
const uint32_t flags_bitmask,
uint32_t position);
- 前 8 个参数同上
position------RDMA 操作在队列中放置的位置。范围:0 -mask_max_position
doca_gpu_dev_rdma_send_*
要从 CUDA kernel 进行 RDMA 发送,DOCA GPUNetIO 提供 strong 和 weak 两种模式将操作入队到 RDMA 队列。两种模式的作用域均为单个 CUDA 线程。
c++
__device__ doca_error_t doca_gpu_dev_rdma_send_strong(struct doca_gpu_dev_rdma *rdma,
uint32_t connection_index,
struct doca_gpu_buf *local_buf, uint64_t local_offset,
size_t length, uint32_t imm,
const enum doca_gpu_dev_rdma_write_flags flags)
rdma------RDMA 队列 GPU 句柄connection_index------使用 RDMA CM 时,必须指定连接索引。默认为 0local_buf------来自 DOCA GPU 缓冲区数组的本地 DOCA 缓冲区,从中取数据用于发送local_offset------从本地缓冲区取数据的偏移量(字节)length------要发送的字节数imm------立即数(uint32_t)flags------doca_gpu_dev_rdma_write_flags枚举中的标志之一
c++
__device__ doca_error_t doca_gpu_dev_rdma_send_weak(struct doca_gpu_dev_rdma *rdma,
uint32_t connection_index,
struct doca_gpu_buf *local_buf, uint64_t local_offset,
size_t length, uint32_t imm,
const enum doca_gpu_dev_rdma_write_flags flags,
uint32_t position);
- 前 7 个参数同上
position------RDMA 操作在队列中放置的位置。范围:0 -mask_max_position
doca_gpu_dev_rdma_commit_*
当所有 RDMA write、send 或 read 请求都已入队到 RDMA 队列后,必须到达一个同步点以合并并执行这些请求。同一时刻只能有 1 个 CUDA 线程调用此函数。
c++
__device__ doca_error_t doca_gpu_dev_rdma_commit_strong(struct doca_gpu_dev_rdma *rdma, uint32_t connection_index)
rdma------RDMA 队列 GPU 句柄connection_index------使用 RDMA CM 时,必须指定连接索引。默认为 0
c++
__device__ doca_error_t doca_gpu_dev_rdma_commit_weak(struct doca_gpu_dev_rdma *rdma, uint32_t connection_index, uint32_t num_ops)
rdma------RDMA 队列 GPU 句柄connection_index------使用 RDMA CM 时,必须指定连接索引。默认为 0num_ops------自上次 commit 以来入队的 RDMA 请求数量
doca_gpu_dev_rdma_wait_all
commit 之后,RDMA 请求由网卡在应用继续执行其他操作时异步执行。如果应用需要验证网卡已完成所有 RDMA 操作,可以使用这个"wait all"函数等待之前提交的所有操作。同一时刻只能有 1 个 CUDA 线程调用此函数。
c++
__device__ doca_error_t doca_gpu_dev_rdma_wait_all(struct doca_gpu_dev_rdma *rdma, uint32_t *num_commits)
rdma------RDMA 队列 GPU 句柄num_commits------输出参数;已完成的 commit 操作数量
此函数是可选的,可用于在应用继续推进之前,确保所有 RDMA Send/Write/Read 操作确实已被执行。
doca_gpu_dev_rdma_recv_*
要接收来自 RDMA send、带立即数 send 或带立即数 write 的数据,目的端应提交一个接收操作。DOCA GPUNetIO 的 RDMA 接收操作必须通过 doca_gpu_dev_rdma_r 句柄完成。该句柄可通过 doca_gpu_dev_rdma_get_recv 函数获得。
所有接收操作都必须使用此对象。
c++
__device__ doca_error_t doca_gpu_dev_rdma_get_recv(struct doca_gpu_dev_rdma *rdma, struct doca_gpu_dev_rdma_r **rdma_r)
rdma------RDMA 队列 GPU 句柄rdma_r------RDMA 接收队列 GPU 句柄
对于接收侧,DOCA GPUNetIO 同样提供 strong 和 weak 两种模式将操作入队到 RDMA 队列。两种模式的作用域均为单个 CUDA 线程。
c++
__device__ doca_error_t doca_gpu_dev_rdma_recv_strong(struct doca_gpu_dev_rdma_r *rdma_r,
struct doca_gpu_buf *recv_buf,
size_t recv_length,
uint64_t recv_offset,
const uint32_t flags_bitmask)
rdma_r------RDMA 接收队列 GPU 句柄recv_buf------来自 DOCA GPU 缓冲区数组的本地 DOCA 缓冲区,用于接收数据recv_length------要接收的字节数recv_offset------数据存入本地缓冲区的偏移量(字节)flags_bitmask------必须为 0;保留供将来使用
c++
__device__ doca_error_t doca_gpu_dev_rdma_recv_weak(struct doca_gpu_dev_rdma_r *rdma_r,
struct doca_gpu_buf *recv_buf,
size_t recv_length,
uint64_t recv_offset,
const uint32_t flags_bitmask,
uint32_t position);
- 前 5 个参数同上
position------RDMA 操作在队列中放置的位置。范围:0 -mask_max_position
doca_gpu_dev_rdma_recv_commit_*
提交若干个 RDMA 接收操作后,必须调用 commit 函数来激活队列中的接收。同一时刻只能有 1 个 CUDA 线程调用此函数。
c++
__device__ doca_error_t doca_gpu_dev_rdma_recv_commit_strong(struct doca_gpu_dev_rdma_r *rdma_r)
rdma_r------RDMA 接收队列 GPU 句柄
c++
__device__ doca_error_t doca_gpu_dev_rdma_recv_commit_weak(struct doca_gpu_dev_rdma_r *rdma_r, uint32_t num_ops)
rdma_r------RDMA 接收队列 GPU 句柄num_ops------自上次 commit 以来入队的 RDMA 接收请求数量
doca_gpu_dev_rdma_recv_wait_all
该函数等待之前提交的所有 RDMA 接收操作完成。同一时刻只能有 1 个 CUDA 线程调用此函数。它支持阻塞或非阻塞模式。
c++
enum doca_gpu_dev_rdma_recv_wait_flags {
DOCA_GPU_RDMA_RECV_WAIT_FLAG_NB = 0, /**< 非阻塞模式:wait receive 函数 doca_gpu_dev_rdma_recv_wait
* 检查接收操作是否已发生(数据已收到),
* 然后退出函数。如果没有收到任何数据,
* 函数不会阻塞执行。
*/
DOCA_GPU_RDMA_RECV_WAIT_FLAG_B = 1, /**< 阻塞模式:wait receive 函数 doca_gpu_dev_rdma_recv_wait
* 阻塞执行,等待接收操作被执行。
*/
};
函数:
c++
__device__ doca_error_t doca_gpu_dev_rdma_recv_wait_all(struct doca_gpu_dev_rdma_r *rdma_r, const enum doca_gpu_dev_rdma_recv_wait_flags flags, uint32_t *num_ops, uint32_t *imm_val)
rdma_r------RDMA 接收队列 GPU 句柄flags------接收标志num_ops------输出参数。函数报告已完成的操作数量imm_val------输出参数。应用提供的缓冲区,函数可在其中存放收到的立即数(如果有);若未收到立即数则为 0xFFFFFFFF。若为nullptr,函数忽略此参数
GPU 函数------DMA
本节列出只能在 GPU 上、在 CUDA kernel 内使用以执行 DMA 操作的 DOCA GPUNetIO 函数。
doca_gpu_dev_dma_memcpy
该函数允许 CUDA kernel 通过 DMA GPU 引擎触发 DMA 内存拷贝操作。这里没有 strong/weak 模式之分,DMA 默认采用 strong 行为。
c++
__device__ doca_error_t doca_gpu_dev_dma_memcpy(struct doca_gpu_dma *dma, struct doca_gpu_buf *src_buf, uint64_t src_offset, struct doca_gpu_buf *dst_buf, uint64_t dst_offset, size_t length);
dma------DMA 队列 GPU 句柄src_buf------memcpy 源缓冲区src_offset------从源缓冲区的此偏移量开始取数据dst_buf------memcpy 目的缓冲区dst_offset------从目的缓冲区的此偏移量开始拷贝数据length------要拷贝的字节数
doca_gpu_dev_dma_commit
提交若干个 DMA 内存拷贝后,必须调用 commit 函数来执行 DMA 队列中入队的操作。同一时刻只能有 1 个 CUDA 线程调用此函数。
c++
__device__ doca_error_t doca_gpu_dev_dma_commit(struct doca_gpu_dma *dma);
dma------DMA 队列 GPU 句柄
GPU 函数------Comch
本节列出可在 CUDA kernel 内用于执行 DOCA Comch Producer 或 Consumer 操作的 DOCA GPUNetIO 函数。有关 Producer/Consumer 模型的更多信息,请参阅 DOCA Comch 文档。
线程限制:每个 Producer 或 Consumer 对象只能由一个 CUDA 线程调用这些函数。
Consumer:doca_dev_gpu_comch_consumer_post_recv
该函数允许 DOCA Comch Consumer 提交一个缓冲区,以接收来自远程 producer 的消息。
c++
__device__ doca_error_t doca_dev_gpu_comch_consumer_post_recv(
struct doca_comch_gpu_consumer *consumer,
uint32_t mkey,
uintptr_t addr,
uint32_t recv_len
);
参数:
consumer:Consumer GPU 句柄。mkey:与要提交的缓冲区关联的 mkey。addr:用于接收消息的缓冲区地址。recv_len:可写入缓冲区的最大消息大小。
Consumer:doca_dev_gpu_comch_consumer_recv_wait
该函数允许 DOCA Comch Consumer 等待接收来自远程 producer 的消息。
c++
__device__ doca_error_t doca_dev_gpu_comch_consumer_recv_wait(
struct doca_comch_gpu_consumer *consumer,
uint32_t *mkey,
uintptr_t *addr,
uint32_t *recv_len,
uint8_t **imm_data,
uint32_t *imm_data_len,
const enum doca_gpu_dev_comch_wait_flags flags
);
参数:
consumer:Consumer GPU 句柄。mkey [out]:消息接收到的缓冲区的 mkey。addr [out]:消息接收到的缓冲区地址。recv_len [out]:已接收消息的长度。imm_data:用于存放随消息附带的任何立即数据的缓冲区指针。imm_data_len [in/out]:- 输入:可接收的立即数据的最大大小。
- 输出:实际接收到的立即数据大小。
flags:doca_gpu_dev_comch_wait_flags值(例如阻塞或非阻塞)。
Producer:doca_dev_gpu_comch_producer_send
该函数允许 DOCA Comch Producer 向远程 consumer 发送消息。
c++
__device__ doca_error_t doca_dev_gpu_comch_producer_send(
struct doca_comch_gpu_producer *producer,
uint32_t mkey,
uintptr_t addr,
uint32_t send_len,
uint8_t *imm_data,
uint32_t imm_data_len,
uint32_t consumer_id,
uint64_t user_msg_id
);
参数:
producer:Producer GPU 句柄。mkey:与要发送的缓冲区关联的 mkey。addr:要发送的缓冲区地址。send_len:要从缓冲区发送的字节数。imm_data:随消息附带的立即数据。imm_data_len:要附带的立即数据大小。consumer_id:消息要发送到的远程 consumer 的 ID。user_msg_id:用户定义的 ID;当该消息成功发送后,将由doca_dev_gpu_comch_producer_poll返回。
Producer:doca_dev_gpu_comch_producer_poll
该函数允许 DOCA Comch Producer 轮询发送操作的完成情况。
c++
__device__ doca_error_t doca_dev_gpu_comch_producer_poll(
struct doca_comch_gpu_producer *producer,
uint64_t *user_msg_id,
const enum doca_gpu_dev_comch_wait_flags flags
);
参数:
producer:Producer GPU 句柄。user_msg_id [out]:在相应的doca_dev_gpu_comch_producer_send函数中设置的user_msg_id。flags:doca_gpu_dev_comch_wait_flags值(例如阻塞或非阻塞)。
doca_gpu_dev_comch_wait_flags
该枚举用作 doca_dev_gpu_comch_consumer_recv_wait 和 doca_dev_gpu_comch_producer_poll 两个函数的 flags 参数。它决定在没有任何操作完成时函数的行为方式。
c++
enum doca_gpu_dev_comch_wait_flags {
DOCA_GPU_COMCH_WAIT_FLAG_NB = 0, /**< 非阻塞模式:wait 函数检查是否有任何操作
* 已完成(数据已发送/接收),然后退出函数。
* 如果没有发送/接收到任何内容,函数不会阻塞执行。
*/
DOCA_GPU_COMCH_WAIT_FLAG_B = 1, /**< 阻塞模式:wait 函数阻塞执行,
* 等待发送/接收操作完成。
*/
};
4. GPUNetIO 示例指南
原文:https://networking-docs.nvidia.com/doca/archive/3-5-0/gpunetio-sample-guide(DOCA 3.5.0 归档版本,页面最后更新:2026 年 9 月 1 日)
本节包含若干示例,展示如何启用基本的 GPUNetIO 特性。请确保正确设置以下环境变量:
bash
export PATH=${PATH}:/usr/local/cuda/bin
export CPATH="$(echo /usr/local/cuda/targets/{x86_64,sbsa}-linux/include | sed 's/ /:/'):${CPATH}"
export PKG_CONFIG_PATH=${PKG_CONFIG_PATH}:/usr/lib/pkgconfig:/opt/mellanox/grpc/lib/{x86_64,aarch64}-linux-gnu/pkgconfig:/opt/mellanox/dpdk/lib/{x86_64,aarch64}-linux-gnu/pkgconfig:/opt/mellanox/doca/lib/{x86_64,aarch64}-linux-gnu/pkgconfig
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/cuda/lib64:/opt/mellanox/gdrcopy/src:/opt/mellanox/dpdk/lib/{x86_64,aarch64}-linux-gnu:/opt/mellanox/doca/lib/{x86_64,aarch64}-linux-gnu
本节描述的所有 DOCA 示例均遵循 BSD-3 软件许可协议。
在构建示例之前,请确保您的 GPU 架构已包含在 meson.build 文件中(例如,Ampere 对应 sm_80,L40 对应 sm_89,H100 对应 sm_90 等)。
以太网按时间发送(Ethernet Send Wait Time)
本示例展示如何在 GPUNetIO 应用中启用精确发送调度(Accurate Send Scheduling,即 wait-on-time)特性。精确发送调度是指 NVIDIA 网卡能够按照应用提供的时间戳,在将来的指定时刻发送数据包。
该特性在 ConnectX-7 及更高版本上受支持。
本示例演示了如何通过以 BLOCK 执行作用域调用高层函数 doca_gpu_dev_eth_txq_wait_send,使用精确发送调度从 GPU 发送数据包。
这篇 NVIDIA 博客文章提供了该特性在 5G 网络中的应用示例。
同步时钟
在启动示例之前,必须正确地将 CPU 时钟与网卡时钟同步。这样,系统时钟提供的时间戳才能与网卡中的时间保持同步。
为此,至少需要使用 phc2sys 服务。在 Ubuntu 系统上安装:
sudo apt install linuxptp
要正确启动 phc2sys 服务,必须在 /lib/systemd/system/phc2sys.service 中创建一个配置文件。假设网络接口为 ens6f0:
ini
[Unit]
Description=Synchronize system clock or PTP hardware clock (PHC)
Documentation=man:phc2sys
[Service]
Restart=always
RestartSec=5s
Type=simple
ExecStart=/bin/sh -c "taskset -c 15 /usr/sbin/phc2sys -s /dev/ptp$(ethtool -T ens6f0 | grep PTP | awk '{print $4}') -c CLOCK_REALTIME -n 24 -O 0 -R 256 -u 256"
[Install]
WantedBy=multi-user.target
现在可以启动 phc2sys 服务:
sudo systemctl stop systemd-timesyncd
sudo systemctl disable systemd-timesyncd
sudo systemctl daemon-reload
sudo systemctl start phc2sys.service
检查 phc2sys 的状态:
$ sudo systemctl status phc2sys.service
输出:
● phc2sys.service - Synchronize system clock or PTP hardware clock (PHC)
Loaded: loaded (/lib/systemd/system/phc2sys.service; disabled; vendor preset: enabled)
Active: active (running) since Mon 2023-04-03 10:59:13 UTC; 2 days ago
Docs: man:phc2sys
Main PID: 337824 (sh)
Tasks: 2 (limit: 303788)
Memory: 560.0K
CPU: 52min 8.199s
CGroup: /system.slice/phc2sys.service
├─337824 /bin/sh -c "taskset -c 15 /usr/sbin/phc2sys -s /dev/ptp\$(ethtool -T enp23s0f1np1 | grep PTP | awk '{print \$4}') -c CLOCK_REALTIME -n 24 -O 0 -R >
└─337829 /usr/sbin/phc2sys -s /dev/ptp3 -c CLOCK_REALTIME -n 24 -O 0 -R 256 -u 256
Apr 05 16:35:52 doca-vr-045 phc2sys[337829]: [457395.040] CLOCK_REALTIME rms 8 max 18 freq +110532 +/- 27 delay 770 +/- 3
Apr 05 16:35:53 doca-vr-045 phc2sys[337829]: [457396.071] CLOCK_REALTIME rms 8 max 20 freq +110513 +/- 30 delay 769 +/- 3
...
此时,系统时钟和网卡时钟已同步,CPU 提供的时间戳可以被网卡正确解读。
注意 :此时获得的时间戳可能并不反映真实的日期和时间。要做到这一点,必须在系统上配合外部 grand master 正确设置
ptp4l服务。这超出了本示例的范围。
运行示例
要构建指定示例,运行以下命令。如果您是从 GitHub 下载的示例,请更新第一行中的路径以反映示例文件的位置:
bash
# 确保 DOCA 在 pkgconfig 环境变量中
cd /opt/mellanox/doca/samples/doca_gpunetio/gpunetio_send_wait_time
meson build
ninja -C build
该示例以定时方式向一个伪以太网地址 10:11:12:13:14:15 发送 8 组、每组 32 个 1kB 的原始以太网数据包。通过命令行选项 -t 设定网卡每隔 t 纳秒发送一次。
以下示例将 GPU PCIe 地址为 ca:00.0、网卡 PCIe 地址为 17:00.0 的系统编程为每 5 毫秒发送 32 个数据包:
bash
# 确保 DOCA 和 DPDK 在 LD_LIBRARY_PATH 环境变量中
$ sudo ./build/doca_gpunetio_send_wait_time -n 17:00.0 -g ca:00.0 -t 5000000
[09:22:54:165778][1316878][DOCA][INF][gpunetio_send_wait_time_main.c:195][main] Starting the sample
[09:22:54:438260][1316878][DOCA][INF][gpunetio_send_wait_time_main.c:224][main] Sample configuration:
GPU ca:00.0
NIC 17:00.0
Timeout 5000000ns
EAL: Detected CPU lcores: 128
...
EAL: Probe PCI driver: mlx5_pci (15b3:a2d6) device: 0000:17:00.0 (socket 0)
[09:22:54:819996][1316878][DOCA][INF][gpunetio_send_wait_time_sample.c:607][gpunetio_send_wait_time] Wait on time supported mode: DPDK
EAL: Probe PCI driver: gpu_cuda (10de:20b5) device: 0000:ca:00.0 (socket 1)
[09:22:54:830212][1316878][DOCA][INF][gpunetio_send_wait_time_sample.c:252][create_tx_buf] Mapping send queue buffer (0x0x7f48e32a0000 size 262144B) with legacy nvidia-peermem mode
[09:22:54:832462][1316878][DOCA][INF][gpunetio_send_wait_time_sample.c:657][gpunetio_send_wait_time] Launching CUDA kernel to send packets
[09:22:54:842945][1316878][DOCA][INF][gpunetio_send_wait_time_sample.c:664][gpunetio_send_wait_time] Waiting 10 sec for 256 packets to be sent
[09:23:04:883309][1316878][DOCA][INF][gpunetio_send_wait_time_sample.c:684][gpunetio_send_wait_time] Sample finished successfully
[09:23:04:883339][1316878][DOCA][INF][gpunetio_send_wait_time_main.c:239][main] Sample finished successfully
要验证数据包确实在正确的时间点发出,可以在对端使用抓包工具(如 tcpdump):
$ sudo tcpdump -i enp23s0f1np1 -A -s 64
17:12:23.480318 IP5 (invalid)
Sent from DOCA GPUNetIO...........................
....
17:12:23.480368 IP5 (invalid)
Sent from DOCA GPUNetIO...........................
# 第一组 32 个数据包结束,时间跳动 +5ms
17:12:23.485321 IP5 (invalid)
Sent from DOCA GPUNetIO...........................
...
17:12:23.485369 IP5 (invalid)
Sent from DOCA GPUNetIO...........................
# 第二组 32 个数据包结束,时间跳动 +5ms
17:12:23.490278 IP5 (invalid)
Sent from DOCA GPUNetIO...........................
...
输出应显示每 32 个数据包约有 5 毫秒的时间跳动。
注意 :
tcpdump在抓包和报告接收时间戳时可能增加延迟,因此报告的 32 个数据包组之间的时间差可能小于预期,尤其是在 500 微秒(-t 500000)这样的小间隔时间下。
以太网简单接收(Ethernet Simple Receive)
本示例应用演示了构建 DOCA GPUNetIO 接收应用的基本步骤。它为 UDP 数据包创建一个队列,并使用单个 CUDA kernel 在 GPU 上接收这些数据包。
该示例使用高层函数 doca_gpu_dev_eth_rxq_recv,其执行作用域(thread、warp 或 block)可以在运行时通过 -e 命令行参数设置。
注意 :在 CUDA kernel 中调用
printf对发布版软件来说不是好的做法,因为它会拖慢 kernel 的整体执行,只应用于调试。要在本示例中启用数据包信息打印,必须将DOCA_GPUNETIO_SIMPLE_RECEIVE_DEBUG宏设置为1。
构建说明
bash
# 确保 DOCA 在 pkgconfig 环境变量中
cd /opt/mellanox/doca/samples/doca_gpunetio/gpunetio_simple_receive
meson build
ninja -C build
执行与测试
本指南假设采用双机设置:
- 接收方机器 :运行
doca_gpunetio_simple_receive应用。 - 数据包生成器机器 :使用
nping之类的应用发送 UDP 数据包。
数据包生成器机器示例
在数据包生成器机器上,使用 nping 向接收方的 IP 地址(本例中假设为 192.168.1.1)发送 10 个 UDP 数据包。
命令:
$ nping --udp -c 10 -p 2090 192.168.1.1 --data-length 1024 --delay 500ms
输出:
Starting Nping 0.7.80 ( https://nmap.org/nping ) at 2023-11-20 11:05 UTC
SENT (0.0018s) UDP packet with 1024 bytes to 192.168.1.1:2090
SENT (0.5018s) UDP packet with 1024 bytes to 192.168.1.1:2090
SENT (1.0025s) UDP packet with 1024 bytes to 192.168.1.1:2090
SENT (1.5025s) UDP packet with 1024 bytes to 192.168.1.1:2090
SENT (2.0032s) UDP packet with 1024 bytes to 192.168.1.1:2090
SENT (2.5033s) UDP packet with 1024 bytes to 192.168.1.1:2090
SENT (3.0040s) UDP packet with 1024 bytes to 192.168.1.1:2090
SENT (3.5040s) UDP packet with 1024 bytes to 192.168.1.1:2090
SENT (4.0047s) UDP packet with 1024 bytes to 192.168.1.1:2090
SENT (4.5048s) UDP packet with 1024 bytes to 192.168.1.1:2090
Max rtt: N/A | Min rtt: N/A | Avg rtt: N/A
UDP packets sent: 10 | Rcvd: 0 | Lost: 10 (100.00%)
Nping done: 1 IP address pinged in 5.50 seconds
接收方机器示例
在接收方机器上,使用网卡和 GPU 对应的 PCI 地址运行示例。本例使用 BLOCK 作用域(-e 2)。
命令:
bash
# 确保 DOCA 在 LD_LIBRARY_PATH 环境变量中
$ sudo ./doca_gpunetio_simple_receive -n 9f:00.0 -g 8a:00.0 -e 2
输出:
[2025-10-27 00:52:32:387590][3382972416][DOCA][INF][doca_log.cpp:633] DOCA version 3.2.0111
[2025-10-27 00:52:32:387627][3382972416][DOCA][INF][gpunetio_simple_receive_main.c:198][main] Starting the sample
[2025-10-27 00:52:32:681807][3382972416][DOCA][INF][gpunetio_simple_receive_main.c:240][main] Sample configuration:
GPU 8a:00.0
NIC 9f:00.0
Shared QP exec scope Block
[2025-10-27 00:52:32:687128][3382972416][DOCA][WRN][engine_model.c:90] adapting queue depth to 128.
[2025-10-27 00:52:32:753758][3382972416][DOCA][WRN][hws_port.c:864] ARGUMENT_256B resource doens't exist, skip creating NAT64 actions
[2025-10-27 00:52:32:755527][3382972416][DOCA][INF][gpunetio_simple_receive_sample.c:479][create_rxq] Creating Sample Eth Rxq
[2025-10-27 00:52:32:755713][3382972416][DOCA][INF][gpunetio_simple_receive_sample.c:544][create_rxq] Mapping receive queue buffer (0x0x7eef8e000000 size 33554432B dmabuf fd 43) with dmabuf mode
[2025-10-27 00:52:32.795823][3382972416][DOCA][INF][gpunetio_simple_receive_sample.c:717][gpunetio_simple_receive] Launching CUDA kernel to receive packets
[2025-10-27 00:52:32:799403][3382972416][DOCA][INF][gpunetio_simple_receive_sample.c:721][gpunetio_simple_receive] Waiting for termination
# 输入 Ctrl+C 终止示例
[2025-10-27 00:53:35:034046][3382972416][DOCA][INF][gpunetio_simple_receive_sample.c:61][signal_handler] Signal 2 received, preparing to exit!
Exiting from simple receive sample. Total number of received packets: 10
[2025-10-27 00:53:35:034322][3382972416][DOCA][INF][gpunetio_simple_receive_sample.c:395][destroy_rxq] Destroying Rxq
[2025-10-27 00:53:35:061065][3382972416][DOCA][WRN][hws_group_pool.c:85] group_pool has 1 used groups
[2025-10-27 00:53:35:845568][3382972416][DOCA][INF][gpunetio_simple_receive_sample.c:738][gpunetio_simple_receive] Sample finished successfully
[2025-10-27 00:53:35:845583][3382972416][DOCA][INF][gpunetio_simple_receive_main.c:259][main] Sample finished successfully
以太网简单发送(Ethernet Simple Send)
本示例实现了一个简单的 GPU 以太网数据包生成器,持续发送原始以太网数据包流。它演示了发送数据包的两种不同实现"风格":一种使用低层 API,一种使用高层 API。
行为由 -q 命令行选项控制:
-q 0(低层 API):禁用共享队列特性,使用低层发送函数执行。-q 1(高层 API) :启用共享队列特性。设置后,还可以使用-e选项选择执行作用域(thread、warp 或 block)。
构建说明
bash
# 确保 DOCA 在 pkgconfig 环境变量中
cd /opt/mellanox/doca/samples/doca_gpunetio/gpunetio_simple_send
meson build
ninja -C build
执行示例
假设系统的网卡 PCIe 地址为 9f:00.0,GPU 地址为 8a:00.0,以下命令启动示例:发送 1024 字节的数据包(-s 1024),使用 1024 个 CUDA 线程(-t 1024),启用高层 API(-q 1)并采用 BLOCK 执行作用域(-e 2)。
命令:
bash
# 确保 DOCA 在 LD_LIBRARY_PATH 环境变量中
$ sudo ./doca_gpunetio_simple_send -n 9f:00.0 -g 8a:00.0 -s 1024 -t 1024 -q 1 -e 2
输出:
[2025-10-28 04:38:10:548387][961593344][DOCA][INF][doca_log.cpp:645] DOCA version 3.3.0010
[2025-10-28 04:38:10:548420][961593344][DOCA][INF][gpunetio_simple_send_main.c:363][main] Starting the sample
[2025-10-28 04:38:10:839382][961593344][DOCA][INF][gpunetio_simple_send_main.c:433][main] Sample configuration:
GPU 8a:00.0
NIC 9f:00.0
Packet size 1024
CUDA threads 1024
CPU Proxy No
Shared QP Yes
Shared QP exec scope Block
[2025-10-28 04:38:10:911219][961593344][DOCA][INF][gpunetio_simple_send_sample.c:291][create_txq] Creating Sample Eth Txq
[2025-10-28 04:38:10:916434][961593344][DOCA][INF][gpunetio_simple_send_sample.c:429][create_txq] Mapping send queue buffer (0x0x7fd718800000 size 1048576B dmabuf fd 45) with dmabuf mode
[2025-10-28 04:38:10:917479][961593344][DOCA][INF][gpunetio_simple_send_sample.c:580][gpunetio_simple_send] Launching CUDA kernel to send packets.
[2025-10-28 04:38:10:920972][961593344][DOCA][INF][gpunetio_simple_send_sample.c:584][gpunetio_simple_send] Waiting for ctrl+c termination
# 输入 Ctrl+C 终止示例
[2025-10-28 04:38:22:681160][961593344][DOCA][INF][gpunetio_simple_send_sample.c:67][signal_handler] Signal 2 received, preparing to exit!
[2025-10-28 04:38:22:681176][961593344][DOCA][INF][gpunetio_simple_send_sample.c:590][gpunetio_simple_send] Exiting from sample
[2025-10-28 04:38:22:681387][961593344][DOCA][INF][gpunetio_simple_send_sample.c:204][destroy_txq] Destroying Txq
[2025-10-28 04:38:22:990964][961593344][DOCA][INF][gpunetio_simple_send_sample.c:612][gpunetio_simple_send] Sample finished successfully
[2025-10-28 04:38:22:990980][961593344][DOCA][INF][gpunetio_simple_send_main.c:457][main] Sample finished successfully
性能验证
要验证数据包正在发送,可以在同一台机器上使用 mlnx_perf 命令检查流量吞吐量。(假设位于 9f:00.0 的网卡接口名为 ens6f0np0):
$ mlnx_perf -i ens6f0np0
tx_vport_unicast_packets: 21,033,677
tx_vport_unicast_bytes: 21,538,485,248 Bps = 172,307.88 Mbps
tx_packets_phy: 21,033,286
tx_bytes_phy: 21,622,256,208 Bps = 172,978.4 Mbps
tx_prio0_bytes: 21,617,084,940 Bps = 172,936.67 Mbps
tx_prio0_packets: 21,028,292
UP 0: 172,936.67 Mbps = 100.00%
UP 0: 21,028,292 Tran/sec = 100.00%
不同系统之间的吞吐量会有所差异,取决于 GPU 与网卡之间的 PCIe 连接、网卡型号、GPU 型号等因素。可以通过改变数据包大小和 CUDA 线程数量,使用本示例评估您系统的发送性能。
RDMA 客户端-服务器(RDMA Client Server)
本示例展示如何使用 GPUNetIO RDMA API,通过单个 RDMA 队列进行接收以及发送/带立即数写入(send/write with immediate)。
服务器有一个 GPU 缓冲区数组 A,由 GPU_BUF_NUM 个 doca_gpu_buf 元素组成,每个大小为 1kB。客户端有两个 GPU 缓冲区数组 B 和 C,各由 GPU_BUF_NUM 个 doca_gpu_buf 元素组成,每个大小为 512B。
目标是让客户端用两个 512B 的 GPU 缓冲区填满服务器的单个 1kB 缓冲区,如下图所示:
【图示:服务器(Server)侧的缓冲区数组 A(A0~A3,每个 1kB),每个 A 缓冲区由虚线分为两半,分别接收来自客户端 B 和 C 缓冲区的数据;客户端(Client)侧,线程 Th0 负责 B 缓冲区(B0~B3),线程 Th1 负责 C 缓冲区(C0~C3);偶数编号缓冲区通过 RDMA Write Imm 发送,奇数编号通过 RDMA Send Imm 发送,两个 512B 数据合并填满服务器一个 1kB 缓冲区。】
为演示 RDMA write 和 send 的用法,偶数缓冲区由客户端以 write immediate 发送,奇数缓冲区以 send immediate 发送。两种情况下,服务器都必须预先提交(pre-post)RDMA 接收操作。
对于每个缓冲区,CUDA kernel 代码重复以下握手过程:
【图示:握手流程------服务器执行
gpunetio_rdma_recv提交接收,然后通过gpunetio_rdma_write(flag)向客户端写一个标志;客户端poll(flag)轮询该标志;随后客户端执行gpunetio_rdma_write/send发送数据;服务器执行gpunetio_rdma_recv_wait_all等待全部接收完成。】
所有缓冲区填满后,服务器再次检查所有值是否有效。服务器输出应如下所示:
bash
# 确保 DOCA 和 DPDK 在 LD_LIBRARY_PATH 环境变量中
$ cd /opt/mellanox/doca/samples/doca_gpunetio/gpunetio_rdma_client_server_write
$ ./build/doca_gpunetio_rdma_client_server_write -gpu 17:00.0 -d mlx5_0
[14:11:43:000930][1173110][DOCA][INF][gpunetio_rdma_client_server_write_main.c:250][main] Starting the sample
...
[14:11:43:686610][1173110][DOCA][INF][rdma_common.c:91][oob_connection_server_setup] Listening for incoming connections
[14:11:45:681523][1173110][DOCA][INF][rdma_common.c:105][oob_connection_server_setup] Client connected at IP: 192.168.2.28 and port: 46274
...
[14:11:45:771807][1173110][DOCA][INF][gpunetio_rdma_client_server_write_sample.c:644][rdma_write_server] Before launching CUDA kernel, buffer array A is:
[14:11:45:771822][1173110][DOCA][INF][gpunetio_rdma_client_server_write_sample.c:646][rdma_write_server] Buffer 0 -> offset 0: 1111 | offset 128: 1111
[14:11:45:771837][1173110][DOCA][INF][gpunetio_rdma_client_server_write_sample.c:646][rdma_write_server] Buffer 1 -> offset 0: 1111 | offset 128: 1111
[14:11:45:771851][1173110][DOCA][INF][gpunetio_rdma_client_server_write_sample.c:646][rdma_write_server] Buffer 2 -> offset 0: 1111 | offset 128: 1111
[14:11:45:771864][1173110][DOCA][INF][gpunetio_rdma_client_server_write_sample.c:646][rdma_write_server] Buffer 3 -> offset 0: 1111 | offset 128: 1111
RDMA Recv 2 ops completed with immediate values 0 and 1!
RDMA Recv 2 ops completed with immediate values 1 and 2!
RDMA Recv 2 ops completed with immediate values 2 and 3!
RDMA Recv 2 ops completed with immediate values 3 and 4!
[14:11:45:781561][1173110][DOCA][INF][gpunetio_rdma_client_server_write_sample.c:671][rdma_write_server] After launching CUDA kernel, buffer array A is:
[14:11:45:781574][1173110][DOCA][INF][gpunetio_rdma_client_server_write_sample.c:673][rdma_write_server] Buffer 0 -> offset 0: 2222 | offset 128: 3333
[14:11:45:781583][1173110][DOCA][INF][gpunetio_rdma_client_server_write_sample.c:673][rdma_write_server] Buffer 1 -> offset 0: 2222 | offset 128: 3333
[14:11:45:781593][1173110][DOCA][INF][gpunetio_rdma_client_server_write_sample.c:673][rdma_write_server] Buffer 2 -> offset 0: 2222 | offset 128: 3333
[14:11:45:781602][1173110][DOCA][INF][gpunetio_rdma_client_server_write_sample.c:673][rdma_write_server] Buffer 3 -> offset 0: 2222 | offset 128: 3333
[14:11:45:781640][1173110][DOCA][INF][gpunetio_rdma_client_server_write_main.c:294][main] Sample finished successfully
在另一侧,假设服务器的 IP 地址为 192.168.2.28,客户端输出应如下所示:
bash
# 确保 DOCA 和 DPDK 在 LD_LIBRARY_PATH 环境变量中
$ cd /opt/mellanox/doca/samples/doca_gpunetio/gpunetio_rdma_client_server_write
$ ./build/doca_gpunetio_rdma_client_server_write -gpu 17:00.0 -d mlx5_0 -c 192.168.2.28
[16:08:22:335744][160913][DOCA][INF][gpunetio_rdma_client_server_write_main.c:197][main] Starting the sample
...
[16:08:25:753316][160913][DOCA][INF][rdma_common.c:147][oob_connection_client_setup] Connected with server successfully
......
Client waiting on flag 7f6596735000 for server to post RDMA Recvs
Thread 0 post rdma write imm 0
Thread 1 post rdma write imm 0
Client waiting on flag 7f6596735001 for server to post RDMA Recvs
Thread 0 post rdma send imm 1
Thread 1 post rdma send imm 1
Client waiting on flag 7f6596735002 for server to post RDMA Recvs
Thread 0 post rdma write imm 2
Thread 1 post rdma write imm 2
Client waiting on flag 7f6596735003 for server to post RDMA Recvs
Thread 0 post rdma send imm 3
Thread 1 post rdma send imm 3
[16:08:25:853454][160913][DOCA][INF][gpunetio_rdma_client_server_write_main.c:241][main] Sample finished successfully
使用 RDMA 时,网络设备必须按名称指定(例如 mlx5_0),而不是像以太网那样使用 PCIe 地址。
也可以启用 RDMA CM 模式,用同一个 RDMA GPU 句柄建立两条连接。客户端示例:
bash
# 确保 DOCA 和 DPDK 在 LD_LIBRARY_PATH 环境变量中
$ cd /opt/mellanox/doca/samples/doca_gpunetio/gpunetio_rdma_client_server_write
$ ./build/samples/doca_gpunetio_rdma_client_server_write -d mlx5_0 -gpu 17:00.0 -gid 3 -c 10.137.189.28 -cm --server-addr-type ipv4 --server-addr 192.168.2.28
[11:30:34:489781][3853018][DOCA][INF][gpunetio_rdma_client_server_write_main.c:461][main] Starting the sample
...
[11:30:35:038828][3853018][DOCA][INF][gpunetio_rdma_client_server_write_sample.c:950][rdma_write_client] Client is waiting for a connection establishment
[11:30:35:082039][3853018][DOCA][INF][gpunetio_rdma_client_server_write_sample.c:963][rdma_write_client] Client - Connection 1 is established
...
[11:30:35:095282][3853018][DOCA][INF][gpunetio_rdma_client_server_write_sample.c:1006][rdma_write_client] Establishing connection 2..
[11:30:35:097521][3853018][DOCA][INF][gpunetio_rdma_client_server_write_sample.c:1016][rdma_write_client] Client is waiting for a connection establishment
[11:30:35:102718][3853018][DOCA][INF][gpunetio_rdma_client_server_write_sample.c:1029][rdma_write_client] Client - Connection 2 is established
[11:30:35:102783][3853018][DOCA][INF][gpunetio_rdma_client_server_write_sample.c:1046][rdma_write_client] Client, terminate kernels
Client waiting on flag 7f16067b5000 for server to post RDMA Recvs
Thread 0 post rdma write imm 0
Thread 1 post rdma write imm 1
Client waiting on flag 7f16067b5001 for server to post RDMA Recvs
Thread 0 post rdma send imm 1
Thread 1 post rdma send imm 2
Client waiting on flag 7f16067b5002 for server to post RDMA Recvs
Thread 0 post rdma write imm 2
Thread 1 post rdma write imm 3
Client waiting on flag 7f16067b5003 for server to post RDMA Recvs
Thread 0 post rdma send imm 3
Thread 1 post rdma send imm 4
Client posted and completed 4 RDMA commits on connection 0. Waiting on the exit flag.
Client waiting on flag 7f16067b5000 for server to post RDMA Recvs
Thread 0 post rdma write imm 0
Thread 1 post rdma write imm 1
Client waiting on flag 7f16067b5001 for server to post RDMA Recvs
Thread 0 post rdma send imm 1
Thread 1 post rdma send imm 2
Client waiting on flag 7f16067b5002 for server to post RDMA Recvs
Thread 0 post rdma write imm 2
Thread 1 post rdma write imm 3
Client waiting on flag 7f16067b5003 for server to post RDMA Recvs
Thread 0 post rdma send imm 3
Thread 1 post rdma send imm 4
Client posted and completed 4 RDMA commits on connection 1. Waiting on the exit flag.
[11:30:35:122448][3853018][DOCA][INF][gpunetio_rdma_client_server_write_main.c:512][main] Sample finished successfully
使用 RDMA CM 时,服务器侧必须指定命令选项 -cm。
注意:从 CUDA kernel 打印不利于性能,仅对调试目的以及像这样的简单示例才有意义。
Verbs 示例
doca_gpunetio_verbs_* 系列示例演示如何在各种场景中使用 GPUNetIO Verbs API。这些示例需要客户端-服务器设置,客户端需要 -c <server IP> 参数。
所有 Verbs 示例都支持以下参数:
-n:网卡句柄类型(0:AUTO,1:CPU Proxy,2:GPU DB)。默认为 0(AUTO)。-e:共享 QP 的执行模式(0:per-thread,1:per-warp)。默认为 0(per-thread)。-d:网卡设备名。-g:GPU 设备 PCIe 地址。-gid:DOCA RDMA 的 GID 索引(可选)。-i:迭代次数(可选)。-t:CUDA 线程数量(可选)。
请阅读下文各具体示例小节,查看额外参数。
本指南的示例中,假设 GPU PCIe 地址为 8A:00.0,网卡 PCIe 地址相应指定。
RoCE 注意事项 :如果示例运行在 RoCE 连接上(即 ConnectX/BlueField 设置为以太网模式而非 InfiniBand 模式),您可能会遇到如下(或类似的)错误:
FW failed to modify object, status=BAD_PARAM_ERR (0x3), syndrome=0x1f3b5d。要解决此问题,请从
samples/doca_gpunetio/verbs_common.c文件中connect_verbs_qp函数里移除doca_verbs_ah_attr_set_dlid函数调用。
为获得最佳带宽性能,verbs_common.cpp 中的默认 MTU 设置为 4KB:
c
doca_verbs_qp_attr_set_path_mtu(verbs_qp_attr, DOCA_MTU_SIZE_4K_BYTES);
为配合此设置,必须确保您的网络接口配置为支持至少 4200 字节的 MTU。可以使用以下命令配置:
$ ifconfig <interface_name> mtu 4200
如果您的网络基础设施不支持 4KB MTU,则必须修改示例代码以使用 1KB MTU(DOCA_MTU_SIZE_1K_BYTES)。
带宽(Bandwidth)示例
以 _bw 结尾的示例测量特定 GPUNetIO Verbs API 函数的带宽。在这些示例中,客户端从 CUDA kernel 准备并发送数据,而服务器在 CPU 上等待接收数据,在收到 Ctrl+C 信号时进行校验并报告结果。
关键特征
- 客户端输出以各种消息大小准备和发送消息所达到的 MB/s。
- 服务器输出一条表明执行结果的消息。
- 所有带宽示例都支持前面列出的命令行参数。
简化 QP/CQ/UAR 创建
为简化 DOCA RDMA Verbs 与 GPUNetIO 的集成,samples/doca_gpunetio/verbs_high_level.cpp 中提供了 doca_gpu_verbs_create_qp_hl() 和 doca_gpu_verbs_create_qp_group_hl() 等高层函数。这些函数封装了创建 QP/CQ/UAR 的必要步骤,使开发者更容易在自己的应用中组合使用 DOCA RDMA Verbs 和 GPUNetIO。
doca_gpunetio_verbs_write_bw
doca_gpunetio_verbs_write_bw 测试使用 GPUNetIO Verbs API 测量 RDMA Write 操作的带宽。默认情况下,它启动一个包含 1 个 block、512 个线程的 CUDA kernel,所有线程在不同位置提交 RDMA Write WQE,由最后一个线程统一 submit。然后测试轮询与最后一个 WQE 对应的 CQE,以确保之前所有 WQE 都已正确执行。
示例命令行:
-
服务器:
doca_gpunetio_verbs_write_bw -g 8A:00.0 -d mlx5_0 -
客户端(可添加其他命令行选项):
doca_gpunetio_verbs_write_bw -g 8A:00.0 -d mlx5_0 -c 192.168.1.63
doca_gpunetio_verbs_put_bw
doca_gpunetio_verbs_put_bw 测试使用带共享 QP 特性的 GPUNetIO Verbs API 测量 RDMA Write 操作(称为 "Put")的带宽。它启动一个包含 2 个 block、每个 block 256 个线程的 CUDA kernel。通过将 KERNEL_DEBUG_TIMES 宏设置为 1,该测试还可以测量各个函数的延迟。
示例命令行:
-
服务器:
doca_gpunetio_verbs_put_bw -g 8A:00.0 -d mlx5_0 -
客户端(可添加其他命令行选项):
doca_gpunetio_verbs_put_bw -g 8A:00.0 -d mlx5_0 -c 192.168.1.63
doca_gpunetio_verbs_put_signal_bw
doca_gpunetio_verbs_put_signal_bw 测试使用 GPUNetIO Verbs API 测量 Put + Signal 操作(RDMA Write + RDMA Atomic,带共享 QP)的带宽。它启动一个包含 2 个 block、每个 block 256 个线程的 CUDA kernel。
示例命令行:
-
服务器:
doca_gpunetio_verbs_put_signal_bw -g 8A:00.0 -d mlx5_0 -
客户端(可添加其他命令行选项):
doca_gpunetio_verbs_put_signal_bw -g 8A:00.0 -d mlx5_0 -c 192.168.1.63
该示例可以正常工作,但 CUDA kernel 循环中有一处小错误。修正后的循环应为:
c
do {
final_val = doca_gpu_dev_verbs_atomic_read<uint64_t, DOCA_GPUNETIO_VERBS_RESOURCE_SHARING_MODE_GPU>(&prev_flag_buf[tidx]);
doca_gpu_dev_verbs_fence_acquire<DOCA_GPUNETIO_VERBS_SYNC_SCOPE_SYS>();
} while((final_val != (iter_thread - 1)) && (final_val != ((iter_thread * 2) - 1)));
doca_gpunetio_verbs_put_counter_bw
doca_gpunetio_verbs_put_counter_bw 测试使用 GPUNetIO Verbs API 测量 Put + Counter 操作(RDMA Write + Wait WQE + RDMA Atomic,带共享 QP)的带宽。它启动一个包含 2 个 block、每个 block 256 个线程的 CUDA kernel。
示例命令行:
-
服务器:
doca_gpunetio_verbs_put_counter_bw -g 8A:00.0 -d mlx5_0 -
客户端(可添加其他命令行选项):
doca_gpunetio_verbs_put_counter_bw -g 8A:00.0 -d mlx5_0 -c 192.168.1.63
该示例可以正常工作,但 CUDA kernel 循环中有一处小错误。修正后的循环应为:
c
do {
final_val = doca_gpu_dev_verbs_atomic_read<uint64_t, DOCA_GPUNETIO_VERBS_RESOURCE_SHARING_MODE_GPU>(&prev_flag_buf[tidx]);
doca_gpu_dev_verbs_fence_acquire<DOCA_GPUNETIO_VERBS_SYNC_SCOPE_SYS>();
} while((final_val != (iter_thread - 1)) && (final_val != ((iter_thread * 2) - 1)));
doca_gpunetio_verbs_twosided_bw
doca_gpunetio_verbs_twosided_bw 测试使用带共享 QP 特性的 GPUNetIO Verbs API,通过 Send/Recv 操作测量客户端-服务器数据交换的带宽。它启动一个包含 2 个 block、每个 block 256 个线程的 CUDA kernel。
示例命令行:
-
服务器:
doca_gpunetio_verbs_twosided_bw -g 8A:00.0 -d mlx5_0 -
客户端(可添加其他命令行选项):
doca_gpunetio_verbs_twosided_bw -g 8A:00.0 -d mlx5_0 -c 192.168.1.63
doca_gpunetio_verbs_get_bw
本示例应用测量单边 RDMA Read("Get")操作的带宽。它使用带共享 QP 特性的 GPUNetIO Verbs API。
该应用启动一个包含 2 个 block、每个 block 256 个线程的 CUDA kernel。
示例用法:
-
服务器:
doca_gpunetio_verbs_get_bw -g 8A:00.0 -d mlx5_0 -
客户端(连接到服务器 IP,例如
192.168.1.63):doca_gpunetio_verbs_get_bw -g 8A:00.0 -d mlx5_0 -c 192.168.1.63可添加其他命令行选项。
延迟(Latency)示例
以 _lat 结尾的示例通过在客户端和服务器之间进行乒乓(ping-pong)交换,测量特定 GPUNetIO Verbs API 函数的延迟。这些测试启动仅含单个 CUDA 线程的 CUDA kernel,不支持 -e 和 -t 命令行选项。
客户端和服务器都以微秒为单位输出不同大小消息的准备和交换往返时间(RTT)延迟(半程和全程)。服务器还会输出一条表明执行结果的消息。
doca_gpunetio_verbs_write_lat
doca_gpunetio_verbs_write_lat 测试使用不带共享 QP 特性的 GPUNetIO Verbs API 测量 RDMA Write 操作的延迟。它类似于 perftest。
本示例通过命令行选项 -r <0|1> 支持可靠门铃(reliable doorbell)特性。
示例命令行:
-
服务器:
doca_gpunetio_verbs_write_lat -g 8A:00.0 -d mlx5_0 -
客户端(可添加其他命令行选项):
doca_gpunetio_verbs_write_lat -g 8A:00.0 -d mlx5_0 -c 192.168.1.63
doca_gpunetio_verbs_put_signal_lat
doca_gpunetio_verbs_put_signal_lat 测试使用 GPUNetIO Verbs API 测量 Put 和 Signal 操作(RDMA Write + RDMA Atomic,带共享 QP)的延迟。它使用来自 doca_gpunetio_dev_verbs_onesided.cuh 的高层 API 函数,并启动仅含单个 CUDA 线程的 CUDA kernel。
示例命令行:
-
服务器:
doca_gpunetio_verbs_put_signal_lat -g 8A:00.0 -d mlx5_0 -
客户端(可添加其他命令行选项):
doca_gpunetio_verbs_put_signal_lat -g 8A:00.0 -d mlx5_0 -c 192.168.1.63
doca_gpunetio_verbs_put_counter_lat
doca_gpunetio_verbs_put_counter_lat 测试使用 GPUNetIO Verbs API 测量 Put + Counter 操作(RDMA Write + Wait WQE + RDMA Atomic,带共享 QP)的延迟。它利用来自 doca_gpunetio_dev_verbs_counter.cuh 的高层 API 函数和 Core Direct 计数器特性------即使只有单个 CUDA 线程也是如此。
本示例通过命令行选项 -r <0|1> 支持可靠门铃特性。
示例命令行:
-
服务器:
doca_gpunetio_verbs_put_counter_lat -g 8A:00.0 -d mlx5_0 -
客户端(可添加其他命令行选项):
doca_gpunetio_verbs_put_counter_lat -g 8A:00.0 -d mlx5_0 -c 192.168.1.63
GPU DMA 拷贝(GPU DMA Copy)
本示例展示如何使用 DOCA DMA 和 DOCA GPUNetIO 库,从 CUDA kernel 中以 DMA 方式拷贝内存缓冲区:从 CPU 到 GPU(使用 DOCA DMA CPU 函数),以及从 GPU 到 CPU(使用 DOCA GPUNetIO DMA device 函数)。本示例需要 DPU,因为它使用 DPU 上的 DMA 引擎。
bash
$ cd /opt/mellanox/doca/samples/doca_gpunetio/gpunetio_dma_memcpy
# 构建示例然后执行
$ ./build/doca_gpunetio_dma_memcpy -g 17:00.0 -n ca:00.0
[15:44:04:189462][862197][DOCA][INF][gpunetio_dma_memcpy_main.c:164][main] Starting the sample
EAL: Detected CPU lcores: 64
EAL: Detected NUMA nodes: 2
EAL: Detected shared linkage of DPDK
EAL: Selected IOVA mode 'VA'
EAL: No free 2048 kB hugepages reported on node 0
EAL: No free 2048 kB hugepages reported on node 1
EAL: VFIO support initialized
TELEMETRY: No legacy callbacks, legacy socket not created
EAL: Probe PCI driver: gpu_cuda (10de:2331) device: 0000:17:00.0 (socket 0)
[15:44:04:857251][862197][DOCA][INF][gpunetio_dma_memcpy_sample.c:211][init_sample_mem_objs] The CPU source buffer value to be copied to GPU memory: This is a sample piece of text from CPU
[15:44:04:857359][862197][DOCA][WRN][doca_mmap.cpp:1743][doca_mmap_set_memrange] Mmap 0x55aec6206140: Memory range isn't cache-line aligned - addr=0x55aec52ceb10. For best performance align address to 64B
[15:44:04:858839][862197][DOCA][INF][gpunetio_dma_memcpy_sample.c:158][init_sample_mem_objs] The GPU source buffer value to be copied to CPU memory: This is a sample piece of text from GPU
[15:44:04:921702][862197][DOCA][INF][gpunetio_dma_memcpy_sample.c:570][submit_dma_memcpy_task] Success, DMA memcpy job done successfully
CUDA KERNEL INFO: The GPU destination buffer value after the memcpy: This is a sample piece of text from CPU
CPU received message from GPU: This is a sample piece of text from GPU
[15:44:04:930087][862197][DOCA][INF][gpunetio_dma_memcpy_sample.c:364][gpu_dma_cleanup] Cleanup DMA ctx with GPU data path
[15:44:04:932658][862197][DOCA][INF][gpunetio_dma_memcpy_sample.c:404][gpu_dma_cleanup] Cleanup DMA ctx with CPU data path
[15:44:04:954156][862197][DOCA][INF][gpunetio_dma_memcpy_main.c:197][main] Sample finished successfully