Chapter 12

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

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

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

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

시간 → 요청 도착 대기 프리필 (프롬프트 전체) 1번째 디코드: 한 스텝에 한 토큰 TTFT TPOT 종단 지연 = TTFT + (n − 1) × TPOT 프리필은 연산량이 크고 한 번, 디코드는 연산량이 작고 n번 — 병목이 서로 다르다
그림 12-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
B300 / GB300 (Blackwell Ultra, 2025 하반기)288 GB HBM3e약 8 TB/s약 2,500약 5,000≈ 313 FLOP/BNVLink 약 1.8 TB/s
Rubin (Vera Rubin, 2026 하반기 공급)288 GB HBM4약 19~22 TB/s약 4,000약 17,500≈ 208 FLOP/BNVLink 6 약 3 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%만 할당한다. B300 수치는 NVIDIA의 GB300 NVL72 랙 사양(GPU 72개, 희소성 포함 표기)을 GPU 하나·dense로 환산한 값이다. Rubin은 NVIDIA 제품 페이지(2026) 기준 BF16 dense 약 4 PFLOPS, FP8 dense 약 17.5 PFLOPS이고, 대역폭은 초기 발표(2026 초)의 약 22 TB/s와 현재 제품 페이지의 19.2 TB/s가 함께 보여 "약 19~22"로 적었다(시뮬레이터는 19.2 TB/s 사용). 4비트 연산은 별도 칸으로 넣지 않았다: B300은 dense FP4 약 15 PFLOPS, Rubin은 NVFP4 추론 약 50 PFLOPS(NVIDIA 발표치, 희소성 기준 표기)다. FP4로 가중치까지 계산하는 경로(9장의 NVFP4/MXFP4)는 이 장의 단순 모델에 넣지 않았으므로, 4비트 가중치를 골라도 연산은 BF16(또는 FP8) 피크로 계산한다. 이런 불확실성 때문에 이 장의 모든 계산은 "자릿수가 맞는 추정"으로 읽어야 한다.

표에서 바로 읽히는 것세대가 바뀔 때 연산 성능은 대역폭보다 빨리 늘어난다(A100→B200: FLOPS 약 7배, 대역폭 약 4배. Rubin의 FP8은 A100 BF16의 50배가 넘지만 대역폭은 약 10배). 메모리 용량과 대역폭이 상대적으로 귀해진다는 뜻이고, 그래서 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
gpt-oss-120b (MoE, 2025)116.8B (활성 5.1B)36 (풀 18 + 창 128 18)2,8808 × 6436 KiB (+ 요청당 고정 약 4.7 MB)0.31 GB
Qwen3.5-397B-A17B (MoE, 2026)397B (활성 17B)60 (풀 15 + 선형 45)4,0962 × 25630 KiB (+ 요청당 상태 약 192 MB)0.44 GB
DeepSeek-V4-Flash (MoE, 2026)284B (활성 13B)43 (CSA 21 + HCA 20 + 창 2)4,096압축 KV약 3.4 KiB*약 0.03 GB
DeepSeek-V4-Pro (MoE, 2026)1.6T (활성 49B)61 (CSA 30 + HCA 31)7,168압축 KV약 4.9 KiB*약 0.04 GB

아래 네 줄은 구조가 Llama와 다르다(층 구성은 각 모델의 Hugging Face config.json 기준). gpt-oss-120b는 풀 어텐션과 윈도우 128 슬라이딩 어텐션을 한 층씩 번갈아 쓰므로, 길이에 비례해 자라는 KV는 풀 어텐션 18층분뿐이고 슬라이딩 층은 요청당 128토큰어치(약 4.7 MB)로 고정된다. Qwen3.5는 60층 중 45층이 Gated DeltaNet 선형 어텐션이라 KV 대신 고정 크기 상태(층당 64헤드 × 128 × 128)를 갖는다. 상태를 FP32로 저장한다고 가정하면(config의 mamba_ssm_dtype) 요청당 약 192 MB로, 짧은 요청에서는 오히려 KV보다 크지만 컨텍스트가 길어져도 늘지 않는다. 풀 어텐션 15층은 KV 헤드 2개 × 256차원이다. DeepSeek-V4는 KV를 시퀀스 방향으로 4배(CSA) 또는 128배(HCA) 압축하고 대부분을 FP8로 저장하는데, 층마다 구조가 달라 위 식 하나로 쓰기 어렵다. 그래서 *표의 값은 공개된 상대 수치 "8헤드 GQA(BF16) 대비 약 2%"(Hugging Face 블로그, 2026)를 같은 층 수의 8 × 128 GQA에 곱해서 얻었다. 슬라이딩 창 분기와 인덱서 키도 이 근사 안에 들어 있다고 본다. 시뮬레이터도 이 값을 쓰며, V4에서는 KV 정밀도 선택을 무시한다.

