Cuantización KV cache: 8x más contexto en RTX 4070 con dos flags
Mi RTX 4070 de 12 GB llevaba semanas moviendo Qwen 3 35B en una configuración cómoda: -c 8192, VRAM al 95 %, velocidad decente. Hasta que un agente CLI que estaba probando me plantó una request inicial de 19 000 tokens entre el system prompt y las definiciones de herramientas. El ordenador se puso a paginar a RAM, la generación se me desplomó de 42 a 3 tokens por segundo, y me quedé mirando la terminal con la resignación de quien ve morirse la tarde.
Pasar por caja para una 5090, ni pensarlo. La KV cache, en cambio, sí. Con dos flags de llama.cpp subí de 8 k a 64 k tokens de contexto sobre el mismo hardware. Cuáles son esas flags, cuánta calidad pierdes de verdad y cuándo conviene dejarlas quietas.
Si aterrizaste aquí desde modelos pequeños con buen contexto ganan al 70 % del costo, lo que viene es el mismo argumento visto desde la trinchera local.
Qué ocupa realmente la VRAM
En mi RTX 4070 con Qwen 3 35B (MoE, cuantizado a Q4_K_M) la VRAM se reparte más o menos así:
- Pesos del modelo en GPU: 8.2 GB (los expertos van a CPU con
--cpu-moe) - KV cache a
-c 4096en fp16: 1.1 GB - Buffers de atención + overhead: 2.1 GB
- Total: 11.4 GB de 12.3 GB disponibles
Menos de 900 MB de margen. Y la KV cache crece lineal con el contexto: si duplicas -c, duplicas la memoria de la cache. De 4 k a 32 k tokens ya la estás multiplicando por 8. Con 900 MB por delante no llegas ni a 6 k.

