GPU-Initiated I/O

导言

把 SSD 数据送进 GPU,最直观的优化似乎是“绕过 CPU”。但这句话混合了两个不同问题:数据是否经过 CPU 内存,以及 I/O 请求究竟由 CPU 还是 GPU 发起。NVIDIA GDS 解决前者,BaM 进一步改变后者;代价是原本由 CPU 承担的队列管理、并发和缓存压力转移到了 GPU。

本文整理 DaMoN 2025 论文 Path to GPU-Initiated I/O for Data-Intensive Systems:先沿 Figure 1 比较五条 GPU-centric Storage 路径,再还原 BaM、GDS 与 SPDK 的实验边界。核心结论不是“GPU 发起一定更快”,而是系统应根据 CPU、GPU、PCIe、SSD 与数据复用状态,选择由谁支付 I/O 控制成本。

如果只记住一句话,可以记住这句:GPU-initiated I/O 消除的是 CPU 在请求热路径上的职责,不是 I/O 本身的资源账单。

两个容易混淆的问题

传统数据路径分两段:CPU 先把数据从 SSD 读入 CPU Memory,再把它复制到 GPU Memory。只要工作集装不进 GPU 或 CPU 内存,这条路径就会不断重复,GPU 也可能因为下一批数据尚未到达而停顿。

“GPU-centric”至少包含两条彼此独立的轴线:

  1. 控制路径:谁创建并提交 NVMe 请求,谁轮询或接收完成通知。
  2. 数据路径:Payload 是否经过 CPU Memory,最终落到 CPU Memory 还是 GPU Memory。

由此会得到几个乍看反常、但很重要的组合:

  • GPU 可以先调用文件 API,真正访问 SSD 的仍是 CPU,数据也仍经过 CPU Memory;GPUfs 与 ActivePointers 属于这一类。
  • CPU 可以负责完整 I/O 控制,数据却通过 PCIe P2P 直接进入 GPU Memory;GDS 属于这一类。
  • GPU 可以自己提交 NVMe 请求,并让 SSD 通过 PCIe P2P 把数据送入 GPU Memory;BaM 才属于这一类。

所以,“直接进入 GPU Memory”不等于“由 GPU 发起”,而“由 GPU 发起”也不等于初始化阶段完全不需要 CPU。

五条数据路径

下面的 Figure 1 是整篇论文最重要的认知锚点。图中数字表示一次读取的操作顺序;论文把第一条编号箭头从 CPU 发出的方案称为 CPU-initiated,其余称为 GPU-initiated。读图时还要同时观察 Payload 最终是否绕开 CPU Memory。

