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)

安装步骤

  1. 安装GPU驱动与CUDA:从NVIDIA官网下载runfile安装驱动与CUDA 11.x/12.x,确保nvidia-smi与nvcc -V可用。
  2. 安装MLNX_OFED:从NVIDIA官网下载对应系统版本的MLNX_OFED,执行sudo ./mlnxofedinstall --force,完成后运行sudo /etc/init.d/openibd restart。
  3. 验证驱动加载:确认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;
}

运行与验证

  1. 编译:gcc -o gdr_pingpong gdr_pingpong.c -libverbs -lcuda
  2. 运行服务端:./gdr_pingpong -s
  3. 运行客户端(另一终端或另一台机器):./gdr_pingpong -c <server_ip>
  4. 通过条件:程序输出“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性能可能受限。