今天给大家分享的主题是百度智能云在「GPU 容器虚拟化」方面的最新进展和全场景实践,希望通过这次分享和大家一起探讨如何在实际业务场景更好的应用 GPU 容器虚拟化技术。
本次分享将首先介绍百度智能云 GPU 容器虚拟化 2.0 的升级变化,然后介绍新版本中的技术实现方法并演示具体功能,最后介绍在各类业务场景的实践和探索。
一、双引擎 GPU 容器虚拟化 2.0
我们去年发布了业内首个双引擎 GPU 容器虚拟化架构,采用了「用户态」和「内核态」两种引擎,以满足用户对隔离性、性能、效率等多方面不同侧重的需求。
在隔离引擎之上是资源池化层,该层次主要基于远程调用实现资源的解耦和池化。
在资源池化层之上是 K8s 统一资源调度层。在调度机制之上,我们会根据不同业务场景,抽象出来多种混布方式,包括共享混布、抢占混布、分时混布、潮汐混布等。
通过以上技术能力支持了各类 AI 业务的落地,包括模型开发、模型训练、在线推理等,大幅提升了 GPU 资源的使用率,减少了 GPU 的使用数量。
关于这些内容更详细全面的讲解,在我们去年的分享中介绍过,文字稿件的链接在文末。
1.0 版本很好地满足了 AI 场景的业务需求,不管是厂内还是厂外的业务中,得到了比较广泛的应用。
我们的技术目标就是希望:吃干榨尽所有资源,覆盖所有业务场景,提升业务总体表现。
所以 1.0 版本还不够完美,并没有释放 GPU 的全部能力:GPU 上的所有资源在容器虚拟化环境中并没有完全使能,更多的场景是无法使用 GPU 容器虚拟化能力的,所以今年我们继续推出了 2.0 版本。
这是我们双引擎 GPU 容器虚拟化 2.0 架构图。
在 2.0 版本中, 除了对 GPU 的显存和 AI 算力进行隔离之外,还实现了对 GPU 的渲染算力和编解码器的隔离。
基于这些新的能力,在资源调度层面可以提供渲染混布和编解码混布,实现了 AI 算力、渲染算力、编解码器等 GPU 全部资源的统一调度。
同时,2.0 版本可以通过多调度器的方式,支持客户的现有业务平滑融合到我们的架构中,这对客户业务能够快速使用最新的 GPU 容器虚拟化能力,是非常重要的。
在这些新能力的支持下,更多的业务场景,比如自动驾驶仿真、ARM 平台的云游戏等都可以通过这套平台提升资源利用率,将所需 GPU 的使用量明显降低。
除此之外,两个容器虚拟化引擎我们都会随着底层库的更新进行不断迭代,确保用户能够使用业界最新的技术。其中用户态支持最新版本 nvidia driver 和cuda 12.1,内核态支持 nvidia driver 525/530/535 等最新版本。
二、新能力技术解析
接下来我会详细介绍渲染算力和编解码器隔离的技术方案。
首先我们先分析一下 AI 算力和渲染算力的区别。
在 NVIDIA GPU 上不仅能进行 AI 计算,还可以做图形的渲染计算。AI 负载通过 Cuda 访问 GPU,渲染负载通过 OpenGL/Vulkan 访问 GPU。 这两类计算均使用相同的算力资源。
那么在 AI 计算隔离已经实现的基础上,渲染负载能否在 AI 计算的隔离环境中成功运行?
如果渲染负载能够隔离成功,使用哪种类型的 GPU 容器虚拟化引擎是合适的方案?
接下来我们详细分析一下 AI 算力架构。
从上往下看,最上层的是 AI APP,它们依赖于底层的 Cuda 系列库,包括 cuda-x,cuda runtime 等,它们给上层 AI APP 提供易于使用的高级 API。再往下就是应用层 driver 库,包括 cuda driver,NVML 等,它们会通过设备文件和内核态的 driver 进行通信,最终达到使用 GPU 的目的。
架构图中灰色的箭头是 AI 程序使用 GPU 的控制流,红色的箭头是 AI 程序使用 GPU 的的数据流。
那么 AI 算力架构和渲染架构有什么不一样呢?我们继续往下看。
渲染算力架构,从上往下看,最上层的是 UI/3D APP,比如游戏引擎,它的底层库相比 AI 算力架构要复杂一些,调用底层库主要包括两种方式:通过转发层 GLX 调用 X11 server,或者直接调用 EGL。
最终他们都会调用底层的图形库 OpenGL 或者 Vulkan,这些图形库就相当于 AI 计算中的 Cuda 库。
再往下就是应用层 driver 库,包括 libnvidia-glcore/libnvidia-eglcore/libdrm 等,它们也是通过设备文件和内核态的 driver 进行通信,最终达到使用 GPU 的目的。
架构图中灰色的箭头是渲染程序使用 GPU 的控制流,红色的箭头是渲染程序使用 GPU 的数据流。
经过以上分析和对比,我们可以发现 AI 算力架构和渲染算力架构,虽然上层软件不同,但是他们的控制流都是一致的,使用相同的设备文件和内核模块进行通信。
回到我们之前的问题,渲染负载能否在 AI 计算的隔离环境中成功运行?依据这个分析结果预测,渲染应用是可以在 AI 算力的隔离环境中运行。
但是在实际验证中,结论是否定的。
经过逆向分析发现两个方案的控制命令字存在一些差异,需要将这部分差异在隔离实现中区分开来。通过大量的实验后,最终在内核层面实现了渲染算力的隔离。
为什么我们没有选择在用户态实现这个方案呢?
因为用户态实现需要拦截的上层库函数多,实现难度高。同时,对用户的软件不透明。所以通过用户态实现渲染算力隔离并不是一个好的工程方案。
接下来我向大家演示 AI 算力和渲染算力的隔离效果。演示的硬件环境为一张 NVIDIA V100 16G。
在这个 GPU 上运行单个 AI 负载,pytorch resnet50 训练,batch-size 为 32 时,分配 100% 算力,吞吐在 340 左右。运行单个渲染负载,使用的 GPUtest 的 Furmask 测试,分配 100% 算力,FPS 在 550。在混合负载测试中,一个 AI 负载,一个渲染负载,各分配 50% 算力,AI 负载的吞吐为 170,渲染的 FPS 为 260~270。
可以看到在单个 GPU 上,AI 负载和渲染负载都实现了隔离,获得了约一半的算力,得到了预期的性能表现。
在 2.0 版本中,另外一个新增的功能就是编解码器的隔离。
我们分别在用户态和内核态都实现了编解码实例。
用户态的编解码器实例中,编解码器是裸混使用,不支持对编解码器的算力做隔离,每个实例都可以全部编解码算力。
内核态的编解码器实例中,我们实现了对编解码器的隔离,在实例中编码器的权重和 AI 算力、渲染算力的权重共享,做到统一算力分配。
那么内核态和用户态的实现有什么不同呢?用户态在实例中使用的是编解码器的全部算力,而内核态实现了编解码器的算力分配。比如在内核态分配 20% 的算力,那么你就可以在内核态的编解码实例中使用 20% 的编解码能力。
这样我们就完成了 GPU 资源的统一算力分配和使能,做到了资源的吃干榨净。
三、全场景实践
接下来我们将会结合前面的技术,做各个场景的实践分享。
我们先看看用户态和内核态两个引擎在技术和场景上的差异。
在技术特性对比上,业务层一般会在隔离性能的强弱、延时高低、资源分配颗粒度、多用户支持能力等维度做技术考量,为应用匹配合适的 GPU 容器虚拟化引擎。
依据这些特性的分析,我们列出了不同应用场景适用的技术方案。
比如在线推理,对延时的要求很高,一般就会推荐用户态的方案。离线推理,则两种方案都可以选择。在渲染仿真场景,由于用户态不支持渲染隔离,故只能使用内核态的方案。
这是一个经典的互联网的推荐业务,包含了数据处理、模型开发和在线服务等业务。其中数据处理和模型开发是离线业务,用来支持在线服务,所有业务都使用了大量的 GPU。
在没有使用 GPU 容器虚拟化方案之前,每个业务实例使用一个 GPU,通过大量的监控数据发现,在线推理服务的整体 GPU 使用率并不高,整体在 20%,这是一个业界普遍存在的问题。
因为在线服务对时延要求比较高,我们在这种场景选择部署用户态的方案。在保证业务 SLA 相同的情况下,大幅提升整体 GPU 资源使用率,将整体资源利用率到 35%。
在结合用户态本身支持的抢占混布和分时混布,使得数据处理和模型开发等离线任务,可以和在线推理业务进行在离线混布,当在线业务处于波谷时,离线业务抢占较多 GPU 空闲资源进行业务处理,节省了整体的 GPU 使用数量。
这种场景在厂内和厂外都得到了大量的应用,节省了很多的成本。在 GPU 资源比较紧张的时代是一个很好的技术选择。
很多客户的平台中已经存在自己定制的任务调度器,包括自定义的排队算法等功能。客户如果要引入第三方供应商的 GPU 容器虚拟化平台,则需要使用相应的任务调度器。
如何使得定制的任务调度器和百度智能云的任务调度器在客户业务中共存,一种方式是使用多套 K8s 集群,这会导致管理复杂。另外一种方式就是使用百度智能云开发的多调度器支持方案,在同一 K8s 调度集群内使用多个调度器。
在这个方案里面我们将 GPU 资源分为两个池子,通过标签识别两个 GPU 池的资源。任务描述使用不同的标签,K8s 会把任务分配对应的任务调度器,从而使不同任务调度器的共存。
如果客户使用的是开源版的任务调度器,未做深度改动,则可以通过这套方案实现业务的平滑过渡,最后将所有业务都迁移到百度智能云的任务调度器上。
开发人员会用小规模的模型进行调试、验证、算子开发等工作,这些模型参数规模一般在 1.5B 以内。一般使用一块 A100 或者 A800 GPU进行,支持 2~4 个用户。
但是在开发过程中,GPU 有较多时间处于空闲状态,导致整体 GPU 使用率较低。
同时,每个开发人员需要大量的存储资源,保存自己的训练数据和模型数据,需要通过大容量的远程文件系统来存储。
远程文件系统要挂载到客户容器内,需要采用不同参数进行挂载,以挂载不同的卷。这是通过不同用户的 user id 来识别用户的身份来实现的。
但是用户态虚拟化不支持不同 user id 的用户使用,即不支持多用户隔离,无法在此场景使用。内核态虚拟化可以实现多用户隔离,可以保证不同用户同时挂载不同的资源来使用。
内核态虚拟化支持 burst 调度策略,可以只针对活跃的容器负载,按照权重进行算力分配。比如一个 GPU,虚拟化给两个用户使用,平分 50% 的算力,当只有一个用户实际使用 GPU 时,调度策略会把所有算力分配给此用户。当两个用户都在使用时,各使用 50% 的算力。
这样既提供了共享算力的能力,又总体提高了 GPU 的使用率和业务表现。
自动驾驶仿真场景涉及到了 3 个模块,其中渲染仿真编码模块和感知推理模块使用了 GPU,覆盖了 AI 计算、图形渲染和编解码器等全部资源类型。
在没有使用 GPU 容器虚拟化方案之前,渲染仿真编码仿真使用一块 GPU 运行类似游戏引擎的环境,实时渲染出车辆和路面状况,每隔 1 到 2 秒,截取一张图片编码后输出给感知推理模块。感知推理模块使用另外一块 GPU,运行类似图像识别和分类推理的 AI 模型,识别出车道线、行人、障碍物等,把数据给规控模块。规控模块会根据感知数据,规划和控制车辆的下一步状态,发送控制命令给仿真模块,进行下一步操作。
这时候业务对 GPU 的使用率都较低,不超过 50%。
我们采用内核态虚拟化技术,在同一个 GPU 上同时运行仿真和推理任务,每个任务分配 50% 的算力,达到同时在一个 GPU 运行两种不同的负载。同时数据在一个 GPU 中的显存传递,提高了数据传输效率,提升了业务性能。
相比过去,这样在仿真环境提高了 100% 的 GPU 使用率。
云游戏通常是同一个 GPU 上运行多个容器,每个容器运行着一个完整的 android 实例。在这个实例里包含了 android 的运行时环境,在最上面会运行一个 android 游戏,例如王者荣耀、和平精英、原神等。
因为云游戏是没有真实屏幕的,只有模拟出来的虚拟屏幕,每个实例会使用 GPU 渲染资源,把游戏的图像界面渲染到虚拟屏幕的上。这些图像画面会通过对虚拟屏幕的截屏和编码,输出成类似 H.264 视频流,通过流媒体协议播放到用户的手机客户端上。
在没有使用 GPU 容器虚拟化方案之前,多个云游戏实例是通过裸混争抢的方式,共享 GPU 的渲染资源,可以说是没有任何服务保证和资源隔离能力的,导致游戏体验无法进行 SLA 管理。
由于手机云游戏运行在 ARM 平台中,我们做了大量新的技术开发,使得整个云原生 AI 平台能够运行在 ARM 上。
我们使用内核态方案来对每个 android 实例进行管理,进行渲染显存的隔离,保证显存分配的 QoS。
那么在这个场景中,我们为什么没有用内核态虚拟化对渲染算力和编解码器做隔离?因为云游戏对实时性要求比较高,内核态虚拟化无法满足时延的要求,渲染隔离更多满足对实时性不高的场景。所以在这个场景,只使用了内核态虚拟化的显存隔离能力,保证显存分配的 QoS。
以上就是我今天和大家分享的全部内容了。
点击阅读原文,了解 AI 大底座更多内容
——END——
推荐阅读:
百度APP iOS端包体积50M优化实践(五) HEIC图片和无用类优化实践