Chapter 11

LLM 서빙 설계 플레이그라운드

모델을 학습시키는 것은 한 번이지만 서빙은 매일, 매초 일어난다. "Llama 3 70B를 하루 수백만 명에게 보여 주려면 GPU가 몇 장 필요하고, 응답은 얼마나 빠르며, 토큰 백만 개에 얼마가 드는가?" 이 질문에 답하는 데 필요한 것은 의외로 몇 개의 숫자뿐이다 — 메모리 용량, 메모리 대역폭, 연산 성능, 그리고 모델의 크기와 KV 캐시. 이 장에서는 그 숫자들을 손으로 계산하고, 시뮬레이터에서 직접 조립해 보면서 서빙 시스템을 설계하는 감각을 기른다.

서빙의 요구: 지연과 처리량

8장에서 본 것처럼 LLM 응답은 두 단계로 만들어진다. 프롬프트 전체를 한 번에 통과시켜 KV 캐시를 채우는 프리필(prefill)과, 토큰을 하나씩 뽑아내는 디코드(decode)다. 사용자가 느끼는 속도는 이 두 단계에 각각 대응하는 두 숫자로 요약된다.

시간 → 요청 도착 대기 프리필 (프롬프트 전체) 1번째 디코드: 한 스텝에 한 토큰 TTFT TPOT 종단 지연 = TTFT + (n − 1) × TPOT 프리필은 연산량이 크고 한 번, 디코드는 연산량이 작고 n번 — 병목이 서로 다르다
그림 11-1. 요청 하나의 타임라인. TTFT는 대기와 프리필, TPOT은 디코드 한 스텝의 길이로 정해진다. 출력이 500 토큰이고 TPOT이 30 ms면 디코드에만 15초가 걸리므로, 긴 답변에서는 TPOT이 체감 속도를 좌우한다.

지연과 처리량은 서로 부딪힌다. 요청 여러 개를 묶어(배치) 한 번에 처리하면 GPU가 바쁘게 일해 처리량이 오르지만, 한 스텝이 길어져 각 사용자의 TPOT은 늘어난다. 그래서 서빙 설계는 보통 SLA(Service Level Agreement) 또는 SLO 형태로 "TTFT p95 ≤ 1 s, TPOT p95 ≤ 50 ms"처럼 지연 상한을 먼저 정하고, 그 안에서 처리량(= 비용)을 최대화하는 문제로 정리된다. 이 장의 시뮬레이터는 모두 이 한 문장을 다른 각도에서 들여다본다.

오프라인 vs 온라인대량 문서 요약, 합성 데이터 생성 같은 오프라인 배치 작업은 지연 제약이 거의 없으므로 배치를 메모리가 허락하는 한 키워 처리량만 최대화한다. 채팅·코딩 보조 같은 온라인 서비스는 TTFT·TPOT 제약 아래에서 처리량을 짜낸다. 같은 GPU, 같은 모델이라도 두 경우의 최적 설정과 토큰 단가는 몇 배씩 차이 난다.

하드웨어 숫자: GPU 표

서빙 설계에 필요한 GPU 숫자는 세 개다. 메모리 용량(가중치와 KV 캐시가 들어가는가), 메모리 대역폭(디코드 한 스텝에 가중치를 얼마나 빨리 읽는가), 연산 성능(프리필과 큰 배치를 얼마나 빨리 계산하는가). 여기에 여러 GPU를 묶을 때의 GPU 간 대역폭(NVLink)이 더해진다. 아래 표는 이 장의 시뮬레이터가 쓰는 값이다.

GPU메모리대역폭BF16 denseFP8 dense리지 포인트 (BF16)GPU 간 연결
A100 SXM 80GB80 GB HBM2e약 2.0 TB/s312 TFLOPS— (미지원)≈ 156 FLOP/BNVLink 600 GB/s
H100 SXM80 GB HBM3약 3.35 TB/s약 989 TFLOPS약 1,979≈ 295 FLOP/BNVLink 900 GB/s
H200141 GB HBM3e약 4.8 TB/s약 989약 1,979≈ 206 FLOP/BNVLink 900 GB/s
B200 (2024~25 발표치)약 180~192 GB HBM3e약 8 TB/s약 2,250약 4,500≈ 281 FLOP/BNVLink 약 1.8 TB/s
RTX 409024 GB GDDR6X약 1.0 TB/s약 165 (FP16 텐서)약 330≈ 165 FLOP/BPCIe 4.0 약 32 GB/s
Apple M2 Ultra최대 192 GB 통합 메모리약 0.8 TB/s약 27 (GPU, 추정)—≈ 34 FLOP/B— (단일 칩)

표의 FLOPS는 희소성(sparsity)을 쓰지 않은 dense 텐서 코어 최대치이고, 대역폭도 사양서 최대치다. 실제 커널은 대역폭의 70~90%, 연산의 40~70% 정도를 낸다. B200 수치는 2024~25년 발표 기준이며 제품 구성(공랭/수랭, HGX/GB200)에 따라 다르고, RTX 4090의 텐서 FLOPS는 FP16 누산 기준 약 165 TFLOPS(FP32 누산이면 그 절반)이다. Apple 통합 메모리는 macOS가 기본적으로 GPU에 전체의 약 75%만 할당한다. 이런 불확실성 때문에 이 장의 모든 계산은 "자릿수가 맞는 추정"으로 읽어야 한다.

