提升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需求。
某購物平臺上面的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實例之間的內存空間訪問互不干擾,保障每一個使用者的工作時延和吞吐量都是可預期的。
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... |
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
部署成功,節點資源如預期,GPU卡的兩倍(因為我們之SHARE了2)。
- 部署一個使用shared資源的容器(它只能使用一個shared設備)
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秒
GPU使用率,可以看到GPU開啟MPS之后是 Exclusive 模式,GPU利用率 100%
img
- 在開啟MPS的卡上,串行運行計算任務(就是兩個任務先后計算)
實例1,計算耗時74秒
img
實例2, 計算耗時74秒
img
GPU使用率截圖
img
- 在沒開啟MPS的卡上,進行并行計算
實例1,用時188秒
img
實例2,用時188秒
img
- 串行計算,耗時71秒
結論:開啟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
- 這兒我們將0號卡拆成兩個3g.40gb單元
nvidia-smi mig -i 0 -cgi 9
,執行兩次,創建了兩。
- 查看創建結果
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
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策略。
- 開啟之后的驗證方式參考實踐與測試