![GPU-centric Storage 五类数据路径](https://pic.shaojiemike.top/shaojiemike/2026/08/4eed6d3f624a7966fb66a669cc69dcd4.png){ width=100% }
完整保留自论文 Figure 1,CC BY 4.0。五个子图分别展示 Conventional、GPUfs/ActivePointers、NVIDIA GDS、BaM 与 GMT 的请求顺序和数据路径。

传统路径:CPU 包办控制与中转

传统方式由 CPU 发起读取,SSD 先 DMA 到 CPU Memory,CPU 再把数据复制到 GPU Memory:

1
CPU 提交读取 → SSD 写入 CPU Memory → CPU 复制到 GPU Memory

这条路径的优点是软件栈成熟,文件系统、页缓存、预处理和错误处理都由 CPU 生态承接。缺点也很直接:CPU Memory 多做一次中转,CPU 还要负责分块与搬运调度。

GPUfs 与 ActivePointers:GPU 写 I/O 代码

GPUfs 把 open()pread() 一类接口带进 GPU Kernel,对应提供 gopen()gread()。ActivePointers 在此基础上增加类似 mmap()gvmmap(),用软件地址转换和 GPU Page Cache 支持更自然的虚拟地址访问。

它们改善的是可编程性:应用不必在 CPU 侧写大量喂数代码,GPU Kernel 可以主动请求文件数据。但真正访问存储设备、把数据送入 CPU Memory,再复制到 GPU Memory 的仍是 CPU。换句话说,请求意图来自 GPU,设备 I/O 仍由 CPU 执行。

论文也提醒读者不要直接拿 GPUfs 的旧实验外推现代 NVMe 性能。GPUfs 在 2014 年使用 7200 RPM 机械硬盘;其顺序读取平均只比高度优化的 CUDA 方案慢约 5%,主要证明的是可编程性代价可控,而不是现代 SSD 上的性能上限。[^torp2025]

GDS:绕过 CPU Memory

NVIDIA GPUDirect Storage(GDS)利用 PCIe Peer-to-Peer,让 NVMe SSD 直接把数据传到 GPU Memory:

1
CPU 提交读取 → SSD 通过 PCIe P2P 写入 GPU Memory

CPU Memory 从 Payload 路径中消失了,但 CPU 仍负责文件系统、I/O 编排与请求提交。因此 Figure 1 把 GDS 归为 CPU-initiated、GPU-direct Data Path

这一区分也解释了 GDS 的资源形态:它不需要用 GPU SM 管理 NVMe 队列,却可能消耗较多 CPU。论文引用的既有研究还显示,GDS 的微基准优势不必然转化为本地 ResNet-50 训练吞吐提升;若训练本身不受存储限制,缩短存储路径主要改善 CPU 利用率,而不是总用时。[^torp2025]

BaM:GPU 敲响 NVMe Doorbell

BaM(Big accelerator Memory)把 NVMe Queue Pair 放进 GPU Memory,GPU Thread 可以直接写 Submission Queue,并敲响 NVMe Doorbell。SSD 完成读取后,再通过 PCIe P2P 把数据送入 GPU Memory:

1
GPU 提交 NVMe 请求 → SSD 通过 PCIe P2P 写入 GPU Memory

CPU 仍需在启动阶段连接 NVMe、建立 Queue Pair 和准备映射,但不再参与每次请求的发送。BaM 还提供数组式高层 API:GPU Kernel 像访问一个映射到存储设备的数组一样取数,重复 Offset 可以命中 GPU 侧软件缓存。

真正困难的是并发。一个 GPU 可能运行数千个 Warp,而 SSD 常见可用 Queue Pair 只有 32 到 128 个。BaM 让一个 Warp 共享 Queue Pair,由 Lane 0 选择队列,再把多个 Warp 轮转映射到有限的队列上。CPU 热路径被移除后,队列竞争、请求生成与缓存占用并没有消失,而是落到了 GPU。

GMT:在三层之间做选择

GMT(GPU Orchestrated Memory Tiering)在 BaM 上增加 GPU Memory、CPU Memory 与 SSD 三层层次结构:

  • SSD 与 GPU Memory 之间复用 BaM;
  • SSD 与 CPU Memory 之间使用用户态 NVMe 驱动 libnvm
  • CPU Memory 与 GPU Memory 之间对小传输使用 cudaMemcpyAsync,其余使用 Zero-copy。

GMT 的目标不是坚持所有数据都绕开 CPU Memory,而是让 GPU 根据数据位置与 Page Placement Policy 决定路径。它在高复用工作负载中最多减少 80% 的存储访问;但三层查找与迁移本身有显著开销,复用不足时甚至可能比 BaM 更慢。这个数字来自 GMT 自己的 PCIe Gen3、消费级 SSD 和 A100-40GB 设置,不能直接当作另一套系统的预期收益。[^torp2025]

把五条路径放在同一边界下,可以得到下面的对象账本:

方案 首个请求来自 SSD Payload 路径 CPU 的主要职责 GPU 的新增职责 抽象
Conventional CPU SSD → CPU Memory → GPU Memory 请求、完成、中转 消费数据 File/Buffer
GPUfs / ActivePointers GPU SSD → CPU Memory → GPU Memory 实际设备访问与中转 发出文件或映射访问意图 File / Mapped File
GDS CPU SSD → GPU Memory 文件系统与完整 I/O 控制 消费数据 File
BaM GPU SSD → GPU Memory 初始化设备与 Queue Pair 请求、完成、Queue Pair 共享、Cache Array
GMT GPU 按命中层级选择 CPU 层缓存与部分搬运 三层调度、BaM 路径与 Cache Tiered Memory

这张表揭示了一个比“少一次拷贝”更稳定的判断标准:优化路径时必须分别计算控制面由谁执行、数据面经过哪里、缓存状态放在哪里。

BaM 与 SPDK 的对照

论文接着问了一个很实际的问题:既然 BaM 是 GPU-centric 方案中的高性能代表,它和成熟的 CPU-centric 用户态 NVMe 栈 SPDK 相比,到底能做到什么程度?作者还加入 GDS,形成三种资源分工的对照:BaM 偏 GPU、GDS 偏 CPU、SPDK 使用少量 CPU 但数据先到 CPU Memory。

实验边界

这不是端到端模型训练或数据库查询,而是一组 Random Read Microbenchmark。关键条件如下:

维度 设置
Server Gigabyte G292-Z20
CPU / DRAM AMD EPYC 7402P 24-Core;8 × 32 GB DDR4-2400
GPU NVIDIA Tesla V100-16GB,PCIe Gen3
SSD 4 × 1 TB Samsung 980 PRO,SSD 为 PCIe Gen4
OS Ubuntu 20.04;为兼容 BaM/libnvm 使用 Linux 5.8
BaM Commit d20ef4a;CUDA 12.6;每设备 128 Queue Pair,Queue Depth 1024
GDS MLNX_OFED 5.8;16 个 CPU Thread;每线程 10 GB 文件
SPDK 24.09,启用 LTO;2 个逻辑 CPU,Queue Depth 256
Workload SSD 全盘顺序写入预处理后执行 Random Read;每项运行 5 次,报告均值与标准差

三套方法使用各自原生基准工具:BaM 用 nvm-block-bench,GDS 用 gdsio,SPDK 用 bdevperf。这样能测到各接口接近自身最佳状态,却也意味着线程数、Queue Depth、缓存与计时方式并不完全同构。论文把复现实验脚本放在 karlowich/bam-eval。[^torp2025]

带宽说明了什么

Figure 3 左侧使用四块 SSD,右侧只用一块。黑色虚线分别标出 PCIe 与 SSD 的理论上限;橙色 BaM、蓝色 GDS、粉色 SPDK 展示报告带宽,带 PCIe 后缀的线来自链路监控。

![BaM、GDS 与 SPDK 的 SSD 和 PCIe 带宽](https://pic.shaojiemike.top/shaojiemike/2026/08/3ce24a5b48f24187113b746ce1422c6b.png){ width=100% }
裁取自论文 Figure 3,CC BY 4.0。左图为四块 SSD,右图为一块 SSD;两张图的纵轴范围不同。

四盘实验给出三个关键观察:

  1. BaM 在 I/O Size 达到 32 KiB 后填满 V100 的 PCIe Gen3 链路。4 KiB 与 8 KiB 时,BaM 大致追平 SPDK;16 KiB 后 SPDK 继续扩展,BaM 逐渐受 GPU 侧 PCIe Gen3 限制。
  2. SPDK 展示了 SSD 阵列尚未耗尽的上限。四盘可达到约 25 GB/s,但 BaM 与 GDS 经过 V100,只能受 PCIe Gen3 ×16 的约 16 GB/s 理论链路约束。
  3. GDS 在这套机器上没有填满 PCIe。作者把主要原因推测为 EPYC 7402P 的多线程能力弱于原 BaM 论文使用的 EPYC 7702;这是基于硬件差异的解释,不是实验直接隔离出的因果结论。[^torp2025]

单盘时,BaM 与 SPDK 在 512 KiB 以前几乎重合,4 KiB 下 BaM 还略高。512 KiB 之后,橙色报告值突然超过链路上限,并不代表 SSD 真的获得百 GB/s 带宽:nvm-block-bench 开始重复请求相同 LBA,BaM 的 GPU Cache 命中后不再产生 PCIe 流量。这部分只能证明缓存生效,不能用来证明存储带宽。

Figure 3 没有证明什么

Figure 3 证明的是给定硬件、工具和 Random Read 参数下的接口带宽,不是端到端训练或查询加速比。SPDK 的数据还要从 CPU Memory 搬到 GPU,论文没有把这一步加入图中;BaM 与 GDS 的安装复杂度、GPU Kernel 可用计算资源、真实预处理和访问局部性也没有被同一张图量化。

隐藏的资源账单

带宽相近不代表成本相近。论文用 dcgmitop 观察资源利用率:

  • BaM:占满整个 GPU,同时占满 1 个逻辑 CPU;
  • GDS:没有观察到 GPU SM 利用,但占满 16 个逻辑 CPU;
  • SPDK:占满 2 个逻辑 CPU,带宽不低于另外两者,但还需要后续 CPU-to-GPU 搬运。

这正是论文最有价值的结果。BaM 可以不让 CPU 进入每次 I/O 的请求路径,却需要大量 GPU Thread 和 GPU Memory 来制造足够并发。对于昂贵的 V100/A100 一类加速器,如果计算 Kernel 本来已经接近满载,用整块 GPU 喂 SSD 未必比用两个 CPU Thread 更划算。

反过来,如果 GPU 正在等数据、CPU 又是系统瓶颈,那么把空闲 SM 用于生成 I/O 请求就可能合理。BaM 的优势不是抽象意义上的“更直接”,而是把可用但闲置的 GPU 资源换成更短的请求路径。这是基于论文结果形成的系统设计判断,而不是论文测得的通用加速比例。

扩展与拓扑

在 4 KiB Random Read 下,BaM 只要有足够多 GPU Thread,就能在一到三块 SSD 上追平 SPDK;加入第四块 SSD 后不再线性扩展,性能略低于 SPDK。论文推测,V100-16GB 无法提供足够大的 BaM Cache 和请求规模来填满四盘;原 BaM 实验使用的是 A100-80GB。

拓扑也会影响小 I/O。作者把 SSD 放在与 GPU 相同或不同的 PCIe Root Complex 下,在 Queue Depth 2 时测到 512 B 到 4 KiB 的 IOPS 差异最高约 33%;I/O Size 达到 8 KiB 后,额外路径延迟被更大的传输摊薄,差异消失。这个结果说明:GPU Direct 并没有取消 PCIe 拓扑,低并发、小 I/O 尤其需要检查 Root Complex 与 PCIe Switch 的位置。

怎样选择路径

论文没有给出一个永远获胜的实现,反而把选择条件说得更清楚了。下面是结合论文证据形成的工程判断,不是论文已经完成的端到端选型实验。

  1. 数据能常驻 GPU 或 CPU Memory:先用预取与复用减少磁盘访问,直读 SSD 不一定是主矛盾。
  2. GPU 已经满载计算:优先考虑 GDS 或 CPU-centric I/O,把 GPU 留给模型和查询 Kernel;同时确认 CPU 是否足以持续供数。
  3. GPU 因等待数据而空闲、CPU 又紧张:BaM 的 GPU-initiated 路径更值得评估,但要把 SM、显存、Queue Pair 和 Cache 纳入预算。
  4. 页面复用率高:GMT 的三层缓存可能减少 SSD 访问;复用不足时,额外层级可能得不偿失。
  5. 数据来自远端存储:论文所述 BaM 与 GMT 尚不支持 Network I/O,GDS 则已有远端路径;本地 NVMe 结论不能直接搬到对象存储或分布式文件系统。

实践中至少应同时报告以下指标,而不是只看 GB/s:

  • 端到端 Step/Query Latency 与 Throughput;
  • CPU Core、GPU SM、GPU Memory 与 Host Memory 占用;
  • SSD IOPS/Bandwidth、PCIe Traffic 与实际 Cache Hit Rate;
  • 请求大小、Queue Depth、访问局部性与并发计算重叠;
  • 初始化约束、Kernel/Driver 版本、故障回退与集成成本。

仍未解决的问题

论文最后留下的不是某个更快的固定路径,而是三个系统问题。

文件还是数组

GPUfs、ActivePointers 与 GDS 保留 File Abstraction,容易承接多文件 Dataset 与现有生态,但文件系统开销不低。BaM 与 GMT 使用 Array Abstraction,便于 GPU Kernel 直接索引,却隐含数据顺序与邻近访问;对没有邻近性的工作负载,这种抽象未必自然。

CPU 与 GPU 能否动态协作

更合理的目标可能是 Resource-aware Hybrid I/O:GPU 忙时由 CPU 发起,GPU 闲时由 GPU 发起,并根据 PCIe、NVLink 与 SSD 的实时瓶颈切换。难点在于 CPU Memory 与 GPU Memory 之间如何共享或划分 NVMe Queue,怎样保证完成顺序、Cache 一致性与故障回退。

GMT 虽然混合了 CPU、GPU 与 SSD 三层,但其 Policy 主要围绕页面位置,并没有根据 CPU/GPU 利用率动态选择请求发起者。

如何进入真实框架

BaM 的实验需要旧 Linux Kernel 与特定 GPU Driver 组合;GDS 又依赖 MLNX_OFED、NVIDIA Driver、CUDA、nvidia-peermem 的安装顺序。论文认为,GPU-centric I/O 如果长期停留在专用 Demo 或单一框架补丁中,很难进入 PyTorch、JAX 与 GPU Database 的普通数据路径。

作者提出的方向是扩展 xNVMe 一类统一接口,让 CPU-centric 与 GPU-centric Backend 共享更稳定的集成层,再用 TPC、训练和推理等复杂负载做端到端评价。这个方向截至论文发表时仍是研究建议,不是已有实现。

总结

回到开头的问题:GPU 直读 SSD 的关键从来不只是删掉一个 CPU Memory Box。

Figure 1 把控制路径和数据路径拆开后,五种方案的差异就很清楚了:GPUfs 与 ActivePointers 把文件意图放进 GPU,GDS 让 Payload 绕过 CPU Memory,BaM 让 GPU 真正提交 NVMe 请求,GMT 再把 CPU Memory 放回可选择的缓存层。

实验进一步表明,BaM 能在一到三块 SSD 上追平 SPDK,也能在大 I/O 下填满 GPU 的 PCIe Gen3;但这个性能需要整个 GPU 与大量 GPU Memory 支撑。GPU-initiated I/O 不是消灭成本,而是重新分配成本。因此下一步真正值得验证的,不是“能否完全绕过 CPU”,而是能否让 CPU 与 GPU 根据实时利用率共同承担 I/O,并在真实训练或查询中获得端到端收益。

参考资料

[^torp2025]: 论文内容与实验边界均以 Torp、Lund、Tözün 在 DaMoN 2025 发表的 ACM 版本为准。论文采用 CC BY 4.0 许可。

Author

Shaojie Tan

Posted on

2026-08-28

Updated on

2026-08-28

Licensed under