GPU Direct Storage (GDS) 基础
GDS 是 GPUDirect 家族的存储成员,允许 GPU 通过 PCIe 直接读写 NVMe 存储,绕过 CPU 内存中转。基于 RTX 5090 + CUDA 12.8 (GDS 1.13.1) 环境。
1. GPUDirect 家族全景
| 技术 | 解决的问题 | 物理路径 |
|---|---|---|
| GPUDirect P2P | GPU↔GPU 直通 | PCIe / NVLink |
| GPUDirect RDMA | GPU↔网卡 直通 | PCIe + InfiniBand/RoCE |
| GPUDirect Storage | GPU↔NVMe 直通 | PCIe + NVMe |
| GPUDirect Video | GPU↔采集卡 直通 | PCIe |
GDS 是最晚加入的成员 (CUDA 11.4+)。它解决一个具体问题:训练数据从 NVMe 加载到 GPU 时,传统路径是 NVMe → CPU RAM → GPU,GDS 是 NVMe → GPU,省掉 CPU 中转。
传统: NVMe ──PCIe──▶ CPU RAM ──PCIe──▶ GPU VRAM (2 次 PCIe 传输)
GDS: NVMe ──PCIe──────────▶ GPU VRAM (1 次 PCIe 传输)
为什么这事重要? 大模型训练的 checkpoint 文件动辄数百 GB。传统路径下,每个字节都要:NVMe DMA → CPU 内存 → CPU memcpy → pinned buffer → GPU DMA。CPU 全程参与内存拷贝,而 GPU 的 PCIe 带宽(~56 GB/s)可能被 CPU 内存带宽(~100 GB/s DDR5)限制到一半。GDS 让 NVMe 控制器直接通过 PCIe 把数据 DMA 到 GPU VRAM——GPU 的 GDDR7 控制器接收数据,不需要 CPU 侧的中间人。不仅少了一次 PCIe 穿越,还释放了 CPU 做更有意义的计算。
2. 前置条件
# 1. CUDA 11.4+
nvcc --version
# 2. GDS 包安装
ls /usr/local/cuda/gds/README
# 3. cuFile 库存在
ls /usr/local/cuda/targets/x86_64-linux/lib/libcufile.so*
# 4. NVMe 驱动器
lsblk | grep nvme
# 预期: 至少有一个 nvmeXnY 设备
# 5. nvidia-fs.ko 内核模块
lsmod | grep nvidia_fs
# Legacy 模式 (pre-CUDA 12.8): 必须 + MOFED 补丁 NVMe 驱动
# Modern 模式 (CUDA 12.8+ + Linux ≥ 6.2): 可选,上游 pci_p2pdma 替代
# 6. 硬件/系统前提条件
# - ACS 旁路: PCIe Switch 下行端口关闭 ACS 转发限制
# - IOMMU=pt: 内核启动参数 iommu=pt (或 intel_iommu=pt / amd_iommu=pt)
# - O_DIRECT: 文件打开必须使用 O_DIRECT 标志
2.1 本环境确认
ls /sys/block/ | grep nvme
# nvme0n1 nvme1n1 nvme2n1 ← 3 块 NVMe 块设备
cat /usr/local/cuda/gds/README | head -1
# GDS Version: 1.13.1
ls /usr/local/cuda/targets/x86_64-linux/lib/libcufile.so*
# libcufile.so libcufile_rdma.so ← cuFile + RDMA 支持
GDS 要求 NVMe 格式化为文件系统并挂载(如 ext4/xfs +
mount),仅作为块设备存在 (/dev/nvme*) 无法直接使用cuFileRead。
3. cuFile API 基础
GDS 通过 libcufile 暴露 API,核心流程:
#include <cufile.h>
// 1. 打开 NVMe 文件 (标准 POSIX open,需 O_DIRECT)
int fd = open("/mnt/nvme/data.bin", O_RDONLY | O_DIRECT);
// 2. 注册文件为 GPU 可直接访问
CUfileDescr_t desc = {};
desc.type = CU_FILE_HANDLE_TYPE_OPAQUE_FD;
desc.cookie = (void*)(uintptr_t)fd;
CUfileHandle_t fh;
cuFileHandleRegister(&fh, &desc);
// 3. 注册 GPU buffer
cuFileBufRegister(gpu_buffer, size, 0);
// 4. GPU 直接读取 NVMe(异步)
cuFileRead(fh, gpu_buffer, size, offset, 0);
// 5. 清理
cuFileBufDeregister(gpu_buffer);
cuFileHandleDeregister(fh);
close(fd);
编译:
nvcc -I/usr/local/cuda/include \
-L/usr/local/cuda/targets/x86_64-linux/lib \
-lcufile \
-o gds_read gds_read.cu
4. 性能对比:传统 vs GDS
| 路径 | PCIe 穿越次数 | 有效带宽 | CPU 占用 |
|---|---|---|---|
| NVMe → CPU → GPU | 2 | ~½ 理论值 | 高 (memcpy) |
| NVMe → GPU (GDS) | 1 | ~理论值 | 低 (DMA only) |
但由于 PCIe 带宽共享(GPU 和 NVMe 通常在同一 PCIe root complex 下),实际加速取决于拓扑。如果 NVMe 和 GPU 连接到同一 PCIe switch 且支持 P2P,加速最明显。
5. 常见限制
| 限制 | 说明 |
|---|---|
| NVMe 必须直接挂载 | 网络存储 (NFS/Ceph) 不支持 GDS |
| 文件对齐 | 偏移和大小需对齐到 4 KB |
| nvidia-fs 内核模块 | Legacy (pre-CUDA 12.8) 必须 + MOFED 补丁 NVMe 驱动;Modern (CUDA 12.8+) 可选 |
| 消费级 GPU | 支持 GDS 但性能低于数据中心 GPU(BAR1 窗口限制) |
| PCIe topology | GPU 和 NVMe 需在同一 PCIe domain 且支持 P2P |
6. 适用场景
| 场景 | GDS 适用性 | 说明 |
|---|---|---|
| 大模型 checkpoint 加载 | ✅ 强推荐 | 数十 GB 文件直接加载到 GPU |
| 训练数据流式读取 | ✅ 推荐 | 绕过 CPU RAM 中转 |
| KV Cache 磁盘卸载 | ✅ 适用 | 与 LMCache 等方案配合 |
| 小文件随机读取 | ❌ 不适用 | 每次 open/register 开销过大 |
| 已有 page cache 热点 | ⚠️ 可能更慢 | 绕过 cache 反而增加延迟 |
7. 快速测试脚本
cat > test_gds.sh << 'EOF'
#!/bin/bash
echo "=== GDS Environment Check ==="
echo "CUDA: $(nvcc --version 2>/dev/null | grep release)"
echo "GDS: $(cat /usr/local/cuda/gds/README 2>/dev/null | head -1)"
echo "NVMe devices:"
lsblk 2>/dev/null | grep nvme || echo " No NVMe found"
echo "libcufile:"
ls /usr/local/cuda/targets/x86_64-linux/lib/libcufile.so* 2>/dev/null || echo " Not found"
echo "nvidia-fs module:"
lsmod 2>/dev/null | grep nvidia_fs || echo " Not loaded (optional)"
EOF
chmod +x test_gds.sh
./test_gds.sh