표에서 바로 읽히는 것세대가 바뀔 때 연산 성능은 대역폭보다 빨리 늘어난다(A100→B200: FLOPS 약 7배, 대역폭 약 4배). 메모리 용량과 대역폭이 상대적으로 귀해진다는 뜻이고, 그래서 9장의 양자화나 KV 캐시 압축처럼 "읽을 바이트를 줄이는" 기법의 가치가 계속 커진다. Apple 칩은 연산은 약하지만 용량이 커서, 배치 1로 큰 모델을 "돌려 보는" 데에는 의외로 좋은 선택이 된다.

메모리 예산: 가중치 + KV 캐시 + 작업 공간

서빙 GPU 메모리는 세 덩어리로 나뉜다. 첫째는 가중치로, 파라미터 수 × 파라미터당 바이트다. Llama 3 70B(약 70.6B 파라미터)를 BF16(2바이트)으로 올리면 141 GB, FP8이면 71 GB, 4비트(그룹 스케일 포함 약 4.125비트)면 약 36 GB다.

$$M_{\text{weight}} = N_{\text{param}} \times \frac{b_w}{8}\ \text{바이트}$$
여기서 \(b_w\)는 가중치 비트 수(BF16 = 16, FP8 = 8, INT4 ≈ 4.125)다.

둘째는 KV 캐시다. 8장에서 본 대로 각 층은 지나간 모든 토큰의 키와 값을 저장한다. GQA(Grouped-Query Attention)로 KV 헤드가 \(n_{kv}\)개라면 토큰 하나당 크기는 다음과 같다.

$$M_{\text{KV/token}} = 2 \times L \times n_{kv} \times d_{\text{head}} \times b_{kv}$$
2는 K와 V, \(L\)은 층 수, \(d_\text{head}\)는 헤드 차원, \(b_{kv}\)는 원소당 바이트(BF16 = 2, FP8 = 1)다.
모델파라미터층 \(L\)\(d_\text{model}\)KV 헤드 × \(d_\text{head}\)KV/토큰 (BF16)8K 컨텍스트 1요청
Llama 3 8B8.03B324,0968 × 128128 KiB1.07 GB
Llama 3 70B70.6B808,1928 × 128320 KiB2.68 GB
Llama 3.1 405B405.9B12616,3848 × 128504 KiB4.23 GB
Mixtral 8x7B (MoE)46.7B (활성 약 12.9B)324,0968 × 128128 KiB1.07 GB

셋째는 작업 공간이다. CUDA 컨텍스트, cuBLAS·어텐션 커널의 임시 버퍼, 프리필 중간 활성값, 메모리 단편화를 합쳐 GPU당 수 GB가 든다. vLLM 같은 서빙 엔진은 기본으로 전체 메모리의 90%만 쓰고(gpu_memory_utilization=0.9), 그 안에서 가중치와 작업 공간을 뺀 나머지를 전부 KV 캐시 블록으로 미리 잡아 둔다(PagedAttention). 결국 동시에 처리할 수 있는 요청 수는 다음처럼 정해진다.

$$B_{\max} = \left\lfloor \frac{0.9\,C_{\text{GPU}} \cdot N_{\text{GPU}} - M_{\text{weight}} - M_{\text{work}}}{(\,\text{프롬프트} + \text{출력}\,) \times M_{\text{KV/token}}} \right\rfloor$$

H100 한 장(80 GB)에 Llama 3 8B BF16을 올리면 가중치 16 GB, 작업 공간 2 GB를 빼고 약 54 GB가 KV 캐시 몫이다. 컨텍스트 8K 요청은 요청당 1.07 GB이므로 약 50개를 동시에 처리할 수 있다. 같은 GPU에 70B를 BF16으로는 아예 올릴 수 없다. 아래 시뮬레이터에서 막대 위쪽을 끌어 올리면 동시 요청 수가 늘어나며 KV 캐시가 차오른다.

SIMULATOR

GPU 메모리 적재

막대를 위아래로 끌어 동시 요청 수 조절
가중치 정밀도
KV 캐시 정밀도
GPU 개수 (텐서 병렬)
가중치 (전체)—
요청당 KV—
GPU당 사용—
최대 동시 요청—
—
해볼 것: ① Llama 3 70B BF16을 H100 1장에 올려 보자 — 가중치만으로 넘친다. 2장(텐서 병렬)이면 들어가지만 KV 여유가 얼마 없다. ② 가중치를 FP8로 바꾸면 최대 동시 요청이 몇 배가 되는지, 다시 KV까지 FP8로 바꾸면 또 몇 배가 되는지 보자. ③ 컨텍스트를 128K로 올리면 요청 하나의 KV가 수십 GB가 되어 동시 요청이 한 자릿수로 떨어진다. ④ 405B를 BF16/FP8/INT4로 바꿔 가며 H100 8장에 들어가는 조합을 찾아보자. GPU당 작업 공간 2 GB, 사용 상한 90%(Apple은 75%)로 가정했다.

