GDS(GPUDirect Storage)使GPU内存和存储之间的直接内存访问(DMA)传输具备直接数据路径,避免通过CPU进行反弹缓冲。这个直接路径增加了系统带宽,减少了CPU的延迟和负载利用率。
本指南提供了关于GPUDirect Storage(GDS)的高级概述,以帮助您为GDS启用文件系统,并提供有关文件系统功能及其与GDS的关系的一些见解。
GDS 为应用程序开发者提供如下好处:
实现 GPU 内存与存储之间的直接通道。
增加带宽,降低延迟,减轻 CPU 和 GPU 在数据传输中的负担。
减少性能影响并减少对 CPU 处理存储数据传输的依赖。
对于完全迁移到 GPU 的计算管线而言,性能得以倍增,以便 GPU 而不是 CPU 对在存储和 GPU 之间移动的数据进行首次和一次处理。
支持与其他基于操作系统的文件访问的互操作性,从而通过传统文件 IO 在设备之间传输数据,然后由使用 cuFile API 的程序进行访问。
cuFile API 及其实现提供以下好处:
一系列 API,为 CUDA 应用程序提供对本地或分布式文件和块存储的性能访问。
在传输到和从 GPU 时,性能相对于现有标准 Linux 文件 IO 得到提升。
通过消除对内存分配和数据移动的仔细专业管理,提供更大的易用性。
相对于现有的隐式文件-GPU数据传输方法,提供了更简单的 API 序列,后者需要在 CPU 和 GPU 之间对内存和数据移动进行更复杂的管理。
通用性覆盖各种存储类型,包括各种本地和分布式文件系统、块接口和命名空间系统,包括标准 Linux 和第三方解决方案。
为 GPU 应用程序提供独立于内存类型的主要文件 I/O API。
cuFile API 中的 Stream 子集提供以下好处:
异步卸载操作相对于 CUDA 流进行排序。
计算后进行 IO:GPU 内核在将数据传输到 IO 之前生成数据。
IO 后进行计算:在数据传输完成后,GPU 内核可以继续执行。
流之间存在并发性。
使用不同的 CUDA 流允许并发执行以及多个 DMA 引擎的并发使用的可能性。
cuFile 的特性可以以以下方式使用:
在存储和 GPU 内存之间的 IO 成为性能瓶颈时,cuFile 的实现可以增加吞吐量。这种情况出现在计算管线已从 CPU 迁移到 GPU,因此在与存储进行传输之前或之后,接触数据的代理在 GPU 上执行。
cuFile API 目前是显式的,适用于在存储和完全适应可用 GPU 物理内存的缓冲区之间进行读取或写入。与细粒度的随机访问不同,cuFile API 更适合用于粗粒度的流式传输。对于细粒度访问,进行内核切换并通过操作系统的底层软件开销可以摊销。
本节提供了 GDS 的功能概述。它涵盖了基本用法、通用性、性能考虑以及解决方案的范围。本文档适用于由 CPU 发出的 cuFile API。
GDS是一个以性能为中心的解决方案,因此端到端传输的性能取决于延迟开销和可实现的带宽。
以下是GDS中使用的一些术语:
明确的编程请求
立即调用存储和GPU内存之间的传输的明确的编程请求是主动的。
隐式请求
由内存引用引发的隐式请求,导致GPU向CPU引发页面缺失,可能导致CPU向存储发出反应性请求。
注意:
GDS带来的延迟改进在小型传输时明显。
通过GDS,虽然有例外情况,但零拷贝方法是可行的。此外,一旦不再需要通过CPU进行复制,数据路径将不包括CPU。在某些系统上,通过PCIe交换机直接连接本地或远程存储的数据路径相对于通过CPU进行数据传输,提供了至少两倍的峰值带宽。使用cuFile API来访问GDS技术可以实现明确和直接的传输,从而提供更低的延迟和更高的带宽。对于GPU内存和存储之间的直接数据传输,文件必须以O_DIRECT模式打开。如果文件没有以这种模式打开,内容可能会被缓存在CPU系统内存中,这与直接传输不兼容。
在实现了明确和直接在存储和GPU内存之间传输数据的可行路径之后,还存在额外的机会来提高性能。
GDS使得存储附近(NVMe或NIC)的DMA引擎能够直接将数据推送(或拉取)到GPU内存中(并从中取出)。cuFile APIs传递了一个文件的参数,文件的偏移量,要传输的大小,以及参数可以读取或写入的GPU虚拟地址。虽然累积传输是一个连续的虚拟地址范围,但在实现中可能会发生多个较小的传输。文件系统将连续的虚拟地址范围分成可能成为多个跨越多个设备的传输的部分。一个示例是RAID-0和潜在的具有非连续物理地址范围的多个页面。物理地址范围集合称为散布-聚合列表。
试图编程DMA引擎的现有操作系统无法处理GPU虚拟地址,除非有帮助。启用GDS的内核驱动程序使用回调到GDS内核模块,即nvidia-fs.ko。这些回调提供了DMA引擎所需的GPU虚拟地址,用于编程DMA引擎的散布-聚合列表。
GDS软件架构中的主要组件包括:
(来自NVIDIA的)libcufile.so,即用户级cuFile库:
实现cuFile API,这是面向应用程序的GDS API。
架构概述图中显示了cuFileRead。
有两种实现cuFile API的替代方法:
使用nvidia-fs.ko内核驱动程序。
所有使用VFS的文件系统都使用此路径。
cuFile用户库实施了一个替代实现,执行以下操作:
在CPU系统内存中使用其非页缓冲。
使用标准的POSIX调用实现。
无需使用NVFS内核驱动程序。
这是一种不享受GDS优势的兼容模式。
与GDS功能相关的两个工作流程如下图所示:

