테마 선택

📚 지식 노트

AI·하드웨어·주변기기·게임·컴퓨터 시스템·이론 컴퓨터 과학·수학·개발과 운영·보안·응용 분야에 대해 알아 두면 좋은 내용을 정리한 노트다. 위에서 분야를 고르고 아래에서 노트를 고른다.

AI 인프라

큰 모델을 학습하고 서빙하는 데 GPU가 얼마나, 왜 필요한지를 계산량·메모리·통신·비용의 숫자로 어림해 보고, 병렬화와 혼합 정밀도, 연속 배칭과 PagedAttention 같은 기법이 어느 병목을 푸는지 정리한다.

목차 (13)
  1. 학습에 드는 계산량: 6·N·D
  2. GPU 메모리: 학습과 추론
  3. 병렬화: 데이터, 텐서, 파이프라인, ZeRO/FSDP
  4. 혼합 정밀도: FP32, BF16, FP8
  5. GPU를 잇는 길: NVLink와 InfiniBand
  6. GPU 말고: TPU와 AI 가속기
  7. 서빙 1: 프리필과 디코드
  8. 서빙 2: 연속 배칭과 PagedAttention
  9. 서빙 3: 처리량과 지연
  10. 비용 어림하기
  11. 기기 안 추론
  12. 흔한 오해
  13. 핵심 논문

GPU라는 부품 자체는 GPU 노트에, 이 노트에서 쓰는 KV 캐시·양자화·추측 디코딩의 원리는 LLM 노트에 있다. 제품 사양 숫자는 계산의 기준점으로 쓰려고 NVIDIA H100의 공식 사양표에서 가져왔고(2026년 10월 기준), 더 새 세대 GPU는 숫자가 더 크지만 계산 방법은 같다.

학습에 드는 계산량: 6·N·D

트랜스포머 학습의 계산량은 파라미터 수 N과 학습 토큰 수 D만으로 꽤 정확히 어림된다. 토큰 하나가 순전파에서 지나갈 때 가중치마다 곱셈·덧셈을 한 번씩 하므로 약 2N FLOPs, 역전파는 입력 쪽 기울기와 가중치 기울기를 둘 다 구하느라 그 두 배인 4N이다. 합치면 토큰당 6N, 전체는 C ≈ 6·N·D다(Kaplan 등 2020). 어텐션 계산은 컨텍스트가 아주 길지 않으면 작은 몫이라 뺀다.

학습 계산량 막대: 70B 모델을 1.4조 토큰으로 학습하면 순전파 1.96 × 10²³, 역전파 3.92 × 10²³, 합계 5.88 × 10²³ FLOPs다학습 계산량 ≈ 6 × N(파라미터) × D(토큰) — 70B × 1.4T순전파 2·N·D토큰마다 곱셈·덧셈 N번씩1.96 × 10²³역전파 4·N·D입력 쪽 기울기와 가중치 기울기를 둘 다 계산3.92 × 10²³합계 5.88 × 10²³ FLOPs
70B 모델을 1.4조 토큰으로 학습하는 계산량(Chinchilla와 같은 크기). 역전파가 전체의 3분의 2다.

이 5.88 × 10²³ FLOPs를 H100으로 돌려 보자. 사양표의 BF16 성능 1,979 TFLOPS는 희소성을 쓴 값이라 일반(밀집) 행렬 곱은 그 절반인 약 990 TFLOPS다. 실제 학습은 통신·메모리 대기 때문에 최고 성능을 다 쓰지 못한다. 실제로 쓴 비율을 MFU(model FLOPs utilization, PaLM 논문이 제안한 지표)라 하고, 여기서는 40%로 가정한다.

  • GPU 시간: 5.88 × 10²³ ÷ (990 × 10¹² × 0.4) ≈ 412,666 GPU·시간
  • GPU 1,024장이면 약 16.8일. 실제로는 장애로 멈췄다 다시 시작하는 시간, 실험과 실패한 학습이 더해진다.

GPU 수천 장을 몇 주씩 돌리면 장애는 예외가 아니라 일상이다. Llama 3 논문은 최대 H100 1만 6천 장으로 405B 모델을 사전 학습한 54일 동안 학습이 466번 멈췄고, 그중 419번이 예정에 없던 중단이었다고 밝혔다. 예상 못 한 중단의 58.7%가 GPU 문제였고, GPU의 HBM3 메모리만 17.2%를 차지했다. 그런데도 실제 학습에 쓴 시간은 90%를 넘었다. 그래서 큰 학습은 모델 상태를 주기적으로 저장하는 체크포인트, 고장 난 노드를 빼고 바로 이어 가는 자동 재시작, 느려진 GPU를 찾아내는 감시를 기본으로 갖춘다.

