如何在K8s集群中管理与使用GPU

背景

随着人工智能的兴起,GPU作为重要的智算算力类型愈发受到重视,而Kubernetes(k8s)作为业界主流的集群管理系统,如何方便管理、使用GPU也是其需要解决的一大问题,故此收集整理了K8s管理与使用GPU的相关资料以学习。

物理机如何使用GPU

如果给一台普通的物理机,例如我们日常用的笔记本电脑应该如何使用GPU呢。其主要涉及到两个插件的安装,分别是Nvidia DriverCUDA Toolkit

Nvidia Driver

Nvidia Driver就是GPU驱动,其与其他驱动类似,其主要作用是作为操作系统与GPU硬件之间沟通的桥梁,它需要负责将GPU复杂的硬件功能抽象为标准化接口,方便操作系统和软件调用,并能把GPU硬件的反馈结果传递给操作系统或应用程序。

Cuda Toolkit

Cuda toolkit是NVIDIA提供的一个开发工具集,包含了一系列用于GPU编程的工具和库。其主要由以下组件组成:

  • Compiler: CUDA-C和CUDA-C++编译器NVCC位于bin/目录中。它建立在NVVM优化器之上,而NVVM优化器本身构建在LLVM编译器基础结构之上。希望开发人员可以使用nvm/目录下的Compiler SDK来直接针对NVVM进行开发。
  • Tools: 提供一些像profiler,debuggers等工具,这些工具可以从bin/目录中获取
  • Libraries: 下面列出的部分科学库和实用程序库可以在lib/目录中使用(Windows上的DLL位于bin/中),它们的接口在include/目录中可获取。
    • cudart: CUDA Runtime
    • cudadevrt: CUDA device runtime
    • cupti: CUDA profiling tools interface
    • nvml: NVIDIA management library
    • nvrtc: CUDA runtime compilation
    • cublas: BLAS (Basic Linear Algebra Subprograms,基础线性代数程序集)
    • cublas_device: BLAS kernel interface
  • Runtime Api:提供GPU访问的接口,包括:
    • CUDA Runtime API: 提供简单易用的高层接口,简化GPU的初始化和资源管理。
    • CUDA Driver API: 更底层的接口,提供对GPU的精细控制,适合需要自定义优化的高级用户。
  • CUDA Samples: 演示如何使用各种CUDA和library API的代码示例。可在Linux和Mac上的samples/目录中获得,Windows上的路径是C:\ProgramData\NVIDIA Corporation\CUDA Samples中。在Linux和Mac上,samples/目录是只读的,如果要对它们进行修改,则必须将这些示例复制到另一个位置。

说明

安装完以上插件后就可以使用GPU了,我们可以直接使用CUDA来编程也可以利用Pytorch、TensorFlow等机器学习库来间接使用GPU,在使用GPU时,其整体的调用链如下图所示:

请添加图片描述

Docker如何使用GPU

配置nvidia-container-runtime

正常创建一个容器的流程是这样的:

请添加图片描述

简单来说主要有以下这些步骤:

  1. 用户命令传递: CLI 将用户命令解析并使用 HTTP 或 Unix Socket 与 dockerd 通信。
  2. 调度与管理: dockerd 解析命令并检查、拉取镜像,再调用 containerd 创建一个新的容器任务,准备容器的元数据和配置(如挂载点、网络设置、环境变量等),并为每个任务创建一个 containerd-shim 进程
  3. 隔离与启动: containerd-shim 启动并调用 runc 创建隔离环境,runc 从 containerd 提供的配置中读取容器规格,包括文件系统挂载、网络命名空间、cgroups 配置(限制 CPU、内存等资源),并配置PID、Network、Mount Namespace级别的隔离,再配置 Cgroups,限制资源使用,设置 rootfs,将镜像内容挂载为容器的根文件系统。
  4. 容器运行: runc 启动用户指定的进程,容器进入运行状态。

而为了能够让容器也能直接使用GPU,我们就需要修改创建容器的关键runtime为nvidia-container-runtime,而我们一般都通过NVIDIA Container Toolkit来安装nvidia-container-runtime。旧版本修改runtime为nvidia-container-runtime是需要手动在etc/docker/daemon.json中增加配置,指定使用 nvidia 的 runtime,如下:

    "runtimes": {"nvidia": {"args": [],"path": "nvidia-container-runtime"}}