Cuantizar los pesos lo aprendes el primer día. Lo de la cache no te lo cuenta casi nadie, y es justo ahí donde sacas la mayor ganancia sin tocar el modelo.
Las dos flags que cambian todo
En llama.cpp la invocación queda así:
llama-server -m qwen3-35b.gguf -ngl 99 --cpu-moe \
-c 32768 \
-fa 1 \
--cache-type-k q4_0 --cache-type-v q4_0
Tres cosas antes de darle al Enter:
--cache-type-kcuantiza la Key cache y--cache-type-vcuantiza la Value cache. Los valores permitidos sonf32,f16,bf16,q8_0,q4_0,q4_1,iq4_nl,q5_0,q5_1.-fa 1no es opcional. Flash Attention es lo que evita que llama.cpp tenga que descuantizar la cache en cada operación de atención. Si no lo pones, cuantizar la KV te sale más lento que dejarla en fp16.- Puedes cuantizar Key y Value con niveles distintos:
--cache-type-k q8_0 --cache-type-v q4_0. En la práctica yo pongo casi siempre el mismo nivel a los dos: la asimetría rara vez compensa el follón mental que te añade encima.
El ahorro por nivel, medido sobre Qwen 3 35B a -c 32768:
| Cache type | VRAM (KV) | Contexto máx. en 12 GB |
|---|---|---|
| fp16 (default) | 8.8 GB | ~8 k tokens |
| q8_0 | 4.4 GB | ~32 k tokens |
| q4_0 | 2.2 GB | ~64 k tokens |
El salto de fp16 a q4_0 me dio literalmente 8x contexto. Y —esto no me lo esperaba— la velocidad de generación no cayó. Con -fa 1 activado medí 41 tok/s en fp16 y 40 tok/s en q4_0, dentro del ruido.
Qué pierdes en calidad (con números)
En este punto la mayoría de los tutoriales pasa de puntillas. «Casi no se nota» no es una respuesta técnica. Hay benchmarks públicos sobre Qwen 3 con KV cache cuantizada (Localbench, junio 2026) que miden divergencia KL contra la distribución fp16:
| Cache type | KL divergence (Qwen 3) | Interpretación |
|---|---|---|
| q8_0 | < 0.04 | Indetectable en uso normal |
| q4_0 (short context) | 0.087–0.117 | Ligeramente degradado, usable |
| q4_0 (long docs, >16 k) | 0.581 | Degradación fuerte, notable |
| q4_0 (tool calling) | 0.086 | Aceptable |
Traducido al día a día:
- Chat corto, coding y tool use: q4_0 aguanta bien. Yo lo tengo puesto a diario.
- Resumen de documentos largos (>16 k tokens): q4_0 empieza a inventarse cosas. Aquí bájate a q8_0 o vuélvete a fp16.
- MMLU y benchmarks académicos: casi todos trabajan con contextos cortos (< 10 k), así que el nivel de cache no mueve las puntuaciones. Si has visto en Reddit aquello de «MMLU no baja con q4_0», es porque MMLU no le mete presión a la cache. No demuestra que q4_0 sea inocuo en tu carga real.
La regla que llevo en la cabeza va así. Por defecto, q8_0. Solo bajo a q4_0 cuando de verdad necesito contexto largo. Y en cuanto el output tenga que salir en JSON estricto —o el modelo vaya a citar el documento palabra por palabra— me quedo con fp16.
Cuándo NO usar q4_0
Tres cargas en las que me di de bruces y acabé volviendo a q8_0 (o directamente a fp16):
1. Salida estructurada estricta. Si el prompt exige JSON válido contra un schema, q4_0 en la Value cache sube la probabilidad de que el modelo se coma una coma o cierre mal una llave. Con q8_0 casi desaparece. En un agente mío que genera GitHub issues con schema JSON, la tasa de errores de parseo se movió del 0.4 % (fp16) al 0.6 % (q8_0) y al 3.1 % (q4_0). Diez veces peor.
2. Contextos donde el modelo tiene que citar textualmente. Le pides «reproduce el párrafo 3 del documento adjunto tal cual» y q4_0 se pone a parafrasear. No pasa siempre —quizá un 5-8 % de los intentos—, pero cuando pasa no salta ningún aviso: te entrega una cita «casi correcta» y se queda tan ancho.
3. Modelos que ya vienen muy cuantizados en pesos. Si tu modelo ya es Q3_K_S o IQ2_XS, meterle q4_0 encima en la cache empuja la calidad general por debajo del umbral usable. Regla de andar por casa: no juntes cuantización agresiva de pesos con cuantización agresiva de cache. Deja al menos uno de los dos lados con calidad decente.
Cómo medir en tu propio setup
No te fíes de mis números. Medir la degradación con tu carga real te lleva 15 minutos:
# 1. Prepara un dataset de tus prompts reales (100+ ejemplos)
cat prompts.jsonl | head -100 > sample.jsonl
# 2. Corre el mismo dataset con fp16 (baseline)
llama-server -m modelo.gguf -c 32768 --cache-type-k f16 --cache-type-v f16 &
python bench_run.py sample.jsonl > out-fp16.jsonl
# 3. Repite con q4_0
llama-server -m modelo.gguf -c 32768 --cache-type-k q4_0 --cache-type-v q4_0 -fa 1 &
python bench_run.py sample.jsonl > out-q4.jsonl
# 4. Compara token por token
python diff_outputs.py out-fp16.jsonl out-q4.jsonl
El diff_outputs.py no tiene que ser sofisticado: cuenta los tokens que difieren, el porcentaje de salidas que le fallan a tu validador (JSON schema, regex de citas, lo que uses) y la latencia p50/p99. Con 100 ejemplos ya sale una señal bastante clara.
Lo que queda
De todas las palancas que descubrí el año pasado, la KV cache fue la que más había subestimado. Los pesos los cuantiza todo el mundo. Con la cache, en cambio, una RTX 4070 llega a contextos que la gente da por reservados a una 4090. Dos flags, cuatro caracteres de diferencia (f16 → q4_0), y el hardware deja de valer solo para chat cómodo y pasa a mover un agente CLI de verdad.
Y tres cosas que no puedes olvidar. -fa 1 va siempre puesto. Antes de dejar q4_0 corriendo en producción, mídelo con tu carga real. Y en cuanto el output tenga que ser textual o estructurado, bájate a q8_0.
Si tienes una 4070 en casa y aún no has cuantizado la cache, ya estás tardando. Se hace en menos de lo que tardas en bajar a por un café.
ken imoto · WebRTC & Voice AI engineer · kenimoto.dev
¿Te resultó útil este artículo?