모델 운영

MLOPS / 61번째 글

GPU 오토스케일링 — 늘리는 데 7분 걸리는 자원 다루기

웹 서버를 늘리듯 GPU 복제본을 늘리면 늦습니다. 콜드 스타트가 어디서 시간을 먹는지, 어떤 지표로 늘릴지, 줄일 때 무엇이 깨지는지 정리합니다.

PALDYN Team11 MIN READ

지난 글에서 어댑터 여럿을 서버 한 대에 태워 카드를 아꼈다. 그래도 트래픽이 늘면 서버 자체를 늘려야 한다. 웹 서버라면 오래된 문제이고 답도 정해져 있는데, GPU에서는 같은 답이 잘 듣지 않는다. 늘리라고 말한 뒤 실제로 늘어나기까지 몇 분이 걸리기 때문이다.

복제본 하나가 늘어나는 데 걸리는 시간

CPU 컨테이너는 이미지가 수백 MB이고 프로세스가 뜨면 곧 요청을 받는다. 몇 초다. GPU 서빙 컨테이너는 사정이 다르다.

GPU 복제본 하나가 늘어나기까지 걸리는 7분

이 그림이 왜 중요한가 하면, 오토스케일링을 처음 붙이는 사람들이 대개 ①과 ②만 보고 설정을 만지기 때문이다. 그 둘은 스케일러의 설정값이라 줄이면 바로 줄어든다. 그런데 전체의 3분의 2는 ③~⑥에 있고, 그쪽은 설정으로 안 줄어든다.

줄이는 방법은 있다. 다만 전부 미리 해 두는 종류다.

무엇을 어떻게 얼마나
③ 노드 확보 여유 노드를 미리 띄워 둔다(웜 풀) 120초 → 0
④ 이미지 노드 이미지에 컨테이너를 미리 넣어 둔다 90초 → 5초
⑤ 가중치 로컬 NVMe에 캐시해 둔다. 네트워크 스토리지에서 매번 읽지 않는다 70초 → 25초
⑥ 워밍업 준비 완료 신호(readiness)를 워밍업 뒤로 미룬다 시간은 그대로, 대신 덜 익은 서버로 요청이 안 간다

⑥에 손대는 방식이 조금 다르다는 점을 눈여겨볼 만하다. 워밍업 시간 자체는 줄지 않지만, 끝나기 전에 트래픽을 받지 않게 하는 것만으로 사용자가 겪는 지연은 확 달라진다. 준비 신호를 프로세스 기동에 걸어 두면 CUDA 그래프도 안 잡힌 서버가 첫 요청에서 몇 초를 잡아먹는다.

GPU 이용률로 스케일하면 안 되는 이유

지표를 무엇으로 삼을지가 다음 문제다. 가장 먼저 떠오르는 것이 GPU 이용률이고, 가장 자주 틀리는 것도 그것이다.

GPU 이용률은 포화점에서 말을 멈춘다

nvidia-smi가 말하는 이용률은 「최근 구간에 커널이 하나라도 돌고 있던 시간의 비율」이다. 카드를 얼마나 알차게 쓰는지가 아니라 놀지 않았는지를 잰다. 그래서 배칭이 잘 도는 추론 서버는 부하가 어중간할 때 이미 90%를 넘기고, 그 뒤로는 요청이 두 배가 되든 다섯 배가 되든 95% 언저리에 붙어 있다.

지표로서는 치명적이다. 정작 늘려야 할 구간에서 값이 안 움직인다. 임계를 80%로 잡으면 여유 있을 때 늘어나고, 90%로 잡으면 이미 밀린 뒤에야 늘어난다.

쓸 만한 지표는 밀린 정도를 직접 재는 것들이다.

지표 무엇을 말하나 주의
대기 중인 요청 수 지금 못 받고 있는 양 복제본 수로 나눠서 봐야 비교가 된다
KV 캐시 사용률 동시 요청을 더 받을 자리가 있는가 90%를 넘으면 엔진이 요청을 밀어내기 시작한다
첫 토큰 지연(TTFT) 사용자가 겪는 것 요청 길이에 따라 흔들려 잡음이 많다
초당 도착 요청 앞으로 올 부하 지연 지표보다 먼저 움직인다

실무에서는 대기열과 KV 캐시 사용률을 함께 보는 구성이 무난하다. 둘 다 엔진이 직접 내보내는 값이고(vLLM은 vllm:num_requests_waiting, vllm:gpu_cache_usage_perc), 포화 이후에도 계속 자란다.

몇 대가 필요한지 계산하기

늘리기로 정했다면 얼마나 늘릴 것인가. 대기열 길이만 보고 한 대씩 늘리면 7분짜리 콜드 스타트 동안 대기열이 계속 자라 과하게 늘어난다.

리틀의 법칙에서 시작하는 편이 낫다. 도착률 λ\lambda(초당 요청), 요청 하나의 평균 처리 시간 ss(초), 복제본 하나가 동시에 담을 수 있는 요청 수 cc라 하면 필요한 복제본 수는

N=⌈λ×sc×u⌉N = \left\lceil \frac{\lambda \times s}{c \times u} \right\rceil

