로컬 모델 카드마다 128k 컨텍스트를 자랑한다. 데스크톱에서 그 숫자는 RAM이 읽지 않고 서명한 임대계약서다. 긴 문서를 어디까지 밀어넣을 수 있는지 결정하는 건 가중치가 아니다 — KV 캐시다. 그리고 그것은 남의 통화로 합의한 세금처럼 컨텍스트에 비례해 불어난다.
컨텍스트 윈도우는 켜고 끄는 기능이 아니다. 서버가 도는 매 초마다 내는 메모리 임대료다.
128k 컨텍스트는 실제로 RAM을 얼마나 쓰나?
산술은 공개돼 있고, 짧다. Transformer Inference Arithmetic이 설명하듯, KV 캐시는 레이어마다, KV 헤드마다, 토큰마다 두 개의 텐서 — key와 value — 를 보관한다:
cache_bytes = 2 × 레이어 수 × 컨텍스트 × KV 헤드 수 × head_dim × 요소당 바이트 Llama 3.1 8B를 보자: 레이어 32, KV 헤드 8, head_dim 128 — Hugging Face의 config.json에서 그대로. fp16(2바이트)에서 131,072 토큰이면:
2 × 32 × 131072 × 8 × 128 × 2 = 17,179,869,184 바이트 ≈ 16 GiB 같은 모델의 fp16 가중치는 약 16 GiB. 최대 컨텍스트에서 캐시는 자기가 속한 모델만큼 크다. “16 GiB에 들어가는 8B 모델”은 num_ctx를 128k로 올리는 순간 조용히 32 GiB짜리 문제가 된다.
캐시는 왜 창과 함께 자라나?
어텐션은 모든 토큰을 그 앞의 모든 토큰과 비교해야 하고, 추론은 자기회귀적이다 — 아무것도 다시 계산하지 않고, 모두 기억한다. 생성을 빠르게 만드는 트릭이 그것 하나뿐이다: 각 토큰의 key와 value를 한 번만 계산해 저장한다. 토큰당 저장 비용은 일정하므로 총량은 컨텍스트에 선형이다. 128k는 “더 큰 숫자”가 아니라 1k 창 캐시의 128배를 세션 내내 할당해 둔다는 뜻이다.
Grouped-query attention(GQA)는 모델 쪽 할인이다: KV 헤드가 적으면 캐시가 얇다. Qwen2.5 7B는 레이어 28에 KV 헤드가 겨우 4다(config는 여기). 같은 128k 창이라도 fp16 기준 약 7 GiB — 실질적인 완화이고, 이 모델들이 빈약한 장비에서도 유독 관대해 보이는 이유 하나다.
| 모델 (fp16) | 레이어 × KV 헤드 | KV 캐시 @ 128k | 가중치 | 캐시 대비 가중치 |
|---|---|---|---|---|
| Llama 3.1 8B | 32 × 8 | ~16 GiB | ~16 GiB | ~100% |
| Qwen2.5 7B | 28 × 4 | ~7 GiB | ~15 GiB | ~47% |
직접 계산해 보라 — 검증은 이게 전부다:
def kv_cache_gib(layers, kv_heads, head_dim, ctx, bytes_per=2):
return 2 * layers * ctx * kv_heads * head_dim * bytes_per / 2**30
print(kv_cache_gib(32, 8, 128, 131_072)) # Llama 3.1 8B -> 16.0
print(kv_cache_gib(28, 4, 128, 131_072)) # Qwen2.5 7B -> 7.0 마케팅 숫자가 빼놓는 것들
이 주장의 정직한 장부다 — 구부러지는 지점까지 포함해서:
- prefill은 두 번째 청구서다. 첫 응답 토큰이 나오기 전에 프롬프트 전체를 한꺼번에 처리한다. 100k 토큰 프롬프트면 첫 글자를 보기 전에 100k 토큰을 씹어야 한다. 3060에서는 밀리초가 아니라 분 단위다.
- sliding-window 모델은 이 계산을 깬다 — 당신에게 유리하게. Mistral류 구조는 레이어마다 고정 창으로 어텐션을 제한하므로 캐시가 무한히 자라지 않는다. 단순 공식은 이런 모델을 과대평가한다. 이 글은 풀 어텐션 transformer 기준이고, 그게 사람들이 돌리는 대부분이다.
- KV 양자화는 실제지만 부분적이다. llama.cpp는 KV를 q8_0으로 둘 수 있다 — 캐시가 대략 절반으로 줄고, 품질 리스크는 조금 있다. 16 GiB의 절반도 여전히 8 GiB다.
- 스펙란에는 KV 헤드 수를 거의 안 적는다. config.json을 뒤져야 한다 — 그 사실 자체가 그 숫자가 어떻게 읽히길 바랐는지를 말해준다.
이 모든 게 긴 컨텍스트를 무용지물로 만들지는 않는다. 창을 가득 채우는 게 “4절 읽어”라고 말하는 가장 비싼 방식이기에 RAG가 존재한다. 하지만 메모리 청구서 없이 “128k”만 인쇄하는 카드는 최고 속도만 팔고 연료 탱크는 끝내 언급하지 않는 차 판매다.
아무도 128,000 토큰을 읽지 않는다. 그래도 RAM은 전부 지불하고 있다.
처방은 지겹다: 워크로드가 실제 쓰는 만큼만 컨텍스트를 설정하라. 32k 창이면 128k 캐시의 4분의 1로 끝나고, 혼자 일하는 개발자가 실제로 보내는 프롬프트는 거의 다 커버된다. 이건 로컬 LLM의 RAM 산술과 같은 장부 규율이다 — 모델 크기는 예산의 절반일 뿐이고, 양자화 레벨은 가중치를 줄여도 캐시 계산은 그대로 둔다.
캐시가 컨텍스트의 진짜 가격이라면, 왜 모델 카드는 창을 팔면서 청구서는 팔지 않는가? 그리고 고해 타임: 끝까지 실제로 채운 가장 큰 컨텍스트가 뭐였는가 — 그 출력이 기가바이트 값은 했는가? 댓글은 열려 있다. 시리즈 다음 편은 진 쪽 편을 든다.
FAQ
128k 컨텍스트 윈도우는 RAM을 얼마나 쓰나?
Llama 3.1 8B fp16 기준, KV 캐시만 131,072 토큰에서 약 16 GiB — 모델 가중치와 맞먹는다. 공식: 2 × 레이어 × 컨텍스트 × KV 헤드 × head_dim × 바이트.
긴 컨텍스트가 더 많은 메모리를 쓰는 이유는?
캐시의 모든 토큰은 어텐션 레이어마다 key·value 텐서를 보관한다. 컨텍스트를 두 배로 하면 캐시도 두 배. 창을 채우지 않아도 할당 자체는 존재한다.
로컬 LLM에서 KV 캐시 메모리를 줄이려면?
실제 쓰는 만큼만 컨텍스트를 설정하고, grouped-query attention 모델(KV 헤드가 적은)을 고르고, 런타임이 지원하면 KV 캐시를 q8로 양자화하거나 sliding-window·하이브리드 구조를 쓴다.
— mrsaynothing
— mrsaynothing
의견은 출고 전 load-test를 거쳤습니다. 대부분.
다음 주장을 이메일로 받아보세요
포스트당 한 통. 동의해도, 해체해도.
이게 뭔가요?글이 마음에 드셨나요? 제 본업이 바로 이런 일입니다. 저를 고용하세요