Guías de IA · 12 min de lectura

¿Qué es una KV cache? Cómo los LLM reutilizan estados de atención

Descubre qué es una KV cache, cómo acelera la decodificación de LLM, crece con el contexto y se relaciona con los costes de API.

Ilustración de una secuencia de fichas de tokens de colores que alimentan bloques de caché apilados y producen un flujo de salida luminoso más rápido

Una KV cache (caché Key-Value) es una optimización de inferencia en la que un modelo de lenguaje grande almacena, para cada capa, los tensores de atención Key y Value calculados para los tokens ya procesados. Durante la decodificación autorregresiva, el modelo reutiliza esos estados almacenados en lugar de volver a calcular todos los tokens anteriores en cada paso, lo que reduce los cálculos redundantes a medida que crecen las secuencias.

Tanto si llamas a API alojadas como si ejecutas modelos de pesos abiertos en tus propias GPU, la KV cache ocupa un lugar central en la latencia, la presión sobre la memoria y el rendimiento. Entender cómo funciona explica por qué el tiempo hasta el primer token se percibe de forma distinta a la velocidad de streaming, por qué los contextos largos consumen tanta VRAM y por qué el almacenamiento en caché de prompts entre solicitudes de API exige coincidencias exactas de prefijo.

¿Cómo funciona la atención sin una KV cache?

Sin una KV cache, un Transformer autorregresivo debe volver a calcular desde cero las proyecciones de atención de toda la secuencia cada vez que genera un nuevo token en un modelo de IA.

En cada capa del decodificador Transformer, la autoatención proyecta la representación de cada token de entrada en tres vectores:

  • Query ($Q$): representa la posición actual que busca contexto relevante.
  • Key ($K$): representa lo que ofrece cada posición para que las Query encuentren coincidencias.
  • Value ($V$): contiene la información que se agrega una vez calculados los pesos de atención a partir de los productos escalares Query-Key.

Como los modelos de lenguaje que solo usan decodificador generan texto token a token, para generar el token $t+1$ solo es necesario que el vector Query del token más reciente atienda a las Key y Value de los tokens $1$ a $t$. Si el entorno de ejecución descarta esas Key y Value después de emitir el token $t$, el paso $t+1$ debe volver a pasar cada token del prefijo por todas las capas solo para recrear exactamente los mismos tensores $K$ y $V$. A medida que se alargan el prompt y la salida, ese trabajo repetido crece de forma cuadrática a lo largo de toda la generación.

¿Por qué la atención causal permite usar una KV cache?

El enmascaramiento causal garantiza que los tensores Key y Value de un token dependan únicamente de los tokens anteriores, nunca de los generados después.

En un decodificador causal (de izquierda a derecha), el token $i$ no puede atender a ninguna posición $j > i$. Como la información solo avanza por la secuencia:

  1. La representación del token $i$ en la capa $l$ queda completamente fijada una vez procesados los tokens $1 \dots i$.
  2. Añadir el token $i+1$ al final de la secuencia no modifica los estados ocultos, las Key ni las Value de las posiciones $1 \dots i$ en ninguna capa.
  3. El entorno de ejecución puede añadir los vectores recién calculados $K_{i+1}$ y $V_{i+1}$ a un búfer por capa, la KV cache, y pasar solo el último token a la siguiente etapa de decodificación.

Como documentan la guía de KV cache de Hugging Face Transformers y la descripción de la arquitectura de caché de Transformers, la caché convierte cada paso de decodificación de un recorrido de toda la secuencia en una actualización incremental de un solo token que lee las Key y Value anteriores de la memoria.

¿En qué se diferencia el uso de la KV cache en Prefill y Decode?

La inferencia de un LLM se divide en dos fases distintas, Prefill y Decode, que interactúan con la KV cache de formas fundamentalmente diferentes y encuentran distintos cuellos de botella de hardware.

