4、GPU资源抽象:GPU设备插件、资源模型、显存与计算单元

核心原理:Kubernetes如何“看见”GPU

在容器化环境中,GPU并非Kubernetes原生认识的资源。默认情况下,kubelet只管理CPU和内存这类“可压缩/可量化”资源。要让GPU成为可调度、可分配、可隔离的资源,必须完成一次关键的资源抽象:将物理GPU的显存容量与计算单元(SM/CUDA Core)映射为节点上可声明的扩展资源(Extended Resource)。这一过程由设备插件(Device Plugin)机制承担,它本质上是一个遵循gRPC协议的守护进程,运行在Pod中,向kubelet上报设备列表,并负责在容器启动时将GPU设备注入到容器的cgroup与命名空间中。

核心链路:GPU驱动 → 设备插件探测物理设备 → 通过Device Plugin API向kubelet注册 → kubelet将设备ID写入Pod的DeviceRequests → 容器运行时(如containerd)调用nvidia-container-toolkit完成设备挂载与驱动注入。

资源模型上,Kubernetes将GPU视为不可压缩资源(不支持超卖),且只能以整数为单位进行分配。例如,你不能申请0.5张GPU,但可以通过时间切片(Time-Slicing)或MIG(多实例GPU)在物理层面将一张GPU切分为多个逻辑设备,从而让每个容器获得“1个逻辑GPU”。显存与计算单元的抽象粒度取决于底层切分方式:MIG可以精确划分显存与SM分区,而时间切片则共享全部显存与计算单元,仅做时间片轮转。

GPU资源抽象模型:从物理设备到可调度资源 物理GPU设备 (A100 / H100 / V100) 显存: 40GB / 80GB | 计算单元: 108 SM / 132 SM | 显存带宽: 1.6TB/s ~ 3.35TB/s 驱动: NVIDIA Driver + CUDA Runtime 设备插件探测 Kubernetes 设备插件抽象层 (Device Plugin Framework) ListAndWatch() → 上报设备ID | Allocate() → 返回设备环境变量、挂载点、设备节点 资源注册 节点扩展资源模型 nvidia.com/gpu: 2 → 表示该节点可分配2个逻辑GPU单元(整数分配,不支持超卖) MIG (多实例GPU) — 硬件级隔离 显存分区: 1g.10gb / 2g.20gb / 3g.40gb | SM分区: 7/14/21 SM 时间切片 (Time-Slicing) — 软件级共享 显存共享: 全部80GB | SM共享: 全部108 SM | 按时间片轮转调度 硬件切分 软件切分

方法流程:设备插件的完整生命周期

理解GPU资源抽象,必须掌握设备插件的四个核心阶段。以NVIDIA官方提供的nvidia-device-plugin为例,其工作流程如下:

  1. 初始化与探测:插件启动后调用NVIDIA Management Library(NVML)枚举节点上的所有GPU,获取设备UUID、显存大小、SM数量等属性。
  2. 注册到kubelet:插件通过Unix Socket(默认/var/lib/kubelet/device-plugins/nvidia.sock)向kubelet发送RegisterRequest,声明资源名称nvidia.com/gpu及接口版本。
  3. ListAndWatch:kubelet持续监听插件上报的设备列表。当GPU发生故障或热插拔时,插件主动推送更新,kubelet更新节点资源容量。
  4. Allocate:当调度器将Pod绑定到节点后,kubelet调用插件的Allocate方法,传入请求的设备ID列表。插件返回容器需要注入的环境变量(如NVIDIA_VISIBLE_DEVICES)、设备节点挂载列表及驱动库路径。

关键配置示例:在Pod中声明GPU资源的方式如下:

resources:
  limits:
    nvidia.com/gpu: 1   # 请求1个逻辑GPU

调度器根据节点上nvidia.com/gpu的剩余可分配数量进行过滤与打分,确保不会超卖。

案例/场景:MIG与时间切片的实际选择

假设你有一张NVIDIA A100 80GB GPU,其物理规格为108个SM(流式多处理器)和80GB HBM2e显存。不同抽象策略带来的效果差异显著:

抽象方式 逻辑设备数量 每设备显存 每设备SM数 隔离强度 适用场景
整卡分配 1 80GB 108 最强(硬件隔离) 大模型训练、需全卡显存
MIG 3g.40gb 2 40GB 约36 强(显存/SM硬件隔离) 中型推理、多租户隔离
MIG 1g.10gb 7 10GB 约14 强(独立L2缓存分区) 轻量推理、开发测试
时间切片 可配置(如4) 共享80GB 共享108 弱(仅时间隔离) 批量推理、低优先级任务

实际部署中,MIG模式要求GPU驱动与容器运行时支持compute-instance和GPU instance配置。启用MIG后,设备插件会自动将每个MIG实例视为独立的nvidia.com/gpu设备。例如,将A100配置为2个3g.40gb实例后,节点资源变为nvidia.com/gpu: 2,每个Pod申请1个资源时,会获得一个完整的40GB显存分区,且SM资源被硬件隔离,互不抢占。

重要陷阱:时间切片模式虽然允许多个Pod共享一张GPU,但显存并不隔离。如果两个Pod的总显存需求超过80GB,其中一个Pod会因CUDA_OUT_OF_MEMORY而崩溃。因此,时间切片更适合显存占用可控的推理场景,不适合训练任务。

误区/复盘:资源抽象中的常见错误

  • 误区一:将GPU视为可压缩资源。CPU可以设置CPU shares实现弹性共享,但GPU不支持。如果两个容器同时申请nvidia.com/gpu: 1,而节点只有1张物理GPU且未配置MIG或时间切片,调度器会拒绝第二个Pod,而不是让它们共享。
  • 误区二:忽略设备插件的健康检查。设备插件通过ListAndWatch上报设备状态,但若GPU出现ECC错误或温度过高,插件需要主动将设备标记为Unhealthy。未实现健康上报的插件会导致kubelet继续调度Pod到故障GPU上。
  • 误区三:混淆显存与计算单元的比例。MIG的切分比例并非随意设定。例如A100的MIG支持1g.10gb(14 SM + 10GB)、2g.20gb(28 SM + 20GB)、3g.40gb(42 SM + 40GB)等固定组合。你不能创建“20GB显存 + 50 SM”的自定义组合,因为硬件设计限制了SM与显存分区的绑定关系。
  • 误区四:忘记配置nvidia-container-toolkit。即使设备插件正确上报了GPU,如果容器运行时的prestart钩子未配置,容器内依然无法访问GPU设备节点。必须确保/etc/docker/daemon.json或containerd配置中设置了nvidia-container-runtime作为默认运行时。

复盘建议:在规划GPU资源抽象时,先回答三个问题:

  1. 工作负载的显存需求是否超过单卡物理显存?(决定是否需要多卡或MIG大分区)
  2. 多个负载之间是否需要故障隔离?(决定使用MIG还是时间切片)
  3. 设备插件是否支持目标切分模式?(NVIDIA官方插件支持MIG,但需要显式启用--mig-strategy=single或mixed)

通过上述机制,Kubernetes成功将物理GPU抽象为可调度的逻辑资源。设备插件负责设备发现与生命周期管理,资源模型定义了分配语义(整数、不可超卖),而MIG与时间切片则提供了不同粒度的显存与计算单元切分方案。理解这三层抽象,是后续实现GPU资源配额、优先级抢占与多租户隔离的基础。