KV cache: la memoria de trabajo que sostiene la inferencia LLM

TL;DR

El KV cache es la memoria de trabajo que un modelo de lenguaje mantiene durante una conversación. Sin él, cada token nuevo obligaría a recalcular toda la conversación desde el principio, con un coste cuadrático en la longitud del texto. Con él, el coste es lineal pero a cambio el cache vive en VRAM y crece con cada token. En la práctica, no es el modelo lo que limita cuánto contexto puedes servir: es el KV cache. Para una RTX 4090 con Llama 3 8B, cabe el modelo en 16 GB y queda apenas espacio para ~64 K tokens de cache totales (sumando todas las sesiones simultáneas). Entender este número es la diferencia entre prometerle a un cliente “contexto de 128 K” y entregárselo.

Estás aquí: Deploy

Este post abre la serie de fundamentos de inferencia LLM. Dentro del pipeline LLMOps de seis etapas que articula todo el sistema, el KV cache vive en la etapa Deploy: es la pieza que dicta cuánto tráfico cabe en tu motor de inferencia y, por tanto, cuánta plataforma puedes ofrecer encima.

Estás aquí: DEPLOY · KV cache como cuello de botella de VRAM1 · Data2 · Tune3 · Eval4 · Deploy5 · Observe6 · Retrain

La analogía: el orador con amnesia

Imagina que asistes a una conferencia técnica de dos horas. El ponente, cada vez que va a decir una frase nueva, rebobina mentalmente toda la charla desde el inicio, recompone el hilo, y solo entonces continúa. Su próxima frase requiere rememorar la anterior; la siguiente, las dos anteriores; al cabo de una hora, cada palabra nueva le cuesta una hora de recapitulación. Una conferencia así sería materialmente imposible.

Ahora imagina al mismo ponente con un cuaderno donde apunta, mientras habla, las dos o tres ideas clave de cada frase: sujeto, objeto, vínculo con lo anterior. Antes de cada frase nueva, ojea el cuaderno y sigue. Su próxima palabra sólo cuesta una ojeada al cuaderno, no rebobinar la charla entera.

Ese cuaderno, en un transformer, se llama KV cache. Sin él, los modelos de lenguaje conversacionales serían inviables. Con él, son productos comerciales. Pero el cuaderno pesa: y entender cuánto, dónde y por qué, es lo que separa una infraestructura de inferencia que funciona de una que se cae al tercer cliente concurrente.

El mecanismo en sí (en cristiano)

Un transformer genera texto un token cada vez. Para decidir el siguiente token, el modelo aplica un mecanismo llamado atención sobre todos los tokens previos: pregunta “¿qué partes del contexto anterior son relevantes para predecir lo que viene ahora?”.

Internamente, cada token de entrada se proyecta a tres vectores:

  • Q (Query): “qué estoy buscando”
  • K (Key): “qué oferta este token”
  • V (Value): “qué información lleva este token”

La atención del token actual contra el contexto se calcula multiplicando su Q contra las K de todos los tokens previos, normalizando con softmax, y ponderando las V correspondientes. Resultado: una representación contextualizada del token actual.

Cálculo de atención para el token NQ (token N)"qué busco"K (tokens 1..N)del cacheV (tokens 1..N)del cache

Q·Kᵀ → softmax× V

representación del token N

Aquí está la clave: para predecir el token N, sólo necesito Q nuevo (el del token N) y K, V de todos los tokens anteriores. Las K y V de los tokens 1..N-1 no han cambiado desde la iteración anterior. Recalcularlas sería tirar trabajo.

El KV cache es exactamente eso: la memoria que guarda K y V de cada token ya procesado, en cada capa del modelo, para no recalcularlos.

Por qué existe: el coste cuadrático sin él

Generar un texto de N tokens implica N pasos. En el paso i, se calcula la atención sobre i tokens anteriores. Sin cache, en cada paso recomputas las K, V de los i-1 tokens anteriores más las del nuevo. La cuenta total de cómputos de atención crece como:

$$\sum_{i=1}^{N} i = \frac{N(N+1)}{2} \approx \frac{N^2}{2}$$

Con KV cache, sólo procesas el token nuevo en cada paso: coste lineal en N.

Cómputo acumulado para generar N tokens(escala esquemática — los datos exactos están en la tabla)

tokens generados (N)operaciones de atención

01K2K3K4K

con KV cacheO(N) — lineal

sin KV cacheO(N²) — cuadrático

Los números concretos son demoledores:

Tokens generadosSin KV cache (operaciones)Con KV cacheRatio
1288 25612864×
1 024524 8001 024512×
4 0968 390 6564 0962 048×
32 768536 887 29632 76816 384×

A los 32 K tokens, el cache te ahorra cuatro órdenes de magnitud de cómputo. No es una optimización: es lo que hace que la inferencia conversacional sea posible.

El precio: cuánto pesa la mochila

El KV cache se paga en VRAM. La fórmula, por secuencia, es:

KV_size  =  2  ·  n_layers  ·  n_kv_heads  ·  head_dim  ·  context_len  ·  bytes_per_param
            ↑
        K y V

Por token (sin el context_len), es una constante propia del modelo. Veamos números reales:

Modelon_layersn_kv_headshead_dimBytes/token (BF16)GB a 8 K ctxGB a 32 KGB a 128 K
Llama 3 8B (MHA hipotético)3232128524 2884.0016.0064.00
Llama 3 8B (GQA real)328128131 0721.004.0016.00
Llama 3 70B (GQA)808128327 6802.5010.0040.00
Qwen3 8B (GQA)368128147 4561.124.5018.00
Mistral 7B (GQA)328128131 0721.004.0016.00

Dos lecturas inmediatas:

  1. Sin GQA, no hay 128 K que valga. Un Llama 3 8B con atención multi-head clásica necesitaría 64 GB sólo de KV cache para una única secuencia con 128 K tokens. Es decir, no cabe en ninguna GPU consumer. Por eso Meta, Mistral y compañía adoptaron Grouped Query Attention.
  2. El KV cache puede ser mayor que el modelo. Llama 3 8B BF16 ocupa ~16 GB. Con 128 K de contexto, su cache son otros 16 GB. Una sola sesión empata al modelo en VRAM.
KV cache (GB) vs longitud de contexto (1 secuencia, BF16)

010203040 GB

08K32K64K128K

≈ VRAM libre tras cargar 8B en una 4090

Llama 3 8BQwen3 8BLlama 3 70B

La línea roja punteada marca la VRAM realista disponible en una RTX 4090 después de cargar el modelo. Cualquier modelo cuya curva cruza esa línea no podrá servir ese contexto sin estrategias adicionales (cuantización del cache, offload, particionado).

La inferencia es memory-bound, no compute-bound

Hay un equívoco común: pensar que “GPU rápida = inferencia rápida”. En el régimen donde realmente operan los servicios de inferencia con KV cache, lo que se mide es el ancho de banda de memoria. Cada token nuevo exige leer las K y V de todos los tokens anteriores desde HBM. El cómputo es modesto; el movimiento de datos, masivo.

Por eso, una H100 SXM (3.35 TB/s de HBM3) puede ser 2–3× más rápida que una A100 (1.55–2 TB/s) sin que la frecuencia ni el número de cores expliquen del todo la diferencia. Lo explica el ancho de banda.

Y por eso, también, las ofertas de “GPU baratas con mucha VRAM pero HBM lenta” (algunas variantes con GDDR6 o LPDDR5) decepcionan en inferencia con contextos largos: tienen sitio para guardar el cache pero les cuesta una eternidad releerlo.

Trucos para que el cuaderno sea más fino

Tres técnicas, en orden cronológico, han ido aplanando el tamaño del KV cache:

Multi-Head Attention (MHA). El planteamiento original del transformer (Vaswani et al., 2017). Cada cabeza de atención tiene su propia K y V. Caro en cache pero teóricamente máximo en expresividad. Es lo que tenían los modelos hasta ~2023.

Multi-Query Attention (MQA). Una sola K y V compartida por todas las cabezas. Reduce el cache n_heads veces. Funciona razonablemente pero degrada calidad de generación en algunos benchmarks.

Grouped Query Attention (GQA). El término medio que ha ganado. Las cabezas se agrupan: en Llama 3 8B, 32 cabezas de query comparten K, V en grupos de 4 → 8 grupos de KV. Reduce el cache 4× respecto a MHA con casi idéntica calidad. Es el estándar de facto desde 2024.

Multi-Head Latent Attention (MLA). La innovación de DeepSeek-V2/V3: en vez de almacenar K, V por cabeza, comprime el estado en un vector latente más pequeño y proyecta a K, V en el momento. El cache puede llegar a 70 bytes/token, dos órdenes de magnitud menos que GQA. Es la razón principal por la que DeepSeek-V3 (671 B parámetros, 37 B activos) es servible en infraestructura abordable.

KB de cache por token (Llama 3 8B equivalente, BF16)MHA (32 KV heads)512 KBGQA (8 KV heads)128 KBMQA (1 KV head)16 KBMLA (DeepSeek-V3)~0.5 KB (real V3)

Nota: la barra de MLA es ilustrativa con valores típicos publicados por DeepSeek; la implementación exacta depende del tamaño latente. Lo importante es el orden de magnitud.

A esto se suma una cuarta técnica ortogonal: cuantizar el cache a FP8, INT8 o incluso INT4. vLLM y TensorRT-LLM ya lo soportan en producción. Pasar de BF16 (2 bytes) a FP8 (1 byte) divide el cache por dos con coste pequeño en calidad. Pasar a INT4, por cuatro, con coste algo mayor.

El siguiente dragón: la fragmentación

Hasta aquí hemos hablado del cache como si fuera un bloque contiguo. En la práctica, un servidor de inferencia atiende decenas de sesiones simultáneas, cada una con su propio cache que crece a un ritmo distinto. La asignación naïve —reservar el máximo posible por sesión— desperdicia entre el 60 % y el 80 % de la VRAM según el paper original de PagedAttention.

Asignación naïve (contigua)PagedAttention (bloques)

sesión A

sesión B

sesión C

sesión D

→ ~70 % de VRAM reservada y vacía

<rect x="0" y="22" width="30" height="20" class="used blk"/>
<rect x="30" y="22" width="30" height="20" class="used blk"/>
<rect x="60" y="22" width="30" height="20" class="used blk"/>
<rect x="90" y="22" width="30" height="20" class="used blk"/>
<rect x="120" y="22" width="30" height="20" class="used blk"/>
<rect x="150" y="22" width="30" height="20" class="used blk"/>
<rect x="180" y="22" width="30" height="20" class="free blk"/>
<rect x="210" y="22" width="30" height="20" class="free blk"/>
<rect x="0" y="44" width="30" height="20" class="used blk"/>
<rect x="30" y="44" width="30" height="20" class="used blk"/>
<rect x="60" y="44" width="30" height="20" class="used blk"/>
<rect x="90" y="44" width="30" height="20" class="free blk"/>
<rect x="120" y="44" width="30" height="20" class="free blk"/>
<rect x="150" y="44" width="30" height="20" class="free blk"/>
<rect x="180" y="44" width="30" height="20" class="free blk"/>
<rect x="210" y="44" width="30" height="20" class="free blk"/>
</g>
→ < 4 % desperdicio (paper vLLM)

