본문 바로가기

05. KV 캐시

핵심 질문
  • KV 캐시는 무엇으로 이루어지고, 왜 토큰·레이어와 함께 자라는가?
  • "KV 캐시가 몇 GB"라는 값에서 하한(lower bound)실제 할당(actual allocation)은 어떻게 다른가?

목표
  • KV 캐시 크기의 하한 계산식을 각 기호의 뜻과 함께 세운다.
  • 하한과 실제 할당을 반드시 구분한다.

먼저: KV 캐시가 뭔가요? (다시, 정식으로)

02장에서 짧게 소개했지만, 이 장의 주제이니 정식으로 정의합니다.

autoregressive 생성(decode)에서 새 토큰을 만들 때, attention은 앞선 모든 토큰의 key와 value를 참조합니다. 매 스텝 이것을 처음부터 다시 계산하면 낭비가 큽니다. 그래서 각 토큰의 key/value를 한 번 계산해 저장(cache)해 두고 재사용합니다 — 이것이 KV(key-value) 캐시입니다.

일상 비유: 회의록을 매번 처음부터 다시 쓰지 않고, 이미 적어둔 앞 문장들은 그대로 두고 새 문장만 이어 적는 것과 같습니다. 회의가 길어질수록(토큰이 늘수록) 회의록은 계속 두꺼워집니다 — KV 캐시가 자라는 이유입니다.

flowchart LR
    T1[토큰1 K,V]:::mem --> C[(KV 캐시)]:::mem
    T2[토큰2 K,V]:::mem --> C
    T3[토큰3 K,V]:::mem --> C
    NEW[새 토큰 생성 시]:::comp -->|앞 K,V 읽기| C
    NEW -->|자기 K,V 추가| C

    classDef mem fill:#e5dbff,stroke:#7048e8,color:#000;
    classDef comp fill:#d0ebff,stroke:#1971c2,color:#000;

KV 캐시는 무엇에 비례해 자라나

한 요청의 KV 캐시 크기는 다음 요소들의 곱으로 커집니다. 각 기호를 말로 풀면:

  • L = 레이어 수 (layer). 레이어마다 독립적인 KV를 갖습니다.
  • H_kv = KV head 수 (key/value head). attention 표현 방식에 따라 달라집니다 — 06장의 핵심.
  • d_h = head 차원 (head dimension). head 하나가 다루는 벡터 크기.
  • T = 토큰 수 (컨텍스트 길이 = 프롬프트 + 지금까지 생성된 토큰).
  • 2 = key와 value, 둘을 저장하므로.
  • b = 요소당 바이트 수 (dtype: FP16이면 2, INT8이면 1 …).

이걸 곱하면 한 요청 한 시퀀스의 KV 하한이 나옵니다:

CODE
KV_하한(한 요청) ≈ 2 × L × H_kv × d_h × T × b
                    │    │     │      │    │   └ dtype 바이트
                    │    │     │      │    └ 토큰 수(계속 증가)
                    │    │     │      └ head 차원
                    │    │     └ KV head 수 (MHA/GQA/MLA에 따라 다름 → 06장)
                    │    └ 레이어 수
                    └ key + value

동시 요청이 N개면 대략 N배가 됩니다(요청마다 자기 시퀀스가 있으므로). 여기서 핵심은 T — 컨텍스트가 길어질수록 KV가 선형으로 커진다는 점입니다. 긴 컨텍스트가 왜 그렇게 비싼지가 이 식에 들어 있습니다.

🔎 이 안내서는 산술 워크스루를 최소화합니다. 위 식은 기호와 구조를 이해하는 것이 목적입니다. 구체 숫자를 넣어 계산하는 워크시트는 부록에 둡니다.

⚠️ 하한 ≠ 실제 할당

