Kubernetes 中的时间分片 GPU#

了解时间分片 GPU#

NVIDIA GPU Operator 通过 NVIDIA Kubernetes Device Plugin 的一组扩展选项,实现了 GPU 的超额订阅。GPU 时间分片使计划在超额订阅 GPU 上的工作负载能够相互交错运行。

这种在 Kubernetes 中启用 GPU时间分片的机制使系统管理员能够为 GPU 定义一组副本,每个副本都可以独立分发给 Pod 以运行工作负载。与多实例 GPU (MIG) 不同,副本之间没有内存或故障隔离,但对于某些工作负载来说,这比完全无法共享要好。在内部,GPU 时间分片用于多路复用来自同一底层 GPU 副本的工作负载。

注意

典型的资源请求提供对 GPU 的独占访问。对时间分片 GPU 的请求提供共享访问。请求多个时间分片 GPU 并不能保证 Pod 获得成比例的 GPU 计算能力。

请求多个时间分片 GPU 仅指定 Pod 可以访问与其他 Pod 共享的 GPU。每个 Pod 可以在底层 GPU 上运行任意数量的进程,而没有限制。GPU 只是在所有 Pod 的所有 GPU 进程之间提供相等的时间份额。

您可以应用集群范围的默认时间分片配置。您还可以应用节点特定的配置。例如,您可以仅对具有 Tesla-T4 GPU 的节点应用时间分片配置,而不修改具有其他 GPU 型号的节点。

您可以将这两种方法结合起来,应用集群范围的默认配置,然后标记节点,以便这些节点接收节点特定的配置。

比较:时间分片和多实例 GPU#

最新一代 NVIDIA GPU 提供了一种名为多实例 GPU (MIG) 的操作模式。MIG 允许您将 GPU 分区为几个较小的、预定义的实例,每个实例看起来都像一个迷你 GPU,在硬件层提供内存和故障隔离。您可以通过在这些预定义实例之一上运行工作负载,而不是在完整的原生 GPU 上运行工作负载来共享对 GPU 的访问。

MIG 支持于 2020 年添加到 Kubernetes 中。有关其工作原理的详细信息,请参阅在 Kubernetes 中支持 MIG

时间分片牺牲了 MIG 提供的内存和故障隔离,以换取更多用户共享 GPU 的能力。时间分片还为不支持 MIG 的旧代 GPU 提供了一种共享访问 GPU 的方法。但是,您可以结合使用 MIG 和时间分片来提供对 MIG 实例的共享访问。

支持平台和资源类型#

GPU 时间分片可以与裸金属应用程序、具有 GPU 直通的虚拟机以及具有 NVIDIA vGPU 的虚拟机一起使用。

目前,唯一支持的资源类型是 nvidia.com/gpu 以及通过使用混合 MIG 策略配置节点而出现的任何资源类型。

限制#

  • 当启用 GPU 时间分片并使用 NVIDIA Kubernetes Device Plugin 时,DCGM-Exporter 不支持将指标与容器关联。

  • Operator 不会监视时间分片 ConfigMap 的更改。请参阅更新时间分片 ConfigMap

节点标签的更改#

除了 GPU Feature Discovery (GFD) 应用于节点的标准节点标签之外,在您为节点配置 GPU 时间分片后,还会应用以下标签

nvidia.com/<resource-name>.replicas = <replicas-count>

其中 <replicas-count><resource-name> 的每个资源被超额订阅的因子。

此外,默认情况下,nvidia.com/<resource-name>.product 标签会被修改

nvidia.com/<resource-name>.product = <product-name>-SHARED

例如,在 NVIDIA DGX A100 机器上,根据时间分片配置,标签可能类似于以下示例

nvidia.com/gpu.replicas = 8
nvidia.com/gpu.product = A100-SXM4-40GB-SHARED

使用这些标签,您可以请求时间分片 GPU 的访问权限或独占 GPU 的访问权限,就像您传统上指定节点选择器来请求一种 GPU 型号而不是另一种型号一样。也就是说,-SHARED 产品名称后缀确保您可以指定节点选择器,以将 Pod 分配给具有时间分片 GPU 的节点。