MoE의 메모리와 속도는 다른 파라미터로 센다MoE(6장)는 토큰마다 전문가 몇 개만 쓴다. 하지만 어느 토큰이 어느 전문가로 갈지 미리 모르므로 모든 전문가를 메모리에 올려 두어야 한다. 그래서 메모리 예산은 총 파라미터(gpt-oss-120b 116.8B, V4-Pro 1.6T)로 세고, 토큰당 연산은 활성 파라미터(5.1B, 49B)로 센다. 대역폭은 그 중간이다. 배치 1이면 활성 전문가만 읽는다. 배치 \(b\)의 토큰이 각각 \(E\)개 중 \(k\)개를 고르면, 한 스텝에 건드리는 전문가 비율은 무작위 라우팅 가정에서 \(1-(1-k/E)^{b}\)이고 빠르게 1에 다가간다. 큰 배치에서는 결국 거의 모든 전문가 가중치를 매 스텝 읽게 된다. '공개 형식' 버튼은 각 모델이 공개된 정밀도다. gpt-oss는 MoE 가중치가 MXFP4(블록 스케일 포함 약 4.25비트)이고 나머지는 BF16이라 약 65 GB로 80 GB GPU 한 장에 들어간다. V4는 전문가가 FP4(약 4.25비트로 가정)이고 나머지는 FP8이라 Flash 약 154 GB, Pro 약 875 GB다. Llama·Mixtral·Qwen3.5의 공개 형식은 BF16으로 두었다.

셋째는 작업 공간이다. 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장에 들어가는 조합을 찾아보자. ⑤ gpt-oss-120b를 '공개 형식'으로 H100 1장에 올려 보자. 다음으로 DeepSeek-V4-Pro를 공개 형식으로 B300 8장에 올리고 컨텍스트를 128K로 늘려 보자. 압축 KV 덕분에 128K 요청 하나가 약 0.65 GB라 동시 요청 256개를 다 채워도 KV 예산이 한참 남는다(같은 길이에서 Llama 3 70B는 요청당 약 43 GB). ⑥ Qwen3.5는 컨텍스트 512에서도 요청당 약 0.2 GB다(선형 어텐션 상태). 128K로 늘려도 약 4 GB로 완만하게만 는다. 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⁴
그림 12-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개 요청이 훨씬 일찍 끝난다 가로 = 디코드 스텝(시간). 정적 배칭의 점선 칸은 슬롯은 잡혀 있지만 아무 일도 하지 않는 시간이다.
그림 12-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로 복제
그림 12-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\)만큼 전문가 가중치를 읽고, 연산은 활성 파라미터 \(P_\text{act}\)로 센다. 어텐션 연산 \(4Ld\bar c\)는 모델별로 바꿔 쓴다. 풀 어텐션 층만 컨텍스트에 비례하고, 슬라이딩 창 층은 128토큰, V4의 CSA·HCA 층은 압축된 길이(\(c/4\)의 top-k, \(c/128\))만 본다.
  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·EP)
