Veröffentlicht am

vLLM auf Kubernetes: GPU-effiziente LLM-Inferenz Guide

Teilen:
Authors

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.

FeaturevLLMTGITriton
PagedAttentionJaNein (Flash Attention)Nein (pluggable)
OpenAI-kompatible APIJa, nativNeinNein
Tensor ParallelismJaJaJa
Multimodale ModelleEingeschraenktEingeschraenktJa
Batching-StrategieContinuous BatchingContinuous BatchingDynamic Batching
Kubernetes-native DeploymentDocker ImageDocker ImageHelm Chart
LernkurveNiedrigNiedrigHoch

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:

  1. Worker Nodes mit NVIDIA GPUs (A100, H100, L40S oder aequivalent)
  2. NVIDIA GPU Operator oder manuell installiertes Device Plugin
  3. 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:

MetrikBeschreibungAlerting-Schwelle
vllm:num_requests_runningAktuell laufende Requests> 80% der max. Batch-Groesse
vllm:num_requests_waitingRequests in der Queue> 0 ueber laengere Zeit
vllm:gpu_cache_usage_percKV-Cache-Auslastung> 95%
vllm:avg_generation_throughput_toks_per_sTokens pro Sekunde< erwarteter Baseline
DCGM_FI_DEV_GPU_UTILGPU Compute-Auslastung< 30% (Underutilization)
DCGM_FI_DEV_FB_USEDGPU 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:

ModellPrecisionVRAM-BedarfTokens/s (A100-40GB)
Mistral-7BFP16~14 GB~120
Mistral-7BAWQ 4-bit~4 GB~180
Llama-3.1-70BFP16~140 GB (4x A100)~30
Llama-3.1-70BAWQ 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:

  1. Eine NetworkPolicy, die Zugriff auf den vLLM-Service nur von erlaubten Namespaces zulasst.
  2. RBAC, damit nur das ML-Team die Deployments verwalten kann.
  3. 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