Fase de inferencia Qué ocurre con la KV cache Principal cuello de botella de hardware Métrica visible para el usuario
Prefill (procesamiento del prompt) Calcula $K$ y $V$ en paralelo para todos los tokens del prompt y llena la caché inicial en todas las capas. Limitado por el cálculo (grandes multiplicaciones de matrices en todo el prompt). Tiempo hasta el primer token (TTFT).
Decode (generación de tokens) Calcula $Q$, $K$ y $V$ para un token nuevo cada vez, añade la nueva entrada $K$/$V$ y lee todo el historial almacenado. Limitado por el ancho de banda de memoria (carga de los pesos del modelo y de los estados KV crecientes en cada paso). Latencia entre tokens / velocidad de streaming de salida.

Como explica NVIDIA en su artículo técnico sobre Prefill, Decode y KV cache, la fase Prefill procesa todos los tokens de entrada simultáneamente. Como el prompt completo se conoce de antemano, las GPU pueden agrupar las proyecciones en operaciones de matrices densas, saturar las unidades de cálculo y llenar la KV cache en una sola pasada. Los prompts largos, como los contextos de varios documentos en sistemas de generación aumentada por recuperación (RAG), aumentan directamente el trabajo de Prefill y el tiempo hasta el primer token.

Una vez emitido el primer token de salida, la inferencia entra en la fase Decode. Cada paso procesa solo un token nuevo por secuencia, añade sus vectores Key y Value a la caché y lee todos los tensores $K$ y $V$ almacenados previamente en la memoria de alto ancho de banda (HBM) de la GPU para calcular la atención. Como la intensidad aritmética por paso es baja mientras las lecturas de memoria crecen con la longitud de secuencia y el tamaño del lote, la decodificación pronto queda limitada por el ancho de banda de memoria y la VRAM disponible, en lugar de por los FLOPs de cálculo bruto.

¿Cuánta memoria usa una KV cache?

El espacio de memoria de una KV cache crece linealmente con la longitud de secuencia, el tamaño del lote, el número de capas y el número de cabezas de atención Key-Value.

A partir de las dimensiones de los tensores utilizados en las cachés Transformer estándar, donde cada capa almacena un tensor Key y uno Value con dimensiones de lote, cabezas KV, longitud de secuencia y dimensión de cabeza, el tamaño de caché por secuencia puede expresarse simbólicamente así:

bytes ≈ 2 × layers × kv_heads × head_dim × seq_len × bytes_per_element

Cada término de la fórmula representa un factor que los arquitectos de modelos e ingenieros de inferencia ajustan para controlar la memoria:

  • 2: corresponde al almacenamiento tanto de los tensores Key (K) como de los Value (V).
  • layers: número total de bloques decodificadores Transformer del modelo.
  • kv_heads y head_dim: número de cabezas de atención Key/Value por capa y dimensión de cada cabeza.
  • seq_len: total de tokens almacenados (tokens del prompt más los generados hasta el momento).
  • bytes_per_element: precisión numérica de los tensores almacenados (por ejemplo, 2 bytes para FP16/BF16, 1 byte para formatos de 8 bits o fracciones menores con cuantización de KV cache de 4 bits como NVFP4).

Reducir kv_heads: MHA, MQA y GQA

En la Multi-Head Attention clásica (MHA), cada cabeza Query tiene su propia cabeza Key y su propia cabeza Value (kv_heads = query_heads). Al crecer las ventanas de contexto y los tamaños de lote, almacenar proyecciones K y V separadas para cada cabeza se convirtió en un importante cuello de botella de memoria.

Dos cambios de arquitectura reducen directamente kv_heads en la fórmula anterior:

Además de GQA, los motores de inferencia reducen bytes_per_element al cuantizar los tensores K y V almacenados en formatos de menor precisión, lo que permite alojar contextos más largos o lotes simultáneos más grandes en la memoria de la GPU.

¿Cómo gestionan los motores de servicio la memoria de la KV cache a gran escala?

Los motores de servicio en producción gestionan la memoria de la KV cache dividiendo los tensores contiguos en bloques de tamaño fijo para que las GPU puedan alojar más solicitudes simultáneas sin desperdiciar VRAM.