루프라인: 메모리 한계인가, 연산 한계인가

메모리에 올렸다면 다음 질문은 "얼마나 빠른가"다. 커널 하나의 실행 시간은 두 가지 중 느린 쪽으로 정해진다. 필요한 바이트를 메모리에서 읽어 오는 시간 \(Q/\beta\)와, 필요한 연산을 수행하는 시간 \(W/\pi\)다(\(\beta\): 대역폭, \(\pi\): 최대 FLOPS). 둘을 가르는 기준은 산술 강도(arithmetic intensity) — 읽어 온 바이트 하나당 몇 번 연산하는가 — 이다.

$$I = \frac{W\ (\text{FLOPs})}{Q\ (\text{바이트})},\qquad P_{\text{attainable}} = \min(\pi,\ \beta \cdot I),\qquad I_{\text{ridge}} = \frac{\pi}{\beta}$$
H100 SXM: \(I_\text{ridge} = 989\times10^{12} / 3.35\times10^{12} \approx 295\) FLOP/B. 산술 강도가 이보다 작으면 메모리 바운드, 크면 계산 바운드다.
I_ridge ≈ 295 메모리 바운드: P = β × I 계산 바운드: P = π 디코드 배치 1 (I ≈ 1) 디코드 배치 64 프리필 2K 토큰 산술 강도 I (FLOP/바이트, 로그) 성능 (FLOP/s, 로그) 0.1 1 10 100 10⁴
그림 11-2. 루프라인 모델(H100 SXM BF16 기준 모식도). 비스듬한 지붕은 대역폭 한계, 평평한 지붕은 연산 한계다. 배치 1 디코딩은 지붕의 왼쪽 끝 근처에 있어 최대 FLOPS의 0.3%만 쓴다. 배치를 키우면 점이 오른쪽 위로 올라간다.

디코드와 프리필의 산술 강도

디코드 한 스텝을 보자. 파라미터 \(P\)개짜리 모델에서 토큰 하나는 대략 \(2P\) FLOPs(곱셈 + 덧셈)를 쓴다(6장). 배치에 토큰이 \(b\)개 있으면 연산은 \(2Pb\)지만, 가중치는 한 번만 읽어서 \(b\)개 토큰이 같이 쓴다. BF16이면 읽는 바이트는 \(2P\)다.

$$I_{\text{decode}} \approx \frac{2Pb}{2P + b \cdot c \cdot M_{\text{KV/token}}} \;\xrightarrow{\ c\,\text{작을 때}\ }\; b\ \ \text{FLOP/B}$$
\(c\)는 각 요청의 현재 컨텍스트 길이. KV 캐시는 요청마다 따로 읽어야 하므로 배치로 나눠 쓸 수 없다.

배치 1이면 \(I \approx 1\) FLOP/B로, H100의 리지 포인트 295와 비교하면 300배 가까이 부족하다. 즉 배치 1 디코딩은 거의 순수하게 "가중치를 읽는 속도"로 결정된다. 토큰당 걸리는 시간의 하한은 \(2P/\beta\)이고, Llama 3 8B BF16을 H100에서 돌리면 \(16\,\text{GB} / 3.35\,\text{TB/s} \approx 4.8\) ms, 즉 사용자 1명 기준 약 210 tok/s가 이론 상한이다. 반면 프리필은 프롬프트 \(p\)개 토큰을 한 번에 처리하므로 \(I \approx p\)가 되어, 프롬프트가 수백 토큰만 넘어도 계산 바운드 영역에 들어간다.

하나 더 중요한 점: KV 캐시 읽기는 배치로 나눠 쓸 수 없다. 어텐션 부분만 떼어 보면 토큰당 연산 \(4 L d\, c\)에 비해 읽는 KV는 \(c \cdot 2 L n_{kv} d_\text{head} \cdot 2\) 바이트라서, 산술 강도가 \(d/(n_{kv} d_\text{head})\) = Llama 3 8B 기준 약 4 FLOP/B로 배치와 무관하게 고정된다. 컨텍스트가 길수록 디코드는 배치를 키워도 계산 바운드로 넘어가지 못한다.

SIMULATOR

루프라인 플레이그라운드

점을 좌우로 끌기
모델 (BF16)
리지 포인트—
디코드 I / 달성—
디코드 한 스텝—
프리필 I / 시간—
해볼 것: ① 배치 1에서 디코드 점이 지붕의 비스듬한 부분 아래쪽에 있는 것을 확인하자 — 달성 FLOPS가 최대치의 1%도 안 된다. ② 'KV 포함'을 끄고 배치를 키우면 \(I \approx b\)로 거의 정확히 따라가며, b ≈ 295에서 H100의 지붕 모서리에 닿는다. ③ KV 포함을 켜고 프롬프트를 32K로 늘리면 배치를 아무리 키워도 디코드 점이 리지 포인트에 못 미친다(KV 읽기는 배치로 공유되지 않으므로). ④ GPU를 A100 → B200으로 바꾸며 리지 포인트가 어떻게 움직이는지 보자. 흐린 선은 다른 GPU의 지붕이다. 최대치(이상적 상한) 기준이며 용량 제한은 무시했다.

배치: 처리량과 지연의 트레이드오프