GPU 메모리: 학습과 추론

학습할 때 GPU 메모리에는 가중치만 있는 것이 아니다. 혼합 정밀도와 Adam 옵티마이저를 쓰는 흔한 설정에서 파라미터 하나가 차지하는 양은 다음과 같다(ZeRO 논문의 셈).

  • 가중치 BF16 2바이트, 기울기 BF16 2바이트
  • 옵티마이저 상태 12바이트: 정밀한 갱신을 위한 FP32 가중치 사본, Adam의 모멘텀과 분산(각 FP32 4바이트)
  • 합계 파라미터당 16바이트. 7B 모델이면 112.0GB로, 80GB GPU 한 장에 모델 상태조차 들어가지 않는다.

여기에 역전파에 쓰려고 순전파 중간값을 저장하는 활성값이 더해진다. 층마다 s·b·h·(34 + 5·a·s/h) 바이트로 어림한다(s 시퀀스 길이, b 배치, h 은닉 크기, a 헤드 수, Korthikanti 등 2022). 7B 모양(층 32, h 4096, 헤드 32)에 시퀀스 4096·배치 1이면 104.2GB인데, 뒤의 5·a·s/h 항이 어텐션 점수 행렬이다. FlashAttention처럼 점수 행렬을 메모리에 쓰지 않고 칩 안에서 타일로 계산하는 커널을 쓰면 18.3GB로 준다. 남은 활성값은 일부를 버렸다가 역전파 때 다시 계산하는 활성값 재계산(체크포인팅)으로 더 줄인다.

7B 모델 메모리: 학습은 가중치·기울기 각 14GB, Adam 상태 84GB, 활성값 18GB로 합계 130GB이고, 추론은 가중치 14GB와 4096토큰 KV 캐시 2.1GB다. 80GB GPU 한 장에 학습은 들어가지 않는다GPU 한 장 80GB학습가중치기울기Adam 상태(FP32 사본·모멘텀·분산)활성값130GB추론가중치16GB학습: BF16 가중치·기울기 각 2바이트 + FP32 사본·모멘텀·분산 12바이트 = 파라미터당 16바이트활성값: 시퀀스 4096·배치 1, 점수 행렬을 저장하지 않는 커널 기준. 추론 KV: 4096토큰.
7B 모델의 메모리. 학습은 옵티마이저 상태가 가장 크고 합계가 80GB를 훌쩍 넘는다. 추론은 가중치와 KV 캐시만 있으면 된다.

추론은 훨씬 가볍다. BF16 가중치 14.0GB와 요청마다의 KV 캐시면 된다. 그래서 학습은 GPU 여러 장에 나눠야 하는 문제가 되고, 추론은 KV 캐시를 얼마나 잘 관리해 요청을 많이 받느냐의 문제가 된다.

병렬화: 데이터, 텐서, 파이프라인, ZeRO/FSDP

모델과 데이터가 GPU 한 장에 들어가지 않거나 너무 오래 걸리면 여러 장에 나눈다. 무엇을 나누느냐에 따라 네 가지가 있고, 큰 학습은 이들을 겹쳐 쓴다.