migStrategy 配置选项对产品名称的节点标签有影响。当 renameByDefault=false(默认值)和 migStrategy=single 时,MIG 配置文件名称和 -SHARED 后缀都将附加到产品名称,例如以下示例

nvidia.com/gpu.product = A100-SXM4-40GB-MIG-1g.5gb-SHARED

如果您设置 renameByDefault=true,则 nvidia.com/gpu.product 节点标签的值不会被修改。

配置#

关于配置 GPU 时间分片#

您可以通过执行以下高级步骤来配置 GPU 时间分片

  • 将 ConfigMap 添加到 GPU Operator 使用的命名空间。

  • 配置集群策略,以便 Device Plugin 使用 ConfigMap。

  • 将标签应用于要配置 GPU 时间分片的节点。

在具有一个 GPU 的机器上,以下 ConfigMap 配置 Kubernetes,以便节点通告四个 GPU 资源。具有两个 GPU 的机器通告八个 GPU,依此类推。

示例 ConfigMap

apiVersion: v1
kind: ConfigMap
metadata:
  name: time-slicing-config
data:
  any: |-
    version: v1
    flags:
      migStrategy: none
    sharing:
      timeSlicing:
        renameByDefault: false
        failRequestsGreaterThanOne: false
        resources:
          - name: nvidia.com/gpu
            replicas: 4

下表描述了 ConfigMap 中的关键字段。

字段

类型

描述

data.<key>

字符串

指定时间分片配置名称。

如果您要分配节点特定的配置,则可以指定多个配置。在前面的示例中,key 的值是 any

flags.migStrategy

字符串

指定如何标记接收时间分片配置的节点的 MIG 设备。指定 nonesinglemixed 之一。

默认值为 none

renameByDefault

布尔值

设置为 true 时,每个资源都以名称 <resource-name>.shared 而不是 <resource-name> 通告。

例如,如果此字段设置为 true 并且资源通常是 nvidia.com/gpu,则配置为时间分片 GPU 访问的节点随后会将资源通告为 nvidia.com/gpu.shared。如果您想通过在资源请求中指定 <resource-name>.shared 来在具有共享访问权限的 GPU 上调度 Pod,则将此字段设置为 true 会很有帮助。

当此字段设置为 false 时,通告的资源名称(例如 nvidia.com/gpu)不会被修改。但是,产品名称的标签会附加 -SHARED 后缀。例如,如果 kubectl describe node 的输出显示节点标签 nvidia.com/gpu.product=Tesla-T4,则在节点配置为时间分片 GPU 访问后,标签将变为 nvidia.com/gpu.product=Tesla-T4-SHARED。在这种情况下,您可以指定包含 -SHARED 后缀的节点选择器,以在具有共享访问权限的 GPU 上调度 Pod。

默认值为 false

failRequestsGreaterThanOne

布尔值

此字段的目的是强制意识到请求多个 GPU 副本不会导致获得更多比例的 GPU 访问权限。

例如,如果 4 个 GPU 副本可用,并且两个 Pod 各请求 1 个 GPU,而第三个 Pod 请求 2 个 GPU,则这三个 Pod 中的应用程序具有相等的 GPU 计算时间份额。具体来说,请求 2 个 GPU 的 Pod 不会获得比请求 1 个 GPU 的 Pod 多两倍的计算时间。

设置为 true 时,对多个 GPU 的资源请求将失败,并显示 UnexpectedAdmissionError。在这种情况下,您必须手动删除 Pod、更新资源请求并重新部署。

resources.name

字符串

指定要通过时间分片访问提供的资源类型,例如 nvidia.com/gpunvidia.com/mig-1g.5gb 等。

resources.replicas

整数

指定要为共享访问指定资源类型的 GPU 提供的分时 GPU 副本的数量。

应用一个集群范围的配置#