루프라인을 시간으로 바꿔 쓰면 디코드 한 스텝(= TPOT)의 길이를 바로 계산할 수 있다. 배치 \(b\), 평균 컨텍스트 \(c\)일 때

$$t_{\text{step}}(b) \approx \max\!\left(\frac{M_{\text{weight}} + b\,c\,M_{\text{KV/token}}}{\beta},\ \frac{b\,(2P + 4 L d\, c)}{\pi}\right),\qquad \text{총 처리량} = \frac{b}{t_{\text{step}}},\quad \text{사용자당} = \frac{1}{t_{\text{step}}}$$

배치가 작을 때는 가중치 읽기가 지배하므로 \(t_\text{step}\)이 거의 일정하다 — 배치를 2배로 늘려도 TPOT은 그대로이고 총 처리량만 2배가 된다. 사실상 공짜 점심이다. 배치가 커지면 두 가지 일이 생긴다. KV 캐시 읽기 \(b\,c\,M_\text{KV}\)가 가중치 읽기를 넘어서며 \(t_\text{step}\)이 \(b\)에 비례해 늘기 시작하고, 결국 연산 시간 항이 지붕에 닿는다. 그 지점부터는 배치를 키워도 총 처리량이 더 이상 늘지 않고 사용자당 속도만 떨어진다. 그리고 그 전에 메모리 용량(최대 동시 요청)이 한계를 긋는 경우가 많다.

SIMULATOR

배치 크기 스윕

그래프를 끌어 배치 선택
모델 · 정밀도
TPOT총 처리량사용자당 tok/sKV 읽기 무시
배치 b—
TPOT—
총 / 사용자당—
병목—
해볼 것: ① b = 1 → 16으로 끌어 보자. TPOT은 거의 그대로인데 총 처리량은 16배가 된다. ② 컨텍스트를 512로 줄이면 KV 읽기가 작아져 '무시' 곡선과 거의 겹치고, 전환점이 리지 포인트 근처(b ≈ 수백)로 간다. ③ 컨텍스트 32K에서는 배치 수십 개부터 KV 읽기가 가중치 읽기를 넘어서고, 그 전에 메모리 용량(세로 점선)이 먼저 끝난다. ④ FP8로 바꾸면 가중치·KV 바이트가 반으로 줄어 같은 배치에서 TPOT이 짧아지고 용량 한계는 오른쪽으로 간다. 이상적 상한(최대 대역폭·FLOPS) 기준이다.
TPOT SLA가 배치를 정한다"TPOT ≤ 30 ms"라는 목표가 있으면, 그래프에서 TPOT 곡선이 30 ms를 넘지 않는 가장 큰 \(b\)가 곧 최적 배치다. 이 배치에서의 총 처리량이 GPU 한 대가 그 SLA로 벌 수 있는 최대 토큰 수이고, 토큰 단가를 결정한다. 지연 목표를 느슨하게 할수록 처리량이 오르고 단가가 떨어진다.

연속 배칭: 빈자리를 즉시 채우기

배치가 처리량의 열쇠라면, 실제 서비스에서 문제는 요청이 제각각의 시각에 도착하고 출력 길이도 제각각이라는 점이다. 전통적인 정적 배칭(static batching)은 요청 몇 개를 묶어 시작한 뒤 가장 긴 요청이 끝날 때까지 배치를 유지한다. 10 토큰만에 끝난 요청의 자리는 500 토큰짜리 요청이 끝날 때까지 비어 있고, 그 사이 도착한 요청은 대기열에서 기다린다.

연속 배칭(continuous batching, iteration-level scheduling)은 스케줄링 단위를 "요청"에서 "디코드 한 스텝"으로 바꾼다(Orca, 2022). 매 스텝마다 끝난 요청을 빼고 대기 중인 요청을 빈 슬롯에 넣는다. 디코드 스텝 비용은 배치 크기에 크게 의존하지 않으므로(앞 절), 슬롯을 늘 채워 두는 것이 곧 처리량이다. vLLM, TensorRT-LLM, SGLang 등 현대 서빙 엔진은 모두 이 방식이며, 새 요청의 프리필을 디코드 스텝에 잘게 끼워 넣는 chunked prefill도 함께 쓴다.

정적 배칭 연속 배칭 슬롯0 슬롯1 슬롯2 슬롯3 A B 비어 있지만 못 씀 C (가장 긺) D 배치 종료 → 다음 배치 E, F, G, H 시작 E F G H 슬롯0 슬롯1 슬롯2 슬롯3 A G B E H C D F 같은 8개 요청이 훨씬 일찍 끝난다 가로 = 디코드 스텝(시간). 정적 배칭의 점선 칸은 슬롯은 잡혀 있지만 아무 일도 하지 않는 시간이다.
그림 11-3. 정적 배칭(위)과 연속 배칭(아래). 같은 요청 A~H를 처리할 때 연속 배칭은 끝난 슬롯에 대기 중인 요청을 즉시 넣는다. 출력 길이의 편차가 클수록 차이가 커진다.

