怎样节省 2/3 的 GPU?爱奇艺 vGPU 的探索与实践

2024-03-23 20:30

本文主要是介绍怎样节省 2/3 的 GPU?爱奇艺 vGPU 的探索与实践,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

随着人工智能技术的发展,爱奇艺内部越来越多的服务使用深度学习模型和技术来驱动,为我们的用户提供更加智能和便捷的在线视频观看体验。其中在线类的服务,通常单个容器实例需要独占一个 GPU,以实现在毫秒/秒级延时内完成例如视频、图片、语音、文本的深度学习模型推理请求;为了保证响应延时,请求通常单独进行,无法对请求做batch以提升计算效率,且不同请求间隔随机,会导致这些服务的 GPU 计算资源的利用率通常较低(如图1所示)。且在线类服务请求量在一天或者一定时间周期内存在波峰波谷的现象,进一步降低了 GPU 的利用率。鉴于GPU本身高昂的价格,较低的 GPU 利用率浪费了大量计算资源,增加了 AI 服务的成本。

图1:在线推理服务 GPU 利用率统计

在此背景下,最直接的解决方案是将多个服务部署在同一张 GPU 卡上,在保证服务质量的前提下通过 GPU 共享来提升 GPU 的利用率。目前英伟达官方的 GPU 共享技术主要有两种方案:

(1)vGPU ;(2)MPS

接下来我们将简单对比下两种方案。

Nvidia vGPU方案

Nvidia的vGPU方案采用虚拟化的技术,基于 SR-IOV 进行 GPU 设备虚拟化管理,在驱动层提供了时间分片执行的逻辑,并做了一定的显存隔离,这样在对显卡进行初始化设置的时候就可以根据需求将显卡进行划分。其中时间分片调度的逻辑可以是按实例均分,或者是自定义比例,显卡的显存需要按照预设的比例进行划分。Nvdia的vGPU方案在实施中有下面两点限制

(1)vGPU划分完成之后,如果要改变这种预定义的划分,需要重启显卡才能生效,无法做到不重启更改配置。

(2)其方案基于虚机,需要先对 GPU 物理机进行虚拟化之后,再在虚拟机内部署容器,无法直接基于物理机进行容器化的调度,另外 vGPU 方案需要收取 license 费用,增加了使用成本。

Nvidia MPS方案

Nvidia的MPS方案是一种算力分割的软件虚拟化方案。该方案和vGPU方案相比,配置很灵活,并且和docker适配良好。MPS 基于C/S架构,配置成MPS模式的GPU上运行的所有进程,会动态的将其启动的内核发送给MPS server,MPS Server借助CUDA stream,实现多个内核同时启动执行。除此之外,MPS 还可配置各个进程对 GPU 的使用占比。

该方案的一个问题在于,各个服务进程依赖 MPS,一旦 MPS 进程出现问题,所有在该GPU上的进程直接受影响,需要使用 Nvidia-smi 重置GPU 的方式才能恢复。

01

爱奇艺的vGPU方案

调研以上方案后,为了更好地适用于爱奇艺内部 AI 容器化应用场景,我们重新开发了容器场景下的 GPU 虚拟共享方案,基于CUDA API 截获方式实现显存及算力隔离和分配,并基于开源项目aliyun-gpushare scheduler[1]实现 K8S 上对虚拟 GPU 的调度和分配,实现了多应用容器部署在一张 GPU 卡的目标。

我们方案的主要特点配置灵活,和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 倍以上。

图2:隔离性测试结果

02

 爱奇艺GPU虚拟共享的底层原理

首先我们来看看 GPU 虚拟共享的底层原理。GPU作为一个强大的计算外设,提供的两个主要资源是显存和算力。要实现单个 GPU 的共享,我们要实现对显存和算力资源的隔离,并验证隔离方案的效率及性能。

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展示了显存隔离和分配的整个流程。

图3:显存分割中隔离和分配流程

2.2算力隔离

除了显存,另外一个重要的GPU资源是算力。对于Nvidia volta显卡架构来说,它的算力来源于三个方面,浮点计算单元、整形计算单元、tensor core加速计算单元。其中浮点计算单元和整形计算单元是流处理器SP的内部结构,而SM中包含着多个流处理器SP。对于V100来说,它有80个SM,每个SM中有64个SP,合5120个流处理器,其中tensor core是位于SM的内部,与SP共享寄存器/share mem/L1 cache。图4给出了Nvidia GPU的硬件架构组织关系框图。