uu는 목표 이용률이다. 초당 12건이 오고 요청 하나가 8초 걸리며 복제본이 동시에 24건을 담고 목표를 70%로 두면 12×8/(24×0.7)=5.712 \times 8 / (24 \times 0.7) = 5.7이라 여섯 대다.

uu를 1에 가깝게 두지 않는 것이 중요하다. 꽉 채워 계산하면 도착이 조금만 몰려도 대기열이 폭발한다. 여유분이 곧 콜드 스타트 7분을 견디는 완충재이므로, 스케일이 느릴수록 uu를 낮게 잡아야 한다.

0까지 줄일 것인가

트래픽이 없는 시간대에 복제본을 0으로 내리면 비용이 확 준다. 개발 환경이나 사내 도구라면 대개 옳은 선택이다.

대신 0에서 1로 가는 요청은 7분을 기다린다. 사용자가 있는 서비스라면 못 쓴다. 절충안이 몇 가지 있다.

  • 최소 1대 유지: 가장 흔하다. 첫 사용자가 겪는 지연이 사라지고, 비용은 한 대 값이다
  • 시간대 예약: 트래픽 패턴이 뚜렷하면 지표를 기다리지 말고 시간표로 미리 늘린다. 출근 시각 20분 전에 늘리는 식이다
  • 작은 모델로 받아 두기: 큰 모델이 뜨는 동안 작은 모델이 응답한다. 라우팅과 캐스케이드의 구조를 그대로 쓴다

세 번째는 구현이 복잡해 보이지만, 이미 라우팅 계층이 있다면 규칙 한 줄이다.

줄일 때 깨지는 것

늘리는 쪽만 신경 쓰다 줄이는 쪽에서 사고가 난다. LLM 서빙은 요청 하나가 수십 초 동안 토큰을 흘리는 작업이라 웹 요청과 다르다.

  • 진행 중인 생성을 끊으면 사용자는 문장 중간에서 끊긴 답을 받는다. 재시도도 어렵다 — 이미 절반을 스트리밍으로 보냈기 때문이다
  • KV 캐시는 그 복제본에만 있다. 다른 복제본으로 옮겨 이어 쓸 수 없다
  • 프리픽스 캐시도 함께 사라진다. 오래 살아 캐시가 잘 쌓인 복제본을 내리면 남은 복제본의 적중률까지 떨어진다

그래서 줄일 때는 새 요청만 끊고 진행 중인 것은 끝까지 보낸다. 쿠버네티스라면 종료 신호를 받은 뒤 준비 신호를 내리고, terminationGracePeriodSeconds를 가장 긴 생성이 끝날 만큼(보통 5~10분) 넉넉히 준다. 이 값이 기본값 30초로 남아 있는 것이 흔한 실수다.

줄이는 속도도 늘리는 속도와 달라야 한다. 빨리 늘리고 천천히 줄인다. 잘못 늘리면 잠깐 돈이 더 들지만, 잘못 줄이면 7분 동안 복구가 안 된다.

정리

자리 자주 하는 선택 더 나은 선택
지표 GPU 이용률 70% 복제본당 대기열 + KV 캐시 사용률
늘리는 양 한 번에 한 대 리틀의 법칙으로 목표 대수를 계산
최소 복제본 0 사용자 서비스면 1 이상
종료 유예 30초 (기본값) 가장 긴 생성 시간 이상
콜드 스타트 그대로 둔다 이미지·가중치를 노드에 미리 둔다

오토스케일링의 목적은 「자동으로 알아서」가 아니라 사람이 새벽에 안 깨는 것이다. 그러려면 늦게 반응하는 것을 전제로 여유분을 들고 있어야 한다. 여유를 0으로 깎은 자동 확장은 자동으로 장애를 만든다.


읽어주셔서 감사합니다. 😊

LATEST

모델 운영의 최신 글

모델 운영2026.09.04

KV 캐시 양자화 — 가중치보다 이쪽이 먼저 넘친다

긴 문맥에서 GPU 메모리를 실제로 잡아먹는 것은 가중치가 아니라 KV 캐시입니다. 캐시 크기를 계산하는 법, K와 V를 다르게 다뤄야 하는 이유, 어디까지 줄여도 되는지를 정리합니다.

16 MIN
모델 운영2026.09.04

양자화 보정 — 데이터 128개가 모델 품질을 정한다

양자화에서 스케일을 정하는 절차가 보정입니다. 무엇을 재는지, 데이터를 어디서 몇 개 뽑아야 하는지, 자르는 지점을 어떻게 고르는지, 그리고 잘못된 보정이 어떤 모양으로 드러나는지를 정리합니다.

21 MIN
모델 운영2026.09.03

과제 특화 증류 — 큰 모델의 답을 작은 모델에 옮긴다

범용 성능이 아니라 우리 과제 하나만 잘하는 작은 모델을 만드는 방법입니다. 교사에게 무엇을 받아야 하는지, 데이터를 어떻게 모으고 거르는지, 손익 분기가 어디인지를 정리합니다.

15 MIN