적재 / 배치—
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가 어떻게 되는지 보자. ⑥ gpt-oss-120b를 공개 형식으로 H100 1장에 올리고 동시 사용자를 늘려 보자. 진단 줄의 '전문가 %'가 배치 수십에서 거의 100%가 되며, 가중치 읽기가 활성 5.1B분에서 전체 117B분으로 커진다. ⑦ DeepSeek-V4-Pro를 공개 형식으로 B300 8장 또는 Rubin 8장에 올려 비교해 보자(16장은 NVL72 같은 랙 단위 NVLink 도메인을 가정한 값이다).
설계 순서 요약① 정밀도를 정한다(9장: 대개 FP8 가중치 + FP8/BF16 KV는 품질 손실이 작다). ② 가중치 + 목표 동시 요청의 KV가 들어가는 최소 GPU 묶음을 찾는다. ③ TPOT SLA를 넘지 않는 최대 배치를 찾는다. ④ 그 배치의 처리량으로 단가를 계산하고, 필요한 총 트래픽을 그 처리량으로 나눠 복제본(DP) 수를 정한다. ⑤ 긴 프롬프트가 많으면 TTFT를 위해 프리필 전용 GPU를 따로 두는 분리형(disaggregated) 서빙도 고려한다.

분리 서빙: 프리필과 디코드를 다른 GPU로

지금까지는 한 GPU(또는 TP 묶음)가 프리필과 디코드를 모두 했다. 이렇게 섞어 쓰는 방식을 콜로케이션(colocation)이라 한다. 문제는 두 단계의 성질이 정반대라는 점이다(루프라인 절). 프리필은 계산 바운드이고 한 번에 수천 토큰을 처리하며 수백 ms가 걸린다. 디코드는 대역폭 바운드이고 수십 ms마다 한 토큰씩 꾸준히 나와야 한다. 같은 GPU에서 4K 토큰짜리 프롬프트가 프리필되는 동안, 그 GPU에서 디코딩 중이던 수십 명의 다음 토큰은 함께 멈춘다. 그래서 TPOT의 꼬리(p99)가 튄다. 이것을 프리필–디코드 간섭이라 한다.

해결책은 두 가지다. 하나는 연속 배칭 절에서 말한 chunked prefill이다. 프롬프트를 수백 토큰 조각으로 잘라 디코드 스텝마다 조금씩 끼워 넣으면 멈춤은 사라진다. 대신 모든 디코드 스텝이 조금씩 길어지고, 긴 프롬프트의 TTFT도 늘어난다. 다른 하나는 분리 서빙(prefill/decode disaggregation)이다. GPU를 프리필 풀과 디코드 풀로 아예 나누고, 프리필이 끝난 요청의 KV 캐시를 네트워크로 디코드 GPU에 보낸다(DistServe·Splitwise, 2024). 2026년에는 NVIDIA Dynamo(+NIXL 전송 라이브러리), llm-d, vLLM, SGLang, TensorRT-LLM이 모두 이 구성을 지원한다. DeepSeek도 V3 기술 보고서(2024)부터 프리필과 디코드를 서로 다른 배치·병렬 구성으로 나눠 서빙한다고 밝혔다.

콜로케이션 GPU0 프리필+디코드 GPU1 프리필+디코드 긴 프리필 이 동안 디코드 토큰이 멈춘다 → TPOT 꼬리가 튄다 분리 서빙 프리필 풀 P0 P1 계산 바운드 큰 토큰 배치 FP8/FP4 연산 GPU 디코드 풀 D0 D1 대역폭 바운드 많은 동시 요청 HBM 용량·대역폭 KV 캐시 전송 NVLink / RDMA 라우터 요청 (프롬프트) 출력 토큰 스트림 프리필이 디코드를 멈추지 못하게 물리적으로 떼어 놓는다. 대가는 KV 전송과 풀 비율 조정이다.
그림 12-5. 콜로케이션(왼쪽)과 분리 서빙(오른쪽). 분리 서빙에서는 프리필 GPU가 첫 토큰과 KV 캐시를 만들고, 디코드 GPU가 그 KV를 받아 나머지 토큰을 생성한다. 두 풀은 GPU 종류, 병렬 구성, 배치 크기를 따로 고를 수 있다.

분리 서빙의 새 비용은 KV 전송이다. 프롬프트 \(p\) 토큰의 KV를 링크 대역폭 \(B_\text{link}\)로 보내는 시간은 다음과 같다.

$$t_{\text{KV}} \approx \frac{p \cdot M_{\text{KV/token}}}{B_{\text{link}}}$$
Llama 3 70B(BF16 KV, 320 KiB/토큰)의 4K 프롬프트는 약 1.34 GB다. 400 Gb/s RDMA(약 50 GB/s)로는 약 27 ms, NVLink(약 900 GB/s)로는 약 1.5 ms가 걸린다. 같은 길이에서 DeepSeek-V4-Pro의 압축 KV(약 4.9 KiB/토큰)는 약 20 MB라 RDMA로도 1 ms가 안 걸린다. KV를 작게 만드는 구조는 분리 서빙과도 궁합이 좋다.