아래 시뮬레이터는 실제로 요청을 생성해(포아송 도착, 시드 고정) 두 스케줄러를 같은 입력으로 돌린다. 시간 단위는 디코드 한 스텝이고, 한 스텝의 비용은 배치 크기와 무관하다고 가정했다(메모리 바운드 영역). 프리필 시간은 생략했다.

SIMULATOR

스케줄러 타임라인: 정적 vs 연속 배칭

끌어서 시점 이동 · 막대 클릭으로 요청 강조
재생
슬롯 활용률 (정적 / 연속)—
평균 대기 (스텝)—
평균 완료 시간 (스텝)—
전체 소요 (스텝)—
해볼 것: ① 기본 설정에서 정적 배칭의 회색 칸(끝났는데 비워 둔 슬롯)과 위 삼각형(도착) 아래로 쌓이는 대기열을 보자. ② 출력 길이를 '모두 같음'으로 바꾸면 두 방식의 차이가 크게 줄어든다 — 연속 배칭의 이득은 길이 편차에서 나온다. ③ 도착률을 0.05로 낮추면 둘 다 한가해 차이가 작고, 0.3 이상으로 올리면 정적 배칭의 대기 시간이 폭발한다. ④ 슬롯 S를 16으로 늘리면 같은 부하에서 대기가 줄어든다(메모리가 허락한다면). 막대를 누르면 같은 요청이 두 레인에서 강조된다.

병렬화: 한 GPU를 넘어서

모델이 GPU 하나에 안 들어가거나, 하나로는 지연 목표를 맞출 수 없으면 여러 GPU로 나눈다. 나누는 방법은 크게 네 가지다.

텐서 병렬 (TP) G0 G1 G2 G3 층 하나의 행렬을 쪼갬 층마다 all-reduce → NVLink 필수 (노드 안) 파이프라인 병렬 (PP) 층 1–20 · G0 층 21–40 · G1 층 41–60 · G2 층 61–80 · G3 층을 단계로 나눔 경계에서만 활성값 전달 · 버블 데이터 병렬 (DP) 모델 모델 모델 모델 모델 통째로 복제 요청을 나눠 받음 (복제본) 전문가 병렬 (EP) 라우터 E0,1 E2,3 E4,5 E6,7 MoE 전문가를 GPU에 분산 토큰을 all-to-all로 배달 실제 배치 예: Llama 3.1 405B (FP8 ≈ 406 GB) 노드 1개 = H100 8장 (640 GB) → TP = 8로 한 노드 안에 · KV 여유 약 150 GB BF16 (≈ 812 GB)이면 H100 16장 → 노드 2개: 노드 안 TP 8 × 노드 사이 PP 2 처리량을 늘릴 때는 이 묶음 전체를 DP로 복제
그림 11-4. 네 가지 병렬화. 통신이 잦은 TP는 NVLink로 묶인 노드 안에서, 통신이 드문 PP와 DP는 노드 사이(InfiniBand 등)에 쓰는 것이 기본 원칙이다.

텐서 병렬(Tensor Parallelism)은 각 층의 가중치 행렬을 열/행 방향으로 쪼개 GPU마다 일부를 계산하게 한다(Megatron-LM 방식). 어텐션은 헤드 단위로, FFN은 중간 차원 단위로 나누면 층 하나에 all-reduce 두 번이면 된다. 장점은 가중치와 KV가 \(N\)등분되고 대역폭도 \(N\)배가 되어 디코드 TPOT이 실제로 짧아진다는 점이다. 대신 토큰 하나마다 층당 두 번씩, 80층이면 160번의 all-reduce가 필요하다. 각 all-reduce는 \(b \cdot d\) 원소로 작지만 횟수가 많아 지연이 쌓이므로, GPU 사이가 수백 GB/s~TB/s급 NVLink로 묶여 있지 않으면(PCIe는 약 32 GB/s) 이득이 금방 사라진다. KV 헤드가 8개인 모델은 TP 8까지가 자연스럽다.

파이프라인 병렬(Pipeline Parallelism)은 층을 몇 단계로 잘라 GPU마다 연속된 층 묶음을 맡긴다. 경계에서 활성값만 넘기면 되므로 통신은 적지만, 한 요청의 한 토큰은 단계를 순서대로 지나가므로 단일 요청의 지연은 줄지 않는다. 여러 마이크로배치를 흘려 넣어야 모든 단계가 동시에 바쁘고, 시작과 끝에 일부 단계가 노는 파이프라인 버블이 생긴다. GPipe 방식에서 버블 비율은 다음과 같다.

$$\text{버블 비율} = \frac{p - 1}{m + p - 1}$$
\(p\): 파이프라인 단계 수, \(m\): 마이크로배치 수. 학습에서는 1F1B, interleaved 스케줄로 버블을 더 줄인다.
SIMULATOR

파이프라인 버블

칸을 누르면 같은 마이크로배치 강조
작업
버블 비율—
전체 시간—
단일 GPU 대비 속도—
해볼 것: ① p = 4, m = 1이면 버블이 75% — GPU 네 대 중 늘 세 대가 논다. ② m을 16으로 늘리면 버블이 약 16%로 줄어든다. ③ p를 8로 늘리면 같은 m에서 버블이 커진다. F는 한 칸, B는 두 칸(역전파가 순전파의 약 2배)으로 그린 GPipe 스케줄이다. 추론 서빙에서는 요청이 계속 흘러 들어오므로 마이크로배치가 사실상 무한히 이어져 버블은 작지만, 단일 요청의 지연은 p단계를 모두 지나야 해서 줄지 않는다.

