서론: model.generate()의 휴면 상태인 Tensor Core
소프트웨어 엔지니어가 머신러닝 모델링에서 고성능 추론 시스템으로 처음 전환할 때, PyTorch와 Hugging Face에서 교과서적인 진입점은 속임수처럼 간단해 보입니다:
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
...
깔끔하고 관용적이지만, Nsight Systems (nsys) 또는 PyTorch Profiler를 사용하여 프로덕션 하드웨어에서 이 실행 루프를 프로파일링하면 놀라운 역설을 발견하게 됩니다:
[NVIDIA H100 SXM5 (80GB) GPU 프로파일링 스냅샷]
--------------------------------------------------------------------------------
Phase 1: 프롬프트 처리 (Prefill) -> 지속 시간: ~12.4 ms | Tensor Core: 82.4%
...
엄청나게 빠른 80GB의 HBM3 메모리는 완전히 점유되고, 700와트 서버 섀시는 최대 부하로 웅웅거립니다. 그럼에도 불구하고 GPU의 가장 비싼 물리적 자산인 989 TFLOPS의 BF16 Tensor Core 컴퓨팅은 전체 생성 시간의 95% 이상 동안 배고프게 놀고 있습니다.
이러한 컴퓨팅 붕괴는 왜 발생하는 것일까요? 가장 강력한 엔터프라이즈 가속기조차도 단일 클라이언트 스트림에 대해 초당 수십 개의 토큰을 출력하는 데 어려움을 겪는 이유는 무엇일까요?
《Production LLM Inference Engines: Evolution, Architecture, and Future》 시리즈의 첫 번째 장으로서, 이 글은 트랜스포머 추론을 지배하는 근본적인 하드웨어 한계들, 즉 Roofline Model, **산술 강도(Arithmetic Intensity)**의 물리적 원리, 그리고 Prefill과 Decode 간의 화해할 수 없는 분리를 분석합니다.
1. 자기회귀 생성의 두 가지 물리적 상태
디코더 전용 트랜스포머의 현저한 컴퓨팅 분기는 그 **자기회귀적(autoregressive), 인과 의존성(causal dependency)**에서 비롯됩니다.
모든 요청은 물리적으로 두 개의 뚜렷한 실행 영역으로 나뉩니다:
flowchart TD
subgraph Prefill["Phase 1: Prefill (Prompt Processing)"]
P1["Input Prompt: [T1, T2, T3, ... Tn] (Length L_prompt)"] --> P2["Ingest all tokens simultaneously into Transformer layers"]
...
1. Prefill 단계 (프롬프트 수집)
- 계산 특성: 입력 프롬프트는 $L_{ ext{prompt}}$개의 토큰이 동시에 사용 가능합니다. 모든 토큰 표현이 미리 알려져 있기 때문에, 어텐션 점수와 선형 투영은 밀도 높고 구조화된 행렬-행렬 곱셈(GEMM)으로 평가될 수 있습니다.
- 데이터 흐름: 모델 가중치 $W
otin ext{R}^{d_{ ext{in}} imes d_{ ext{out}}}$는 HBM에서 한 번 읽어와 프롬프트 내의 수천 개의 병렬 토큰에 걸쳐 재사용됩니다.
- 하드웨어 병목: 계산 제한(Compute-bound). 작업 부하가 GPU Tensor Core를 포화시키며, 총 실행 시간은 칩의 산술 처리량(TFLOPS)에 비례하여 확장됩니다.
2. 디코드 단계 (자기회귀 생성)
- 계산 특성: 모델은 순방향 패스당 엄격하게 하나의 토큰을 생성합니다. 토큰 $t$가 토큰 $t-1$에 조건적으로 의존하기 때문에($P(x_t ext{ | } x_{<t})$), 시간 축을 따라 병렬화할 수 없습니다.
- 데이터 흐름: 단일 토큰 표현만 투영하기 위해, GPU는 매 단계마다 고대역폭 메모리(HBM)에서 온칩 SRAM으로 수십 기가바이트의 모델 가중치를 스트리밍해야 합니다. 이 연산은 GEMM에서 메모리 부족 현상이 발생하는 벡터-행렬 곱셈(GEMV)으로 퇴화됩니다.
- 하드웨어 병목: 메모리 대역폭 제한(Memory-bandwidth bound). 계산 실행 장치는 대부분의 클럭 사이클 동안, 가중치와 역사적 KV 캐시가 메모리 버스를 통과하기를 기다리며 정지 상태에 머무릅니다.
2. 최신 가속기를 위한 Roofline 모델 도출
⚠️ [TIP]
대화형 계산기: 모델 규모(7B, 70B, 671B), 정밀도 형식(FP16, FP8, INT4) 및 대상 배치 동시성(batch concurrency) 전반에 걸친 메모리 사용량과 대역폭 포화를 추정하려면, 저희의 대화형 LLM VRAM & Throughput Calculator를 탐색해 보세요.
컴퓨터 아키텍처에서 Roofline Model은 특정 하드웨어 플랫폼에서 알고리즘의 이론적 성능 범위(performance envelope)를 정의합니다:
xychart-beta
title "Roofline Model: Compute-Bound vs Memory-Bound Regimes"
x-axis "Arithmetic Intensity (FLOPs / Byte)" [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048]
...
수학적 공식은 다음과 같이 정의됩니다:
$$\text{Attainable Performance (P)} = \min\left(P ext{\textunderscore peak}, ; I \times B ext{\textunderscore mem}\right)$$
여기서:
- $P ext{\textunderscore peak}$: 이론적 최대 컴퓨팅 성능 ($ ext{FLOPs/s}$).
- $B ext{\textunderscore mem}$: 물리적 메모리 대역폭 ($ ext{Bytes/s}$).
- $I$: 산술 강도 (Arithmetic Intensity) (또는 연산 강도, operational intensity)로, 장치 메모리에서 전송된 바이트당 실행되는 부동 소수점 연산 횟수를 나타냅니다 ($ ext{FLOPs/Byte}$): $$I = \frac{\text{Total Operations (FLOPs)}}{\text{Total Memory Traffic (Bytes)}}$$
리지 포인트(The Ridge Point): 하드웨어 분기선
메모리 제한 경사면과 컴퓨팅 제한 천장이 교차하는 지점을 **리지 포인트 ($I ext{\textunderscore ridge}$)**라고 합니다:
$$I ext{\textunderscore ridge} = \frac{P ext{\textunderscore peak}}{B ext{\textunderscore mem}}$$
- 알고리즘의 산술 강도가 $I \ge I ext{\textunderscore ridge}$를 만족하면, 이는 평평한 컴퓨팅 제한 (compute-bound) 영역에서 작동합니다. 하드웨어의 컴퓨팅 코어를 완전히 활용할 수 있습니다.
- 만약 $I < I ext{\textunderscore ridge}$라면, 작업 부하는 기울어진 메모리 대역폭 제한 (memory-bandwidth bound) 영역에 갇히게 됩니다. 이 경우 최대 TFLOPS와 관계없이 달성 가능한 처리량은 메모리 버스 속도에 의해 제한됩니다.
아래 표는 BF16/FP16 밀집 텐서 연산을 사용하는 최신 엔터프라이즈 가속기들의 공식 물리적 사양 및 리지 포인트(ridge point)를 정리한 것입니다:
| Accelerator | Architecture | Memory Capacity & Type | Bandwidth ($B_{\text{mem}}$) | Dense Peak Compute ($P_{\text{peak}}$, BF16) | Ridge Point ($I_{\text{ridge}}$) |
|---|
| NVIDIA A100 | Ampere (SXM4) | 80 GB HBM2e | 2,039 GB/s (~2.04 TB/s) | 312.0 TFLOPS | ~153.0 FLOPs/Byte |
| ... | | | | | |
[!IMPORTANT]
주요 아키텍처 시사점: 컴퓨팅 밀도($P_{\text{peak}}$)는 연속적인 GPU 아키텍처를 거치면서 메모리 대역폭($B_{\text{mem}}$, 1.6x에서 3.9x로 증가)보다 훨씬 빠르게(3.1배에서 7.2배) 성장했습니다. 따라서 리지 포인트 $I_{\text{ridge}}$는 오른쪽으로 더 많이 이동했습니다 (153에서 거의 300 FLOPs/Byte까지). 가속기들은 메모리 정체(memory stall)에 훨씬 민감해졌으며, 막대한 연산 강도 없이는 최고급 실리콘이 대부분 활용되지 못합니다.
3. 수학적 유도: 왜 디코드(Decode)가 하드웨어를 제약하는가
$P$개의 파라미터를 포함하는 트랜스포머 모델을 가정해 봅시다. 16비트 정밀도(BF16 또는 FP16)에서 각 파라미터는 2바이트를 차지합니다. $B$를 배치 크기(batch size)로, $L$을 처리되는 시퀀스 길이(sequence length)로 나타냅니다.
1. 선형 투영의 산술 강도 (Arithmetic Intensity of Linear Projections)
선형 레이어(MLP / FFN 확장 및 Attention QKV 투영)는 모델 파라미터의 대부분을 차지합니다. 표준 행렬 곱셈은 파라미터당 2개의 부동 소수점 연산(하나의 곱셈, 하나의 덧셈)이 필요합니다.
$B \times L$ 토큰을 포함하는 실행 배치에 대해:
- 부동 소수점 연산: $ ext{FLOPs} = 2 \times P \times B \times L$
- 가중치 메모리 트래픽: HBM에서 가중치 행렬을 스트리밍하는 데 필요한 양은 $ ext{Bytes}_{\text{weights}} = 2 \times P$
선형 투영에 대한 산술 강도를 평가하면 다음과 같습니다:
$$I ext{ extunderscore weights} = \frac{2 \times P \times B \times L}{2 \times P} = B \times L \quad (\text{FLOPs/Byte})$$
이 공식은 근본적인 법칙을 도출합니다: 선형 투영(linear projections)의 산술 강도(arithmetic intensity)는 동시에 처리되는 토큰 수($B \times L$)와 수치적으로 같습니다!
Case A: Prefill 단계 (The Prefill Regime)
Prefill 과정 동안 $L = L ext{ extunderscore prompt}$입니다. 표준 컨텍스트 프롬프트 길이 $L ext{ extunderscore prompt} = 2048$을 가진 단일 사용자 질의($B = 1$)의 경우에도:
$$I ext{ extunderscore prefill} = 1 \times 2048 = 2048 \text{ FLOPs/Byte}$$
이를 H100 리지 포인트($I ext{ extunderscore ridge} = 295.3 \text{ FLOPs/Byte}$)와 비교하면:
$$I ext{ extunderscore prefill} ; (2048) \gg I ext{ extunderscore ridge} ; (295.3)$$
Prefill 강도는 하드웨어 임계치를 거의 7배 초과합니다. 이는 컴퓨팅 바운드(compute-bound)한 평평한 리지라인에 확실하게 위치합니다. Tensor Core는 최고 효율로 작동하며, 실행 시간은 순전히 부동소수점 처리량(floating-point throughput)에 달려 있습니다.
Case B: Decode 단계 (The Decode Regime)
자기회귀적 디코드 루프(autoregressive decode loop)에서 모델은 시퀀스당 정확히 하나의 토큰을 방출합니다($L = 1$). 이 방정식은 다음과 같이 단순화됩니다:
$$I ext{ extunderscore decode} = B \times 1 = B \text{ FLOPs/Byte}$$
상호작용하는 개발자 세션 또는 단일 클라이언트를 서비스하는 비공개 추론 엔드포인트($B = 1$)의 경우:
$$I ext{ extunderscore decode} = 1 \text{ FLOPs/Byte} \lll 295.3 \text{ FLOPs/Byte}$$
H100에서 달성 가능한 성능을 평가하면:
$$P ext{ extunderscore attainable} = I ext{ extunderscore decode} \times B ext{ extunderscore mem} = 1 \text{ FLOPs/Byte} \times 3,350 \text{ GB/s} = 3.35 \text{ TFLOPS}$$
$$\text{Compute Efficiency} = \frac{3.35 \text{ TFLOPS}}{989.4 \text{ TFLOPS}} \approx \mathbf{0.34%}$$
이는 프로파일러가 생성 과정 중 Tensor Core 활용률이 거의 0에 가깝게 측정되는 이유에 대한 수학적 증거를 제공합니다. 배치 크기(batch size)가 1일 때, 컴퓨팅 하드웨어의 99.66%는 물리적으로 정지 상태(stalled)입니다.
기업 서버가 $B=32$의 동시성을 달성하기 위해 트래픽을 풀링하더라도, ${3}2 \text{ FLOPs/Byte}$의 산술 강도는 295.3 FLOPs/Byte 임계값에 훨씬 미치지 못하여 시스템이 메모리 대역폭(memory-bandwidth)에 깊게 구속됨을 보여줍니다.
2. 누적 페널티: KV 캐시 대역폭 오버헤드
위의 유도는 모델 가중치만을 고려했습니다. 실제로는, 자기회귀 디코드 루프(autoregressive decode loop)에서 모든 선행 토큰에 대한 키-값 (Key-Value, KV) 캐시를 로드해야 합니다.
토큰 $t$를 생성하기 위해, 단일 쿼리 벡터 (${1} \times d_{\text{head}}$)는 모든 레이어에 걸쳐 모든 역사적 키 및 값 벡터 ($L_{\text{ctx}} \times d_{\text{head}}$)와 내적(dot-product attention) 연산을 수행해야 합니다:
$$\text{Bytes}{\text{KV}_read} = 2 \times 2 \times n{\text{layers}} \times n_{\text{kv}_heads} \times d_{\text{head}} \times L_{\text{ctx}} \times B$$
(첫 번째 계수 2는 키와 값을, 두 번째 계수 2는 16비트 저장을 나타냅니다.)
어텐션 단계의 작동 강도(operational intensity)를 평가하면:
- 어텐션 연산: ${4} \times n_{\text{layers}} \times n_{\text{heads}} \times d_{\text{head}} \times L_{\text{ctx}} \times B$ FLOPs
- 어텐션 산술 강도: $$I_{\text{KV}} = \frac{4 \times n_{\text{layers}} \times n_{\text{heads}} \times d_{\text{head}} \times L_{\text{ctx}} \times B}{4 \times n_{\text{layers}} \times n_{\text{kv}_heads} \times d_{\text{head}} \times L_{\text{ctx}} \times B} = \frac{n_{\text{heads}}}{n_{\text{kv}_heads}}$$
이 수학적 결과는 엔지니어링 병목 현상을 보여줍니다:
- 표준 **Multi-Head Attention (MHA)**의 경우 $n ext{_heads} = n ext{_kv_heads}$일 때, KV Cache를 읽는 작업 강도는 배치 크기나 시퀀스 길이에 관계없이 **엄격하게 ${1} \text{ FLOP/Byte}$**입니다.
- **Grouped-Query Attention (GQA)**의 경우(예: 8:1 헤드 비율을 가진 Llama-3), 산술 강도는 ${8} \text{ FLOPs/Byte}$까지만 상승합니다.
- 컨텍스트 길이가 확장됨($L ext{_ctx} \ge 32\text{K}$)에 따라, KV Cache가 소비하는 메모리 트래픽은 모델 가중치 자체를 스트리밍하는 데 필요한 대역폭을 초과합니다 (저희의 Kimi KDA 및 DeepSeek MLA Memory Wall Architectures 분석 참조).
4. 지표의 괴리: TTFT와 TPOT 사이의 내재적 긴장
이러한 물리적 하드웨어 분할은 프로덕션 서비스 수준 계약(SLA)에서 피할 수 없는 충돌을 만듭니다:
flowchart LR
subgraph ClientTimeline["클라이언트가 인지하는 지연 시간"]
direction TB
...
1. Time to First Token (TTFT)
요청 전송부터 첫 번째 생성 토큰을 수신할 때까지의 지속 시간으로, 프리필(prefill) 단계에 해당합니다:
$$\text{TTFT} \approx \frac{2 \cdot P \cdot L\text{_prompt}}{\text{Achieved FLOPS}} + T\text{_queue}$$
- 주요 동인: 최대 GPU FLOPS, 프롬프트 토큰 수, 커널 병렬성.
- 사용자 인식: 높은 TTFT는 애플리케이션이 응답하지 않거나 멈춘 것처럼 느끼게 합니다.
2. Time Per Output Token (TPOT / ITL)
생성 중 연속적인 스트리밍 토큰 사이의 증분 지연 시간:
$$\text{TPOT} \approx \frac{\text{Bytes} ext{_Weights} + \text{Bytes} ext{_KV} ext{_Cache}(L\text{_ctx})}{\text{Achieved Memory Bandwidth}} \times \frac{1}{B}$$