병렬화 네 가지를 GPU 두 장으로 그린 도해: 데이터 병렬은 모델을 통째로 복제하고, 텐서 병렬은 층 안의 행렬을 반씩 나누며, 파이프라인 병렬은 층을 나눠 맡고, ZeRO-3와 FSDP는 모델 상태를 반씩 나눠 두었다가 쓸 때 모은다데이터 병렬GPU 0데이터 AGPU 1데이터 B모델을 통째로 복제, 기울기를 all-reduce로 평균텐서 병렬 (Megatron-LM)GPU 0모든 층의 왼쪽 반GPU 1모든 층의 오른쪽 반층마다 행렬을 반씩 나눠 계산, 층마다 통신파이프라인 병렬GPU 0층 1–2GPU 1층 3–4층을 나눠 맡고 활성값을 다음 GPU로 넘김ZeRO-3 / FSDPGPU 0데이터 A · 상태 절반GPU 1데이터 B · 상태 절반상태를 1/N씩 나눠 두고, 층을 쓸 때만 모음
GPU 두 장으로 그린 네 가지 병렬화. 색칠한 막대가 그 GPU가 들고 있는 층(또는 층의 일부)이다. ZeRO-3·FSDP의 점선은 평소엔 남이 들고 있다가 쓸 때만 모아 오는 부분이다.
  • 데이터 병렬: 모델을 GPU마다 통째로 복제하고 배치를 나눈다. 각자 기울기를 구한 뒤 all-reduce로 평균해 모두 같은 갱신을 한다. 가장 단순하지만 GPU마다 모델 상태 전체가 들어가야 한다.
  • 텐서 병렬(Megatron-LM): 층 안의 큰 행렬을 열·행으로 나눠 GPU들이 한 행렬 곱을 함께 한다. 층마다 결과를 합치는 통신이 있어 아주 빠른 연결이 필요하므로 보통 한 서버(노드) 안에서만 쓴다.
  • 파이프라인 병렬(GPipe): 층을 구간으로 나눠 GPU마다 맡긴다. 주고받는 것이 구간 경계의 활성값뿐이라 노드 사이에도 쓸 수 있지만, 앞 GPU가 일을 넘겨줄 때까지 뒤 GPU가 노는 거품이 생긴다.
  • ZeRO / FSDP(ZeRO, PyTorch FSDP): 데이터 병렬인데, GPU마다 똑같이 들고 있던 상태를 1/N씩 나눠 가진다. 층을 계산할 차례가 되면 그 층의 가중치만 all-gather로 모아 쓰고 버린다. 통신은 늘지만 메모리가 GPU 수에 반비례해 준다.
파이프라인 일정: GPU 4장이 마이크로배치 6개를 한 칸씩 늦게 받아 처리하고, 앞뒤의 빈칸(거품)이 시간의 33%다GPU 0123456GPU 1123456GPU 2123456GPU 3123456시간 → (칸 안 숫자는 마이크로배치 번호, 빈칸은 일이 없는 거품)거품 = (p − 1) / (m + p − 1) = 3 / 9 ≈ 33%
파이프라인 병렬의 순전파 일정(GPipe). GPU 4장이 마이크로배치를 한 칸씩 늦게 받는다. 처음과 끝의 빈칸이 거품이고, 마이크로배치를 늘릴수록 비율이 준다.

거품 비율은 (p − 1) / (m + p − 1)이다(p 단계 수, m 마이크로배치 수). 4단계에 마이크로배치 6개면 33%, 32개면 8.6%다. 그래서 배치를 잘게 많이 쪼개고, 순전파와 역전파를 엇갈려 끼워 넣는 일정(1F1B 등)으로 거품과 활성값 메모리를 함께 줄인다.

ZeRO 단계별 GPU 한 장의 메모리(7B, GPU 8장): 나누지 않음 112GB, 1단계: 옵티마이저 38.5GB, 2단계: +기울기 26.25GB, 3단계: +가중치 14GB가중치기울기옵티마이저 상태나누지 않음112GB1단계: 옵티마이저38.5GB2단계: +기울기26.25GB3단계: +가중치14GB파라미터당 16바이트(BF16 가중치·기울기 + FP32 Adam 상태). 활성값은 따로 든다.
7B 모델을 GPU 8장에서 데이터 병렬로 학습할 때 GPU 한 장의 모델 상태 메모리. ZeRO는 옵티마이저 상태, 기울기, 가중치 순으로 나눠 가진다.

GPU 8장이면 GPU 한 장의 모델 상태가 112.0GB → 38.5GB → 26.3GB → 14.0GB로 준다. 실제 큰 학습은 노드 안에서 텐서 병렬, 노드 사이에서 파이프라인 병렬, 그 바깥을 데이터 병렬(또는 FSDP)로 겹쳐 쓴다(Narayanan 등 2021). 통신이 많은 병렬일수록 빠른 연결 안쪽에 두는 것이 원칙이다.

혼합 정밀도: FP32, BF16, FP8

숫자 형식의 비트 수를 줄이면 메모리와 통신이 줄고, GPU의 텐서 코어도 더 빨리 계산한다. 부동소수점 수는 부호·지수·가수로 이루어지는데, 지수 비트가 표현 범위를, 가수 비트가 정밀도를 정한다.

