본문 바로가기
C.W.K.
Stream
Lesson 03 of 07 · published

KV 캐시는 대화하는 동안 자라

~16 min · llm-physics, kv-cache, gqa, mla, linear-attention, context

Level 0스펙 시트 훑는 사람
0 XP0/91 lessons0/19 achievements
0/100 XP to next level100 XP to go0% complete
"가중치는 학습에 대한 모델의 기억이야. KV 캐시는 이 대화에 대한 기억이고, 값은 토큰마다, 레이어마다 바이트로 치러."

캐시가 뭔지

어텐션은 새 토큰마다 앞의 모든 토큰을 보게 해. 앞 토큰들의 키와 값을 매 스텝 다시 계산하지 않으려고 모델은 그걸 보관해. 완전 어텐션이 있는 모든 레이어에 대해, 지금까지의 모든 토큰에 대해, 키 벡터 하나와 값 벡터 하나. 그 저장소가 KV 캐시야. 프리필 때 쓰이고, 디코드된 토큰마다 항목 하나씩 자라고, 모든 디코드 스텝이 통째로 읽어. 가중치와 나란히 토큰당 바이트 공식에 속하는 이유고, 대화가 길어지면 디코드가 느려지는 이유야.

토큰당 크기는 아키텍처에 대한 산수야. NVIDIA 추론 가이드가 기본형을 줘. "토큰당 KV 캐시 크기(바이트) = 2 × (레이어 수) × (헤드 수 × 헤드 차원) × 정밀도 바이트." 진짜 체크포인트엔 세 가지 보정이 중요해. 그룹 쿼리 어텐션은 키/값 헤드를 여러 쿼리 헤드가 공유하니까, 세는 건 키-값 헤드 수고 보통 훨씬 작아. 하이브리드 아키텍처, 그러니까 Qwen3.5의 선형 어텐션 대 완전 어텐션 3:1 배치는 완전 어텐션 레이어에만 KV 캐시를 두고, 선형 레이어엔 작은 고정 상태를 둬. 잠재 어텐션(DeepSeek의 MLA. GLM-5.3도 DSA 희소 인덱서 아래에 같은 걸 써)은 캐시하기 전에 키와 값을 저차원 잠재 벡터로 압축해서, 토큰당 바이트를 한 자릿수 이상 줄여.

config에서 읽은 것

체크포인트KV 캐시가 있는 레이어KV 헤드 × 헤드 차원토큰당 바이트 (bf16 캐시)32K 토큰에서1M 토큰에서증거
Llama-3.2-1B (순수 GQA)16 중 168 × 6432 KB1.07 GB34 GB (131K 윈도우 초과)config.json에서 나온 물리
Qwen3.5-0.8B / 2B (하이브리드)24 중 62 × 25612 KB0.40 GB12.9 GB물리
Qwen3.5-4B / 9B (하이브리드)32 중 84 × 25632 KB1.07 GB34.4 GB물리
Qwen3.5-27B (하이브리드)64 중 164 × 25664 KB2.15 GB68.7 GB물리
Qwen3.5-35B-A3B (하이브리드 MoE)40 중 102 × 25620 KB0.67 GB21.5 GB물리
GLM-5.3 (MLA + 희소 인덱서)78잠재 512 + rope 64~95 KB (유도)3.0 GB~100 GB카드와 config에서 유도한 물리
Kimi K3 (KDA + 게이트 MLA)93 중 24잠재~27.6 KB (유도) + 고정 ~232 MB0.9 GB29 GB물리, 유도
Qwen3.8-Flash-Next (DeltaNet + 희소)48 중 12~25 KB (유도) + 고정 ~115 MB0.8 GB25 GB물리, 유도
DeepSeek-V4.1-Flash (CSA2 + 슬라이딩 윈도우)890 B (모델 카드)0.03 GB0.93 GB벤더 (카드)

같은 일에 위아래로 두 자릿수 차이. 헤드 64개짜리 순수 어텐션 모델이면 이 표 꼭대기 위에 앉을 거야. GLM-5.3의 잠재 캐시는 그 모양의 비압축 캐시보다 대략 59배 작아. DeepSeek 설계는 토큰당 1킬로바이트 아래로 바닥에 앉아서, 컨텍스트 100만 토큰이 1기가바이트야. 가운데의 Qwen3.5 사다리가 이 집의 측정이 사는 곳이고, 그 하이브리드 배치가 가중치는 아홉 배 차이 나는데 9B와 1B가 32 KB 수치를 공유하는 이유야.