평가 지표로는 굿풋(goodput)을 쓴다. 굿풋은 SLA(TTFT 상한과 TPOT 상한)를 둘 다 만족한 요청만 센 처리량이다. 아무리 토큰을 많이 뽑아도 TPOT 목표를 넘긴 요청은 사용자에게는 실패다. 아래 시뮬레이터는 같은 요청열(시드 고정)을 세 방식으로 실제로 스케줄링한다.

SIMULATOR

분리 서빙 vs 콜로케이션: TTFT·TPOT 분포와 굿풋

위 GPU 줄의 경계를 끌어 프리필:디코드 비율 조절 · 아래 그래프의 세로 점선(SLA)을 끌기
방식
요청열
분리콜로케이션Chunked굵은 선 = 선택한 방식
TTFT p50 / p99—
TPOT p50 / p99—
SLA 달성률—
굿풋—
KV 전송 (중앙값 요청)—
—
해볼 것: ① 기본값(70B FP8, H200 8장, λ ≈ 7 요청/s)에서 세 방식의 TPOT 곡선을 비교하자. 콜로케이션(주황)은 오른쪽 꼬리가 길고, 분리(굵은 선)는 좁은 범위에 몰린다. ② 위 GPU 줄의 경계를 끌어 P를 1 → 5로 바꿔 보자. 프리필 GPU가 너무 적으면 대기열이 생겨 TTFT가 폭발한다. 너무 많으면 디코드 GPU당 배치가 커져 TPOT이 는다. '최적 분할 적용'은 모든 P를 돌려 굿풋이 가장 큰 비율을 찾는다. ③ KV 링크를 2 GB/s(구형 이더넷 수준)로 내리면 전송 대기열이 생겨 분리의 TTFT가 나빠진다. NVLink급(900 GB/s)이면 전송은 무시할 만하다. ④ 출력 중앙값을 32로 줄여 보자(분류·짧은 답). 디코드가 거의 없으니 분리의 이점이 사라지고, 오히려 디코드 GPU가 놀아 손해다. ⑤ 프롬프트 중앙값을 8K로 올려 보자(RAG·에이전트처럼 긴 입력). 프리필 풀이 커져야 하고, 콜로케이션의 TPOT 꼬리는 더 길어진다. ⑥ TPOT SLA 점선을 50 ms로 늦추면 콜로케이션도 대부분 통과한다. 분리의 이득은 SLA가 빡빡할 때 크다.
언제 분리할까프롬프트가 길고(RAG, 코딩 에이전트, 긴 시스템 프롬프트) TPOT 목표가 빡빡할수록 분리가 유리하다. 반대로 요청이 적거나 입출력이 짧으면 GPU를 두 풀로 나눈 만큼 각 풀이 덜 차서 손해다. 실제 시스템은 부하에 따라 P:D 비율을 바꾸고(Dynamo의 플래너 등), 프리필 풀에는 연산이 센 GPU를, 디코드 풀에는 HBM이 크고 빠른 GPU를 배치하기도 한다. 이 시뮬레이터는 링크 경합과 KV 메모리 재사용(프리픽스 캐시)을 생략했으므로 수치보다 경향을 보자.

MoE 서빙과 KV 캐시 계층화

2026년 대형 오픈 모델은 대부분 MoE다. gpt-oss-120b(전문가 128개 중 4개), Qwen3.5-397B-A17B(512개 중 10개 + 공유 1개), DeepSeek-V4(Flash 256개·Pro 384개 중 6개 + 공유 1개)처럼 전문가 수는 수백 개이고 활성 비율은 1~5%다. 앞 절들에서 본 대로 메모리는 총 파라미터만큼, 연산은 활성 파라미터만큼 든다. 이 절에서는 MoE를 여러 GPU에서 서빙할 때 생기는 문제와, 에이전트처럼 긴 대화를 반복하는 워크로드에서 KV 캐시를 어디에 두는지를 다룬다.

전문가 병렬과 all-to-all