부동소수점 형식 비교: FP32는 지수 8비트·가수 23비트로 최댓값 3.4 × 10³⁸, BF16는 지수 8비트·가수 7비트로 최댓값 3.4 × 10³⁸, FP16는 지수 5비트·가수 10비트로 최댓값 65,504, FP8 E4M3는 지수 4비트·가수 3비트로 최댓값 448, FP8 E5M2는 지수 5비트·가수 2비트로 최댓값 57,344. 지수 비트가 범위를, 가수 비트가 정밀도를 정한다부호지수(범위)가수(정밀도)최댓값 · 1 다음 간격FP323.4 × 10³⁸간격 1.2 × 10⁻⁷BF163.4 × 10³⁸간격 7.8 × 10⁻³FP1665,504간격 9.8 × 10⁻⁴FP8 E4M3448간격 0.125FP8 E5M257,344간격 0.25
형식별 비트 배치와 최댓값, 1 다음에 올 수 있는 수까지의 간격(정밀도). BF16은 FP32와 지수가 같아 범위가 같고 정밀도만 낮다. FP16은 정밀도가 더 높은 대신 범위가 좁다.
  • FP16 + 손실 배율: Micikevicius 등(2017)은 계산은 FP16으로 하고 FP32 가중치 사본을 두어 갱신했다. FP16은 최댓값이 65,504로 범위가 좁아 작은 기울기가 0으로 사라지므로, 손실에 큰 수를 곱해 계산한 뒤 나누는 손실 배율이 필요하다.
  • BF16: 지수 8비트로 FP32와 범위가 같아 손실 배율 없이 쓰기 쉽다. 오늘날 LLM 학습의 기본 형식이다.
  • FP8(FP8 Formats 논문): 순전파의 가중치·활성값은 정밀도가 중요해 E4M3(최댓값 448), 역전파의 기울기는 범위가 중요해 E5M2(최댓값 57,344)를 쓰자고 제안했다. 범위가 아주 좁아 텐서마다 배율을 두고 계속 조정해야 한다. H100부터 FP8 텐서 코어가 들어갔다.

GPU를 잇는 길: NVLink와 InfiniBand

병렬화는 GPU끼리 데이터를 주고받아야 성립하므로, 연결의 속도가 곧 어떤 병렬을 어디에 쓸지를 정한다. 연결은 두 층이다.

GPU 연결 도해: 노드마다 GPU 8장이 NVLink 스위치로 서로 빠르게 연결되고, 두 노드는 각 GPU의 네트워크 카드를 거쳐 InfiniBand나 이더넷 스위치로 연결된다노드 1 (서버 한 대)NVLink 스위치GPUGPUGPUGPUGPUGPUGPUGPU노드 2 (서버 한 대)NVLink 스위치GPUGPUGPUGPUGPUGPUGPUGPUInfiniBand · 이더넷 스위치노드 안: NVLink, 넓고 빠름노드 사이: 네트워크, 한 자릿수 이상 좁음
노드 안의 GPU 8장은 NVLink 스위치로 모두 빠르게 이어지고, 노드 사이는 GPU마다 붙은 네트워크 카드와 InfiniBand·이더넷 스위치로 잇는다.
  • 노드 안: NVLink는 GPU끼리 직접 잇는 연결이다. H100 SXM은 GPU당 900GB/s(양방향 합)다. 8장짜리 서버 안에서는 NVLink 스위치로 모든 GPU가 이 속도로 서로 통한다.
  • 노드 사이: GPU마다 네트워크 카드를 하나씩 붙여 InfiniBand나 RDMA 이더넷으로 잇는다. ConnectX-7 카드가 400Gb/s, 곧 50GB/s다. NVLink보다 한 자릿수 이상 좁다.

7B 모델의 BF16 기울기 14.0GB를 GPU 8장이 링 all-reduce로 평균하면 GPU마다 크기의 2(n−1)/n배인 24.5GB를 보낸다. NVLink(한 방향 450GB/s)로는 약 54ms, 50GB/s 네트워크로는 약 490ms다. 실제로는 역전파가 끝난 층의 기울기부터 미리 보내 계산과 통신을 겹친다. 그래도 층마다 통신하는 텐서 병렬은 노드 안에, 덜 자주 통신하는 데이터·파이프라인 병렬은 노드 사이에 두는 이유가 이 차이다.

GPU 말고: TPU와 AI 가속기

