GPUDirect Storage(GDS)已经能够让存储数据绕过 CPU 内存,直接进入 GPU 显存。但经典 GDS 仍有一个重要特征:I/O 请求由 CPU 侧应用代码提交。

当 GPU kernel 在执行过程中才知道下一份数据位于哪里时,控制权必须在 GPU 与 CPU 之间往返。对于图计算、向量检索、稀疏模型和超大规模数据分析,这种细粒度同步可能比数据传输本身更昂贵。

NVIDIA GPU-Initiated Data Storage,简称 GIDS,试图进一步消除这层控制瓶颈:让 GPU kernel 直接发起存储 I/O,使 GPU 不仅是数据的接收者,也是 I/O 请求的生产者。

本文介绍 GIDS 的动机、架构、工作流程、适用场景,以及它和 GDS 的本质区别。

一、为什么有了 GDS 还需要 GIDS

GDS 优化了数据路径:

CPU 发起请求
存储 ─────────────► GPU 显存
       数据不经过 CPU 内存

这已经消除了大量主机内存中转,但控制路径仍然经过 CPU。经典流程可能是:

  1. GPU 执行 kernel;
  2. GPU 产生下一批要访问的数据索引;
  3. kernel 结束或与 CPU 同步;
  4. CPU 读取结果并构造文件 I/O;
  5. CPU 调用 cuFile;
  6. 数据进入 GPU;
  7. CPU 再次启动 kernel。

如果每次请求都是大块、批量且可提前预测,这种方式通常没有问题。但如果 GPU 在计算过程中产生数以万计甚至更多的动态访问,频繁的 GPU—CPU 往返会带来:

  • kernel launch 和同步开销;
  • CPU 请求生成开销;
  • 设备到主机的控制信息传输;
  • CPU 线程调度和队列竞争;
  • GPU 等待数据时的空闲。

GIDS 的目标就是把这段 I/O 控制逻辑下沉到 GPU。

二、GIDS 是什么

GIDS 的核心定义是:

允许运行中的 GPU kernel 通过设备侧 API 主动生成和提交存储请求,并在 GPU 侧获取完成结果。

它把传统的“CPU 驱动 GPU 和存储”模式,转变为 GPU 可以自主驱动部分数据访问的模式。

理想化的数据与控制路径如下:

GPU kernel
   │ 生成 I/O 请求
设备侧存储接口
NVMe / 文件系统 / 远程存储
   │ DMA
GPU 显存
   └── GPU kernel 继续处理

与 GDS 一样,数据目标仍然是 GPU 显存,数据路径也尽可能避免 CPU 内存。但不同的是,请求不再必须由 CPU 对每个 I/O 逐一提交。

三、GIDS 改变了什么

理解 GIDS,可以把 GPU 存储 I/O 分为三个层面:

  1. 数据面:数据从存储传到 GPU;
  2. 控制面:谁决定读什么、何时读;
  3. 执行面:谁提交请求、检查完成并消费数据。

GDS 主要优化数据面,而 GIDS 进一步改变控制面与执行面。

层面传统 I/OGDSGIDS
数据经过 CPU 内存通常否通常否
CPU 决定并提交每个 I/O不一定
GPU kernel 可直接请求存储
GPU 可基于中间结果继续取数需要 CPU 协助需要 CPU 协助可以设备侧闭环

因此,GIDS 不是 GDS 的另一个名字,也不只是异步 GDS。它代表的是更加自主的 GPU I/O 执行模型。

四、GIDS 的典型工作流程

一个 GPU 发起存储访问的程序可以抽象为以下过程:

  1. CPU 完成初始化,打开文件或数据集;
  2. CPU 建立 GPU 可访问的存储上下文、映射和请求队列;
  3. CPU 启动 GPU kernel;
  4. GPU 线程根据计算结果生成读取请求;
  5. 设备侧运行时将请求送入存储栈;
  6. 存储数据通过 DMA 写入 GPU 缓冲区;
  7. GPU 检查完成状态并消费数据;
  8. kernel 继续生成后续请求,不必为每批 I/O 返回 CPU。

可以把它理解为 GPU 侧的生产者—消费者系统:

GPU 工作线程
    │ 产生地址、偏移、长度
GPU 请求队列
存储服务与设备
GPU 完成队列
GPU 工作线程继续执行

CPU 并没有从系统中完全消失。它仍然负责程序启动、资源创建、权限、文件生命周期、异常恢复和全局协调。GIDS 移除的是高频、细粒度 I/O 请求中的 CPU 代理角色

五、设备侧 API 的设计挑战

