LLM 서빙 설계 플레이그라운드
모델을 학습시키는 것은 한 번이지만 서빙은 매일, 매초 일어난다. "Llama 3 70B를 하루 수백만 명에게 보여 주려면 GPU가 몇 장 필요하고, 응답은 얼마나 빠르며, 토큰 백만 개에 얼마가 드는가?" 이 질문에 답하는 데 필요한 것은 의외로 몇 개의 숫자뿐이다 — 메모리 용량, 메모리 대역폭, 연산 성능, 그리고 모델의 크기와 KV 캐시. 이 장에서는 그 숫자들을 손으로 계산하고, 시뮬레이터에서 직접 조립해 보면서 서빙 시스템을 설계하는 감각을 기른다.
- TTFT·TPOT·처리량의 의미와 서로 부딪히는 이유를 설명할 수 있다.
- 가중치·KV 캐시·작업 공간으로 GPU 메모리 예산을 세우고, 동시에 처리할 수 있는 요청 수를 계산한다.
- 루프라인 모델로 디코딩이 왜 메모리 대역폭 바운드이고 프리필은 왜 계산 바운드인지 산술 강도로 설명한다.
- 배치 크기에 따른 처리량–지연 트레이드오프와 연속 배칭(continuous batching)의 이점을 시뮬레이션으로 확인한다.
- 텐서·파이프라인·데이터·전문가 병렬의 역할과 NVLink, 파이프라인 버블을 이해한다.
- $/1M 토큰 비용과 학습 연산량 \(C \approx 6ND\)를 직접 추정할 수 있다.
서빙의 요구: 지연과 처리량
8장에서 본 것처럼 LLM 응답은 두 단계로 만들어진다. 프롬프트 전체를 한 번에 통과시켜 KV 캐시를 채우는 프리필(prefill)과, 토큰을 하나씩 뽑아내는 디코드(decode)다. 사용자가 느끼는 속도는 이 두 단계에 각각 대응하는 두 숫자로 요약된다.
- TTFT(Time To First Token): 요청을 보낸 뒤 첫 토큰이 보일 때까지의 시간. 대기열에서 기다린 시간 + 프리필 시간이다. 채팅에서는 보통 수백 ms~1~2초 안을 목표로 한다.
- TPOT(Time Per Output Token, ITL이라고도 한다): 이어지는 토큰 사이의 간격. 사람이 읽는 속도(초당 5~10 단어)보다 빠르면 충분하다는 관점에서 보통 20~50 ms(사용자당 20~50 tok/s)를 목표로 잡는다.
- 처리량(throughput): GPU 한 대(또는 클러스터)가 초당 만들어 내는 전체 토큰 수. 사업자 입장의 숫자이며, 곧바로 비용이 된다.
지연과 처리량은 서로 부딪힌다. 요청 여러 개를 묶어(배치) 한 번에 처리하면 GPU가 바쁘게 일해 처리량이 오르지만, 한 스텝이 길어져 각 사용자의 TPOT은 늘어난다. 그래서 서빙 설계는 보통 SLA(Service Level Agreement) 또는 SLO 형태로 "TTFT p95 ≤ 1 s, TPOT p95 ≤ 50 ms"처럼 지연 상한을 먼저 정하고, 그 안에서 처리량(= 비용)을 최대화하는 문제로 정리된다. 이 장의 시뮬레이터는 모두 이 한 문장을 다른 각도에서 들여다본다.
하드웨어 숫자: GPU 표
서빙 설계에 필요한 GPU 숫자는 세 개다. 메모리 용량(가중치와 KV 캐시가 들어가는가), 메모리 대역폭(디코드 한 스텝에 가중치를 얼마나 빨리 읽는가), 연산 성능(프리필과 큰 배치를 얼마나 빨리 계산하는가). 여기에 여러 GPU를 묶을 때의 GPU 간 대역폭(NVLink)이 더해진다. 아래 표는 이 장의 시뮬레이터가 쓰는 값이다.
| GPU | 메모리 | 대역폭 | BF16 dense | FP8 dense | 리지 포인트 (BF16) | GPU 간 연결 |
|---|---|---|---|---|---|---|
| A100 SXM 80GB | 80 GB HBM2e | 약 2.0 TB/s | 312 TFLOPS | — (미지원) | ≈ 156 FLOP/B | NVLink 600 GB/s |
| H100 SXM | 80 GB HBM3 | 약 3.35 TB/s | 약 989 TFLOPS | 약 1,979 | ≈ 295 FLOP/B | NVLink 900 GB/s |
| H200 | 141 GB HBM3e | 약 4.8 TB/s | 약 989 | 약 1,979 | ≈ 206 FLOP/B | NVLink 900 GB/s |
| B200 (2024~25 발표치) | 약 180~192 GB HBM3e | 약 8 TB/s | 약 2,250 | 약 4,500 | ≈ 281 FLOP/B | NVLink 약 1.8 TB/s |
| RTX 4090 | 24 GB GDDR6X | 약 1.0 TB/s | 약 165 (FP16 텐서) | 약 330 | ≈ 165 FLOP/B | PCIe 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%만 할당한다. 이런 불확실성 때문에 이 장의 모든 계산은 "자릿수가 맞는 추정"으로 읽어야 한다.
메모리 예산: 가중치 + KV 캐시 + 작업 공간
서빙 GPU 메모리는 세 덩어리로 나뉜다. 첫째는 가중치로, 파라미터 수 × 파라미터당 바이트다. Llama 3 70B(약 70.6B 파라미터)를 BF16(2바이트)으로 올리면 141 GB, FP8이면 71 GB, 4비트(그룹 스케일 포함 약 4.125비트)면 약 36 GB다.
둘째는 KV 캐시다. 8장에서 본 대로 각 층은 지나간 모든 토큰의 키와 값을 저장한다. GQA(Grouped-Query Attention)로 KV 헤드가 \(n_{kv}\)개라면 토큰 하나당 크기는 다음과 같다.
| 모델 | 파라미터 | 층 \(L\) | \(d_\text{model}\) | KV 헤드 × \(d_\text{head}\) | KV/토큰 (BF16) | 8K 컨텍스트 1요청 |
|---|---|---|---|---|---|---|
| Llama 3 8B | 8.03B | 32 | 4,096 | 8 × 128 | 128 KiB | 1.07 GB |
| Llama 3 70B | 70.6B | 80 | 8,192 | 8 × 128 | 320 KiB | 2.68 GB |
| Llama 3.1 405B | 405.9B | 126 | 16,384 | 8 × 128 | 504 KiB | 4.23 GB |
| Mixtral 8x7B (MoE) | 46.7B (활성 약 12.9B) | 32 | 4,096 | 8 × 128 | 128 KiB | 1.07 GB |
셋째는 작업 공간이다. CUDA 컨텍스트, cuBLAS·어텐션 커널의 임시 버퍼, 프리필 중간 활성값, 메모리 단편화를 합쳐 GPU당 수 GB가 든다. vLLM 같은 서빙 엔진은 기본으로 전체 메모리의 90%만 쓰고(gpu_memory_utilization=0.9), 그 안에서 가중치와 작업 공간을 뺀 나머지를 전부 KV 캐시 블록으로 미리 잡아 둔다(PagedAttention). 결국 동시에 처리할 수 있는 요청 수는 다음처럼 정해진다.
H100 한 장(80 GB)에 Llama 3 8B BF16을 올리면 가중치 16 GB, 작업 공간 2 GB를 빼고 약 54 GB가 KV 캐시 몫이다. 컨텍스트 8K 요청은 요청당 1.07 GB이므로 약 50개를 동시에 처리할 수 있다. 같은 GPU에 70B를 BF16으로는 아예 올릴 수 없다. 아래 시뮬레이터에서 막대 위쪽을 끌어 올리면 동시 요청 수가 늘어나며 KV 캐시가 차오른다.
GPU 메모리 적재
루프라인: 메모리 한계인가, 연산 한계인가
메모리에 올렸다면 다음 질문은 "얼마나 빠른가"다. 커널 하나의 실행 시간은 두 가지 중 느린 쪽으로 정해진다. 필요한 바이트를 메모리에서 읽어 오는 시간 \(Q/\beta\)와, 필요한 연산을 수행하는 시간 \(W/\pi\)다(\(\beta\): 대역폭, \(\pi\): 최대 FLOPS). 둘을 가르는 기준은 산술 강도(arithmetic intensity) — 읽어 온 바이트 하나당 몇 번 연산하는가 — 이다.
디코드와 프리필의 산술 강도
디코드 한 스텝을 보자. 파라미터 \(P\)개짜리 모델에서 토큰 하나는 대략 \(2P\) FLOPs(곱셈 + 덧셈)를 쓴다(6장). 배치에 토큰이 \(b\)개 있으면 연산은 \(2Pb\)지만, 가중치는 한 번만 읽어서 \(b\)개 토큰이 같이 쓴다. BF16이면 읽는 바이트는 \(2P\)다.
배치 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로 배치와 무관하게 고정된다. 컨텍스트가 길수록 디코드는 배치를 키워도 계산 바운드로 넘어가지 못한다.
루프라인 플레이그라운드
배치: 처리량과 지연의 트레이드오프
루프라인을 시간으로 바꿔 쓰면 디코드 한 스텝(= TPOT)의 길이를 바로 계산할 수 있다. 배치 \(b\), 평균 컨텍스트 \(c\)일 때
배치가 작을 때는 가중치 읽기가 지배하므로 \(t_\text{step}\)이 거의 일정하다 — 배치를 2배로 늘려도 TPOT은 그대로이고 총 처리량만 2배가 된다. 사실상 공짜 점심이다. 배치가 커지면 두 가지 일이 생긴다. KV 캐시 읽기 \(b\,c\,M_\text{KV}\)가 가중치 읽기를 넘어서며 \(t_\text{step}\)이 \(b\)에 비례해 늘기 시작하고, 결국 연산 시간 항이 지붕에 닿는다. 그 지점부터는 배치를 키워도 총 처리량이 더 이상 늘지 않고 사용자당 속도만 떨어진다. 그리고 그 전에 메모리 용량(최대 동시 요청)이 한계를 긋는 경우가 많다.
배치 크기 스윕
연속 배칭: 빈자리를 즉시 채우기
배치가 처리량의 열쇠라면, 실제 서비스에서 문제는 요청이 제각각의 시각에 도착하고 출력 길이도 제각각이라는 점이다. 전통적인 정적 배칭(static batching)은 요청 몇 개를 묶어 시작한 뒤 가장 긴 요청이 끝날 때까지 배치를 유지한다. 10 토큰만에 끝난 요청의 자리는 500 토큰짜리 요청이 끝날 때까지 비어 있고, 그 사이 도착한 요청은 대기열에서 기다린다.
연속 배칭(continuous batching, iteration-level scheduling)은 스케줄링 단위를 "요청"에서 "디코드 한 스텝"으로 바꾼다(Orca, 2022). 매 스텝마다 끝난 요청을 빼고 대기 중인 요청을 빈 슬롯에 넣는다. 디코드 스텝 비용은 배치 크기에 크게 의존하지 않으므로(앞 절), 슬롯을 늘 채워 두는 것이 곧 처리량이다. vLLM, TensorRT-LLM, SGLang 등 현대 서빙 엔진은 모두 이 방식이며, 새 요청의 프리필을 디코드 스텝에 잘게 끼워 넣는 chunked prefill도 함께 쓴다.
아래 시뮬레이터는 실제로 요청을 생성해(포아송 도착, 시드 고정) 두 스케줄러를 같은 입력으로 돌린다. 시간 단위는 디코드 한 스텝이고, 한 스텝의 비용은 배치 크기와 무관하다고 가정했다(메모리 바운드 영역). 프리필 시간은 생략했다.
스케줄러 타임라인: 정적 vs 연속 배칭
병렬화: 한 GPU를 넘어서
모델이 GPU 하나에 안 들어가거나, 하나로는 지연 목표를 맞출 수 없으면 여러 GPU로 나눈다. 나누는 방법은 크게 네 가지다.
텐서 병렬(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 방식에서 버블 비율은 다음과 같다.
파이프라인 버블
데이터 병렬은 모델 전체를 복제해 요청을 나눠 받는다. 서빙에서는 가장 단순하고 확장성이 좋은 방법이다 — 모델이 한 묶음(예: H100 2장)에 들어가면, 그 묶음을 여러 벌 띄우고 로드 밸런서로 나누면 된다. 전문가 병렬은 MoE(6장)의 전문가들을 GPU마다 나눠 두고 라우터가 고른 전문가 쪽으로 토큰을 all-to-all로 보낸다. 전문가 수가 많은 대형 MoE(예: 수백 개 전문가)에서 필수적이다.
비용: $/1M 토큰
서빙 비용은 결국 "GPU 시간을 몇 개의 토큰으로 나눠 갖느냐"다. GPU \(N\)장을 시간당 \(c_\text{GPU}\) 달러에 빌렸고 총 처리량이 \(T\) tok/s라면
예를 들어 시간당 $2.5짜리 H100 한 장이 Llama 3 8B를 배치 1로 약 200 tok/s 낸다면 약 $3.5/1M 토큰이지만, 배치를 키워 2,500 tok/s를 내면 약 $0.28/1M 토큰으로 열 배 이상 싸진다. 같은 하드웨어, 같은 모델인데 단가를 정하는 것은 배치(= 처리량)다. 여기서 GPU 시간당 가격은 클라우드·약정·시기에 따라 크게 다르므로 아래 슬라이더의 값은 모두 사용자가 넣는 예시값이다. 또한 이 식은 출력 토큰만 셌다. 실제 API는 입력(프리필) 토큰을 출력보다 훨씬 싸게 받는데, 프리필은 계산 바운드라 토큰당 GPU 시간이 디코드보다 훨씬 적기 때문이다.
비용 계산기
종합 서빙 플레이그라운드
이제 지금까지의 식을 모두 묶어 하나의 서빙 구성을 평가해 보자. 아래 시뮬레이터가 쓰는 모델은 다음과 같다(모두 GPU 하나당 값으로 계산한 뒤 텐서 병렬 \(N\)을 반영한다). 교육용으로 단순화한 모델이며, 실제 엔진은 커널 효율, 스케줄링, 프리필 끼워 넣기, 투기적 디코딩 등으로 수십 % 이상 다를 수 있다.
- 적재: 가중치 \(M_w/N\) + KV + 작업 공간 2 GB가 GPU 메모리의 90% 안에 들어가야 한다. 실제 배치 \(b = \min(U, B_{\max})\). \(U > B_{\max}\)면 나머지는 대기열로 간다.
- 디코드 스텝: \(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\)만큼 전문가 가중치를 읽는다.
- 프리필: \(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})\)을 더한다.
- 처리량·비용: 총 \(b/\text{TPOT}\) tok/s(출력 기준), 사용자당 \(1/\text{TPOT}\), 단가는 앞 절의 식.
종합 서빙 플레이그라운드
학습 쪽 숫자: \(C \approx 6ND\)
서빙과 같은 방식으로 학습 규모도 추정할 수 있다. 파라미터 \(N\)개 모델을 토큰 \(D\)개로 학습하는 데 드는 연산량은 대략
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 수천 장에 쪼갠다.
학습 연산량·기간 추정
핵심 정리
- 서빙 품질은 TTFT(대기 + 프리필), TPOT(디코드 한 스텝), 처리량(비용)으로 요약되며, 지연 SLA 아래에서 처리량을 최대화하는 문제로 정리된다.
- GPU 메모리 = 가중치(파라미터 × 바이트) + KV 캐시(\(2Ln_{kv}d_\text{head}b_{kv}\) × 토큰 × 요청) + 작업 공간. KV 예산이 최대 동시 요청 수를 정한다.
- 디코드의 산술 강도는 BF16 기준 약 \(b\) FLOP/B로 리지 포인트(H100 약 295)보다 훨씬 작아 메모리 대역폭 바운드다. 배치 1의 상한은 \(\beta / M_w\) tok/s. 프리필은 \(I \approx p\)로 계산 바운드다.
- 배치를 키우면 TPOT은 거의 그대로인 채 처리량이 늘다가, KV 읽기와 연산이 지배하면서 꺾인다. KV 읽기는 배치로 공유되지 않아 긴 컨텍스트일수록 일찍 꺾인다.
- 연속 배칭은 매 디코드 스텝마다 빈 슬롯을 채워, 출력 길이가 제각각인 실제 트래픽에서 슬롯 활용률과 대기 시간을 크게 개선한다.
- TP는 가중치·KV·대역폭을 N등분/N배 하지만 층마다 all-reduce가 필요해 NVLink가 필수이고, PP는 통신이 적은 대신 버블 \((p-1)/(m+p-1)\)이 생기며, DP는 복제로 처리량을 늘린다.
- $/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로 디코딩할 때, 이론적 최대 속도에 가장 가까운 것은?
2. KV 캐시를 무시하면 BF16 디코딩의 산술 강도는 배치 \(b\)에 대해 약 \(b\) FLOP/B다. H100에서 디코딩이 계산 바운드가 되기 시작하는 배치는 대략?
3. Llama 3 70B(80층, KV 헤드 8, 헤드 차원 128)의 KV 캐시를 BF16으로 저장할 때, 8,192 토큰 요청 하나의 KV 크기는?
4. 연속 배칭이 정적 배칭보다 처리량이 좋은 가장 근본적인 이유는?
5. 시간당 $2.5인 GPU 한 장이 총 2,500 tok/s를 생성한다. 100% 가동 시 1M 출력 토큰당 비용은?
6. 70B 파라미터 모델을 15조(1.5×10¹³) 토큰으로 학습하는 연산량은 \(C \approx 6ND\)로 약?