3. NVLink带宽基准测试

任务目标

编写一个运行在DPU(BlueField)Host侧的程序,通过NVLink在GPU与DPU之间持续传输大数据块,测量实际可达带宽。程序需支持双向传输(H2D与D2H)并分别报告带宽值。

环境与依赖

  • 硬件:NVIDIA BlueField-2/3 DPU + 配套GPU(如A100/H100),NVLink已连接
  • 软件:CUDA Toolkit(≥11.0)、DOCA SDK(≥2.0)、gcc
  • 关键库:libcuda.so(通过cuda.h调用)、libdoca-dma.so(DOCA DMA引擎)

实现原理

利用DOCA DMA引擎在GPU显存与DPU本地内存之间建立直接数据通路。程序分配GPU显存(cudaMalloc)和DPU侧内存(DOCA内存注册),通过DMA描述符发起批量传输,统计传输耗时并计算带宽。核心是绕过PCIe,直接使用NVLink通道。

关键完整代码

#include <cuda.h>
#include <doca_dma.h>
#include <doca_mem.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define BUFFER_SIZE (256 * 1024 * 1024)  // 256MB
#define ITERATIONS 100

static double now_usec(void) {
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return ts.tv_sec * 1e6 + ts.tv_nsec / 1e3;
}

int main(void) {
    CUdevice cu_dev;
    CUcontext cu_ctx;
    CUdeviceptr d_gpu_buf;
    struct doca_dma *dma_ctx;
    struct doca_mem *doca_buf;
    struct doca_buf *src_buf, *dst_buf;
    void *host_buf;
    size_t buf_size = BUFFER_SIZE;
    int i;
    double t_start, t_end, total_bytes;

    // 初始化CUDA
    cuInit(0);
    cuDeviceGet(&cu_dev, 0);
    cuCtxCreate(&cu_ctx, 0, cu_dev);
    cuMemAlloc(&d_gpu_buf, buf_size);

    // 初始化DOCA
    doca_dma_create(&dma_ctx);
    doca_mem_create(&doca_buf);
    doca_mem_map(doca_buf, NULL, buf_size, DOCA_MEM_FLAG_HOST);
    doca_mem_get_addr(doca_buf, &host_buf);

    // 填充host端数据
    memset(host_buf, 0xAB, buf_size);

    // 创建DMA描述符
    doca_dma_buf_create(dma_ctx, doca_buf, buf_size, &src_buf);
    doca_dma_buf_create(dma_ctx, doca_buf, buf_size, &dst_buf);

    // H2D传输(GPU写入DPU)
    total_bytes = 0;
    t_start = now_usec();
    for (i = 0; i < ITERATIONS; i++) {
        doca_dma_buf_set_data(src_buf, host_buf, buf_size);
        doca_dma_buf_set_data(dst_buf, (void *)d_gpu_buf, buf_size);
        doca_dma_transfer(dma_ctx, src_buf, dst_buf, DOCA_DMA_DIRECTION_HOST_TO_DEV);
        doca_dma_wait(dma_ctx);
        total_bytes += buf_size;
    }
    t_end = now_usec();
    printf("H2D带宽: %.2f GB/s\n", total_bytes / (t_end - t_start) / 1e3);

    // D2H传输(DPU写入GPU)
    total_bytes = 0;
    t_start = now_usec();
    for (i = 0; i < ITERATIONS; i++) {
        doca_dma_buf_set_data(src_buf, (void *)d_gpu_buf, buf_size);
        doca_dma_buf_set_data(dst_buf, host_buf, buf_size);
        doca_dma_transfer(dma_ctx, src_buf, dst_buf, DOCA_DMA_DIRECTION_DEVICE_TO_HOST);
        doca_dma_wait(dma_ctx);
        total_bytes += buf_size;
    }
    t_end = now_usec();
    printf("D2H带宽: %.2f GB/s\n", total_bytes / (t_end - t_start) / 1e3);

    // 清理
    doca_dma_buf_destroy(src_buf);
    doca_dma_buf_destroy(dst_buf);
    doca_mem_unmap(doca_buf);
    doca_mem_destroy(doca_buf);
    doca_dma_destroy(dma_ctx);
    cuMemFree(d_gpu_buf);
    cuCtxDestroy(cu_ctx);
    return 0;
}

编译与运行

# 编译(需DOCA SDK环境)
gcc -o nvlink_bw nvlink_bw.c -ldoca-dma -ldoca-mem -lcuda -I/opt/mellanox/doca/include -L/opt/mellanox/doca/lib

# 运行(需root权限)
sudo ./nvlink_bw

验证方法

程序输出两个带宽数值。判定通过的参考标准:

  • H2D与D2H带宽均大于100 GB/s(NVLink 3.0单链路理论值200 GB/s,实际可达约150 GB/s)
  • 两次传输的带宽值差异小于10%(双向对称)
  • 程序无报错退出,退出码为0

若带宽远低于预期(如<50 GB/s),检查NVLink链路状态(nvidia-smi nvlink -s)确认链路为Active。

常见问题

  • cudaMemcpy无法达到NVLink峰值:标准cudaMemcpy走驱动优化路径,但DOCA DMA可更直接控制描述符,减少软件开销。若需更高带宽,可增大BUFFER_SIZE至1GB并减少ITERATIONS。
  • DOCA初始化失败:确认DPU固件已启用NVLink(mlxlink -d mlx5_0 -m查看link type为NVLink)。
  • 内存对齐:DOCA要求buffer地址256字节对齐,cudaMalloc和doca_mem_map默认满足。