Kubernetes EKS GPU vLLM LLM GenAI

Pod vLLM w CrashLoopBackOff z torch.cuda.OutOfMemoryError na EKS: naprawa wyczerpania VRAM

Naprawa podów vLLM kończących z CUDA OutOfMemoryError na EKS. Diagnostyka przepełnienia KV cache, dostrajanie parametrów pamięci, zmiana kwantyzacji.

·
Pod vLLM wchodzi w CrashLoopBackOff z torch.cuda.OutOfMemoryError? Model ładował się wczoraj, ale po zmianie konfiguracji dziś nie wykonuje się poprawnie. Problemem jest zazwyczaj pamięć KV cache przekraczająca dostępny VRAM - a nie same wagi modelu.

Objawy

Status poda pokazuje pętlę restartów:

kubectl get pods -n inference
# NAME                          READY   STATUS             RESTARTS   AGE
# vllm-llama-scout-7b9f4-xyz   0/1     CrashLoopBackOff   5          8m

Logi ujawniają OOM:

kubectl logs -n inference vllm-llama-scout-7b9f4-xyz --previous
torch.cuda.OutOfMemoryError: CUDA out of memory. Tried to allocate 2.00 GiB. GPU 0 has
a total capacity of 23.65 GiB of which 1.12 GiB is free. Including non-PyTorch memory,
this process has 22.53 GiB memory in use.

Lub wariant specyficzny dla vLLM:

ValueError: The model's max seq len (32768) is larger than the maximum number of tokens
that can be stored in KV cache (8192). Try increasing `gpu_memory_utilization` or
decreasing `max_model_len` when initializing the engine.

Przyczyna

vLLM alokuje pamięć GPU w dwóch częściach:

  1. Wagi modelu - stały rozmiar, ładowane przy starcie (np. Llama 4 Scout 17B AWQ = ~9GB)
  2. KV cache - dynamiczny, alokowany z pozostałego VRAM na kontekst równoczesnych requestów

Wzór: Dostępny VRAM × gpu_memory_utilization − wagi_modelu = budżet KV cache

Na g5.xlarge (24GB A10G):

  • 24GB × 0.9 (domyślna utylizacja) = 21.6GB dostępne
  • Wagi modelu: ~9GB (17B AWQ 4-bit)
  • KV cache: ~12.6GB

OOM występuje gdy:

  • max-model-len za wysoki - ustawienie --max-model-len 32768 prealokuje KV cache na sekwencje 32K tokenów, wyczerpując VRAM nawet przy kilku równoczesnych requestach
  • gpu-memory-utilization za wysokie - 0.95 nie zostawia zapasu na narzut alokatora PyTorch CUDA
  • Model większy niż oczekiwano - upgrade z AWQ 4-bit na GPTQ 8-bit bez dostosowania budżetu pamięci
  • Niezgodność tensor parallelism - uruchamianie modelu wymagającego 2 GPU na węźle z jedną kartą
  • CUDA graphs - kompilacja CUDA graphs w vLLM rezerwuje dodatkową pamięć przy starcie

Rozwiązanie

Krok 1: Sprawdź aktualne użycie VRAM na węźle

Jeśli pod nie kończy, sprawdź co zajmuje pamięć GPU:

# Znajdź węzeł, na którym działa pod
kubectl get pod -n inference vllm-llama-scout-7b9f4-xyz -o wide

# SSH lub exec na węzeł i sprawdź nvidia-smi
kubectl debug node/<node-name> -it --image=nvidia/cuda:12.4.0-base-ubuntu22.04 -- nvidia-smi

Oczekiwane wyjście pokazuje total/used/free:

| 0  NVIDIA A10G  | 00000000:00:1E.0 | 24576MiB / 24576MiB |

Krok 2: Zmniejsz max-model-len (najszybsza poprawka)

Jeśli nie potrzebujesz kontekstu 32K, zmniejsz go:

