提升GPU利用率:探索NVIDIA的MIG与MPS虚拟化技术

图片

1. 背景

目前GPU卡资源紧张且业务需求逐渐递增,存在整卡不够分配或GPU利用率低造成资源浪费的情况。

我们也不可否认还有非常多的应用场景对算力的需求不大,比如:

  • AI推理场景,基本都是在线实时计算,要求延时低,batchsize小,计算量不大。

  • AI开发机场景,团队内部共享GPU,对算力要求低。

这些场景的分布非常广泛,在这些场景下,AI应用是无法把GPU强大的计算能力全部发挥出来的。所以,长期以来,很多用户的GPU利用率都不高,基本都只有10%-30%。

GPU的切分(虚拟化)需求基本来自于两个方面,一个是普通消费者,二个是计算/服务中心。

对于普通消费者(用户),希望使用到新推出的GPU特性,比如某些高性能的CUDA操作,而这些操作只有高版本的硬件SM才具备;同时,很多情况下消费者并不能用满一整张显卡(比如V100或者A100)的所有资源;另外“数据中心”类的GPU产品,价格都比较高(V100、A100都是wRMB为单位)。所以消费者在使用、价格方面有小资源高性能的GPU需求。

img

img

某购物平台上面的GPU价格

对于服务厂商(比如云服务),一方面需要提供价格便宜、性能稳定的GPU给用户使用。由于整卡的成本价格高,所以服务费用(租金)不会太低。另一个方面,大型的计算中心需要管理成千上万的GPU,服务厂商有提升集群利用率的诉求,小规格的GPU资源能够提升配置的细粒度,从而能够更好的提升集群GPU利用率。

目前,对于像V100这样的GPU,有些厂商会让多个用户来共用一张GPU,从而降低单个用户的费用。在共享GPU过程中,一个重要的操作就是虚拟化,但是虚拟化在安全问题、服务质量上面还有较大的进步空间。

2. GPU Share策略方案

①MIG(MULTI-INSTANCE****GPU****)

随着AMPERE架构的发布,NVIDIA推出了划时代的产品–A100,性能达到前所未有的高度。从性能压榨的角度讲,普通的一个AI应用要想把全部A100性能发挥出来是很难的。反过来说,大量资源没用上,闲置就是浪费。

因此,MIG(multi-Instance GPU)就这样应运而生了。

MIG 打破了原有 GPU 资源的分配方式,能够基于 A100 从硬件层面把一个GPU切分成最多 7 个 GPU 实例,并且可以使每一个 GPU 实例都能够拥有各自的 SMs 和内存系统。简单理解就是现在可以并发的同时跑7个不同的AI应用,最大程度把强大的GPU资源全部用上。

由于是基于硬件切分的方式,MIG可以让每个GPU实例之间的内存空间访问互不干扰,保障每一个使用者的工作时延和吞吐量都是可预期的。

img

img

由于采用的是硬切分的方式,GPU实例之间的隔离度很好,但是灵活度就比较受限了。MIG的切分方式,每个GPU实例的大小只能按照固定的profile来切分:

img

img

这个表格清晰的展示了各种不同大小的 GPU 实例他们具备的流处理器比例、内存比例、以及可以分配的数量。

各种profile的组合方式也是非常有限的,如下图所示:

img

img

②MPS(MULTI-PROCESS SERVICE )

MPS,包含在CUDA工具包中的多进程服务。它是一组可以替换的,二进制兼容的CUDA API实现,包括3个模块:

  • 守护进程,用于启动或停止MPS服务进程, 同时也负责为用户进程和服务进程之间建立连接关系

  • 服务进程, 多个用户在单个GPU上面的共享连接,为多个用户之间执行并发的服务

  • 用户运行时,集成在CUDA driver库中,对于CUDA应用程序来说,调用过程透明

当用户希望在多进程条件下发挥GPU的并发能力,就可以使用MPS。MPS允许多个进程共享同一个GPU context。这样可以避免上下文切换造成的额外的开销,以及串行化执行带来的时间线拉长。同时,MPS还允许不同进程的kernel和memcpy操作在同一GPU上并发执行,以实现最大化GPU利用率 。

具体可以用下面2个图片对比来说明MPS的特点。

首先,在没有开启MPS的情况下,有两个进程A(蓝色)和B(红色),每个进程都有自己的CUDA context。从图中可以看到,两个进程虽然同时被发送,但是在实际执行中是被串行执行的,两个进程会被GPU中的时间片轮转调度机制轮流调度进GPU进行执行。这就是执行的时间线被拉长的原因。

img

img

继续往下看,如果我们开启了MPS,同样是启动两个进程A(蓝色)和B(红色),MPS服务进程会将它们两个CUDA context融合到一个CUDA context里面。这就是最大的不同。两个context融合到一个之后,GPU上不存在context轮转切换,减少额外开销;而且从时间片上来看的话,进程A和B的函数是真正的实现了并发执行的。这就是MPS带来的好处。