En implementaciones ingenuas, un entorno de servicio reserva de antemano un bloque contiguo de memoria de GPU con el tamaño de la longitud máxima posible de cada solicitud. Como las longitudes reales de salida no se conocen por adelantado, esta reserva provoca una grave fragmentación interna y externa de la memoria: quedan espacios reservados sin usar mientras se bloquea la ejecución de nuevas solicitudes.

Kwon et al. abordaron este cuello de botella con PagedAttention en Efficient Memory Management for Large Language Model Serving with PagedAttention (arXiv:2309.06180), el mecanismo central de vLLM. Inspirado en la memoria virtual y la paginación de los sistemas operativos:

  • Bloques no contiguos de tamaño fijo: PagedAttention divide la KV cache de cada solicitud en bloques de tamaño fijo que contienen las Key y Value de un número determinado de tokens, asignados mediante una tabla de bloques lógicos a físicos.
  • Asignación bajo demanda: los nuevos bloques físicos solo se asignan a medida que se generan tokens, lo que elimina prácticamente la fragmentación interna.
  • Uso compartido de prefijos por bloques: cuando varias solicitudes comparten un prefijo de prompt idéntico, como un prompt de sistema o ejemplos few-shot comunes, sus tablas de bloques lógicos pueden apuntar a los mismos bloques KV físicos en memoria, utilizando copia en escritura solo cuando las secuencias divergen.

En la evaluación del artículo de vLLM (arXiv:2309.06180), esta gestión paginada de KV logró un rendimiento 2–4× mayor que FasterTransformer y Orca con una latencia comparable, al alojar lotes considerablemente mayores en la misma memoria de GPU. Las plataformas de servicio modernas, incluidos vLLM y el sistema de reutilización de KV cache de NVIDIA TensorRT-LLM, utilizan gestión de KV basada en bloques para reutilizar prefijos compartidos y maximizar la concurrencia.

¿Qué diferencia hay entre una KV cache y la caché de prompts?

Una KV cache es el mecanismo de inferencia subyacente por solicitud que reutiliza estados de atención entre pasos de decodificación, mientras que la caché de prompts es una función de servicio y API entre solicitudes que conserva y reutiliza esos bloques KV entre llamadas distintas con un prefijo idéntico.

Como los desarrolladores suelen encontrar ambos términos al optimizar la latencia y las facturas, conviene distinguir lo que ocurre dentro de una llamada de generación de lo que ocurre entre varias solicitudes de API:

Dimensión KV cache (decodificación dentro de una solicitud) Caché de prompts (reutilización de prefijos entre solicitudes)
Alcance Una solicitud, a lo largo de los pasos de generación de tokens. Varias solicitudes durante un periodo breve de retención.
Qué evita Recalcular tokens anteriores dentro de la misma respuesta al decodificar cada token nuevo. Volver a ejecutar Prefill sobre prefijos estáticos de prompt repetidos entre llamadas distintas.
Ciclo de vida Se crea durante Prefill, se amplía durante Decode y se libera o expulsa al completar la solicitud. Se conserva en almacenamiento por bloques (memoria de GPU o del host), con una política de expulsión y TTL para futuras llamadas.
Visibilidad en la factura Incluida en la inferencia estándar; no se factura como partida independiente. Los proveedores de API suelen ofrecer precios reducidos para tokens de entrada almacenados y un TTFT menor.

La atención causal impone una regla estricta a la caché de prompts: el prefijo debe coincidir exactamente desde el primer token. Como los tensores Key y Value de cada token dependen de la secuencia exacta de tokens que lo precede, cambiar incluso una sola marca de tiempo, un ID de usuario o una instrucción al principio de un prompt invalida los estados $K$ y $V$ almacenados de todos los tokens posteriores.

Para conocer las reglas de retención, los umbrales mínimos de tokens y los descuentos de cada proveedor, consulta nuestra guía completa sobre qué es la caché de prompts y cómo la cobran los proveedores.