왜 기울기가 비율인지

디코드는 가중치 더하기 캐시를 읽어. 그러니 긴 컨텍스트로 인한 상대적 감속은 대략 가중치 대비 캐시의 크기야. 29K 토큰에서 Llama-3.2-1B는 가중치 0.70 GB 위에 캐시 0.94 GB를 지고, 바이트가 두 배 넘게 돼. Qwen3.5-9B는 같은 0.94 GB를 4.47 GB 위에 져서 5분의 1 더, 27B는 1.88 GB를 14.42 위에 져서 8분의 1 더. 다음 레슨이 정확히 그 곡선 셋을 재. 가져갈 요점은 이거야. 캐시의 비용은 컨텍스트 길이만의 속성이 아니야. KV 바이트 대 가중치 바이트의 비율이고, 그 비율은 아키텍처가 정하고 config 파일에서 읽어.

Code

kv_per_token.py — config.json에서 캐시의 토큰당 크기·python
#!/usr/bin/env python3
"""KV cache bytes per token from a checkpoint's config: 2 (K and V) × layers
that keep a cache × kv_heads × head_dim × bytes. Hybrids cache only on the
full-attention layers (full_attention_interval). Latent-attention models
(MLA) need the latent dims instead; this script handles GQA and hybrids."""
import json, sys
from pathlib import Path
from huggingface_hub import snapshot_download

repos = sys.argv[1:] or ["mlx-community/Llama-3.2-1B-Instruct-4bit", "mlx-community/Qwen3.5-9B-4bit",
                         "mlx-community/Qwen3.5-27B-4bit", "mlx-community/Qwen3.5-35B-A3B-4bit"]
BYTES = 2   # bf16 / fp16 cache

for repo in repos:
    cfg = json.loads((Path(snapshot_download(repo, local_files_only=True)) / "config.json").read_text())
    tc = cfg.get("text_config", cfg)
    L = tc["num_hidden_layers"]
    interval = tc.get("full_attention_interval")           # Qwen3.5: every 4th layer is full attention
    cached_layers = L // interval if interval else L
    kv_heads = tc["num_key_value_heads"]
    head_dim = tc.get("head_dim") or tc["hidden_size"] // tc["num_attention_heads"]
    per_token = 2 * cached_layers * kv_heads * head_dim * BYTES
    print(f"{repo.split('/')[-1]:28} cache on {cached_layers:3}/{L:3} layers, {kv_heads}×{head_dim} -> "
          f"{per_token/1024:5.1f} KB/token; 32K = {per_token*32768/1e9:.2f} GB, 1M = {per_token*1_048_576/1e9:.1f} GB")

# office, 2026-09-15:
# Llama-3.2-1B-Instruct-4bit   cache on  16/ 16 layers, 8×64  ->  32.0 KB/token; 32K = 1.07 GB, 1M = 34.4 GB
# Qwen3.5-9B-4bit              cache on   8/ 32 layers, 4×256 ->  32.0 KB/token
# Qwen3.5-27B-4bit             cache on  16/ 64 layers, 4×256 ->  64.0 KB/token
# Qwen3.5-35B-A3B-4bit         cache on  10/ 40 layers, 2×256 ->  20.0 KB/token

External links

Exercise

다운로드해 둔 모든 체크포인트에 kv_per_token.py를 돌려. 제일 많이 쓰는 모델에 대해 네 평소 컨텍스트에서의 캐시 바이트를 계산하고, 캐시 ÷ 가중치 비율을 카드에 추가해. 그다음 빈 컨텍스트에서 평소 컨텍스트로 갈 때의 디코드 감속을 예측해. 그 예측을 다음 레슨까지 보관해. 거기서 측정하니까.
Hint
감속 ≈ (가중치 + 캐시) ÷ 가중치. 작은 모델에선 고정 오버헤드로 희석돼. 비율이 0.1 아래면 긴 컨텍스트는 디코드에 거의 안 보이고 전부 첫 토큰까지 시간에 보여. 1 위면 대화가 모델보다 무거워진 거야.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.