img

img

MPS的好处是显而易见的,可以提升GPU利用率,减少GPU上下文切换时间、减少GPU上下文存储空间。总的来说,就是可以充分利用GPU资源。那么,这么好的技术,为什么在业界用得很少呢?

因为MPS的context融合方式会带来一个严重的问题:错误会互相影响。一个进程错误退出(包括被kill),如果该进程正在执行kernel,那么和该进程共同share IPC和UVM的其他进程也会一同出错退出。因此无法在生产场景上大规模使用。

一个节点只能指定一种GPU Share策略

GPU Share策略

说明

备注

MPS

多个进程或应用程序共享同一个GPU的资源,适用于需要并行处理大量数据或执行复杂计算任务的应用场景。

单卡均分;可指定一个节点中多少张卡进行拆分;备注:均分份数—》拆分卡数GPU:1/4 * NVIDIA Ampere A100显存:1/4 * 24GB 或 6GB

MIG

在一个物理GPU上同时运行多个独立的GPU实例,不会相互干扰。

支持每个卡都可以指定一种MIG策略;单卡:排序固定,最多7切分:7*14+2+14+1+1+1...img NVIDIA Ampere A100(3g.40gb)

GPU卡型

MIG**(仅支持A系列、H系列的卡型)**

NVIDIA Ampere A800(80G)

7 * (1g.10gb)4 * (1g.20gb)3 * (2g.20gb)2 * (3g.40gb)1 * (4g.40gb)1 * (7g.80gb)1 * (1g.12gb)、1 * (2g.24gb)、1 * (3g.47gb)2 * (1g.10gb)、1 * (2g.20gb)、1 * (3g.40gb)

NVIDIA Ampere 4090(24G)

不支持

NVIDIA Ampere A100(40G)

7 * (1g.5gb)3 * (2g.10gb)2 * (3g.20gb)1* (4g.20gb)1 * (7g.40gb)

NVIDIA Ampere A30(24G)

4 * (1g.6gb)2 * (2g.12gb)1 * (4g.24gb)2 * (1g.6gb)、1 * (2g.12gb)

NVIDIA GeForce 3090

不支持

3. 实践与测试

方案一(MPS)

源自nvidia官方开源项目 https://github.com/nvidia/k8s-device-plugin

nvidia-device-plugin 支持两种gpu-share的方式,分别为 “时间片” 和 “mps”,二者不兼容,只能二选其一。

  1. 时间片方案:应用可以完整使用GPU内存,各应用采用时间片的方式共享GPU计算能力,各应用间内存不隔离(直接放弃这个方案)。

  2. MPS方案:GPU卡被MPS DAEMON 托管,按照拆分SHARE的副本数,均分GPU memory,应用使用GPU memory超过均分值后 OOM,各个进程间GPU memory隔离,GPU的计算能力也按照比例拆分(还是基于时间片的)。

相对而言,MPS方案在隔离性和资源分配方面更具优势,本次验证主要做MPS的,没有做时间片的。

支持的粒度:可以支持到单节点级别,只对某个节点开启MPS或者时间片的SHARE。

启用MPS操作

安装nvidia-device-plugin的时候,启用第二配置,默认配置不开启mps或者时间片,在第二配置中启用gpu-share。

nvidia-device-plugin 支持到具体节点开启mps,如果某个节点需要开启MPS,需要在节点上打对应标签开启。

nvidia.com/mps.capable 决定是否在节点启用MPS, 例如:true

nvidia.com/device-plugin.config 决定当前节点使用的配置名字。例如:config1

理论上配置个数是没限制的,单集群下,可以做多个配置,例如 nvidia-share-4,nvidia-share-2,按照不同的业务需求,对不同的节点按照不同比例拆分。

一个完整的部署编排yaml实例如下(yaml中的节点亲和性和容忍,按需修改即可):

---
# Source: nvidia-device-plugin/templates/service-account.yml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nvidia-device-plugin-service-account
  namespace: kube-system
  labels:
    helm.sh/chart: nvidia-device-plugin-0.15.0-rc.2
    app.kubernetes.io/name: nvidia-device-plugin
    app.kubernetes.io/version: "0.15.0-rc.2"
    app.kubernetes.io/managed-by: Helm
---
# Source: nvidia-device-plugin/templates/configmap.yml
apiVersion: v1
kind: ConfigMap
metadata:
  name: nvidia-device-plugin-configs
  namespace: kube-system
  labels:
    helm.sh/chart: nvidia-device-plugin-0.15.0-rc.2
    app.kubernetes.io/name: nvidia-device-plugin

    app.kubernetes.io/version: "0.15.0-rc.2"
    app.kubernetes.io/managed-by: Helm
data:
  config0: |-
    version: v1

  config1: |-
    version: v1
    sharing:
      mps:
        renameByDefault: true
        resources:
        - name: nvidia.com/gpu
          replicas: 2