Recomendaciones prácticas para desarrolladores y equipos

Entender el comportamiento de la KV cache se traduce directamente en una mejor arquitectura de prompts, una latencia más estable y menos gasto en infraestructura:

  1. Mantén el contenido estático al principio del prompt: coloca las instrucciones de sistema, los esquemas de herramientas y los documentos de referencia compartidos antes de cualquier entrada dinámica del usuario o metadato específico de la solicitud. Un prefijo idéntico permite que tanto las API alojadas como los motores locales, como vLLM o TensorRT-LLM, reutilicen bloques KV almacenados en lugar de ejecutar pasadas Prefill redundantes.
  2. Observa cómo la longitud del contexto afecta a la concurrencia en GPU autoalojadas: como el tamaño de la KV cache crece linealmente con seq_len, duplicar la longitud media del contexto reduce a la mitad el número de solicitudes simultáneas que caben en la memoria de la GPU. Al elegir modelos de pesos abiertos para autoalojamiento, prioriza arquitecturas con Grouped-Query Attention (GQA) y entornos que admitan asignación paginada y cuantización de KV.
  3. Reduce el contexto innecesario incluso con ventanas de contexto enormes: introducir historiales enormes en cada turno aumenta la latencia de Prefill, la presión sobre la memoria KV y las facturas de tokens. Combina la caché de prefijos con recuperación selectiva y resúmenes periódicos, estrategias centrales de nuestra guía sobre cómo reducir los costes de API de IA y nuestra comparativa de precios de API de IA.

Preguntas frecuentes

¿Una KV cache cambia la calidad de salida del modelo?

Una KV cache estándar mantenida en la precisión de inferencia nativa del modelo, como FP16 o BF16, equivale matemáticamente a recalcular el prefijo en cada paso, porque el enmascaramiento causal impide que los tokens futuros modifiquen los vectores Key y Value anteriores. Solo aparecen compromisos de calidad si se aplica cuantización con pérdida de la KV cache, como compresión de 8 o 4 bits, o se expulsan tokens antiguos para ahorrar memoria.

¿Por qué una KV cache no puede reutilizar tokens del medio de un prompt si cambió el principio?

En un Transformer causal, la representación de cada token incorpora atención sobre todos los tokens anteriores de la secuencia. Si cambian los primeros diez tokens de un prompt, también cambiarán los estados ocultos y, por tanto, las proyecciones Key y Value desde el token once en adelante. Como esos estados dependen del prefijo exacto que los precede, los motores de servicio solo pueden reutilizar bloques KV almacenados entre solicitudes cuando el prefijo coincide desde el principio.

¿La KV cache contiene los pesos del modelo?

No. Los pesos del modelo son parámetros estáticos aprendidos durante el entrenamiento y compartidos por todas las solicitudes atendidas por la GPU. La KV cache contiene tensores de activación dinámicos específicos de cada solicitud (las proyecciones Key y Value de los tokens presentes en el contexto). Durante la inferencia, la memoria total de la GPU debe alojar los pesos estáticos del modelo, las activaciones temporales del entorno y las KV cache dinámicas de todas las solicitudes activas del lote.

¿Por qué se ralentiza la generación de más tokens de salida cuando hay mucha concurrencia?

Durante la fase Decode, cada paso de un nuevo token requiere leer los tensores Key y Value almacenados para todos los tokens anteriores de cada solicitud activa del lote. A medida que aumentan las longitudes de secuencia y el número de solicitudes simultáneas, crece el volumen de datos KV transferidos desde la memoria de la GPU en cada paso, lo que satura el ancho de banda de memoria y obliga al planificador a limitar el tamaño del lote o esperar las lecturas de memoria.

Fuentes

Escrito por

Chunyu Cai

Chunyu Cai es el director ejecutivo de Lotook, LLC y un emprendedor que escribe sobre modelos de IA, agentes y cómo cambian el trabajo cotidiano. Chunyu Cai tiene un título de máster de Imperial College London.