GPU虚拟化技术总结
随着AI、加密货币等技术的发展,GPU在市场上“一卡难求”,这也导致GPU售价非常昂贵,而且供货周期也不稳定。对于有GPU需求的企业用户,不但需要思考GPU卡的选型,同时需要考虑怎样尽可能高效利用GPU资源。为了提高GPU资源利用率,很多人选择对GPU进行虚拟化。下面就对当今的GPU虚拟化技术进行一下总结和介绍。
目前,在GPU虚拟化大类上一般分为三种:软件模拟、直通独占(类似网卡独占、显卡独占)、直通共享(如vGPU、MIG)。
全软件模拟(sGPU)
{BANNED}中国第一种,全软件模拟(eg sGPU)。这种方式主要通过软件模拟来完成,类似早期qemu用纯软件模拟网卡一样,主要原理就是在Host操作系统层面上建立一些比较底层的API,让Guest看上去好像就是真的硬件一样。这种方式的优点是比较灵活,而且并不需要有实体GPU,当然没有实体GPU的缺点就很明显了,模拟出来的东西运行比较慢。另外就是这个方式并没有官方研发,因此产品质量肯参差不齐。软件模拟虚拟化就不讲了,因为真实场景太少,做做实验还将就用,几乎没法用在生产环境,毕竟性能损失太多。
GPU直通(pGPU)
第二种,直通独占 (即pGPU) 。其实严格意义上说,这种方式不能称之为虚拟化。直通是{BANNED}最佳早出现,即技术上{BANNED}最佳简单和成熟的方案。直通主要是利用PCIe Pass-through技术,将物理主机上的整块GPU显卡直通挂载到虚拟机上使用,与市场网卡直通的原理类似,但是这种方式需要主机支持IOMMU,VT-d对IOVA的地址转换使得直通设备可以在硬件层次直接使用GPA(Guest Physical Address)地址。
直通模式的技术方案与其他任何PCI直通没有任何区别。由于GPU的复杂性和安全隔离的要求,GPU直通技术相对于任何其他设备来说,会有额外的PCI 配置空间模拟和MMIO的拦截(参见QEMU VFIO quirk机制)。比如Hypervisor或者Device Module 不会允许虚拟机对GPU硬件关键寄存器的完全的访问权限,一些高权限的操作会被直接拦截。大家或许已经意识到原来直通设备也是有MMIO模拟和拦截的。这对于我们理解GPU 半虚拟化很有帮助。
PCI 直通的技术实现:所有直通设备的PCI 配置空间都是模拟的。而且基本上都只模拟256 Bytes的传统PCI设备,很少有模拟PCIE设备整个4KB大小的。而对PCI设备的PCI bars则绝大部分被mmap到qemu进程空间,并在虚拟机首次访问设备PCI bars的时候建立EPT 页表映射,从而保证了设备访问的高性能。想了解细节的同学可以去参考Linux kernel document: vfio.txt
PCI 直通架构
因为直通方式的性能损耗{BANNED}最佳小,各大公用云厂商广泛采用直通模式,而且直通方式相当于虚拟机独享GPU,因此硬件驱动无需修改。另外因为直通模式没有对GPU功能性做阉割,因此大多数功能可以在直通模式下无修改支持。但GPU 直通的缺点是一张GPU卡不能同时直通给多个虚拟机使用,相当于虚拟机独占了GPU卡。如果多个虚拟机需要同时使用GPU,需要在服务器中安装多块GPU卡,分别直通给不同的虚拟机使用。而且直通GPU的虚拟机不支持在线迁移。
为了应对GPU直通不能共享GPU的限制,第三种方式直通共享的虚拟化方式出现了。这也是目前GPU厂商主推的技术趋势,也是我们这篇文章结束的重点。在介绍直通共享方案前,首先介绍一下直通共享的原理。
GPU虚拟化技术的原理
如下图所示,是GPU的典型使用架构,从应用层到底层硬件,每一层都可以通过相关的技术实现GPU虚拟化。
按照CUDA 计算 stack和OpenGL 渲染 Stack两个场景划分,又可以分为不同的虚拟化技术。如下图所示。
CUDA 计算 stack
OpenGL 渲染 Stack
一个典型的 GPU 设备的工作流程是:
1. 应用层调用 GPU 支持的某个 API,如 OpenGL 或 CUDA
2. OpenGL 或 CUDA 库,通过 UMD (User Mode Driver),提交 workload 到 KMD (Kernel Mode Driver)
3. KMD 写 CSR MMIO,把它提交给 GPU 硬件
4. GPU 硬件开始工作... 完成后,DMA 到内存,发出中断给 CPU
5. CPU 找到中断处理程序 —— KMD 此前向 OS Kernel 注册过的 —— 调用它
6. 中断处理程序找到是哪个 workload 被执行完毕了,...{BANNED}最佳终驱动唤醒相关的应用
可以看出,从 API 库开始,直到 GPU 硬件,Stack 中的每一个阶段,都有被截获、转发的可能性。下面主要以CUDA stack为例介绍。
API转发
在介绍API转发方案前首先看一下CUDA的整体架构。如下图所示:
CUDA 开发者使用的,通常是 CUDA Runtime API,它是 high-level 的;而 CUDA Driver API 则是 low-level 的,它对程序和 GPU 硬件有更精细的控制。Runtime API 是对 Driver API 的封装。
CUDA Driver 即是 UMD(User Mode Driver),它直接和 KMD(Kernel Mode Driver) 打交道。两者都属于 NVIDIA Driver package,它们之间的 ABI,是 NVIDIA Driver package 内部的,不对外公开。英伟达软件生态封闭包括:
l 无论是 nvidia.ko,还是 libcuda.so,还是 libcudart,都是被剥离了符号表的
l 大多数函数名是加密替换了的
以 nvidia.ko 为例,为了兼容不同版本的 Linux 内核 API,它提供了相当丰富的兼容层,于是也就开源了部分代码:
其中这个 26M 大小的、被剥离了符号表的 nv-kernel.o_binary,就是 GPU 驱动的核心代码,所有的 GPU 硬件细节都藏在其中。
从CUDA架构上看,API转发分为用户态截取和内核态截取。用户层API截取是一种在用户态实现的虚拟化技术。通过创建一个函数库(如libwrapper),它能够拦截用户的API调用,解析后转发到实际的GPU驱动,从而实现资源的隔离和调度。这种方法的优势在于对用户代码零侵入,且部署灵活,无论是在裸机还是容器化环境中都易于实现。内核层拦截通过在操作系统内核空间实现模块,模拟GPU设备文件,从而拦截对GPU驱动的访问。这种方法可以有效防止用户篡改,提高安全性,但由于NVIDIA驱动的闭源性质,技术实现难度较高。
从虚拟机和host的调用关系上看,API转发分为被调方和调用方,两方对外提供同样的接口(API),被调方API实现是真实的渲染、计算处理逻辑,而调用方API实现仅仅是转发,转发给被调方。其核心架构示意如下图:
l 在GPU 用户态API层的转发,业界有针对OpenGL的AWS Elastic GPU,OrionX,有针对CUDA的腾讯vCUDA,瓦伦西亚理工大学rCUDA;
l 在GPU内核驱动层的转发,有针对CUDA的阿里云cGPU和腾讯云qGPU。
如下图是阿里云cGPU架构,相比其他方案:通过一个内核驱动,为容器提供了虚拟的GPU设备,从而实现了显存和算力的隔离;通过用户态轻量的运行库,来对容器内的虚拟GPU设备进行配置。阿里云异构计算cGPU在做到算力调度与显存隔离的同时,也做到了无需替换CUDA静态库或动态库;无需重新编译CUDA应用;CUDA,cuDNN等版本随时升级无需适配等特性。
如下图是腾讯云的qGPU(qGPU == QoS GPU)架构:
API转发的灵活性以及支持GPU虚拟化的数量不受限制,往往应用于serverless 容器场景。
API转发方案的优点是实现了1:N,并且N是可以自行设定,灵活性高。同时不依赖GPU硬件厂商。但缺点复杂度极高。同一功能有多套 API(渲染的 DirectX 和 OpenGL),同一套 API 还有不同版本(如 DirectX 9 和 DirectX 11),兼容性非常复杂。并且功能不完整,如不支持媒体编解码,并且,编解码甚至还不存在业界公用的 API。
硬件虚拟化
从硬件虚拟化角度又可分为Hardware Partition即空分和Time Sharing即时分。
Hardware Partition
PCIe SR-IOV
前文我们提到了GPU直通,这种通过PCIe直通GPU的方式只能支持1:1,不支持GPU资源分隔。于是为了解决这个问题,PCIe SR-IOV(Single Root Input/Output Virtualization)出现。AMD 从 S7150 开始、英伟达从 Turing 架构开始,数据中心 GPU 都支持了 SR-IOV。但是它不是 NIC 那样的 SR-IOV,它需要 Host 上存在一个 vGPU 的 device-model,来模拟从 VM 来的 VF 访问。
基于PCIe SR-IOV的GPU虚拟化方案,本质是把一个物理GPU显卡设备(PF)拆分成多份虚拟(VF)的显卡设备,而且VF 依然是符合 PCIe 规范的设备。核心架构如下图:
SRIOV的本质是把一个PCI卡资源(PF)拆分成多个小份(VF),这些VF依然是符合PCI规范的endpoint设备。由于VF都带有自己的Bus/Slot/Function号,IOMMU/VT-d在收到这些VF的DMA请求的过程中可以顺利查找IOMMU2nd Translation Table从而实现GPA到HPA的地址转换。这一点与GVT-g和Nvidia的GRID vGPU(分片虚拟化)有本质上的区别。GVT-g与Nvidia GRID vGPU并不依赖IOMMU。其分片虚拟化的方案是在宿主机端实现地址转换和安全检查。应该说安全性上SRIOV方法要优于GVT-g和GRID vGPU,因为SRIOV多了一层IOMMU的地址访问保护。SRIOV代价就是性能上大概有5%左右的损失(当然mdev分片虚拟化的MMIO trap的代价更大)。由于SRIOV的优越性和其安全性,不排除后续其他GPU厂商也会推出GPU SRIOV的方案。
这里要说一点,AMD GPU SRIOV从硬件的角度看就是一个对GPU资源的分时复用的过程(严格意义上说AMD的实现其实是一种Time Sharing),并不是真的资源切割,因此其运行方式也是与GPU分片虚拟化类似。
PCIe SR-IOV的有点就是真正实现了真正实现了1:N,一个PCIe设备提供给多个VM使用;但缺点是灵活性较差,无法进行更细粒度的分割与调度;并且不支持热迁移。
NVIDIA MIG
上文介绍的基于 SR-IOV 硬件虚拟化技术的 GPU,VF 的数量比较固定,且每个 VF 获得的资源是均分的、定额的。将这些 VF 透传给虚拟机后,由于各个虚机的 workload 不同,就可能出现某些 VF 的资源不够用,而另一些 VF 的资源用不完的情况。
作为业界一哥的 Nvidia,自 2020 年的 Ampere 微架构(比如 A100)开始支持一种叫做 MIG (Multi Instance GPU) 的技术,GPU可以被安全分割为{BANNED}最佳多七种独立的GPU实例,服务于CUDA应用,从而使多个用户能各自拥有独立的GPU资源,以达到{BANNED}最佳佳利用率。
对于有多租户需求的云服务提供商(CSP),MIG技术确保了单一客户端的行为不会干扰其他客户端的运行或调度,同时增强了对各个客户的安全隔离性。
在MIG模式下,每个实例所对应的处理器具备独立且隔绝的内存系统访问路径——片上交叉开关端口、L2缓存分段、内存控制器以及DRAM地址总线均会专一地分配给单个实例。这使得即便有其他任务在对其自身缓存进行大量读写操作或已使DRAM接口达到饱和的情况下,单个工作负载仍能获得稳定、可预期的执行速度和延迟时间,同时保证相同水平的L2缓存分配与DRAM带宽资。
MIG能够对GPU中的计算资源(包括流式多处理器或SM,以及诸如拷贝引擎或解码器之类的GPU引擎)进行划分,从而为不同的客户(例如虚拟机、容器或进程)提供预设的服务质量(QoS)保障及故障隔离机制。
运用MIG技术后,用户可以像管理实体GPU一样查看和安排在新建的虚拟GPU实例上的任务。MIG兼容Linux操作系统,支持基于Docker Engine的容器部署,同时亦对接Kubernetes及建立于Red Hat虚拟化和VMware vSphere等支持的虚拟机管理程序之上的虚拟机。
Multi-Instance,那一个 instance 具体是什么呢?
划分方式多种多样,在不牺牲隔离性的同时提高了应用的灵活性:
不过 slice 的划分不是随心所欲的,不是你想切多细就能切多细(想起了涡虫),还是受到底层硬件设计的限制:
MIG 技术可视作是基于传统 SR-IOV 的演进和创新,一个 instance 大致可对应一个 PCIe VF。以 MIG 为代表的硬件资源划分主要在空间上(spatial),同一物理 GPU上的各个 vGPU 获得的资源是专属的(dedicate),可以获得良好的并行性。
GPU分片虚拟化
技术上讲GPU分片虚拟化,就是指基于VFIO mediated 透传框架的GPU虚拟化方案。该方案由NVIDIA提出,并联合Intel一起提交到了Linux 内核4.10代码库,该方案的kernel部分代码简称mdev模块。随后RedHat Enterprise,CentOS{BANNED}最佳新的发行版花了不少力气又back porting到了3.10.x内核。所以如果目前采用{BANNED}最佳新的RedHat发行版(企业版或者CentOS 7.x)等都已经自带mdev模块。而如果采用Ubuntu 17.x以后版本的话,不但自带mdev功能,连Intel GPU驱动(i915)也已经更新到支持vGPU。无需任何代码编译就可以直接体验vGPU虚拟机功能。
那么什么叫mediated透传呢? 它与透传的区别是什么呢? 一句话解释:把会影响性能的访问直接透传给虚拟机,把性能无关和功能性的MMIO访问做拦截并在mdev模块内做模拟(是不是和网卡虚拟化vDPA的思想一样)。
如果熟悉网卡虚拟化vDPA技术的同学一定都对mdev不陌生,没错,mdev{BANNED}最佳早就是为GPU分片虚拟化而生的。另外要说明一点关于vGPU这个词的含义,它有两层含义,通用的含义是只通过虚拟化技术虚拟出的虚拟GPU都可以称作vGPU,但是有时也特指NVIDIA GRID vGPU技术。
GPU分片虚拟化的方案被NVIDIA与Intel两个GPU厂家所采用。NVIDIA GRID vGPU系列与Intel的GVT-g(XenGT或KVMGT)系列。
当然,只有内核的支持还不够,需要加上QEMU v2.0 以后版本,以及Intel或者NVIDIA自带的GPU mdev驱动(也就是对GPU MMIO访问的模拟),那么GPU分片虚拟化的整个路径就全了。而GPU厂家的mdev驱动是否开源取决于自己。按照一贯的作风,Intel开源了其绝大部分代码,包括{BANNED}最佳新的基于mdev的GPU热迁移技术,而NVIDIA也保持其一贯作风:不公开。
GPU分片虚拟化看起来整个框架就如下图一样(以KVMGT作为例子):
可以从上图看到,vGPU的模拟是通过kvmGT(Intel)或者NVIDIA-vgpu-vfio(NVIDIA)来完成的。该模块只模拟对MMIO的访问,也就是功能性,不影响性能的GPU寄存器。而对GPU aperture和GPU graphic memory则通过VFIO的透传方式直接映射到VM内部。
值得注意的是一般Pass through的方式都依赖IO MMU来完成GPA到HPA的地址转换,而GPU的分片虚拟化完全不依赖IO MMU,也就是说其vGPU的cmd提交(内含GPA地址)并不能直接运行于GPU硬件之上,至少需要有一个GPA到HPA的翻译过程。该过程可以通过host端的cmd扫描来修复(KVM GT),NVIDIA GRID vGPU每一个上下文有其内部页表,会通过修改页表来实现。
需要注意的是,NVIDIA GRID vGPU是收费的,所以企业用户要去官网购买license才可以使用。
下面是MIG和vGPU的一个能力对比:
Time Sharing
NVIDIA MPS
MPS (NVIDIA Multi-Process Service, )是NVIDIA公司为了进行GPU共享而推出的一套方案,由多个CUDA程序共享同一个GPU context,省去了 Context Switch 的开销,也在 Context 内部实现了算力隔离,从而达到多个CUDA程序共享GPU的目的。同时,在Volta GPU上,用户也可以通过CUDA_MPS_ACTIVE_THREAD_PERCENTAGE变量设定每个CUDA程序占用的GPU算力的比例。
该方案和PCIe SR-IOV方案相比,配置很灵活,并且和docker适配良好。MPS基于C/S架构,配置成MPS模式的GPU上运行的所有进程,会动态的将其启动的内核发送给MPS server,MPS Server借助CUDA stream,实现多个内核同时启动执行。
但该方案的一个问题在于,各个服务进程依赖MPS,一旦MPS进程出现问题,所有在该GPU上的进程直接受影响,需要使用Nvidia-smi重置GPU 的方式才能恢复。
Time-Slicing GPU
NVIDIA GPU 支持基于 Engine 的 Context Switch。不管是哪一代的 GPU,其 Engine 都是支持多任务调度的。一个 OS 中同时运行多个 CUDA 任务,这些任务就是在以 Time Sharing 的方式共享 GPU。这种虚拟化方式就和 CPU 虚拟化就比较像了。vGPU 被串行地调度执行,一个 vGPU 需等待其他 vGPU 让出物理 GPU,但当它获得物理 GPU 时,其对 GPU engine 的使用是 exclusive 的。
这种方式非常适合容器runC的场景,为此NVIDIA还提供给了对k8s的支持:
总结
以上就是GPU目前的场景虚拟化技术,其实按照“空分”和“时分”只是一个大概分类,很多技术,如分片虚拟化其实是两者的结合。下图是NVIDIA GPU虚拟化的发展实际线:
以及NVIDIA GPU虚拟化技术对比