如果您已经安装了 GPU Operator 并希望在集群中的所有节点上应用相同的时间分片配置,请执行以下步骤来配置 GPU 时间分片。

  1. 创建一个文件,例如 time-slicing-config-all.yaml,内容如下例所示

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: time-slicing-config-all
    data:
      any: |-
        version: v1
        flags:
          migStrategy: none
        sharing:
          timeSlicing:
            resources:
            - name: nvidia.com/gpu
              replicas: 4
    
  2. 将 ConfigMap 添加到与 GPU Operator 相同的命名空间

    $ kubectl create -n gpu-operator -f time-slicing-config-all.yaml
    
  3. 使用 ConfigMap 配置 Device Plugin 并设置默认时间分片配置

    $ kubectl patch clusterpolicies.nvidia.com/cluster-policy \
        -n gpu-operator --type merge \
        -p '{"spec": {"devicePlugin": {"config": {"name": "time-slicing-config-all", "default": "any"}}}}'
    
  4. 可选:确认 gpu-feature-discoverynvidia-device-plugin-daemonset Pod 重新启动。

    $ kubectl get events -n gpu-operator --sort-by='.lastTimestamp'
    

    示例输出

    LAST SEEN   TYPE      REASON             OBJECT                                     MESSAGE
    33s         Normal    Created            pod/nvidia-device-plugin-daemonset-cffds   Created container toolkit-validation
    33s         Normal    Started            pod/nvidia-device-plugin-daemonset-cffds   Started container toolkit-validation
    33s         Normal    Started            pod/gpu-feature-discovery-rvlg9            Started container toolkit-validation
    33s         Normal    Created            pod/gpu-feature-discovery-rvlg9            Created container toolkit-validation
    33s         Normal    Pulled             pod/gpu-feature-discovery-rvlg9            Container image "nvcr.io/nvidia/cloud-native/gpu-operator-validator:v22.9.1" already present on machine
    33s         Normal    Pulled             pod/nvidia-device-plugin-daemonset-cffds   Container image "nvcr.io/nvidia/cloud-native/gpu-operator-validator:v22.9.1" already present on machine
    32s         Normal    Created            pod/nvidia-device-plugin-daemonset-cffds   Created container config-manager-init
    32s         Normal    Pulled             pod/nvidia-device-plugin-daemonset-cffds   Container image "nvcr.io/nvidia/k8s-device-plugin:v0.13.0-ubi8" already present on machine
    32s         Normal    Pulled             pod/gpu-feature-discovery-rvlg9            Container image "nvcr.io/nvidia/k8s-device-plugin:v0.13.0-ubi8" already present on machine
    32s         Normal    Created            pod/gpu-feature-discovery-rvlg9            Created container config-manager-init
    32s         Normal    Started            pod/gpu-feature-discovery-rvlg9            Started container config-manager-init
    32s         Normal    Started            pod/nvidia-device-plugin-daemonset-cffds   Started container config-manager-init
    31s         Normal    Created            pod/gpu-feature-discovery-rvlg9            Created container config-manager
    31s         Normal    Started            pod/gpu-feature-discovery-rvlg9            Started container gpu-feature-discovery
    31s         Normal    Created            pod/gpu-feature-discovery-rvlg9            Created container gpu-feature-discovery
    31s         Normal    Pulled             pod/gpu-feature-discovery-rvlg9            Container image "nvcr.io/nvidia/gpu-feature-discovery:v0.7.0-ubi8" already present on machine
    31s         Normal    Started            pod/nvidia-device-plugin-daemonset-cffds   Started container config-manager
    31s         Normal    Created            pod/nvidia-device-plugin-daemonset-cffds   Created container config-manager
    31s         Normal    Pulled             pod/nvidia-device-plugin-daemonset-cffds   Container image "nvcr.io/nvidia/k8s-device-plugin:v0.13.0-ubi8" already present on machine
    31s         Normal    Started            pod/nvidia-device-plugin-daemonset-cffds   Started container nvidia-device-plugin
    31s         Normal    Created            pod/nvidia-device-plugin-daemonset-cffds   Created container nvidia-device-plugin
    31s         Normal    Pulled             pod/nvidia-device-plugin-daemonset-cffds   Container image "nvcr.io/nvidia/k8s-device-plugin:v0.13.0-ubi8" already present on machine
    31s         Normal    Pulled             pod/gpu-feature-discovery-rvlg9            Container image "nvcr.io/nvidia/k8s-device-plugin:v0.13.0-ubi8" already present on machine
    31s         Normal    Started            pod/gpu-feature-discovery-rvlg9            Started container config-manager
    

