1. 环境搭建与验证
任务目标
搭建支持GPUDirect RDMA(GDR)的开发环境,使GPU显存与RDMA网卡之间能够通过PCIe Peer-to-Peer(P2P)直接交换数据,绕过CPU与主机内存拷贝。本案例完成环境安装与连通性验证,为后续GDR编程提供可运行的基础平台。
环境需求
- 硬件:NVIDIA GPU(Pascal架构及以上)、支持RDMA的网卡(Mellanox ConnectX-4/5/6 或 BlueField)、PCIe交换机(可选,用于跨NUMA节点P2P)
- 操作系统:Ubuntu 20.04/22.04 x86_64
- 软件:NVIDIA驱动、CUDA Toolkit、MLNX_OFED(含libibverbs、librdmacm)
安装步骤
- 安装GPU驱动与CUDA:从NVIDIA官网下载runfile安装驱动与CUDA 11.x/12.x,确保
nvidia-smi与nvcc -V可用。 - 安装MLNX_OFED:从NVIDIA官网下载对应系统版本的MLNX_OFED,执行
sudo ./mlnxofedinstall --force,完成后运行sudo /etc/init.d/openibd restart。 - 验证驱动加载:确认
ibv_devinfo能列出RDMA设备,且nvidia-smi topo -m能显示GPU与网卡的PCIe拓扑连接关系。
连通性验证代码
以下代码使用libibverbs直接对GPU显存发起RDMA写操作,验证GPU显存地址能否被网卡硬件访问。若写操作成功且远端收到数据,则证明GDR路径可用。
// gdr_pingpong.c - 验证GPU显存与RDMA网卡间的P2P连通性
// 编译: gcc -o gdr_pingpong gdr_pingpong.c -libverbs -lcuda
// 运行: 服务端 ./gdr_pingpong -s;客户端 ./gdr_pingpong -c <server_ip>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <infiniband/verbs.h>
#include <cuda_runtime.h>
#include <cuda.h>
#define BUFFER_SIZE 4096
#define MSG "GDR_P2P_OK"
struct context {
struct ibv_context *ctx;
struct ibv_pd *pd;
struct ibv_mr *mr;
struct ibv_cq *cq;
struct ibv_qp *qp;
char *buf;
int is_server;
};
static int create_context(struct context *ctx, int is_server) {
struct ibv_device **dev_list;
struct ibv_device *dev;
int num_devices;
dev_list = ibv_get_device_list(&num_devices);
if (!dev_list || num_devices < 1) {
fprintf(stderr, "未找到RDMA设备\n");
return -1;
}
dev = dev_list[0];
ctx->ctx = ibv_open_device(dev);
if (!ctx->ctx) {
fprintf(stderr, "打开设备失败\n");
return -1;
}
ibv_free_device_list(dev_list);
ctx->pd = ibv_alloc_pd(ctx->ctx);
ctx->cq = ibv_create_cq(ctx->ctx, 16, NULL, NULL, 0);
if (!ctx->pd || !ctx->cq) {
fprintf(stderr, "分配PD/CQ失败\n");
return -1;
}
// 分配GPU显存并注册到RDMA
if (cudaMalloc(&ctx->buf, BUFFER_SIZE) != cudaSuccess) {
fprintf(stderr, "cudaMalloc失败\n");
return -1;
}
if (is_server) {
cudaMemcpy(ctx->buf, MSG, strlen(MSG) + 1, cudaMemcpyHostToDevice);
}
ctx->mr = ibv_reg_mr(ctx->pd, ctx->buf, BUFFER_SIZE,
IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE);
if (!ctx->mr) {
fprintf(stderr, "注册MR失败\n");
return -1;
}
// 创建QP
struct ibv_qp_init_attr attr = {
.send_cq = ctx->cq,
.recv_cq = ctx->cq,
.qp_type = IBV_QPT_RC,
.cap = { .max_send_wr = 8, .max_recv_wr = 8,
.max_send_sge = 1, .max_recv_sge = 1 }
};
ctx->qp = ibv_create_qp(ctx->pd, &attr);
if (!ctx->qp) {
fprintf(stderr, "创建QP失败\n");
return -1;
}
return 0;
}
static int exchange_qp_info(struct context *ctx, const char *peer_ip) {
// 实际应通过TCP/socket交换QPN、LID、GID、MR信息
// 此处简化为本地回环测试,仅用于验证GDR路径
(void)peer_ip;
return 0;
}
static int run_pingpong(struct context *ctx) {
struct ibv_sge sge = { .addr = (uintptr_t)ctx->buf,
.length = BUFFER_SIZE,
.lkey = ctx->mr->lkey };
struct ibv_send_wr wr = { .sg_list = &sge, .num_sge = 1,
.opcode = IBV_WR_SEND };
struct ibv_send_wr *bad_wr;
struct ibv_wc wc;
if (ibv_post_send(ctx->qp, &wr, &bad_wr)) {
fprintf(stderr, "post_send失败\n");
return -1;
}
// 等待完成(简化:轮询)
int timeout = 1000;
while (timeout--) {
if (ibv_poll_cq(ctx->cq, 1, &wc) == 1) {
if (wc.status == IBV_WC_SUCCESS) {
printf("RDMA操作完成,GPU显存地址可被网卡访问\n");
return 0;
}
}
usleep(1000);
}
fprintf(stderr, "操作超时\n");
return -1;
}
int main(int argc, char *argv[]) {
struct context ctx = {0};
int is_server = 0;
if (argc > 1 && strcmp(argv[1], "-s") == 0) is_server = 1;
if (create_context(&ctx, is_server)) return 1;
if (exchange_qp_info(&ctx, argc > 2 ? argv[2] : NULL)) return 1;
int ret = run_pingpong(&ctx);
// 清理资源
ibv_destroy_qp(ctx.qp);
ibv_dereg_mr(ctx.mr);
ibv_destroy_cq(ctx.cq);
ibv_dealloc_pd(ctx.pd);
ibv_close_device(ctx.ctx);
cudaFree(ctx.buf);
return ret;
}
运行与验证
- 编译:
gcc -o gdr_pingpong gdr_pingpong.c -libverbs -lcuda - 运行服务端:
./gdr_pingpong -s - 运行客户端(另一终端或另一台机器):
./gdr_pingpong -c <server_ip> - 通过条件:程序输出“RDMA操作完成,GPU显存地址可被网卡访问”,且无CUDA或verbs错误。
说明:上述代码为最小验证实现,QP连接握手信息(QPN、LID、GID等)需通过socket交换,完整实现可参考libibverbs示例。本案例核心是验证GPU显存地址能被RDMA网卡硬件直接访问,若代码能成功对GPU显存执行RDMA写操作,即证明GDR路径已打通。
常见问题排查
- ibv_devinfo无设备:确认OFED已正确安装且
ib_uverbs模块已加载。 - cudaMalloc失败:检查GPU驱动与CUDA版本匹配,运行
nvidia-smi确认GPU可见。 - 注册MR失败:确认GPU显存地址满足页对齐要求,且驱动支持GDR(Pascal及以上架构)。
- 拓扑检查:运行
nvidia-smi topo -m确认GPU与网卡位于同一PCIe交换机下(显示“NV”或“PIX”连接),否则P2P性能可能受限。