我们从2011年坚守至今,只想做纯粹的技术论坛,请不要发布和技术无关的话题。  本站有两万多份高大上的PCB和原理图下载,每日更新中,欢迎常回来看看

 找回密码
 立即注册
搜索
查看: 291|回复: 0

[IT/数码] 提升GPU利用率:探索NVIDIA的MIG与MPS虚拟化技术

[复制链接]
  • 打卡等级:以坛为家II
  • 打卡总天数:198
  • 打卡月天数:0
  • 打卡总奖励:714
  • 最近打卡:2025-11-21 20:33:51

1940

主题

405

回帖

1712

积分

二级逆天

积分
1712
发表于 2025-5-27 23:07 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区

您需要 登录 才可以下载或查看,没有账号?立即注册

×

1. 背景

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

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

  • AI推理场景,基本都是在线实时计算,要求延时低,batchsize小,计算量不大。
  • AI开发机场景,团队内部共享GPU,对算力要求低。

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

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

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

230359508d29c6.png img

某购物平台上面的GPU价格

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

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

2. GPU Share策略方案

①MIG(MULTI-INSTANCEGPU****)

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

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

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

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

230403b6972c27.png img

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

230406b210506f.png img

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

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

23040794a3e719.png 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进行执行。这就是执行的时间线被拉长的原因。

2304099e609157.png img

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

230412741dbe3f.png 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... 2304159922106c.png 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”,二者不兼容,只能二选其一。

  • 时间片方案:应用可以完整使用GPU内存,各应用采用时间片的方式共享GPU计算能力,各应用间内存不隔离(直接放弃这个方案)。
  • 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-daemon,
        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左右。

  • 部署yaml到集群
2304199bc86a41.png img 230426aba8b111.png img

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

  • 部署一个使用shared资源的容器(它只能使用一个shared设备)
230434caae7a32.png img 2304385dc15836.png img

部署成功。

  • 验证GPU MEMORY

初始状态

230444955857a9.png img

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

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

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

  • (1024,1024,1024) * 2 + (1024,1024,1024)结果,如预期。
2304525276e44d.png img
  • (1024, 1024, 1024) * 3,结果如预期。
2305051ebc34de.png 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后,同时在一个卡上执行测试脚本,分别看两个实例同时执行脚本的耗时和单独执行的耗时,进行对比结果。

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

实例1,计算用时 193 秒

2305080a45d772.png img

实例2,计算用时 193秒

23051100d466c0.png img

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

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

实例1,计算耗时74秒

2305225c12618a.png img

实例2, 计算耗时74秒

230525c7e677e7.png img

GPU使用率截图

2305416c18b163.png img
  • 在没开启MPS的卡上,进行并行计算

实例1,用时188秒

23054506ec4523.png img

实例2,用时188秒

23054917895b04.png img
  • 串行计算,耗时71秒
2305522537278d.png img

结论:开启MPS的性能影响不大,开启后193 ,未开启188, 性能损失 = 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。 首先将这些操作例出来,然后对一些创建与删除操作进行讲解。

功能
命令
说明

【开】指定某卡 开启MIGnvidia-smi -i 0 -mig 1-i 指定的GPU编号 可以是0,1,3
【关】指定某卡 关闭MIGnvidia-smi -i 0 -mig 0

【开】全部卡的MIG使能nvidia-smi -mig 11 打开; 0 关闭;
【查看】子GPU实例的profilenvidia-smi mig -lgip获得子GPU可创建的情况
【查看】子GPU实例的placementnvidia-smi mig -lgipp获得子GPU可以创建的位置
【查看】子GPU上CI的profilenvidia-smi mig -lcip添加 -gi指定特定的子GPU,如指定子GPU 2查看上面的CI实例: nvidia-smi mig -lci -gi 2
【查看】已创建的子GPU的情况nvidia-smi mig -lgi

【创建】子GPU + 对应的CInvidia-smi mig -i 0 -cgi 9 -C-i: 指定父GPU -cgi:列出需要创建的子GPU的类型 格式:9 或者 3g.20gb 或者 MIG 3g.20gb -C :连同一起创建CI
【创建】子GPUnvidia-smi mig -i 0 -cgi 9创建一个profile为9的GI实例: 3个计算单元 + 20gb显存。
【创建】子GPU上面的CInvidia-smi mig -cci 0,1 -gi 1-cci:创建的CI实例的编号 -gi:指定子GPU
【删除】子GPUnvidia-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

2305578aed68a3.png img
  • 检查卡类型 nvidia-smi,卡要求A系列以后
  • 针对单卡开启MIG,nvidia-smi -i 0 -mig 1,如果出现pending的情况可能需要重启机器。
230615de30ff6c.png img
  • 查看支持的mig profile,nvidia-smi mig -i 0 -lgip
230619ed25dbe6.png img
  • 这儿我们将0号卡拆成两个3g.40gb单元 nvidia-smi mig -i 0 -cgi 9,执行两次,创建了两。
230623d9a0c8b5.png img
  • 查看创建结果 nvidia-smi mig -i 0 -lgi
2306279ee004ba.png img
  • 查看每个GI实例支持的CI规格 nvidia-smi mig -i 0 -lcip
230636092526ae.png img
  • 给mig实例创建 CI nvidia-smi mig -gi 0 -cci 2
230640423ab22a.png img
  • 查看最终结果 nvidia-smi
230652ffb51cba.png img
  • 部署参考文档

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

  • 测试部署容器
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
  • 看结果
230655a20f081d.png img 230708e223ff53.png img 230712baada76d.png img 230718aa9e7f46.png 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策略

操作方法:

  • 开启MPS策略:将节点标签 nvidia.com/device-plugin.config 的值修改成具体的MPS策略,具体策略值必须存在配置的ConfigMap中,例如上面配置中的 config1,它表示针对两个卡开启的MPS。
  • 开启MIG策略:将节点标签 nvidia.com/device-plugin.config 的值修改成mig-mixed策略,它表示可以允许不同规格的MIG出现在一台机器上。然后还需要将节点标签 nvidia.com/mig.config 的值修改成具体的mig策略配置名字。例如上面的 custom-1,它表示针对第一个卡开启的MIG策略。
  • 开启之后的验证方式参考实践与测试


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Copyright ©2011-2024 NTpcb.com All Right Reserved.  Powered by Discuz! (NTpcb)

本站信息均由会员发表,不代表NTpcb立场,如侵犯了您的权利请举报投诉

本站所有资料仅限参考学习交流,PCB原理图源码等所有资料不保证准确没问题,请不要用作生产。否则后果自负。

闽公网安备35062302000276号 | 闽ICP备2024076463号-1 | 论坛技术支持QQ群171867948 | 论坛问题,充值问题请联系QQ1308068381

平平安安
TOP
快速回复 返回顶部 返回列表