请参阅验证 GPU 时间分片配置

应用多个节点特定的配置#

应用一个集群范围配置的替代方法是在 ConfigMap 中指定多个时间分片配置,并逐节点应用标签以控制将哪个配置应用于哪些节点。

  1. 创建一个文件,例如 time-slicing-config-fine.yaml,内容如下例所示

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: time-slicing-config-fine
    data:
      a100-40gb: |-
        version: v1
        flags:
          migStrategy: mixed
        sharing:
          timeSlicing:
            resources:
            - name: nvidia.com/gpu
              replicas: 8
            - name: nvidia.com/mig-1g.5gb
              replicas: 2
            - name: nvidia.com/mig-2g.10gb
              replicas: 2
            - name: nvidia.com/mig-3g.20gb
              replicas: 3
            - name: nvidia.com/mig-7g.40gb
              replicas: 7
      tesla-t4: |-
        version: v1
        flags:
          migStrategy: none
        sharing:
          timeSlicing:
            resources:
            - name: nvidia.com/gpu
              replicas: 4
    
  2. 将 ConfigMap 添加到与 GPU Operator 相同的命名空间

    $ kubectl create -n gpu-operator -f time-slicing-config-fine.yaml
    
  3. 使用 ConfigMap 配置 Device Plugin 并设置默认时间分片配置

    $ kubectl patch clusterpolicies.nvidia.com/cluster-policy \
        -n gpu-operator --type merge \
        -p '{"spec": {"devicePlugin": {"config": {"name": "time-slicing-config-fine"}}}}'
    

    由于规范不包含 devicePlugin.config.default 字段,因此当 Device Plugin Pod 重新部署时,它们不会自动将时间分片配置应用于所有节点。

  4. 可选:确认 gpu-feature-discoverynvidia-device-plugin-daemonset Pod 重新启动。

    $ kubectl get events -n gpu-operator --sort-by='.lastTimestamp'
    

    示例输出

    LAST SEEN   TYPE      REASON             OBJECT                                     MESSAGE
    33s         Normal    Created            pod/nvidia-device-plugin-daemonset-cffds   Created container toolkit-validation
    33s         Normal    Started            pod/nvidia-device-plugin-daemonset-cffds   Started container toolkit-validation
    33s         Normal    Started            pod/gpu-feature-discovery-rvlg9            Started container toolkit-validation
    33s         Normal    Created            pod/gpu-feature-discovery-rvlg9            Created container toolkit-validation
    33s         Normal    Pulled             pod/gpu-feature-discovery-rvlg9            Container image "nvcr.io/nvidia/cloud-native/gpu-operator-validator:v22.9.1" already present on machine
    33s         Normal    Pulled             pod/nvidia-device-plugin-daemonset-cffds   Container image "nvcr.io/nvidia/cloud-native/gpu-operator-validator:v22.9.1" already present on machine
    32s         Normal    Created            pod/nvidia-device-plugin-daemonset-cffds   Created container config-manager-init
    32s         Normal    Pulled             pod/nvidia-device-plugin-daemonset-cffds   Container image "nvcr.io/nvidia/k8s-device-plugin:v0.13.0-ubi8" already present on machine
    32s         Normal    Pulled             pod/gpu-feature-discovery-rvlg9            Container image "nvcr.io/nvidia/k8s-device-plugin:v0.13.0-ubi8" already present on machine
    32s         Normal    Created            pod/gpu-feature-discovery-rvlg9            Created container config-manager-init
    32s         Normal    Started            pod/gpu-feature-discovery-rvlg9            Started container config-manager-init
    32s         Normal    Started            pod/nvidia-device-plugin-daemonset-cffds   Started container config-manager-init
    31s         Normal    Created            pod/gpu-feature-discovery-rvlg9            Created container config-manager
    31s         Normal    Started            pod/gpu-feature-discovery-rvlg9            Started container gpu-feature-discovery
    31s         Normal    Created            pod/gpu-feature-discovery-rvlg9            Created container gpu-feature-discovery
    31s         Normal    Pulled             pod/gpu-feature-discovery-rvlg9            Container image "nvcr.io/nvidia/gpu-feature-discovery:v0.7.0-ubi8" already present on machine
    31s         Normal    Started            pod/nvidia-device-plugin-daemonset-cffds   Started container config-manager
    31s         Normal    Created            pod/nvidia-device-plugin-daemonset-cffds   Created container config-manager
    31s         Normal    Pulled             pod/nvidia-device-plugin-daemonset-cffds   Container image "nvcr.io/nvidia/k8s-device-plugin:v0.13.0-ubi8" already present on machine
    31s         Normal    Started            pod/nvidia-device-plugin-daemonset-cffds   Started container nvidia-device-plugin
    31s         Normal    Created            pod/nvidia-device-plugin-daemonset-cffds   Created container nvidia-device-plugin
    31s         Normal    Pulled             pod/nvidia-device-plugin-daemonset-cffds   Container image "nvcr.io/nvidia/k8s-device-plugin:v0.13.0-ubi8" already present on machine
    31s         Normal    Pulled             pod/gpu-feature-discovery-rvlg9            Container image "nvcr.io/nvidia/k8s-device-plugin:v0.13.0-ubi8" already present on machine
    31s         Normal    Started            pod/gpu-feature-discovery-rvlg9            Started container config-manager
    
  5. 通过运行以下一个或多个命令,将标签应用于节点

    • 通过指定节点名称,逐个将标签应用于节点

      $ kubectl label node <node-name> nvidia.com/device-plugin.config=tesla-t4
      
    • 通过指定标签选择器,一次将标签应用于多个节点

      $ kubectl label node \
          --selector=nvidia.com/gpu.product=Tesla-T4 \
          nvidia.com/device-plugin.config=tesla-t4
      

