其他
怎样节省 2/3 的 GPU?爱奇艺 vGPU 的探索与实践
(1)vGPU ;(2)MPS。
接下来我们将简单对比下两种方案。
Nvidia的vGPU方案采用虚拟化的技术,基于 SR-IOV 进行 GPU 设备虚拟化管理,在驱动层提供了时间分片执行的逻辑,并做了一定的显存隔离,这样在对显卡进行初始化设置的时候就可以根据需求将显卡进行划分。其中时间分片调度的逻辑可以是按实例均分,或者是自定义比例,显卡的显存需要按照预设的比例进行划分。Nvdia的vGPU方案在实施中有下面两点限制:
(1)vGPU划分完成之后,如果要改变这种预定义的划分,需要重启显卡才能生效,无法做到不重启更改配置。
(2)其方案基于虚机,需要先对 GPU 物理机进行虚拟化之后,再在虚拟机内部署容器,无法直接基于物理机进行容器化的调度,另外 vGPU 方案需要收取 license 费用,增加了使用成本。
Nvidia的MPS方案是一种算力分割的软件虚拟化方案。该方案和vGPU方案相比,配置很灵活,并且和docker适配良好。MPS 基于C/S架构,配置成MPS模式的GPU上运行的所有进程,会动态的将其启动的内核发送给MPS server,MPS Server借助CUDA stream,实现多个内核同时启动执行。除此之外,MPS 还可配置各个进程对 GPU 的使用占比。
该方案的一个问题在于,各个服务进程依赖 MPS,一旦 MPS 进程出现问题,所有在该GPU上的进程直接受影响,需要使用 Nvidia-smi 重置GPU 的方式才能恢复。
01
我们方案的主要特点是配置灵活,和K8S能够有机的进行结合,按需实时分配用户所需要的vGPU实例,同时尽可能的让物理GPU实例能够充分的被共享,实现资源的最大化利用。
在完成方案的设计之后,我们对整体进行了效果的评估,测试这种隔离和共享对应用性能的影响。即对于单一进程来说,需要保证:首先它不会使用超过其被分配的算力大小,其次隔离本身不应该对于 GPU 算力有过多损耗,第三是多个进程同时共享的时候,与其单独运行时相比,不应有太大的性能偏差,即共享可以有效避免进程之间的干扰。
针对以上标准,我们对 GPU 虚拟共享方案进行了性能测试,结果如图2所示。
第一个测试是单进程算力隔离后性能的评估。物理 GPU上只运行单一进程,但配置了三次,分别为100%,50%和10% 算力时,其性能和该程序独立运行时的比例关系。纵轴为达到无虚拟化运行时性能的百分比,横轴为进行的单元测试用例,区域相同的颜色表示该组测试用例为同一CUDA kernel,但是不同的运行参数。其中图内的绿点,蓝点,和红点分配是500多个测试用例在各自算力分配的情况下得到的性能,和完全没有算力分割且独占GPU时运行的性能的相对比值。另外曲线是这些独立点的数值在全体维度上做了一个平滑,以更好的进行可视化的对比。
第二个和第三个测试分别用不同算力配比对两个GPU进程进行相互干扰实验。如第二个两个进程分别配置为50% 算力,绿点为两个GPU进程性能平均值,而红色曲线为这些绿点的平滑曲线。该曲线和第一个测试中50%算力的曲线对比相差无几,这就说明了我们方案中配置50%算力时同时运行相互干扰是几乎可以忽略的。第三个为一个配置为70%,另外一个配置为30%算力,同样可以和第一个测试中的独立分配70%/30%时各自的曲线进行对比。
测试结果表明了方案可以将GPU相互干扰控制在合理的范围之内。服务上线后内部统计显示,平均 100+ 深度学习容器服务可以共享的部署在 35 张物理 GPU 之上,并且做到应用相互之间无影响;对于单张GPU物理卡,平均承载的服务数量从 1 变为了3;同时GPU的平均利用率也提升了2 倍以上。
02
2.1显存隔离
1)第一是模型的CUDA kernel context,可类比于CPU程序中的text段,提供给CUDA kernel执行的环境,这是一项刚需,没有充足的显存,kernel将无法启动,且context的大小随着kernel的复杂程度有增长,但在整体模型显存需求中是最小的一部分。
2)第二部分来自于模型训练得出的一些参数,如卷积中的weight和bias。
3)第三部分来自于模型在推理过程中的临时存储,用于储存中间的计算结果。
对于一般的模型来说,基本都不需要占用整个GPU的显存。但是这里有一个例外,Tensorflow框架默认分配所有GPU的显存来进行自己的显存管理。当然Tensorflow框架有相应的选项可以屏蔽该行为,但是对于平台来说,要让每个用户修改 TF 的配置为屏蔽该行为,就不太可行。
为应对这一问题,一个巧妙的方法可以在不需要应用开发者参与的情况下,让Tensorflow的部署应用只分配它所需的显存大小而不出现问题。该方法即API动态拦截。Tensorflow之所以可以知道当前GPU的剩余显存,是通过cuDeviceTotalMem/cuMemGetInfo这两个CUDA library API。通过LD_PRELOAD的方式,在的钩子so中实现这两个API,那么Tensorflow执行的时候,link首先会调用的是的API实现,而不是CUDA的,这样就可以动态的修改这两个API的返回结果,如这里想做的,将特定Tensorflow应用的显存配额限制在其申请数值。
在系统实现的过程中,还对cuMemAlloc/cuMemFree做了同样的拦截,目的是为了能够对同容器中的多个GPU进程进程统一管理。当多个GPU进程分配显存之和超过其配额时,可以通过cuMalloc来返回显存不足的错误。容器内显存配额管理是通过share mem来做的。图3展示了显存隔离和分配的整个流程。
2.2算力隔离
在知道了GPU的底层结构,以及CUDA的设计原理之后,可以就如何算力虚拟化来做一下初步设想。既然一些模型无法完全利用GPU的全部算力,那么何不削减其占用的SM个数,使得空闲下来的SM可以为其他GPU程序所用?
这样的想法是好的,但是一些限制阻止了这种优化的实现。GPU程序的执行,是通过kernel的片段来具体实施,在CPU侧launch了 kernel之后,具体的kernel及其调用参数随即交由GPU的硬件调度器来在某个未来的时间点真正运行起来。在默认的情况下,kernel是被派发给GPU上所有的SM,且执行过程中不能被中断。如图5所示,软件系统在发送完毕启动命令之后,随即命令及参数由PCIe转交给GPU硬件,并插入其队列中,由GPU硬件中固化的逻辑去具体处理在何时真正启动。
为了形象化来阐述思路,这里我们对GPU做了一个抽象化的改动,SM的个数被定义为10个。然后有一个启动参数为<<<15,1>>>的内核,即CUDA block size为15,thread size为1。它正常启动的时候,硬件调度器会给每一个SM上分配一个内核的副本。这样在第一时间就消耗了10个block的副本,随后每个SM上内核执行完毕之后会退出,硬件调度器会进一步分配剩下的5个block副本,在这个也执行完毕之后就完成了整个内核的执行。
算力切分之后,我们会在内核启动时,动态的修改其启动参数,将其CUDA block size从15变为5。这样硬件调度器就会将内核副本分配到GPU上一半数目的SM上,空闲的一半可以为其他内核所使用,如图7所示。
这次需要指出的是blockIdx/threadIdx为只读寄存器,所以没办法直接更改它的值。作为一个替代的解决方案时,将内核中的blockIdx/threadIdx进行整体替换为可写的寄存器,这样我们就可以在预设的跳转逻辑中做更改操作,如图8所示。
03
K8S 容器中使用 GPU 的方案一般采用 Nvidia device plugin(英伟达官方插件),它可以为 Pod 分配一卡或多卡,分配的最小单元是1张卡,无法支持底层隔离的 GPU 资源调度。调研之后,我们选择阿里云容器服务开源的aliyun-gpushare作为调度方案,实现对 GPU 隔离资源的调度。
以显存为例,使用aliyun-gpushare,为 Pod 分配的是一张卡中的部分显存,这样从逻辑上说,单卡的资源就可以再进一步被切分。假设有一张 V100 32GB 卡,可以给 Pod1 分配 4GB 显存,也可以同时给 Pod2 分配 8GB 卡,直到 32GB 显存分配完毕。整个调度过程如图9所示
图中的主要流程如下:
用户创建一个 Share GPU Pod 时,必须带 aliyun.com/gpu-mem 这种 K8S 自定义资源,表明其需要多少显存。
SGSE 根据用户的 Share GPU 显存请求和集群整体资源情况,给该 Pod 分配一个 Node,并通过 patch Pod annotation 来指定使用某张卡。
Kubelet 调用 SGDP 的 Allocate 方法,将指定的 GPU 卡分配给 Pod 使用,同时设置环境变量 ALIYUN_COM_GPU_MEM_CONTAINER(容器可用显存)、LD_PRELOAD(其值为限制显存的动态链接库路径)。
Pod 启动后,因为设置了 LD_PRELOAD,所有 AI 框架的 GPU 显存申请都被动态链接库的钩子劫持,当使用的总资源超过 ALIYUN_COM_GPU_MEM_CONTAINER 的值,则拒绝请求。从而达到限制用户使用显存的效果。
算力资源的调度策略类似以上显存调度。
实践中,对于 1 张物理 GPU 卡,我们进行 1/4 和 1/2 的显存和算力的分割,业务可根据实际需要选择对应的比例,单张 GPU 最多可部署 4 个不同应用,并可实现有效隔离,互不影响。
04
后续工作中,我们还计划开发和实现跨主机 GPU 远程调用的方案,来解决 GPU 虚拟共享之后,产生的单机多卡机器上 CPU/GPU 比例失衡,导致的部分虚拟 GPU 资源无 CPU可分配的问题。
想要立刻加入爱奇艺成为我们的一员吗?
爱奇艺计算云招聘中:·分布式储存架构
·深度学习平台资深研发工程师
·Kubernetes 研发工程师
·高级网络研发工程师
等岗位等你来!
关注公众号
后台回复“招聘2”
获取更多职位详细信息~