图4:Nvidia GPU硬件架构组织关系图

对于Nvidia的GPU的编程语言CUDA来说,它的语言设计逻辑上和上图的硬件层次是对应的。CUDA有三层逻辑层次,分别为gridblock,和thread。Grid可以认为是对整个显卡的逻辑抽象,block可以认为是对SM单元的逻辑抽象,而thread是SP的逻辑抽象。为了达到最高的并发程度,SM之间可以认为是没有交互的,当然这也不是绝对的,有一些程序为了自己的特殊逻辑,也可以设计出SM之间依赖的程序,但这个代价是极大的性能浪费。

在知道了GPU的底层结构,以及CUDA的设计原理之后,可以就如何算力虚拟化来做一下初步设想。既然一些模型无法完全利用GPU的全部算力,那么何不削减其占用的SM个数,使得空闲下来的SM可以为其他GPU程序所用?

这样的想法是好的,但是一些限制阻止了这种优化的实现。GPU程序的执行,是通过kernel的片段来具体实施,在CPU侧launch了 kernel之后,具体的kernel及其调用参数随即交由GPU的硬件调度器来在某个未来的时间点真正运行起来。在默认的情况下,kernel是被派发给GPU上所有的SM,且执行过程中不能被中断。如图5所示,软件系统在发送完毕启动命令之后,随即命令及参数由PCIe转交给GPU硬件,并插入其队列中,由GPU硬件中固化的逻辑去具体处理在何时真正启动。

图5:GPU软件和硬件调度系统的交互图

但世事无绝对,默认情况下不行,不代表没有别的办法。让我们再来回顾一下CUDA的设计。CUDA作为一个用于操控GPU来完成高效并行计算的语言,它的代码编写逻辑是以thread为基本单元的。SM上所有SP都运行着一份kernel的代码,且在一定程度上来说连运行节奏都完全一致。CUDA中用来区分thread,来判断代码应该处理数据的偏移量的方法,是通过CUDA中的blockIdx/threadIdx这两个内嵌变量。这两个变量在机器码上是只读的,在thread由硬件调度器派发的时候所指定。通过硬件调度器,就完成了抽象的blockIdx/threadIdx和具体的SM/SP的绑定。图6大概的描述了这一映射关系。

图6:CUDA逻辑块和硬件计算单元的映射关系

为了能够精确的控制算力,我们就不能再依赖硬件调度器来控制内核启动。在这里用了一个取巧的方法,就是让内核启动之后被“困”在固定数目的SM上面,这个数目的值和GPU整体SM个数的比例就是给这个内核算力配比。

为了形象化来阐述思路,这里我们对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所示。

图7:动态修改启动参数来进行算力分割

我们虽然通过动态修改启动参数的方法,避免了内核占满全部SM资源,但此时还没完成“困”这一动作。所以此时的内核行为是其完成预定逻辑之后,会退出,导致此时内核不能覆盖block size为15时的数据空间。为了将其“困“住,我们在内核的汇编EXIT处,替换成了BRANCH操作。这样内核完成本身的逻辑之后,会跳转到我们预设的一段逻辑中。这个逻辑完成虚拟blockIdx/threadIdx的自增操作,随后再跳转到内核开始位置,来基于更新的blockIdx/threadIdx来进行新一轮计算。

这次需要指出的是blockIdx/threadIdx为只读寄存器,所以没办法直接更改它的值。作为一个替代的解决方案时,将内核中的blockIdx/threadIdx进行整体替换为可写的寄存器,这样我们就可以在预设的跳转逻辑中做更改操作,如图8所示。

图8:汇编修改更改内核运行逻辑

03

爱奇艺GPU虚拟共享的调度设计

完成了 GPU 底层的资源隔离之后,我们还需要基于 K8S 平台实现对隔离的 GPU 资源的分配和调度管理,以方便业务的深度学习服务能够快速部署到共享的 GPU。

K8S 容器中使用 GPU 的方案一般采用 Nvidia device plugin(英伟达官方插件),它可以为 Pod 分配一卡或多卡,分配的最小单元是1张卡,无法支持底层隔离的 GPU 资源调度。调研之后,我们选择阿里云容器服务开源的aliyun-gpushare作为调度方案,实现对 GPU 隔离资源的调度。