请参阅验证 GPU 时间分片配置

在安装 NVIDIA GPU Operator 之前配置时间分片#

您可以通过在安装期间传递 devicePlugin.config.name=<config-map-name> 参数,使用 NVIDIA GPU Operator 启用时间分片。

执行以下步骤以在安装 Operator 之前配置时间分片

  1. 为 Operator 创建命名空间

    $ kubectl create namespace gpu-operator
    
  2. 创建一个文件,例如 time-slicing-config.yaml,其中包含 ConfigMap 内容。

    请参阅应用一个集群范围的配置应用多个节点特定的配置部分。

  3. 将 ConfigMap 添加到与 GPU Operator 相同的命名空间

    $ kubectl create -f time-slicing-config.yaml
    
  4. 使用 Helm 安装 Operator

    $ helm install gpu-operator nvidia/gpu-operator \
        -n gpu-operator \
        --version=v24.9.2 \
        --set devicePlugin.config.name=time-slicing-config
    
  5. 请参阅应用一个集群范围的配置应用多个节点特定的配置,并执行以下任务

    • 通过运行 kubectl patch 命令来配置 Device Plugin。

    • 如果您添加了带有节点特定配置的 ConfigMap,请将标签应用于节点。

安装后,请参阅验证 GPU 时间分片配置

更新时间分片 ConfigMap#

Operator 不会监视时间分片 ConfigMap。因此,如果您修改 ConfigMap,Device Plugin Pod 不会重新启动,也不会应用修改后的配置。

要应用修改后的 ConfigMap,请手动重新启动 Device Plugin Pod

$ kubectl rollout restart -n gpu-operator daemonset/nvidia-device-plugin-daemonset

当前正在运行的工作负载不受影响并继续运行,但 NVIDIA 建议在维护期间执行重新启动。

验证 GPU 时间分片配置#