---
# Source: nvidia-device-plugin/templates/role.yml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: nvidia-device-plugin-role
  namespace: kube-system
  labels:
    helm.sh/chart: nvidia-device-plugin-0.15.0-rc.2
    app.kubernetes.io/name: nvidia-device-plugin

    app.kubernetes.io/version: "0.15.0-rc.2"
    app.kubernetes.io/managed-by: Helm
rules:
  - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["get""list""watch"]
---
# Source: nvidia-device-plugin/templates/role-binding.yml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: nvidia-device-plugin-role-binding
  namespace: kube-system
  labels:
    helm.sh/chart: nvidia-device-plugin-0.15.0-rc.2
    app.kubernetes.io/name: nvidia-device-plugin
    app.kubernetes.io/version: "0.15.0-rc.2"
    app.kubernetes.io/managed-by: Helm
subjects:
  - kind: ServiceAccount
    name: nvidia-device-plugin-service-account
    namespace: kube-system
roleRef:
  kind: ClusterRole
  name: nvidia-device-plugin-role
  apiGroup: rbac.authorization.k8s.io
---
# Source: nvidia-device-plugin/templates/daemonset-device-plugin.yml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: nvidia-device-plugin
  namespace: kube-system
  labels:
    helm.sh/chart: nvidia-device-plugin-0.15.0-rc.2
    app.kubernetes.io/name: nvidia-device-plugin
    app.kubernetes.io/version: "0.15.0-rc.2"
    app.kubernetes.io/managed-by: Helm
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: nvidia-device-plugin
  updateStrategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        app.kubernetes.io/name: nvidia-device-plugin
      annotations:
        checksum/config: 5cae25ed78745124db43b014773455550cf9c60962da45074548790b2acb66f0
    spec:
      priorityClassName: system-node-critical
      securityContext:
        {}
      serviceAccountName: nvidia-device-plugin-service-account
      shareProcessNamespace: true
      initContainers:
      - image: nvcr.io/nvidia/k8s-device-plugin:v0.17.1
        name: nvidia-device-plugin-init
        command: ["config-manager"]
        env:
        - name: ONESHOT
          value: "true"
        - name: KUBECONFIG
          value: ""
        - name: NODE_NAME
          valueFrom:
            fieldRef:
              fieldPath: "spec.nodeName"
        - name: NODE_LABEL
          value: "nvidia.com/device-plugin.config"
        - name: CONFIG_FILE_SRCDIR
          value: "/available-configs"
        - name: CONFIG_FILE_DST
          value: "/config/config.yaml"
        - name: DEFAULT_CONFIG
          value: "config0"
        - name: FALLBACK_STRATEGIES
          value: "named,single"
        - name: SEND_SIGNAL
          value: "false"
        - name: SIGNAL
          value: ""
        - name: PROCESS_TO_SIGNAL
          value: ""
        volumeMounts:
          - name: available-configs
            mountPath: /available-configs
          - name: config
            mountPath: /config
      containers:
      - image: nvcr.io/nvidia/k8s-device-plugin:v0.17.1
        name: nvidia-device-plugin-sidecar
        command: ["config-manager"]
        env:
        - name: ONESHOT
          value: "false"
        - name: KUBECONFIG
          value: ""
        - name: NODE_NAME
          valueFrom:
            fieldRef:
              fieldPath: "spec.nodeName"
        - name: NODE_LABEL
          value: "nvidia.com/device-plugin.config"
        - name: CONFIG_FILE_SRCDIR
          value: "/available-configs"
        - name: CONFIG_FILE_DST
          value: "/config/config.yaml"
        - name: DEFAULT_CONFIG
          value: "config0"
        - name: FALLBACK_STRATEGIES
          value: "named,single"
        - name: SEND_SIGNAL
          value: "true"
        - name: SIGNAL
          value: "1" # SIGHUP
        - name: PROCESS_TO_SIGNAL
          value: "nvidia-device-plugin"
        volumeMounts:
          - name: available-configs
            mountPath: /available-configs
          - name: config
            mountPath: /config
        securityContext:
          capabilities:
            add:
              - SYS_ADMIN
      - image: nvcr.io/nvidia/k8s-device-plugin:v0.17.1
        imagePullPolicy: IfNotPresent
        name: nvidia-device-plugin-ctr
        command: ["nvidia-device-plugin"]
        env:
          - name: MPS_ROOT
            value: "/run/nvidia/mps"
          - name: CONFIG_FILE
            value: /config/config.yaml
          - name: NVIDIA_MIG_MONITOR_DEVICES
            value: all
          - name: NVIDIA_VISIBLE_DEVICES
            value: all
          - name: NVIDIA_DRIVER_CAPABILITIES
            value: compute,utility
        securityContext:
          capabilities:
            add:
              - SYS_ADMIN
        volumeMounts:
          - name: device-plugin
            mountPath: /var/lib/kubelet/device-plugins
          # The MPS /dev/shm is needed to allow for MPS daemon health-checking.
          - name: mps-shm
            mountPath: /dev/shm
          - name: mps-root
            mountPath: /mps
          - name: cdi-root
            mountPath: /var/run/cdi
          - name: available-configs
            mountPath: /available-configs
          - name: config
            mountPath: /config
      volumes:
        - name: device-plugin
          hostPath:
            path: /var/lib/kubelet/device-plugins
        - name: mps-root
          hostPath:
            path: /run/nvidia/mps
            type: DirectoryOrCreate
        - name: mps-shm
          hostPath:
            path: /run/nvidia/mps/shm
        - name: cdi-root
          hostPath:
            path: /var/run/cdi
            type: DirectoryOrCreate
        - name: available-configs
          configMap:
            name: "nvidia-device-plugin-configs"
        - name: config
          emptyDir: {}
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: nvidia.com/gpu.present
                operator: In
                values:
                - "true"
      tolerations:
        - key: CriticalAddonsOnly
          operator: Exists
        - effect: NoSchedule
          key: nvidia.com/gpu
          operator: Exists
