KV Cache 양자화 (Quantization): 12GB VRAM에서 Qwen 35B의 컨텍스트를 8배로 확장하기
요약
llama.cpp를 사용하여 제한된 VRAM 환경에서 KV 캐시 양자화를 통해 컨텍스트 길이를 8배 확장하는 방법을 설명합니다. q8_0 양자화를 적용하면 모델 성능 저하를 최소화하면서도 메모리 점유율을 획기적으로 줄일 수 있습니다.
핵심 포인트
- KV 캐시 양자화(q8_0)를 통해 VRAM 사용량을 약 50% 절감 가능
- Qwen 35B 모델 기준, 컨텍스트 윈도우를 4096에서 32768로 8배 확장
- 양자화 적용 시 토큰 생성 속도 및 퍼플렉시티(품질) 저하는 미미함
- 메모리 부족 시 -ctk 및 -ctv 플래그를 활용한 데이터 타입 조절 권장
600 MiB의 여유 공간
이전 실행에서 사용했던 --cpu-moe 트릭 덕분에 제 RTX 4070은 Qwen 35B를 아주 훌륭하게 구동하고 있었습니다. 초당 토큰 수(tokens/sec)도 제가 원하던 수준이었습니다. VRAM 사용량은 12,281 MiB 중 11,714 MiB로, 95%가 차 있었습니다.
남은 공간은 600 MiB입니다. 본격적인 에이전트(agent)를 돌리기에는 부족한 양입니다.
제가 llama.cpp에 부여했던 컨텍스트 윈도우(context window)는 -c 4096이었습니다. 채팅용으로는 괜찮습니다. 하지만 Claude Code 스타일의 에이전트가 인사를 건네기도 전에 모델에게 12,000개의 도구 정의(tool definitions) 토큰을 전달하는 상황이라면 이야기가 달라집니다.
저는 -c 32768을 원했습니다. 이는 8배 점프하는 것입니다. 그리고 컨텍스트 길이와 함께 증가하는 메모리는 KV 캐시(KV cache)입니다. 600 MiB의 여유 공간이 있는 상태에서 캐시를 8배로 늘리면, llama.cpp는 워밍업(warm-up) 도중에 종료됩니다. 제가 직접 시도해봤기 때문에 알고 있는 사실입니다.
실제로 GPU에 상주하는 것
MoE 전문가(experts)들을 CPU로 오프로딩(offloading)한 후(이전 장의 트릭), GPU에는 두 가지가 남아 있습니다:
- 어텐션 가중치(attention weights) 및 비-MoE 파라미터(non-MoE parameters)
- KV 캐시(KV cache) — 모델이 이미 읽은 모든 토큰의 실행 기록
첫 번째는 고정되어 있습니다. 두 번째는 컨텍스트 길이와 선형적으로 증가합니다. 컨텍스트를 두 배로 늘리면 캐시도 두 배가 됩니다. -c 4096 → -c 32768은 단순히 8배 더 많은 토큰을 처리하기를 원하는 것이 아니라, 전체 시간 동안 VRAM에 8배 더 많은 캐시가 상주하기를 요구하는 것입니다.
공간이 없습니다. 따라서 캐시 자체를 줄여야 합니다.
두 가지 플래그
llama.cpp는 KV 캐시 데이터 타입(dtype)을 위해 두 가지 플래그를 사용합니다:
lama-server -m qwen35.gguf -ngl 99 --cpu-moe -c 32768 \
-ctk q8_0 -ctv q8_0
-ctk는 키 캐시(Key cache)이고, -ctv는 값 캐시(Value cache)입니다. 기본값은 f16 (16-bit)입니다. q8_0은 각각을 절반으로 줄입니다. 둘 다 절반으로 줄이면 KV 캐시 점유 공간(footprint)이 약 50% 감소합니다.
이렇게 확보된 VRAM은 모델 가중치(model weights)를 건드리지 않고 컨텍스트를 8배 더 크게 만들기 위해 정확히 필요한 것입니다.
측정
동일한 프롬프트, 동일한 시드(seed)로 두 번의 실행을 진행했습니다 — 하나는 f16 KV, 다른 하나는 q8_0 KV입니다:
| KV dtype | 할당 가능한 최대 -c | Tokens/sec (decode) | Perplexity delta |
|---|---|---|---|
| f16 (default) | 4096 | ~34.6 | baseline |
| q8_0 | 32768 | ~34.1 | 내 테스트에서는 무시할 만한 수준 |
속도 저하는 오차 범위 내에 있습니다. 컨텍스트는 8배 더 길어졌습니다. 품질 저하는 실행 시마다 발생하는 변동성(variance)과 구별할 수 없을 정도였습니다.
커뮤니티의 측정 결과도 일치합니다. 대칭형 (symmetric) q8_0 KV는 대부분의 모델에서 퍼플렉시티 (perplexity) 변화량이 0.1% 미만입니다. 더 강하게 — K와 V 모두에 q4_0를 적용하는 — 설정으로 가면 실제 성능 저하가 나타나기 시작하며, 만약 그 단계까지 간다면 비대칭 설정 (-ctk q4_0 -ctv q8_0)이 실용적인 Q4 구성이 됩니다.
저는 거기까지 가지 않았습니다. q8_0만으로도 제가 실행하는 에이전트 (agent) 워크로드에 충분한 여유 공간(headroom)이 있었습니다.
트레이드오프 (Trade-off) 요약
다음 두 줄로 기억해 두세요:
- f16 KV: 가장 빠르지만, 12GB 그래픽 카드에서는 컨텍스트가 4096 근처에 머뭅니다. 채팅용으로 적합합니다.
- q8_0 KV: 이전에 측정했던 것과 동일한 속도이며, 캐시 측면의 VRAM은 대략 절반으로 줄어들고, 컨텍스트는 32768까지 확장될 수 있습니다. 에이전트용으로 적합합니다.
결정은 이것이 전부입니다.
실제로 -c를 높여야 할 때
양자화된 (quantized) KV를 사용하더라도 더 큰 -c 값은 공짜가 아닙니다. llama.cpp는 전체 윈도우를 미리 예약합니다. 즉, 32k 컨텍스트는 서버가 부팅되는 순간 사용 여부와 상관없이 32k만큼의 캐시를 점유합니다.
그래서 저는 다음과 같이 단계별로 나눕니다:
- 채팅, 단발성 생성 (single-shot generation):
-c 4096. KV 양자화를 굳이 할 필요 없습니다. - 가벼운 에이전트 (Claude Code 스타일):
-c 8192면 제가 겪은 거의 모든 케이스를 커버합니다. 여유 공간이 있다면 양자화가 필요 없습니다. - 무거운 에이전트 (Qwen Code CLI와 같은 멀티 툴 CLI):
-ctk q8_0 -ctv q8_0와 함께-c 32768을 사용하세요. 시스템 프롬프트와 도구 스키마 (tool schemas)가 로드되면 첫 번째 에이전트 턴만으로도 19,000 토큰에 도달할 수 있습니다. 8192 설정으로는 실행 도중 충돌(crash)이 발생할 것입니다. - 긴 문서 읽기:
-c를 실제 문서 크기에 맞춰서만 높이세요. 6k 문서에 대해 32k를 미리 할당하지 마세요.
"에이전트는 큰 컨텍스트와 양자화된 KV가 필요하다"라는 일반화는 너무 거칠게 분류된 것입니다. 적절한 수치는 CLI의 도구 정의(tool definition) 무게에 전적으로 달려 있습니다. 동일한 모델을 사용하는 두 에이전트 프레임워크라도 완전히 다른 -c 값을 요구할 수 있습니다.
두 줄 요약
12GB 그래픽 카드를 가지고 있고 Qwen 35B로 에이전트를 실행하고 싶다면:
- MoE 전문가(experts)를 CPU로 오프로드(Offload)합니다 (이전 장).
- q8_0 KV 캐시(KV cache)를 활성화합니다 (이번 장).
이렇게 총 세 개의 플래그를 사용합니다: --cpu-moe -ctk q8_0 -ctv q8_0. 이를 통해 기존과 거의 동일한 초당 토큰 생성량(tokens/sec)을 유지하면서도, 사용 가능한 컨텍스트(context)를 8배 더 길게 확보할 수 있으며, 제가 테스트한 워크로드(workloads)에서는 측정 가능한 품질 저하가 나타나지 않았습니다.
저는 여기까지 도달하기 위해 4090이 필요할 것이라고 가정하며 일주일을 보냈습니다. 하지만 필요하지 않았습니다.
만약 여러분이 이 설정에 Claude Code 스타일의 에이전트(agent)를 연결한다면 — 도구 스키마(tool schemas)와 시스템 프롬프트(system prompts)가 사용자가 아무것도 입력하기 전에 이미 12k 토큰을 소비하는 환경이라면 — 크기 결정(sizing decisions) 및 CLAUDE.md 강화(hardening)에 관한 내용은 여기서 다룹니다:
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기