执行以下步骤以验证时间分片配置是否已成功应用

  1. 确认节点通告了额外的 GPU 资源

    $ kubectl describe node <node-name>
    

    示例输出

    示例输出因节点中的 GPU 和您应用的配置而异。

    以下输出适用于 renameByDefault 设置为 false(默认值)时。关键考虑因素如下

    • nvidia.com/gpu.count 标签报告机器中物理 GPU 的数量。

    • nvidia.com/gpu.product 标签在产品名称中包含 -SHARED 后缀。

    • nvidia.com/gpu.replicas 标签与报告的容量匹配。

    ...
    Labels:
                      nvidia.com/gpu.count=4
                      nvidia.com/gpu.product=Tesla-T4-SHARED
                      nvidia.com/gpu.replicas=4
    Capacity:
      nvidia.com/gpu: 16
      ...
    Allocatable:
      nvidia.com/gpu: 16
      ...
    

    以下输出适用于 renameByDefault 设置为 true 时。关键考虑因素如下

    • nvidia.com/gpu.count 标签报告机器中物理 GPU 的数量。

    • nvidia.com/gpu 容量报告 0

    • nvidia.com/gpu.shared 容量等于物理 GPU 的数量乘以要创建的指定 GPU 副本的数量。

    ...
    Labels:
                      nvidia.com/gpu.count=4
                      nvidia.com/gpu.product=Tesla-T4
                      nvidia.com/gpu.replicas=4
    Capacity:
      nvidia.com/gpu:        0
      nvidia.com/gpu.shared: 16
      ...
    Allocatable:
      nvidia.com/gpu:        0
      nvidia.com/gpu.shared: 16
      ...
    
  2. 可选:部署工作负载以验证 GPU 时间分片

    • 创建一个文件,例如 time-slicing-verification.yaml,内容如下

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: time-slicing-verification
        labels:
          app: time-slicing-verification
      spec:
        replicas: 5
        selector:
          matchLabels:
            app: time-slicing-verification
        template:
          metadata:
            labels:
              app: time-slicing-verification
          spec:
            tolerations:
              - key: nvidia.com/gpu
                operator: Exists
                effect: NoSchedule
            hostPID: true
            containers:
              - name: cuda-sample-vector-add
                image: "nvcr.io/nvidia/k8s/cuda-sample:vectoradd-cuda11.7.1-ubuntu20.04"
                command: ["/bin/bash", "-c", "--"]
                args:
                  - while true; do /cuda-samples/vectorAdd; done
                resources:
                 limits:
                   nvidia.com/gpu: 1
      
    • 使用多个副本创建部署

      $ kubectl apply -f time-slicing-verification.yaml
      
    • 验证所有五个副本都在运行

      $ kubectl get pods
      

      示例输出

      NAME                                         READY   STATUS    RESTARTS   AGE
      time-slicing-verification-7cdc7f87c5-lkd9d   1/1     Running   0          23s
      time-slicing-verification-7cdc7f87c5-rrzq7   1/1     Running   0          23s
      time-slicing-verification-7cdc7f87c5-s8qwk   1/1     Running   0          23s
      time-slicing-verification-7cdc7f87c5-xhmb7   1/1     Running   0          23s
      time-slicing-verification-7cdc7f87c5-zsncp   1/1     Running   0          23s
      
    • 查看其中一个 Pod 的日志

      $ kubectl logs deploy/time-slicing-verification
      

      示例输出

      Found 5 pods, using pod/time-slicing-verification-7cdc7f87c5-s8qwk
      [Vector addition of 50000 elements]
      Copy input data from the host memory to the CUDA device
      CUDA kernel launch with 196 blocks of 256 threads
      Copy output data from the CUDA device to the host memory
      Test PASSED
      Done
      [Vector addition of 50000 elements]
      Copy input data from the host memory to the CUDA device
      CUDA kernel launch with 196 blocks of 256 threads
      Copy output data from the CUDA device to the host memory
      ...
      
    • 停止部署

      $ kubectl delete -f time-slicing-verification.yaml
      

    示例输出

    deployment.apps "time-slicing-verification" deleted
    

参考资料#