新版 toolkit 带了一个nvidia-ctk 工具,执行以下命令即可一键配置:

sudo nvidia-ctk runtime configure --runtime=docker

然后重启 Docker 即可,再创建使用GPU的容器时,只需要加入--gpu参数即可,如docker run --rm --gpus all nvidia/cuda:12.0.1-runtime-ubuntu22.04 nvidia-smi

说明

修改runtime后,如下图所示,containerd-shim会调用指定的运行时nvidia-container-runtime。nvidia-container-runtime相比于默认的runc多实现了nvidia-container-runime-hook,该hook是在容器启动后(Namespace已创建完成),容器自定义命令(Entrypoint)启动前执行。当检测到NVIDIA_VISIBLE_DEVICES环境变量时,会调用libnvidia-container挂载GPU Device和CUDA Driver。如果没有检测到NVIDIA_VISIBLE_DEVICES就直接执行默认的runc。

请添加图片描述

在Docker 环境中的 CUDA 调用的整体层级如下图所示,NVIDIA 将原来 CUDA 应用依赖的API环境划分为两个部分:

  • 驱动级API:由libcuda.so.major.minor动态库和内核module提供支持,图中表示为CUDA Driver,它必须在宿主机上就配置好,且只能有一个版本。
  • 非驱动级API:由动态库libcublas.so等用户空间级别的API(算是对驱动级API的一种更高级的封装)组成,图中表示为CUDA Toolkit,直接存在在各个容器中,各个容器中的CUDA Toolkit的版本也可以不同。

请添加图片描述

K8s如何使用GPU

通过上述说明,我们可以手动在宿主机中起一个使用GPU的容器,但是对于k8s管理的大规模集群,我们还需要做到可以让k8s感知到有哪些GPU可以使用,可以通过k8s的pod来创建使用GPU的容器。

手动配置

在k8s中使用GPU资源涉及到的一个关键组件就是NVIDIA Device Plugin

Device plugin是k8s 用于管理和调度容器中设备资源的一种插件机制,它可以将物理设备(如 GPU、FPGA 等)暴露给容器,从而提供更高级别的资源管理和调度能力。它由各个硬件对应的厂商提供,其主要是通过DeamonSet部署到各个主机上,然后上报给kubelet对应的硬件资源的情况,再上报给master。

当我们安装了NVDIA的device plugin后再次查看node的可分配资源就可以看到GPU相关的信息:

root@test:~# k describe node test|grep Capacity -A7
Capacity:cpu:                48ephemeral-storage:  460364840Kihugepages-1Gi:      0hugepages-2Mi:      0memory:             98260824Kinvidia.com/gpu:     2pods:               110

可以看到,除了常见的 cpu、memory 之外,还有nvidia.com/gpu, 这个就是GPU资源,数量为 2 说明我们有两张 GPU。当我们要为pod分配GPU资源的时候也比较简单,只需要在resources.limits中加入nvidia.com/gpu: 1,就可以为pod申请一块GPU资源了。

具体实现

而具体来说NVDIA device plugin主要是通过实现ListAndWatch 接口来上报节点上的GPU数量,实现Allocate接口, 支持分配GPU的行为。

这部分的关键源码如下:

