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.
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:
- Wagi modelu - stały rozmiar, ładowane przy starcie (np. Llama 4 Scout 17B AWQ = ~9GB)
- 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 32768prealokuje 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
- Uruchamianie LLM na AWS: Bedrock vs SageMaker vs self-hosting na EKS - pełny przewodnik po wyborze instancji GPU i konfiguracji Karpenter
- Dokumentacja argumentów silnika vLLM