把存储 API 放进 GPU kernel 并不是简单地把 read() 编译成设备函数。GPU 的执行模型与 CPU 有根本差异。

1. 海量并发线程

一个 kernel 可能同时运行数十万个线程。如果每个线程独立发起小 I/O,请求数量会迅速超过存储设备和软件队列的承载能力。

因此 GIDS 运行时通常需要:

  • 合并相邻请求;
  • 以 warp、thread block 或 cooperative group 为单位协作;
  • 限制 outstanding I/O 数量;
  • 对请求进行排队和背压;
  • 避免热点数据被重复读取。

2. GPU 不能像 CPU 一样阻塞

CPU 线程可以睡眠等待 I/O,GPU 中大量线程若直接自旋,会浪费执行资源。设备侧 API 需要设计轮询、让出执行、异步通知或协作等待机制。

3. 文件语义复杂

POSIX 文件 API 包含权限、目录、文件描述符、页缓存和一致性语义。将完整 POSIX 模型暴露给 GPU 既复杂又低效。

因此 GIDS 更适合使用受限且面向数据路径的接口,例如:

  • 已打开对象的偏移读取;
  • 预注册的数据区域;
  • 固定大小 block 或 page;
  • key 到数据块的映射;
  • 简化的异步请求和完成队列。

4. 错误处理

存储可能返回短读、超时、介质错误或远端失败。GPU 侧代码必须能够识别错误,并决定重试、跳过、记录还是通知 CPU。

六、GIDS 的关键收益

1. 减少 GPU—CPU 控制往返

GIDS 最大的收益不是再次缩短数据路径,因为 GDS 已经能做到直接 DMA;它真正减少的是请求生成和完成处理中的控制往返。

2. 支持数据依赖型访问

GPU 可以根据当前计算结果立即决定下一次读取:

读取节点 A
GPU 计算得到邻居 B、C
   ├── 读取 B
   └── 读取 C

这类访问很难由 CPU 提前完整预测,却非常适合 GPU 自主发起。

3. 提高细粒度 I/O 的扩展性

如果请求生成本身高度并行,GPU 可以直接构造请求,避免先压缩成一批索引传给 CPU,再由 CPU 展开为存储操作。

4. 构建存储支持的超量数据模型

GPU 显存容量有限,而图、向量索引、embedding 表和科学数据可能达到 TB 甚至 PB 级。GIDS 让应用更容易把存储作为 GPU 可按需访问的后备层。

七、GIDS 的典型应用场景

1. 图计算

图遍历通常具有强数据依赖:只有访问当前顶点后,才能知道下一批邻居。GPU 线程可以直接按顶点或边的索引读取存储中的分片,减少每层遍历与 CPU 的同步。

2. 向量数据库与近似最近邻搜索

图结构的 ANN 索引会根据当前距离结果继续探索新的节点。索引无法完全驻留显存时,GPU 可按搜索路径请求存储中的向量和邻接表。

3. 超大 embedding 表

推荐系统中的 embedding 表可能远大于 GPU 显存。查表键由 GPU 上的 batch 动态产生,GIDS 可以让设备端直接发起缺失 embedding 的读取。

4. 稀疏计算

稀疏矩阵、稀疏张量和自适应网格只访问数据集的一部分,访问位置由计算结果动态决定,适合设备侧按需取数。

5. out-of-core 分析

数据库扫描、过滤、join 和科学数据分析可让 GPU 根据谓词或中间结果继续读取相关分区,而不是由 CPU 预先加载全部数据。

八、GIDS 与缓存的关系

直接访问存储并不意味着每次都应读取设备。存储延迟远高于 HBM,因此高效的 GIDS 系统通常需要多级缓存:

GPU 寄存器 / Shared Memory
          GPU HBM
     主机内存或扩展内存
       NVMe / 远程存储

GIDS 更像是在最下层缓存未命中时,为 GPU 提供一种自主补数能力。系统仍然需要:

  • 数据预取;
  • 热点缓存;
  • 请求合并;
  • page 或 block 粒度选择;
  • 数据淘汰;
  • 多 GPU 之间的缓存一致性或分片策略。

如果完全忽略局部性,让每个 GPU 线程随机读取很小的数据块,存储延迟和 IOPS 很可能压垮系统。

九、GIDS 与 GDS 的区别

对比项GDSGIDS
全称GPUDirect StorageGPU-Initiated Data Storage
优化重点存储到 GPU 的数据路径GPU 自主生成和提交存储请求
I/O 发起位置CPU 主机代码GPU kernel
数据是否经过 CPU 内存通常不经过通常不经过
是否需要逐批返回 CPU通常需要 CPU 组织请求可减少或避免
访问模式大块、批量、可提前规划细粒度、动态、数据依赖
编程接口主机侧 cuFile设备侧 API 与请求队列
软件复杂度相对成熟更复杂、更前沿
对存储 IOPS 要求以吞吐为主可能同时强调吞吐和 IOPS

