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.