데이터 병렬은 모델 전체를 복제해 요청을 나눠 받는다. 서빙에서는 가장 단순하고 확장성이 좋은 방법이다 — 모델이 한 묶음(예: H100 2장)에 들어가면, 그 묶음을 여러 벌 띄우고 로드 밸런서로 나누면 된다. 전문가 병렬은 MoE(6장)의 전문가들을 GPU마다 나눠 두고 라우터가 고른 전문가 쪽으로 토큰을 all-to-all로 보낸다. 전문가 수가 많은 대형 MoE(예: 수백 개 전문가)에서 필수적이다.

405B를 올리려면 GPU 몇 장?Llama 3.1 405B는 BF16 가중치만 약 812 GB, FP8이면 약 406 GB다. H100(80 GB) 8장 = 640 GB 노드 하나에 BF16은 들어가지 않는다. FP8로는 들어가고 KV 캐시에 약 150 GB가 남는다(작업 공간 제외 전). 실제로 Meta는 405B를 FP8로 양자화해 단일 8-GPU 노드에서 서빙할 수 있게 공개했다. BF16을 고집하면 H100 16장(노드 2개, TP 8 × PP 2), 또는 H200(141 GB) 8장 = 1,128 GB 한 노드가 필요하다.

비용: $/1M 토큰

서빙 비용은 결국 "GPU 시간을 몇 개의 토큰으로 나눠 갖느냐"다. GPU \(N\)장을 시간당 \(c_\text{GPU}\) 달러에 빌렸고 총 처리량이 \(T\) tok/s라면

$$\text{비용 (\$/1M 토큰)} = \frac{N \cdot c_{\text{GPU}}\ (\$/\text{h})}{T\ (\text{tok/s}) \times 3600} \times 10^6$$

예를 들어 시간당 $2.5짜리 H100 한 장이 Llama 3 8B를 배치 1로 약 200 tok/s 낸다면 약 $3.5/1M 토큰이지만, 배치를 키워 2,500 tok/s를 내면 약 $0.28/1M 토큰으로 열 배 이상 싸진다. 같은 하드웨어, 같은 모델인데 단가를 정하는 것은 배치(= 처리량)다. 여기서 GPU 시간당 가격은 클라우드·약정·시기에 따라 크게 다르므로 아래 슬라이더의 값은 모두 사용자가 넣는 예시값이다. 또한 이 식은 출력 토큰만 셌다. 실제 API는 입력(프리필) 토큰을 출력보다 훨씬 싸게 받는데, 프리필은 계산 바운드라 토큰당 GPU 시간이 디코드보다 훨씬 적기 때문이다.

SIMULATOR

비용 계산기

점을 끌어 처리량 변경
GPU 개수
$/1M 출력 토큰—
하루 생산 토큰—
월 GPU 비용—
해볼 것: ① 처리량을 200 → 2,000 tok/s로 끌면 단가가 정확히 1/10이 된다(로그-로그 그래프에서 기울기 −1의 직선). ② 점선 표시는 앞의 배치 스윕 모델로 계산한 'H100 · Llama 3 8B BF16 · 컨텍스트 2K'의 배치 1/16/128 처리량(이상적 상한)이다. ③ 가동률을 30%로 낮추면(밤에는 요청이 거의 없다) 실효 단가가 3배 이상 오른다 — 서비스 사업자가 트래픽을 평평하게 만들려는 이유다. 가격은 예시값이며 실제 계약가와 다르다.

종합 서빙 플레이그라운드

이제 지금까지의 식을 모두 묶어 하나의 서빙 구성을 평가해 보자. 아래 시뮬레이터가 쓰는 모델은 다음과 같다(모두 GPU 하나당 값으로 계산한 뒤 텐서 병렬 \(N\)을 반영한다). 교육용으로 단순화한 모델이며, 실제 엔진은 커널 효율, 스케줄링, 프리필 끼워 넣기, 투기적 디코딩 등으로 수십 % 이상 다를 수 있다.

  1. 적재: 가중치 \(M_w/N\) + KV + 작업 공간 2 GB가 GPU 메모리의 90% 안에 들어가야 한다. 실제 배치 \(b = \min(U, B_{\max})\). \(U > B_{\max}\)면 나머지는 대기열로 간다.
  2. 디코드 스텝: \(t_\text{mem} = (M_w^{\text{read}}(b) + b\,\bar c\,M_\text{KV}) / (N \beta \eta_m)\), \(t_\text{comp} = b(2P_\text{act} + 4Ld\bar c)/(N\pi\eta_c)\), \(t_\text{comm} = 2L\left(\frac{2(N-1)}{N}\frac{2bd}{\beta_\text{link}} + \tau\right)\), \(\text{TPOT} = \max(t_\text{mem}, t_\text{comp}) + t_\text{comm}\). 여기서 \(\bar c = p + o/2\)는 평균 컨텍스트, \(\eta_m = 0.8\), \(\eta_c = 0.6\)은 실효 효율, \(\tau\)는 all-reduce 한 번의 고정 지연(NVLink 5 µs, PCIe 20 µs로 가정)이다. MoE는 배치의 토큰들이 건드리는 전문가 비율 \(1-(1-k/E)^b\)만큼 전문가 가중치를 읽는다.
  3. 프리필: \(t_\text{pf} = \max\!\big(p(2P_\text{act} + 2Ldp)/(N\pi\eta_c),\ M_w/(N\beta\eta_m)\big) + t_\text{comm}(p)\). TTFT = 대기 + \(t_\text{pf}\) + 진행 중인 스텝 하나. 대기열이 있으면 슬롯이 비는 데 걸리는 시간 \(\lceil U/b - 1\rceil (t_\text{pf} + o \cdot \text{TPOT})\)을 더한다.
  4. 처리량·비용: 총 \(b/\text{TPOT}\) tok/s(출력 기준), 사용자당 \(1/\text{TPOT}\), 단가는 앞 절의 식.