AI 계산의 대부분은 큰 행렬 곱이라, 그것만 잘하도록 만든 전용 칩도 쓰인다. 대표가 Google의 TPU다. Cloud TPU 안내에 따르면 TPU는 머신러닝용 주문형 반도체(ASIC)로, 핵심인 행렬 장치(MXU)가 128×128 시스톨릭 배열이고 칩 옆에 HBM을 둔다. 여러 칩을 묶은 슬라이스로 키우는 점은 GPU 클러스터와 같다.

  • 컴파일러가 핵심: 모델의 행렬 계산을 XLA 컴파일러가 TPU 명령으로 바꾼다. 행렬 크기가 MXU를 다 채우지 못하면 0으로 채워 계산을 버리므로, 배치나 특징 차원 하나를 128의 배수로 맞추라고 권한다.
  • 맞는 일, 안 맞는 일: 행렬 곱 위주로 오래 도는 큰 학습, 큰 배치에 맞고, 분기가 잦거나 텐서 모양이 계속 바뀌거나 사용자 정의 연산이 많은 모델에는 덜 맞는다. 그런 모델과 빠른 실험에는 GPU가 유연하다.
  • 클라우드 회사들의 자체 학습·추론 칩, 휴대폰·노트북의 NPU(아래 기기 안 추론 절)도 같은 발상이다. 칩마다 지원하는 연산과 소프트웨어가 달라, 하드웨어를 고를 때는 성능표만큼 프레임워크 지원을 함께 본다.

서빙 1: 프리필과 디코드

LLM 요청 하나의 처리는 성격이 전혀 다른 두 단계로 나뉜다. 이 차이를 알아야 서빙 성능 숫자를 읽을 수 있다.

프리필과 디코드 단계 그림: 프롬프트 6토큰을 한 번의 프리필 실행으로 처리해 첫 토큰을 내고, 이어지는 디코드 실행마다 출력 토큰이 하나씩 붙는다프롬프트출력모델 실행서울의가을날씨를한줄로써줘서울의가을날씨를한줄로써줘맑고색칠한 토큰: K·V가 캐시에 들어 있음프리필6토큰 한꺼번에한계: 계산 성능 (행렬 × 행렬)서울의가을날씨를한줄로써줘맑고선선하며색칠한 토큰: K·V가 캐시에 들어 있음프리필6토큰 한꺼번에디코드 11토큰한계: 메모리 대역폭 (가중치를 읽는 시간 > 계산 시간)서울의가을날씨를한줄로써줘맑고선선하며하늘이색칠한 토큰: K·V가 캐시에 들어 있음프리필6토큰 한꺼번에디코드 11토큰디코드 21토큰한계: 메모리 대역폭 (가중치를 읽는 시간 > 계산 시간)서울의가을날씨를한줄로써줘맑고선선하며하늘이높다색칠한 토큰: K·V가 캐시에 들어 있음프리필6토큰 한꺼번에디코드 11토큰디코드 21토큰디코드 31토큰한계: 메모리 대역폭 (가중치를 읽는 시간 > 계산 시간)
1 / 5

요청이 도착했다. 프롬프트는 6토큰이다.

프리필과 디코드. 프리필은 프롬프트 전체를 한 번에 계산해 GPU의 계산 성능을 꽉 쓰고, 디코드는 실행마다 토큰 하나만 만들어 메모리 대역폭에 묶인다. 첫 토큰까지의 시간(TTFT)은 프리필이, 그 뒤 토큰 사이 간격은 디코드가 정한다.
  • 프리필: 프롬프트 토큰을 한꺼번에 넣어 KV 캐시를 채우고 첫 토큰을 낸다. 토큰 수만큼 큰 행렬 곱이라 계산 성능이 한계다. 7B 모델에서 프롬프트 2,000토큰은 2 × 7B × 2,000 FLOPs로, H100에서 성능의 절반을 쓴다고 보면 약 57ms다.
  • 디코드: 실행마다 토큰 하나를 만든다. 계산은 적은데 가중치 14.0GB를 매번 다 읽어야 하므로 메모리 대역폭이 한계다. 3.35TB/s로 나누면 배치 1에서 한 단계에 약 4.3ms, 초당 235토큰이 상한이다.
  • 그래서 프롬프트 2,000토큰의 프리필은 출력 약 13토큰을 디코드하는 시간과 비슷하다. 첫 토큰까지의 시간(TTFT)은 프롬프트 길이에, 토큰 사이 간격(TPOT)은 디코드 성능에 달린다.

두 단계가 한 GPU에서 섞이면 긴 프리필이 들어올 때마다 다른 요청의 디코드가 멈칫한다. 긴 프리필을 잘게 나눠 디코드 사이에 끼우거나, DistServe처럼 프리필과 디코드를 아예 다른 GPU에 나눠 돌리는 방법이 쓰인다.

서빙 2: 연속 배칭과 PagedAttention