---
# Source: nvidia-device-plugin/templates/daemonset-mps-control-daemon.yml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: nvidia-device-plugin-mps-control-daemon
  namespace: kube-system
  labels:
    helm.sh/chart: nvidia-device-plugin-0.15.0-rc.2
    app.kubernetes.io/name: nvidia-device-plugin
    app.kubernetes.io/version: "0.15.0-rc.2"
    app.kubernetes.io/managed-by: Helm
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: nvidia-device-plugin
  updateStrategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        app.kubernetes.io/name: nvidia-device-plugin
      annotations:
        checksum/config: 5cae25ed78745124db43b014773455550cf9c60962da45074548790b2acb66f0
    spec:
      priorityClassName: system-node-critical
      securityContext:
        {}
      serviceAccountName: nvidia-device-plugin-service-account
      shareProcessNamespace: true
      initContainers:
      - image: nvcr.io/nvidia/k8s-device-plugin:v0.17.1
        name: mps-control-daemon-mounts
        command: [mps-control-daemonmount-shm]
        securityContext:
          privileged: true
        volumeMounts:
        - name: mps-root
          mountPath: /mps
          mountPropagation: Bidirectional
      - image: nvcr.io/nvidia/k8s-device-plugin:v0.17.1
        name: mps-control-daemon-init
        command: ["config-manager"]
        env:
        - name: ONESHOT
          value: "true"
        - name: KUBECONFIG
          value: ""
        - name: NODE_NAME
          valueFrom:
            fieldRef:
              fieldPath: "spec.nodeName"
        - name: NODE_LABEL
          value: "nvidia.com/device-plugin.config"
        - name: CONFIG_FILE_SRCDIR
          value: "/available-configs"
        - name: CONFIG_FILE_DST
          value: "/config/config.yaml"
        - name: DEFAULT_CONFIG
          value: "config0"
        - name: FALLBACK_STRATEGIES
          value: "named,single"
        - name: SEND_SIGNAL
          value: "false"
        - name: SIGNAL
          value: ""
        - name: PROCESS_TO_SIGNAL
          value: ""
        volumeMounts:
          - name: available-configs
            mountPath: /available-configs
          - name: config
            mountPath: /config
      containers:
        TODO: How do we synchronize the plugin and control-daemon on restart.
        - image: nvcr.io/nvidia/k8s-device-plugin:v0.17.1
          name: mps-control-daemon-sidecar
          command: ["config-manager"]
          env:
          - name: ONESHOT
            value: "false"
          - name: KUBECONFIG
            value: ""
          - name: NODE_NAME
            valueFrom:
              fieldRef:
                fieldPath: "spec.nodeName"
          - name: NODE_LABEL
            value: "nvidia.com/device-plugin.config"
          - name: CONFIG_FILE_SRCDIR
            value: "/available-configs"
          - name: CONFIG_FILE_DST
            value: "/config/config.yaml"
          - name: DEFAULT_CONFIG
            value: "config0"
          - name: FALLBACK_STRATEGIES
            value: "named,single"
          - name: SEND_SIGNAL
            value: "true"
          - name: SIGNAL
            value: "1"
          - name: PROCESS_TO_SIGNAL
            value: "/usr/bin/mps-control-daemon"
          volumeMounts:
            - name: available-configs
              mountPath: /available-configs
            - name: config
              mountPath: /config
        - image: nvcr.io/nvidia/k8s-device-plugin:v0.17.1
          imagePullPolicy: IfNotPresent
          name: mps-control-daemon-ctr
          command: [mps-control-daemon]
          env:
          - name: NODE_NAME
            valueFrom:
              fieldRef:
                apiVersion: v1
                fieldPath: spec.nodeName
          - name: CONFIG_FILE
            value: /config/config.yaml
          - name: NVIDIA_MIG_MONITOR_DEVICES
            value: all
          - name: NVIDIA_VISIBLE_DEVICES
            value: all
          - name: NVIDIA_DRIVER_CAPABILITIES
            value: compute,utility
          securityContext:
            privileged: true
          volumeMounts:
          - name: mps-shm
            mountPath: /dev/shm
          - name: mps-root
            mountPath: /mps
          - name: available-configs
            mountPath: /available-configs
          - name: config
            mountPath: /config
      volumes:
      - name: mps-root
        hostPath:
          path: /run/nvidia/mps
          type: DirectoryOrCreate
      - name: mps-shm
        hostPath:
          path: /run/nvidia/mps/shm
      - name: available-configs
        configMap:
          name: "nvidia-device-plugin-configs"
      - name: config
        emptyDir: {}
      nodeSelector:
        # We only deploy this pod if the following sharing label is applied.
        nvidia.com/mps.capable: "true"
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: nvidia.com/gpu.present
                operator: In
                values:
                - "true"
      tolerations:
        - key: CriticalAddonsOnly
          operator: Exists
        - effect: NoSchedule
          key: nvidia.com/gpu
          operator: Exists