전문가 병렬(Expert Parallelism, EP)은 전문가들을 GPU에 나눠 둔다. EP = 64면 V4-Pro의 384개 전문가가 GPU마다 6개씩 들어간다. 어텐션은 보통 데이터 병렬(DP)로 각 GPU가 자기 요청을 처리한다. 그 뒤 MoE 층마다 두 번의 all-to-all 통신이 일어난다. 먼저 각 토큰의 은닉 벡터를 라우터가 고른 \(k\)개 전문가가 있는 GPU로 보내고(dispatch), 전문가 출력을 원래 GPU로 모은다(combine). 토큰 하나가 층 하나에서 보내는 양은 대략 다음과 같다.

$$V_{\text{a2a}} \approx 2 \cdot k \cdot d \cdot b_{\text{act}}\quad\text{바이트/토큰/층}$$
\(k\): 활성 전문가 수, \(d\): 은닉 차원, \(b_\text{act}\): 활성값 바이트(FP8 dispatch면 1). V4-Pro(\(k=6,\ d=7168\))는 BF16 기준 층당 약 170 KB, 61층이면 토큰당 약 10 MB다. TP의 all-reduce처럼 지연에 민감하므로 NVLink 도메인(NVL72 랙 등)이나 고속 RDMA와 전용 커널(DeepEP 등)이 필요하다.

큰 배치가 MoE에 특히 중요한 이유는 산술 강도에 있다. 디코드 스텝에서 배치 \(b\)의 토큰이 전문가 \(E\)개에 고르게 흩어지면, 전문가 하나가 받는 토큰은 평균 \(bk/E\)개뿐이다. 전문가 가중치는 한 번 읽어서 그 몇 개 토큰에만 쓰인다. 그래서 dense 모델에서 배치 \(b\)가 하던 역할(산술 강도 \(\approx b\))을 MoE의 전문가 GEMM에서는 \(bk/E\)가 한다(FP8 가중치면 \(I \approx 2bk/E\)). FP8 가중치로 H100의 리지 포인트(FP8 약 590 FLOP/B)에 닿으려면 전문가당 수백 토큰이 필요하다. V4-Pro라면 한 스텝에 \(b \approx 300 \times 384/6 \approx 2\)만 토큰이다. 그래서 대형 MoE의 디코드는 많은 GPU를 하나의 EP 그룹으로 묶어 전역 배치를 키운다. DeepSeek-V3 보고서(2024)는 디코드 최소 배치 단위를 40노드·320 GPU로 적었다. 대신 인기 있는 전문가에 토큰이 몰리면 그 GPU만 바빠지는 부하 불균형이 생긴다. 그래서 인기 전문가를 여러 GPU에 복제하는 중복 전문가(redundant experts)를 둔다.

SIMULATOR

MoE 라우팅: 배치, 전문가 적중, EP 부하

아래 그래프를 좌우로 끌어 배치 b 조절 · 전문가 칸을 누르면 그 전문가가 받은 토큰 수 표시
EP 크기 (GPU 수)
라우팅 표본
건드린 전문가 (표본 / 식)—
스텝당 가중치 읽기 (공개 형식)—
토큰당 읽기—
전문가당 토큰 (평균 / 최대)—
EP 부하 불균형 (최대/평균)—
해볼 것: ① b = 1에서 칸 몇 개만 켜진다(활성 전문가만 읽는다). b를 64 → 1K로 끌면 거의 모든 칸이 켜지고, '토큰당 읽기'는 줄어들다가 dense 모델처럼 \(1/b\)로 떨어진다. 그래프의 실선은 식 \(1-(1-k/E)^b\), 점은 실제로 뽑은 표본이다. ② α(쏠림)를 0으로 하면 라우팅이 고르게 퍼져 EP 불균형이 1에 가깝다. 1.2로 올리면 인기 전문가가 있는 GPU에 토큰이 몰리고, 그 GPU가 스텝 시간을 정한다. 이때 건드린 전문가 비율은 식보다 낮아진다. ③ EP를 8 → 64로 키우면 GPU당 전문가 수가 줄어 GPU당 메모리는 줄지만, 불균형은 커지기 쉽다(GPU 하나에 인기 전문가가 몰릴 확률). ④ '전문가당 토큰 평균'이 수백이 되려면 b가 얼마여야 하는지 보자. 그래야 전문가 GEMM이 계산 바운드에 가까워진다. 라우팅은 교육용 가정이다: 전문가 인기도를 순위\(^{-α}\)로 두고 토큰마다 \(k\)개를 중복 없이 뽑았다. 실제 라우터는 입력에 따라 다르고, 부하 균형 학습(보조 손실 없는 bias 조정 등)으로 쏠림을 줄인다.