디코드는 가중치를 읽는 시간이 대부분이라, 한 번 읽을 때 요청 여럿의 토큰을 함께 계산하면(배칭) 거의 공짜로 처리량이 는다. 문제는 요청마다 답 길이가 다르다는 것이다.

배칭 비교: 요청 12개를 칸 4개로 처리할 때 정적 배칭은 27단계(칸 사용률 48%), 연속 배칭은 15단계(사용률 87%)가 걸린다정적 배칭27단계 · 칸 사용률 48%칸 1칸 2칸 3칸 4123456789101112연속 배칭15단계 · 칸 사용률 87%칸 1칸 2칸 3칸 4123456789101112숫자는 요청 번호, 막대 길이는 그 요청의 출력 토큰 수다. 회색은 노는 칸이다.
같은 요청 12개를 칸 4개로 처리한다. 정적 배칭은 묶음 안의 가장 긴 요청이 끝날 때까지 다른 칸이 논다. 연속 배칭은 요청이 끝난 칸에 다음 요청을 바로 넣는다.
  • 연속 배칭(Orca, 2022): 요청 단위가 아니라 디코드 한 단계마다 배치를 다시 짠다. 끝난 요청은 바로 빼고 기다리던 요청을 바로 넣는다. 오늘날 LLM 서빙 엔진의 기본이다.
  • KV 캐시가 발목을 잡는다: 배치를 키우려면 요청마다 KV 캐시 자리가 있어야 한다. 예전 방식은 요청마다 최대 길이만큼 연속된 메모리를 미리 잡았다. 최대 2,048토큰 자리를 잡았는데 실제 답이 300토큰이면 85%가 빈자리로 버려진다.
  • PagedAttention(vLLM, 2023): 운영체제의 가상 메모리 페이지처럼 KV 캐시를 작은 블록으로 나누고, 요청마다 블록 표로 흩어진 블록을 잇는다. 필요할 때 한 블록씩 받으므로 낭비는 마지막 블록의 빈칸뿐이다. 블록 16토큰이면 같은 예에서 1.3%다. 같은 프롬프트로 시작하는 요청끼리 블록을 함께 쓸 수도 있다.
PagedAttention 도해: 요청 A의 KV 캐시 10토큰은 논리 블록 3개로, 요청 B의 7토큰은 2개로 나뉘고, 블록 표가 이를 메모리 곳곳의 물리 블록 5·1·6번과 2·7번에 연결한다. 비는 칸은 마지막 블록에만 생긴다논리 블록(요청마다 이어진 순서)블록 표GPU 메모리의 물리 블록A012A: 0→5, 1→1, 2→6B01B: 0→2, 1→701 (A1)2 (B0)345 (A0)6 (A2)7 (B1)블록 하나 = 4토큰. 요청이 길어지면 빈 물리 블록 하나를 더 받는다.낭비는 요청마다 마지막 블록의 빈칸뿐이다. 최대 길이만큼 미리 잡아 두지 않는다.
PagedAttention. 요청의 KV 캐시는 논리적으로 이어져 있지만, 실제로는 메모리 곳곳의 블록에 흩어져 있고 블록 표가 둘을 잇는다.

vLLM 논문은 이렇게 아낀 메모리로 배치를 키워, 같은 지연에서 처리량을 2~4배 올렸다고 보고했다. 직접 서빙해 보려면 vLLM 문서가 출발점으로 좋다.

서빙 3: 처리량과 지연

배치를 키우면 GPU 전체가 초당 만드는 토큰(처리량)은 늘지만, 한 단계에 읽을 KV 캐시도 늘어 사용자 한 명이 보는 속도(지연)는 나빠진다. 서빙은 이 둘 사이에서 고르는 일이다.

배치 크기 1·2·4·8·16·32·64에서 전체 처리량은 235·461·889·1660·2930·4745·6876토큰/초로 늘고, 사용자 한 명의 속도는 235·230·222·207·183·148·107토큰/초로 준다전체 처리량 (토큰/초)23514612889416608293016474532687664사용자 한 명의 속도 (토큰/초)2351230222242078183161483210764가로축: 함께 처리하는 요청 수(배치)
7B 모델(BF16, KV 헤드 8개, 요청당 2,048토큰)을 H100 한 장에서 디코드할 때, 가중치와 KV 캐시를 읽는 시간만으로 어림한 값이다. 실제 엔진은 이 상한보다 느리다.
  • 배치 1에서 64로 키우면 전체 처리량은 초당 235에서 6,876토큰으로 약 29배가 되고, 사용자 한 명의 속도는 235에서 107토큰으로 준다.
  • 배치 64에서도 계산 시간(2 × 7B × 64 FLOPs)은 메모리 읽기 시간보다 짧아 여전히 대역폭이 한계다. 배치를 더 키우면 언젠가 계산이 한계가 되고, 그 전에 KV 캐시가 메모리를 다 채운다.
  • 서비스는 보통 ‘토큰 사이 간격 몇 ms 이하’ 같은 지연 목표를 먼저 정하고, 그 목표를 지키는 가장 큰 배치로 돌린다.