验证

我们的测试集群只有一个节点,有8张4090卡,单卡GPU Memory为 24G。我们采用MPS方案,将卡副本SHARE为两份,理论上集群节点上看到的 nvidia.com/gpu.shared 资源为16个,单个资源的可用GPU Memory为12G左右。

  1. 部署yaml到集群

img

img

img

img

部署成功,节点资源如预期,GPU卡的两倍(因为我们之SHARE了2)。

  1. 部署一个使用shared资源的容器(它只能使用一个shared设备)

img

img

img

img

部署成功。

  1. 验证GPU MEMORY

初始状态

img

img

由于理论上线为12G,我们这儿创建 (1024, 1024, 1024) 类型为fp32的tensor的size为 4G

为了不超过12G,测试 (1024, 1024, 1024) * 2 + (1024,1024,512)理论上不会OOM;

为了达到12G,测试(1024, 1024, 1024) * 3 就应该OOM。

  1. (1024,1024,1024) * 2 + (1024,1024,1024)结果,如预期。

img

img

  1. (1024, 1024, 1024) * 3,结果如预期。

img

img

性能

测试脚本

import torch
import time

# size of tensor
w = 10000000

def compute_pi(n):
    count = 0
    total =  n // w
    print(f"total is {total}")
    for idx in range(total):
        print(f"{idx}/{total}")
        x = torch.rand(w, device='cuda') * 2 - 1
        y = torch.rand(w, device='cuda') * 2 - 1
        distance = x**2 + y **2
        count += (distance <= 1).sum().item()
    return 4 * count / n

n = w * 100000
s = time.time()
pi = compute_pi(n)
e = time.time()

print(f"total {e - s}")
print(f"pi is {pi}")

测试方案: 直接在主机上,在没有开启MPS的卡上先后进行多进程并行计算和多进程串行计算,得到计算时间。

再在mps的卡上,进行同样的计算,对比两种方案的计算耗时。

在测试机器上(4090显卡)开启mps后,同时在一个卡上执行测试脚本,分别看两个实例同时执行脚本的耗时和单独执行的耗时,进行对比结果。

  1. 在开启MPS的卡上,两个进程并行计算。

实例1,计算用时 193 秒

img

img

实例2,计算用时 193秒

img

img

GPU使用率,可以看到GPU开启MPS之后是 Exclusive 模式,GPU利用率 100%

img

img

  1. 在开启MPS的卡上,串行运行计算任务(就是两个任务先后计算)

实例1,计算耗时74秒

img

img

实例2, 计算耗时74秒

img

img

GPU使用率截图

img

img

  1. 在没开启MPS的卡上,进行并行计算

实例1,用时188秒

img

img

实例2,用时188秒

img

img

  1. 串行计算,耗时71秒

img

img

结论:开启MPS的性能影响不大,开启后193 ,未开启188, 性能损失 = 2%

适用场景
  1. 小模型

  2. 计算量不是很大,QPS不是很高的情况下。

卡级别的MPS开启

目前官方nvidia-device-plugin没有支持单机上指定卡开启mps,(场景,单机8卡,四卡开MPS,四卡独占),源代码中,可以看到有多种resource的支持,但是参数被屏蔽了,另外就是单配置多资源的情况下,会开启多个mps daemon,导致nvidia-device-plugin容器不能正常运行。

针对这个问题,进行了单独的适配,目前已经支持了卡级别MPS的开启。需要更多大规模的测试后,再投入生产。

方案二(VGPU)

来自第四范式的vgpu方案,目前volcano(要求版本>=1.8.0)集成的也是它。 https://github.com/Project-HAMi/HAMi

