- Authors

- Name
- Phillip Pham
- @ddppham
GPU-Workloads in Kubernetes: Scheduling, Sharing und Kostenoptimierung
TL;DR
- Die durchschnittliche GPU-Auslastung liegt bei vielen Teams unter 30% -- das meiste Geld verbrennt im Leerlauf
- NVIDIA MIG partitioniert eine A100/H100 physisch in bis zu 7 isolierte Instanzen, ideal fuer Inferenz
- Time-Slicing teilt eine GPU zeitlich zwischen mehreren Pods, einfach einzurichten, aber ohne Memory-Isolation
- Der NVIDIA GPU Operator automatisiert das gesamte Setup (Driver, Device Plugin, DCGM Exporter) per Helm Chart
- Volcano Scheduler oder Kueue sind dem Default-Scheduler fuer Batch-ML-Workloads deutlich ueberlegen
Das Problem: Teure GPUs, niedrige Auslastung
Eine NVIDIA A100 kostet circa 15.000 EUR. Eine H100 liegt bei 30.000 EUR und mehr. Trotzdem zeigen Monitoring-Daten bei vielen Teams eine durchschnittliche Auslastung von 20-35%. Der Grund ist strukturell.
ML-Workloads laufen in Schueben: Training laeuft Stunden bis Tage, dann folgen Phasen der Evaluation und Datenaufbereitung, in denen die GPU idle ist. Inferenz-Workloads brauchen oft nur einen Bruchteil der verfuegbaren Rechenleistung. Und der Default-Kubernetes-Scheduler vergibt GPUs als unteilbare Einheit -- ein Pod bekommt eine ganze GPU oder keine.
Die Loesung liegt in drei Bereichen: besseres Scheduling, GPU-Sharing und systematisches Monitoring.
GPU-Grundlagen in Kubernetes
Bevor wir in die Optimierung einsteigen, hier die Basis. Kubernetes kennt GPUs nicht nativ. Die Integration laeuft ueber Device Plugins.
Installation des NVIDIA GPU Operators per Helm:
# Helm Repo hinzufuegen
helm repo add nvidia https://helm.ngc.nvidia.com/nvidia
helm repo update
# GPU Operator installieren (beinhaltet Driver, Device Plugin, DCGM Exporter)
helm install gpu-operator nvidia/gpu-operator \
--namespace gpu-operator \
--create-namespace \
--set operator.defaultRuntime=containerd \
--set mig.strategy=mixed \
--set devicePlugin.config.name=gpu-feature-config
Der GPU Operator installiert automatisch den NVIDIA-Treiber, das Device Plugin, den DCGM Exporter fuer Metriken und optional MIG-Konfiguration. Nach der Installation koennt ihr GPUs in Pod-Specs anfordern:
apiVersion: v1
kind: Pod
metadata:
name: cuda-test
spec:
restartPolicy: OnFailure
containers:
- name: cuda-vectoradd
image: nvcr.io/nvidia/k8s/cuda-sample:vectoradd-cuda12.2.0
resources:
limits:
nvidia.com/gpu: 1
Ohne Device Plugin sieht der Scheduler die GPU-Ressourcen nicht. Das ist der haeufigste Fehler bei der Ersteinrichtung.
GPU-Sharing: Drei Ansaetze im Vergleich
Es gibt mehrere Wege, eine physische GPU zwischen Pods zu teilen. Jeder hat andere Trade-offs.
| Eigenschaft | MIG (Multi-Instance GPU) | Time-Slicing | MPS (Multi-Process Service) |
|---|---|---|---|
| GPU-Modelle | A100, H100, A30 | Alle NVIDIA GPUs | Alle NVIDIA GPUs (Volta+) |
| Isolation | Physisch (Memory + Compute) | Keine | Nur Compute |
| Max. Partitionen | 7 (bei A100) | Beliebig (per Config) | Beliebig |
| Memory-Isolation | Ja | Nein | Nein |
| Fehler-Isolation | Ja | Nein | Nein |
| Overhead | Minimal | Context-Switching | Gering |
| Typischer Einsatz | Inferenz, Multi-Tenant | Entwicklung, kleine Jobs | Inferenz mit vielen kleinen Modellen |
| Konfigurationsaufwand | Hoch | Niedrig | Mittel |
MIG: Physische Partitionierung
MIG ist der sicherste Ansatz, weil jede Partition eigenen dedizierten GPU-Speicher und eigene Compute-Einheiten bekommt. Ein Out-of-Memory in einer Partition betrifft die anderen nicht.
# MIG-Konfiguration ueber den GPU Operator (ConfigMap)
apiVersion: v1
kind: ConfigMap
metadata:
name: mig-config
namespace: gpu-operator
data:
config.yaml: |
version: v1
mig-configs:
all-balanced:
- devices: all
mig-enabled: true
mig-devices:
"3g.40gb": 2
"1g.10gb": 1
all-inference:
- devices: all
mig-enabled: true
mig-devices:
"1g.10gb": 7
Die Konfiguration all-inference teilt eine A100 (80GB) in 7 kleine Instanzen mit je 10GB. Fuer Inferenz-Workloads, die nur 2-4GB VRAM brauchen, ist das ein enormer Effizienzgewinn.
Pods fordern dann spezifische MIG-Partitionen an:
resources:
limits:
nvidia.com/mig-1g.10gb: 1
Time-Slicing: Der einfache Weg
Time-Slicing ist deutlich einfacher einzurichten und funktioniert mit jeder NVIDIA-GPU. Der GPU Operator verwaltet die Konfiguration:
# Time-Slicing ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: time-slicing-config
namespace: gpu-operator
data:
any: |-
version: v1
flags:
migStrategy: none
sharing:
timeSlicing:
renameByDefault: false
failRequestsGreaterThanOne: false
resources:
- name: nvidia.com/gpu
replicas: 4
Mit replicas: 4 kann jede physische GPU von bis zu 4 Pods gleichzeitig genutzt werden. Der Scheduler sieht statt einer GPU nun vier nvidia.com/gpu-Ressourcen pro Node.
Der Haken: Es gibt keine Memory-Isolation. Wenn Pod A 40GB VRAM belegt und Pod B ebenfalls 40GB anfordert, stuerzt einer von beiden mit OOM ab. Time-Slicing eignet sich daher vor allem fuer Entwicklungsumgebungen oder Workloads mit bekanntem, geringem Memory-Bedarf.
Scheduling: Ueber den Default-Scheduler hinaus
Der Standard-Kube-Scheduler behandelt GPUs wie jede andere Ressource: First-come-first-served. Fuer ML-Workloads ist das ungenuegend. Typische Anforderungen, die der Default-Scheduler nicht abdeckt:
- Gang Scheduling: Verteiltes Training braucht N Pods gleichzeitig, nicht nacheinander
- Fair Sharing: Mehrere Teams teilen sich GPU-Nodes, keines soll die anderen aushungern
- Preemption: Ein dringendes Inferenz-Deployment soll weniger wichtige Training-Jobs verdraengen koennen
- Bin Packing: GPUs auf moeglichst wenigen Nodes konzentrieren, um die restlichen Nodes herunterzufahren
Kueue: Der Kubernetes-native Ansatz
Kueue ist seit Kubernetes 1.27 der empfohlene Weg fuer Job-Queueing. Es integriert sich nahtlos mit dem bestehenden Scheduler:
# ClusterQueue definiert das Budget
apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
name: gpu-cluster-queue
spec:
namespaceSelector: {}
resourceGroups:
- coveredResources: ["cpu", "memory", "nvidia.com/gpu"]
flavors:
- name: a100-flavor
resources:
- name: "nvidia.com/gpu"
nominalQuota: 8
- name: "cpu"
nominalQuota: 64
- name: "memory"
nominalQuota: 256Gi
---
# LocalQueue fuer ein Team
apiVersion: kueue.x-k8s.io/v1beta1
kind: LocalQueue
metadata:
name: ml-team-queue
namespace: ml-training
spec:
clusterQueue: gpu-cluster-queue
Jobs werden ueber die Queue eingereicht und automatisch eingeplant, sobald Ressourcen verfuegbar sind. Kueue unterstuetzt Fair Sharing zwischen Teams und Preemption basierend auf Prioritaeten.
Monitoring: GPU-Metriken mit DCGM
Ohne Monitoring wisst ihr nicht, ob eure Optimierungen wirken. Der DCGM Exporter (im GPU Operator enthalten) liefert Prometheus-Metriken.
Die wichtigsten Metriken:
# GPU Utilization (Compute) -- Ziel: >70%
DCGM_FI_DEV_GPU_UTIL
# Memory Utilization -- zeigt, ob MIG/Time-Slicing sinnvoll waere
DCGM_FI_DEV_FB_USED
DCGM_FI_DEV_FB_FREE
# GPU Temperature -- fuer Capacity Planning relevant
DCGM_FI_DEV_GPU_TEMP
# Power Usage -- direkt in Kosten umrechenbar
DCGM_FI_DEV_POWER_USAGE
Ein Grafana-Dashboard mit diesen vier Metriken zeigt euch innerhalb einer Woche, wo das groesste Optimierungspotenzial liegt. Typischerweise findet ihr heraus, dass 2-3 Workloads die GPUs dominieren und der Rest nur sporadisch zugreift.
Kostenbetrachtung: Was die Optimierung bringt
Nehmen wir ein konkretes Beispiel. Ein Team betreibt 4 NVIDIA A100 GPUs On-Premise:
| Position | Ohne Optimierung | Mit MIG + Kueue |
|---|---|---|
| Auslastung (Durchschnitt) | 28% | 72% |
| Effektive GPU-Kapazitaet | 1.12 GPU-Aequivalente | 2.88 GPU-Aequivalente |
| Geplante Neubeschaffung | 2 GPUs in 6 Monaten | Nicht noetig in 18 Monaten |
| Vermiedene Kosten (Hardware) | -- | 30.000 EUR |
| Stromkosten (jaehrlich) | 8.400 EUR | 6.200 EUR |
| Wartezeit auf GPU (P95) | 4.2 Stunden | 35 Minuten |
Die Hardware-Ersparnis allein rechtfertigt den Aufwand. Dazu kommt die hoehere Entwicklerproduktivitaet: Wenn ML-Engineers nicht mehr Stunden auf freie GPUs warten, sondern in Minuten loslegen koennen, ist das ein erheblicher Produktivitaetsgewinn.
Schrittweiser Rollout
Woche 1-2: GPU Operator installieren, DCGM Exporter einrichten, Baseline-Metriken erheben. Wo steht die aktuelle Auslastung?
Woche 3-4: Time-Slicing fuer Entwicklungs-Namespaces aktivieren. Das ist der schnellste Weg zu mehr Effizienz mit geringstem Risiko.
Woche 5-8: MIG fuer Inferenz-Workloads konfigurieren. Kueue installieren und Training-Jobs auf Queue-basiertes Scheduling umstellen.
Woche 9-12: Ergebnisse auswerten. Grafana-Dashboards fuer GPU-Kosten-Tracking aufbauen. Dokumentation und Runbooks fuer das Team erstellen.
Weitergehende Themen
- GPU-Cluster Architektur -- Hardware-Planung und Node-Setup
- GPU Monitoring im Detail -- DCGM, Grafana Dashboards, Alerting
- Capacity Planning -- Ressourcenplanung ueber GPUs hinaus
- MLOps auf Kubernetes -- Die gesamte ML-Pipeline
- Hosting-Kosten im Vergleich -- Cloud vs. On-Premise TCO
Fazit
GPU-Optimierung in Kubernetes ist kein Hexenwerk, erfordert aber ein systematisches Vorgehen. Der GPU Operator nimmt euch den groessten Teil der Basisarbeit ab. MIG und Time-Slicing adressieren das Sharing-Problem, Kueue loest die Scheduling-Herausforderungen.
Der wichtigste erste Schritt ist Monitoring: Ohne Daten ueber die aktuelle Auslastung optimiert ihr im Blindflug. Installiert den DCGM Exporter, schaut euch die Zahlen eine Woche lang an, und die sinnvollsten naechsten Schritte werden offensichtlich.
Wenn ihr Hilfe bei der Planung eures GPU-Setups oder bei der Migration bestehender ML-Workloads auf Kubernetes braucht, meldet euch bei uns unter /kontakt.
Kubernetes-Beratung gesucht?
Wir helfen deutschen Unternehmen bei der Kubernetes-Implementierung, Migration und Optimierung. DSGVO-konform und praxiserprobt.
📖 Verwandte Artikel
Weitere interessante Beiträge zu ähnlichen Themen
GPU Workloads auf Kubernetes: NVIDIA Device Plugin einrichten
GPU-Workloads auf Kubernetes betreiben: NVIDIA Device Plugin installieren, GPU-Scheduling mit Taints und Affinity konfigurieren und mit DCGM überwachen.
CUDA Cores Vergleich: GPU-Auswahl für ML auf Kubernetes
NVIDIA A100 vs H100 vs T4 im Vergleich: Welche GPU für welchen ML-Workload auf Kubernetes und wie ein AI-Startup damit 180.000 Euro jährlich spart.
Kubernetes GPU Cluster aufbauen: Setup und Betrieb
Kubernetes GPU Cluster für AI/ML-Workloads einrichten: NVIDIA GPU Operator, GPU Sharing mit MIG und Kostenoptimierung für deutsche Unternehmen.
KI-Qualitätskontrolle auf Kubernetes mit YOLO
KI-gestützte Qualitätskontrolle auf Kubernetes: Kamera-zu-Inference-Pipeline mit YOLO und Triton Inference Server für automatisierte Defekterkennung.
YOLO Inference auf Kubernetes: GPU-Scheduling und Autoscaling
YOLO-Modelle als skalierbare Inference-Services auf Kubernetes deployen: NVIDIA Device Plugin, GPU-Scheduling, HPA mit Custom Metrics und Triton Serving.