containers:
  - name: vllm
    args:
      - "--model"
      - "meta-llama/Llama-4-Scout-17B-Instruct"
      - "--max-model-len"
      - "4096"            # Reduced from 32768
      - "--gpu-memory-utilization"
      - "0.9"

To drastycznie redukuje prealokację KV cache. Dla większości przypadków RAG (prompt + kontekst = 2-4K tokenów) wartość 4096 lub 8192 jest wystarczająca.

Krok 3: Obniż gpu-memory-utilization

Jeśli jesteś na 0.95, obniż do 0.85:

      - "--gpu-memory-utilization"
      - "0.85"            # Leave 15% headroom for CUDA overhead

Te 10%, które „tracisz”, zapobiega sporadycznym OOM spowodowanym fragmentacją pod obciążeniem.

Krok 4: Wyłącz CUDA graphs (gdy pamięć jest ekstremalnie ograniczona)

CUDA graphs prekompilują wzorce wykonania dla szybkości, ale zużywają dodatkowy VRAM przy starcie:

      - "--enforce-eager"   # Disables CUDA graphs, saves ~1-2GB VRAM

Kompromis: ~10-15% wolniejsza przepustowość inferencji, ale pod faktycznie wystartuje.

Krok 5: Przejdź na bardziej agresywną kwantyzację

Jeśli same wagi modelu są za duże:

Kwantyzacja Rozmiar modelu 17B Wpływ na jakość
FP16 (bez kwantyzacji) ~34GB Bazowy
GPTQ 8-bit ~17GB Minimalny
AWQ 4-bit ~9GB Lekka degradacja na złożonym rozumowaniu
GGUF Q4_K_M ~10GB Umiarkowany, dobry do chatu

Przejście z FP16 na AWQ:

      - "--model"
      - "meta-llama/Llama-4-Scout-17B-Instruct-AWQ"
      - "--quantization"
      - "awq"

Krok 6: Przeskaluj na większe GPU (jeśli model faktycznie potrzebuje więcej VRAM)

Jeśli potrzebujesz pełnego kontekstu 32K z modelem 17B na FP16, pojedyncze A10G (24GB) nie wystarczy. Opcje:

  • g5.12xlarge (4x A10G, 96GB łącznie) z --tensor-parallel-size 4
  • g6.xlarge (1x L4, 24GB) - ten sam VRAM, ale nowszy GPU, nieco lepszy do inferencji
  • p4d.24xlarge (8x A100, 320GB) - do modeli 70B+

Zaktualizuj wymagania w NodePool Karpenter:

requirements:
  - key: "karpenter.k8s.aws/instance-family"
    operator: In
    values: ["g5"]
  - key: "karpenter.k8s.aws/instance-size"
    operator: In
    values: ["12xlarge"]    # Upgraded from xlarge

Walidacja

Po wdrożeniu poprawki zweryfikuj, że pod startuje i VRAM jest stabilny:

# Pod powinien być w stanie Running
kubectl get pods -n inference -l app=vllm-llama-scout
# NAME                          READY   STATUS    RESTARTS   AGE
# vllm-llama-scout-7b9f4-abc   1/1     Running   0          5m

# Sprawdź, że użycie VRAM jest stabilne (nie rośnie)
kubectl exec -n inference vllm-llama-scout-7b9f4-abc -- nvidia-smi --query-gpu=memory.used,memory.total --format=csv
# memory.used [MiB], memory.total [MiB]
# 18432 MiB, 24576 MiB   (75% - healthy)

Wyślij testowe zapytanie, żeby potwierdzić, że inferencja działa:

kubectl port-forward -n inference svc/vllm-llama-scout 8000:8000 &
curl -s http://localhost:8000/v1/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"meta-llama/Llama-4-Scout-17B-Instruct","prompt":"Hello","max_tokens":10}'

Oczekiwany wynik: poprawna odpowiedź JSON z wygenerowanymi tokenami w ciągu 2-3 sekund.

Powiązane