其目的是为了统一算力卡的虚拟化调度,正在集成华为vNPU(这个ISSUE),值得调研和投入。

方案三(MIG)

如何管理MIG,参考了知乎(https://zhuanlan.zhihu.com/p/558046644),其中如果关注资源利用率,可以看看 vgpu 和 mig-vgpu部分的吞吐量,性能对比部分。 MIG由gi和ci组成,gi表示gpu实例,ci表示算力单元。

拆分MIG操作流程

MIG的shell操作主要包括:查看命令、创建命令和删除命令。MIG的操作都要用root权限,所以如果是非root用户,操作命令要加上sudo字符,下面操作示例中默认用户是root。 首先将这些操作例出来,然后对一些创建与删除操作进行讲解。

功能

命令

说明

【开】指定某卡 开启MIG

nvidia-smi -i 0 -mig 1

-i 指定的GPU编号 可以是0,1,3

【关】指定某卡 关闭MIG

nvidia-smi -i 0 -mig 0

【开】全部卡的MIG使能

nvidia-smi -mig 1

1 打开; 0 关闭;

【查看】子GPU实例的profile

nvidia-smi mig -lgip

获得子GPU可创建的情况

【查看】子GPU实例的placement

nvidia-smi mig -lgipp

获得子GPU可以创建的位置

【查看】子GPU上CI的profile

nvidia-smi mig -lcip

添加 -gi指定特定的子GPU,如指定子GPU 2查看上面的CI实例: nvidia-smi mig -lci -gi 2

【查看】已创建的子GPU的情况

nvidia-smi mig -lgi

【创建】子GPU + 对应的CI

nvidia-smi mig -i 0 -cgi 9 -C

-i: 指定父GPU -cgi:列出需要创建的子GPU的类型 格式:9 或者 3g.20gb 或者 MIG 3g.20gb -C :连同一起创建CI

【创建】子GPU

nvidia-smi mig -i 0 -cgi 9

创建一个profile为9的GI实例: 3个计算单元 + 20gb显存。

【创建】子GPU上面的CI

nvidia-smi mig -cci 0,1 -gi 1

-cci:创建的CI实例的编号 -gi:指定子GPU

【删除】子GPU

nvidia-smi mig -dgi -i 0 -gi 1

-i:指定父GPU -gi:待删除的子GPU

【删除】子GPU上面的CI 实例

nvidia-smi mig -dci -i 0 -gi 1 -ci 0

-i:指定父GPU -gi:待操作的子GPU -ci: 待删除的CI实例

【查看】 整个MIG实例情况

nvidia-smi -L

MIG的操作顺序概况为:

使能MIG -> 创建GI实例 -> 创建CI实例 -> 删除CI实例 -> 删除GI实例 -> 关闭MIG

img

img

  1. 检查卡类型 nvidia-smi,卡要求A系列以后

  2. 针对单卡开启MIG,nvidia-smi -i 0 -mig 1,如果出现pending的情况可能需要重启机器。

img

img

  1. 查看支持的mig profile,nvidia-smi mig -i 0 -lgip

img

img

  1. 这儿我们将0号卡拆成两个3g.40gb单元 nvidia-smi mig -i 0 -cgi 9,执行两次,创建了两。

img

img

  1. 查看创建结果 nvidia-smi mig -i 0 -lgi

img

img

  1. 查看每个GI实例支持的CI规格 nvidia-smi mig -i 0 -lcip

img

img

  1. 给mig实例创建 CI nvidia-smi mig -gi 0 -cci 2

img

img

  1. 查看最终结果 nvidia-smi

img

img

  1. 部署参考文档

https://docs.nvidia.com/datacenter/cloud-native/kubernetes/latest/index.html

  1. 测试部署容器
apiVersion: v1
kind: Pod
metadata:
  name: test1
spec:
  containers:
  - image: harbor.maip.io/base/pytorch-alpaca:v3
    imagePullPolicy: IfNotPresent
    name: test
    command:
    - /bin/bash
    - -c
    - "sleep infinity"
    resources:
      requests:
        nvidia.com/mig-3g.40gb:  1
      limits:
        nvidia.com/mig-3g.40gb:  1
---
apiVersion: v1
kind: Pod
metadata:
  name: test2
spec:
  containers:
  - image: harbor.maip.io/base/pytorch-alpaca:v3
    imagePullPolicy: IfNotPresent
    name: test
    command:
    - /bin/bash
    - -c
    - "sleep infinity"
    resources:
      requests:
        nvidia.com/mig-3g.40gb:  1
      limits:
        nvidia.com/mig-3g.40gb:  1
---
apiVersion: v1
kind: Pod
metadata:
  name: test3
spec:
  containers:
  - image: harbor.maip.io/base/pytorch-alpaca:v3
    imagePullPolicy: IfNotPresent
    name: test
    command:
    - /bin/bash
    - -c
    - "sleep infinity"
    resources:
      requests:
        nvidia.com/gpu:  1
      limits:
        nvidia.com/gpu:  1
  1. 看结果

img

img

img

img

img

img

img

img

删除mig 需要先删除 ci 删除ci

nvidia-smi mig -dci -i 0 -gi 1 -ci 0 删除gi nvidia-smi mig -dgi -i 0 -gi 1

4. gpu-operator一键部署

GPU Operator 是 NVIDIA 提供的一个 Kubernetes Operator,它简化了在 Kubernetes 集群中使用 GPU 的过程,通过自动化的方式处理 GPU 驱动程序安装、NVIDIA Device Plugin、DCGM Exporter 等组件。

helm repo add nvidia https://helm.ngc.nvidia.com/nvidia
helm repo update
helm install --wait --generate-name \
     -n gpu-operator --create-namespace \
     nvidia/gpu-operator \
     # 如果已经安装了 NVIDIA 驱动程序,可以在 GPU Operator 中禁用驱动安装,修改values.yaml
     --set driver.enabled=false
  • 其中mps和mig的配置,需要在values.yaml中devicePlugin和migManager新增,然后创建configmap
devicePlugin:
  enabled: true
  repository: nvcr.io/nvidia
  image: k8s-device-plugin
  version: v0.17.1
  imagePullPolicy: IfNotPresent
  env:
    - name: PASS_DEVICE_SPECS
      value: "true"
    - name: FAIL_ON_INIT_ERROR
      value: "true"
    - name: DEVICE_LIST_STRATEGY
      value: envvar
    - name: DEVICE_ID_STRATEGY
      value: uuid
    - name: NVIDIA_VISIBLE_DEVICES
      value: all
    - name: NVIDIA_DRIVER_CAPABILITIES
      value: all
    - name: NODE_LABEL
      value: nvidia.com/device-plugin.config
  config:
    name: device-plugin-config
    default: default
migManager:
  enabled: true
  repository: nvcr.io/nvidia/cloud-native
  image: k8s-mig-manager
  version: v0.12.1-ubuntu20.04
  imagePullPolicy: IfNotPresent
  env:
    - name: WITH_REBOOT
      value: "false"
  config:
    default: all-disabled
    name: custom-mig-parted-config
  gpuClientsConfig:
    name: ""
  • 测试用的device-plugin配置文件。
---
# Source: gpu-operator/templates/plugin_config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: device-plugin-config
  namespace: gpu-operator
  labels:
    app.kubernetes.io/name: gpu-operator
    helm.sh/chart: gpu-operator-v24.3.0
    app.kubernetes.io/instance: stable
    app.kubernetes.io/version: "v24.3.0"
    app.kubernetes.io/managed-by: Helm
data:
  default: |-
    version: v1
    flags:
      migStrategy: none
  mig-mixed: |-
    version: v1
    flags:
      migStrategy: mixed
  mig-single: |-
    version: v1
    flags:
      migStrategy: single
  config1: |-
    flags:
      migStrategy: none
    sharing:
      mps:
        renameByDefault: true
        resources:
        - devices:
          - "0"
          - "1"
          name: nvidia.com/gpu
          replicas: 2
    version: v1

测试用的mig策略配置文件

apiVersion: v1
kind: ConfigMap
metadata:
  name: custom-mig-parted-config
  namespace: gpu-operator
data:
  config.yaml: |
    version: v1
    # 针对第一个卡开启的MIG
    mig-configs:
      custom-1:
        - devices: [0]
          mig-enabled: true
          mig-devices:
            "1g.10gb": 2
            "2g.20gb": 1
            "3g.40gb": 1

      all-disabled:
        - devices: all
          mig-enabled: false

      all-enabled:
        - devices: all
          mig-enabled: true
          mig-devices: {}

      # A100-40GB, A800-40GB
      all-1g.5gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "1g.5gb"7

      all-1g.5gb.me:
        - devices: all
          mig-enabled: true
          mig-devices:
            "1g.5gb+me"1

      all-2g.10gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "2g.10gb"3

      all-3g.20gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "3g.20gb"2

      all-4g.20gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "4g.20gb"1

      all-7g.40gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "7g.40gb"1

      # H100-80GB, H800-80GB, A100-80GB, A800-80GB, A100-40GB, A800-40GB
      all-1g.10gb:
        # H100-80GB, H800-80GB, A100-80GB, A800-80GB
        - device-filter: ["0x233010DE""0x233110DE""0x232210DE""0x20B210DE""0x20B510DE""0x20F310DE""0x20F510DE"]
          devices: all
          mig-enabled: true
          mig-devices:
            "1g.10gb"7

        # A100-40GB, A800-40GB
        - device-filter: ["0x20B010DE""0x20B110DE""0x20F110DE""0x20F610DE"]
          devices: all
          mig-enabled: true
          mig-devices:
            "1g.10gb"4

      # H100-80GB, H800-80GB, A100-80GB, A800-80GB
      all-1g.10gb.me:
        - devices: all
          mig-enabled: true
          mig-devices:
            "1g.10gb+me"1

      # H100-80GB, H800-80GB, A100-80GB, A800-80GB
      all-1g.20gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "1g.20gb"4

      all-2g.20gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "2g.20gb"3

      all-3g.40gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "3g.40gb"2

      all-4g.40gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "4g.40gb"1

      all-7g.80gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "7g.80gb"1

      # A30-24GB
      all-1g.6gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "1g.6gb"4

      all-1g.6gb.me:
        - devices: all
          mig-enabled: true
          mig-devices:
            "1g.6gb+me"1

      all-2g.12gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "2g.12gb"2

      all-2g.12gb.me:
        - devices: all
          mig-enabled: true
          mig-devices:
            "2g.12gb+me"1

      all-4g.24gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "4g.24gb"1

      # H100 NVL, H800 NVL, GH200
      all-1g.12gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "1g.12gb"7

      all-1g.12gb.me:
        - devices: all
          mig-enabled: true
          mig-devices:
            "1g.12gb+me"1

      all-1g.24gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "1g.24gb"4

      all-2g.24gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "2g.24gb"3

      # H100 NVL, H800 NVL
      all-3g.47gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "3g.47gb"2

      all-4g.47gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "4g.47gb"1

      all-7g.94gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "7g.94gb"1

      # H100-96GB, PG506-96GB, GH200
      all-3g.48gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "3g.48gb"2

      all-4g.48gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "4g.48gb"1

      all-7g.96gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "7g.96gb"1

      # H100-96GB, GH200, H100 NVL, H800 NVL, H100-80GB, H800-80GB, A800-40GB, A800-80GB, A100-40GB, A100-80GB, A30-24GB, PG506-96GB
      all-balanced:
        # H100 NVL, H800 NVL
        - device-filter: ["0x232110DE""0x233A10DE"]
          devices: all
          mig-enabled: true
          mig-devices:
            "1g.12gb"1
            "2g.24gb"1
            "3g.47gb"1

        # H100-80GB, H800-80GB, A100-80GB, A800-80GB
        - device-filter: ["0x233010DE""0x233110DE""0x232210DE""0x20B210DE""0x20B510DE""0x20F310DE""0x20F510DE"]
          devices: all
          mig-enabled: true
          mig-devices:
            "1g.10gb"2
            "2g.20gb"1
            "3g.40gb"1

        # A100-40GB, A800-40GB
        - device-filter: ["0x20B010DE""0x20B110DE""0x20F110DE""0x20F610DE"]
          devices: all
          mig-enabled: true
          mig-devices:
            "1g.5gb"2
            "2g.10gb"1
            "3g.20gb"1

        # A30-24GB
        - device-filter"0x20B710DE"
          devices: all
          mig-enabled: true
          mig-devices:
            "1g.6gb"2
            "2g.12gb"1

        # H100-96GB, PG506-96GB, GH200
        - device-filter: ["0x234210DE""0x233D10DE""0x20B610DE"]
          devices: all
          mig-enabled: true
          mig-devices:
            "1g.12gb"2
            "2g.24gb"1
            "3g.48gb"1
  • 部署完成之后查看服务状态
kubectl get pod -n gpu-operator
  • 使用打对gpu节点打标签的方式开启对应的share策略

操作方法:

  1. 开启MPS策略:将节点标签 nvidia.com/device-plugin.config 的值修改成具体的MPS策略,具体策略值必须存在配置的ConfigMap中,例如上面配置中的 config1,它表示针对两个卡开启的MPS。

  2. 开启MIG策略:将节点标签 nvidia.com/device-plugin.config 的值修改成mig-mixed策略,它表示可以允许不同规格的MIG出现在一台机器上。然后还需要将节点标签 nvidia.com/mig.config 的值修改成具体的mig策略配置名字。例如上面的 custom-1,它表示针对第一个卡开启的MIG策略。

  • 开启之后的验证方式参考实践与测试

公众号:运维开发故事

github:https://github.com/orgs/sunsharing-note/dashboard

博客**:https://www.devopstory.cn**

爱生活,爱运维

我是冬子先生,《运维开发故事》公众号团队中的一员,一线运维农民工,云原生实践者,这里不仅有硬核的技术干货,还有我们对技术的思考和感悟,欢迎关注我们的公众号,期待和你一起成长!

图片

扫码二维码

关注我,不定期维护优质内容

温馨提示

如果我的文章对你有所帮助,还请帮忙点赞、在看、转发一下,你的支持会激励我输出更高质量的文章,非常感谢!

你还可以把我的公众号设为「星标」,这样当公众号文章更新时,你会在第一时间收到推送消息,避免错过我的文章更新。

........................

------本页内容已结束,喜欢请分享------

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发
运维开发故事的头像-运维开发故事

昵称

取消
昵称表情代码图片