最简洁的区分方式是:

GDS  = CPU 发起 + GPU 直收
GIDS = GPU 发起 + GPU 直收

十、GIDS 不等于这些技术

1. GIDS 不等于异步 GDS

异步 GDS 仍然是 CPU 侧程序调用 API,只是请求与 CUDA Stream 协同并异步完成。GIDS 的请求生成逻辑位于 GPU kernel。

2. GIDS 不等于统一内存

CUDA Unified Memory 提供统一虚拟地址和按页迁移,重点是 CPU/GPU 内存管理。GIDS 面向的是文件、块设备或对象数据的设备侧存储 I/O。

3. GIDS 不等于 GPU Direct RDMA

GPUDirect RDMA 让网卡等 PCIe 设备直接访问 GPU 显存。它可以成为远程 GIDS 数据路径的一部分,但本身不定义 GPU kernel 如何生成文件或对象存储请求。

4. GIDS 不等于传统 GPU 文件系统

某些 GPU 文件系统或研究系统允许 kernel 使用类似文件的 API,但实现可能依赖 CPU 代理线程。只有当请求控制路径真正由 GPU 发起并尽量减少 CPU 逐请求参与时,才符合 GIDS 的核心目标。

十一、GIDS 面临的工程挑战

1. 存储延迟远高于 GPU 指令延迟

GPU 擅长隐藏数百个周期的内存延迟,但 NVMe 和远程存储延迟高出多个数量级。运行时必须提供足够并发,或者让等待 I/O 的工作与其他计算交错。

2. 小 I/O 会降低效率

GPU 自然产生细粒度访问,但 SSD 和文件系统更喜欢较大的连续请求。请求聚合是 GIDS 性能的关键。

3. 一致性与写入更加复杂

读取通常比写入容易。多个 GPU 线程并发修改文件或共享对象时,需要定义原子性、顺序、可见性和崩溃恢复语义。

4. 安全与隔离

GPU kernel 不应绕过文件权限、进程隔离和地址校验。设备侧请求必须被限制在 CPU 预先授权的文件、对象或地址范围内。

5. 可观测性

传统工具主要观察 CPU 系统调用。GIDS 请求在 GPU 侧生成后,需要新的 tracing、profiling 和错误诊断能力,才能定位队列拥塞、缓存未命中和设备热点。

十二、如何选择 GDS 还是 GIDS

优先选择 GDS 的情况:

  • I/O 可以由 CPU 提前知道;
  • 数据以大块或 batch 形式读取;
  • 需要成熟的软件栈和广泛文件系统支持;
  • 主要瓶颈是 CPU 内存中转和带宽;
  • 希望较小改造现有 CUDA 应用。

考虑 GIDS 的情况:

  • 下一次访问依赖 GPU kernel 的中间结果;
  • GPU—CPU 控制同步已经成为瓶颈;
  • 数据规模远大于显存,且访问稀疏、动态;
  • 应用可以使用设备侧专用数据接口;
  • 存储系统能承受高度并发请求,并支持请求聚合。

很多系统并不是二选一,而是组合使用:CPU 通过 GDS 预取大块数据,GPU 在计算过程中通过 GIDS 处理不可预测的缓存未命中。

十三、总结

GIDS 把 GPU 存储架构从“GPU 被动接收数据”推进到“GPU 主动获取数据”。它继承了 GDS 直接数据路径的思想,又进一步减少 CPU 在细粒度 I/O 控制路径中的参与。

它最适合解决以下问题:

  • 数据访问由 GPU 中间结果动态决定;
  • 工作集远大于 GPU 显存;
  • GPU 与 CPU 的频繁同步限制扩展性;
  • 图、向量、稀疏和超量数据应用需要按需读取。

不过,GIDS 也比 GDS 更依赖请求聚合、缓存、背压和设备侧运行时。它不是让 GPU 对存储进行无限制随机读取,而是为 GPU 主导的数据系统提供一种新的 I/O 控制模型。

一句话总结:

GDS 让数据绕过 CPU,GIDS 进一步让请求也绕过 CPU。

系列导航

参考资料

  • NVIDIA GPUDirect Storage Documentation
  • NVIDIA CUDA Documentation
  • NVIDIA 关于 GPU-Initiated Networking 与 GPU-Initiated Storage 的技术资料
  • GPU-initiated storage access、GPUfs、BaM 等相关系统研究论文与技术资料