目录
一、GPU调度划分痛点案例
背景:某客户拥有一台2张Nvidia L20(45G显存)显卡的GPU服务器,现客户要求在服务器上部署1个显存占用20G的模型和1个显存占用40G的模型服务,且模型服务作为一个k8s的Pod,由k8s进行统一管理。
工程师小A开始了他的工作:首先在 k8s 中资源是和节点绑定的,对于 GPU 资源,使用 NVIDIA 提供的 device-plugin 进行感知,并上报到 kube-apiserver,这样我们就能在 Node 对象上看到对应的资源了。
就像这样:
root@liqivm:~# kubectl describe node gpu01|grep Capacity -A 7
Capacity:
cpu: 128
ephemeral-storage: 879000896Ki
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 1056457696Ki
nvidia.com/gpu: 2
pods: 110
可以看到,该节点除了基础的 cpu、memory 之外,还有一个http://nvidia.com/gpu: 2 信息,表示该节点上有 2 个 GPU。
然后就可以在创建 Pod 时申请对应的资源了,比如申请一个 GPU:
apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
containers:
- name: gpu-container
image: nvidia/cuda:12.0-base # 一个支持 GPU 的镜像
resources:
limits:
nvidia.com/gpu: 1 # 申请 1 个 GPU
command: ["nvidia-smi"] # 示例命令,显示 GPU 的信息
restartPolicy: OnFailure
随后apply 该 yaml 之后,kube-scheduler 在调度该 Pod 时就会将其调度到一个拥有足够 GPU 资源的 Node 上。同时该 Pod 申请的部分资源也会标记为已使用,不会再分配给其他 Pod。
于是,小A顺利的完成了客户的需求,2个模型分别占用了2张显卡,但是其中的一张显卡只占用了20G显存,客户要求充分利用资源,再部署一个模型,占满这张显卡。
显而易见,小A无法完成新的需求。因为两张显卡均有模型使用,并且被两个pod标记为已使用,因此当有新的Pod创建时来申请某一张显卡,即使显卡有空余显存,K8s无法将这张显卡分配给这个新的pod。
案例痛点总结
我们可以总结上述过程中遇到问题,即:Node 上的 GPU 资源被 Pod 申请之后,在 k8s 中就被标记为已消耗了,后续创建的 Pod 会因为资源不够导致无法调度。实际上,可能 GPU 性能比较好,可以支持多个 Pod 共同使用,但是因为 k8s 中的调度限制导致多个 Pod 无法正常共享。
因此,我们才需要一个能够完成 GPU 共享、切分等的方案,来解决GPU资源碎片化的问题。
实际上,NVIDIA Time Slicing 中给大家分享了一个 GPU 共享方案。可以实现多个 Pod 共享同一个 GPU,但是存在一个问题:Pod 之间并未做任何隔离,每个 Pod 能用到多少 GPU core、memory 都靠竞争,可能会导致部分 Pod 占用大部分资源导致其他 Pod 无法正常使用的情况。
三、HAMi介绍
1)概念
HAMi,原名“k8s-vGPU-scheduler”,是管理Kubernetes中异构设备的中间件。它可以管理不同类型的异构设备(如GPU、NPU等),在Pod之间共享异构设备,根据设备的拓扑信息和调度策略做出更好的调度决策。它旨在消除不同异构设备之间的差距,为用户提供一个统一的管理接口,且无需更改应用程序。
2)组件
HAMi 包含以下几个组件,一个统一的 mutatingwebhook,一个统一的调度器,以及针对各种不同的异构算力设备对应的设备插件和容器内的控制组件,整体的架构特性如下图所示:
HAMi 实现GPU core 和 memory 隔离、限制是使用的 vCUDA 方案,具体设计如下:
HAMi 使用的是软件层面的 vCUDA 方案,对 NVIDIA 原生的 CUDA 驱动进行重写(libvgpu.so),然后挂载到 Pod 中进行替换,然后在自己的实现的 CUDA 驱动中对 API 进行拦截,实现资源隔离以及限制的效果。
例如:原生 libvgpu.so 在进行内存分配时,只有在 GPU 内存真的用完的时候才会提示 CUDA OOM,但是对于 HAMi 实现的 libvgpu.so 来说,检测到 Pod 中使用的内存超过了 Resource 中的申请量就直接返回 OOM,从而实现资源的一个限制。
然后在执行 nvidia-smi 命令查看 GPU 信息时,也只返回 Pod Resource 中申请的资源,这样在查看时也进行隔离。
3)能力
异构算力虚拟化中间件 HAMi 满足了对于管理异构算力集群所需要的能力,包括:
- 设备复用:每个任务可以只占用一部分显卡,多个任务可以共享一张显卡
- 可限制分配的显存大小:你现在可以用显存值(例如 3000M)或者显存比例(例如 50%)来分配 GPU,vGPU 调度器会确保任务使用的显存不会超过分配数值
- 指定设备型号:当前任务可以通过设置 annotation 的方式,来选择使用或者不使用某些具体型号的设备
- 设备指定 UUID:当前任务可以通过设置 annotation 的方式,来选择使用或者不使用指定的设备,比如:"http://nvidia.com/use-gpuuuid" or "http://nvidia.com/nouse-gpuuuid"
- 无侵入:vGPU 调度器兼容 nvidia 官方插件的显卡分配方式,所以安装完毕后,你不需要修改原有的任务文件就可以使用 vGPU 的功能。当然,你也可以自定义的资源名称
- 调度策略:vGPU 调度器支持多种调度策略,包括节点、GPU 卡纬度的调度策略,可以通过调度器的参数来进行默认设置,同时也可以根据应用场景,通过设置 Pod 的 annotation 来选择, 比如 "http://hami.io/node-scheduler-policy" 或 "http://hami.io/gpu-scheduler-policy",两个纬度都支持 binpack 和 spread 两种策略。
下图形象展示了是否使用HAMi来进行资源调度的差别,对比之下,HAMi调度器将用户A和B的需求均调至GPU0和1,完整保留了GPU3和4两张空卡。
三、HAMi部署
1)安装要求
·NVIDIA drivers >= 440
·nvidia-docker version > 2.0
·docker/containerd/cri-o已配置nvidia作为默认runtime
·Kubernetes version >= 1.16
·glibc >= 2.17 & glibc < 2.3.0
·kernel version >= 3.10
·helm > 3.0
2)安装
首先使用helm添加 repo
helm repo add hami-charts (https://project-hami.github.io/HAMi/)
随后,需要将所有要使用到的GPU节点打上gpu=on标签,否则该节点不会被调度到
kubectl label nodes {nodeid} gpu=on
使用下列指令获取集群服务端版本
kubectl version
在安装过程中须根据集群服务端版本(上一条指令的结果)指定调度器镜像版本,例如集群服务端版本为1.16.8,则可以使用如下指令进行安装
helm install hami hami-charts/hami --set scheduler.kubeScheduler.imageTag=v1.16.8 -n kube-system
通过kubectl get pods指令看到 vgpu-device-plugin 与 vgpu-scheduler 两个pod 状态为Running 即为安装成功
kubectl get pods -n kube-system
3)验证
1、查看 Node GPU 资源,环境中只有一个物理 GPU,但是 HAMi 默认会扩容 10 倍,理论上现在 Node 上能查看到 1*10 = 10 个 GPU。
2、使用以下 yaml 来创建 Pod。
apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
containers:
- name: ubuntu-container
image: ubuntu:18.04
command: ["bash", "-c", "sleep 86400"]
resources:
limits:
nvidia.com/gpu: 1 # 请求1个vGPUs
nvidia.com/gpumem: 21000 # 每个vGPU申请21G显存 (可选,整数类型)
nvidia.com/gpucores: 30 # 每个vGPU的算力为30%实际显卡的算力核心 (可选,整数类型)
注意 resources.limit 除了原有的 http://nvidia.com/gpu 之外还新增了 http://nvidia.com/gpumem 和 http://nvidia.com/gpucores,用来指定显存大小和算力大小。
http://nvidia.com/gpu:请求的 vgpu 数量,例如 1
http://nvidia.com/gpumem :请求的显存数量,例如 3000M
http://nvidia.com/gpumem-percentage:显存百分百,例如 50 则是请求 50%显存
http://nvidia.com/priority: 优先级,0 为高,1 为低,默认为 1。
对于高优先级任务,如果它们与其他高优先级任务共享 GPU 节点,则其资源利用率不会受到 resourceCores 的限制。换句话说,如果只有高优先级任务占用 GPU 节点,那么它们可以利用节点上所有可用的资源。
对于低优先级任务,如果它们是唯一占用 GPU 的任务,则其资源利用率也不会受到 resourceCores 的限制。这意味着如果没有其他任务与低优先级任务共享 GPU,那么它们可以利用节点上所有可用的资源。
Pod正常启动,进入 Pod执行 nvidia-smi 命令,查看 GPU 信息,可以看到展示的限制就是 Resource 中申请的 21G。
四、总结
本文以一个实际案例作为切入点,抛出了在云原生环境下,GPU算力分配存在碎片化的问题,随后引入了开源 vGPU 方案 HAMi,并通过简单 Demo 进行了验证。
我们解释了为什么需要 GPU 共享、切分,在 k8s 中使用默认 device plugin 时,GPU 资源和物理 GPU 是一一对应的,导致一个物理 GPU 被一个 Pod 申请后,其他 Pod 就无法使用了。为了提高资源利用率,因此我们需要 GPU 共享、切分等方案。
阐述了HAMi 大致实现原理,即通过替换容器中的 libvgpu.so 库,实现 CUDA API 拦截,最终实现对 GPU core 和 memory 的隔离和限制。
相关链接:
1、开源 vGPU 方案:HAMi,实现细粒度 GPU 切分 - (http://lixueduan.com)
2、HAMi官方仓库
https://github.com/Project-HAMi
版权声明:本文由神州数码云基地团队整理撰写,若转载请注明出处。
公众号搜索神州数码云基地,了解更多技术干货!