에이전트를 위한 KV 캐시: 프리픽스 캐싱과 계층화

11장의 에이전트는 같은 대화를 수십 번 이어 간다. 매 턴의 입력은 [시스템 프롬프트 + 도구 정의 + 지금까지의 대화와 도구 결과 + 새 메시지]다. 앞부분 수만 토큰은 지난 턴과 똑같다. 캐시가 없으면 매 턴 이 전체를 다시 프리필해야 해서 턴 수의 제곱으로 비용이 쌓인다. 프리픽스 캐싱(prefix caching)은 같은 접두사의 KV 블록을 해시로 찾아 재사용한다(vLLM의 automatic prefix caching, SGLang의 RadixAttention). 그러면 새로 붙은 토큰만 프리필하면 된다. 상용 API의 "프롬프트 캐싱" 할인도 같은 원리다.

문제는 HBM이 작다는 것이다. 수천 개의 대화 세션 KV를 모두 HBM에 둘 수는 없다. 그래서 KV를 계층화(KV tiering/offloading)한다. 지금 디코딩 중인 요청의 KV는 HBM에 두고, 잠시 쉬는 세션(도구 실행을 기다리는 에이전트 등)의 KV는 CPU DRAM으로, 더 오래된 것은 로컬 NVMe SSD나 원격 저장소로 내린다(LMCache, Dynamo의 KV 블록 매니저 등). 다시 쓸 때 불러오는 시간 \(c \cdot M_\text{KV}/B_\text{tier}\)이 재계산(프리필) 시간보다 짧으면 이득이다.

GPU HBM ~100~300 GB · 수~20 TB/s CPU DRAM ~1~2 TB · PCIe 약 50 GB/s NVMe SSD / 원격 수~수십 TB · 약 5~20 GB/s ← 빠르고 작다 · · · 느리고 크다 → 턴 t 공통 접두사 (시스템 + 도구 + 이전 대화) 새 입력 턴 t+1 캐시 적중 → 재사용 (프리필 생략) 새 입력 ← 이것만 프리필 대역폭 수치는 대략의 자릿수다(구성에 따라 다름). Grace 같은 CPU는 NVLink-C2C로 DRAM을 훨씬 빠르게 붙인다.
그림 12-6. KV 캐시 계층과 에이전트의 프리픽스 재사용. 턴이 이어질수록 공통 접두사가 길어지므로, 그 KV를 어딘가에 보관했다가 다시 쓰면 매 턴의 프리필이 새 입력 몇 백~몇 천 토큰으로 줄어든다.
SIMULATOR

에이전트 턴별 TTFT: 재계산 vs 캐시 불러오기

