gpunetio, 是类似cpu app doca sender,receiver 过程的 gpu化,现在探究两者的联系。
GPUNetIO 就是把我们 demo 里 CPU 扮演的那个"App"角色,整体搬到 GPU 线程(CUDA kernel)里 ------同一条 构造WQE → post到SQ → 敲门铃 → 网卡DMA → 写CQE → 轮询CQ 流水线,一步都没少,只是干活的人换了。CPU 没有消失,而是从"数据面"退到了"控制面"。
一、同一个流水线,两边对照
| 流水线环节 | CPU 版 demo(已完成) | GPUNetIO 版 |
|---|---|---|
| 注册内存(MR) | posix_memalign + doca_mmap(主机内存) |
cudaMalloc(显存)+ 注册(GPUDirect RDMA,网卡直接 DMA 到显存) |
| QP 位置 | QP 在网卡上,SQ/RQ/CQ 在主机内存 | SQ/RQ/CQ 映射到 GPU 显存,GPU 线程直接读写 |
| 构造 WQE / post | CPU 调 doca_rdma_task_send_allocate_init + doca_task_submit |
GPU kernel 内的线程调同一族 API 的任务提交(GPUNetIO 提供了设备侧版本的 submit) |
| 敲门铃 | submit 内部完成 | GPU 线程直接写映射进 GPU 地址空间的门铃寄存器(PCIe BAR),CPU 完全不参与 |
| 收 CQE | doca_pe_progress() 轮询 CQ → 回调 |
GPU kernel 内的轮询循环直接读显存里的 CQE(没有 PE、没有回调函数) |
| 数据处理 | 回调里 printf / memcmp |
kernel 里拿到数据后直接接着算(这就是意义所在:零拷贝进计算) |
关键收益就在最后一行:数据从网线进显存,GPU 算完直接发回网线,全程不落地主机内存、CPU 一次都不碰数据。
二、需要改变的具体地方(改动清单)
-
内存全部换 CUDA 显存 :收发缓冲区用
cudaMalloc,mmap 注册时走 GPU 内存路径。这是"GPUDirect RDMA"的本体。 -
队列搬到 GPU :创建
doca_rdma(或以太网模式的doca_eth_txq/doca_eth_rxq)时指定队列导出到 GPU 显存,让 GPU 线程够得着 WQE/CQE。 -
提交与轮询搬进 kernel :CUDA kernel 常驻运行(persistent kernel),里面就是一个循环:提交任务/发包 → 轮询完成计数 → 处理数据 → 再提交。PE 没了------PE 是 CPU 线程亲和的事件引擎,GPU 侧用不上;回调也没了,换成 kernel 里自己 poll、自己计数。
-
连接建立留在 CPU :开卡、建 QP、
doca_rdma_export/connect握手、注册显存------这些控制面操作仍由 CPU 在 kernel 启动前完成 ,然后通过参数/CUDA 事件把"连接已就绪、队列地址"等交给 kernel。这也是 GPUNetIO 的固定分工:CPU 管控制面,GPU 管数据面。 -
CPU↔GPU 同步换机制 :demo 里用
volatile int send_done让回调通知主线程;GPUNetIO 里用 GPUNetIO 自带的 GPU 信号量/等待原语(doca_gpu同步对象)+ CUDA 事件,比如 CPU 发令"开始收发"、kernel 回报"已完成 N 个"。 -
(可选)以太网模式 :如果不走 RDMA 而跑裸以太网,QP 换成
doca_eth_txq/rxq(无连接,按 burst 收发包),配doca_flow做报文 steering------这是 GPUNetIO 另一种形态,适合自定义协议。
三、语义不变的部分
- QP/RC 连接/EXPORT-CONNECT 握手、
先 post RECEIVE 再 SEND的顺序约束、CQE 语义------一模一样; - WQE/CQE 的字段含义、门铃机制、环形队列------硬件层面没有任何区别。
所以在 CPU demo 里搞清楚的那套模型,在 GPUNetIO 里是 1:1 复用的,改的只是"谁来 post、谁来 poll、内存住哪"。建议路径:先按官方 doca-samples 仓库里的 GPUNetIO RDMA 收发示例跑通(结构上会看到很多眼熟的函数名),再把 demo 的 loopback 逻辑搬进去------注意 kernel 里的轮询循环必须有退出条件和超时,否则 kernel 挂死时你连报错都看不到,调试难度比 CPU 版高一个量级。