1. 背景
目前GPU卡资源紧张且业务需求逐渐递增,存在整卡不够分配或GPU利用率低造成资源浪费的情况。
我们也不可否认还有非常多的应用场景对算力的需求不大,比如:
-
AI推理场景,基本都是在线实时计算,要求延时低,batchsize小,计算量不大。
-
AI开发机场景,团队内部共享GPU,对算力要求低。
这些场景的分布非常广泛,在这些场景下,AI应用是无法把GPU强大的计算能力全部发挥出来的。所以,长期以来,很多用户的GPU利用率都不高,基本都只有10%-30%。
GPU的切分(虚拟化)需求基本来自于两个方面,一个是普通消费者,二个是计算/服务中心。
对于普通消费者(用户),希望使用到新推出的GPU特性,比如某些高性能的CUDA操作,而这些操作只有高版本的硬件SM才具备;同时,很多情况下消费者并不能用满一整张显卡(比如V100或者A100)的所有资源;另外“数据中心”类的GPU产品,价格都比较高(V100、A100都是wRMB为单位)。所以消费者在使用、价格方面有小资源高性能的GPU需求。
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
由于采用的是硬切分的方式,GPU实例之间的隔离度很好,但是灵活度就比较受限了。MIG的切分方式,每个GPU实例的大小只能按照固定的profile来切分:
img
这个表格清晰的展示了各种不同大小的 GPU 实例他们具备的流处理器比例、内存比例、以及可以分配的数量。
各种profile的组合方式也是非常有限的,如下图所示:
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
继续往下看,如果我们开启了MPS,同样是启动两个进程A(蓝色)和B(红色),MPS服务进程会将它们两个CUDA context融合到一个CUDA context里面。这就是最大的不同。两个context融合到一个之后,GPU上不存在context轮转切换,减少额外开销;而且从时间片上来看的话,进程A和B的函数是真正的实现了并发执行的。这就是MPS带来的好处。
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... 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, mount-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左右。
- 部署yaml到集群
img
img
部署成功,节点资源如预期,GPU卡的两倍(因为我们之SHARE了2)。
- 部署一个使用shared资源的容器(它只能使用一个shared设备)
img
img
部署成功。
- 验证GPU MEMORY
初始状态
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)结果,如预期。
img
- (1024, 1024, 1024) * 3,结果如预期。
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 秒
img
实例2,计算用时 193秒
img
GPU使用率,可以看到GPU开启MPS之后是 Exclusive 模式,GPU利用率 100%
img
- 在开启MPS的卡上,串行运行计算任务(就是两个任务先后计算)
实例1,计算耗时74秒
img
实例2, 计算耗时74秒
img
GPU使用率截图
img
- 在没开启MPS的卡上,进行并行计算
实例1,用时188秒
img
实例2,用时188秒
img
- 串行计算,耗时71秒
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。 首先将这些操作例出来,然后对一些创建与删除操作进行讲解。
功能
命令
说明
【开】指定某卡 开启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
-
检查卡类型 nvidia-smi,卡要求A系列以后
-
针对单卡开启MIG,
nvidia-smi -i 0 -mig 1
,如果出现pending的情况可能需要重启机器。
img
- 查看支持的mig profile,
nvidia-smi mig -i 0 -lgip
img
- 这儿我们将0号卡拆成两个3g.40gb单元
nvidia-smi mig -i 0 -cgi 9
,执行两次,创建了两。
img
- 查看创建结果
nvidia-smi mig -i 0 -lgi
img
- 查看每个GI实例支持的CI规格
nvidia-smi mig -i 0 -lcip
img
- 给mig实例创建 CI
nvidia-smi mig -gi 0 -cci 2
img
- 查看最终结果
nvidia-smi
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
- 看结果
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策略
操作方法:
-
开启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策略。
- 开启之后的验证方式参考实践与测试
公众号:运维开发故事
github:https://github.com/orgs/sunsharing-note/dashboard
博客**:https://www.devopstory.cn**
爱生活,爱运维
我是冬子先生,《运维开发故事》公众号团队中的一员,一线运维农民工,云原生实践者,这里不仅有硬核的技术干货,还有我们对技术的思考和感悟,欢迎关注我们的公众号,期待和你一起成长!
扫码二维码
关注我,不定期维护优质内容
温馨提示
如果我的文章对你有所帮助,还请帮忙点赞、在看、转发一下,你的支持会激励我输出更高质量的文章,非常感谢!
你还可以把我的公众号设为「星标」,这样当公众号文章更新时,你会在第一时间收到推送消息,避免错过我的文章更新。
........................