그래프를 좌우로 끌어 턴 선택
DRAM 링크
캐시 없음 (전체 재계산)SSD에서 불러오기DRAM에서 불러오기HBM 적중
선택 턴의 컨텍스트—
TTFT (없음 / DRAM / HBM)—
T턴 누적 프리필 GPU 시간—
세션 하나의 KV—
해볼 것: ① 기본값(70B FP8, 접두사 8K, 턴당 2K, 30턴)에서 '캐시 없음' 곡선이 턴마다 계속 오르는 것을 보자. 마지막 턴(컨텍스트 약 66K)의 프리필은 10초를 넘고, 30턴 누적 프리필 GPU 시간은 HBM 적중의 열 배가 넘는다. T를 60턴으로 늘리면 그 배수가 더 커진다(제곱으로 쌓이므로). ② DRAM에서 불러오기는 KV 바이트 ÷ 링크 대역폭이다. 70B의 64K 컨텍스트(약 21 GB)는 PCIe로 0.4초 남짓이라 재계산보다 훨씬 싸다. ③ 모델을 DeepSeek-V4-Flash로 바꾸면 압축 KV(토큰당 약 3.4 KiB) 덕분에 SSD에서 불러와도 거의 공짜다. KV가 작은 구조는 계층화와도 궁합이 좋다. ④ Δ를 크게(16K: 큰 파일을 통째로 읽는 도구 결과) 하면 새 토큰 프리필이 지배해 캐시의 상대 이득이 줄어든다. 불러오기와 계산을 층 단위로 겹치면(파이프라이닝) 실제 지연은 더 줄어든다. 여기서는 겹치지 않는다고 가정했고, SSD는 약 10 GB/s로 두었다.
에이전트 서빙 체크리스트① 시스템 프롬프트·도구 정의처럼 고정된 부분을 맨 앞에 두어 접두사를 최대한 공유한다. 앞쪽 내용(예: 타임스탬프)이 바뀌면 그 뒤 캐시가 전부 무효가 된다. ② 같은 세션의 요청은 같은 GPU(또는 그 KV를 가진 노드)로 보내는 캐시 인지 라우팅을 쓴다. ③ 도구 실행을 기다리는 동안 KV를 DRAM으로 내렸다가 돌아오면 올린다. ④ 추론 모델(11장)은 출력의 상당 부분이 생각 토큰이다. 디코드 비중과 KV 길이가 모두 늘어나므로, 앞의 분리 서빙과 KV 압축 구조의 가치가 더 커진다.

학습 쪽 숫자: \(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 수천 장에 쪼갠다.

MoE, 저정밀도, 새 옵티마이저

MoE 학습에서는 식의 \(N\)에 활성 파라미터를 넣는다: \(C \approx 6 N_\text{act} D\). 토큰마다 활성 전문가만 순전파·역전파하기 때문이다. DeepSeek-V3(671B 총/37B 활성, 14.8조 토큰)는 \(6 \times 37\times10^9 \times 14.8\times10^{12} \approx 3.3\times10^{24}\) FLOPs로 405B dense의 약 1/11이다. 보고된 학습량은 H800 약 279만 GPU·시간(2,048장, FP8 혼합 정밀도)이었다(DeepSeek, 2024). Kimi K2(1T 총/32B 활성, 15.5조 토큰)도 같은 자릿수다. 반면 메모리 쪽 상태(가중치·기울기·옵티마이저)는 총 파라미터에 비례한다. 671B × 16바이트 ≈ 10.7 TB이므로 MoE도 상태를 수천 장에 나눠야 한다. 서빙과 똑같은 "메모리는 총, 연산은 활성" 구조다.

2025~26년에 \(C/(\text{GPU 수}\cdot\pi\cdot\text{MFU})\)의 각 항을 줄이는 기법도 실전에 들어왔다. ① 더 낮은 정밀도: DeepSeek-V3의 FP8 학습에 이어, NVIDIA는 12B 모델을 10조 토큰 동안 NVFP4로 사전학습해 FP8에 가까운 손실을 보고했다(arXiv 2509.25149, 2025). 4비트 텐서 코어의 \(\pi\)를 학습에도 쓰려는 방향이다. ② Muon 옵티마이저: 2D 가중치의 모멘텀 업데이트를 Newton–Schulz 반복으로 직교화해 적용한다. Kimi는 같은 손실에 AdamW 대비 약 2배의 계산 효율을 보고했다('Muon is Scalable', 2025). K2는 이를 개량한 MuonClip으로 손실 스파이크 없이 학습했다. 같은 \(C\)로 더 낮은 손실을 얻는 셈이고, 모멘트를 하나만 들고 있어 옵티마이저 상태 메모리도 Adam보다 작다. ③ RL 사후학습: 추론 모델(11장)의 GRPO류 강화학습은 프롬프트마다 응답 여러 개를 생성(롤아웃)한 뒤 학습한다. 그래서 비용의 상당 부분이 이 장의 디코드 처리량 문제가 되고, 학습 클러스터 안에서도 서빙 엔진(vLLM, SGLang 등)이 롤아웃을 맡는다.

SIMULATOR

학습 연산량·기간 추정

점을 좌우로 끌어 토큰 수 변경
학습 정밀도 (피크 FLOPS)
프리셋
연산량 C—
학습 기간—
GPU·시간—
토큰/파라미터—
해볼 것: ① 기본값(405B, 15.6T 토큰, H100 16K장, MFU 40%)에서 기간이 약 두 달로 나오는지 확인하자 — 실제 보고(장애·재시작 포함 54일 학습 구간 등)와 자릿수가 맞는다. ② 점을 끌어 토큰을 줄이면 연산량 축에서 GPT-3(약 3.1×10²³) 근처로 내려간다. ③ 토큰/파라미터 비율이 20(Chinchilla 최적) 근처인지, Llama 3처럼 수백~수천으로 '과학습'하는지 비교해 보자. 작은 모델을 오래 학습하면 학습비는 더 들지만 서빙(이 장의 주제)이 싸진다. GPU 시간당 가격을 곱하면 학습 비용도 추정할 수 있다. ④ 'DeepSeek-V3' 프리셋(N = 활성 37B, 14.8T 토큰, H100급 2,048장, FP8, MFU 20%)은 약 47일이 나온다. 보고된 사전학습 약 266만 GPU·시간(약 54일)과 자릿수가 맞는다(H800은 연산 성능이 H100과 같고 NVLink만 느리다). FP8 피크 기준 MFU가 낮게 나오는 것은 6ND가 어텐션·MTP를 빼고 센 근사이기 때문이다. ⑤ GPU를 B300 또는 Rubin으로 바꿔 같은 학습이 며칠로 줄어드는지 보자.

핵심 정리

  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\)(MoE는 \(N\) = 활성 파라미터), 기간은 \(C/(\text{GPU 수} \cdot \pi \cdot \text{MFU})\).
  8. MoE는 메모리를 총 파라미터로, 토큰당 연산을 활성 파라미터로 센다. 한 스텝에 읽는 전문가 비율 \(1-(1-k/E)^b\)는 배치와 함께 1로 다가간다. 전문가당 토큰 \(bk/E\)가 산술 강도를 정하므로 대형 MoE는 큰 EP 그룹과 큰 전역 배치로 서빙하고, MoE 층마다 dispatch/combine all-to-all이 필요하다.
  9. 분리 서빙은 프리필 풀과 디코드 풀을 나눠 프리필–디코드 간섭을 없앤다. 대가는 KV 전송 \(p\,M_\text{KV}/B_\text{link}\)와 P:D 비율 조정이며, 평가는 SLA를 모두 지킨 요청만 세는 굿풋으로 한다. 에이전트처럼 접두사를 반복하는 워크로드에서는 프리픽스 캐싱과 KV 계층화(HBM → DRAM → SSD)가 프리필을 새 토큰분으로 줄인다.