// ListAndWatch lists devices and update that list according to the health status
func (plugin *NvidiaDevicePlugin) ListAndWatch(e *pluginapi.Empty, s pluginapi.DevicePlugin_ListAndWatchServer) error {s.Send(&pluginapi.ListAndWatchResponse{Devices: plugin.apiDevices()})for {select {case <-plugin.stop:return nilcase d := <-plugin.health:// 收到某个设备有健康问题,标志该设备不健康// FIXME: there is no way to recover from the Unhealthy state.d.Health = pluginapi.Unhealthylog.Printf("'%s' device marked unhealthy: %s", plugin.rm.Resource(), d.ID)// 重新发送新的可用的device列表s.Send(&pluginapi.ListAndWatchResponse{Devices: plugin.apiDevices()})}}
}// Allocat主要是分配显卡,给容器指定要附加的NVIDIA_VISIBLE_DEVICES环境变量
func (plugin *NvidiaDevicePlugin) Allocate(ctx context.Context, reqs *pluginapi.AllocateRequest) (*pluginapi.AllocateResponse, error) {responses := pluginapi.AllocateResponse{}// 为每个请求分配设备for _, req := range reqs.ContainerRequests {if plugin.config.Sharing.TimeSlicing.FailRequestsGreaterThanOne && rm.AnnotatedIDs(req.DevicesIDs).AnyHasAnnotations() {if len(req.DevicesIDs) > 1 {return nil, fmt.Errorf("request for '%v: %v' too large: maximum request size for shared resources is 1", plugin.rm.Resource(), len(req.DevicesIDs))}}// 判断一下申请的设备ID是不是自己所管理的,也就是所拥有的设备,也就是校验是不是自己注册的那些设备for _, id := range req.DevicesIDs {if !plugin.rm.Devices().Contains(id) {return nil, fmt.Errorf("invalid allocation request for '%s': unknown device: %s", plugin.rm.Resource(), id)}}response := pluginapi.ContainerAllocateResponse{}// 将注册时的设备ID转换为具体的gpu idids := req.DevicesIDsdeviceIDs := plugin.deviceIDsFromAnnotatedDeviceIDs(ids)// 将分配的设备信息保存到Env里面去,后续docker的runC将设备信息以环境变量的形式注入到容器if *plugin.config.Flags.Plugin.DeviceListStrategy == spec.DeviceListStrategyEnvvar {response.Envs = plugin.apiEnvs(plugin.deviceListEnvvar, deviceIDs)}if *plugin.config.Flags.Plugin.DeviceListStrategy == spec.DeviceListStrategyVolumeMounts {response.Envs = plugin.apiEnvs(plugin.deviceListEnvvar, []string{deviceListAsVolumeMountsContainerPathRoot})response.Mounts = plugin.apiMounts(deviceIDs)}if *plugin.config.Flags.Plugin.PassDeviceSpecs {response.Devices = plugin.apiDeviceSpecs(*plugin.config.Flags.NvidiaDriverRoot, ids)}if *plugin.config.Flags.GDSEnabled {response.Envs["NVIDIA_GDS"] = "enabled"}if *plugin.config.Flags.MOFEDEnabled {response.Envs["NVIDIA_MOFED"] = "enabled"}responses.ContainerResponses = append(responses.ContainerResponses, &response)}return &responses, nil
}

整个Kubernetes调度GPU的过程如下:

  1. GPU Device plugin 部署到GPU节点上,通过ListAndWatch接口,上报注册节点的GPU信息和对应的DeviceID。
  2. 当有声明nvidia.com/gpu的GPU Pod创建出现,调度器会综合考虑GPU设备的空闲情况,将Pod调度到有充足GPU设备的节点上。
  3. 节点上的kubelet启动Pod时,根据request中的声明调用各个Device plugin的allocate接口,由于容器声明了GPU。kubelet根据之前ListAndWatch接口收到的Device信息,选取合适的设备,DeviceID作为参数,调用GPU DevicePlugin的allocate接口。
  4. GPU device plugin接收到调用,将DeviceID转换为NVIDIA_VISIBLE_DEVICES环境变量,返回给kubelet
  5. kubelet收到返回内容后,会自动将返回的环境变量注入到容器中,并开始创建容器。
  6. 容器创建时,nvidia-container-runtime调用gpu-containers-runtime-hook根据容器的NVIDIA_VISIBLE_DEVICES环境变量,来决定这个容器是否为GPU容器,并且可以使用哪些GPU设备。如果没有携带NVIDIA_VISIBLE_DEVICES这个环境变量,那么就会按照普通的docker启动方式来启动。

使用GPU Operator安装

GPU Operator旨在简化在Kubernetes环境中使用GPU的过程,通过自动化的方式处理GPU驱动程序安装、Controller Toolkit、Device-Plugin 、监控等组件。
NVIDIA GPU Operator总共包含如下的几个组件:

  • NFD(Node Feature Discovery): 用于给节点打上某些标签,这些标签包括 cpu id、内核版本、操作系统版本、是不是GPU节点等,其中需要关注的标签是nvidia.com/gpu.present=true,如果节点存在该标签,那么说明该节点是GPU节点。
  • GFD(GPU Feature Discovery): 用于收集节点的GPU设备属性(GPU驱动版本、GPU型号等),并将这些属性以节点标签的方式透出。在k8s集群中以DaemonSet方式部署,只有节点拥有标签nvidia.com/gpu.present=true时,DaemonSet控制的Pod才会在该节点上运行。
    • 新版本 GFD迁移到了NVIDIA/k8s-device-plugin
  • NVIDIA Driver Installer:基于容器的方式在节点上安装 NVIDIA GPU驱动,在k8s集群中以DaemonSet 方式部署,只有节点拥有标签nvidia.com/gpu.present=true时,DaemonSet 控制的 Pod 才会在该节点上运行。
  • NVIDIA Container Toolkit Installer:能够实现在容器中使用GPU设备,在k8s集群中以DaemonSet 方式部署,同样的,只有节点拥有标签nvidia.com/gpu.present=true时,DaemonSet 控制的 Pod 才会在该节点上运行。
  • NVIDIA Device Plugin:NVIDIA Device Plugin 用于实现将GPU设备以 Kubernetes 扩展资源的方式供用户使用,在k8s集群中以DaemonSet 方式部署,只有节点拥有标签nvidia.com/gpu.present=true时,DaemonSet 控制的 Pod 才会在该节点上运行。
  • DCGM Exporter:周期性的收集节点GPU设备的状态(当前温度、总的显存、已使用显存、使用率等)并暴露Metrics,结合Prometheus和Grafana使用。在k8s集群中以DaemonSet 方式部署,只有节点拥有标签nvidia.com/gpu.present=true时,DaemonSet 控制的Pod才会在该节点上运行。

首先是 GFD、NFD,二者都是用于发现 Node 上的信息,并以label形式添加到k8snode对象上,特别是GFD会添加nvidia.com/gpu.present=true标签表示该节点有GPU,只有携带该标签的节点才会安装后续组件。
然后则是Driver Installer、Container Toolkit Installer用于安装GPU驱动和container toolkit。
接下来这是device-plugin让k8s能感知到GPU资源信息便于调度和管理。
最后的exporter则是采集GPU监控并以Prometheus Metrics格式暴露,用于做GPU监控。

这里着重提及一下NVIDIA Driver InstallerNVIDIA Container Toolkit Installer是如何通过容器的方式来给主机安装对应的内容的。
其安装的主要方法还是通过hostPath挂载的方式来将相关的目录挂载进容器中,然后控制容器将对应的内容添加进目录里。而如果要将相关内容卸载,也是只需要将对应的容器删除,容器就会自动移除相应安装的内容。

GPU Operator虽然方便了安装但是仍然存在一些缺点:

  • Driver Installer 以DaemonSet 方式运行的,每个节点上运行的 Pod 都一样,但是镜像由 驱动版本+内核版本+操作系统版本拼接而成,因此需要集群中所有节点操作系统一致
  • NVIDIA Container Toolkit Installer 同样是以DaemonSet 方式运行的,另外安装时需要指定 Runtime,这也造成了集群的节点必须安装相同的 Container Runtime

思考

目前学习下来的一大感受就是GPU管理的粒度很粗,都是以整卡为单位进行划分,这势必会造成资源的浪费,如何结合GPU卡虚拟化来进行细粒度的分配感觉是一个很大的问题。同时它也没有刻画GPU卡之间、CPU与GPU卡之间的拓扑关系,而当前对于大模型训练其网络管理与优化是非常重要的。

参考资料

  1. https://www.lixueduan.com/posts/ai/01-how-to-use-gpu
  2. https://www.lixueduan.com/posts/ai/02-gpu-operator/
  3. https://www.aneasystone.com/archives/2023/12/scheduling-gpus-in-kubernetes.html
  4. https://blog.csdn.net/qq_43684922/article/details/127024933
  5. https://kubernetes.io/zh-cn/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/
  6. https://kubernetes.io/zh-cn/docs/tasks/manage-gpus/scheduling-gpus/
  7. https://blog.csdn.net/qq_43684922/article/details/127025776
  8. https://www.lixueduan.com/posts/kubernetes/21-device-plugin/#

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/20056.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

kubepi管理k8s集群,演示如何连接阿里云k8s容器

一、背景 对k8s容器运维的过程中&#xff0c;如果是自建k8s的话&#xff0c;一般会安装dashboard&#xff0c;方便管理&#xff1b;如果是阿里云k8s容器&#xff0c;它是有提供web ui&#xff0c;但是它有个不便之处–需要定期登录&#xff0c;且缺少命令控制台。 当你需要使…

Conda 安装纯净版ComfyUI

网上有很多整合包&#xff0c; 我个人喜欢纯净版&#xff0c; 自已搭建 1 拉代码 git clone https://github.com/comfyanonymous/ComfyUI 如果没有装过git,下载安装: https://git-scm.com/ https://git-lfs.com/ 2 创建环境 cd ComfyUI conda create -n ComfyUI python3.11…

LeetCode:700. 二叉搜索树中的搜索

目录 题目描述: 代码: 题目描述: 给定二叉搜索树&#xff08;BST&#xff09;的根节点 root 和一个整数值 val。 你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在&#xff0c;则返回 null 。 示例 1: 输入&#xff1a;root [4,2,7,1,3…

摄影:相机控色

摄影&#xff1a;相机控色 白平衡&#xff08;White Balance&#xff09;白平衡的作用&#xff1a; 白平衡的使用环境色温下相机色温下总结 白平衡偏移与包围白平衡包围 影调 白平衡&#xff08;White Balance&#xff09; 人眼看到的白色&#xff1a;会自动适应环境光线。 相…

【大选】2024年美国总统选举数据分析可视化

前言 • &#x1f453; 可视化主要使用 Plotly • &#x1f50e; 数据处理主要使用 pandas • &#x1f449; 本文是我自己在和鲸社区的原创 1.项目背景描述 2024年美国大选是该国政治生活中的重要事件&#xff0c;吸引了全球的关注。本报告通过对选举数据的分析&#xff0c…

Linux进阶:常用操作

systemctl&#xff1a; 控制系统服务的启动、关闭 系统内置服务均可被systemctl控制第三方软件&#xff0c;如果自动注册了可以被systemctl控制第三方软件&#xff0c;如果没有自动注册&#xff0c;可以手动注册 语法&#xff1a;systemctl start | stop | restart | disable…

JVM类加载过程-Loading

一、Class对象的生命周期 .class文件是如何加载到内存中:.class文件是ClassLoader通过IO将文件读到内存,再通过双亲委派的模式进行Loading,再Linking、以及Initializing,代码调用等一系列操作后,进行GC,组成完整的生命周期; 二、双亲委派模式(Loading的过程): 1、类…

002创建ASP.NET Core项目-数据库优先

创建数据库和表 创建数据库和表 添加关系 Product表引用Category 创建ASP.NET Core Web项目 根据数据库创建Models 在【程序包管理器控制台输入命令】 Scaffold-DbContext Data Source.;Initial Catalogshopdb;Usersa;Password123456;TrustServerCertificatetrue’Microso…

探究IOC容器刷新环节初始化前的预处理

目录 一、IOC容器的刷新环节快速回顾 二、初始化前的预处理prepareRefresh源码分析 三、初始化属性源 &#xff08;一&#xff09;GenericWebApplicationContext初始化属性源 &#xff08;二&#xff09;StaticWebApplicationContext初始化属性源 四、初始化早期事件集合…

25.UE5时间膨胀,慢动作,切换地图,刷BOSS

2-27 时间膨胀、慢动作、切换地图、刷BOSS_哔哩哔哩_bilibili 目录 1.刷新BOSS逻辑 2.时间膨胀实现慢动作 3.胜利画面&#xff0c;下一关 3.1胜利画面UI 3.2第一关、第二关游戏模式 3.3下一关按钮事件的绑定 1.刷新BOSS逻辑 实现当场上的怪物都死亡后&#xff0c;进行刷…

自己编写的前后端分离程序,解决跨域问题

跨域问题在前端解决很麻烦&#xff0c;既然前后端都是自己编写的&#xff0c;就直接在后端解决了。 1. 后端中 在controller文件中加上 CrossOrigin // 解决跨域问题&#xff0c;不加的话虽然数据能正常传输&#xff0c;但是前端页面会没有正常响应 2. 前端中 可以正常访问…

小米顾此失彼:汽车毛利大增,手机却跌至低谷

科技新知 原创作者丨依蔓 编辑丨蕨影 三年磨一剑的小米汽车毛利率大增&#xff0c;手机业务毛利率却出现下滑景象。 11月18日&#xff0c;小米集团发布 2024年第三季度财报&#xff0c;公司实现营收925.1亿元&#xff0c;同比增长30.5%&#xff0c;预估902.8亿元&#xff1b;…

E45.【C语言】练习:输入10个整数查找找并打印不相同的数字及个数

1.题目 输入10个整数查找找并打印不相同的数字及个数 输入示例 数组元素个数:10 20 50 30 10 60 90 70 30 10 20 输出示例 20 50 30 10 60 90 70 一共7个 2.初始代码 和E27.【C语言】练习&#xff1a;在一个整型数组中&#xff0c;只有一个数字出现一次&#xff0c;其他数…

播放器开发之ffmpeg 硬件解码方案

硬件编解码的概念 硬件编解码是⾮CPU通过烧写运⾏视频加速功能对⾼清视频流进⾏编解码&#xff0c;其中⾮CPU可包括GPU、FPGA或者 ASIC等独⽴硬件模块&#xff0c;把CPU⾼使⽤率的视频解码⼯作从CPU⾥分离出来&#xff0c;降低CPU的使⽤负荷&#xff0c;使得平台能 ⾼效且流畅…

DDD领域应用理论实践分析回顾

目录 一、DDD的重要性 &#xff08;一&#xff09;拥抱互联网黑话&#xff08;抓痛点、谈愿景、搞方法论&#xff09; &#xff08;二&#xff09;DDD真的重要吗&#xff1f; 二、领域驱动设计DDD在B端营销系统的实践 &#xff08;一&#xff09;设计落地步骤 &#xff0…

读懂top后显示内容

第一行&#xff1a;系统信息 top - 06:33:12 up 42 min, 1 user, load average: 0.04, 0.02, 0.00 06:33:12&#xff1a;当前时间。up 42 min&#xff1a;系统已经启动了 42 分钟。1 user&#xff1a;当前有 1 个用户登录。load average: 0.04, 0.02, 0.00&#xff1a;这三个…

2024年第十四届APMCM亚太杯数学建模A题B题C题思路+代码解析汇总

2024年第十四届亚太地区大学生数学建模竞赛 开赛后第一时间更新解题思路代码参考文章&#xff01; 点击链接加入群聊【2024亚太杯数学建模竞赛助攻】&#xff1a;http://qm.qq.com/cgi-bin/qm/qr?_wv1027&ksIq03p_73AYtWuH-bNy6VGD2652Um6y2&authKeyZsYwQzbxX0BKPyH…

Tomcat 如何管理 Session

Tomcat 如何管理 Session 我们知道&#xff0c;Tomcat 中每一个 Context 容器对应一个 Web 应用&#xff0c;而 Web 应用之间的 Session 应该是独立的&#xff0c;因此 Session 的管理肯定是 Context 级的&#xff0c;也就是一个 Context 一定关联多个 Session。 Tomcat 中主…

【ArcGISPro】使用AI模型提取要素-提取车辆(目标识别)

示例数据下载 栅格数据从网上随便找一个带有车辆的栅格数据 f094a6b1e205cd4d30a2e0f816f0c6af.jpg (1200799) (588ku.com) 添加数据

SpringBoot3_Web开发

4. 内容协商 一套系统适配多端数据返回 移动端&#xff1a;返回JSON数据第三方&#xff1a;返回XMLIoT&#xff1a;返回自定义协议数据 1. 默认规则 1. SpringBoot 多端内容适配 基于请求头内容协商 【默认】 客户端向服务端发送请求&#xff0c;携带HTTP标准的 Accept 请求…