GPU Direct相关技术和原理
NVIDIA从2010年6月开始推广并使用GPU Direct技术,并持续演进。NVIDIA GPUDirect 是一系列技术集合,旨在优化 GPU 之间 (P2P) 或 GPU 与第三方设备 (RDMA) 之间的数据传输。GPU Direct 技术包括 GPUDirect Storage、GPUDirect RDMA、GPUDirect P2P 和 GPUDirect Video等。
GPUDirect Shared Memory
2010年,GPUDirect Shared Memory,支持GPU与第三方PCI Express设备(网卡、存储等)通过共享pin住的host memory实现共享内存访问从而加速通信。
从上图对比可以看到,在没有使用GPUDirect情况下,GPU需要将数据从显存复制到GPU驱动在系统内存中pin住的Memory 1,再从Memory 1复制到Memory 2,之后才能再做进一步传输。
使用了GPUDirect Shared Memory之后,内存空间实现了共享,程序pin住一段内存后,既可以用作与GPU的数据交互,减少了一次数据复制,降低了数据交换延迟。
GPUDirect P2P(Peer-to-Peer)
2011年,第二代GPUDirect技术被称作GPUDirect P2P,重点解决的是节点内GPU通信问题。两个GPU可以通过PCIe P2P直接进行数据搬移,避免了主机内存和CPU的参与。
如图所示,在同一PCI Express总线上,P2P支持GPU之间直接互访显存,不需要将数据复制到系统内存进行中转,进一步降低了数据交换的延迟。
GPUDirect RDMA(GDR)
2013年,在硬件RDMA支持的基础上,nvidia发布GPUDirect RDMA,增加了RDMA直接访问GPU显存的支持,使得第三方PCI Express设备(网卡、存储等)可以bypass CPU host memory直接访问GPU,完美的解决了服务器之间GPU卡通信问题。
接下来我们以 GPUDirect RDMA为例,探讨上述技术究竟如何落实。在上述GPUDirect RDMA的示意图中,我们只看到了GPU和网卡进行数据搬移,但是还有很多问题其实都被忽略掉了。那么,如果我们将上述通信过程细化,至少有三个关键问题需要解决。首先,为了实现CPU控制通信,数据进行网卡和GPU之间P2P搬移数据,我们要解决网卡直接读写GPU显存的问题;其次,为了实现GPU直接控制通信,还有两个问题要解决。其一,GPU如何访问通信资源?其二,GPU如何与网卡进行同步?
首先,对于网卡读写GPU显存,我们不妨先回顾一下网卡是如何访问CPU内存的。对于用户进程而言,其将一个虚拟地址传递给,通过注册内存区域获取物理页表项,然后将页表填入网卡的MTT表中。在这个过程中,内存中建立了页表项,同时,pin memory的操作对每个物理内存页的元数据进行了修改,对于网卡而言,其动作是进行虚拟地址到物理地址的转换,然后发起PCIe请求,至于物理地址映射到主机内存还是设备内存它并不关心。因此,如果我们能够解决向网卡注册GPU虚拟地址的问题,就等价于解决了网卡读写GPU显存的问题。
但是,当我们直接使用一个GPU虚拟地址进行内存注册时,会得到一个Segmetation Fault的错误。因为reg_mr注册时会陷入内核,通过调用get_user_pages获取物理页表,但对于GPU虚拟地址,CPU并不存在其对应的页表项,自然会出现错误。为了实现GPU内存的注册,需要对驱动进行一定的修改。
为了实现网卡对其它设备内存的注册,MLNX提供了一套标准的注册框架。所有的设备驱动需要向MLNX的设备管理模块进行注册,提供类似于操作系统get_ser_pages的,当网卡驱动需要对一个地址进行注册时,会对地址进行判断,然后调用相应的函数获得设备指针,{BANNED}最佳终调用设备驱动中的物理页获取函数,得到设备内存的物理地址。
这张图是Nvidia提供的与MLNX驱动框架之间的对接,具体细节就不再详细阐述了。
在新的注册框架下,通过内存注册,GPU内存在BAR空间的地址被下发到网卡,当网卡使用这些地址读写GPU显存时,GPU内部的HSHUB再进行一次,将BAR空间地址映射为实际的显存页面。这里有一个隐含的Trick,GPU的虚拟地址到物理地址也是分页寻址的。
至此,网卡能够直接读写GPU显存了,这也就意味着我们已经实现了CPU控制的GPU通信,同时数据通过PCIe P2P进行传输。接下来我们重点考虑GPU直接控制的通信方式。
GPU控制通信其实就是GPU对通信资源进行相应的操作。为了解决这个问题,我们首先从一个比较宏观的角度来看CPU进行RDMA通信时包含哪些操作。从下图中的分类可以看到,CPU在通信过程中,除了提交工作请求和同步,其它所有工作都在进行通信资源的创建时设置,而且都需要和内核进行交互。首先,GPU没有必要管理这些资源创建过程,其次,GPU上的代码也没有办法直接跟主机操作系统进行交互。因此,唯一能够且有必要由GPU控制的流程就是提交工作请求和。接下来我们重点考虑这一过程的实现。
在提交工作请求中,涉及到的通信资源有哪些呢?按照资源类型,可以分为上下文资源,队列资源和。按照资源所处的位置,又可以分为位于设备内存和主机内存。其中,部分资源是仅由网卡进行访问的,这部分资源可以留在主机内存中保持不变,例如队列基地址,通信序列号等。另一部分资源是由CPU进行读写或临时创建的,因此需要详细考虑。
当我们迁移到GPU的场景下,我们发现,GPU要访问这些通信资源,至少要考虑两个问题。
首先,doorbell资源位于网卡,GPU要控制通信必然要涉及到写doorbell,也就是GPU如何访问网卡寄存器的问题。第二个问题在于,除了刚才提到的网卡要直接操作的资源,GPU控制的这些资源,可以放在,也可以放在显存中。也就是这些通信资源放置的位置问题。
对于问题一,我们还是先参考CPU是如何访问网卡BAR空间的。在CPU的页表中其实包含两种表项,一种表项指向实际的物理内存,表项的创建发生于出现缺页异常,另一种表项指向IO设备空间,由ioremap函数创建,或者直接通过MMIO将设备内存被映射到CPU的虚拟地址空间。
对于GPU而言,使用cudaHostMemRegister和cudaHostGetDevicePointer等函数可以建立GPU虚拟地址到CPU内存空间的映射,也就是在GPU中建立到主机内存的页表项。
但在早期的CUDA版本中,如果使用ioremap映射后的地址进行注册,会引发段错误。在CUDA 4.0之后该问题被修正,PCIe BAR空间能够直接映射到GPU。GPU访问门doorbell存器的问题得以解决。
下面看通信资源放置的位置问题,放在显存中显然可以加速对通信资源的访问,但如果通信连接较大,势必会消耗大量的显存资源,因此需要进行折衷考虑。所以一般GDR按照如下方式进行内存分工:
{BANNED}最佳后一个问题就是GPU如何提交网络请求并与网卡进行同步。实际上有ibv_post_send,ibv_post_recv和ibv_poll_cq三个函数就可以了,因此,需要做的事情就是将libibverbs移植到GPU上执行。移植本身没有什么太大的难度,大部分libibverbs库的代码都可以直接在GPU上运行。至此,我们解决了全部GPU控制通信的问题。
我们对整个过程做一个小结和回顾。首先,我们修改了Mellanox和Nvidia的内存注册部分,达到了网卡直接读写GPU显存的目的,实现了CPU控制的GPUDirect;接着,我们对通信资源进行划分,结合内存映射部分的修改,达到了GPU访问通信资源的目的,{BANNED}最佳后对libibverbs进行代码移植,{BANNED}最佳终实现了GPU控制的GPUDirect功能。
GPUDirect Storage(GDS)
就像GPUDirect RDMA(远程直接内存访问)在网络接口卡(NIC)和GPU内存之间直接传输数据时改善了带宽和延迟一样,一种名为GPUDirect Storage的新技术使本地或远程存储(如NVMe或NVMe over Fabric(NVMe-oF))与GPU内存之间建立了直接数据路径。 无论是GPUDirect RDMA还是GPUDirect Storage,都通过CPU内存中的反弹(Bounce)缓冲区(Buffer)避免了额外的数据复制,并且通过在靠近NIC或存储设备的直接内存访问(DMA)引擎上移动数据,实现了直接路径进出GPU内存,而无需增加CPU或GPU的负担。对于GPUDirect Storage来说,存储位置并不重要;它可以位于机箱内部、机架内部或通过网络连接。GPUDirect Storage已经合并进了CUDA 11.4以后的版本,不需要再单独的部署安装。
传统的场景下,RDMA的IO都是从存储和Host RAM之间进行交互,如果GPU需要访问数据依然需要通过CPU RAM复制到GPU RAM。GDS使用与GPUDirect RDMA相同的技术,允许远程存储系统直接在GPU上寻址内存,并消除了CPU RAM缓冲区以及复制的问题。通过将新的内核驱动程序(nvidia-fs.ko和nvidia.ko)插入VFS堆栈来完成,该堆栈管理GPU内存地址空间并将IO定向到CPU RAM或GPU RAM的指定块。数据通过PCI总线直接在GPU和网络接口之间移动。元数据等许多操作仍将使用CPU RAM,而数据块可以直接进入GPU RAM。nvidia-fs.ko负责把GPU的内存(GPU的部分BAR空间)给到文件系统。整个软件栈如下:
更具体一点:
用户态的代码写的时候,就变成下面这样了:
其中cudaMalloc/cuFileBufRegister会从GPU内存分配,并调用nvidia-fs.ko做映射,得到一个va和gpu pa/dma、cpu pa的映射,后面再调用cuFileRead/cuFileWrite的时候把这个va传递给虚拟文件系统VFS,并通过kernel的call_write_iter/call_read_iter函数进行文件读写,之后底层sas控制器或者nvme控制器的驱动通过dma_map相关函数把这个va又转换成具体的pa,把内容读或者写到这块地址中,具体流程如下:
具体可以参考代码:
GPUDirect Async
上面我们讲通过GDS,已经可以让RDMA网卡访问GPU的内存了。但这里面其实还有两种方式,{BANNED}最佳开始GDS并不是GPU直接调用verbs接口控制数据发送和接收的,而仅仅是将内存注册到网卡,收发包的控制依然是CPU,如下图所示。
为了让GPU和网卡并行起来,CPU仍然扮演了厚重的调度角色,而且GPU空转时间比较长。以上逻辑对应代码如下:
CPU之所以区别于GPU的主要原因是:CPU更擅长完成更加复杂的任务,而不是执行计算密集的特定任务。但是NVIDIA作为GPU的领导者,它是准备改变这个现状。通过一篇论文《GPU-Initiated On-Demand High-Throughput Storage Access in the BaM System Architecture》解释了NVIDIA设计提出了一种称为BaM(Big accelerator Memory)的新系统架构。BaM 的设计目标是为 GPU 线程提供高效的抽象,以便轻松地按需、细粒度地访问存储中的海量数据集,并实现比{BANNED}最佳先进的解决方案更高的应用程序性能。
目前缺乏在不依赖 CPU 的情况下编排存储访问的 GPU 机制,为了解决这个问题,BaM 提供了一个用户级 GPU 库,其中包含 GPU 内存中的高度并发提交/完成队列,使按需访问未命中软件缓存的 GPU 线程能够以高吞吐量方式进行存储访问。为此,BaM 在 GPU 内存中配置了存储 I/O 队列和缓冲区,如下图所示,基于 GPU {BANNED}最佳近的内存映射功能将存储DB reg映射到 GPU 地址空间。
通过对比原有的存储数据访问模式(GDS),我们明显的可以看到BaM 使 GPU 线程能够直接访问存储,从而实现细粒度的计算和 I/O 重叠。首先,降低了 CPU-GPU 同步和 GPU 内核启动的频率,不用每次数据访问都需要CPU来启动和调度。其次,CPU调度的场景以大块数据任务为单位,而不是GPU真实需要的随机数据,因此带来了很大的IO放大,使用新的模式,避免了IO放大。{BANNED}最佳后使用新的方式,业务设计不需要考虑数据集大小的问题,以前需要通过应用层面的数据分块和分割来处理数据,现在就可以一套方案应对不同的数据规模。
结合BAM的技术研究,在AI的图景下,英伟达推出的是NVIDIA DOCA GPUNetIO,DOCA GPUNetIO库支持NIC和GPU之间通过一个或多个CUDA内核进行直接通信。这会从关键路径中删除 CPU。,并且将这部分IO的调度工作集成到了CUDA框架中。GPUDirect Async Kernel-Initiated NetworkGPU(CUDA 内核)可以直接与网卡交互,在 GPU 内存 (GPUDirect RDMA) 中发送或接收数据包,而无需 CPU 的干预。具体流程如下:
对应代码流程如下所示
借用一张图表示GPUDirect技术全景,如下所示:
与代理发起的通信相比,IBGDA使用GPUDirect Async-Kernel-Initiated(GPUDirect Async-KI)使GPU SM能够直接与NIC进行交互。如上图2所示,这涉及以下步骤:应用程序启动一个CUDA核函数,在GPU内存中生成数据。应用程序调用NVSHMEM操作(如nvshmem_put)与另一个PE进行通信。NVSHMEM操作使用SM创建NIC工作描述符,并直接将其写入WQ缓冲区。与CPU代理方法不同,这个WQ缓冲区位于GPU内存中。SM更新位于GPU内存中的DBR缓冲区。SM通过向NIC的DB寄存器写入来通知NIC。NIC使用GPUDirect RDMA读取WQ缓冲区中的工作描述符。NIC使用GPUDirect RDMA读取GPU内存中的数据。NIC将数据传输到远程节点。NIC通过使用GPUDirect RDMA向CQ缓冲区写入来通知GPU网络操作已完成。正如所示,IBGDA从通信控制路径中排除了CPU。在使用IBGDA时,GPU和NIC直接交换通信所需的信息。为了通过SM访问时提高效率,WQ和DBR缓冲区也被移到GPU内存中,同时保留NIC通过GPUDirect RDMA的访问能力。
当然,以上只是GPUDirect Async(GDA)在GDS场景的应用流程,同样GDA也可以用在GDR等场景。将GPUDirect Async引入NVSHMEM中的实现成为InfiniBand GPUDirect Async(IBGDA)的通信方法,该方法建立在GPUDirect Async系列技术之上。它使GPU能够在发出节点间NVSHMEM通信时绕过CPU,而无需对现有应用程序进行任何更改。这为使用NVSHMEM的应用程序带来了吞吐量和扩展的显着改进。
Nvidia Magnum IO
有了上面这些网络加速、IO加速技术之后,Nvidia更进一步提出了Magnum IO技术,把这些都打包在了一起。不得不说Nvidia在造概念和名词上真是老手。