- Authors

- Name
- Phillip Pham
- @ddppham
vLLM auf Kubernetes betreiben: GPU-effiziente LLM-Inferenz in der Praxis
TL;DR
- vLLM nutzt PagedAttention, um den GPU-Speicher (KV-Cache) aehnlich wie Virtual Memory in Betriebssystemen zu verwalten -- das bringt 2-4x bessere Speichereffizienz gegenueber naiven Ansaetzen.
- Ein einzelnes vLLM-Deployment auf Kubernetes laesst sich mit dem NVIDIA Device Plugin, Resource Requests und einem HPA fuer GPU-Metriken produktionsreif betreiben.
- Fuer Modelle ab 13B Parametern ist Tensor Parallelism ueber mehrere GPUs noetig -- vLLM unterstuetzt das nativ.
- Monitoring ueber Prometheus mit dem DCGM Exporter ist Pflicht, um GPU-Auslastung und Inferenz-Latenz im Blick zu behalten.
- Die OpenAI-kompatible API von vLLM macht die Integration in bestehende Anwendungen trivial.
Warum vLLM statt anderer Serving Frameworks
Wer LLMs in Produktion bringen will, steht vor einer Auswahl an Serving-Frameworks: TGI (Text Generation Inference) von Hugging Face, Triton Inference Server von NVIDIA, oder eben vLLM. Die Entscheidung haengt vom konkreten Use Case ab.
vLLM hat sich in den letzten Monaten als Standard fuer reine Text-Generierung etabliert. Der Grund ist PagedAttention: Der KV-Cache wird nicht als zusammenhaengender Speicherblock allokiert, sondern in Seiten aufgeteilt. Das vermeidet Fragmentierung und erlaubt dynamisches Batching mit deutlich hoeherer GPU-Auslastung.
In der Praxis bedeutet das: Wo ein naiver Ansatz bei 8 gleichzeitigen Anfragen an die Speichergrenze stoesst, schafft vLLM oft 20-30 Anfragen auf derselben Hardware.
| Feature | vLLM | TGI | Triton |
|---|---|---|---|
| PagedAttention | Ja | Nein (Flash Attention) | Nein (pluggable) |
| OpenAI-kompatible API | Ja, nativ | Nein | Nein |
| Tensor Parallelism | Ja | Ja | Ja |
| Multimodale Modelle | Eingeschraenkt | Eingeschraenkt | Ja |
| Batching-Strategie | Continuous Batching | Continuous Batching | Dynamic Batching |
| Kubernetes-native Deployment | Docker Image | Docker Image | Helm Chart |
| Lernkurve | Niedrig | Niedrig | Hoch |
Fuer die meisten Szenarien -- Chatbots, Code-Assistenten, Zusammenfassungen, RAG-Pipelines -- ist vLLM die pragmatischste Wahl. Wer multimodale Workloads (Bild + Text) oder komplexe Ensemble-Modelle braucht, sollte sich Triton genauer anschauen.
Voraussetzungen: GPU-Nodes im Cluster
Bevor vLLM laeuft, muss der Kubernetes-Cluster GPU-faehig sein. Das erfordert drei Dinge:
- Worker Nodes mit NVIDIA GPUs (A100, H100, L40S oder aequivalent)
- NVIDIA GPU Operator oder manuell installiertes Device Plugin
- Container Runtime mit GPU-Support (containerd mit nvidia-container-toolkit)
Der GPU Operator ist der sauberste Weg, weil er Treiber, Device Plugin und DCGM Exporter als DaemonSets verwaltet. Installation per Helm:
helm repo add nvidia https://helm.ngc.nvidia.com/nvidia
helm repo update
helm install gpu-operator nvidia/gpu-operator \
--namespace gpu-operator \
--create-namespace \
--set driver.enabled=true \
--set toolkit.enabled=true \
--set dcgmExporter.enabled=true
Nach der Installation sollte kubectl describe node <gpu-node> unter Capacity den Eintrag nvidia.com/gpu: N zeigen. Falls nicht, pruefen Sie die Logs des Device-Plugin-Pods.
Mehr zum Thema GPU-Cluster finden Sie in unserem GPU-Cluster Aufbau Guide.
vLLM Deployment: Vom YAML bis zum laufenden Pod
Hier ist ein produktionsnahes Deployment fuer Mistral-7B-Instruct. Die wichtigsten Entscheidungen sind kommentiert:
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-mistral-7b
namespace: ml-serving
labels:
app: vllm
model: mistral-7b
spec:
replicas: 1
selector:
matchLabels:
app: vllm
model: mistral-7b
template:
metadata:
labels:
app: vllm
model: mistral-7b
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8000"
prometheus.io/path: "/metrics"
spec:
containers:
- name: vllm
image: vllm/vllm-openai:v0.7.2
args:
- "--model"
- "mistralai/Mistral-7B-Instruct-v0.3"
- "--max-model-len"
- "8192"
- "--gpu-memory-utilization"
- "0.90"
- "--enable-prefix-caching"
ports:
- containerPort: 8000
name: http
resources:
requests:
nvidia.com/gpu: 1
memory: "16Gi"
cpu: "4"
limits:
nvidia.com/gpu: 1
memory: "24Gi"
cpu: "8"
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 120
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 180
periodSeconds: 30
volumeMounts:
- name: model-cache
mountPath: /root/.cache/huggingface
- name: shm
mountPath: /dev/shm
volumes:
- name: model-cache
persistentVolumeClaim:
claimName: vllm-model-cache
- name: shm
emptyDir:
medium: Memory
sizeLimit: "8Gi"
---
apiVersion: v1
kind: Service
metadata:
name: vllm-mistral-7b
namespace: ml-serving
spec:
selector:
app: vllm
model: mistral-7b
ports:
- port: 8000
targetPort: 8000
name: http
type: ClusterIP
Drei Dinge, die oft vergessen werden:
Shared Memory (/dev/shm): vLLM braucht Shared Memory fuer die interne Kommunikation bei Tensor Parallelism. Ohne das emptyDir mit medium: Memory crasht der Pod bei Multi-GPU-Setups sofort.
gpu-memory-utilization: Der Default ist 0.9 (90%). Das ist ein guter Startwert. Auf 0.95 zu gehen bringt marginal mehr Durchsatz, aber erhoet das Risiko von OOM-Kills bei Lastspitzen.
Readiness Probe mit langem initialDelaySeconds: Grosse Modelle brauchen 60-180 Sekunden zum Laden. Ohne angepasste Probe wird der Pod in eine Restart-Schleife geschickt.
Modell-Cache: PVC statt erneuter Download
Das Herunterladen eines 7B-Modells dauert je nach Bandbreite 5-15 Minuten. Bei einem 70B-Modell sind es schnell 30+ Minuten. Ein PersistentVolumeClaim verhindert, dass das Modell bei jedem Pod-Restart neu geladen wird:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: vllm-model-cache
namespace: ml-serving
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-ssd
resources:
requests:
storage: 100Gi
Fuer Multi-Replica-Setups brauchen Sie ReadWriteMany (NFS oder ein verteiltes Dateisystem). Alternativ koennen Sie das Modell in ein eigenes Container-Image backen -- das macht den Start deterministisch, aber die Images werden gross.
Details zur Storage-Strategie finden Sie unter Kubernetes Storage Loesungen.
Auto-Scaling: HPA mit GPU-Metriken
Der Standard-HPA skaliert auf CPU und Memory. Fuer LLM-Workloads ist das unbrauchbar -- die GPU-Auslastung ist die relevante Metrik. Mit dem DCGM Exporter und dem Prometheus Adapter laesst sich ein Custom-Metric-HPA konfigurieren:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: vllm-hpa
namespace: ml-serving
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: vllm-mistral-7b
minReplicas: 1
maxReplicas: 4
metrics:
- type: Pods
pods:
metric:
name: DCGM_FI_DEV_GPU_UTIL
target:
type: AverageValue
averageValue: "75"
behavior:
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Pods
value: 1
periodSeconds: 120
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Pods
value: 1
periodSeconds: 300
Die stabilizationWindowSeconds beim Herunterskalieren sind bewusst konservativ (5 Minuten). Ein neuer vLLM-Pod braucht Zeit zum Starten und Modell-Laden. Zu aggressives Skalieren fuehrt zu Flapping.
Eine Alternative zum HPA ist KEDA mit einem Prometheus-Scaler. Das gibt Ihnen mehr Flexibilitaet bei der Metrik-Auswahl (z.B. Queue-Laenge eines vorgeschalteten Message Brokers).
Mehr zu Autoscaling-Strategien finden Sie in unserem Kubernetes Autoscaling Guide.
Monitoring: Was Sie messen sollten
vLLM exportiert Prometheus-Metriken auf /metrics. Die wichtigsten:
| Metrik | Beschreibung | Alerting-Schwelle |
|---|---|---|
vllm:num_requests_running | Aktuell laufende Requests | > 80% der max. Batch-Groesse |
vllm:num_requests_waiting | Requests in der Queue | > 0 ueber laengere Zeit |
vllm:gpu_cache_usage_perc | KV-Cache-Auslastung | > 95% |
vllm:avg_generation_throughput_toks_per_s | Tokens pro Sekunde | < erwarteter Baseline |
DCGM_FI_DEV_GPU_UTIL | GPU Compute-Auslastung | < 30% (Underutilization) |
DCGM_FI_DEV_FB_USED | GPU Memory Used (MB) | > 90% von Total |
Wenn num_requests_waiting dauerhaft ueber 0 liegt, ist der Server ueberlastet. Dann hilft entweder Hochskalieren (mehr Replicas) oder ein groesseres Batching-Window.
Wenn gpu_cache_usage_perc an 100% stoesst, werden Requests abgelehnt. In dem Fall: max-model-len reduzieren oder gpu-memory-utilization erhoehen.
Fuer eine vollstaendige Monitoring-Strategie empfehle ich den Artikel zu Kubernetes Monitoring und Observability.
Tensor Parallelism fuer grosse Modelle
Modelle ab 13B Parametern passen nicht auf eine einzelne GPU (ausser A100-80GB oder H100). vLLM unterstuetzt Tensor Parallelism nativ. Die Konfiguration ist simpel -- Sie aendern einen Startparameter und die Resource Requests:
args:
- "--model"
- "meta-llama/Llama-3.1-70B-Instruct"
- "--tensor-parallel-size"
- "4"
- "--max-model-len"
- "4096"
- "--gpu-memory-utilization"
- "0.92"
Und in den Resources:
resources:
requests:
nvidia.com/gpu: 4
memory: "64Gi"
cpu: "16"
limits:
nvidia.com/gpu: 4
memory: "96Gi"
cpu: "32"
Wichtig: Die GPUs muessen auf demselben Node liegen. Kubernetes verteilt GPU-Requests nicht ueber Nodes -- wenn kein Node 4 freie GPUs hat, bleibt der Pod Pending.
Fuer Multi-Node-Tensor-Parallelism (z.B. 8 GPUs ueber 2 Nodes) brauchen Sie Ray Serve oder ein aehnliches Framework. Das geht ueber den Scope dieses Artikels hinaus, ist aber fuer Modelle jenseits von 70B relevant.
Quantisierte Modelle: Mehr Leistung pro GPU
Falls die GPU-Kapazitaet begrenzt ist, sind quantisierte Modelle eine Option. AWQ und GPTQ reduzieren den Speicherbedarf um 50-75% bei moderatem Qualitaetsverlust:
| Modell | Precision | VRAM-Bedarf | Tokens/s (A100-40GB) |
|---|---|---|---|
| Mistral-7B | FP16 | ~14 GB | ~120 |
| Mistral-7B | AWQ 4-bit | ~4 GB | ~180 |
| Llama-3.1-70B | FP16 | ~140 GB (4x A100) | ~30 |
| Llama-3.1-70B | AWQ 4-bit | ~36 GB (1x A100-80GB) | ~55 |
vLLM laedt quantisierte Modelle automatisch, wenn das Modell im entsprechenden Format vorliegt:
# AWQ-quantisiertes Modell laden
--model TheBloke/Mistral-7B-Instruct-v0.2-AWQ --quantization awq
Die Qualitaet ist fuer die meisten Business-Anwendungen (Chatbots, Zusammenfassungen, Klassifikation) ausreichend. Fuer Code-Generierung oder komplexes Reasoning sollten Sie die Qualitaet mit Ihren spezifischen Prompts benchmarken, bevor Sie in Produktion gehen.
Sicherheit: NetworkPolicy und RBAC
Ein vLLM-Server sollte nicht ohne Zugangskontrollen im Cluster stehen. Minimal brauchen Sie:
- Eine NetworkPolicy, die Zugriff auf den vLLM-Service nur von erlaubten Namespaces zulasst.
- RBAC, damit nur das ML-Team die Deployments verwalten kann.
- Optional einen API-Key ueber einen vorgeschalteten Reverse Proxy (z.B. Envoy oder nginx).
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: vllm-access
namespace: ml-serving
spec:
podSelector:
matchLabels:
app: vllm
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
access-vllm: "true"
ports:
- port: 8000
protocol: TCP
Wer DSGVO-sensible Daten verarbeitet, sollte zusaetzlich pruefen, ob die Prompts und Antworten geloggt werden und wie lange diese Logs aufbewahrt werden. vLLM loggt standardmaessig keine Prompts -- das ist gut. Aber ein vorgeschalteter Load Balancer koennte Request Bodies mitschneiden.
Mehr zu Kubernetes Security finden Sie unter Penetration Testing als Defense-Strategie.
Typische Probleme und Loesungen
Pod bleibt im Status Pending: Haeufigste Ursache: Kein Node mit genuegend freien GPUs. Pruefen Sie mit kubectl describe pod die Events. Wenn dort Insufficient nvidia.com/gpu steht, brauchen Sie entweder mehr GPU-Nodes oder muessen andere GPU-Workloads reduzieren.
OOM-Kill kurz nach dem Start: Das Modell passt nicht in den verfuegbaren VRAM. Loesungen: gpu-memory-utilization reduzieren, max-model-len verkuerzen, oder ein quantisiertes Modell verwenden.
Hohe Latenz bei wenigen Anfragen: Pruefen Sie, ob enable-prefix-caching aktiviert ist. Bei wiederholten System-Prompts (typisch fuer Chatbots) kann Prefix Caching die Time-to-First-Token um 30-50% reduzieren.
Metriken zeigen 0 Tokens/s trotz laufendem Pod: Der Prometheus-Scrape-Pfad ist vermutlich falsch konfiguriert. vLLM exponiert Metriken auf Port 8000 unter /metrics, nicht auf einem separaten Port.
Fazit
vLLM auf Kubernetes zu betreiben ist kein Hexenwerk, erfordert aber ein paar gezielte Anpassungen gegenueber Standard-Deployments: GPU Resource Management, angepasste Probes, Shared Memory fuer Tensor Parallelism, und Monitoring mit GPU-spezifischen Metriken.
Der Stack aus vLLM + NVIDIA GPU Operator + Prometheus/DCGM Exporter + HPA mit Custom Metrics bildet eine solide Grundlage fuer LLM-Inferenz in Produktion. Fangen Sie mit einem kleinen Modell (7B) auf einer einzelnen GPU an, validieren Sie die Pipeline, und skalieren Sie dann schrittweise.
Falls Sie Unterstuetzung beim Aufbau Ihrer ML-Serving-Infrastruktur benoetigen, sprechen Sie uns an. Wir helfen bei der Planung und Umsetzung -- von der GPU-Node-Konfiguration bis zum Production-Ready-Setup. Mehr dazu 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
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.
GPU-Workloads in Kubernetes: Scheduling, Sharing und Kosten
GPU-Auslastung in Kubernetes von 30% auf über 70% steigern mit NVIDIA Device Plugin, MIG, Time-Slicing und Volcano Scheduler.
KI auf Kubernetes im Healthcare: ML-Pipelines produktiv betreiben
Wie ein deutsches Healthcare-Unternehmen 50+ ML-Modelle DSGVO-konform auf Kubernetes betreibt und die Diagnose-Genauigkeit um 35% verbesserte.
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.