以显存为例,使用aliyun-gpushare,为 Pod 分配的是一张卡中的部分显存,这样从逻辑上说,单卡的资源就可以再进一步被切分。假设有一张 V100 32GB 卡,可以给 Pod1 分配 4GB 显存,也可以同时给 Pod2 分配 8GB 卡,直到 32GB 显存分配完毕。整个调度过程如图9所示

图9:阿里公开的整体调用方案图

其中,Share GPU Device Plugin 和 Share GPU Schd Extender 是主要的新增组件,下文简写为 SGDP和SGSE。其余的组件均为 k8s 官方组件。

图中的主要流程如下:

  1.  用户创建一个 Share GPU Pod 时,必须带 aliyun.com/gpu-mem 这种 K8S 自定义资源,表明其需要多少显存。

  2. SGSE 根据用户的 Share GPU 显存请求和集群整体资源情况,给该 Pod 分配一个 Node,并通过 patch Pod annotation 来指定使用某张卡。

  3. Kubelet 调用 SGDP 的 Allocate 方法,将指定的 GPU 卡分配给 Pod 使用,同时设置环境变量 ALIYUN_COM_GPU_MEM_CONTAINER(容器可用显存)、LD_PRELOAD(其值为限制显存的动态链接库路径)。

  4. Pod 启动后,因为设置了 LD_PRELOAD,所有 AI 框架的 GPU 显存申请都被动态链接库的钩子劫持,当使用的总资源超过 ALIYUN_COM_GPU_MEM_CONTAINER 的值,则拒绝请求。从而达到限制用户使用显存的效果。

算力资源的调度策略类似以上显存调度。

实践中,对于 1 张物理 GPU 卡,我们进行 1/4 和 1/2 的显存和算力的分割,业务可根据实际需要选择对应的比例,单张 GPU 最多可部署 4 个不同应用,并可实现有效隔离,互不影响。

04

结语和展望

通过底层LD_PRELOAD动态劫持的方式,我们实现了容器中轻量级 GPU 显存和算力的隔离,从而支持多个容器应用部署在同一个 GPU 上。该方案从一个动态的维度实现单张 GPU资源的划分,针对在线推理服务场景,很好的提升了GPU硬件的使用效率。

后续工作中,我们还计划开发和实现跨主机 GPU 远程调用的方案,来解决 GPU 虚拟共享之后,产生的单机多卡机器上 CPU/GPU 比例失衡,导致的部分虚拟 GPU 资源无 CPU可分配的问题。

参考文献

1.aliyun-gpushare: https://github.com/AliyunContainerService/GPUshare-scheduler-extender

2.Nvidia vGPU:https://docs.Nvidia.com/grid/latest/grid-vGPU-user-guide/index.html

3. Nvidia MPS:https://docs.Nvidia.com/deploy/mps/index.html

看完心动了吗?

想要立刻加入爱奇艺

成为我们的一员吗?

爱奇艺计算云招聘中:

·分布式储存架构

·深度学习平台资深研发工程师

·Kubernetes 研发工程师

·高级网络研发工程师

等岗位等你来

关注公众号

后台回复“招聘2

获取更多职位详细信息~

也许你还想看

听见用户的声音,爱奇艺全渠道用户反馈分析的探索与实践

推理性能提升一倍,TensorFlow Feature Column性能优化实践

 关注我们,更多精彩内容陪伴你!

这篇关于怎样节省 2/3 的 GPU?爱奇艺 vGPU 的探索与实践的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/839449

相关文章

C++对象布局及多态实现探索之内存布局(整理的很多链接)

本文通过观察对象的内存布局,跟踪函数调用的汇编代码。分析了C++对象内存的布局情况,虚函数的执行方式,以及虚继承,等等 文章链接:http://dev.yesky.com/254/2191254.shtml      论C/C++函数间动态内存的传递 (2005-07-30)   当你涉及到C/C++的核心编程的时候,你会无止境地与内存管理打交道。 文章链接:http://dev.yesky

C++必修:模版的入门到实践

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:C++学习 贝蒂的主页:Betty’s blog 1. 泛型编程 首先让我们来思考一个问题,如何实现一个交换函数? void swap(int& x, int& y){int tmp = x;x = y;y = tmp;} 相信大家很快就能写出上面这段代码,但是如果要求这个交换函数支持字符型

不懂怎样摘草莓的电影我

