Contexto largo y KV offloading: cuando el cuaderno de notas no cabe en la mesa
TL;DR
Servir contexto largo no es servir un modelo más listo, es gestionar un cuaderno de notas que no cabe en la mesa. La ventana de contexto la pone el modelo (y se extiende con RoPE scaling / YaRN), pero el coste de servirla lo pone el KV cache, que crece linealmente con la longitud de secuencia y, en producción, explota: un contrato de 300k tokens en Llama 3 70B se come ~93 GB de KV —más que una H100 entera— y un millón de tokens pide ~125 GB. Cuando el KV no cabe en la HBM, solo hay dos salidas: recomputar (carísimo, la atención es cuadrática) o descargar (offload) el KV a una jerarquía de memoria más barata: DRAM, NVMe, red. El estado del arte OSS de 2026 —LMCache, Mooncake (la plataforma de Kimi) y NVIDIA Dynamo/KVBM— convierte ese offload en una capa de KV de primera clase, con reutilización entre peticiones, prefill/decode disaggregation y routing consciente del caché. Resultados reportados: 3×–10× menos latencia con LMCache y hasta +525 % de throughput en escenarios de contexto largo con Mooncake. El precio: cada salto de memoria añade latencia de transferencia, así que el offload solo compensa cuando lo que ahorras recomputando supera lo que cuesta mover los bytes.
La analogía
Un investigador trabaja en una mesa pequeña. Encima caben los papeles de los que está tirando ahora mismo —eso es la HBM de la GPU, rapidísima pero diminuta. Cuando el caso es largo (un sumario de mil páginas, un contexto de un millón de tokens), los papeles no caben. Tiene tres opciones:
- Tirar papeles y volver a pedirlos al archivo cada vez que los necesita. Es recomputar el KV: correcto, pero lentísimo, porque “pedirlos al archivo” en un LLM significa volver a pasar todo el prompt por la atención —y la atención es cuadrática.
- Poner una estantería al lado de la mesa (la DRAM de la CPU) y, más allá, un almacén en el sótano (NVMe) y un depósito en otro edificio (red/objeto). Mover papeles entre la mesa y la estantería cuesta segundos, no horas. Eso es el KV offloading jerárquico.
- Que dos investigadores se repartan el trabajo: uno lee y subraya todo el sumario (prefill), pasa sus notas al segundo, que solo redacta (decode). Eso es la arquitectura disaggregated KVCache-centric.
Este post va de las opciones 2 y 3, que son las que hacen viable el contexto largo en producción. La 1 es la que pagas por defecto si no haces nada.
Parte 1 · Por qué el contexto largo es un problema de memoria
El KV cache crece con la secuencia
Por cada token que entra o sale, el modelo guarda sus vectores key y value en cada capa para no recomputarlos. El tamaño es:
$$\text{KV bytes} = 2 \times L \times h_{kv} \times d_{head} \times s \times b$$con \(L\) capas, \(h_{kv}\) cabezas KV (con GQA, pocas), \(d_{head}\) la dimensión por cabeza, \(s\) la longitud de secuencia y \(b\) los bytes por elemento. Todo es constante del modelo salvo \(s\): el KV es lineal en la longitud de contexto. Doblar el contexto dobla el KV. Es la base de KV cache: la memoria de trabajo de la inferencia.
Los números asustan
A escala de producción, ese término lineal se vuelve brutal:
- Un contrato de 300k tokens en Llama 3 70B consume ~93 GB solo de KV — más que los 80 GB de una H100 entera (DigitalOcean · Long-Context Inference Cost).
- Un contexto de 1M tokens necesita ~125 GB de KV, que excede tanto una RTX 4090 (24 GB) como una A100 de 80 GB (Introl · Long-Context LLM Infrastructure).
- Incluso un 7B a 128k sube a ~14 GB de KV (frente a ~6 GB a 4k).
El KV deja de ser un detalle de implementación y se convierte en el recurso que dicta tu concurrencia.
Y encima la atención es cuadrática
El KV es lineal, pero el cómputo de la atención es cuadrático en la longitud. Cuando el prompt llega a 1M tokens, generar cada token puede requerir del orden de 1.765 segundos, con más del 96 % de la latencia gastada en atención (Introl). El efecto agregado es un colapso de throughput de 10×–100× frente a contextos cortos. Por eso el contexto largo no se “arregla” solo con más VRAM: hay un problema de memoria (KV) y un problema de cómputo (atención) a la vez.
Extender la ventana ≠ servirla barato
Una confusión habitual: “mi modelo soporta 1M de contexto” no significa “puedo servir 1M barato”. La ventana se extiende con técnicas de RoPE scaling como YaRN, que es la opción práctica para fine-tunear modelos open source a contexto largo: necesita 10× menos tokens de entrenamiento y 2,5× menos pasos que la interpolación RoPE naíf, y llevó LLaMA-2 de 4k a 32k y 128k (YaRN/LongRoPE). Pero eso resuelve que el modelo atienda contexto largo, no que te quepa el KV en la GPU. Según los recopilatorios de 2026, mayo de 2026 marcó la primera generación de modelos open de millón de tokens (con familias que soportan 256k extensibles a 1M vía YaRN) (letsdatascience) — trátalo como tendencia, no como spec cerrada, y verifica capacidades por modelo concreto.
Parte 2 · La jerarquía de memoria del KV
La idea central del offload es vieja en sistemas: caching jerárquico. El KV “caliente” (lo que se está usando) vive en HBM; el “templado” baja a DRAM; el “frío” a NVMe; el “compartido entre nodos” a red u objeto. Cada salto multiplica la capacidad y divide el ancho de banda.
El cálculo de cuándo compensa el offload
El offload no es magia: mover KV de DRAM/NVMe de vuelta a HBM cuesta tiempo. La regla es simple — descarga compensa cuando el coste de recomputar supera el coste de transferir:
$$t_{recompute}(s) \;>\; t_{transfer} = \frac{\text{KV bytes}}{BW_{enlace}}$$Como \(t_{recompute}\) crece con la atención (cuadrático) y \(t_{transfer}\) crece solo con el tamaño del KV (lineal) dividido por el ancho de banda del enlace, cuanto más largo el contexto, más a favor del offload juega la balanza. Por eso el offload es la palanca del contexto largo: es justo donde recomputar se vuelve insoportable. La documentación de LMCache lo dice sin rodeos: el offload solo ayuda cuando la recomputación que evita es mayor que el overhead que introduce (LMCache benchmark).
Parte 3 · KV offloading en la práctica (OSS)
El límite del prefix caching “de serie”
vLLM ya cachea prefijos en HBM (ver prefix cache: ingeniería del hit rate y PagedAttention). El problema: solo vive en HBM, que es pequeña. Cuando el KV útil excede la HBM, se desaloja y, en la siguiente petición, hay que recomputarlo — no hay speedup aunque el prefix caching esté activado, porque el caché ya no está (benchmark vLLM vs LMCache). El prefix caching nativo es necesario pero insuficiente para contexto largo.
LMCache: la capa de KV persistente
LMCache añade backends de almacenamiento persistente al prefix cache de vLLM (y soporta también SGLang y NVIDIA Dynamo como motores). Extrae el KV de la HBM y lo comparte entre motores y entre consultas, cubriendo una jerarquía de tres niveles: GPU HBM, CPU DRAM y NVMe SSD, con backends adicionales de Redis/Valkey, Mooncake, InfiniStore, S3 y NIXL/GDS (GitHub LMCache, arXiv 2510.09665). Como la DRAM/NVMe guardan mucho más KV que la HBM, el hit ratio sube, y LMCache reporta 3×–10× menos latencia combinado con vLLM. Frente al offload básico de vLLM a CPU, LMCache carga el KV a nivel de chunk con kernels CUDA de alto rendimiento, lo que reduce el overhead de transferencia.
Un arranque mínimo, offload a CPU:
# vLLM con LMCache como capa de KV (offload a DRAM)
pip install lmcache
LMCACHE_CONFIG_FILE=lmcache.yaml \
vllm serve meta-llama/Llama-3.1-70B-Instruct \
--tensor-parallel-size 4 \
--kv-transfer-config '{"kv_connector":"LMCacheConnector","kv_role":"kv_both"}'
# lmcache.yaml — jerarquía HBM -> DRAM -> NVMe
chunk_size: 256
local_cpu: true # KV templado en DRAM
max_local_cpu_size: 200 # GB
local_disk: "file:///nvme/lmcache" # KV frío en NVMe
max_local_disk_size: 2000 # GB
El offload a CPU añade overhead moderado pero retiene buena parte del beneficio frente a recomputar (LMCache docs). Ojo a la ruta física: ese tráfico HBM↔DRAM↔NVMe pasa por PCIe y por los nodos NUMA — conviene leer topología PCIe, GPUDirect y ACS y NUMA, hugepages y aislamiento de CPU, porque un offload mal cableado puede comerse su propia ganancia.
Combínalo con FP8
El offload reduce dónde vive el KV; la cuantización reduce cuánto pesa. Son ortogonales y se suman: KV en FP8 corta el tamaño a la mitad (ver FP8 end-to-end), lo que significa la mitad de bytes que transferir en cada offload. En contexto largo, FP8 + offload es la combinación por defecto.
Parte 4 · Arquitectura KVCache-centric / disaggregated
El siguiente nivel no es solo descargar KV, sino rediseñar el serving alrededor del KV.
Mooncake (la plataforma de Kimi)
Mooncake, la plataforma de serving de Kimi (Moonshot AI), es la referencia de arquitectura KVCache-centric disaggregated: separa los clusters de prefill y decode, y aprovecha los recursos infrautilizados de CPU, DRAM y SSD del cluster GPU para montar un caché de KV distribuido. Su núcleo es un scheduler centrado en el KVCache que maximiza throughput respetando SLOs, con una política de rechazo temprano basada en predicción para escenarios sobrecargados (arXiv 2407.00079, USENIX FAST'25). Los números en contexto largo son llamativos: hasta +525 % de throughput en ciertos escenarios simulados respetando SLO, y en producción Kimi maneja 115 % y 107 % más peticiones en clusters A800 y H800 respectivamente frente a sistemas previos. El título de su charla en FAST lo resume: “Trading More Storage for Less Computation” — exactamente la regla de la Parte 2.
NVIDIA Dynamo y KVBM
NVIDIA Dynamo es el framework open source de serving distribuido con prefill/decode disaggregated, scheduling dinámico de GPU y routing consciente del KV: el PrefillRouter calcula overlap scores entre la petición entrante y los bloques de KV ya cacheados, y enruta a las GPUs que ya tienen el KV relevante, evitando recomputar (Dynamo · Disaggregated Serving, Dynamo · KV-aware routing). La transferencia de KV entre prefill y decode la hace NIXL directamente GPU-a-GPU por el mejor transporte disponible (NVLink, InfiniBand). Y su gestor de offload, KVBM (KV Block Manager), tiene una arquitectura de tres capas (runtime LLM, gestión lógica de bloques, transporte NIXL) que descarga el KV frío a CPU RAM, NVMe o almacenamiento en red para liberar HBM (Dynamo · KV Cache Offloading). Conecta con disaggregated serving: prefill y decode en pods especializados.
El proyecto llm-d recorre el mismo camino desde el prefix caching de vLLM hasta el scheduling distribuido con conciencia de KV (llm-d · KV-Cache Wins You Can See).
Parte 5 · Arquitectura de referencia
Sobre el cluster de ejemplo del blog —un nodo 4×H100 SXM (80 GB, NVLink) con NVMe local y DRAM holgada— una pila sensata para servir contexto largo sin recomputar a lo bobo:
- KV en FP8 en el motor (vLLM), para empezar con la mitad de bytes.
- LMCache como capa de KV con jerarquía DRAM→NVMe, de modo que los prefijos largos (un manual, un código base, un sumario) se cacheen una vez y se reutilicen entre sesiones sin recomputar.
- Routing consciente de KV (Dynamo o llm-d) si tienes varios nodos: que la petición vaya a la GPU que ya tiene el prefijo, no a una cualquiera.
- Prefill/decode disaggregation cuando el prefill de contextos enormes empiece a robarle decode a las demás peticiones — el prefill de 300k tokens monopoliza la GPU y mata la latencia de todos.
- Métricas de hit ratio del KV, bytes transferidos por nivel y ratio prefill/decode, exportadas a tu observabilidad (instrumentar vLLM con OTel). Sin esas tres métricas, el offload es fe, no ingeniería.
Para prototipar la pila —validar la config de LMCache, el formato de los backends, el comportamiento de hit/miss— una RTX 5090 (Blackwell, 32 GB) con NVMe sobra para servir un 7–14B y ver el offload funcionando a contextos de 32–128k. No esperes servir 1M en una tarjeta de consumo: el problema de contexto largo es, precisamente, que ni el KV ni la atención caben en hardware pequeño. El dimensionamiento real parte del SLO y de la distribución de longitudes de contexto de tu tráfico, no de la ventana máxima del modelo — y ahí enlaza con capacity planning.
Parte 6 · Pitfalls operativos (y escepticismo honesto)
- Creer que el prefix caching nativo basta. Solo vive en HBM; en contexto largo se desaloja y recomputas. Necesitas una capa persistente (LMCache/KVBM) para que el caché sobreviva.
- Offload sin medir el break-even. Si tus prefijos no se reutilizan, el offload solo añade latencia de transferencia sin ahorrar recomputación. El offload brilla con prefijos compartidos (RAG sobre el mismo corpus, agentes con el mismo system prompt, sesiones largas), no con peticiones únicas e irrepetibles.
- Olvidar que la atención sigue siendo cuadrática. El offload arregla la memoria del KV, no el cómputo de la atención. Para 1M tokens reales necesitas además atención eficiente, sparse attention o enfoques de recuperación (RetrievalAttention, ShadowKV); el offload por sí solo no baja los 1.765 s/token.
- Cablear mal el offload. HBM↔DRAM↔NVMe atraviesa PCIe y NUMA. Un offload que cruza el nodo NUMA equivocado o satura un carril PCIe puede ser más lento que recomputar. Mide el ancho de banda real del enlace, no el del datasheet.
- Tratar las cifras como constantes. El 93 GB, el +525 %, el 3–10× dependen de modelo, longitud, hardware y patrón de reutilización. Son órdenes de magnitud para diseñar; mide en tu carga.
- Confiar specs de modelos de million-token sin verificar. El panorama de modelos open de 1M es muy reciente (2026) y se mueve rápido; los nombres y límites concretos cambian entre versiones. Verifica la ventana y el coste de servirla por modelo, no te fíes del titular.
Una nota de prudencia para junio de 2026: la capa de KV distribuido (LMCache, Mooncake, KVBM, NIXL, llm-d) está cuajando ahora mismo, con APIs y backends que cambian release a release. Es exactamente el tipo de pieza que conviene montar con una abstracción por medio (el kv_connector de vLLM) y no acoplarse a un solo proveedor.
Cierre
El contexto largo es un problema de memoria y de cómputo a la vez, y el KV cache es el cuello de los dos. La ventana la regala el modelo; servirla barata es ingeniería de sistemas: cuantiza el KV (FP8), descárgalo por niveles (HBM→DRAM→NVMe→red) con una capa persistente, y reorganiza el serving alrededor del KV (routing consciente, prefill/decode separados) cuando la escala lo pida. La regla que lo gobierna todo es la de Mooncake: cambiar almacenamiento por cómputo. Guardas más KV en sitios baratos para no recomputar atención cara. Hazlo bien y un nodo de 4×H100 sirve contextos que, recomputando, ni siquiera arrancarían. Hazlo mal —o no lo hagas— y cada petición larga vuelve a leerse el sumario entero desde cero, mientras la GPU se ahoga en una atención cuadrática que ya habías pagado una vez.
Fuentes
- LMCache · GitHub — https://github.com/LMCache/LMCache
- LMCache: An Efficient KV Cache Layer (arXiv 2510.09665) — https://arxiv.org/pdf/2510.09665
- LMCache · Offload KV cache to CPU (docs) — https://docs.lmcache.ai/getting_started/quickstart/offload_kv_cache.html
- vLLM Prefix Caching vs. LMCache: Benchmarking KV Reuse Tradeoffs — https://levelup.gitconnected.com/vllm-prefix-caching-vs-lmcache-benchmarking-kv-reuse-tradeoffs-944fbaf98b56
- Mooncake: A KVCache-centric Disaggregated Architecture (arXiv 2407.00079) — https://arxiv.org/abs/2407.00079
- Mooncake · USENIX FAST'25 (Trading More Storage for Less Computation) — https://www.usenix.org/conference/fast25/presentation/qin
- Mooncake · GitHub — https://github.com/kvcache-ai/Mooncake/
- NVIDIA Dynamo · Disaggregated Serving (docs) — https://docs.dynamo.nvidia.com/dynamo/design-docs/disaggregated-serving
- NVIDIA Dynamo · KV-aware routing (docs) — https://docs.nvidia.com/dynamo/latest/user-guides/kv-cache-aware-routing
- NVIDIA Dynamo · KV Cache Offloading / KVBM (docs) — https://docs.nvidia.com/dynamo/backends/v-llm/kv-cache-offloading
- llm-d · KV-Cache Wins You Can See — https://llm-d.ai/blog/kvcache-wins-you-can-see
- Long-Context Inference at Scale: The Hidden Infrastructure Cost · DigitalOcean — https://www.digitalocean.com/community/tutorials/long-context-inference-production-cost
- Long-Context LLM Infrastructure · Introl — https://introl.com/blog/long-context-llm-infrastructure-million-token-windows-guide
- YaRN / LongRoPE (arXiv 2402.13753) — https://arxiv.org/html/2402.13753v1