SIMULATOR

종합 서빙 플레이그라운드

아래 그래프를 끌어 동시 사용자 수 조절
가중치
KV 캐시
GPU 개수 (TP)
적재 / 배치—
TTFT—
TPOT—
사용자당 / 총—
$/1M 출력 토큰—
—
해볼 것: ① 기본값(70B FP8, H100 2장)에서 동시 사용자를 1 → 32 → 최대까지 끌어 보자. 총 처리량은 오르고 단가는 떨어지지만, 최대 동시 요청을 넘는 순간 TTFT가 대기열 때문에 폭증한다. ② GPU를 4장으로 늘리면 TPOT이 줄어드는지(대역폭 N배), 통신 비중이 얼마나 커지는지 보자. RTX 4090 4장으로 바꾸면 PCIe 통신이 병목이 된다. ③ 405B BF16을 H100 8장에 올려 보고(실패), FP8 → INT4로 바꿔 보자. ④ Mixtral은 총 파라미터(47B)만큼 메모리를 차지하지만 배치 1에서는 활성 13B만 읽어 빠르다. 배치를 키우면 거의 모든 전문가를 읽게 되어 이점이 줄어든다. ⑤ 프롬프트 32K로 TTFT가 어떻게 되는지, 출력 길이 4K로 KV가 어떻게 되는지 보자.
설계 순서 요약① 정밀도를 정한다(9장: 대개 FP8 가중치 + FP8/BF16 KV는 품질 손실이 작다). ② 가중치 + 목표 동시 요청의 KV가 들어가는 최소 GPU 묶음을 찾는다. ③ TPOT SLA를 넘지 않는 최대 배치를 찾는다. ④ 그 배치의 처리량으로 단가를 계산하고, 필요한 총 트래픽을 그 처리량으로 나눠 복제본(DP) 수를 정한다. ⑤ 긴 프롬프트가 많으면 TTFT를 위해 프리필 전용 GPU를 따로 두는 분리형(disaggregated) 서빙도 고려한다.

학습 쪽 숫자: \(C \approx 6ND\)

서빙과 같은 방식으로 학습 규모도 추정할 수 있다. 파라미터 \(N\)개 모델을 토큰 \(D\)개로 학습하는 데 드는 연산량은 대략

$$C \approx 6ND\ \text{FLOPs}$$
순전파가 토큰당 \(2N\), 역전파가 그 두 배인 \(4N\) FLOPs라서 합쳐 \(6N\)이다(어텐션 항과 재계산은 제외). 학습 시간 = \(C / (\text{GPU 수} \times \pi \times \text{MFU})\).

MFU(Model FLOPs Utilization)는 이론 최대 FLOPS 중 모델 연산에 실제로 쓰인 비율이다. 통신, 파이프라인 버블, 메모리 바운드 연산, 장애 복구 때문에 대규모 학습의 MFU는 보통 30~50% 수준이다. 공개 보고 기준으로 Llama 3.1 405B는 약 15.6조 토큰으로 학습했고, \(6 \times 405\times10^9 \times 15.6\times10^{12} \approx 3.8\times10^{25}\) FLOPs, 최대 약 16K장의 H100에서 BF16 MFU 약 38~43%로 학습했다(Meta, 2024). 메모리 쪽도 계산해 보자. Adam 혼합 정밀도 학습은 파라미터당 약 16바이트(BF16 가중치·기울기 4 B + FP32 마스터 가중치·모멘트 2개 12 B)가 필요하므로 405B는 상태만 약 6.5 TB — 그래서 학습은 FSDP/ZeRO로 상태까지 GPU 수천 장에 쪼갠다.

SIMULATOR

학습 연산량·기간 추정

점을 좌우로 끌어 토큰 수 변경
연산량 C—
학습 기간—
GPU·시간—
토큰/파라미터—
해볼 것: ① 기본값(405B, 15.6T 토큰, H100 16K장, MFU 40%)에서 기간이 약 두 달로 나오는지 확인하자 — 실제 보고(장애·재시작 포함 54일 학습 구간 등)와 자릿수가 맞는다. ② 점을 끌어 토큰을 줄이면 연산량 축에서 GPT-3(약 3.1×10²³) 근처로 내려간다. ③ 토큰/파라미터 비율이 20(Chinchilla 최적) 근처인지, Llama 3처럼 수백~수천으로 '과학습'하는지 비교해 보자. 작은 모델을 오래 학습하면 학습비는 더 들지만 서빙(이 장의 주제)이 싸진다. GPU 시간당 가격을 곱하면 학습 비용도 추정할 수 있다.