PagedAttention —la idea de Kwon et al. (2023) que dio origen a vLLM— resuelve esto pidiendo prestada una técnica de los sistemas operativos: dividir la VRAM en bloques pequeños (típicamente de 16 tokens) y mantener una tabla de páginas lógicas → físicas por sesión. Una sesión ya no reserva un bloque contiguo enorme: crece un bloque cada vez, y los bloques pueden estar dispersos por la VRAM. Resultado: ocupación efectiva del 90 % en lugar del 30 %, y por tanto 2–4× más throughput agregado en el mismo hardware.

PagedAttention merece artículo propio. Lo dejo apuntado para el siguiente.

Aplicado a hardware on-premise genérico

Bajemos a casos concretos.

Caso 1 — RTX 4090 (24 GB, Ada Lovelace)

Configuración típica con Qwen3-8B BF16:

Modelo BF16:          ~16 GB
Activations + overhead: ~2 GB
VRAM disponible para KV cache: ~6 GB (con margen)

Con 144 KB/token (Qwen3-8B GQA), eso son ~43 K tokens totales de cache distribuidos entre todas las sesiones simultáneas. En la práctica:

ConcurrenciaContexto máximo por sesión
132 768
48 192
84 096
162 048

Si necesitas anunciar “soportamos 32 K de contexto” con concurrencia 4+, hay que cuantizar el cache (FP8 baja a 72 KB/token, duplica capacidad) o subir el modelo de gama (un 4B con GQA y cache cuantizado holgaría).

Con tensor parallel = 4 y Llama 3 70B BF16:

Modelo BF16:                  ~140 GB (35 GB/GPU)
Overhead vLLM por GPU:         ~2 GB
VRAM libre para KV por GPU:   ~43 GB → ~172 GB agregados

Con 320 KB/token (Llama 3 70B GQA), eso son ~537 K tokens totales de cache. Margen amplio para contextos largos con concurrencia alta:

ConcurrenciaContexto máximo por sesión
4134 000
1633 500
648 375

Para DeepSeek-V3 671 B con MLA: la economía cambia radicalmente porque el cache es ~100× más fino. Lo que limita ya no es el cache sino la VRAM del propio modelo (cuantizado FP8 son ~335 GB → cabe en 4×H100 con margen para KV cache).

Implicaciones operativas

Tres observaciones que repetimos en cada consultoría:

Primero, el contexto máximo anunciado por un modelo no es el que puedes servir en tu hardware. Llama 3 8B “soporta” 128 K, pero en una 4090 con 4 sesiones simultáneas tu contexto efectivo son ~8 K. Es trivial comprobarlo antes de prometérselo al cliente.

Segundo, cuantizar el KV cache es de las optimizaciones con mejor relación coste/beneficio en el contexto ENS. No toca los pesos, no afecta a la reproducibilidad de auditoría, y duplica capacidad. vLLM lo soporta vía --kv-cache-dtype fp8.

Tercero, si los SLA dictan contextos largos con muchos usuarios concurrentes, GQA es necesario pero no suficiente. A medio plazo, hay que mirar modelos con MLA o variantes de attention con compresión.

Lo que no hemos cubierto (próximos artículos)

  • PagedAttention y su implementación en vLLM: bloques, tabla de páginas, evicción.
  • Prefix caching: cuando varias peticiones comparten el system prompt, no hace falta recomputar las K, V de la parte común.
  • Speculative decoding y su interacción con el cache.
  • Cache offloading: mover bloques fríos a RAM o a NVMe, técnica clave para contextos > 1 M.

Ver también

Referencias

  • Vaswani et al., Attention Is All You Need (NeurIPS 2017) — paper fundacional del transformer.
  • Ainslie et al., GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints (EMNLP 2023).
  • Kwon et al., Efficient Memory Management for Large Language Model Serving with PagedAttention (SOSP 2023) — paper original de vLLM.
  • DeepSeek-AI, DeepSeek-V2 Technical Report (2024) — introducción de Multi-Head Latent Attention.
  • Documentación oficial de vLLM: https://docs.vllm.ai/.
  • Llama 3 model card (Meta): especificaciones GQA, n_layers, n_kv_heads.