使用以下步骤完成 Workflow 1。
工作流程涉及到 cuFileRead 和 cuFileWrite 的使用。GPU虚拟地址由代理CPU系统内存地址表示。这些代理CPU系统内存地址通过Linux IO堆栈传递,并转换为特定设备的DMA总线地址。
注意:标准的pread或pwrite POSIX调用不使用以下任何步骤。
应用程序到 libcufile.so。
GPU应用程序或启用GPU的框架链接到 cuFile 库。
应用程序或框架调用 cuFile 驱动程序和IO API,如 cuFileRead 和 cuFileWrite。
在这个级别处理对齐,并可能有一些性能影响,以便缓冲区无需对齐,比如对齐到4KB页或512KB存储偏移和块大小。
libcufile.so 到 nvidia-fs.ko。
cuFile库,libcufile.so,提供这些调用并对 nvidia-fs.ko 驱动程序进行适当的IOCTL调用。
该库与CUDA用户模式驱动程序库 libcuda.so 进行交互,以满足 cuFile APIs 的流子集所需的情况。
nvidia-fs.ko 到 VFS。
内核驱动程序迭代一组必要的IO操作,并传递IO完成回调,在 kiocb->common.ki_complete 中带有回调函数值 nvfs_io_complete,该值将在步骤7中使用。
这些调用是对VFS的,VFS调用适当的底层,比如标准的Linux块系统(ext4/XFS和NVMe)或另一个供应商分布式文件系统,比如EXAScaler。
存储内核驱动程序到 nvidia-fs.ko:
通过 cuFileDriverOpen 初始化注册回调API,如 GDS 外部架构规范中的文件系统互操作性所述。使用此设计,驱动程序只需通过下面的子步骤处理GPU地址。
通过 nvidia-fs.ko APIs 完成以下任务,GPU内存地址可在Linux页映射之外的单独映射中使用:
检查DMA目标地址是否在GPU上(nvfs_is_gpu_page),并且是否需要以不同的方式处理。
使用 nvfs_dma_map_sg* 查询GPU DMA目标地址列表,这些地址代替通过VFS传递的CPU系统内存地址。
存储内核驱动程序到 DMA/RDMA 引擎:
在获取适当的GPU内存地址后,底层DMA引擎(例如,NVMe驱动程序)或靠近存储的DMA引擎(例如,NIC)可以被编程,以直接在存储(例如,NVMe或存储控制器或NIC)和GPU内存之间移动数据。
CPU系统内存中的特殊代理地址不被DMA引擎访问。
DMA/RDMA 引擎到存储内核驱动程序:
每个块传输的完成被回传给存储驱动程序层。
每次迭代的完成通过在步骤4中注册的回调传递给 nvidia-fs.ko 驱动程序。
本节提供与使用ib_verbs的用户空间RDMA进行读写相关的第二个工作流程的信息。
应用程序到 libcufile.so:
GPU应用程序或启用GPU的框架链接到 cuFile库,并调用 cuFile驱动程序和IO API。
在这个级别处理对齐,尽管可能会有一些性能影响,以便缓冲区无需对齐,比如4KB页或512KB存储偏移和块大小。
libcufile_rdma.so 到 libcufile.so:
获取RDMA信息(密钥、GID、LID等)传递给 libcufile。
libcufile.so 到供应商库:
libcufile 调用适当的供应商库回调函数,以直接在用户空间中通信Rkeys,或通过 nvidia-fs 内核回调,具体取决于供应商驱动程序的实现。
在Linux社区中,有努力增加对等设备之间的DMA本地支持的工作,这些设备可以包括NIC和GPU。在这种支持被上游化后,需要一段时间让所有用户通过发行版采用新的Linux版本。在此之前,NVIDIA将与第三方供应商合作,以启用GDS。
cuFile API及其实现是CUDA为文件IO提供支持的机制。cuFile API涵盖了CPU和GPU存储和内存之间的显式数据传输。这些API还提供了对异步性和批处理的支持,这些功能在POSIX IO中不可用。在Linux添加前面提到的功能之后,cuFile API仍然保持相关性。只有底层的实现会发生变化,但cuFile API本身不会变化。
NVIDIA为cuFile的实现重点放在分布式文件系统和已安装适当驱动程序以实现存储和GPU内存之间的直接传输的系统上,而不使用CPU中的跳转缓冲区。为了保持兼容性和更广泛的适用性,以后的实现可能会支持本地存储和隐式传输的扩展。
有效使用GDS需要充分了解GDS的部署方式、其依赖关系以及其限制和约束。
cuFile API是CUDA驱动程序和运行时API的补充,与CUDA工具包一起分发和安装。
应用程序通过包含cuFile.h并链接到libcufile.so库来访问cuFile功能。对于cuFile API的流子集,需要CUDA流参数,对于运行时和驱动程序API,形式略有不同。分别使用cudaFile和cuFile前缀。可以在头文件中完成从运行时到驱动程序API的转换。
除了libcufile.so外,使用cuFile API时不需要其他链接依赖,但存在对libcuda.so的运行时动态依赖。不需要链接依赖于其他CUDA工具库、CUDA运行时库或显示驱动程序的任何其他组件。然而,对于在添加到CUDA运行时后使用cudaFile* API的应用程序,应预期运行时依赖于CUDA运行时库。这一步骤与应用程序使用任何其他cuda* API以及在部署中使用CUDA运行时的情况一致,相关信息可以在NVIDIA开发者文档中的CUDA部署文档中找到。
注意
本文仅供信息目的,不得视为对产品的某种功能、状态或质量的保证。NVIDIA公司(“NVIDIA”)对本文中所含信息的准确性或完整性,不作明示或暗示的陈述或保证,也不对此文中的任何错误承担任何责任。NVIDIA对于使用该信息或因其使用而可能导致的侵犯第三方专利或其他权利的后果概不承担责任。本文不构成开发、发布或交付任何物料(下文定义)或代码或功能的承诺。
在下订单之前,客户应获取相关信息,并验证该信息是否当前和完整。