핵심 정리

  1. 서빙 품질은 TTFT(대기 + 프리필), TPOT(디코드 한 스텝), 처리량(비용)으로 요약되며, 지연 SLA 아래에서 처리량을 최대화하는 문제로 정리된다.
  2. GPU 메모리 = 가중치(파라미터 × 바이트) + KV 캐시(\(2Ln_{kv}d_\text{head}b_{kv}\) × 토큰 × 요청) + 작업 공간. KV 예산이 최대 동시 요청 수를 정한다.
  3. 디코드의 산술 강도는 BF16 기준 약 \(b\) FLOP/B로 리지 포인트(H100 약 295)보다 훨씬 작아 메모리 대역폭 바운드다. 배치 1의 상한은 \(\beta / M_w\) tok/s. 프리필은 \(I \approx p\)로 계산 바운드다.
  4. 배치를 키우면 TPOT은 거의 그대로인 채 처리량이 늘다가, KV 읽기와 연산이 지배하면서 꺾인다. KV 읽기는 배치로 공유되지 않아 긴 컨텍스트일수록 일찍 꺾인다.
  5. 연속 배칭은 매 디코드 스텝마다 빈 슬롯을 채워, 출력 길이가 제각각인 실제 트래픽에서 슬롯 활용률과 대기 시간을 크게 개선한다.
  6. TP는 가중치·KV·대역폭을 N등분/N배 하지만 층마다 all-reduce가 필요해 NVLink가 필수이고, PP는 통신이 적은 대신 버블 \((p-1)/(m+p-1)\)이 생기며, DP는 복제로 처리량을 늘린다.
  7. $/1M 토큰 = GPU 비용 / (처리량 × 3600) × 10⁶. 같은 하드웨어에서 단가는 배치와 가동률이 정한다. 학습 연산량은 \(C \approx 6ND\), 기간은 \(C/(\text{GPU 수} \cdot \pi \cdot \text{MFU})\).

확인 퀴즈

1. H100 SXM(약 3.35 TB/s) 한 장에서 Llama 3 8B BF16을 배치 1로 디코딩할 때, 이론적 최대 속도에 가장 가까운 것은?

배치 1 디코딩은 매 토큰 가중치 전체(8.03B × 2 B ≈ 16 GB)를 읽어야 하는 메모리 바운드 작업이다. 3.35 TB/s ÷ 16 GB ≈ 209 tok/s가 상한이다. 연산(989 TFLOPS ÷ 16 GFLOPs ≈ 60,000)이 아니라 대역폭이 속도를 정한다.

2. KV 캐시를 무시하면 BF16 디코딩의 산술 강도는 배치 \(b\)에 대해 약 \(b\) FLOP/B다. H100에서 디코딩이 계산 바운드가 되기 시작하는 배치는 대략?

리지 포인트 = 989 TFLOPS ÷ 3.35 TB/s ≈ 295 FLOP/B. \(I \approx b\)이므로 배치 약 300에서 지붕 모서리에 닿는다. 실제로는 KV 읽기와 메모리 용량 때문에 그 전에 다른 한계를 만나는 경우가 많다.

3. Llama 3 70B(80층, KV 헤드 8, 헤드 차원 128)의 KV 캐시를 BF16으로 저장할 때, 8,192 토큰 요청 하나의 KV 크기는?

토큰당 2(K, V) × 80 × 8 × 128 × 2 B = 327,680 B(320 KiB). × 8,192 = 약 2.68 GB. GQA가 없었다면(KV 헤드 64) 8배인 21 GB가 된다.

4. 연속 배칭이 정적 배칭보다 처리량이 좋은 가장 근본적인 이유는?

정적 배칭은 가장 긴 요청이 끝날 때까지 배치를 유지해, 먼저 끝난 요청의 슬롯이 놀고 새 요청은 기다린다. 연속 배칭은 스케줄링 단위가 디코드 스텝이라 빈 슬롯을 즉시 채운다. 디코드 스텝 비용은 배치에 거의 무관하므로 채운 만큼 처리량이 된다.

5. 시간당 $2.5인 GPU 한 장이 총 2,500 tok/s를 생성한다. 100% 가동 시 1M 출력 토큰당 비용은?

시간당 토큰 = 2,500 × 3,600 = 9M. $2.5 ÷ 9M × 1M ≈ $0.28. 가동률이 50%면 두 배가 된다.

6. 70B 파라미터 모델을 15조(1.5×10¹³) 토큰으로 학습하는 연산량은 \(C \approx 6ND\)로 약?

6 × 7×10¹⁰ × 1.5×10¹³ = 6.3×10²⁴ FLOPs. H100 1만 장, MFU 40%(약 4×10¹⁸ FLOP/s)라면 약 1.6×10⁶초 ≈ 18일이다.