비용 어림하기

출력 토큰 100만 개당 비용은 GPU 시간당 가격 ÷ (초당 토큰 수 × 3,600) × 100만이다. GPU 가격은 계약·지역·시점마다 크게 달라 여기서는 시간당 3달러로 가정한다. 처리량은 앞 절의 어림값을 쓴다.

출력 토큰 100만 개당 비용: GPU 한 장 시간당 3달러를 가정하면 배치 1에서 3.55달러, 8에서 0.50달러, 32에서 0.18달러, 64에서 0.12달러다배치 1235 토큰/초$3.55배치 81,660 토큰/초$0.50배치 324,745 토큰/초$0.18배치 646,876 토큰/초$0.12가정: GPU 한 장 시간당 $3, 늘 배치가 꽉 찬 상태, 출력 토큰만 셈(프롬프트 처리 비용 제외).
같은 GPU라도 배치를 얼마나 채우느냐에 따라 토큰당 비용이 수십 배 달라진다. 가격과 처리량은 모두 가정·어림값이다.
  • 배치 1이면 100만 토큰에 약 3.55달러, 배치 64면 약 0.12달러다. 요청이 늘 몰려 있어야 싼값이 나온다. 밤에 요청이 적으면 GPU를 놀려도 값은 나간다.
  • 빠진 것: 프롬프트 처리(입력 토큰), 요청이 고르지 않은 시간대, 장애 대비 여유 GPU, 엔진의 실제 효율. 실제 비용은 이 어림보다 높게 잡는다.
  • 반대로 양자화(LLM 노트), MoE, 추측 디코딩, 같은 프롬프트 앞부분의 KV 캐시 재사용은 같은 GPU로 더 많은 토큰을 만들어 비용을 낮춘다.

기기 안 추론

노트북·휴대폰에서 모델을 직접 돌리면 네트워크가 없어도 되고, 데이터가 기기 밖으로 나가지 않으며, 서버 비용이 들지 않는다. 대신 메모리와 대역폭이 작고, 전력과 발열을 아껴야 한다.

  • 크기: 기기에서는 보통 1~8B 모델을 4비트로 양자화해 쓴다. 8B를 4비트로 줄이면 약 4.1GB로, 기기 메모리를 다른 앱과 나눠 써야 하는 점을 생각하면 휴대폰에서는 이 정도가 한계에 가깝다.
  • 속도: 서버와 마찬가지로 디코드는 메모리 대역폭이 한계다. 토큰마다 가중치를 한 번 다 읽는다고 보면 상한은 대역폭 ÷ 모델 크기다.
  • 도구: 개인 컴퓨터에서 공개 모델을 돌릴 때 많이 쓰는 llama.cpp는 의존성 없는 C/C++ 추론 엔진으로, 1.5비트부터 8비트까지의 정수 양자화와 CPU·Apple Metal·CUDA·Vulkan 등 여러 장치를 지원한다. 모델은 이 엔진용 GGUF 파일로 바꿔 쓴다.
  • 가속기: 휴대폰·노트북 칩의 NPU는 적은 전력으로 행렬 곱을 하도록 만든 장치다. 모델을 그 칩이 지원하는 형식과 연산으로 바꿔야 해서, 같은 모델이라도 기기용으로 다시 변환·최적화하는 과정이 따른다.
기기 안 추론 속도 상한: 메모리 대역폭 60GB/s를 가정하면 1B(0.5GB)는 초당 약 116토큰, 3B(1.5GB)는 초당 약 39토큰, 8B(4.1GB)는 초당 약 15토큰이다4비트 모델(크기)초당 토큰 상한 = 대역폭 ÷ 모델 크기1B (0.5GB)1163B (1.5GB)398B (4.1GB)15가정: 메모리 대역폭 60GB/s, 토큰마다 가중치를 한 번 다 읽음. 실제 속도는 이보다 낮다.
기기 메모리 대역폭을 60GB/s로 가정했을 때 4비트 모델의 디코드 속도 상한. 모델이 작을수록 빠르지만 그만큼 능력도 줄어, 쉬운 일은 기기에서, 어려운 일은 서버에서 나눠 처리하는 구성이 흔하다.