拿起来后摘掉茎的电影 今天的拿起来后摘掉茎的电影,诶,我在某某自选商店,他们上了我的太阳飞机,那些小平房呢,不懂怎样摘草莓的电影我,我开着飞机,哪来的高楼大厦,我找了两个小时,是不是作弊了。 只好求助农民伯伯,都是很简单的,这是冀州市吗,快快充实交代梁锦宇笑着说,拿起来后摘掉茎,我说,咦,许多同学问梁锦宇,后来我才知道。 是东面还是西面,是团队的富民政策把平房变成了高楼大厦,找自己的住处,

亮相WOT全球技术创新大会,揭秘火山引擎边缘容器技术在泛CDN场景的应用与实践

2024年6月21日-22日,51CTO“WOT全球技术创新大会2024”在北京举办。火山引擎边缘计算架构师李志明受邀参与,以“边缘容器技术在泛CDN场景的应用和实践”为主题,与多位行业资深专家,共同探讨泛CDN行业技术架构以及云原生与边缘计算的发展和展望。 火山引擎边缘计算架构师李志明表示:为更好地解决传统泛CDN类业务运行中的问题,火山引擎边缘容器团队参考行业做法,结合实践经验,打造火山

探索蓝牙协议的奥秘:用ESP32实现高质量蓝牙音频传输

蓝牙(Bluetooth)是一种短距离无线通信技术,广泛应用于各种电子设备之间的数据传输。自1994年由爱立信公司首次提出以来,蓝牙技术已经经历了多个版本的更新和改进。本文将详细介绍蓝牙协议,并通过一个具体的项目——使用ESP32实现蓝牙音频传输,来展示蓝牙协议的实际应用及其优点。 蓝牙协议概述 蓝牙协议栈 蓝牙协议栈是蓝牙技术的核心,定义了蓝牙设备之间如何进行通信。蓝牙协议

探索Elastic Search:强大的开源搜索引擎,详解及使用

🎬 鸽芷咕:个人主页  🔥 个人专栏: 《C++干货基地》《粉丝福利》 ⛺️生活的理想,就是为了理想的生活! 引入 全文搜索属于最常见的需求,开源的 Elasticsearch (以下简称 Elastic)是目前全文搜索引擎的首选,相信大家多多少少的都听说过它。它可以快速地储存、搜索和分析海量数据。就连维基百科、Stack Overflow、

9 个 GraphQL 安全最佳实践

GraphQL 已被最大的平台采用 - Facebook、Twitter、Github、Pinterest、Walmart - 这些大公司不能在安全性上妥协。但是,尽管 GraphQL 可以成为您的 API 的非常安全的选项,但它并不是开箱即用的。事实恰恰相反:即使是最新手的黑客,所有大门都是敞开的。此外,GraphQL 有自己的一套注意事项,因此如果您来自 REST,您可能会错过一些重要步骤!

黑龙江等保测评的具体流程是怎样的

黑龙江等保测评的具体流程 黑龙江等保测评是根据《中华人民共和国网络安全法》及相关法律法规,对信息系统安全保护能力进行评估和验证的过程。以下是黑龙江等保测评的具体流程: 系统定级:根据业务、资产、安全技术、安全管理等方面的情况,对企业的安全防护水平进行评估,编制定级报告,为客户提供技术支持,协助客户编制定级报告,并组织相关专家对定级报告进行评估。 系统备案:持定级报告及登记表到当地的公安网监

Netty ByteBuf 释放详解:内存管理与最佳实践

Netty ByteBuf 释放详解:内存管理与最佳实践 在Netty中(学习netty请参考:🔗深入浅出Netty:高性能网络应用框架的原理与实践),管理ByteBuf的内存是至关重要的(学习ByteBuf请参考:🔗Netty ByteBuf 详解:高性能数据缓冲区的全面介绍)。未能正确释放ByteBuf可能会导致内存泄漏,进而影响应用的性能和稳定性。本文将详细介绍如何正确地释放ByteB

Clickhouse 的性能优化实践总结

文章目录 前言性能优化的原则数据结构优化内存优化磁盘优化网络优化CPU优化查询优化数据迁移优化 前言 ClickHouse是一个性能很强的OLAP数据库,性能强是建立在专业运维之上的,需要专业运维人员依据不同的业务需求对ClickHouse进行有针对性的优化。同一批数据,在不同的业务下,查询性能可能出现两极分化。 性能优化的原则 在进行ClickHouse性能优化时,有几条