위 식은 하한(lower bound)입니다. 실제 서빙에서 할당되는 메모리는 이보다 큽니다. 이유:

  • 블록 단위 할당: 실제 엔진은 토큰 하나하나가 아니라 고정 크기 블록 단위로 KV 공간을 잡습니다(PagedAttention, 09장). 마지막 블록은 대개 일부만 차서 내부 단편화가 생깁니다.
  • 미리 예약(pre-allocation): 생성이 어디까지 갈지 몰라 여유를 잡아두는 경우.
  • 정렬·패딩·메타데이터: 블록 테이블 등 부가 자료구조.
📌 핵심
하한과 실제를 항상 갈라 말하라
"이 모델의 KV는 토큰당 X KB"라는 문장을 볼 때, 그게 식으로 계산한 하한인지, 특정 엔진·블록 크기에서 실제 할당한 값인지 물으세요. 둘은 다릅니다. 이 구분을 흐리면 15장의 벤치마크 비교가 무의미해집니다.
🔬 증거 읽기 (주장 유형)
  • 2 × L × H_kv × d_h × T × b로 낸 값은 calculated(입력·식 공개). 다만 "하한"이라는 꼬리표가 반드시 붙어야 합니다.
  • "이 엔진에서 실제로 토큰당 Y KB를 썼다"는 measured이며, 엔진·버전·블록 크기가 명시돼야 합니다.
  • 앞서 자료에서 본 "DeepSeek-V3는 토큰당 약 70KB, GQA 기반 모델은 192–328KB"(확인일 2026-07-20) 같은 수치는 vendor/measured 혼합으로, 모델 아키텍처(MLA vs GQA)라는 조건이 붙어야만 의미가 있습니다 — 아키텍처가 KV 크기를 바꾸기 때문입니다(06장).

이것이 여정에서 왜 중요한가

KV 캐시는 03장에서 본 decode의 memory 병목과, 04장에서 본 VRAM 경쟁의 교차점입니다. KV가 크면:

  • 동시에 받을 수 있는 요청 수(concurrency)가 줄고,
  • 긴 컨텍스트 요청이 특히 비싸지고,
  • KV 공간이 고갈되면 요청을 선점(preempt)하거나 축출(evict)해야 합니다(08장).

그래서 이후 여러 장이 "KV를 어떻게 아낄까"를 다룹니다: 표현을 바꾸기(GQA/MLA 06장), 낭비를 줄이기(PagedAttention 09장), 재사용하기(prefix caching 10장), 정밀도를 낮추기(KV 양자화 11장).

📌 핵심

  • KV 캐시는 앞 토큰의 key/value를 저장해 decode에서 재사용하는 상태로, 토큰·레이어와 함께 자란다.
  • 하한 ≈ 2 × L × H_kv × d_h × T × b. 핵심 변수는 T(컨텍스트 길이)H_kv(06장).
  • 하한 ≠ 실제 할당: 블록 단위 할당·예약·단편화로 실제는 더 크다.
  • KV는 decode 병목과 VRAM 경쟁의 교차점 — 이후 여러 최적화의 표적.

🔎 이 장의 '지지하지 않는 결론'

  • 하한 식만으로 "이 서버가 동시 요청 몇 개를 받는다"를 말할 수 없습니다 — 실제 할당·단편화·다른 손님(04장)이 필요합니다.
  • 두 모델의 토큰당 KB만 비교해 "이게 더 낫다"고 단정할 수 없습니다 — 아키텍처·품질·워크로드 조건이 빠졌습니다.

✍️ 확인 문제

  1. KV 하한 식의 각 기호(L, H_kv, d_h, T, b, 그리고 2)의 뜻을 한 줄씩 쓰세요. 컨텍스트가 2배가 되면 하한은 어떻게 변합니까?
  2. 하한과 실제 할당이 다른 이유를 두 가지 드세요.
  3. (조건 유형) "모델 A는 토큰당 70KB, 모델 B는 200KB니 A가 우월하다"는 주장의 어디가 성급합니까? 성립하려면 어떤 조건이 함께 명시돼야 합니까?
다음 장: 06 · MHA, MQA, GQA, MLA — 식의 H_kv를 바꾸는 attention 표현 방식들.