확인 퀴즈

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일이다.

7. gpt-oss-120b(총 116.8B, 활성 5.1B, MoE 가중치 MXFP4·나머지 BF16)를 배치 1로 디코딩한다. GPU에 올려야 하는 가중치와 토큰 하나에 읽는 가중치에 가장 가까운 것은?

어느 전문가가 선택될지 모르므로 전문가 128개를 모두 올려야 한다. 전문가 약 115B × 4.25비트 ≈ 61 GB에 나머지 약 2B × 2 B ≈ 4 GB를 더해 약 65 GB다. 배치 1에서는 층마다 4개 전문가와 비전문가 부분만 읽는다. 약 1.6B × 2 B + 3.6B × 0.53 B ≈ 5 GB다. 배치가 커지면 건드리는 전문가가 늘어 읽는 양이 65 GB 쪽으로 다가간다.

8. 분리 서빙에서 Llama 3 70B의 8K 토큰 프롬프트 KV(BF16, 약 2.7 GB)를 400 Gb/s RDMA(약 50 GB/s)로 디코드 GPU에 보내는 시간과, 그 영향으로 옳은 것은?

2.7 GB ÷ 50 GB/s ≈ 54 ms다. 8K 프롬프트의 프리필(수백 ms~1 s)에 비하면 작지만 TTFT에 그대로 더해진다. 같은 프리필 GPU의 전송이 줄을 서면 대기도 생긴다. NVLink(약 900 GB/s)라면 약 3 ms다. KV가 작은 모델(GQA, MLA, V4의 압축 KV)일수록 전송 부담이 작다.