Múlt hónapban végre rendesen megnéztem a GPU kihasználtsági dashboardot. Amit láttam, az fizikailag rosszul esett: 14 darab A100-as a klaszterben, átlagos kihasználtság 15% körül. Olyan hardverért fizettünk, ami az ideje nagy részében semmit sem csinált.
Ez kínosan gyakori jelenség. A csapatok kérnek egy teljes GPU-t olyan workloadhoz, ami 20 perces training burst-öknél használja, aztán órákig üresen áll. A Kubernetes integer erőforrásként kezeli a GPU-kat — vagy van egy egész, vagy nincs. Natívan nincs megosztás.
Így szedtem össze a pazarlás nagy részét.
A probléma: a GPU nem úgy működik, mint a CPU
CPU-nál és memóriánál a Kubernetes tud overcommitolni. A requests és limits rugalmasságot ad. GPU-knál? Semmi ilyesmi. Az nvidia.com/gpu extended resource — mindent vagy semmit:
resources:
limits:
nvidia.com/gpu: 1
Ez a pod most birtokolja az egész GPU-t. Még ha csak 2GB-ot használ a 80GB VRAM-ból, és percenként 5 másodpercig futtat inference-t.
Az allokáció vs tényleges használat közti különbséget könnyen meg lehet nézni:
# Mit gondol a Kubernetes, mi van allokálva
kubectl describe nodes | grep -A5 "nvidia.com/gpu"
# Mi történik valójában a GPU-n
kubectl exec -it <gpu-pod> -- nvidia-smi
A két szám közötti különbség — az az égő pénzed.
1. lehetőség: NVIDIA GPU Time-Slicing
A leggyorsabb győzelem. A time-slicing lehetővé teszi, hogy több pod osszon meg egy fizikai GPU-t időosztásos hozzáféréssel. Ez nem MIG (Multi-Instance GPU) — nincs memória-izoláció — de inference workloadoknál és fejlesztői környezeteknél elég jól működik.
Először frissítsd az NVIDIA device plugin konfigot:
# nvidia-device-plugin-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: nvidia-plugin-configs
namespace: kube-system
data:
config.yaml: |
version: v1
sharing:
timeSlicing:
renameByDefault: false
resources:
- name: nvidia.com/gpu
replicas: 4
Ez megmondja a device pluginnek, hogy minden fizikai GPU-t 4 virtuális szeletként hirdessen. Egy 2 GPU-s node most 8 nvidia.com/gpu-t mutat.
Alkalmazd és indítsd újra a device plugint:
kubectl apply -f nvidia-device-plugin-config.yaml
kubectl rollout restart daemonset/nvidia-device-plugin-daemonset -n kube-system
Egy perc múlva ellenőrizd a node kapacitást:
kubectl describe node gpu-node-01 | grep gpu
# nvidia.com/gpu: 8 (volt 2)
Az apró betűs rész
A time-slicing NEM izolálja a GPU memóriát. Ha az A pod lefoglal 70GB VRAM-ot egy 80GB-os kártyán, a B pod OOM-ol, amikor bármit próbál allokálni. Vagy megbízol a workloadjaidban, vagy alkalmazásszinten kell kikényszeríteni a memóriálimiteket (pl. CUDA_VISIBLE_DEVICES vagy framework szintű korlátok, mint a PyTorch max_split_size_mb).
Az inference szolgáltatásainknál ezt tettem minden deployment-be:
env:
- name: NVIDIA_MEM_LIMIT_MB
value: "16000"
És a Python kódban:
import torch
torch.cuda.set_per_process_memory_fraction(0.2) # GPU memória 20%-a
2. lehetőség: Scheduler pluginek GPU-tudatos ütemezéshez
A time-slicing durva eszköz. Okosabb allokációhoz beállítottam egy egyedi scheduler plugint, ami a tényleges GPU kihasználtságot veszi figyelembe pod-elhelyezésnél.
A scheduler-plugins projektben van egy Trimaran plugin család. Én a TargetLoadPacking-et használtam — ez megpróbálja a workloadokat a már részben használt GPU-kra pakolni ahelyett, hogy szétszórná őket.
A végső scheduler konfig:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: gpu-aware-scheduler
plugins:
score:
enabled:
- name: TargetLoadPacking
pluginConfig:
- name: TargetLoadPacking
args:
defaultRequests:
nvidia.com/gpu: "500m"
targetUtilization: 70
metricProvider:
type: Prometheus
address: http://prometheus.monitoring:9090
A lényeg: ez valós kihasználtsági metrikákat húz a Prometheusból (DCGM exporter-en keresztül) és aszerint pontozza a node-okat, amit a GPU ténylegesen csinál, nem amit a Kubernetes gondol.
Telepítsd a DCGM exporter-t, ha még nincs:
helm install dcgm-exporter gpu-helm-charts/dcgm-exporter \
--namespace monitoring \
--set serviceMonitor.enabled=true
Így elérhetők lesznek a DCGM_FI_DEV_GPU_UTIL és DCGM_FI_DEV_FB_USED metrikák a Prometheusban, és a scheduler ezeket használja.
3. lehetőség: Az „elég jó" megközelítés — kezdd a monitoringgal
Ha nem vagy kész a scheduler pluginekre (növelik az üzemeltetési komplexitást), kezdd az átláthatósággal. Nem tudod javítani, amit nem látsz.
A Grafana dashboard lekérdezésem GPU pazarlásra:
# Allokált de használatlan GPU memória (node-onként)
sum by (node) (
DCGM_FI_DEV_FB_FREE{} / DCGM_FI_DEV_FB_TOTAL{}
) * 100
És a kihasználtság időben:
# Átlagos GPU kihasználtság podonként 1 óra alatt
avg_over_time(DCGM_FI_DEV_GPU_UTIL{}[1h])
Beállítottam egy alertet, ami akkor szól, ha bármelyik GPU 2 óránál hosszabb ideje 10% alatt van munkaidőben. Ez egymagában elkapott 4 “elfelejtett” notebookot a dev namespace-ben, amiket senki sem használt.
Mennyit spóroltunk ténylegesen
Két hét time-slicing az inference node-okon + monitoring alertek után:
- GPU-k száma 14-ről 8-ra csökkent (ugyanazok a workloadok, kevesebb node)
- Havi GPU költés ~$14k-ról ~$8k-ra esett
- Átlagos kihasználtság 15%-ról 55%-ra nőtt
Nem tökéletes, de havi $6k-ból sok kávét lehet venni.
Buktatók, amikbe belefutottam
1. CUDA verzió ütközések time-slicingnál. Ha a podjaid különböző CUDA verziókat használnak és osztoznak egy GPU-n, rejtélyes driver hibákat kapsz. Egységesítsd a CUDA verziót az összes GPU workloadnál, vagy használj külön node poolokat CUDA verziónként.
2. A device plugin restart kilövi a futó GPU podokat. Ezt egy kedd délután tanultam meg a nehezebb úton. Mindig drain-eld a node-ot előtte:
kubectl drain gpu-node-01 --ignore-daemonsets --delete-emptydir-data
# most frissítsd a konfigot és indítsd újra a plugint
kubectl uncordon gpu-node-01
3. A Prometheus scrape intervallum számít. Ha metrika-alapú ütemezést használsz és a Prometheus 60 másodpercenként scrape-el, a scheduler elavult adatokat lát. A DCGM exporter GPU metrika scrape-jét letettem 15 másodpercre. Pár time series-szel több, de megéri.
4. Ne time-slice-olj training workloadokat. Komolyan. Két training job egy GPU-n mind a kettőt 3x lassabbá teszi. A time-slicing inference-re és dev notebookokra való. A training dedikált hardvert kap, kivétel nélkül.
Mi jön ezután
Nézem az NVIDIA MIG-et (Multi-Instance GPU) az A100-asainkhoz, ami valódi hardveres izolációt ad — külön memória, külön compute engine-ek. Bonyolultabb beállítani (driver szinten kell particionálni a GPU-kat), de ez a rendes megoldás multi-tenant klaszterekhez.
Egyelőre a time-slicing + monitoring + megfelelő scheduling elvisz az út 80%-án. Kezdd a monitoringgal. Nézd meg a dashboardot. Garantálom, hogy találsz pazarlást.
A legjobb optimalizáció az, amit látsz is.