- Published on
KServe+Knative로 GPU LLM 자동스케일·콜드스타트 제거
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스 LLM 서빙을 한다고 하면 많은 팀이 바로 두 가지 벽을 만납니다. 첫째는 GPU 비용입니다. 트래픽이 들쑥날쑥한데도 GPU 파드를 상시 띄워두면 비용이 폭발합니다. 둘째는 콜드스타트입니다. scale-to-zero를 켜면 비용은 줄지만, 첫 요청이 느려져 사용자 경험이 망가집니다.
이 글은 KServe(모델 서빙 표준화)와 Knative(요청 기반 오토스케일)를 조합해 GPU LLM을 자동 스케일링하면서도 콜드스타트를 사실상 제거하는 방법을, 운영 관점에서 바로 적용 가능한 형태로 정리합니다.
왜 KServe+Knative 조합이 LLM 서빙에 유리한가
KServe가 해결하는 것
KServe는 Kubernetes 위에서 모델 배포를 표준화합니다.
InferenceService로 배포 스펙을 통일(트래픽 라우팅, 롤아웃, 리비전)- Predictor/Transformer/Explainer 같은 구성요소를 붙이기 쉬움
- 모델 아티팩트 스토리지 연동(S3, GCS, PVC 등)
- 관측(메트릭, 로그)과 운영 패턴이 비교적 정형화
LLM의 경우 vLLM, TGI, Triton, custom server 등 다양한 런타임이 있는데, KServe는 “어떤 런타임이든 InferenceService로 감싸서 운영”하게 만들어줍니다.
Knative가 해결하는 것
Knative Serving은 요청 기반으로 파드를 늘리고 줄입니다.
- QPS/동시성 기반 오토스케일
scale-to-zero지원- Revision 단위 트래픽 분배(카나리, 블루/그린)
즉, KServe가 모델 서빙의 표준 인터페이스를 제공하고, Knative가 요청 기반 오토스케일 엔진이 됩니다.
아키텍처: “0으로 내리되, 0처럼 느껴지지 않게”
핵심은 다음 3가지를 동시에 만족시키는 것입니다.
- 평소에는 최소 GPU 점유를 줄인다(가능하면 0 또는 1)
- 첫 요청이 오기 전에 “따뜻한” 워커가 준비되게 만든다
- 준비가 늦어질 때도 사용자 요청을 망치지 않게 흡수한다
이를 위해 운영에서 자주 쓰는 패턴은 아래 조합입니다.
- Knative
minScale로 최소 파드 수 유지(완전 0이 아니라 1) - 또는
scale-to-zero를 유지하되, 프리워밍 트리거를 별도로 둠 - GPU 노드 오토스케일(Cluster Autoscaler/Karpenter)까지 함께 고려
- 모델 로딩/엔진 워밍업을 “준비 상태”로 정확히 반영(Probe 튜닝)
구현 1: KServe InferenceService로 GPU LLM 배포
아래 예시는 vLLM 기반 컨테이너를 KServe로 배포하는 전형적인 형태입니다. 포인트는 resources(GPU), readinessProbe, 그리고 Knative 오토스케일 어노테이션입니다.
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: llm-vllm
namespace: llm
annotations:
autoscaling.knative.dev/class: kpa.autoscaling.knative.dev
autoscaling.knative.dev/metric: concurrency
autoscaling.knative.dev/target: "2"
autoscaling.knative.dev/minScale: "1"
autoscaling.knative.dev/maxScale: "10"
autoscaling.knative.dev/scaleDownDelay: "5m"
autoscaling.knative.dev/window: "60s"
autoscaling.knative.dev/panicWindowPercentage: "10"
autoscaling.knative.dev/panicThresholdPercentage: "200"
spec:
predictor:
containers:
- name: vllm
image: ghcr.io/your-org/vllm-server:latest
args:
- "--model"
- "/models/your-llm"
- "--port"
- "8000"
- "--gpu-memory-utilization"
- "0.90"
ports:
- containerPort: 8000
resources:
requests:
cpu: "2"
memory: "16Gi"
nvidia.com/gpu: "1"
limits:
cpu: "4"
memory: "24Gi"
nvidia.com/gpu: "1"
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 12
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 6
왜 minScale: "1" 이 콜드스타트 제거에 가장 강력한가
LLM 서빙에서 콜드스타트의 대부분은 “컨테이너 시작”이 아니라 아래 단계에서 발생합니다.
- GPU 노드가 없으면 노드 프로비저닝(수 분)
- 이미지 pull
- 모델 가중치 로딩(수십 초)
- 엔진 초기화 및 KV cache 워밍업
minScale을 1로 두면 최소 1개의 워커가 항상 떠 있으므로, “첫 요청 지연”이 거의 사라집니다. 대신 비용은 0이 아닌 1 GPU가 됩니다. 많은 팀에서 GPU 1개는 보험료처럼 두고, 나머지는 요청 기반으로 확장하는 방식이 비용 대비 효과가 좋습니다.
완전한 scale-to-zero가 꼭 필요하다면, 아래 “구현 2”의 프리워밍 패턴을 같이 보세요.
구현 2: scale-to-zero를 유지하면서 콜드스타트를 줄이는 프리워밍
minScale: "0"로 두고도 콜드스타트를 줄이려면, “실사용자 요청”보다 먼저 트래픽을 만들어 워커를 깨워야 합니다.
프리워밍 CronJob 예시
- 주기적으로
InferenceService엔드포인트에 가벼운 요청을 보냄 - 모델이 완전히 준비되었는지 확인하는
/health또는/ready사용
apiVersion: batch/v1
kind: CronJob
metadata:
name: llm-prewarm
namespace: llm
spec:
schedule: "*/5 * * * *"
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: curl
image: curlimages/curl:8.6.0
args:
- "-sS"
- "-m"
- "3"
- "http://llm-vllm.llm.svc.cluster.local/health"
이 방식은 단순하지만 한계도 명확합니다.
- 트래픽이 없는 시간에도 주기적으로 깨우면 비용이 다시 증가
- GPU 노드까지 0인 상태라면 노드 프로비저닝 시간은 여전히 큼
따라서 scale-to-zero를 진짜로 운영에 넣으려면, “노드 계층”도 같이 설계해야 합니다.
GPU 노드 오토스케일: 파드만 늘려선 해결이 안 된다
Knative/KServe는 파드를 늘릴 수 있어도, GPU 노드가 없으면 스케줄링이 막힙니다. 이때 발생하는 현상은 다음과 같습니다.
- 요청은 들어오는데 파드는
Pending - Knative는 스케일 결정을 했지만 실제 처리량이 안 나옴
- 결과적으로 타임아웃과 재시도가 폭발
운영에서 자주 보는 장애 형태는 OOMKilled나 CrashLoopBackOff와 함께 나타납니다. 특히 LLM은 메모리 스파이크가 크기 때문에 리소스 제한을 잘못 주면 초기화 중에 죽기도 합니다. 관련해서는 리소스 제한값과 장애 징후를 함께 정리한 글을 참고하면 좋습니다.
권장 패턴
- GPU 노드풀을 최소 1대 유지(콜드스타트 제거 최강)
- 또는 GPU 노드풀도 0으로 두되, “프리워밍”이 노드까지 깨우도록 설계
- 노드 프로비저닝이 긴 환경이라면
minScale: 1이 결국 더 싸게 먹히는 경우가 많음(사용자 이탈 비용 포함)
Knative 오토스케일 튜닝 포인트(LLM 특화)
LLM 서빙은 일반 HTTP API와 달리, 요청당 처리 시간이 길고 스트리밍 응답이 많으며, 동시성 한계가 뚜렷합니다. 그래서 기본값 그대로 쓰면 스케일이 늦거나 과도하게 튀는 일이 흔합니다.
1) metric은 concurrency가 안전한 출발점
rps는 스트리밍/긴 요청에서 “실제 부하”를 과소평가할 수 있음concurrency는 워커가 동시에 처리 중인 요청 수를 기준으로 잡아 예측이 쉬움
예: A10 24GB에서 7B 모델을 vLLM로 서빙할 때, 배치/시퀀스 길이에 따라 다르지만 target=1~4 사이에서 시작해 측정으로 조정하는 경우가 많습니다.
2) panic 설정으로 급증 트래픽 대응
panicWindowPercentage를 낮추면 급증 시 빠르게 늘림panicThresholdPercentage로 “언제 패닉 모드로 들어갈지” 결정
LLM은 “한 번 밀리면 회복이 느린” 특성이 있어, 급증 시 빠르게 늘리는 편이 체감 품질에 유리합니다.
3) scaleDownDelay로 플랩핑 방지
GPU 워커는 내려갔다가 다시 올라올 때 비용이 큽니다(모델 로딩). 따라서 너무 빨리 줄이면 오히려 평균 지연이 증가합니다.
scaleDownDelay: "5m"또는 더 길게- 트래픽 패턴이 주기적이면 10~20분도 고려
콜드스타트의 진짜 원인 분해: 어디를 줄일 것인가
콜드스타트는 하나가 아니라 여러 지연의 합입니다.
- 스케줄링 지연(노드 부족, taint/toleration, affinity)
- 이미지 pull 지연
- 모델 파일 다운로드/마운트 지연
- 엔진 초기화(vLLM/TGI/Triton)
- 첫 토큰 생성 전 워밍업
각 단계마다 대응이 다릅니다.
이미지 pull 줄이기
- 큰 이미지 레이어를 줄이고, 런타임/모델을 분리
- 노드에 이미지 프리풀(daemonset) 고려
CI에서 이미지를 자주 빌드한다면 캐시 전략이 중요합니다. 빌드 시간이 길면 “배포가 느려져” 결과적으로 장애 대응도 느려집니다.
모델 로딩 줄이기
- 모델을 원격에서 매번 당겨오지 말고 PVC/로컬 캐시 사용
- 여러 리비전이 같은 모델을 공유하면 캐시 적중률 상승
엔진 워밍업을 준비 상태에 반영하기
중요한 함정은 “프로세스가 떴다”와 “서빙 준비가 됐다”가 다르다는 점입니다.
- readinessProbe는 실제 추론 가능 상태를 확인해야 함
/health가 단순 200을 주는 수준이면, 라우터가 트래픽을 너무 빨리 밀어 넣어 첫 요청이 터집니다
가능하면 다음 중 하나를 권장합니다.
- 엔진이 모델 로딩 완료 후에만 readiness를
true로 반환 - 또는 별도의
/ready엔드포인트를 두고, KV cache 워밍업까지 끝난 후 OK
트래픽 흡수: 큐잉과 타임아웃을 설계해야 한다
콜드스타트를 완전히 0으로 만들기 어렵다면, “사용자 요청이 실패하지 않게” 흡수하는 장치가 필요합니다.
- Gateway/Ingress에서 타임아웃을 LLM 응답 특성에 맞게 조정
- 클라이언트 재시도는 백오프와 지터 필수
- 서버 측에서는 429/503 정책을 명확히 하고, 큐 길이를 제한
클라이언트 재시도 설계는 LLM API에서도 그대로 적용됩니다.
운영 체크리스트(바로 점검)
스케일이 안 오르는 경우
- GPU 리소스가
requests에 잡혀 있는지 확인(nvidia.com/gpu) - 노드에 GPU 디바이스 플러그인이 정상인지 확인
- Pod가
Pending이면 노드풀/오토스케일러 이벤트 확인
스케일은 오르는데 지연이 큰 경우
- 모델 로딩이 readiness에 반영되는지
- 이미지 pull이 느린지(레지스트리, 네트워크)
- 모델 아티팩트 다운로드 경로가 병목인지
자주 죽는 경우
- 메모리 제한이 너무 낮아 초기화 중
OOMKilled gpu-memory-utilization같은 런타임 파라미터가 공격적으로 잡혔는지livenessProbe가 너무 빨라서 “정상 초기화 중”을 죽이고 있는지
결론: 가장 현실적인 해법은 “minScale 1 + 공격적 확장”
GPU LLM에서 콜드스타트를 없애는 가장 확실한 방법은 결국 항상 떠 있는 최소 워커입니다. 실무에서는 다음 조합이 비용 대비 효과가 좋습니다.
minScale: 1로 사용자 체감 콜드스타트 제거concurrency기반으로 빠르게 스케일 아웃scaleDownDelay로 불필요한 다운/업 반복 방지- GPU 노드풀 최소 1대 또는 노드 프리워밍으로 스케줄링 지연 제거
완전한 scale-to-zero는 가능하지만, 노드까지 0인 환경에서는 “첫 요청이 느려질 수밖에 없는 물리적 한계”가 있습니다. 따라서 목표가 비용 최적화인지, 사용자 경험인지, 혹은 둘 다인지에 따라 minScale과 노드 전략을 함께 결정하는 것이 정답입니다.
원하시면 사용하는 런타임(vLLM/TGI/Triton), GPU 종류(A10/L4/H100), 목표 TPS, 평균 시퀀스 길이를 기준으로 target concurrency와 리소스(cpu/memory) 권장값까지 더 구체적으로 잡아드릴 수 있습니다.