1. 개요

트랜스포머는 다음 토큰을 정할 때 앞의 모든 토큰을 다시 본다(attention). 이때 쓰는 앞 토큰들의 Key·Value 벡터를 매번 새로 계산하지 않도록 GPU 메모리에 저장해 둔 것이 KV 캐시다.

① prefill — 한 번입력 1,000토큰을 한꺼번에IN입력 토큰 1,000개GPU층마다 K·V 계산 (행렬 곱)OUT첫 토큰여기까지가 TTFT연산이 무거운 단계② decode — 토큰마다출력 200토큰이면 200번NEW새 토큰 1개의 K·V 계산READ캐시 전체 + 가중치 읽기OUT다음 토큰간격이 ITL메모리 읽기가 무거운 단계KV 캐시쓰기읽기
prefill 은 캐시를 채우고, decode 는 캐시를 매번 전부 읽고 한 칸씩 붙인다
단계하는 일무거운 자원지표
prefill입력 토큰 전부의 K·V 를 한 번에 계산해 캐시에 쓴다연산(행렬 곱)TTFT
decode새 토큰 하나의 K·V 만 계산해 붙이고, 캐시 전체와 가중치를 읽어 다음 토큰을 정한다메모리 대역폭ITL

캐시가 없으면 토큰 하나를 만들 때마다 앞 토큰 전부를 다시 계산해야 한다. 캐시는 그 계산을 메모리로 바꾼 것이고, 그래서 메모리가 새 한계가 된다.

2. 크기 계산

KV 캐시는 요청마다, 토큰마다 자란다.

토큰당 바이트 = 2 (K, V) × 층 수 × KV 헤드 수 × 헤드 크기 × 원소 바이트
요청 하나     = 토큰당 바이트 × (입력 + 지금까지 출력한 토큰)
모델층KV 헤드헤드 크기BF16 토큰당16k 문맥 하나
Qwen3-8B368128144KiB2.25GiB
Qwen3-30B-A3B48412896KiB1.5GiB

KV 헤드 수가 Query 헤드 수보다 적은 것은 GQA(Grouped-Query Attention) 덕이다. Qwen3-8B 는 Query 헤드 32개가 KV 헤드 8개를 나눠 써서 캐시가 1/4 로 준다.

3. GPU 메모리 예산

vLLM 은 시작할 때 GPU 메모리의 --gpu-memory-utilization(기본 0.9)만큼을 쓰기로 하고, 가중치와 실행용 메모리를 뺀 나머지를 전부 KV 캐시 블록으로 미리 잡는다. 시작 로그의 GPU KV cache size: N tokens 가 그 결과다.

Qwen3.8-27B (FP8) on L40S 46GB가중치 약 31GBKV 9.3GB남겨 둠 10%≈ 118,000 토큰 (시작 로그의 GPU KV cache size)요청 하나가 차지하는 KV = 토큰 수 × 토큰당 바이트토큰당 바이트 = 2 (K, V) × 층 수 × KV 헤드 수 × 헤드 크기 × 원소 크기Qwen3-8B (BF16): 2 × 36 × 8 × 128 × 2B = 144KiB/토큰 → 16k 문맥 하나 2.25GiBQwen3-30B-A3B (BF16): 2 × 48 × 4 × 128 × 2B = 96KiB/토큰 → FP8 이면 48KiB
가중치가 클수록 KV 캐시 자리가 줄고, KV 캐시 자리가 동시 요청 수를 정한다
모델 (L40S 1장)가중치KV 캐시 자리동시 64 에서 KV 사용률
Qwen3.8-27B FP8약 31GB9.3GB (약 11만 8천 토큰)99%
gpt-oss-20b MXFP4약 13GB훨씬 넉넉8%

자리가 다 차면 새 요청은 대기 줄에서 기다리고, 실행 중인 요청이 블록을 더 못 받으면 vLLM 이 일부 요청을 내려놓았다가(preemption) 자리가 나면 다시 계산한다. 둘 다 TTFT를 늘린다.

4. PagedAttention

요청마다 최대 길이만큼 연속한 메모리를 미리 잡으면, 실제로 짧게 끝나는 요청의 자리가 낭비된다. vLLM 의 PagedAttention 은 KV 캐시를 작은 블록(기본 16토큰)으로 나눠 필요할 때마다 아무 빈 블록에나 붙인다.

요청의 논리 블록요청 A012요청 B01블록 표A: 0→3, 1→9, 2→6B: 0→1, 1→12블록 1개 = 16토큰 (기본)GPU 의 물리 블록#0B0#2A0#4#5A2#7#8A1#10#11B1#13#14#15
운영체제의 페이지처럼 — 연속한 자리를 미리 잡지 않아 버리는 메모리가 거의 없다
이점이유
낭비가 적다마지막 블록의 빈칸만 남는다. 논문 기준 낭비 4% 미만
앞부분 공유같은 시스템 프롬프트로 시작하는 요청은 같은 물리 블록을 함께 가리킨다(prefix caching)
내려놓기·되살리기블록 단위로 비우고 다시 채운다

5. FP8 로 저장하기

--kv-cache-dtype fp8 은 K·V 를 1바이트로 저장한다. 같은 자리에 토큰이 두 배 들어간다.

같은 KV 캐시 자리BF16 (2바이트)8칸FP8 (1바이트)16칸Qwen3-30B-A3B 실측: 최대 동시 20 → 24 · 동시 24 초당 출력 토큰 723 → 880
자리가 두 배라고 동시도 두 배는 아니다 — 연산과 ITL 이 다음 한계가 된다
고려할 점내용
품질값의 정밀도가 줄어든다. 긴 문맥 과제에서 품질을 따로 확인한다
효과가 큰 경우KV 캐시가 먼저 차는 모델(동시 요청이 많거나 문맥이 긴 경우)
효과가 작은 경우가중치가 작아 KV 자리가 이미 넉넉한 모델

6. 관련 문서

  • TTFT — KV 캐시가 차면 늘어나는 첫 토큰 시간
  • Concurrency — KV 캐시가 막는 동시 요청
  • MoE — 가중치를 덜 읽는 구조. KV 캐시 크기는 줄이지 않는다

7. 출처