Field Log · Entry
LLM 추론 메모리 계산: Weight·Activation·KV Cache·Workspace (2/10)
오늘의 결론
- “Checkpoint가 GPU에 올라간다”와 “목표 동시성을 안정적으로 처리한다”는 다른 조건입니다. Weight 외에 KV cache, activation, kernel workspace, collective buffer, allocator·graph pool, 안전 여유가 필요합니다.
- Dense decoder의 KV cache는 대략
layers × tokens × 2(K,V) × KV heads × head_dim × element bytes입니다. GQA·MQA에서는 query head가 아니라 KV head 수를 넣어야 합니다.- Inference activation은 training처럼 모든 layer를 backward까지 보관하지 않지만 0도 아닙니다. Prefill chunk, logits, attention·quantization workspace, sampling, 통신 buffer가 순간 peak를 만듭니다.
- Paged KV는 최대 context를 요청마다 미리 예약하는 낭비를 줄이지만, 물리 block이 무한해지는 것은 아닙니다. 현재 사용 token, 예상 성장, shared prefix, preemption 비용으로 admission해야 합니다.
- Capacity는
남은 GiB ÷ 요청당 최대 GiB한 줄로 정하지 않습니다. 실제 prompt·output·동시성 분포로 token budget을 만들고, p95·burst·fragmentation·worker 복구 headroom을 남깁니다.
앞 글에서는 FLOP와 HBM byte로 prefill·decode 병목을 읽었습니다. 이번 글은 GPU HBM이라는 유한한 공간에 무엇이 얼마나 오래 머무는지 계산합니다.
physical GPU memory
→ persistent: weights · adapter · runtime state
→ request-lived: KV cache · grammar · sampling state
→ iteration-lived: activation · logits · kernel workspace · collectives
→ allocator / graph pools / fragmentation
→ operational headroom
이 글에서 답하는 질문
- Checkpoint 크기와 runtime weight memory는 왜 다른가?
- MHA·GQA·MQA의 KV cache byte를 어떻게 계산하는가?
- Inference에서도 activation과 workspace가 필요한 이유는 무엇인가?
- Tensor parallel을 쓰면 모든 memory가 GPU 수로 정확히 나뉘는가?
- Prefix sharing과 paged allocation을 capacity 계산에 어떻게 반영하는가?
- OOM 전에 어떤 요청을 accept·queue·reject할지 어떻게 정하는가?
그림 1. 메모리 원장은 byte의 크기뿐 아니라 lifetime과 owner를 기록한다. 같은 10GiB라도 고정 weight와 요청마다 늘어나는 KV pool은 운영 방식이 다르다.
1. 먼저 GB와 GiB를 구분한다
모델 사양과 운영체제 도구는 서로 다른 단위를 사용할 수 있습니다.
1 GB = 1,000,000,000 bytes
1 GiB = 1,073,741,824 bytes = 2^30 bytes
예를 들어 8 billion parameter를 BF16 2 byte로만 저장하면:
16,000,000,000 bytes
≈ 16.0 GB
≈ 14.90 GiB
“16GB model이 16GiB GPU에 들어간다”라고 말하면 단위 차이만으로도 틀릴 수 있습니다. 이 글의 계산 표는 GiB를 기준으로 하고 byte 원본도 함께 보관합니다.
2. Memory를 Lifetime으로 나눈다
Worker Lifetime
Model server가 뜬 뒤 계속 유지됩니다.
- Model weights
- 일부 embedding·normalization·output head
- Quantization scale·zero point·packing metadata
- LoRA adapter와 adapter cache
- CUDA context와 library handle
- Graph-captured memory pool
- Engine metadata와 persistent kernel state
Request Lifetime
요청이 진행되는 동안 유지됩니다.
- Prompt와 생성 token의 KV cache
- Sequence·block table
- Sampling RNG와 beam state
- Structured generation grammar state
- Prefix cache reference
- Tool wait 중 보존한 Agent context
Iteration Lifetime
한 prefill chunk 또는 decode step 안에서 생성·재사용됩니다.
- Hidden activation buffer
- Q·K·V projection 결과
- Attention output과 reduction buffer
- Logits 또는 selected logits
- Quantize·dequantize workspace
- Tensor-parallel collective buffer
Peak memory는 세 lifetime이 겹치는 시점에 발생합니다.
3. 전체 Memory Ledger 식
Device 한 장의 안전한 budget을 다음처럼 둡니다.
$$ M_{device} \geq M_{weights}+M_{adapters}+M_{runtime}+M_{KV}+M_{allocator}+M_{headroom} $$
여기서 runtime은 activation·workspace·communication을 포함하고, allocator는 cached block·graph pool·fragmentation을 포함합니다.
Usable Memory
usable
= physical device memory
− driver / context / monitoring reservation
− failure and burst headroom
Engine의 gpu_memory_utilization=0.95 같은 설정은 보편적 정답이 아닙니다. Kernel, graph capture, 다른 process, TP collective, adapter load가 같은 device를 사용한다면 더 낮춰야 할 수 있습니다.
4. Weight Memory의 첫 근사
Dense model의 raw weight payload는:
$$ M_{weights,raw}=N_{parameters}\times bytes_{weight} $$
| Format | Raw bytes/parameter | 8B parameter 예시 |
|---|---|---|
| FP32 | 4 | 약 29.8 GiB |
| FP16/BF16 | 2 | 약 14.9 GiB |
| INT8 | 1 | 약 7.45 GiB |
| INT4 | 0.5 | 약 3.73 GiB |
이는 payload 예시값입니다. 실제 runtime은 다음을 더합니다.
runtime weights
= packed payload
+ group scales / zero points
+ alignment / padding
+ unquantized layers
+ transformed kernel layout
+ duplicate or staged load buffer
5. Checkpoint File 크기를 그대로 쓰면 안 된다
File artifact에는 shard header, tensor metadata가 있고 압축될 수도 있습니다. Runtime은 engine이 선호하는 layout으로 변환할 수 있습니다.
반대로 load 중에는 순간적으로 두 copy가 겹칠 수 있습니다.
CPU checkpoint buffer
→ GPU staging tensor
→ packed runtime tensor
→ old staging tensor release
따라서 세 값을 따로 기록합니다.
- Disk artifact byte
- Host load peak
- GPU steady-state allocated·reserved byte
6. MoE는 Active Parameter만 보면 안 된다
Mixture-of-Experts model은 token마다 일부 expert만 실행하므로 FLOP는 active parameter에 가깝습니다. 하지만 expert를 offload하지 않는 한 device·cluster memory에는 resident parameter 전체가 필요합니다.
compute per token → active experts
memory capacity → resident experts
Expert parallelism은 expert weight를 여러 device에 나눌 수 있지만 routing buffer와 all-to-all 통신이 추가됩니다. “활성 14B”를 dense 14B의 memory처럼 계산하면 안 됩니다.
7. Tensor Parallel에서 단순히 GPU 수로 나누지 않는다
이상적 weight shard만 보면:
device weight ≈ sharded weight / TP degree
하지만 모든 tensor가 같은 축으로 분할되지는 않습니다.
- Embedding과 LM head가 shard되거나 복제될 수 있습니다.
- Norm, bias, small metadata는 rank마다 복제될 수 있습니다.
- KV head 수와 TP degree가 맞지 않으면 KV가 복제되거나 uneven shard가 생깁니다.
- Collective input·output buffer는 rank마다 필요합니다.
- CUDA Graph가 batch shape별 buffer를 예약할 수 있습니다.
따라서 engine의 parameter partition manifest와 rank별 memory snapshot을 확인합니다.
8. Pipeline Parallel의 Memory 성격
Pipeline stage는 layer 일부만 가지므로 weight와 KV layer 수가 나뉩니다.
stage 0: layers 0..7
stage 1: layers 8..15
stage 2: layers 16..23
stage 3: layers 24..31
하지만 stage boundary activation buffer와 microbatch queue가 추가됩니다. Stage별 layer 수·embedding·LM head가 균등하지 않으면 가장 큰 stage가 capacity를 제한합니다.
9. Adapter Memory를 별도 계정으로 둔다
LoRA layer의 두 low-rank matrix는 base보다 작지만 adapter 수가 많아지면 무시할 수 없습니다.
one LoRA target weight [d_out, d_in]
A: [rank, d_in]
B: [d_out, rank]
adapter elements = rank × (d_in + d_out)
Multi-tenant serving에서는 다음을 구분합니다.
- 항상 resident한 hot adapter
- 요청 시 load하는 cold adapter
- GPU adapter cache eviction
- Base와 merge한 별도 weight copy
- Adapter별 graph·kernel compatibility
Adapter를 base에 merge한 checkpoint 여러 개를 올리면 low-rank memory 이점을 잃습니다.
10. KV Cache Byte 공식
Decoder layer마다 과거 token의 key와 value를 저장합니다. Sequence 하나의 기본 식은:
$$ M_{KV}=L\times T\times 2\times H_{KV}\times D_{head}\times B_{elem} $$
L: layer 수T: 현재 prompt + generated token 수2: key와 valueH_KV: KV head 수D_head: head dimensionB_elem: KV element bytes
Token 하나당 byte는:
$$ bytes_{KV/token}=L\times2\times H_{KV}\times D_{head}\times B_{elem} $$
11. 계산 예시: GQA Decoder
설명을 위한 model shape:
layers = 32
query heads = 32
KV heads = 8
head dim = 128
KV dtype = BF16 = 2 bytes
Token 하나의 KV는:
32 × 2 × 8 × 128 × 2
= 131,072 bytes
= 128 KiB / token
8,192 token sequence 하나는:
128 KiB × 8,192
= 1 GiB
동시에 32개 sequence가 모두 8K라면 raw KV만 약 32GiB입니다.
같은 Shape가 MHA라면
KV head도 32개라면 GQA 예시의 4배입니다.
512 KiB / token
8K sequence ≈ 4 GiB
MQA라면
KV head가 1개이면 GQA 예시의 1/8입니다. Multi-Query Attention은 query head들이 하나의 K/V head를 공유해 autoregressive decoding의 KV memory traffic을 줄입니다. Grouped-Query Attention은 품질과 속도 사이에서 여러 query head가 KV head group을 공유합니다.
12. TP Rank당 KV Byte를 검증한다
KV head가 TP rank에 균등하게 shard되면 device당 KV는 대략 TP degree로 나뉩니다. 그러나 다음 경우는 예외입니다.
num_kv_heads < TP degree- KV head 수가 TP degree로 나누어지지 않음
- Engine이 KV head를 rank에 복제
- P/D disaggregation에서 sender와 receiver에 transfer buffer가 겹침
- Prefix block metadata가 rank마다 복제
Model config 식과 실제 rank별 allocated byte를 모두 확인합니다.
13. KV는 Token마다 자란다
Request i의 현재 길이를 T_i라 하면 batch raw KV는:
$$ M_{KV,batch}=bytes_{KV/token}\times\sum_i T_i $$
중요한 것은 sequence 수보다 active token 합입니다.
request A: 1K prompt + 100 generated
request B: 16K prompt + 20 generated
request C: 4K prompt + 2K generated
active KV tokens = 1.1K + 16.02K + 6K
Request 3개라는 지표만으로 memory pressure를 알 수 없습니다.
14. 최대 Context를 전부 곱하는 계산의 한계
보수적인 상한은:
max KV
= max_concurrent_sequences
× max_context_tokens
× KV bytes per token
하지만 production에서 모든 요청이 동시에 최대 길이에 도달하지 않는다면 지나치게 보수적입니다. 반대로 평균 길이만 쓰면 burst에서 OOM이 납니다.
다음 cohort를 사용합니다.
| Cohort | Prompt p50/p95/p99 | Output p50/p95/p99 | 동시성 | Prefix hit |
|---|---|---|---|---|
| 짧은 QA | 1K/2K/4K | 80/200/500 | 높음 | 중간 |
| 보고서 | 8K/20K/40K | 1K/3K/8K | 낮음 | 낮음 |
| Agent tool | 4K/12K/24K | step당 100/400/1K | 중간 | 높음 |
Admission은 이 분포와 request deadline을 함께 사용합니다.
15. Paged KV Cache가 해결하는 것
연속 memory를 sequence 최대 길이만큼 예약하면 실제 사용하지 않은 영역이 낭비됩니다. PagedAttention은 logical KV block을 non-contiguous physical block에 mapping해 요청이 자랄 때 block을 추가합니다.
request logical blocks: [0][1][2]
block table: ↓ ↓ ↓
physical blocks: [9][3][17]
얻는 것은 다음입니다.
- 최대 길이 전체를 미리 연속 예약하지 않음
- Free block을 다른 request에 재사용
- 마지막 block을 제외한 내부 낭비 감소
- Copy-on-write와 prefix block sharing 가능
그러나 다음 비용이 새로 생깁니다.
- Block table metadata
- 마지막 partial block의 내부 fragmentation
- Block 크기와 kernel 효율 trade-off
- Free block 고갈 시 preemption·swap·recompute
- Paged layout을 읽는 attention kernel
16. Virtual Contiguity라는 다른 선택
vAttention은 virtual address는 contiguous하게 유지하고 physical memory를 on-demand로 mapping하는 접근을 제시합니다. 목적은 PagedAttention과 마찬가지로 동적 allocation과 fragmentation을 다루지만 kernel interface와 memory mapping 방식이 다릅니다.
따라서 “paged가 항상 정답”이 아니라 다음을 target engine에서 측정합니다.
- Allocation latency
- Attention kernel compatibility
- HBM fragmentation
- Block table 또는 VMM metadata
- Long-running steady state
- Prefix sharing 구현
17. Prefix Cache는 Logical Token이 아니라 Physical Byte를 줄인다
여러 요청이 같은 prefix를 가지면 KV block을 공유할 수 있습니다.
shared system + tool schema: 3K tokens
request A suffix: 1K
request B suffix: 2K
without sharing: 4K + 5K = 9K KV tokens
with sharing: 3K shared + 1K + 2K = 6K physical KV tokens
Cache hit request 수보다 다음을 측정합니다.
logical KV tokens
physical KV tokens
reused prefix tokens
copy-on-write blocks
evicted tokens
recompute tokens
Tenant authorization, model revision, tokenizer, chat template, adapter가 다르면 같은 text라도 안전하게 공유할 수 없습니다.
18. KV Quantization은 Byte와 Kernel을 함께 바꾼다
BF16 KV를 INT8로 바꾸면 raw payload는 절반, 4-bit라면 1/4에 가깝습니다. 하지만 scale·zero point·outlier·residual window와 dequantization kernel이 추가됩니다.
KIVI는 key와 value의 분포 차이를 분석해 key는 per-channel, value는 per-token 방식의 비대칭 2-bit quantization을 연구했습니다. KVQuant도 per-channel key, pre-RoPE key, outlier 처리 등을 다룹니다.
Capacity gate는 raw bit만 보지 않습니다.
effective KV byte
= packed values
+ scale / zero-point metadata
+ residual high-precision window
+ alignment
+ temporary dequant workspace
그리고 target long-context evaluation에서 quality와 kernel latency를 함께 검증합니다.
19. Inference Activation은 왜 남는가
Training은 backward를 위해 layer activation을 오래 보관합니다. Autoregressive inference는 한 layer 결과를 다음 layer로 넘긴 뒤 많은 중간값을 재사용하거나 해제할 수 있어 훨씬 작습니다.
그래도 다음은 필요합니다.
hidden buffers
Q/K/V projection outputs
attention tile / reduction workspace
MLP intermediate
normalization and residual buffers
selected logits / sampling buffers
대략적인 hidden payload 하나는:
scheduled tokens × hidden size × element bytes
하지만 engine이 double buffering, fused kernel, TP shard, padding을 어떻게 쓰는지에 따라 multiplier가 달라집니다. “activation = hidden 하나”로 고정하지 않고 profiler peak를 사용합니다.
20. Prefill이 만드는 순간 Peak
Prefill은 많은 token을 한 iteration에 처리합니다.
prefill tokens in this iteration
= sum of full prompts or chunks in batch
Chunk가 클수록 GEMM 효율은 좋아질 수 있지만 activation·attention workspace와 TTFT stall이 커집니다. Chunked prefill은 compute scheduling뿐 아니라 peak activation budget을 조절합니다.
Logits Explosion을 피한다
Vocabulary V 전체 logits를 모든 prompt position에 materialize하면:
tokens × vocab_size × logit_bytes
가 매우 커질 수 있습니다. Inference에서는 보통 필요한 position만 계산·유지하거나 chunk로 소비합니다. Engine이 full logits를 반환하는 옵션을 켜면 memory peak가 달라질 수 있습니다.
21. Kernel Workspace와 Library 선택
같은 model shape라도 backend가 다르면 workspace가 다릅니다.
- Attention backend tile workspace
- GEMM heuristic workspace
- INT4 unpack·dequant buffer
- MoE routing·sorting buffer
- Top-k/top-p sampling scratch
- Grammar mask와 vocabulary bitmap
- Beam search candidate buffer
Fast kernel이 더 큰 workspace를 요구할 수 있습니다. Latency benchmark와 peak memory benchmark를 같은 configuration에서 실행합니다.
22. Tensor-Parallel Communication Buffer
Collective는 payload와 staging buffer를 사용합니다.
all-reduce / reduce-scatter / all-gather
→ input tensor
→ output tensor
→ communication scratch
→ possibly graph-captured fixed buffers
Communication과 compute를 overlap하면 두 lifetime이 겹쳐 peak가 증가할 수 있습니다. “Overlap으로 빨라졌다”와 “더 많은 request를 admit할 수 있다”는 별도 결과입니다.
23. Allocated와 Reserved를 구분한다
Caching allocator는 해제된 block을 driver에 즉시 반환하지 않고 재사용할 수 있습니다.
allocated: live tensor가 실제 사용하는 byte
reserved: allocator가 확보해 둔 전체 pool
free inside pool: reserved − allocated 중 재사용 가능한 부분
device free: driver가 보고하는 미예약 공간
OOM 진단에서는 네 값을 time series로 남깁니다. Reserved가 큰데 적절한 block을 만들지 못하면 fragmentation이나 graph pool 분리가 원인일 수 있습니다.
24. CUDA Graph Memory Pool
Dynamic launch overhead를 줄이기 위해 shape별 graph를 capture하면 주소가 안정적인 buffer가 필요할 수 있습니다.
captured batch shapes: 1, 2, 4, 8, 16, 32, 64
each shape → persistent or reusable graph resources
Warm-up 뒤 memory가 갑자기 늘어나는 이유가 될 수 있습니다. 다음을 기록합니다.
- Capture 전 steady memory
- Shape별 capture 증가량
- 최대 capture shape
- Eager fallback memory
- Graph pool 공유 여부
25. OOM은 여섯 종류로 분류한다
1. Load-time OOM
Weight·layout 변환·staging copy가 겹칩니다.
2. Warm-up OOM
Kernel autotune 또는 graph capture에서 예상보다 큰 buffer를 만듭니다.
3. Prefill Spike OOM
긴 prompt 여러 개가 동시에 admit되어 activation과 새 KV block을 한꺼번에 요구합니다.
4. Decode Growth OOM
모든 sequence가 예상보다 길게 생성되며 free KV block을 소진합니다.
5. Fragmentation OOM
총 free byte는 있어 보이지만 필요한 크기·layout을 할당하지 못합니다.
6. Recovery OOM
Worker 재시작, adapter 교체, P/D KV transfer, rolling update에서 old·new resource가 겹칩니다.
OOM timestamp에 request cohort, scheduled token, free KV block, allocator snapshot을 연결해야 합니다.
26. Memory Calculator를 만든다
from dataclasses import dataclass
GIB = 1024 ** 3
@dataclass(frozen=True)
class ModelShape:
parameters: int
layers: int
kv_heads: int
head_dim: int
weight_bytes: float
kv_bytes: float
def raw_weight_gib(self) -> float:
return self.parameters * self.weight_bytes / GIB
def kv_bytes_per_token(self) -> float:
return (
self.layers
* 2 # key + value
* self.kv_heads
* self.head_dim
* self.kv_bytes
)
def sequence_kv_gib(self, tokens: int) -> float:
return self.kv_bytes_per_token() * tokens / GIB
shape = ModelShape(
parameters=8_000_000_000,
layers=32,
kv_heads=8,
head_dim=128,
weight_bytes=2, # BF16 example
kv_bytes=2,
)
print("raw weights GiB", shape.raw_weight_gib())
print("KV KiB/token", shape.kv_bytes_per_token() / 1024)
print("8K sequence KV GiB", shape.sequence_kv_gib(8192))
Runtime Manifest를 입력으로 받는다
수동 숫자 대신 model config와 engine manifest에서 읽되 다음을 출력 artifact로 고정합니다.
{
"model_revision": "immutable-hash",
"engine_revision": "immutable-hash",
"tp": 2,
"pp": 1,
"weight_format": "bf16",
"kv_format": "bf16",
"raw_weight_gib": 14.9,
"measured_weight_steady_gib_per_rank": 8.2,
"measured_runtime_peak_gib_per_rank": 4.1,
"kv_kib_per_token_per_rank": 64.0,
"kv_pool_gib_per_rank": 24.0,
"headroom_gib_per_rank": 7.5
}
숫자는 형식을 보여주는 예시입니다.
27. KV Token Pool로 Capacity를 표현한다
KV pool M_pool과 token당 byte b_token이 있으면 물리 token capacity의 첫 근사는:
$$ T_{capacity}=\left\lfloor\frac{M_{pool}}{b_{token}}\right\rfloor $$
예시:
KV pool = 24 GiB
KV = 128 KiB/token
token capacity
= 24 × 1024 × 1024 KiB / 128 KiB
= 196,608 physical KV tokens
평균 active length가 6K면 약 32 sequence라는 거친 값이지만, block rounding·prefix sharing·output growth·preemption headroom 때문에 실제 admission limit은 더 낮거나 높을 수 있습니다.
28. Admission은 현재 사용량과 미래 성장을 본다
새 요청 r에 필요한 reservation을 다음처럼 추정합니다.
new physical KV
= uncached prompt tokens
+ reserved output tokens
+ block rounding
+ branch / beam multiplier
def can_admit(
free_kv_tokens: int,
uncached_prompt: int,
reserved_output: int,
safety_tokens: int,
) -> bool:
required = uncached_prompt + reserved_output + safety_tokens
return required <= free_kv_tokens
max_tokens는 사용자가 허용한 상한이지 실제 생성 길이 예측과 같지 않습니다. Product cohort의 output quantile, deadline, early-stop 특성을 사용하고 위험 요청은 queue 또는 별도 pool로 보냅니다.
29. RAG Agent에서 Memory가 커지는 지점
Retrieved Context
Top-k와 chunk size가 prefill compute와 KV를 함께 키웁니다. 검색 점수가 낮은 근거를 더 넣는 것은 품질뿐 아니라 concurrency 비용도 발생시킵니다.
Tool Schema
도구 수십 개의 JSON schema가 모든 step의 공통 prefix가 됩니다. Prefix cache로 공유할 수 있지만 schema revision이 잦으면 hit가 깨집니다.
Tool Wait
외부 API를 기다리는 동안 KV를 GPU에 보존하면 다른 요청의 capacity가 줄어듭니다.
short wait → preserve
medium wait → swap / offload
long or uncertain wait → discard + recompute
이 선택은 GPU GiB뿐 아니라 PCIe·network bandwidth와 recompute FLOP를 비교합니다.
Multi-turn Growth
대화 전체를 매번 prompt에 넣으면 turn이 늘수록 KV와 prefill이 자랍니다. Stable prefix reuse, conversation compaction, evidence lifecycle을 설계합니다.
Parallel Branch
Agent가 후보 답변·tool plan을 여러 개 동시에 만들면 branch마다 suffix KV가 필요합니다. Shared prefix와 copy-on-write가 실제로 작동하는지 확인합니다.
30. Memory Telemetry
Request·iteration마다 모든 byte를 log하면 비싸므로 worker metric과 sampled trace를 조합합니다.
Worker Metric
- Device allocated·reserved·free byte
- Weight·runtime·KV pool configured byte
- Free·used KV block 수
- Physical·logical KV token 수
- Prefix reused·evicted token
- Preempted·swapped·recomputed token
- Adapter cache byte와 eviction
- Graph capture pool byte
- OOM·allocation retry count
Request Trace
- Prompt·uncached prompt token
- Reserved·actual output token
- KV block allocation timeline
- Prefix identity와 hit length
- Tool pause duration과 preserve/offload/recompute action
- Finish·cancel 시 block 반환 지연
31. 안전한 Capacity 실험
- Worker를 기동하고 load 전·후 memory snapshot을 기록합니다.
- Representative shape를 모두 warm-up하고 graph capture 후 snapshot을 기록합니다.
- KV pool을 명시적으로 확인합니다.
- 짧은 요청부터 open-loop arrival rate를 올립니다.
- Long prompt와 long output cohort를 섞습니다.
- Prefix hit·miss를 분리합니다.
- Cancellation과 tool pause를 주입합니다.
- 30분 이상 steady state에서 fragmentation과 eviction을 봅니다.
- Worker restart·rolling update 동안 headroom을 검증합니다.
- OOM이 아니라 SLO miss가 먼저 발생하는 operating point를 찾습니다.
32. 최소 구현 순서
- Model config에서 parameter, layer, KV head, head dimension, dtype을 추출합니다.
- Raw weight와 KV bytes/token을 계산합니다.
- Engine load 후 rank별 steady weight·runtime memory를 측정합니다.
- 모든 target batch shape를 warm-up한 뒤 peak를 다시 측정합니다.
- 남은 공간을 KV pool과 operational headroom으로 명시적으로 나눕니다.
- Production prompt·output trace로 active token distribution을 만듭니다.
- Prefix hit·block rounding·branch multiplier를 reservation에 반영합니다.
- Admission에 free token, future growth, deadline을 사용합니다.
- Prefill spike·decode growth·tool pause·restart scenario를 부하 시험합니다.
- Model·engine revision별 memory ledger를 release artifact로 보존합니다.
실전 체크리스트
- GB와 GiB를 구분하고 byte 원본을 보관한다.
- Disk checkpoint와 GPU runtime weight를 구분했다.
- Quantization metadata와 unquantized layer를 포함했다.
- MoE는 active가 아니라 resident parameter로 memory를 계산했다.
- TP·PP rank별 shard와 replicated tensor를 확인했다.
- Adapter resident·cache·merge memory를 포함했다.
- KV 공식에
num_attention_heads가 아니라num_kv_heads를 넣었다. - KV dtype과 scale·residual metadata를 포함했다.
- Logical token과 physical KV token을 구분했다.
- Prefix sharing의 tenant·model·tokenizer boundary가 있다.
- Prefill activation과 logits peak를 측정했다.
- Attention·quantization·sampling workspace를 측정했다.
- TP collective와 overlap buffer를 포함했다.
- Allocated·reserved·device free를 모두 기록한다.
- Graph capture 전후 memory 차이를 측정했다.
- KV free block과 예상 output growth로 admission한다.
- 평균 길이뿐 아니라 p95·p99와 burst를 시험했다.
- Tool wait의 preserve·offload·recompute 정책이 있다.
- Cancellation 뒤 KV 반환을 검증했다.
- Rolling update와 worker recovery headroom을 시험했다.
스스로 확인하기
Q1. 32 layer, KV head 8개, head dimension 128, BF16 KV인 model의 token당 KV는 얼마인가?
32 × 2 × 8 × 128 × 2 = 131,072 byte, 즉 128KiB입니다.
Q2. GQA model에서 query head 수로 KV를 계산하면 어떤 오류가 생기는가?
KV head가 query head보다 적으므로 memory를 과대 계산합니다. 반대로 runtime이 TP rank에 KV를 복제하는 경우까지 무시하면 device당 memory를 과소 계산할 수 있습니다.
Q3. PagedAttention을 쓰면 OOM이 사라지는가?
아닙니다. 최대 길이 사전 예약과 fragmentation 낭비를 줄이지만 물리 KV block, runtime workspace, headroom은 여전히 유한합니다.
Q4. Prefix cache hit가 80%면 KV memory도 항상 80% 절약되는가?
아닙니다. Hit request 비율이 아니라 재사용된 physical token 수, prefix 길이, eviction·copy-on-write, suffix 길이로 계산해야 합니다.
Q5. Inference에서 training activation memory 식을 그대로 쓰면 안 되는 이유는 무엇인가?
Backward용 layer activation을 모두 보존하지 않기 때문입니다. 대신 현재 prefill·decode iteration의 hidden buffer, logits, kernel·communication workspace와 KV를 계산해야 합니다.
다음 글
이번 글은 GPU 공간을 byte 원장으로 만들었습니다. 다음 글 Transformer 추론 커널: GEMM·Attention·FlashAttention·Fusion에서는 그 memory를 실제로 읽고 계산하는 kernel을 분해합니다. 빠른 모델과 빠른 kernel이 같은 말이 아닌 이유를 살펴봅니다.
참고문헌
- Shazeer, Fast Transformer Decoding: One Write-Head is All You Need, 2019.
- Ainslie et al., GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints, EMNLP 2023.
- Kwon et al., Efficient Memory Management for Large Language Model Serving with PagedAttention, SOSP 2023.
- Prabhu et al., vAttention: Dynamic Memory Management for Serving LLMs without PagedAttention, 2024.
- Liu et al., KIVI: A Tuning-Free Asymmetric 2bit Quantization for KV Cache, ICML 2024.
- Hooper et al., KVQuant: Towards 10 Million Context Length LLM Inference with KV Cache Quantization, 2024.
- Abhyankar et al., INFERCEPT: Efficient Intercept Support for Augmented Large Language Model Inference, ICML 2024.
검증 메모 — 식과 문헌은 2026년 7월 16일 확인했습니다. 실제 byte는 model architecture와 engine의 shard·layout·allocator·kernel에 따라 달라집니다. 본문의 숫자는 원리를 설명하는 예시값이며, target revision을 warm-up한 뒤 rank별 allocated·reserved·KV pool을 측정해야 합니다.