제5부 추론·서빙(Inference/Serving)
5.5 서빙 인프라·GPU 운영
프로덕션 LLM 서비스를 처음 세우는 팀이 거의 예외 없이 저지르는 실수가 하나 있다. GPU 서빙을 무상태 웹 서버 늘리듯 다루는 것이다. 트래픽이 오르면 파드를 추가하고, 부하는 라운드로빈으로 흩고, 용량은 평균으로 산정한다. 셋 다 LLM에서는 조용히, 그러나 확실히 붕괴한다. 새 복제본은 수십 초에서 수 분을 기다려야 기동되고(모델 가중치가 수십 GB다), 라운드로빈은 요청 비용의 수백 배 편차와 복제본에 눌어붙은 KV 캐시를 무시해 핫스팟과 캐시 미스를 부르며, 평균으로 산정한 용량은 이용률이 1에 다가서는 순간 대기가 급증해 p95를 악화시킨다. 세 실패의 근원은 하나다. LLM 서빙은 상태(KV·프리픽스 캐시)를 이고, 지연 SLO에 묶이고, 콜드스타트가 느린 — 웹이 아니라 대기행렬·용량 문제다.
앞의 네 장은 한 대의 GPU 안에서 벌어지는 일이었다. 추론의 물리(5.1: prefill/decode·KV 캐시·대역폭 장벽), 정밀도를 낮추는 양자화(5.2), 배칭·투기적 디코딩·PagedAttention으로 처리량을 극대화하는 커널(5.3), 그것들을 묶은 서빙 엔진(5.4). 이 장은 렌즈를 한 단계 넓혀 여러 GPU·여러 노드·여러 요청·여러 테넌트를 하나의 서비스로 운용한다. 이 장이 남기려는 문장은 이것이다 — 필요한 GPU 수는 하나의 숫자가 아니라 세 장벽의 최댓값이고, 병목은 대개 당신이 예상하는 장벽이 아니다.
이 장에 나오는 콜드스타트 시간·처리량·활용률 수치는 전부 환경 의존이다(모델 크기·GPU 세대·스토리지·프레임워크·워크로드 분포에 따라 자릿수가 달라진다). 구체 숫자는 개략 규모와 절차를 몸에 익히기 위한 대표값이며, 실무의 첫걸음은 언제나 당신의 모델·하드웨어로 부하시험해 이 값들을 직접 측정하는 것이다. 이 장의 무게는 특정 숫자가 아니라 어떤 양을 어떤 순서로 재고 역산하는가라는 방법에 있다.
5.5.1 서빙 스택의 지형 — 요청 경로와 제어 루프
용량 계획에 들어가기 전에, 한 요청이 통과하는 경로와 그 옆에서 실행되는 제어 루프를 한 장의 그림으로 정리해야 한다. 프로덕션 LLM 서빙은 대략 다섯 층으로 쌓이는데, 중요한 것은 층의 개수가 아니라 요청이 흐르는 굵은 경로와 그것을 감시·조절하는 점선 루프가 다른 시간 규모로 실행된다는 점이다. 요청은 밀리초~초 단위로 층을 통과하고, 오토스케일러는 수십 초~분 단위로 복제본을 증감한다. 이 시간 규모의 어긋남이 이 장의 거의 모든 어려움을 낳는다.
게이트웨이는 인증·인가·rate limit·쿼터·과금 계량을 맡는다. 스트리밍 연결과 토큰 단위 과금이라는 LLM 특유의 문제는 7.2의 몫이고, 이 장은 게이트웨이 뒤에 집중한다. 라우터는 요청을 어느 복제본으로 보낼지 정하는데, 웹과 달리 프리픽스 캐시·세션·부하를 알아야 잘한다. 큐·스케줄러 층은 복제본이 포화하면 요청을 줄 세우거나 거절하고 상류로 백프레셔를 적용한다. 이 전역 큐는 서빙 엔진 내부의 연속 배칭 스케줄러(5.3)와 완전히 다른 층이라는 점을 처음부터 명시해 두자 — 엔진의 스케줄러는 "이미 이 GPU에 들어온 요청들을 한 스텝에 어떻게 묶을지"를 정하고, 이 층은 "애초에 받을지, 어느 복제본에 줄지"를 정한다. 둘을 혼동하면 백프레셔가 어디서 걸려야 하는지를 놓친다.
복제본 풀이 실제 GPU다. 각 복제본은 한 대(또는 텐서 병렬로 묶인 여러 대) GPU 위에서 실행되는 서빙 엔진으로, 모델 가중치와 KV 캐시를 GPU 메모리에 상주시킨다. 그 옆에서 오토스케일러가 큐·지연을 보고 복제본을 늘리고 줄이며, 관측성이 전 층에서 지표를 걷는다. 그림에서 굵은 화살표는 초 안에 흐르지만, 가중치 저장소에서 새 복제본으로 향하는 보라색 화살표 — 콜드스타트 — 하나만 유독 분(分) 단위다. 이 한 화살표가 이 장에서 우리를 가장 자주 무는 지점이다.
5.5.2 용량 계획의 물리 — 세 개의 장벽
"GPU 몇 장이 필요한가"에 답이 하나가 아닌 이유는, 필요량을 동시에 세 방향에서 제약하기 때문이다. 5.1에서 세운 추론의 물리를 용량의 언어로 옮기면 세 개의 장벽이 된다. 세 장벽은 각자 다른 양을 상한으로 걸고, 실제 필요 GPU 수는 그중 가장 높은 장벽이 정한다.
5.5.2.1 장벽 1 — KV 메모리가 정하는 동시성 상한
한 복제본 GPU의 메모리 예산은 세 부분으로 나뉜다.
$$M_{\text{GPU}} = \underbrace{M_{\text{weights}}}_{\text{가중치(고정)}} + \underbrace{M_{\text{KV}}}_{\text{KV 캐시(가변)}} + \underbrace{M_{\text{overhead}}}_{\text{활성·프레임워크·단편화}}$$
가중치와 오버헤드는 사실상 고정이므로, 남는 자리가 KV 캐시 예산 $M_{\text{KV}}$ 이고 그것이 동시에 몇 개의 시퀀스를 담을 수 있는가를 정한다. 토큰 하나가 전체 층에 걸쳐 남기는 KV 바이트는(5.1.4):
$$\text{kv}_{\text{tok}} = 2 \times n_{\text{layers}} \times n_{\text{kv heads}} \times d_{\text{head}} \times b$$
앞의 $2$ 는 Key와 Value 두 벌, $b$ 는 요소당 바이트다(FP16이면 2). 여기서 GQA/MQA(1.6)가 왜 서빙 용량에 결정적인지가 곧장 드러난다 — $n_{\text{kv heads}}$ 를 줄이면 이 값이 그대로 작아져 같은 메모리에 더 많은 시퀀스를 담는다. KV를 따로 양자화해 $b$ 를 절반으로 내려도 마찬가지다. 담을 수 있는 총 KV 토큰과 최대 동시 시퀀스는:
$$\text{총 KV 토큰} = \frac{M_{\text{KV}}}{\text{kv}_{\text{tok}}}, \qquad B_{\text{mem}} \approx \frac{M_{\text{KV}}}{\overline{\text{ctx}} \cdot \text{kv}_{\text{tok}}}$$
$\overline{\text{ctx}}$ 는 평균 유효 컨텍스트(프롬프트 + 지금까지 생성한 토큰)다. 롱컨텍스트일수록 분모가 커져 이 장벽이 낮아진다 — 긴 입력은 계산 비용만 높이는 것이 아니라 동시성 전체를 깎아먹는다(5.6에서 이 장벽이 주인공이 된다).
5.5.2.2 장벽 2 — 메모리 대역폭이 정하는 처리량 상한
생성은 매 스텝 모델 가중치를 전부 읽으며 토큰을 하나씩 만든다. 그래서 디코드는 연산이 아니라 메모리 대역폭에 묶인다(5.1.6·5.1.8). 배치 1에서 스텝당 읽는 바이트는 대략 가중치 전체이고, 여기에 컨텍스트만큼의 KV 읽기가 추가된다. 스트림당 디코드 속도의 루프라인 상한은:
$$\text{tok/s}_{\text{stream}} \lesssim \frac{\text{BW}_{\text{mem}}}{M_{\text{weights}} + \overline{\text{ctx}} \cdot \text{kv}_{\text{tok}}}$$
배치 1은 가중치를 한 번 읽어 토큰 하나만 만들고 버리므로 산술강도가 극단적으로 낮다(5.1.8의 $I\approx 1$). 연속 배칭(5.3)이 하는 일이 바로 이 낭비를 제거하는 것이다 — 여러 시퀀스가 같은 가중치 읽기를 공유하게 만들어, 한 번의 가중치 읽기로 $B$ 개 토큰을 진행시킨다. 그래서 배치 $B$ 의 집계 처리량 $T_{\text{dec}}$ 는 배치에 거의 비례해 오른다. GPU당 초당 만들 수 있는 총 토큰 수가 이 장벽이다.
5.5.2.3 장벽 3 — 지연 SLO가 정하는 배치 상한
배치를 키우면 집계 처리량은 오르지만 개별 사용자가 느끼는 스트림당 속도는 떨어진다 — 이것이 5.3의 처리량-지연 트레이드오프다. 사용자 체감 지연은 두 조각으로 갈린다. 첫 토큰까지의 시간(TTFT)은 큐 대기와 prefill 시간의 합으로, 프롬프트가 길수록·큐가 길수록 커진다. 이후 토큰 간 간격(TPOT)은 스트림당 디코드 속도의 역수로, 배치가 클수록 커진다. 종단 지연은:
$$\text{end-to-end} \approx \text{TTFT} + (\bar{o}-1)\cdot\text{TPOT}, \qquad \bar{o}=\text{평균 출력 토큰 수}$$
제품이 정한 지연 SLO는 곧 "스트림당 최소 tok/s"로 번역되고, 그 최소 속도를 지키는 가장 큰 배치가 허용 최대 배치 $B_{\max}$ 다. 장벽 1(메모리)이 배치를 200까지 허용해도 장벽 3(지연)이 64에서 막으면 실효 배치는 64다. 지연이 메모리보다 먼저 배치를 막는 이 상황은 짧은 대화·엄격한 SLO에서 아주 흔하다.
5.5.2.4 세 장벽의 종합 — 리틀의 법칙과 GPU 수 공식
세 장벽을 각각 GPU 수 요구로 바꾸고 최댓값을 취한 뒤 가용성·버스트 헤드룸을 추가하면 필요 GPU 수가 나온다.
$$N_{\text{GPU}} = \left\lceil \max\!\Big(\underbrace{\tfrac{\lambda\,\bar{o}}{T_{\text{dec}}}}_{\text{처리량}},\ \underbrace{\tfrac{L}{B_{\max}}}_{\text{동시성·지연}},\ \underbrace{\tfrac{L\,\overline{\text{ctx}}\,\text{kv}_{\text{tok}}}{M_{\text{KV}}}}_{\text{KV 메모리}}\Big) \right\rceil \times (1 + r)$$
여기서 $\lambda$ 는 목표 QPS, $\bar{o}$ 는 평균 출력 토큰, $T_{\text{dec}}$ 는 운영 배치에서 GPU당 집계 디코드 tok/s, $B_{\max}$ 는 지연 SLO를 지키는 최대 배치, $M_{\text{KV}}$ 는 GPU당 KV 예산, $r$ 는 가용성·버스트 여유율이다. $L$ 은 동시에 시스템 안에 살아 있는 요청 수인데, 이것을 도착률과 체류시간에서 뽑는 다리가 리틀의 법칙(Little's Law)이다:
$$L = \lambda \cdot W, \qquad W = \text{평균 체류시간(end-to-end)}$$
리틀의 법칙은 가정이 거의 없는 항등식이라 강력하다 — 정상 상태이기만 하면 도착 분포가 무엇이든 성립한다. 초당 20건이 들어와 각자 평균 4.5초를 머무르면, 어느 순간을 포착해도 시스템 안에는 평균 $20\times4.5=90$ 건이 살아 있다. 이 90이 곧 유지해야 할 디코드 배치의 규모이고, 그것을 $B_{\max}$ 로 나누면 필요한 복제본 수가 나온다. 이 다섯 식이 용량 계획의 전부다. 다음 절에서 숫자를 넣어 끝까지 실행한다.
5.5.3 GPU 수를 직접 역산하기
절차를 몸에 익히기 위해 하나의 예제를 처음부터 끝까지 실행한다. 모든 숫자는 개략 규모를 위한 대표값이고, 실제로는 이 표를 부하시험으로 직접 측정해야 한다는 점을 다시 강조해 둔다.
5.5.3.1 설정 — 모델·하드웨어·워크로드
모델은 dense 13B, FP16(2 bytes), 층 $n_{\text{layers}}=40$, GQA로 $n_{\text{kv heads}}=8$, $d_{\text{head}}=128$ 로 둔다. 가중치는 $13\times10^9 \times 2\,\text{B} = 26\,\text{GB}$. 토큰당 KV는
$$\text{kv}_{\text{tok}} = 2\times 40 \times 8 \times 128 \times 2\,\text{B} = 163{,}840\,\text{B} \approx 160\,\text{KB}.$$
만약 GQA가 아니라 40개 질의 헤드가 전부 KV를 저장하는 MHA였다면 토큰당 KV는 $2\times40\times40\times128\times2 = 800\,\text{KB}$ — 정확히 5배다. 아키텍처 한 줄(KV 헤드 수)이 서빙 원가를 5배로 벌린다. GQA를 쓰는 실제 이유가 벤치마크 점수가 아니라 이 서빙 경제학에 있다는 것을, 용량 계획을 해 보기 전에는 실감하기 어렵다.
GPU는 80 GB HBM에 대역폭 약 3.35 TB/s(H100 SXM급, 세대·모델별로 다르다)로 둔다. KV 예산은 가중치와 오버헤드를 빼고 남는 자리다:
$$M_{\text{KV}} = 80 - \underbrace{26}_{\text{가중치}} - \underbrace{10}_{\text{활성·프레임워크·단편화 여유}} = 44\,\text{GB}, \qquad \text{총 KV 토큰} = \frac{44\times10^9}{163{,}840} \approx 268{,}000.$$
처리량 표는 부하시험의 산물이다. 배치가 커질수록 집계는 오르고 스트림당은 떨어지는데, 순수 루프라인은 이 하강을 낙관적으로 추정한다 — 실측 스트림당 속도는 배치가 커지며 어텐션 연산이 배치·컨텍스트에 비례해 늘고 연산 지붕에 다가서기 때문에 이론선보다 더 가파르게 떨어진다. 그래서 이론이 아니라 측정이 원천이다. 대표적 측정 결과는 이렇다(ctx≈800 기준).
| 배치 $B$ | 스트림당 tok/s | 집계 tok/s | KV 사용 |
|---|---|---|---|
| 1 | 118 | 118 | 0.13 GB |
| 16 | 105 | 1,680 | 2.0 GB |
| 32 | 92 | 2,944 | 4.0 GB |
| 64 | 68 | 4,352 | 8.0 GB |
| 128 | 44 | 5,632 | 16.0 GB |
워크로드는 피크 $\lambda = 20$ req/s, 평균 입력 500토큰, 평균 출력 $\bar{o}=300$토큰(유효 컨텍스트 $\overline{\text{ctx}}\approx 800$)이다. SLO는 end-to-end p95 ≤ 5 s, TTFT p95 ≤ 1 s로, 이는 스트림당 디코드 ≥ 60 tok/s(TPOT ≤ 약 16.7 ms)를 요구한다.
5.5.3.2 세 장벽을 각각 GPU 수로
지연 장벽(장벽 3)이 먼저 배치를 자른다. 스트림당 ≥ 60 tok/s여야 하므로 표에서 $B_{\max}=64$ 다(68 tok/s로 통과, 128은 44라 탈락). 이 $B_{\max}$ 를 쥐고 체류시간과 동시성을 구한다. 스트림당 약 68 tok/s에서 출력 300토큰이면 디코드에 $300/68 \approx 4.4\,\text{s}$, 여기에 TTFT 약 0.3초를 더해 평균 체류 $W \approx 4.5\,\text{s}$. 리틀의 법칙으로 동시 요청은
$$L = \lambda W = 20 \times 4.5 = 90.$$
이제 세 장벽을 각각 GPU 수로 번역한다.
KV 메모리 장벽은 여유롭다. 90 동시 × 800토큰 × 160KB $= 90 \times 0.125\,\text{GB} = 11.25\,\text{GB}$ 로, GPU 한 장의 KV 예산 44 GB에 한참 못 미친다 → $N_{\text{mem}} = \lceil 11.25/44 \rceil = 1$. 이 워크로드에서 메모리는 병목이 아니다.
처리량 장벽은 필요 디코드가 $\lambda\,\bar{o} = 20\times300 = 6{,}000\,\text{tok/s}$ 인데, GPU당 $B=64$ 에서 집계 4,352 tok/s이므로 $N_{\text{thr}} = \lceil 6000/4352 \rceil = 2$.
동시성·지연 장벽은 $L=90$ 을 $B_{\max}=64$ 로 나눠 $N_{\text{lat}} = \lceil 90/64 \rceil = 2$.
$$N_{\text{base}} = \max(N_{\text{thr}},\ N_{\text{lat}},\ N_{\text{mem}}) = \max(2, 2, 1) = 2.$$
5.5.3.3 헤드룸·이중화 — 왜 2로 끝나지 않나
2 GPU는 피크 평균을 정확히 맞춘 수다. 실제로 이것을 그대로 배포하면 며칠 안에 SLO를 위반한다. 확인해 보자. N=2에서 집계 용량은 $2\times4{,}352=8{,}704$ tok/s이고 유입은 6,000 tok/s이니 이용률 $\rho = 6000/8704 \approx 0.69$. 슬롯 관점으로도 $2\times64=128$ 슬롯에 동시 90건이니 $90/128\approx0.70$. 이용률 0.7은 다음 절에서 보듯 나쁘지 않은 지점이다. 문제는 세 가지가 이 계산 뒤에 숨어 있다는 것이다.
첫째, "피크 λ=20"은 피크 시간대의 평균이지 그 안의 분·초 단위 버스트가 아니다. 순간적으로 λ가 30~40으로 급증하면 이용률이 0.9를 넘겨 대기가 급증한다. 둘째, 복제본은 종료된다 — OOM·하드웨어 결함·배포 롤링 중이면 N이 순간 1로 떨어지고, 그러면 이용률이 1.4로 급증해 즉시 붕괴한다. 그래서 N+1 이중화가 필요하다. 셋째, 오토스케일이 반응하는 수십 초~분 동안 버틸 완충이 있어야 한다(5.5.5). 이 셋을 여유율 $r$ 로 흡수한다.
$$N_{\text{GPU}} = N_{\text{base}} \times (1 + r) \approx 2 \times (1 + 0.5) = 3.$$
즉 평균은 2장이 맞지만, 버스트·이중화·콜드스타트 완충까지 지키려면 상시 3장이다. N=3이면 한 장을 잃어도 남은 2장이 이용률 0.69로 감당하고, 정상 시엔 이용률이 0.46으로 내려가 버스트에 여유가 생긴다. 트래픽이 하루 중 크게 변동하면 오토스케일이 이 3장을 기초로 삼아 피크에 5~6장까지 늘렸다가 골짜기에 3장으로 줄인다.
5.5.3.4 이 예제가 반증하는 통념들
이 한 예제가 세 가지 흔한 오해를 깬다. 첫째, 병목은 종종 메모리가 아니라 지연이다. 여기서 KV는 충분했고 배치를 막은 것은 스트림당 속도 SLO였다. "8B니까 16GB면 된다" 식으로 메모리만 보고 사이징하면 지연 장벽에서 막혀 SLO를 위반한다. 워크로드에 따라 병목 장벽이 바뀌므로 — 롱컨텍스트·대형 모델은 메모리가, 짧은 대화·엄격한 SLO는 지연이 먼저 막힌다 — 세 장벽을 다 계산하고 최댓값을 취해야 한다. 둘째, 평균으로 사이징하면 안 된다. 헤드룸·이중화 없는 N=2는 장부상 맞지만 현장에서 붕괴한다. 셋째, 표의 숫자는 측정이 원천이다. 스트림당·집계 tok/s 곡선은 모델·GPU·프레임워크·양자화마다 다르므로, 사이징의 첫걸음은 언제나 대표 트래픽으로 부하시험해 이 표를 만드는 것이다.
5.5.4 왜 GPU를 100%로 못 채우나 — 대기행렬의 비선형
앞 절에서 "이용률 70% 근처로 운영하라"고 했다. 왜 90%나 95%가 아니라 70%인가. GPU를 유휴 상태로 두는 것이 곧 비용을 낭비하는 것이라면(5.5.12), 이용률을 최대한 1에 근접시키고 싶은 것이 인지상정이다. 그런데 대기행렬 이론이 그것을 막는다.
가장 단순한 M/M/1 대기행렬 — 요청이 무작위로 도착하고 한 대의 서버가 처리 — 에서, 정상 상태의 평균 체류시간은 서비스시간 $S$ 와 이용률 $\rho$ 로 이렇게 나온다:
$$W = \frac{S}{1-\rho}, \qquad \text{평균 큐 대기} = \frac{\rho}{1-\rho}\,S, \qquad \rho = \frac{\text{도착률}}{\text{처리율}}.$$
분모에 $(1-\rho)$ 가 있다는 것이 전부를 말한다. $\rho$ 가 1에 다가가면 대기가 급증한다. 숫자로 보면 이 비선형이 얼마나 급격한지 체감된다.
| 이용률 $\rho$ | 지연 배수 $\dfrac{1}{1-\rho}$ | 평균 큐 대기 $\dfrac{\rho}{1-\rho}$ (서비스시간 배) |
|---|---|---|
| 0.5 | 2.0 | 1.0 |
| 0.7 | 3.3 | 2.3 |
| 0.8 | 5.0 | 4.0 |
| 0.9 | 10 | 9 |
| 0.95 | 20 | 19 |
| 0.99 | 100 | 99 |
$\rho=0.7$ 에서 $\rho=0.9$ 로 이용률을 20%p 높이면 지연이 3.3배에서 10배로, 세 배 나빠진다. 앞 예제의 무부하 처리시간을 대략 서비스시간 $S\approx$ 몇 초로 가정하면, $\rho=0.9$ 의 큐 대기는 서비스시간의 9배 — TTFT SLO(≤1s)를 수십 초 규모로 초과한다. 이용률을 100%에 맞추려다 정확히 그 지점에서 서비스가 붕괴하는 것이다. 이것이 헤드룸의 정량적 근거다: 지연 SLO를 지키려면 이용률을 60~80% 대역에서 운영해야 하고, 나머지 20~40%는 낭비가 아니라 꼬리 지연을 사는 값이다.
여기에 실무적으로 중요한 미묘함이 하나 있다. 복제본 풀은 서버 한 대가 아니라 여러 슬롯을 가진 M/M/c 대기행렬에 가깝고, 서버 수 $c$ 가 클수록 안전하게 운영할 수 있는 이용률이 올라간다. 슬롯이 128개면 몇 개가 잠깐 몰려 차도 나머지가 흡수하므로, $\rho$ 를 0.85까지 밀어도 평균 대기는 M/M/1의 0.85만큼 나쁘지 않다. 큰 풀이 작은 풀보다 더 높은 이용률로 가동될 수 있는 이유가 이것이다. 다만 두 가지를 정직하게 인정해야 한다 — $\rho\to1$ 의 무릎(knee)은 $c$ 가 커도 사라지지 않고 위치만 오른쪽으로 밀릴 뿐이며, 평균이 정상일 때도 p95·p99 꼬리는 이미 상해 있다. 그래서 이용률을 스케일 지표로 삼을 때 평균만 보면 늦고, 꼬리 지연과 큐 깊이를 함께 봐야 한다. 그리고 이 계산은 도착이 무작위라는 가정 위에 있는데, 실제 트래픽은 버스트가 몰려 오므로(자기상관) 같은 평균 이용률에서도 대기가 더 나쁘다. 이론은 방향을, 부하시험은 값을 준다.
5.5.5 오토스케일링 — 콜드스타트와 프리워밍
트래픽은 하루·주 단위로 몇 배씩 변동하고 스파이크는 초 단위로 급등한다. 고정 용량으로 피크를 맞추면 골짜기에서 GPU가 유휴 상태다(=비용을 낭비한다). 그래서 오토스케일이 필요한데, LLM 서빙의 오토스케일은 무상태 웹 서버의 그것과 근본적으로 다르다.
무상태 웹 서비스에서 오토스케일은 거의 무비용이다. 컨테이너가 수백 ms~수 초에 기동되고 트래픽이 급증하면 곧바로 파드가 추가된다. LLM은 그렇지 않다. 새 복제본을 기동하려면 GPU 노드를 확보하고(클라우드면 인스턴스 프로비저닝 — 스팟·온디맨드 재고 대기), 컨테이너·CUDA를 초기화하고, 수십 GB의 모델 가중치를 스토리지·네트워크에서 GPU로 로드하고(대개 여기가 가장 길다), 엔진을 워밍업한다(CUDA 그래프 캡처·컴파일·프리필 워밍, 5.3·5.4). 이 합이 콜드스타트이고, 26 GB 가중치라면 스토리지 대역폭과 프로비저닝에 따라 수십 초에서 수 분이 흔하다. 문제의 핵심은 시간 규모의 어긋남이다 — 콜드스타트가 분 단위인데 스파이크가 초 단위라면, 반응형 오토스케일은 스파이크를 놓친다. 큐가 지표를 넘겨 스케일아웃을 촉발할 무렵에는 이미 SLO를 위반하고 있고, 새 복제본이 준비될 무렵에는 스파이크가 지나가 있을 수 있다. "오토스케일을 켜면 트래픽 문제는 해결된다"는 기대는 정확히 이 지연을 무시한다.
5.5.5.1 콜드스타트가 강제하는 헤드룸 — 수계산
콜드스타트가 얼마나 문제가 되는지 숫자로 보자. 앞 예제의 시스템이 피크에 N=3(GPU당 집계 4,350 tok/s, 풀 용량 13,050 tok/s)으로 유입 6,000 tok/s를 처리 중이라 하자(이용률 0.46). 예고 없는 이벤트로 유입이 3배로 급증해 18,000 tok/s가 되었다. 이제 필요 용량은, 이용률을 0.8 이하로 지키려면 $18{,}000/0.8 = 22{,}500$ tok/s → $\lceil 22{,}500/4{,}350\rceil = 6$ 복제본이다. 즉 3장을 더 추가해야 한다.
반응형 스케일만 믿으면 어떻게 되나. 콜드스타트 $T_{\text{cold}}=90$ 초 동안 용량은 13,050 tok/s에 묶여 있고 유입은 18,000 tok/s다. 초당 $18{,}000-13{,}050 = 4{,}950$ 토큰씩 백로그가 쌓인다. 90초 뒤 백로그는 $4{,}950 \times 90 = 445{,}500$ 토큰, 요청당 출력 300토큰이면 약 1,485건이 큐에서 대기 중이다. 이 큐가 빠지는 데만 다시 수십 초가 걸리고, 그동안 새로 오는 요청의 TTFT는 큐 길이만큼 — 수십 초로 — 급등한다. TTFT SLO 1초는 이미 두 자릿수 배로 초과됐다.
여기서 답이 갈린다. 콜드스타트가 반응을 90초 늦추므로, 그 90초를 메우는 방법은 스케일이 아니라 미리 확보한 여유여야 한다. 선택지는 세 가지다. 넉넉한 헤드룸으로 상시 6장을 데워 두면(또는 3장 웜풀 버퍼) 스파이크를 즉시 흡수하지만, 3장을 유휴 상태로 유지하는 비용을 부담한다. 반대로 초 단위 스파이크의 첫 파도는 스케일이 아니라 큐·admission control(5.5.8)로 흡수하고 — 낮은 우선순위를 잠깐 거절하고 인터랙티브만 통과 — 그 사이 3장이 부팅되게 한다. 현실적인 절충은 둘의 혼합이다: 작은 웜 버퍼(+1~2장)로 다리를 놓고, 지속 상승만 스케일로 대응하며, 순간 스파이크는 큐로 깎는다. 스케일과 큐는 다른 시간 규모의 도구라는 것 — 큐는 초, 스케일은 분 — 을 분리해 쓰는 것이 핵심이다.
5.5.5.2 무엇을 지표로 스케일하나
CPU 서비스의 반사적 지표인 GPU 활용률은 LLM에서 오해를 부른다. nvidia-smi의 활용률은 "SM이 뭔가 하고 있는 시간 비율"일 뿐 얼마나 유용한 처리(goodput)를 하는지가 아니다 — 디코드는 대역폭에 묶여 활용률이 높게 측정돼도 실제 tok/s는 낮을 수 있다. 더 나은 신호가 있다.
| 지표 | 무엇을 포착하나 | 스케일 신호로서 |
|---|---|---|
| 큐 대기시간·큐 깊이 | 수요가 용량을 초과 | 가장 직접적 — SLO와 직결 |
| TTFT / TPOT p95 | 사용자 체감 지연 열화 | 강하지만 후행 — 이미 늦을 수 있음 |
| 배치 점유율 | 여유 용량이 얼마 남았나 | 선행 신호로 유용 |
| KV 캐시 점유율 | 메모리 장벽 근접 | 롱컨텍스트에서 중요 |
| GPU 활용률 | SM 바쁜 정도 | 단독 사용 주의 — goodput 아님 |
내 견해로는 큐 깊이·대기시간과 배치 점유율을 주 신호로 삼고 지연 p95를 안전망으로 두는 조합이 가장 견고하다. 배치 점유율은 선행 지표라 지연이 상하기 전에 움직일 수 있고, 지연 p95는 다른 신호가 놓친 열화를 검출하는 최후 방어선이다. GPU 활용률은 하드웨어 건강을 보는 보조로만 쓴다.
5.5.5.3 스케일 제어 루프의 두 함정
스케일아웃 쪽 함정은 이미 본 콜드스타트 공백이다 — 결정과 준비 사이가 크므로 헤드룸과 프리워밍으로 메운다. 스케일인 쪽 함정은 덜 알려졌지만 못지않게 문제가 된다. 성급히 줄이면 그 복제본에서 진행 중이던 긴 생성 전체가 끊긴다. 30초 길이의 생성을 15초에 종료하면 사용자는 절반가량 나온 답을 잃는다. 그래서 스케일인은 반드시 드레인해야 한다 — 새 요청은 라우터에서 차단하고, 진행 중인 요청은 끝까지 마친 뒤에야 복제본을 해제한다. 또 지표가 변동할 때 늘렸다 줄였다 하는 플래핑을 막으려면 스케일인에 쿨다운(내린 뒤 한동안 다시 안 내림)과 히스테리시스(늘릴 때와 줄일 때 임계값을 다르게)를 둔다. 배포할 때는 민첩하게, 내릴 때는 굼뜨게가 원칙이다 — 잘못 높이면 돈이 조금 새지만, 잘못 내리면 SLO가 붕괴한다.
콜드스타트 자체를 줄이거나 감추는 방법도 있다. 예측 트래픽(업무 시작 시간 등)에 앞서 미리 복제본을 기동하는 스케줄·예측 기반 프리워밍, 로컬 NVMe 캐시·모델 사전 스테이징·빠른 로더(safetensors 등)로 가중치 로드를 가속하는 것, 그리고 최소 복제본을 1 이상으로 둬 scale-to-zero의 대가(다음 요청이 콜드스타트 전체를 부담하는 것)를 피하는 것이다. 지연이 중요한 서비스라면 0으로 내리는 유혹을 참고 최소 1을 상주시키는 편이 낫다 — 그 비용이 첫 사용자의 분 단위 대기보다 저렴하다.
5.5.6 GPU 스케줄링·배치 — 복제본을 노드에 배치하기
지금까지 "복제본 N개"라고 셌지만, 복제본을 실제 GPU·노드에 어떻게 앉히느냐가 또 하나의 운영 문제다. 이것은 서빙 엔진 안이 아니라 클러스터 스케줄러(대개 쿠버네티스와 GPU operator·device plugin) 층의 일이고, LLM에서만 유독 까다로운 이유가 있다.
첫째, 복제본이 항상 GPU 한 장이 아니다. 작은 모델은 한 장(또는 MIG 슬라이스)에 여러 복제본이 들어가지만, 한 장에 안 들어가는 대형 모델은 텐서 병렬로 여러 GPU를 하나의 복제본으로 묶는다(2.4). 이 묶음은 갱 스케줄링(gang scheduling)이 필요하다 — 텐서 병렬 4-way 복제본은 GPU 4장이 동시에 할당돼야 기동되고, 3장만 할당되면 기동되지도 못하면서 그 3장을 점유해 교착을 부른다. 순진한 스케줄러는 "빈 GPU에 하나씩" 채우다가 이 all-or-nothing 요구를 어기고, 그러면 여러 복제본이 서로의 GPU를 절반씩 점유하고 아무도 기동되지 못하는 상황이 벌어진다. 그래서 Volcano·Kueue 같은 갱 스케줄링 플러그인으로 "N장을 한꺼번에 아니면 아예 안"을 보장한다.
둘째, 토폴로지가 지연을 좌우한다. 텐서 병렬은 매 디코드 스텝마다 GPU 간에 활성값을 주고받으므로(5.1.6의 인터커넥트 항), 4장을 같은 노드의 NVLink 아일랜드 안에 배치하느냐, 아니면 PCIe나 노드 간 네트워크를 건너 배치하느냐가 TPOT를 크게 가른다. 느린 링크를 건너 텐서 병렬 그룹을 흩으면 매 스텝 통신이 병목이 되어 스트림당 속도가 주저앉는다. 스케줄러는 토폴로지 인지(topology-aware)여야 한다 — 텐서 병렬 그룹은 같은 NVLink 도메인 안에, 그보다 통신이 성근 파이프라인 병렬은 노드를 넘어도 되도록. 이 배치 하나를 잘못하면 5.5.3의 처리량 표 전체가 무의미해진다.
셋째, 단편화가 용량을 좌초시킨다. 8-GPU 노드 여럿에 4-GPU 복제본을 채우다 보면, 각 노드에 2장씩 남는 식으로 조각이 흩어져 총 여유는 충분한데 4장을 한 노드에서 못 모아 새 복제본을 못 기동하는 일이 생긴다 — 빈 GPU가 있는데 스케일아웃이 실패한다. 이를 줄이려면 복제본 크기를 노드 GPU 수의 약수로 맞추거나(8장 노드에 2·4·8-way), 빈 노드/조각을 주기적으로 재정리(defrag)하거나, MIG로 잘게 분할한 슬라이스를 스케줄 단위로 삼는다. 마지막으로, 노드는 유지보수·펌웨어·드라이버 업데이트로 내려야 할 때가 온다. 이때 노드를 코든(cordon)해 새 복제본이 안 앉게 막고, 위의 복제본을 드레인해 진행 요청을 마친 뒤 옮겨야 — 진행 중 생성을 끊지 않고 노드를 비운다. 오토스케일의 드레인과 같은 원리가 하드웨어 정비에도 그대로 적용된다.
5.5.7 멀티테넌시·GPU 공유 — MIG·MPS·프랙셔널
한 장의 GPU를 여러 워크로드·모델·테넌트가 나눠 쓰고 싶을 때가 있다 — 작은 모델 여럿, 개발·실험 환경, 트래픽이 낮은 모델들. 공유 방식은 격리 강도로 갈리고, 격리가 강할수록 간섭이 적은 대신 유연성이 준다.
| 방식 | 격리 | 메모리 분리 | 간섭 | LLM에서 언제 |
|---|---|---|---|---|
| MIG(Multi-Instance GPU) | 하드웨어(강) | 전용 슬라이스 | 거의 없음 | 여러 작은 모델을 한 장에 안전 격리·예측 가능 SLO. 슬라이스가 고정이라 큰 모델엔 부적합 |
| MPS(Multi-Process Service) | 소프트웨어(중) | 공유(부분 제한) | 있음(이웃 영향) | 버스트성 저부하 여러 프로세스로 활용률↑. 강한 SLO 보장은 어려움 |
| 시분할·프랙셔널 | 약 | 공유 | 큼 | 개발·실험·저부하. 프로덕션 지연 SLO엔 부적합 |
| 전용 1 GPU | 완전 | 전용 | 없음 | 대형 모델의 기본 — 모델이 GPU를 꽉 채우거나 넘침 |
| 다중 GPU(텐서 병렬) | — | 모델을 GPU에 분산 | — | 한 장에 안 들어가는 대형 모델. 공유의 반대 방향(2.4) |
LLM 특유의 현실이 여기 하나 있다. 대형 LLM은 한 장을 전부(또는 여러 장을 텐서 병렬로) 차지하는 경우가 많아, MIG·MPS는 주로 작은 모델·다모델 환경에서 의미가 크다. "GPU 한 장을 프랙셔널로 잘게 나눠 큰 모델을 여럿 올린다"는 대개 성립하지 않는다 — 모델 하나가 이미 한 장을 넘기기 때문이다. 그래서 공유의 실제 무대는 트래픽이 낮은 여러 소형 모델(분류기·리랭커·임베더·가드레일 모델 같은 것)을 한 장에 몰아 활용률을 높이는 쪽이다.
간섭(noisy neighbor)은 정직하게 인정해야 한다. MIG를 뺀 공유 방식은 한 테넌트의 부하가 다른 테넌트의 지연을 교란한다 — 메모리 대역폭·L2 캐시·PCIe·스케줄러를 공유하기 때문이다. 특히 디코드는 대역폭 바운드라, 이웃이 대역폭을 점유하면 내 TPOT가 그대로 급등한다. 지연 SLO를 보장해야 하는 프로덕션 테넌트라면 공유의 비용 절감과 격리·예측성을 비교 평가해야 한다. 내 견해로는 SLO가 엄격하면 MIG나 전용으로 가고, SLO가 느슨한 내부·배치 워크로드에서만 MPS로 활용률을 사는 것이 안전하다.
5.5.8 로드밸런싱·라우팅 — 프리픽스 인지·세션 어피니티
요청을 어느 복제본으로 보내는가. 웹에서는 라운드로빈·최소연결이면 충분하지만 LLM에서 라운드로빈은 나쁜 기본값이다. 이유가 둘 있고, 둘 다 5.1에서 세운 물리에서 나온다. 하나, 요청 비용이 극단적으로 불균일하다 — 입력 50토큰·출력 20토큰 요청과 입력 8,000토큰·출력 2,000토큰 요청의 비용은 수십~수백 배 차이다. 라운드로빈은 이 편차를 무시해 어떤 복제본엔 부하가 큰 요청만 쌓이고 어떤 복제본은 유휴 상태다. 둘, 상태가 국소적이다 — KV 캐시와 프리픽스 캐시(5.6)는 그것을 만든 복제본에만 있다. 같은 프리픽스를 가진 요청을 같은 복제본으로 보내면 캐시를 재사용해 prefill 전체를 건너뛰는데, 아무 데나 분배하면 이 이득이 사라진다.
기본 개선은 부하 인지(load-aware)다. 라운드로빈 대신 최소 큐·최소 대기시간·최소 진행토큰 복제본으로 보내면 요청 비용 불균일을 흡수한다. 여기에 상태를 추가한 것이 두 가지다. 세션 어피니티(sticky session)는 멀티턴 대화에서 이전 턴의 KV·프리픽스가 특정 복제본에 있으므로 같은 세션을 같은 복제본으로 보내 누적 컨텍스트의 prefill을 재사용한다(7.1). 프리픽스 인지(prefix-aware) 라우팅은 공유 시스템 프롬프트·few-shot 예시·문서처럼 여러 요청이 앞부분을 공유할 때, 그 프리픽스의 KV를 캐시한 복제본으로 라우팅해 prefill을 절약한다 — 흔히 프리픽스 해시를 일관 해시(consistent hashing)로 복제본에 매핑한다(5.6에서 알맹이를 다룬다). 여러 모델·크기를 운영하면 요청을 적절한 모델로 보내는 모델 인지 라우팅도 있는데, 이는 비용·품질·지연의 트레이드오프라 5.6·7.5의 주제다.
여기서 근본적인 긴장이 하나 있다. 어피니티(캐시 재사용)와 부하 균형은 서로 당긴다. 인기 프리픽스로 요청이 몰리면 그 복제본이 핫스팟이 되고, 그렇다고 분산하면 캐시 이득이 준다. 실무의 절충은 일관 해시에 부하 상한을 추가하는 것이다 — 프리픽스로 복제본을 고르되, 그 복제본이 포화하면 차선으로 넘긴다(그림의 폴백). SLO가 캐시 이득보다 우선이라는 원칙이 여기 내재되어 있다. 그리고 캐시 이득의 크기 자체를 재야 한다 — 프리픽스가 짧거나 공유율이 낮으면 어피니티 이득이 작아 단순 부하 인지가 낫다. 무엇이 실제 이득인지는 측정으로 정한다. 끝으로 어피니티를 강하게 걸 때의 위험을 잊지 말자. 한 복제본이 종료되면 그 복제본에 할당된 세션 전체가 영향을 받고 스케일인 드레인이 복잡해진다. 그래서 어피니티는 "강제"가 아니라 "선호"로 두고, 복제본 장애 시 재라우팅·재프리필로 깨질 수 있게 설계한다.
5.5.9 프리필·디코드 분리 — 이종 풀로 나누기
5.1에서 본 프리필과 디코드의 성격 차이 — 프리필은 연산 바운드, 디코드는 대역폭 바운드 — 는 인프라 층에서 하나의 설계 결정을 낳는다. 둘을 한 복제본에서 섞으면 서로를 잠식한다. 긴 프리필 하나가 GPU를 오래 점유하면 그동안 다른 요청들의 디코드가 멈춰 그들의 TPOT가 급등한다 — 한 사용자의 8,000토큰 프롬프트가 다른 사용자들의 스트리밍을 끊는 것이다. RAG나 긴 문서 요약처럼 프롬프트가 길고 편차가 큰 워크로드에서 이 간섭이 심하다.
완화하는 두 기법이 있다. 청크 프리필(chunked prefill)은 긴 프리필을 작은 조각으로 분할해 디코드 스텝 사이사이에 삽입해 프리필이 디코드를 오래 막지 않게 한다(엔진 스케줄러 수준, 5.3). 더 근본적인 것이 프리필/디코드 분리(disaggregation)로, 아예 프리필 전용 GPU 풀과 디코드 전용 풀을 나눈다. 프리필 풀은 연산 지붕에 맞춰 큰 배치로 실행해 GPU를 꽉 채우고, 디코드 풀은 대역폭에 맞춰 최적 배치로 스트리밍한다. 프리필 풀이 KV 캐시를 만들면 그것을 디코드 풀로 넘겨 이어 받는다.
분리는 무비용이 아니다. 새 병목이 하나 생기는데, 프리필이 만든 KV 캐시를 디코드 풀로 옮기는 전송 비용이다. 컨텍스트가 길면 KV가 수 GB라, 이 이동을 NVLink나 고속 네트워크로 하지 않으면 그 자체가 지연이 된다. 또 풀이 둘이 되면 용량 계획도 둘이 된다 — 프리필 풀은 프리필 처리량(입력 tok/s)의 세 장벽을, 디코드 풀은 디코드 처리량의 세 장벽을 각각 풀어야 하고, 둘의 비율은 워크로드의 입력/출력 길이 비에 따라 달라진다. 입력이 길고 출력이 짧은 워크로드(요약·추출)는 프리필 풀이 커야 하고, 대화처럼 출력이 긴 워크로드는 디코드 풀이 커야 한다. 이 두 풀을 독립적으로 스케일할 수 있다는 것이 분리의 실제 이득이다 — 프리필 부하와 디코드 부하가 따로 움직이므로. 다만 복잡도가 크게 오르니, 프롬프트가 짧고 균일한 챗에서는 분리의 이득이 간섭·복잡도 비용을 못 넘긴다. 내 견해로는 롱컨텍스트·RAG처럼 프리필이 부하가 크고 편차가 크며 TPOT SLO가 엄격한 워크로드에서만 분리가 값을 한다.
5.5.10 admission control·백프레셔 — 포화를 우아하게
용량은 유한하고 수요는 급변한다. 스케일이 따라붙기 전(콜드스타트 공백)이나 순간 스파이크에서 복제본이 포화하면, 무엇을 어떻게 거절·지연시킬지를 정해야 한다. 아무 통제 없이 다 받으면 5.5.4에서 본 대기행렬의 비선형이 그대로 작동해 — 이용률이 1을 넘기며 — 모두가 느려지고 아무도 SLO를 못 지키는 전면 붕괴가 온다. 다 받는 것이 가장 나쁜 선택이라는 것이 요점이다.
거절은 실패가 아니라 설계다. 포화 시 빠르게 429(Too Many Requests)로 돌려주는 것이, 다 받아서 모두를 붕괴시키는 것보다 낫다 — 최소한 일부 요청은 SLO 안에 완료된다. Retry-After 헤더로 클라이언트에 언제 다시 오라는 힌트를 준다. 이 거절 신호를 상류로 전파하는 것이 백프레셔다. 하류(복제본)의 포화를 게이트웨이·클라이언트로 알려 유입 자체를 늦춘다. 스트리밍 연결이면 흐름 제어로도 백프레셔가 걸린다. 우선순위 큐는 인터랙티브(사용자 대면) 요청을 배치·백그라운드 요청보다 앞에 세워, 포화 시 낮은 우선순위를 먼저 거절·지연시킨다(5.5.12의 선점과 이어진다).
LLM 특유의 장치가 큐 나이 제한이다. 큐가 무한정 길어지면 오래된 요청은 이미 클라이언트 타임아웃을 넘겨 있고, 그런 중단된 요청을 처리하는 것은 순수 낭비다 — 사용자는 이미 떠났는데 GPU만 소모한다. 그래서 큐에 상한과 나이 제한을 두고, 실행 직전에 "이 요청이 큐에서 이미 타임아웃을 넘겼는가"를 확인해 넘겼으면 처리 전에 폐기한다. 마지막으로, 이 전역 큐/거절은 서빙 엔진 내부의 연속 배칭 스케줄러(5.3)와 다른 층이라는 점을 다시 명시해 둔다. 엔진은 "이미 이 GPU에 들어온 요청들을 한 스텝에 어떻게 배치할지"를, 이 admission 층은 "애초에 받을지, 어느 복제본에 줄지"를 정한다. 백프레셔는 이 두 층에서 각각 걸리고, 둘을 연결하는 것이 큐 깊이·배치 점유율 지표다.
5.5.11 SLO·신뢰성 — 지연 분포·타임아웃·재시도·서킷브레이커·스팟
일반 API는 "요청 지연 p95" 하나면 되지만, 스트리밍 생성인 LLM은 최소 세 축을 따로 봐야 한다.
| 지표 | 정의 | 사용자에게 | 주 결정 요인 |
|---|---|---|---|
| TTFT | 첫 토큰까지 시간 | 얼마나 빨리 반응하나 | 큐 대기 + prefill(프롬프트 길이) |
| TPOT(=ITL) | 토큰 간 간격 | 얼마나 매끄럽게 흐르나 | 배치 크기·디코드 속도 |
| end-to-end | 완료까지 총시간 | 언제 끝나나 | TTFT + 출력 길이 × TPOT |
그리고 평균이 아니라 분위수로 약속해야 한다. p50(중앙값)은 낙관적이고, p95·p99가 실제 사용자 경험의 꼬리다. 꼬리가 중요한 이유는 증폭 때문이다 — 한 사용자의 여러 요청이나 여러 컴포넌트를 거치는 요청(에이전트의 여러 툴 호출 같은)은 가장 느린 조각에 의해 제한된다. 각 조각의 p95가 나쁘면 전체 p95는 훨씬 더 나쁘다. SLO 목표값 자체(TTFT p95 ≤ 1s 같은)는 제품이 정한다 — 실시간 챗과 야간 배치 요약은 완전히 다르므로 보편 정답이 없다. 원칙은 TTFT·TPOT·end-to-end를 분리해 p95/p99로 약속하고 계측하라는 것이다.
타임아웃과 재시도는 LLM에서 함정이 많다. LLM 요청은 길고 비용이 크다 — 긴 생성은 수십 초가 정상이다. 그래서 "전체 응답 30초" 같은 절대 타임아웃은 정상적인 긴 생성을 종료시킨다. 흔히 TTFT 타임아웃(첫 토큰이 안 오면 죽었다고 판단)과 스트림 유휴 타임아웃(토큰이 흐르는 한 유지, 끊기면 죽음)으로 나눈다. 재시도는 더 위험하다. 실패한 요청을 재시도하면 이미 절반 생성한 비용이 큰 작업을 처음부터 다시 하고(웹의 멱등한 GET 재시도와는 비용이 다르다), 과부하로 실패한 것을 재시도하면 재시도 폭풍으로 과부하를 악화시킨다 — 붕괴하는 시스템에 부하를 더 추가하는 것이다. 방어는 지수 백오프에 지터를 더하고, 재시도 예산을 두어(전체 트래픽의 몇 %만 재시도 허용) 폭풍을 막고, 멱등성 키로 중복 생성을 방지하고, 비용이 큰(긴) 요청은 아예 재시도하지 않는 것이다. 꼬리 지연을 줄이려 같은 요청을 두 복제본에 보내는 헤징(hedging)은 웹에선 유용하지만 LLM에선 비용이 두 배라 신중해야 한다 — 짧은 요청에만, 또는 안 쓰는 편이 낫다.
포화·장애가 번지는 것을 막는 장치가 셋 있다. 서킷브레이커는 특정 복제본·모델이 계속 실패하면 잠시 차단해 그쪽으로 요청을 안 보낸다 — 반복 실패하는 복제본에 요청을 계속 보내는 낭비와 실패 전파를 끊는다. 격벽(bulkhead)은 워크로드·테넌트별로 자원을 격리해 한쪽 장애가 전체로 번지지 않게 한다(멀티테넌시와 admission 우선순위의 연장). 그레이스풀 디그레이데이션은 과부하 시 더 작고 빠른 모델로 폴백하거나, 출력 길이를 제한하거나, 비필수 기능(긴 컨텍스트 등)을 끈다 — 완전 실패보다 열화된 서비스가 낫다는 원칙이다.
스팟(선점형) GPU는 온디맨드보다 크게 저렴하지만(할인폭·재고는 수시로 변동한다) 언제든 회수될 수 있다. LLM은 KV 캐시·진행 중 생성이라는 상태를 들고 있어 회수가 특히 문제가 된다. 선점 통보(대개 짧은 유예)를 받으면 즉시 새 요청을 끊고 진행 요청을 마감·이관하고, 이관이 어려우면 최소한 클라이언트에 재시도 가능한 실패를 반환한다. 배치 구성의 원칙은 온디맨드로 기저(baseline)를 확보하고 스팟으로 버스트를 흡수하는 것이다 — 기저까지 스팟에 두면 대규모 회수 때 SLO 전체가 붕괴한다. 여러 인스턴스 타입·가용영역·리전에 분산해 동시 회수 위험을 낮추고, 스팟은 재시도 가능·지연 관대한 오프라인·배치 추론에 몰아준다. 워크로드 성격으로 스팟과 온디맨드를 나누는 것이 핵심이다.
5.5.12 비용·활용도 — 활용률이 곧 단가
GPU는 켜 두면 쓰든 안 쓰든 돈이 나간다. 그래서 서빙 원가의 지배 변수는 활용률이다 — 같은 GPU로 두 배의 유용한 토큰을 뽑으면 토큰당 원가는 절반이다. 비용의 수식·가격 설계는 7.5이 본진이고, 여기선 인프라 운영이 활용률을 통해 원가를 어떻게 좌우하는지만 본다.
핵심 구분은 활용률(utilization)과 goodput이다. 5.5.5에서 봤듯 GPU 활용률 100%가 유용한 처리 100%를 뜻하지 않는다. 원가는 "SLO를 만족하며 성공적으로 완료된 토큰"당으로 재야 정직하다 — SLO를 어긴 처리량이나 타임아웃 넘긴 중단된 요청은 장부상 숫자일 뿐 실제로는 나쁜 경험을 대량 생산한 것이다.
$$\text{토큰당 원가} = \frac{\text{GPU-시간당 비용}}{\text{goodput(tok/s)} \times 3600}.$$
숫자를 넣어 보자. GPU-시간을 약 \$2.5(온디맨드 대형 GPU 대표값, 시점·계약에 따라 크게 다르다)로 두고, 5.5.3의 표에서 $B=64$ 의 goodput 4,352 tok/s를 쓰면
$$\text{100만 토큰당 원가} = \frac{2.5}{4{,}352 \times 3600} \times 10^6 = \frac{2.5}{15.67\text{M tok/hr}}\times 10^6 \approx \$0.16.$$
이제 배치를 덜 채우면 어떻게 되는지 보자. $B=16$ 으로 실행하면 집계가 1,680 tok/s로 떨어져 100만 토큰당 $2.5/(1{,}680\times3600)\times10^6 \approx \$0.41$ — 배치를 4배 낮췄더니 단가가 2.6배로 증가했다. 트래픽 골짜기에 GPU가 30%만 유용하게 실행되면 실효 goodput은 $0.3\times4{,}352 \approx 1{,}306$ tok/s로, 단가는 $2.5/(1{,}306\times3600)\times10^6 \approx \$0.53$ — 꽉 채운 배치의 3.3배다. "활용률↑=단가↓"가 막연한 구호가 아니라 정확히 이 배수라는 것을, 같은 GPU·같은 모델로도 운영에 따라 단가가 세 배 벌어진다는 것을 이 계산이 보여 준다.
그래서 이 분모(goodput)를 키우는 모든 것이 원가를 낮춘다 — 연속 배칭(5.3)·양자화(5.2)·프리픽스 캐시(5.6)·GQA(1.6)·좋은 라우팅(5.5.8)·골짜기에 줄이는 오토스케일. 인프라 운영이 곧 활용률 최적화이고 그것이 곧 원가 최적화다. 골짜기의 유휴 GPU를 지연에 관대한 백그라운드 작업으로 메우면 활용률이 더 오른다.
| 계층 | 예 | 지연 요구 | 선점 |
|---|---|---|---|
| 인터랙티브(높음) | 실시간 챗·대화 | 엄격(p95 SLO) | 선점 안 함 |
| 준실시간(중) | 비동기 요청·툴 호출 | 느슨 | 상위에 밀림 |
| 배치·오프라인(낮음) | 대량 요약·임베딩·평가 | 관대(분~시간) | 선점 대상 |
인터랙티브 부하가 급증하면 배치 작업을 후순위로 미루거나 일시정지해 GPU를 내주는 것이 선점(preemption)이다. 배치 작업은 재개 가능해야(체크포인트·재시도 가능) 한다. 인터랙티브가 낮 피크·밤 골짜기라면 밤에 배치 작업으로 채워 같은 GPU의 24시간 활용률을 높인다 — 스팟과 배치가 궁합이 좋은 이유가 여기 있다. 다만 함정이 있다. 우선순위·선점을 잘못 적용하면 낮은 우선순위가 기아(starvation)에 빠지거나, 선점 오버헤드(KV 캐시 축출·재계산)가 이득을 깎는다. 선점은 드물고 값싸게 설계해야 한다.
5.5.13 관측성 — 무엇을 계측하나
못 재면 못 고친다. 그리고 이 장의 모든 결정 — 사이징·스케일·라우팅·SLO·비용 — 이 지표를 전제한다. 7.6이 관측성의 본진(트레이싱·품질 모니터링·회귀 탐지)이고, 여기선 서빙 인프라·GPU 운영에 특화된 계측만 정리한다.
| 범주 | 지표 | 왜 |
|---|---|---|
| 지연 | TTFT·TPOT·end-to-end (p50/p95/p99) | SLO의 직접 대상. 셋을 분리해서 |
| 처리량 | req/s, 입력 tok/s, 출력 tok/s | 용량·원가의 분모 |
| 큐 | 큐 깊이·큐 대기시간·거절율(429) | 포화·스케일 신호 |
| 배치 | 배치 크기·배치 점유율 | 활용률·처리량-지연 위치 |
| KV·메모리 | KV 캐시 점유율·축출율·OOM | 메모리 장벽·안정성 |
| GPU | 활용률·메모리·전력·온도 | 하드웨어 건강(단, 활용률은 goodput 아님) |
| 캐시 | 프리픽스·KV 캐시 적중률 | 라우팅·비용 효과 |
| 에러 | 타임아웃·5xx·선점·재시도율 | 신뢰성·꼬리 원인 |
| 원가 | GPU-시간, 요청·토큰당 원가 | 유닛 이코노믹스(7.5) |
| 스케일 | 복제본 수·콜드스타트·드레인 시간 | 오토스케일 건강 |
몇 가지 원칙이 이 카탈로그를 쓸모 있게 만든다. 첫째, 분위수를 저장하라 — 평균 지연은 꼬리를 감추므로 p95·p99를 히스토그램으로 남긴다. 둘째, 요청 관점의 RED(Rate·Errors·Duration)와 자원 관점의 USE(Utilization·Saturation·Errors)를 함께 보되, LLM은 여기에 토큰·큐·캐시를 더한다. 셋째, 입출력 토큰 분포를 관측하라 — 이것이 사이징(5.5.3)의 입력값이고, 프롬프트·출력 길이 분포가 바뀌면 용량 계획 전체가 바뀐다. 넷째, 한 요청의 게이트웨이→라우터→큐→복제본 경로를 분해 추적(trace)해 지연이 어디서 났는지 — 큐 대기냐 prefill이냐 decode냐 — 를 가른다. 이것이 꼬리 디버깅의 핵심이다. 다섯째, 알람은 SLO에서 역산한다 — 에러 예산(error budget)을 정하고 소진 속도로 알람을 설정해, 개별 지표 스파이크가 아니라 SLO 위반 위험을 알린다.
"활용률 X%면 건강", "TTFT Y ms면 좋음" 같은 절대 기준은 없다. 당신의 SLO·워크로드·하드웨어가 기준이다. 관측성이 하는 일은 절대 판정이 아니라, 당신의 목표 대비 지금 어디에 있고 어디서 손실되는지를 보여 주는 것이다.
이 장은 한 대의 GPU 물리(5.1)를 복제본 함대의 경제학으로 확장했다 — 세 장벽으로 GPU 수를 역산하고, 대기행렬로 헤드룸을 정당화하고, 콜드스타트에 맞선 오토스케일과 큐, 상태를 아는 라우팅, 활용률로 환산한 원가까지. 다음 장(5.6)은 이 장에서 골격만 세운 캐시·라우팅의 알맹이를 다룬다 — 긴 입력이 왜·얼마나 비용이 큰지, 프리픽스 캐시로 무엇을 어떻게 재사용하는지, 여러 모델·크기로 요청을 라우팅해 비용과 품질을 함께 최적화하는 법. 그다음 제6부로 넘어가면 이 서빙 위에 프롬프트·RAG·에이전트가 추가된다.