흔한 오해

  • "TFLOPS가 높은 GPU일수록 답이 빨리 나온다" — 토큰을 하나씩 만드는 디코드는 계산보다 메모리 대역폭이 한계다(서빙 1 절). 사양표의 FLOPS는 희소성을 쓴 값인 경우도 있어 밀집 행렬 곱 성능은 그 절반일 수 있다.
  • "가중치가 VRAM에 들어가면 돌릴 수 있다" — 요청마다 KV 캐시가 더 들고, 긴 컨텍스트나 큰 배치에서는 가중치보다 커진다.
  • "GPU를 두 배로 늘리면 학습이 두 배 빨라진다" — 통신, 파이프라인 거품, 장애 복구 시간 때문에 늘 그보다 덜 빨라진다. 그래서 MFU 같은 실제 활용률을 따로 잰다.
  • "AI 비용은 대부분 학습에서 든다" — 학습은 한 번이지만 추론은 모델이 쓰이는 내내 든다. 많이 쓰이는 모델일수록 추론 비용의 몫이 커져, 작은 모델에 데이터를 더 쓰고(LLM 노트의 스케일링 절) 양자화·배칭으로 서빙을 아끼는 이유가 된다.

핵심 논문

요지는 직접 정리한 한 줄이다. 더 자세한 요약은 논문 노트에 모은다.

논문연도한 줄 요지
Mixed Precision Training (Micikevicius 등)2017FP16으로 계산하되 FP32 가중치 사본과 손실 배율(loss scaling)을 두어 정확도를 잃지 않고 메모리·시간을 줄였다.
GPipe (Huang 등)2018층을 여러 장치에 나누고 배치를 마이크로배치로 쪼개 흘려 보내는 파이프라인 병렬을 정리했다.
Megatron-LM (Shoeybi 등)2019어텐션과 MLP의 행렬을 GPU들에 나누는 텐서 병렬로, 코드 몇 줄의 통신만 더해 수십억 파라미터 모델을 학습했다.
ZeRO (Rajbhandari 등)2019데이터 병렬의 GPU마다 똑같이 들고 있던 옵티마이저 상태·기울기·가중치를 나눠 가져 메모리 중복을 없앴다.
Efficient Large-Scale LM Training on GPU Clusters (Narayanan 등)2021텐서(노드 안)·파이프라인(노드 사이)·데이터 병렬을 겹쳐 쓰는 3차원 병렬과 그 배치 원칙을 정리했다.
FlashAttention (Dao 등)2022어텐션을 작은 타일로 나눠 GPU 칩 안 메모리에서 끝내, 큰 점수 행렬을 HBM에 쓰지 않고 정확한 어텐션을 빠르게 계산했다.
Reducing Activation Recomputation (Korthikanti 등)2022트랜스포머 활성값 메모리를 식으로 정리하고, 시퀀스 병렬과 선택적 재계산으로 재계산 비용을 거의 없앴다.
PaLM (Chowdhery 등)20226천여 개 TPU로 540B 모델을 학습하며, 하드웨어를 얼마나 쓰는지 재는 지표 MFU를 제안했다.
FP8 Formats for Deep Learning (Micikevicius 등)2022가중치·활성값용 E4M3과 기울기용 E5M2 두 FP8 형식을 제안하고 큰 모델 학습에서 16비트와 비슷한 결과를 보였다.
Orca (Yu 등, OSDI)2022요청 단위가 아니라 반복(토큰) 단위로 배치를 다시 짜는 스케줄링, 곧 연속 배칭을 처음 제안했다.
PyTorch FSDP (Zhao 등)2023ZeRO-3식 완전 분할 데이터 병렬을 PyTorch에 넣으며 겪은 설계와 성능 문제를 정리했다.
PagedAttention / vLLM (Kwon 등)2023KV 캐시를 운영체제의 페이지처럼 블록으로 관리해 낭비를 거의 없애고, 같은 지연에서 처리량을 2~4배 올렸다.
DistServe (Zhong 등)2024성격이 다른 프리필과 디코드를 다른 GPU에 나눠 돌려 서로 방해하지 않게 하고, 지연 목표를 지키는 처리량을 올렸다.