- Published on
KServe vLLM 배포에서 504·OOM 잡는 HPA 튜닝
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서빙 트래픽이 조금만 튀어도 504가 나고, 스케일아웃을 걸면 갑자기 GPU OOM이 터지는 케이스는 KServe + vLLM 조합에서 꽤 흔합니다. 이유는 단순히 “파드가 부족해서”가 아니라, 스케일링 신호가 늦게 오거나 잘못 오고, vLLM의 동시성/배치가 GPU 메모리를 순간적으로 폭발시키며, KServe/Ingress 타임아웃과 콜드스타트가 맞물리는 구조적 문제가 겹치기 때문입니다.
이 글은 다음을 목표로 합니다.
504의 원인을 “네트워크”가 아니라 큐잉 지연 + 콜드스타트 + 타임아웃 미스매치로 분해OOM을 “모델이 커서”가 아니라 KV cache, max model len, 동시 요청, 배치 관점으로 분해- HPA를 CPU 기준이 아닌 서빙 지표 기반으로 튜닝하는 실전 패턴 제시
- KServe
InferenceService와 HPA, 그리고 vLLM 런타임 플래그를 함께 맞추는 체크리스트 제공
참고로 504가 Ingress 계층(ALB/Nginx)에서 간헐적으로 발생하는 케이스는 인그레스 설정 자체도 원인이 될 수 있습니다. 인그레스 관점의 504 진단은 이 글도 같이 보면 좋습니다: EKS ALB Ingress 504(5xx) 간헐 발생 원인·해결
1) KServe + vLLM에서 504와 OOM이 같이 오는 구조
1-1. 504가 나는 전형적인 흐름
- 트래픽 급증
- 기존 파드의 vLLM 큐가 길어짐(요청 대기)
- 응답 지연이 Ingress/Envoy/ALB의 idle timeout 또는 upstream timeout을 초과
- 클라이언트는
504를 받음 - 뒤늦게 HPA가 스케일아웃을 시작하지만, GPU 노드 스케줄링 + 이미지 풀 + 모델 로딩 때문에 새 파드가 준비되기까지 수십 초~수 분
즉, 스케일아웃이 “되긴 되는데” 504를 막기엔 너무 늦게 오는 경우가 많습니다.
1-2. OOM이 나는 전형적인 흐름
- 스케일아웃으로 파드 수가 늘어도, 각 파드가 처리하는 동시 요청 수가 높거나
- vLLM이 동적 배칭으로 한 번에 많은 토큰/요청을 묶어 처리하거나
max_model_len이 과도해서 KV cache가 커지거나gpu_memory_utilization을 너무 공격적으로 잡아 여유가 없으면
특정 순간에 메모리 스파이크가 나면서 CUDA out of memory가 발생합니다.
핵심은 HPA는 “파드 수”만 바꾸지만, OOM은 “파드 내부 동시성/컨텍스트 길이/배치”가 결정한다는 점입니다. 그래서 504만 잡으려고 무작정 스케일아웃을 걸면 오히려 OOM이 늘어나는 역설이 생깁니다.
2) 먼저 해야 할 관측: 504와 OOM을 수치로 분리하기
HPA 튜닝은 “감”으로 하면 실패합니다. 최소한 아래 3종류의 지표가 필요합니다.
- 큐잉 지표: vLLM 서버 내부 대기열(요청 수, 대기시간)
- 서빙 지표: p95 latency, time to first token(TTFT), tokens per second
- 리소스 지표: GPU 메모리 사용량, OOM 발생 횟수, GPU util
Prometheus를 쓴다면 vLLM 메트릭 엔드포인트를 열고, KServe/Knative/Envoy 지표와 함께 봐야 합니다. 최소 목표는 “504가 난 시점에 큐가 길어졌는지, 아니면 준비되지 않은 파드로 라우팅됐는지”를 구분하는 것입니다.
3) vLLM 파라미터로 OOM을 먼저 안정화
HPA를 만지기 전에, 파드 1개가 안정적으로 버티는 동작 범위를 만들어야 합니다. 그렇지 않으면 스케일링이 오히려 불안정성을 증폭합니다.
3-1. OOM을 줄이는 4가지 레버
--max-model-len- 길수록 KV cache가 커집니다.
- 실제 제품 요구사항보다 크게 잡아두면 OOM 확률이 급증합니다.
--gpu-memory-utilization- 너무 높이면 여유가 없어 스파이크에 취약합니다.
- 운영에서는 보통
0.85전후로 시작해 튜닝합니다.
--max-num-seqs- 동시 시퀀스 수 상한입니다.
- 이 값이 높으면 처리량은 늘지만 메모리 스파이크와 TTFT가 악화될 수 있습니다.
--max-num-batched-tokens- 동적 배칭의 상한입니다.
- 과도하면 순간적으로 GPU 메모리와 지연이 튑니다.
3-2. vLLM 실행 예시(인라인 코드 주의)
아래는 “안정성 우선”으로 시작하는 예시입니다. 모델/카드에 따라 달라지므로 그대로 복붙이 아니라 기준점으로 보세요.
python -m vllm.entrypoints.openai.api_server \
--model /models/your-model \
--host 0.0.0.0 --port 8000 \
--max-model-len 4096 \
--gpu-memory-utilization 0.85 \
--max-num-seqs 32 \
--max-num-batched-tokens 8192
운영 팁:
- 504를 줄이려면 처리량이 중요하지만, OOM이 나면 재시작으로 더 큰 504 폭탄이 옵니다.
- 따라서 1차 목표는 “피크 트래픽에서 OOM 0”입니다.
4) KServe에서 504를 부르는 콜드스타트와 타임아웃 미스매치
KServe는 내부적으로 Knative를 쓰는 구성이 많고(설치 옵션에 따라 다름), 이때 scale to zero와 activation 지연이 504의 주요 원인이 됩니다.
4-1. scale to zero를 끄거나, 최소 레플리카를 둔다
- LLM은 로딩이 무겁기 때문에 scale to zero는 비용은 줄이지만 504를 부르기 쉽습니다.
- 최소 1개 파드를 항상 띄우는 전략이 가장 단순하고 효과적입니다.
KServe InferenceService에서 minReplicas를 주는 예시입니다.
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: vllm-llm
spec:
predictor:
minReplicas: 1
maxReplicas: 10
containers:
- name: vllm
image: your-registry/vllm:latest
args:
- "--model"
- "/models/your-model"
- "--max-model-len"
- "4096"
- "--gpu-memory-utilization"
- "0.85"
resources:
requests:
nvidia.com/gpu: "1"
cpu: "2"
memory: "8Gi"
limits:
nvidia.com/gpu: "1"
cpu: "4"
memory: "16Gi"
4-2. readiness probe가 “모델 준비 완료”를 의미해야 한다
모델 로딩이 끝나기 전에 readiness가 true가 되면, 트래픽이 들어와서 지연이 폭발합니다. vLLM이 준비되었는지 확인 가능한 엔드포인트로 readiness를 잡으세요.
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 12
엔드포인트가 없다면, 최소한 서버 포트 오픈만으로 readiness를 통과시키지 말고 “모델 로딩 완료”를 보장하는 별도 체크를 구현하는 편이 안전합니다.
5) HPA 튜닝 핵심: CPU가 아니라 “서빙 부하”로 스케일링하기
LLM 서빙은 CPU 사용률이 낮아도 큐가 길어질 수 있습니다. GPU는 HPA 기본 지표로 쓰기 어렵고, 결국 커스텀 메트릭(예: 동시 요청 수, 큐 길이, p95 latency, RPS) 기반으로 가는 것이 정석입니다.
여기서 중요한 포인트는 “무엇을 지표로 삼을지”입니다.
- RPS 기반: 요청당 토큰량이 달라지면 실패
- CPU 기반: GPU 병목에서는 의미 없음
- p95 latency 기반: 반응은 하지만 노이즈가 큼
- 동시 요청 수(Concurrency) 기반: LLM에서 가장 직관적이고 안정적인 편
KServe/Knative를 쓰는 경우, 동시성 기반 오토스케일링이 이미 내장돼 있는 구성도 많습니다. 하지만 환경에 따라 HPA를 직접 붙여야 한다면, Prometheus Adapter로 커스텀 메트릭을 HPA에 연결합니다.
5-1. HPA 예시: 커스텀 메트릭으로 동시 요청 수 타깃
아래는 예시이며, 실제 메트릭 이름은 vLLM/프록시/어댑터 구성에 따라 다릅니다.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: vllm-llm-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: vllm-llm
minReplicas: 1
maxReplicas: 10
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 200
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 50
periodSeconds: 60
metrics:
- type: Pods
pods:
metric:
name: vllm_inflight_requests
target:
type: AverageValue
averageValue: "8"
튜닝 포인트:
averageValue는 “파드 1개가 안정적으로 처리 가능한 동시 요청 수”로 잡습니다.- scale up은 빠르게, scale down은 느리게(안정화 윈도우) 가는 편이 504를 줄입니다.
6) 504를 줄이는 “스케일링 지연” 대책
HPA를 잘 잡아도 GPU 파드는 준비까지 시간이 오래 걸립니다. 그래서 아래를 같이 고려해야 합니다.
6-1. 노드 오토스케일러와 함께 설계
파드만 늘려도 GPU 노드가 없으면 스케줄링이 막힙니다. Cluster Autoscaler나 Karpenter를 쓰는 경우:
- GPU 노드 풀에 여유를 약간 둔다(버퍼)
- 스케일아웃이 잦으면 pre-pull(이미지 캐시) 전략을 쓴다
6-2. 신규 파드 워밍업 중 트래픽 유입 차단
readiness를 엄격히 잡는 것이 핵심입니다. “서버 프로세스는 떠 있지만 모델 로딩 중” 상태를 트래픽에서 제외해야 504가 줄어듭니다.
6-3. 타임아웃 정렬
- 클라이언트 타임아웃
- Ingress/ALB 타임아웃
- 서비스 메시(Envoy) 타임아웃
- 애플리케이션의 응답 지연
이 값들이 서로 어긋나면, 실제로는 응답이 가능한데 중간에서 끊기며 504가 납니다. 특히 스트리밍 응답을 쓴다면 idle timeout 계열이 중요합니다.
7) OOM을 줄이는 “파드당 부하 상한” 설계
HPA가 동시 요청을 기준으로 스케일하더라도, 파드 내부에서 동시성이 폭발하면 OOM은 여전히 납니다. 그래서 서버 레벨에서 상한을 걸어야 합니다.
권장 조합:
- HPA는 “평균 동시 요청 수”를 기준으로 스케일
- vLLM은
--max-num-seqs로 하드 상한 - API Gateway 또는 프록시에서 “초과 요청은 429로 빠르게 실패” 또는 큐잉
큐잉을 무한정 허용하면 504가 늘고, 무작정 거절하면 사용자 경험이 나빠집니다. 보통은 “짧은 큐 + 빠른 실패 + 빠른 스케일업”이 운영적으로 예측 가능합니다.
8) 실전 튜닝 절차(재현 가능한 순서)
단일 파드 안정화
- 목표: 피크 입력(최대 토큰 길이, 최대 동시 요청)에서 OOM 0
- 조정:
max_model_len,max_num_seqs,max_num_batched_tokens,gpu_memory_utilization
큐잉 지표 확보
- 목표: “504 시점에 큐가 얼마나 쌓였는지”를 수치로 확인
minReplicas 설정
- 목표: 콜드스타트 제거(또는 최소화)
HPA 입력 지표를 동시 요청 기반으로 전환
- 목표: CPU가 아니라 “서빙 부하”를 따라가게 만들기
스케일링 행동(behavior) 튜닝
- scale up 빠르게, scale down 느리게
타임아웃 정렬
- Ingress와 클라이언트 타임아웃을 “최악의 p99 + 워밍업”보다 길게
부하 테스트로 검증
- RPS만 올리지 말고 “긴 프롬프트 + 긴 출력” 조합도 반드시 포함
9) 자주 하는 실수 6가지
- CPU 기반 HPA로 결론을 내림
minReplicas없이 운영하면서 504를 네트워크 문제로 오해- readiness가 느슨해서 워밍업 중 트래픽을 받음
max_model_len을 과도하게 크게 잡음- 스케일다운이 너무 빨라서 다시 콜드스타트 반복
- 타임아웃을 계층별로 제각각 설정
10) 마무리: 504와 OOM은 같이 잡아야 한다
KServe + vLLM에서 504와 OOM은 서로 독립 이슈가 아니라, 큐잉과 스케일링 지연이 504를 만들고, 그 지연을 줄이려는 동시성/배치 확대가 OOM을 만든다는 식으로 연결됩니다.
정리하면 다음의 우선순위가 가장 안전합니다.
- vLLM 파라미터로 파드 1개를 먼저 안정화(OOM 0)
- KServe에서 콜드스타트 제거(
minReplicas, 엄격한 readiness) - HPA는 동시 요청 같은 “서빙 부하”로 스케일
- scale up은 빠르게, scale down은 느리게
- 타임아웃을 계층별로 정렬
이 과정을 거치면, “스케일은 되는데 504는 계속” 또는 “스케일하면 OOM” 같은 상태에서 벗어나, 예측 가능한 서빙 품질을 만들 수 있습니다.