<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Sglang on lo0 — Blog Técnico</title><link>https://blog.lo0.es/tags/sglang/</link><description>Recent content in Sglang on lo0 — Blog Técnico</description><generator>Hugo -- gohugo.io</generator><language>es</language><lastBuildDate>Mon, 18 May 2026 17:00:00 +0200</lastBuildDate><atom:link href="https://blog.lo0.es/tags/sglang/index.xml" rel="self" type="application/rss+xml"/><item><title>Operators de inferencia LLM en Kubernetes: OME, vLLM Production Stack, NVIDIA Dynamo y llm-d</title><link>https://blog.lo0.es/posts/operators-llm-kubernetes/</link><pubDate>Mon, 18 May 2026 17:00:00 +0200</pubDate><guid>https://blog.lo0.es/posts/operators-llm-kubernetes/</guid><description>&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>Servir un LLM en producción no es ejecutar un binario: es coordinar &lt;strong>un modelo&lt;/strong> (decenas de gigabytes que tardan minutos en cargar), &lt;strong>un runtime&lt;/strong> (vLLM, SGLang, TensorRT-LLM con cien flags), &lt;strong>GPUs heterogéneas&lt;/strong> (NVLink, MIG, PCIe), &lt;strong>prefill y decode&lt;/strong> que viven mejor separados, &lt;strong>un cache de KV&lt;/strong> que quiere offloading a tiers más fríos, &lt;strong>routing inteligente&lt;/strong> que aproveche prefix caching, y &lt;strong>autoscaling&lt;/strong> que reaccione a métricas que no son CPU%. Un &lt;code>Deployment&lt;/code> plano de Kubernetes solo cubre el primer 20% de esto. El otro 80% lo cubren los &lt;strong>operators de inferencia LLM&lt;/strong>, que en 2026 son cuatro relevantes: &lt;strong>OME&lt;/strong> (LMSYS, julio 2025, multi-engine con foco en SGLang), &lt;strong>vLLM Production Stack&lt;/strong> (Helm chart curado del propio vLLM con LMCache para tiered KV), &lt;strong>NVIDIA Dynamo&lt;/strong> (sucesor oficial de Triton, multi-engine, scheduler propio Grove) y &lt;strong>llm-d&lt;/strong> (donación CNCF de marzo 2026 por Red Hat + Google + IBM + CoreWeave + NVIDIA, sobre vLLM, foco en escala distribuida). Detrás de los cuatro está &lt;strong>KServe&lt;/strong>, el operator madre del CNCF que normalizó el concepto de &lt;code>InferenceService&lt;/code> y sobre el que varios se apoyan. Este artículo recorre la jerarquía completa, da un mapa de decisión y enseña a no perderse cuando alguien suelte siete siglas en la primera reunión.&lt;/p>
&lt;blockquote>
&lt;p>Este artículo cierra la serie de inferencia LLM. Los anteriores fueron &lt;a href="https://blog.lo0.es/posts/kv-cache-fundamentos/">KV cache: la memoria de trabajo que sostiene la inferencia LLM&lt;/a>, &lt;a href="https://blog.lo0.es/posts/vllm-kubernetes/">vLLM en Kubernetes: la pieza de inferencia LLM que sí escala&lt;/a> y &lt;a href="https://blog.lo0.es/posts/pagedattention-deep-dive/">PagedAttention por dentro y el estado del arte del KV cache en 2026&lt;/a>. Allí explicamos qué pasa &lt;strong>dentro de un proceso de inferencia&lt;/strong>. Aquí explicamos cómo se coordinan &lt;strong>muchos procesos de inferencia&lt;/strong> a través de Kubernetes.&lt;/p>
&lt;/blockquote>
&lt;h2 id="la-analogía-de-initd-a-systemd-a-operators">La analogía: de &lt;code>init.d&lt;/code> a systemd a operators&lt;/h2>
&lt;p>El que lleva 20 años en sysadmin reconocerá el patrón. Hace décadas, arrancar un servicio en Linux era un script shell en &lt;code>/etc/init.d/&lt;/code>: start, stop, status, recargado a mano. Cuando los servicios se hicieron más complejos —dependencias entre ellos, monitorización, restart on failure, slots por usuario— se hizo evidente que un script no bastaba. Llegó &lt;strong>systemd&lt;/strong>, que convirtió &amp;ldquo;un servicio&amp;rdquo; en una &lt;strong>unidad declarativa&lt;/strong> con dependencias, recursos, restart policy, sockets, timers. El script no desapareció; se subió un nivel de abstracción.&lt;/p>
&lt;p>Kubernetes hizo el mismo movimiento para servicios distribuidos. Un &lt;code>Deployment&lt;/code> declara &amp;ldquo;quiero N réplicas de este contenedor&amp;rdquo;; un &lt;code>Service&lt;/code> declara &amp;ldquo;estas réplicas se exponen así&amp;rdquo;; un &lt;code>Ingress&lt;/code> declara &amp;ldquo;este tráfico HTTP entra aquí&amp;rdquo;. El controller traduce la declaración en estado real y mantiene el sistema convergente.&lt;/p>
&lt;p>Servir LLMs en 2024 era el equivalente al &lt;code>/etc/init.d/&lt;/code>: cada equipo escribía sus &lt;code>Deployment&lt;/code>/&lt;code>Service&lt;/code>/&lt;code>HPA&lt;/code> con scripts customizados de carga de modelo, drenaje de sesiones, manejo de GPU. Lo cubrimos en el &lt;a href="https://blog.lo0.es/posts/vllm-kubernetes/">artículo de vLLM en Kubernetes&lt;/a>: se puede hacer, y de hecho funciona, pero &lt;strong>es repetitivo, frágil y nadie está extrayendo las abstracciones correctas&lt;/strong>. Servir LLMs en 2026 ha vivido la misma transición que los servicios: ha aparecido el equivalente a systemd —los &lt;strong>operators de inferencia&lt;/strong>— que normalizan las abstracciones y dejan al ingeniero declarar lo importante: &amp;ldquo;este modelo, con este runtime, así de escalable, con esta política de routing&amp;rdquo;.&lt;/p>
&lt;p>Hay cuatro operators relevantes en 2026 y un quinto antecesor común. Vamos por orden.&lt;/p>
&lt;h2 id="por-qué-un-operator-y-no-solo-un-deployment">Por qué un operator, y no solo un Deployment&lt;/h2>
&lt;p>Listar lo que un operator de inferencia aporta sobre un Deployment plano es la mejor manera de entender qué problema resuelve:&lt;/p>
&lt;p>&lt;strong>Modelo como ciudadano de primera clase.&lt;/strong> En un Deployment, el modelo es &amp;ldquo;lo que descargas en un initContainer y montas como volumen&amp;rdquo;. En un operator, el modelo es una &lt;code>CustomResource&lt;/code> con metadatos (origen, fingerprint, licencia, GPU requirements). Pueden compartirse entre InferenceServices, versionarse, replicarse a múltiples nodos. Es la diferencia entre &amp;ldquo;un fichero&amp;rdquo; y &amp;ldquo;un artifact gestionado&amp;rdquo;.&lt;/p>
&lt;p>&lt;strong>Runtime como ciudadano de primera clase.&lt;/strong> Idem para el runtime (vLLM/SGLang/TRT-LLM): no es &amp;ldquo;una imagen Docker con flags&amp;rdquo;; es una &lt;code>ServingRuntime&lt;/code> que declara qué args acepta, qué métricas exporta, qué tipos de despliegue soporta (single-node, multi-node TP, PD-disag). Cambiar de runtime es cambiar una referencia, no reescribir todos los manifests.&lt;/p>
&lt;p>&lt;strong>Composición declarativa.&lt;/strong> Una &lt;code>InferenceService&lt;/code> (CRD nuclear de KServe y descendientes) &lt;strong>referencia&lt;/strong> un modelo y un runtime, &lt;strong>declara&lt;/strong> la política de escalado, &lt;strong>enlaza&lt;/strong> observabilidad, &lt;strong>configura&lt;/strong> routing. El controller compone todas las piezas: Deployment(s), Service, HPA, eventualmente LeaderWorkerSet, ScaledObject de KEDA, HTTPRoute de Gateway API. Tú declaras intención; el operator emite los 8 recursos derivados.&lt;/p>
&lt;p>&lt;strong>Prefill–decode disaggregation operacional.&lt;/strong> Como vimos en el artículo de PagedAttention, separar prefill y decode en pools distintos puede dar 7× goodput. Modelar eso con Deployments planos es viable, pero requiere coordinar dos sets de pods, un transport para mover KV cache, routing condicional. Un operator lo modela como una sola &lt;code>InferenceService&lt;/code> con dos sub-pools.&lt;/p>
&lt;p>&lt;strong>Autoscaling con métricas LLM.&lt;/strong> El HPA estándar no entiende &lt;code>vllm:num_requests_waiting&lt;/code>. Un operator integra KEDA o Prometheus Adapter automáticamente y expone las métricas correctas como knobs del CRD.&lt;/p>
&lt;p>&lt;strong>Multi-tenancy.&lt;/strong> Múltiples modelos en el mismo cluster, con cuotas, prioridades y fairness. Un Deployment por modelo escalando independientemente está bien hasta el quinto modelo; a partir de ahí, la coordinación de GPUs entre tenants se vuelve operationally hostil.&lt;/p>
&lt;p>&lt;strong>Lifecycle del modelo.&lt;/strong> Pesos en PVC compartido, calentamiento del primer pod, rolling updates con &lt;code>maxUnavailable: 0&lt;/code>, drenaje de sesiones activas, observabilidad integrada. Cosas que en Deployment plano hay que reinventar en cada equipo.&lt;/p>
&lt;p>Si tu carga es &lt;strong>un modelo, un nodo, hasta tres réplicas&lt;/strong>, un Deployment plano basta y un operator es overkill. Si tu carga es &lt;strong>dos o más modelos, escalado serio, disaggregation o multi-tenancy&lt;/strong>, un operator deja de ser opcional.&lt;/p>
&lt;h2 id="kserve-el-antecesor-común">KServe: el antecesor común&lt;/h2>
&lt;p>Antes de los cuatro nuevos, hay que mencionar a &lt;a href="https://kserve.github.io/website/">KServe&lt;/a>, que es el operator madre del que descienden conceptualmente todos los demás. Nació como &lt;strong>KFServing&lt;/strong> dentro del proyecto Kubeflow en 2019, pasó a llamarse KServe al independizarse en 2021, y en 2025 fue &lt;a href="https://thenewstack.io/kserve-joins-cncf-to-standardize-ai-model-serving-on-kubernetes/">aceptado en la CNCF&lt;/a> como proyecto incubando hacia graduado.&lt;/p>
&lt;p>La contribución conceptual de KServe es &lt;strong>el CRD &lt;code>InferenceService&lt;/code>&lt;/strong>, que se ha convertido en el vocabulario común del campo: un objeto K8s declarativo que une un &lt;code>model&lt;/code> (origen + metadata) con un &lt;code>predictor&lt;/code> (runtime + recursos) y produce un servicio HTTP listo. Bajo el capó, el controller emite Deployments, Services, HorizontalPodAutoscalers, Knative Services si haces serverless, Istio VirtualServices si haces traffic splitting.&lt;/p>
&lt;p>KServe fue diseñado en una era pre-LLM: sus primeros casos de uso eran modelos scikit-learn, TensorFlow y PyTorch tradicionales servidos como REST APIs simples. Eso le da fortalezas (es maduro, lleva 6 años en producción en Bloomberg, JPMorgan y otros) y debilidades (no fue diseñado para gestionar tensor parallel multi-nodo, prefill–decode disaggregation, ni los patrones específicos de LLMs).&lt;/p>
&lt;p>La forma en la que el ecosistema ha reaccionado es elegante: &lt;strong>los nuevos operators de LLM heredan o se inspiran en &lt;code>InferenceService&lt;/code> pero extienden la API con primitivos específicos de LLM&lt;/strong>. OME es el ejemplo más claro: usa el nombre &lt;code>InferenceService&lt;/code> y la idea de &amp;ldquo;modelo + runtime → servicio&amp;rdquo;, pero añade &lt;code>BaseModel&lt;/code>, &lt;code>ServingRuntime&lt;/code> con flags LLM-aware, y modos de despliegue (PD-disag, multi-node) que KServe no contempla nativamente.&lt;/p>
&lt;h2 id="ome-open-model-engine">OME (Open Model Engine)&lt;/h2>
&lt;p>&lt;a href="https://github.com/ome-projects/ome">OME&lt;/a> lo publicó el equipo de LMSYS en julio 2025 (anunciado en &lt;a href="https://www.lmsys.org/blog/2025-07-08-ome/">su blog&lt;/a>). Es un operator que entiende SGLang en profundidad (es su runtime de primera clase) pero también soporta vLLM, TensorRT-LLM y Triton.&lt;/p>
&lt;h3 id="la-jerarquía-de-crds">La jerarquía de CRDs&lt;/h3>
&lt;p>OME modela el dominio con cuatro CRDs principales:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>BaseModel&lt;/code>&lt;/strong> y &lt;strong>&lt;code>ClusterBaseModel&lt;/code>&lt;/strong>: el modelo en sí. Define origen (Hugging Face, S3, URL), fingerprint, metadatos. La versión &lt;code>Cluster*&lt;/code> es global; la &lt;code>BaseModel&lt;/code> es namespaced. Permite que múltiples &lt;code>InferenceService&lt;/code> referencien el mismo modelo sin duplicar la descarga.&lt;/li>
&lt;li>&lt;strong>&lt;code>FineTunedWeight&lt;/code>&lt;/strong>: adapters LoRA o pesos finetuneados que se sirven encima de un &lt;code>BaseModel&lt;/code>. Crítico para multi-tenant donde cada cliente tiene su finetune.&lt;/li>
&lt;li>&lt;strong>&lt;code>ServingRuntime&lt;/code>&lt;/strong> y &lt;strong>&lt;code>ClusterServingRuntime&lt;/code>&lt;/strong>: el runtime (vLLM, SGLang, etc.) con su configuración. Declara qué args acepta, qué métricas exporta, qué modos de despliegue soporta.&lt;/li>
&lt;li>&lt;strong>&lt;code>InferenceService&lt;/code>&lt;/strong>: la pieza central, declarativa, que une &lt;code>BaseModel&lt;/code> + &lt;code>ServingRuntime&lt;/code> + infraestructura.&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ome.io/v1beta1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">InferenceService&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">llama3-70b-prod&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">inference&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">model&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">meta-llama-3-70b-instruct &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># referencia a un BaseModel&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runtime&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">sglang-h100 &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># referencia a un ServingRuntime&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">deploymentMode&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">PrefillDecodeDisaggregated &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># standard | PD | MultiNode | Serverless&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">prefill&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">minReplicas&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">maxReplicas&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">8&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">resources&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">requests&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">nvidia.com/gpu&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">4&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">decode&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">minReplicas&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">4&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">maxReplicas&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">16&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">resources&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">requests&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">nvidia.com/gpu&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">router&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cache-aware &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># SGLang router con cache awareness&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">autoscaling&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metricSource&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">keda&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metrics&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">prometheus&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metricName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm_requests_waiting&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">threshold&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;10&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Esto es lo que el operador toma como entrada. La salida son aproximadamente &lt;strong>8 recursos derivados&lt;/strong> que serían un horror declarar a mano: dos LeaderWorkerSets (uno por pool prefill/decode), dos Services, un Deployment para el router, ScaledObjects de KEDA por cada pool, HTTPRoute de Gateway API, y un PriorityClass que conecta con Kueue para gang scheduling.&lt;/p>
&lt;h3 id="los-cuatro-modos-de-despliegue">Los cuatro modos de despliegue&lt;/h3>
&lt;p>OME materializa la &lt;code>InferenceService&lt;/code> de forma distinta según &lt;code>deploymentMode&lt;/code>:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Standard&lt;/strong>: un Deployment con N réplicas; clásico. Para modelos pequeños o single-GPU.&lt;/li>
&lt;li>&lt;strong>PrefillDecodeDisaggregated&lt;/strong>: dos pools coordinados; el router de SGLang los enruta.&lt;/li>
&lt;li>&lt;strong>MultiNode&lt;/strong>: tensor parallel sobre múltiples nodos vía LeaderWorkerSet, con NCCL/InfiniBand. Para modelos &amp;gt;70B donde un solo nodo no llega.&lt;/li>
&lt;li>&lt;strong>Serverless&lt;/strong>: Knative-style scale-to-zero. Para cargas esporádicas donde el coste de mantener GPUs encendidas no compensa. Trade-off: el primer request paga el coste de cold start del modelo (minutos).&lt;/li>
&lt;/ul>
&lt;h3 id="integración-con-el-ecosistema-k8s">Integración con el ecosistema K8s&lt;/h3>
&lt;p>OME no inventa primitivos donde ya existen. Se apoya en:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;a href="https://kueue.sigs.k8s.io/">Kueue&lt;/a>&lt;/strong> para gang scheduling: todos los pods de un tensor parallel deben arrancar a la vez o ninguno; Kueue lo garantiza.&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://lws.sigs.k8s.io/">LeaderWorkerSet (LWS)&lt;/a>&lt;/strong> para multi-nodo: workers se unen al cluster Ray del leader, ciclo de vida atómico (caída de uno reinicia el grupo).&lt;/li>
&lt;li>&lt;strong>KEDA&lt;/strong> para autoscaling por métricas Prometheus específicas de LLM (queue depth, GPU cache usage, TTFT p95).&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://gateway-api.sigs.k8s.io/">Gateway API&lt;/a>&lt;/strong> y su &lt;strong>Inference Extension&lt;/strong> para routing avanzado (model-aware, prefix-aware, weighted canary).&lt;/li>
&lt;/ul>
&lt;p>La consecuencia: OME se siente &amp;ldquo;idiomáticamente Kubernetes&amp;rdquo;. No introduce conceptos nuevos donde no hace falta; usa primitivos estándar y se concentra en lo específico del dominio LLM.&lt;/p>
&lt;h3 id="cuándo-elegirlo">Cuándo elegirlo&lt;/h3>
&lt;p>OME es la opción natural si &lt;strong>SGLang es tu runtime principal&lt;/strong> y/o si vienes del ecosistema KServe y quieres una evolución idiomática. Es maduro pero relativamente joven (un año en el momento de este artículo); espera bordes ásperos en features avanzadas.&lt;/p>
&lt;h2 id="vllm-production-stack">vLLM Production Stack&lt;/h2>
&lt;p>&lt;a href="https://github.com/vllm-project/production-stack">vLLM Production Stack&lt;/a> es el proyecto &lt;strong>oficial del propio vLLM&lt;/strong> para producción en Kubernetes. Su filosofía es opuesta a la de OME: en lugar de un operator con CRDs nuevos, es &lt;strong>un Helm chart curado&lt;/strong> que despliega un conjunto coherente de piezas.&lt;/p>
&lt;h3 id="las-tres-piezas">Las tres piezas&lt;/h3>
&lt;p>El stack tiene tres componentes:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Serving engines&lt;/strong>: pods de vLLM, configurados con los flags que llevamos viendo en toda la serie (&lt;code>--enable-prefix-caching&lt;/code>, &lt;code>--kv-cache-dtype fp8&lt;/code>, etc.). El Helm chart te deja declararlos como una lista; despliega los Deployments y Services subyacentes.&lt;/li>
&lt;li>&lt;strong>Request router&lt;/strong>: un proxy delante de los engines que decide a cuál enviar cada petición. Soporta varias políticas:
&lt;ul>
&lt;li>&lt;strong>Round-robin&lt;/strong>: trivial, para baseline.&lt;/li>
&lt;li>&lt;strong>Session-based&lt;/strong>: clava cada sesión a una réplica para mantener su KV cache.&lt;/li>
&lt;li>&lt;strong>Prefix-aware&lt;/strong>: detecta prefijos compartidos entre peticiones y las enruta a la réplica que ya los tenga cacheados.&lt;/li>
&lt;li>&lt;strong>KV-aware&lt;/strong>: ve el &lt;code>gpu_cache_usage_perc&lt;/code> de cada réplica y evita las saturadas.&lt;/li>
&lt;li>&lt;strong>Disaggregated-prefill&lt;/strong> con &lt;strong>LMCache&lt;/strong> nativo: separa prefill y decode con LMCache como transport del KV cache entre ambos.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Observability stack&lt;/strong>: Prometheus + Grafana con dashboards listos. Mide TTFT, TBT (Time-Between-Tokens), throughput, queue depth, GPU memory.&lt;/li>
&lt;/ol>
&lt;h3 id="lmcache-y-el-tiered-kv">LMCache y el tiered KV&lt;/h3>
&lt;p>Una de las piezas más interesantes que mete el stack es &lt;a href="https://github.com/LMCache/LMCache">&lt;strong>LMCache&lt;/strong>&lt;/a>, que añade un caché de KV con &lt;strong>múltiples tiers&lt;/strong>: GPU HBM como L1, CPU RAM como L2, disco local como L3, y opcionalmente storage remoto como L4. Cuando un bloque de KV cache no cabe en HBM, en lugar de evictarlo y recalcularlo, LMCache lo baja a un tier inferior. Para cargas con prefijos compartidos y multi-turn, el ahorro es brutal.&lt;/p>
&lt;p>LMCache se integra como sidecar de los engines y como parte del transport en disaggregated-prefill. El Production Stack lo trae habilitado por defecto en su Helm chart.&lt;/p>
&lt;h3 id="manifest-típico-valuesyaml">Manifest típico (values.yaml)&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">servingEngineSpec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">modelSpec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">llama3-8b&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">repository&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm/vllm-openai&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">tag&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">v0.6.3&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">modelURL&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">meta-llama/Meta-Llama-3-8B-Instruct&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">replicaCount&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">3&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">requestCPU&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">4&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">requestMemory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">16Gi&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">requestGPU&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">vllmConfig&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">enablePrefixCaching&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">kvCacheDtype&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">fp8&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">maxModelLen&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">32768&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">enableChunkedPrefill&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">routerSpec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">routingLogic&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">prefix-aware &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># round-robin | session | prefix-aware | kv-aware&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">sessionKey&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">x-user-id &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># cuando routingLogic=session&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">cacheserverSpec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">enabled&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># LMCache para tiered KV&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">storageBackends&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">cpu&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">disk &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># offload a disco local&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">observabilitySpec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">prometheus&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">enabled&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">grafana&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">enabled&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">dashboards&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">vllm-engine-metrics&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">lmcache-metrics&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Esto es declarativo pero &lt;strong>no son CRDs&lt;/strong>: son valores de un Helm chart. La diferencia con OME no es semántica (ambos parten de declaración) sino operacional: con Helm, los cambios pasan por &lt;code>helm upgrade&lt;/code>; con CRDs, pasan por &lt;code>kubectl apply&lt;/code>. Para equipos que ya viven en GitOps con Argo CD o Flux, ambos enfoques se integran limpiamente, pero los flujos son distintos.&lt;/p>
&lt;h3 id="cuándo-elegirlo-1">Cuándo elegirlo&lt;/h3>
&lt;p>Si &lt;strong>tu único runtime es vLLM&lt;/strong> y quieres lo más cercano a &amp;ldquo;el camino feliz que recomienda el proyecto&amp;rdquo;, esto. Es la versión productivizada y mantenida por la misma gente que escribe el motor. Las desventajas: ata a vLLM (no es genérico) y no resuelve algunos casos avanzados como multi-tenancy con cuotas estrictas o gang scheduling, donde OME u operators full-fledged son superiores.&lt;/p>
&lt;h2 id="nvidia-dynamo">NVIDIA Dynamo&lt;/h2>
&lt;p>&lt;a href="https://developer.nvidia.com/dynamo">NVIDIA Dynamo&lt;/a> es el &lt;strong>sucesor oficial de Triton Inference Server&lt;/strong>, anunciado en GTC 2025 y fusionado con la marca como &lt;strong>Dynamo-Triton&lt;/strong> en marzo de ese año. Triton llevaba años siendo el motor de inferencia más usado en infraestructuras NVIDIA &amp;ldquo;serias&amp;rdquo;; Dynamo es lo que NVIDIA cree que la nueva generación necesita.&lt;/p>
&lt;h3 id="qué-es-exactamente">Qué es exactamente&lt;/h3>
&lt;p>Dynamo es &lt;strong>un framework de inferencia distribuida&lt;/strong>, no exactamente un operator de Kubernetes. Tiene runtime propio (puede correr engines), scheduler (Grove), routing inteligente, gestión de KV cache multi-tier y disaggregation. Soporta como engines a &lt;strong>SGLang, TensorRT-LLM y vLLM&lt;/strong>, pero los engines son ejecutados por Dynamo, no a la inversa: el modelo es &amp;ldquo;Dynamo gestiona, el engine ejecuta&amp;rdquo;.&lt;/p>
&lt;p>En Kubernetes, Dynamo se despliega vía operator + CRDs propios, normalizados con la integración K8s que NVIDIA formalizó a finales de 2025 (la cubre &lt;a href="https://www.infoq.com/news/2025/12/nvidia-dynamo-kubernetes/">esta nota de InfoQ&lt;/a>). Los CRDs son específicos del producto: definen un &lt;code>DynamoCluster&lt;/code>, una topología de prefill/decode workers, una política de routing.&lt;/p>
&lt;h3 id="las-cuatro-contribuciones">Las cuatro contribuciones&lt;/h3>
&lt;p>Dynamo se vende sobre cuatro pilares, con números reportados por NVIDIA:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Disaggregated serving&lt;/strong> built-in con scheduler propio.&lt;/li>
&lt;li>&lt;strong>Smart routing&lt;/strong> basado en estado de cache: si un worker ya tiene cacheada la mayoría de un prompt, la petición va ahí.&lt;/li>
&lt;li>&lt;strong>Multi-tier KV cache&lt;/strong>: análogo a LMCache, con HBM/RAM/SSD/NVMe.&lt;/li>
&lt;li>&lt;strong>Autoscaling&lt;/strong> integrado con el scheduler de Dynamo.&lt;/li>
&lt;/ol>
&lt;p>El número marketing: &lt;strong>hasta 30× más throughput&lt;/strong> que Triton legacy en el mismo hardware. Con todas las precauciones que merece un benchmark de vendor.&lt;/p>
&lt;h3 id="grove-scheduler-propio">Grove: scheduler propio&lt;/h3>
&lt;p>Una decisión polémica de Dynamo es no apoyarse al 100% en el scheduler de Kubernetes y, en su lugar, traer un scheduler propio llamado &lt;strong>Grove&lt;/strong> que entiende topologías de GPU. Grove decide qué worker corre en qué GPU física, qué interconexiones (NVLink/InfiniBand) son relevantes, y cómo distribuir tensor parallel entre nodos. Esto le da más control que kube-scheduler estándar.&lt;/p>
&lt;p>Operacionalmente: si tu cluster es &amp;ldquo;puro Kubernetes&amp;rdquo; con kube-scheduler y workloads heterogéneos (no solo LLMs), Grove añade un componente adicional a operar. Si tu cluster es &lt;strong>dedicado a inferencia LLM&lt;/strong> y ya hay equipo dedicado a operarlo, Grove te da más palancas.&lt;/p>
&lt;h3 id="cuándo-elegirlo-2">Cuándo elegirlo&lt;/h3>
&lt;p>Dynamo tiene sentido si:&lt;/p>
&lt;ul>
&lt;li>Tu infraestructura es &lt;strong>NVIDIA-heavy&lt;/strong> (Hopper, Blackwell, GB200) y quieres aprovechar lo más reciente de TensorRT-LLM con la integración de Triton-de-toda-la-vida pero modernizado.&lt;/li>
&lt;li>Ya eras usuario de Triton para inferencia legacy (visión, recomendación) y quieres mantener el ecosistema.&lt;/li>
&lt;li>Tienes equipo SRE dedicado a inferencia y la complejidad operacional adicional de Grove no es un problema.&lt;/li>
&lt;/ul>
&lt;p>Es la opción &lt;strong>vendor-specific&lt;/strong> del cuarteto. A cambio te da el soporte de NVIDIA y la integración de primera con su hardware. Si tu organización ya pelea con NVIDIA por GPUs, igual te llaman para ofrecer asistencia con Dynamo.&lt;/p>
&lt;h2 id="llm-d">llm-d&lt;/h2>
&lt;p>&lt;a href="https://github.com/llm-d/llm-d">llm-d&lt;/a> es el más joven y el más &amp;ldquo;político&amp;rdquo; de los cuatro. En marzo de 2026, en &lt;a href="https://siliconangle.com/2026/03/24/red-hat-bets-big-kubernetes-inference-llm-d-kubeconeu/">KubeCon Europe Amsterdam&lt;/a>, &lt;strong>Red Hat, Google Cloud, IBM Research, CoreWeave y NVIDIA&lt;/strong> anunciaron la donación conjunta del proyecto a la CNCF como Sandbox, con soporte de AMD, Cisco, Hugging Face, Intel, Lambda, Mistral AI, UC Berkeley y University of Chicago. Una coalición de vendor-neutralidad explícita.&lt;/p>
&lt;h3 id="filosofía">Filosofía&lt;/h3>
&lt;p>llm-d se posiciona como &lt;strong>el &amp;ldquo;Kubernetes blueprint&amp;rdquo; vendor-neutral para inferencia distribuida&lt;/strong>. No es un runtime; es un sistema que se monta encima de vLLM (motor por defecto) y orquesta el plano de control.&lt;/p>
&lt;p>Las primitivas que el proyecto pone sobre la mesa:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Routing inteligente&lt;/strong> con prefix-cache awareness y load-aware balancing.&lt;/li>
&lt;li>&lt;strong>Tiered KV cache&lt;/strong> con offload a CPU y disco para multi-turn.&lt;/li>
&lt;li>&lt;strong>Prefill/decode disaggregation&lt;/strong> sobre interconnects rápidos.&lt;/li>
&lt;li>&lt;strong>Wide expert-parallelism&lt;/strong> para servir Mixture-of-Experts (MoE) muy grandes —un patrón crítico que DeepSeek-V3 y Mixtral popularizaron— donde los expertos viven en distintas GPUs y hay que enrutar tokens al experto correcto.&lt;/li>
&lt;/ul>
&lt;h3 id="números">Números&lt;/h3>
&lt;p>El &lt;a href="https://github.com/llm-d/llm-d/releases">release v0.5&lt;/a> valida ~3.1k tok/s por GPU de decode B200, y hasta 50k output tok/s en una topología 16×16 B200 prefill/decode. El benchmark más interesante: &lt;strong>orden de magnitud de reducción de TTFT&lt;/strong> vs una baseline round-robin. Es decir, el routing inteligente vale lo que se dice.&lt;/p>
&lt;h3 id="cncf-y-futuro">CNCF y futuro&lt;/h3>
&lt;p>Donar a la CNCF como Sandbox significa &lt;strong>gobernanza neutral&lt;/strong>: ningún vendor manda. Para una organización que recela de quedar atado a un único proveedor, llm-d es probablemente la apuesta más segura a medio plazo. El precio: como cualquier proyecto Sandbox, todavía no es &amp;ldquo;boring&amp;rdquo; en el sentido en que vLLM lo es. Hay churn de API, features que se mueven, documentación que va por detrás del código.&lt;/p>
&lt;h3 id="cuándo-elegirlo-3">Cuándo elegirlo&lt;/h3>
&lt;p>llm-d tiene sentido si:&lt;/p>
&lt;ul>
&lt;li>Quieres &lt;strong>portabilidad multi-vendor&lt;/strong> sin ataduras a NVIDIA, Red Hat o Google.&lt;/li>
&lt;li>Tu carga incluye &lt;strong>MoE grandes&lt;/strong> (DeepSeek-V3, Mixtral 8x22B, Llama 4 Behemoth si confirma tamaño), donde wide expert parallelism es decisivo.&lt;/li>
&lt;li>Tu organización ya está cómoda con CNCF Sandbox (proyectos en evolución activa, no aún 1.0 estable).&lt;/li>
&lt;li>Quieres apostar por el proyecto que probablemente sea el estándar de facto en 2-3 años.&lt;/li>
&lt;/ul>
&lt;h2 id="el-antecesor-común-sigue-ahí-kserve">El antecesor común sigue ahí: KServe&lt;/h2>
&lt;p>Vale la pena reconectar antes de la comparativa: &lt;strong>KServe sigue vivo y muy usado&lt;/strong> en organizaciones que sirven tanto LLMs como modelos tradicionales (scikit-learn, XGBoost, PyTorch CV). Su &lt;code>InferenceService&lt;/code> es lo bastante genérico como para servir cualquier modelo, incluyendo vLLM o SGLang como &lt;code>ServingRuntime&lt;/code>. Lo que no hace bien es lo específico de LLM: disaggregation, tensor parallel multi-nodo, routing con awareness de KV cache. Si tu organización ya tiene KServe en producción para otros modelos, &lt;strong>añadir un operator específico de LLM al lado&lt;/strong> (OME, vLLM Stack o llm-d) es razonable. Pelearlo todo desde KServe puro no.&lt;/p>
&lt;h2 id="mapa-de-decisión">Mapa de decisión&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Dimensión&lt;/th>
&lt;th>OME&lt;/th>
&lt;th>vLLM Prod Stack&lt;/th>
&lt;th>NVIDIA Dynamo&lt;/th>
&lt;th>llm-d&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Filosofía&lt;/strong>&lt;/td>
&lt;td>Operator clásico K8s-idiomático&lt;/td>
&lt;td>Helm chart curado&lt;/td>
&lt;td>Framework con scheduler propio&lt;/td>
&lt;td>Blueprint CNCF vendor-neutral&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>CRDs propios&lt;/strong>&lt;/td>
&lt;td>Sí (BaseModel, ServingRuntime, InferenceService&amp;hellip;)&lt;/td>
&lt;td>No (Helm values)&lt;/td>
&lt;td>Sí (DynamoCluster)&lt;/td>
&lt;td>Sí (KServe-derived + extensions)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Runtime primario&lt;/strong>&lt;/td>
&lt;td>SGLang (primera clase), también vLLM/TRT-LLM/Triton&lt;/td>
&lt;td>vLLM exclusivamente&lt;/td>
&lt;td>TensorRT-LLM (primera clase), también SGLang/vLLM&lt;/td>
&lt;td>vLLM (primera clase)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>PD-disaggregation&lt;/strong>&lt;/td>
&lt;td>Sí, declarativo&lt;/td>
&lt;td>Sí, con LMCache&lt;/td>
&lt;td>Sí, scheduler propio&lt;/td>
&lt;td>Sí, nativo&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Multi-nodo TP&lt;/strong>&lt;/td>
&lt;td>Sí, via LWS&lt;/td>
&lt;td>Limitado&lt;/td>
&lt;td>Sí, via Grove&lt;/td>
&lt;td>Sí, via LWS y MoE EP&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Multi-modelo en cluster&lt;/strong>&lt;/td>
&lt;td>Sí, multi-tenant maduro&lt;/td>
&lt;td>Sí (lista de modelos en values)&lt;/td>
&lt;td>Sí&lt;/td>
&lt;td>Sí&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Multi-LoRA&lt;/strong>&lt;/td>
&lt;td>Sí, primera clase (FineTunedWeight CRD)&lt;/td>
&lt;td>Limitado&lt;/td>
&lt;td>Sí&lt;/td>
&lt;td>En roadmap&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Tiered KV cache&lt;/strong>&lt;/td>
&lt;td>Vía LMCache (integración externa)&lt;/td>
&lt;td>LMCache nativo&lt;/td>
&lt;td>Multi-tier propio&lt;/td>
&lt;td>Sí, nativo&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Routing inteligente&lt;/strong>&lt;/td>
&lt;td>Cache-aware via SGLang router&lt;/td>
&lt;td>Prefix-aware / KV-aware / session-based&lt;/td>
&lt;td>Smart routing propio&lt;/td>
&lt;td>Prefix-cache + load-aware&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Scheduler GPU&lt;/strong>&lt;/td>
&lt;td>kube-scheduler + Kueue&lt;/td>
&lt;td>kube-scheduler&lt;/td>
&lt;td>Grove (propio)&lt;/td>
&lt;td>kube-scheduler + Kueue&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Hardware&lt;/strong>&lt;/td>
&lt;td>NVIDIA, AMD ROCm, Intel&lt;/td>
&lt;td>NVIDIA, AMD ROCm&lt;/td>
&lt;td>NVIDIA exclusivo (con énfasis)&lt;/td>
&lt;td>NVIDIA, AMD, Intel — neutral&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Madurez (mid-2026)&lt;/strong>&lt;/td>
&lt;td>Joven, en evolución&lt;/td>
&lt;td>Estable&lt;/td>
&lt;td>Estable, vendor-driven&lt;/td>
&lt;td>CNCF Sandbox, evolución rápida&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Gobernanza&lt;/strong>&lt;/td>
&lt;td>LMSYS (académico-industrial)&lt;/td>
&lt;td>vLLM project (académico)&lt;/td>
&lt;td>NVIDIA (vendor)&lt;/td>
&lt;td>CNCF (neutral)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Curva de aprendizaje&lt;/strong>&lt;/td>
&lt;td>Media (4 CRDs nuevos)&lt;/td>
&lt;td>Baja (Helm values familiar)&lt;/td>
&lt;td>Media-alta (Grove + CRDs propios)&lt;/td>
&lt;td>Media (similar a KServe extendido)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="cuándo-elegir-cada-uno">Cuándo elegir cada uno&lt;/h3>
&lt;p>&lt;strong>Elige OME&lt;/strong> si:&lt;/p>
&lt;ul>
&lt;li>SGLang es tu motor principal.&lt;/li>
&lt;li>Necesitas multi-LoRA serving en producción.&lt;/li>
&lt;li>Te encaja la abstracción jerárquica (BaseModel → ServingRuntime → InferenceService) y vienes de o convives con KServe.&lt;/li>
&lt;li>Tienes appetito por un proyecto joven y muy activo.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Elige vLLM Production Stack&lt;/strong> si:&lt;/p>
&lt;ul>
&lt;li>vLLM es tu único motor y quieres alinearte con lo que el proyecto recomienda.&lt;/li>
&lt;li>Tu equipo ya vive en Helm y no quiere aprender CRDs nuevos.&lt;/li>
&lt;li>LMCache + routing avanzado dentro de un solo Helm chart es exactamente lo que necesitas.&lt;/li>
&lt;li>Tu escala es media (decenas de réplicas), no extrema.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Elige NVIDIA Dynamo&lt;/strong> si:&lt;/p>
&lt;ul>
&lt;li>Tu infraestructura es NVIDIA-heavy y quieres el path más optimizado para Hopper/Blackwell.&lt;/li>
&lt;li>Ya operabas Triton para inferencia legacy y la transición es natural.&lt;/li>
&lt;li>Aceptas vendor lock-in a cambio de soporte directo NVIDIA.&lt;/li>
&lt;li>Tu organización tiene equipo SRE dedicado a inferencia.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Elige llm-d&lt;/strong> si:&lt;/p>
&lt;ul>
&lt;li>Quieres apostar por el estándar CNCF futuro, neutro entre vendors.&lt;/li>
&lt;li>Tu carga incluye MoE grandes con wide expert parallelism.&lt;/li>
&lt;li>Operas en multi-cloud o multi-hardware y la portabilidad es valiosa.&lt;/li>
&lt;li>Aceptas la inmadurez de un proyecto Sandbox a cambio de la apuesta a futuro.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Elige KServe puro&lt;/strong> si:&lt;/p>
&lt;ul>
&lt;li>Ya sirves modelos no-LLM y quieres unificar; los LLMs son una minoría de tu carga.&lt;/li>
&lt;li>Necesitas el caso de uso más conservador y maduro.&lt;/li>
&lt;li>Aceptas que features avanzadas de LLM (disaggregation, MoE EP, smart routing) te tocará añadirlas con piezas externas.&lt;/li>
&lt;/ul>
&lt;h3 id="escenarios-concretos">Escenarios concretos&lt;/h3>
&lt;p>&lt;strong>Escenario A — Startup pequeña, 1-2 modelos, 1-3 nodos GPU.&lt;/strong> Probablemente no necesitas operator. Deployment + Service + HPA con métricas de KEDA, como en el artículo de vLLM en Kubernetes. Cuando crezcas a 5+ modelos, evalúa.&lt;/p>
&lt;p>&lt;strong>Escenario B — Empresa media, 5-15 modelos, multi-tenant interno.&lt;/strong> vLLM Production Stack o OME son las opciones razonables. Production Stack si vLLM es todo lo que vas a usar; OME si quieres flexibilidad de runtime y CRDs idiomáticos.&lt;/p>
&lt;p>&lt;strong>Escenario C — Plataforma interna corporativa o servicio externo a clientes finales.&lt;/strong> llm-d o Dynamo. llm-d si valoras vendor-neutralidad; Dynamo si vives en infraestructura NVIDIA y quieres el camino que ellos recomiendan.&lt;/p>
&lt;p>&lt;strong>Escenario D — Cluster mixto LLM + modelos tradicionales.&lt;/strong> KServe como base, operator de LLM al lado (OME es lo más natural por su parentesco conceptual).&lt;/p>
&lt;h2 id="trampas-comunes">Trampas comunes&lt;/h2>
&lt;p>&lt;strong>&amp;ldquo;Voy a empezar con KServe puro porque es maduro&amp;rdquo;.&lt;/strong> Para LLMs medianos en adelante, KServe puro deja muchas optimizaciones sobre la mesa. Lo razonable es KServe como base si convives con otros modelos, pero operator LLM-específico al lado.&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;Voy a montar todo a mano para entenderlo&amp;rdquo;.&lt;/strong> Razonable en PoC, suicida en producción. Hay 8 recursos derivados por modelo. Multiplica por 10 modelos. Estás escribiendo 80 YAMLs y manteniéndolos. Usa un operator.&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;Voy a elegir el que más me gusta y luego pivoto si me equivoco&amp;rdquo;.&lt;/strong> Pivotar entre operators no es gratis: aunque la abstracción &lt;code>InferenceService&lt;/code> se está homogeneizando, los detalles (cómo se modela LoRA, cómo se configura routing, cómo se exponen métricas) varían. Migrar de OME a Dynamo es un proyecto de semanas, no de días.&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;Voy a poner Dynamo porque es de NVIDIA y mejor&amp;rdquo;.&lt;/strong> Solo si tu organización ya está alineada con su filosofía operacional (scheduler propio, vendor lock-in aceptable). Para muchos casos, vLLM Production Stack o llm-d dan 95% del valor con menos fricción.&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;Helm chart vs operator es una decisión técnica&amp;rdquo;.&lt;/strong> Es una decisión cultural/operacional. Si tu equipo entrega vía Argo CD con Helm values en Git, Production Stack encaja sin fricción. Si tu equipo vive en &lt;code>kubectl apply -f&lt;/code> directo y la idea de operators te resulta natural, OME o llm-d.&lt;/p>
&lt;h2 id="lo-que-no-hemos-cubierto">Lo que no hemos cubierto&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>&lt;a href="https://github.com/kvcache-ai/Mooncake">Mooncake&lt;/a>&lt;/strong>: el sistema de cache de KV compartido entre instancias que Kimi/Moonshot lleva en producción a cientos de millones de queries. Es un primitivo (no un operator completo), pero se integra como tier de cache con varios de los anteriores.&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://docs.ray.io/en/latest/serve/llm/serving-llms.html">Ray Serve LLM&lt;/a>&lt;/strong>: la oferta de Anyscale, en Kubernetes a través de KubeRay. Más vinculado al ecosistema Ray que a los CRDs nativos K8s. Útil si Ray ya es parte de tu infraestructura.&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://fireworks.ai/">Fireworks AI&lt;/a>, &lt;a href="https://www.modular.com/">Modular MAX&lt;/a>&lt;/strong>: plataformas comerciales con primitivos similares, pero hospedadas. No son operators K8s; son competidores en otra capa.&lt;/li>
&lt;li>&lt;strong>Gateway API Inference Extension&lt;/strong>: la propuesta sigwg para extender Gateway API con primitivos LLM (model-aware routing, sticky sessions, fairness). En 2026 está en alpha; los operators de arriba ya empiezan a soportarla. Cuando madure, el routing dejará de ser problema de cada operator y será parte del estándar de Kubernetes.&lt;/li>
&lt;li>&lt;strong>Inference observability stack genérico&lt;/strong>: Prometheus + Grafana se está estandarizando en torno a las métricas &lt;code>vllm:*&lt;/code> que cubrimos en el artículo de vLLM. Hay esfuerzo de OpenTelemetry para LLMs (&lt;code>gen-ai&lt;/code> semantic conventions) que probablemente sea el siguiente eslabón.&lt;/li>
&lt;/ul>
&lt;h2 id="cerrando-la-serie">Cerrando la serie&lt;/h2>
&lt;p>Esta serie de cuatro artículos ha recorrido la inferencia LLM en producción de abajo arriba:&lt;/p>
&lt;ol>
&lt;li>&lt;a href="https://blog.lo0.es/posts/kv-cache-fundamentos/">KV cache: la memoria de trabajo que sostiene la inferencia LLM&lt;/a> — por qué cada token consume VRAM y cuánto.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/vllm-kubernetes/">vLLM en Kubernetes: la pieza de inferencia LLM que sí escala&lt;/a> — cómo se sirve un modelo en producción con un Deployment serio.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/pagedattention-deep-dive/">PagedAttention por dentro y el estado del arte del KV cache en 2026&lt;/a> — qué pasa dentro del motor a nivel del bloque, y qué ha llegado después.&lt;/li>
&lt;li>&lt;strong>Este&lt;/strong> — cómo se orquestan muchos modelos en cluster.&lt;/li>
&lt;/ol>
&lt;p>Si has llegado aquí, tienes el vocabulario y el mapa para sentarte en una reunión donde cinco personas tiren siglas y reconocer cada una en su sitio. Y, lo más importante, para empezar a tomar decisiones razonadas sobre por dónde empezar.&lt;/p>
&lt;h2 id="referencias">Referencias&lt;/h2>
&lt;p>Operators y proyectos cubiertos:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/ome-projects/ome">OME — Open Model Engine (GitHub)&lt;/a> — operator de LMSYS para LLM serving con SGLang/vLLM/TRT-LLM/Triton.&lt;/li>
&lt;li>&lt;a href="https://www.lmsys.org/blog/2025-07-08-ome/">Introducing OME (LMSYS Blog, jul 2025)&lt;/a> — anuncio y arquitectura.&lt;/li>
&lt;li>&lt;a href="https://github.com/vllm-project/production-stack">vLLM Production Stack (GitHub)&lt;/a> — Helm chart oficial de vLLM para K8s.&lt;/li>
&lt;li>&lt;a href="https://docs.vllm.ai/projects/production-stack/en/latest/deployment/">vLLM Production Stack docs&lt;/a> — instalación y configuración.&lt;/li>
&lt;li>&lt;a href="https://github.com/LMCache/LMCache">LMCache (GitHub)&lt;/a> — caché de KV con tiers.&lt;/li>
&lt;li>&lt;a href="https://developer.nvidia.com/dynamo">NVIDIA Dynamo&lt;/a> — sucesor de Triton.&lt;/li>
&lt;li>&lt;a href="https://www.infoq.com/news/2025/12/nvidia-dynamo-kubernetes/">NVIDIA Dynamo Addresses Multi-Node LLM Inference Challenges (InfoQ, dic 2025)&lt;/a> — integración K8s.&lt;/li>
&lt;li>&lt;a href="https://github.com/llm-d/llm-d">llm-d (GitHub)&lt;/a> — proyecto CNCF Sandbox.&lt;/li>
&lt;li>&lt;a href="https://thenewstack.io/llm-d-cncf-kubernetes-inference/">IBM, Red Hat, and Google donated llm-d to CNCF (The New Stack)&lt;/a> — anuncio KubeCon EU 2026.&lt;/li>
&lt;li>&lt;a href="https://siliconangle.com/2026/03/24/red-hat-bets-big-kubernetes-inference-llm-d-kubeconeu/">Red Hat bets big on Kubernetes inference with llm-d (SiliconANGLE, mar 2026)&lt;/a> — cobertura del anuncio.&lt;/li>
&lt;/ul>
&lt;p>Antecesores y primitivos:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://kserve.github.io/website/">KServe (sitio)&lt;/a> y &lt;a href="https://thenewstack.io/kserve-joins-cncf-to-standardize-ai-model-serving-on-kubernetes/">KServe joins CNCF (The New Stack)&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://kueue.sigs.k8s.io/">Kueue&lt;/a> — gang scheduling.&lt;/li>
&lt;li>&lt;a href="https://lws.sigs.k8s.io/">LeaderWorkerSet&lt;/a> — workloads coordinados como tensor parallel multi-pod.&lt;/li>
&lt;li>&lt;a href="https://keda.sh/">KEDA&lt;/a> — autoscaling por métricas externas.&lt;/li>
&lt;li>&lt;a href="https://gateway-api.sigs.k8s.io/">Gateway API&lt;/a> — sucesor del Ingress.&lt;/li>
&lt;/ul>
&lt;p>Análisis y perspectivas:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://jimmysong.io/blog/cloud-native-llm-inference-stack/">Building Efficient LLM Inference with the Cloud Native Quartet: KServe, vLLM, llm-d, and WG Serving (Jimmy Song)&lt;/a> — visión integradora.&lt;/li>
&lt;li>&lt;a href="https://dev.to/x4nent/complete-guide-to-llm-d-cncf-sandbox-kubernetes-native-distributed-llm-inference-1imj">Complete Guide to llm-d CNCF Sandbox (DEV Community)&lt;/a> — walkthrough operacional.&lt;/li>
&lt;li>Artículos previos en este blog: &lt;a href="https://blog.lo0.es/posts/kv-cache-fundamentos/">KV cache&lt;/a>, &lt;a href="https://blog.lo0.es/posts/vllm-kubernetes/">vLLM en Kubernetes&lt;/a>, &lt;a href="https://blog.lo0.es/posts/pagedattention-deep-dive/">PagedAttention deep dive&lt;/a>.&lt;/li>
&lt;/ul></description></item><item><title>PagedAttention por dentro: bloques, tabla de páginas, evicción y el estado del arte del KV cache en 2026</title><link>https://blog.lo0.es/posts/pagedattention-deep-dive/</link><pubDate>Mon, 18 May 2026 15:00:00 +0200</pubDate><guid>https://blog.lo0.es/posts/pagedattention-deep-dive/</guid><description>&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>PagedAttention (Kwon et al., SOSP 2023) fue la idea que convirtió la gestión del KV cache de un problema de &lt;strong>malloc clásico&lt;/strong> —reservar contiguo, malgastar el 60-80%— en un problema resuelto &lt;strong>como lo resuelven los sistemas operativos desde hace medio siglo&lt;/strong>: bloques pequeños de tamaño fijo, una tabla de páginas por proceso, asignación bajo demanda. El paper midió un desperdicio menor al 4% y 2-4× más throughput agregado en el mismo hardware. Tres años después, PagedAttention sigue siendo el modelo mental dominante, pero su implementación literal ya no es la de ningún sistema de inferencia serio: la propia documentación de vLLM califica al paper original de &amp;ldquo;documento histórico&amp;rdquo;. Han llegado &lt;strong>vAttention&lt;/strong> (paginar usando la MMU de CUDA, no la indirección software), &lt;strong>EvicPress&lt;/strong> (combinar compresión y evicción), &lt;strong>KVTC&lt;/strong> (transform coding del cache), &lt;strong>LaProx&lt;/strong> (evicción como aproximación matricial), &lt;strong>disaggregated serving&lt;/strong> (prefill y decode en GPUs distintas, en producción en NVIDIA Dynamo, llm-d, Mooncake y media docena más), &lt;strong>RadixAttention&lt;/strong> de SGLang (trie de prefijos compartidos, con hit rates del 85% en cargas de agentes) y la nueva generación de &lt;strong>speculative decoding&lt;/strong> (EAGLE-3, DeepSeek MTP, Mirror Speculative). Este artículo desmonta PagedAttention al nivel del bloque, explica qué hace vLLM hoy en su lugar, y traza el mapa del estado del arte para que no te pierdas eligiendo entre quince siglas en la primera reunión.&lt;/p>
&lt;blockquote>
&lt;p>Este artículo cierra una mini-serie. El primero —&lt;a href="https://blog.lo0.es/posts/kv-cache-fundamentos/">KV cache: la memoria de trabajo que sostiene la inferencia LLM&lt;/a>— explicó por qué cada token consume VRAM. El segundo —&lt;a href="https://blog.lo0.es/posts/vllm-kubernetes/">vLLM en Kubernetes: la pieza de inferencia LLM que sí escala&lt;/a>— mostró cómo se sirve eso en producción. Éste baja al fondo: cómo se gestiona el cache &lt;strong>dentro&lt;/strong> del motor, y qué hay después de PagedAttention.&lt;/p>
&lt;/blockquote>
&lt;h2 id="la-analogía-pasar-de-malloc-al-kernel-multiproceso">La analogía: pasar de &lt;code>malloc()&lt;/code> al kernel multiproceso&lt;/h2>
&lt;p>Un programa C ingenuo pide memoria con &lt;code>malloc(N)&lt;/code> y recibe un bloque contiguo de N bytes. Si pide muchos bloques de tamaños distintos y los libera en cualquier orden, el heap se llena de huecos: hay tres megabytes libres en total, pero ningún hueco contiguo de un megabyte, y el siguiente &lt;code>malloc(1MB)&lt;/code> falla. Fragmentación externa. Si reserva siempre el peor caso &amp;ldquo;para estar seguro&amp;rdquo; —&lt;code>malloc(MAX_POSSIBLE_SIZE)&lt;/code>— el heap se queda lleno con bloques medio vacíos. Fragmentación interna.&lt;/p>
&lt;p>Los sistemas operativos modernos no permiten que eso pase con la memoria virtual de un proceso. La memoria virtual se divide en &lt;strong>páginas&lt;/strong> (4 KB típicamente), cada una asignada a un &lt;strong>marco físico&lt;/strong> en RAM mediante una &lt;strong>tabla de páginas&lt;/strong> específica del proceso. El proceso ve un espacio contiguo enorme; el SO lo respalda con marcos físicos dispersos, asignados bajo demanda y liberados cuando dejan de usarse. El concepto tiene 50 años y funciona.&lt;/p>
&lt;p>Antes de PagedAttention, &lt;strong>los motores de inferencia LLM eran programas C ingenuos&lt;/strong>. Cada sesión reservaba un bloque contiguo de KV cache dimensionado al peor caso &lt;code>max_context_len × bytes_per_token × n_layers × 2&lt;/code>. Una conversación que usa 273 tokens reservaba sitio para 32 768. Cuando el motor servía 50 sesiones simultáneas, el 60-80% de la VRAM dedicada a KV cache estaba reservada y vacía. El paper de PagedAttention midió este desperdicio en cargas reales y propuso lo evidente: tratar el KV cache como &lt;strong>memoria virtual&lt;/strong>. Bloques físicos pequeños (16 tokens), tabla de páginas por sesión, asignación bajo demanda. El resultado: &amp;lt; 4% de desperdicio, 2-4× más throughput agregado en el mismo hardware.&lt;/p>
&lt;p>La idea no era nueva fuera del mundo LLM, era nueva &lt;strong>dentro&lt;/strong>. Y eso vale como contribución: a veces traer una técnica madura de otro campo es más impactante que inventar algo desde cero.&lt;/p>
&lt;h2 id="el-paper-original-en-cristiano">El paper original, en cristiano&lt;/h2>
&lt;p>Kwon et al. publicaron &lt;em>Efficient Memory Management for Large Language Model Serving with PagedAttention&lt;/em> en SOSP 2023 e implementaron simultáneamente vLLM, que en seis meses pasó de proyecto académico a &amp;ldquo;el motor de inferencia que todo el mundo usa&amp;rdquo;. Las tres aportaciones del paper, en orden de importancia:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Cuantificación del problema&lt;/strong>: medir el desperdicio en sistemas existentes y mostrar que el 60-80% de la VRAM se estaba quemando en &lt;em>peor-caso reservations&lt;/em> que no se usaban.&lt;/li>
&lt;li>&lt;strong>El algoritmo de paging&lt;/strong>: cómo dividir el KV cache, qué tamaño de bloque elegir, cómo gestionar la tabla de páginas en GPU.&lt;/li>
&lt;li>&lt;strong>El kernel CUDA&lt;/strong>: cómo implementar la operación de atención cuando los tokens de una secuencia están dispersos por la VRAM, sin destruir el rendimiento.&lt;/li>
&lt;/ol>
&lt;h3 id="el-modelo-de-bloques">El modelo de bloques&lt;/h3>
&lt;p>El KV cache se divide en bloques de tamaño fijo. La elección por defecto en vLLM es &lt;strong>16 tokens por bloque&lt;/strong>, decisión que el paper justifica con un barrido empírico: bloques más pequeños reducen la fragmentación interna pero aumentan el overhead de metadata y de indirección; bloques más grandes mejoran throughput pero pierden eficiencia. 16 es el punto razonable para los modelos y cargas medidas.&lt;/p>
&lt;p>Cada bloque almacena los &lt;strong>K y V de N tokens consecutivos&lt;/strong> de &lt;strong>una sola sesión&lt;/strong> en &lt;strong>una sola capa&lt;/strong> del modelo. Para un Llama 3 8B con 32 capas, una sesión de 128 tokens necesita aproximadamente &lt;code>128 / 16 × 32 = 256 bloques&lt;/code> (uno por capa por grupo de 16 tokens). Los bloques son lógicamente independientes entre sí: pueden vivir en cualquier dirección física de VRAM.&lt;/p>
&lt;h3 id="la-tabla-de-páginas-block-table">La tabla de páginas (block table)&lt;/h3>
&lt;p>Cada sesión tiene asociada una &lt;strong>block table&lt;/strong>: una lista ordenada de identificadores de bloques físicos. Cuando vLLM calcula la atención para el token 200 de la sesión X, mira la block table de X, encuentra que el bloque que contiene el token 200 está en la posición &lt;code>200 / 16 = 12&lt;/code> de la lista, lee qué bloque físico corresponde y va a buscarlo.&lt;/p>
&lt;p>La block table vive en VRAM, no en RAM como la tabla de páginas del SO. Si viviese en CPU, cada paso de decode tendría que hacer una indirección PCIe, lo que mataría el throughput. Está en VRAM, junto al cache, y el kernel CUDA la lee como una estructura más durante el cómputo.&lt;/p>
&lt;div class="diagram" style="max-width:720px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 720 280" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Block table apuntando a bloques físicos dispersos">
&lt;style>.title{font:600 13px sans-serif;fill:#222}.lbl{font:11px sans-serif;fill:#444}.bt{fill:#ffe9d6;stroke:#666}.blk{fill:#d6eaff;stroke:#666}.free{fill:#eee;stroke:#bbb;stroke-dasharray:3 2}.arr{stroke:#888;stroke-width:1.2;fill:none;marker-end:url(#ah)}&lt;/style>
&lt;defs>&lt;marker id="ah" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#888"/>&lt;/marker>&lt;/defs>
&lt;text x="120" y="20" text-anchor="middle" class="title">Block table (sesión X)&lt;/text>
&lt;text x="500" y="20" text-anchor="middle" class="title">VRAM (pool de bloques físicos)&lt;/text>
&lt;rect x="40" y="40" width="160" height="22" class="bt"/>&lt;text x="120" y="56" text-anchor="middle" class="lbl">posición 0 → bloque #7&lt;/text>
&lt;rect x="40" y="65" width="160" height="22" class="bt"/>&lt;text x="120" y="81" text-anchor="middle" class="lbl">posición 1 → bloque #2&lt;/text>
&lt;rect x="40" y="90" width="160" height="22" class="bt"/>&lt;text x="120" y="106" text-anchor="middle" class="lbl">posición 2 → bloque #11&lt;/text>
&lt;rect x="40" y="115" width="160" height="22" class="bt"/>&lt;text x="120" y="131" text-anchor="middle" class="lbl">posición 3 → bloque #5&lt;/text>
&lt;rect x="40" y="140" width="160" height="22" class="bt"/>&lt;text x="120" y="156" text-anchor="middle" class="lbl">posición 4 → bloque #9&lt;/text>
&lt;rect x="300" y="40" width="60" height="22" class="free"/>&lt;text x="330" y="56" text-anchor="middle" class="lbl">#0 libre&lt;/text>
&lt;rect x="365" y="40" width="60" height="22" class="free"/>&lt;text x="395" y="56" text-anchor="middle" class="lbl">#1 libre&lt;/text>
&lt;rect x="430" y="40" width="60" height="22" class="blk"/>&lt;text x="460" y="56" text-anchor="middle" class="lbl">#2 sesión X&lt;/text>
&lt;rect x="495" y="40" width="60" height="22" class="blk"/>&lt;text x="525" y="56" text-anchor="middle" class="lbl">#3 sesión Y&lt;/text>
&lt;rect x="560" y="40" width="60" height="22" class="blk"/>&lt;text x="590" y="56" text-anchor="middle" class="lbl">#4 sesión Z&lt;/text>
&lt;rect x="625" y="40" width="60" height="22" class="blk"/>&lt;text x="655" y="56" text-anchor="middle" class="lbl">#5 sesión X&lt;/text>
&lt;rect x="300" y="70" width="60" height="22" class="blk"/>&lt;text x="330" y="86" text-anchor="middle" class="lbl">#6 sesión Y&lt;/text>
&lt;rect x="365" y="70" width="60" height="22" class="blk"/>&lt;text x="395" y="86" text-anchor="middle" class="lbl">#7 sesión X&lt;/text>
&lt;rect x="430" y="70" width="60" height="22" class="free"/>&lt;text x="460" y="86" text-anchor="middle" class="lbl">#8 libre&lt;/text>
&lt;rect x="495" y="70" width="60" height="22" class="blk"/>&lt;text x="525" y="86" text-anchor="middle" class="lbl">#9 sesión X&lt;/text>
&lt;rect x="560" y="70" width="60" height="22" class="blk"/>&lt;text x="590" y="86" text-anchor="middle" class="lbl">#10 sesión Z&lt;/text>
&lt;rect x="625" y="70" width="60" height="22" class="blk"/>&lt;text x="655" y="86" text-anchor="middle" class="lbl">#11 sesión X&lt;/text>
&lt;path class="arr" d="M200,51 L365,51"/>
&lt;path class="arr" d="M200,76 L430,51"/>
&lt;path class="arr" d="M200,101 L625,76"/>
&lt;path class="arr" d="M200,126 L625,51"/>
&lt;path class="arr" d="M200,151 L495,81"/>
&lt;text x="360" y="200" text-anchor="middle" class="lbl">los bloques de una misma sesión están dispersos; la block table reconstruye su orden lógico&lt;/text>
&lt;text x="360" y="225" text-anchor="middle" class="lbl">cuando un bloque queda libre (sesión termina), vuelve al pool y otra sesión lo ocupa en el siguiente paso&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>Cuando una sesión genera su token N-ésimo, vLLM mira si el último bloque de la block table aún tiene huecos (&lt;code>N mod 16 != 0&lt;/code>). Si los tiene, escribe ahí. Si no, pide un bloque nuevo del &lt;strong>pool global&lt;/strong>, lo añade al final de la block table y escribe en su primera posición. Crecer la sesión cuesta &lt;strong>una asignación O(1) en el pool global más una append O(1) a la block table&lt;/strong>. Liberar una sesión devuelve sus bloques al pool: también O(N_bloques) y rapidísimo.&lt;/p>
&lt;h3 id="el-pool-de-bloques">El pool de bloques&lt;/h3>
&lt;p>El pool global se dimensiona al arrancar el motor. Lo típico:&lt;/p>
&lt;pre tabindex="0">&lt;code>bloques_disponibles = (VRAM_total - modelo - activations - overhead) / block_size_bytes
&lt;/code>&lt;/pre>&lt;p>Para una RTX 4090 (24 GB) sirviendo Llama 3 8B BF16 con cache también en BF16:&lt;/p>
&lt;pre tabindex="0">&lt;code>modelo: ~16 GB
activations: ~1.5 GB
overhead vLLM: ~1 GB
disponible para KV cache: ~5.5 GB
block_size = 16 tokens × 32 capas × 2 (K,V) × 8 KV heads × 128 head_dim × 2 bytes = 2 MB
bloques disponibles ≈ 5.5 GB / 2 MB ≈ 2800 bloques
tokens cacheables totales (todas sesiones) ≈ 2800 × 16 = 44800 ≈ 44 K tokens
&lt;/code>&lt;/pre>&lt;p>Si una sola sesión pide 32 K tokens, ocupa 2 000 bloques (de 2 800). Si las sesiones son más cortas, caben más simultáneas. El pool es &lt;strong>un recurso compartido global&lt;/strong>, no per-sesión, y ahí está la clave del aprovechamiento.&lt;/p>
&lt;h3 id="copy-on-write-para-sampling-paralelo">Copy-on-write para sampling paralelo&lt;/h3>
&lt;p>Una sutileza elegante del paper: cuando una petición usa sampling paralelo o beam search, las N secuencias &lt;strong>comparten el prefijo&lt;/strong> (el prompt + lo que se haya generado hasta el punto de divergencia). En lugar de duplicar el KV cache de ese prefijo, vLLM hace que las N secuencias &lt;strong>compartan los bloques físicos&lt;/strong> vía la block table. Solo cuando una secuencia diverge —genera un token distinto que las otras— vLLM &lt;strong>copia el último bloque&lt;/strong> afectado (no toda la secuencia) y la rama esa pasa a tener su propia versión.&lt;/p>
&lt;p>Esto es exactamente lo que hace el kernel de Linux con &lt;code>fork()&lt;/code>: copy-on-write de las páginas. La memoria solo se duplica cuando se modifica. En beam search con N=4 y prefijos largos, el ahorro es enorme.&lt;/p>
&lt;h3 id="el-kernel-cuda">El kernel CUDA&lt;/h3>
&lt;p>El reto técnico no obvio: el cómputo de atención &lt;strong>debe seguir la indirección de la block table&lt;/strong> para cada token. En la versión naïve (cache contiguo), el kernel asume que los tokens 0..N-1 de la sesión X están en direcciones contiguas y los lee de un tirón. Con paging, los tokens 0..15 están en el bloque #7, los 16..31 en el #2, los 32..47 en el #11, etc.&lt;/p>
&lt;p>El kernel &lt;code>paged_attention&lt;/code> de vLLM resuelve esto con &lt;strong>block-aware tiling&lt;/strong>: divide el cómputo de atención en chunks alineados con el tamaño de bloque (16 tokens), y para cada chunk localiza el bloque físico vía la block table y lo procesa. Es más complejo que el kernel contiguo, pero el coste medido es solo &lt;strong>5-10% de latencia adicional&lt;/strong> frente a la operación contigua equivalente, contra una ganancia de 2-4× en throughput agregado por la mejor utilización de VRAM. Compromiso aplastante.&lt;/p>
&lt;h2 id="evicción-y-preemption-qué-hace-cuando-el-pool-se-agota">Evicción y preemption: qué hace cuando el pool se agota&lt;/h2>
&lt;p>El KV cache crece. Cada token nuevo en cualquier sesión consume bloques. En un servidor con tráfico alto, el pool global se vacía. ¿Qué hacer cuando llega una nueva petición y no hay bloques libres?&lt;/p>
&lt;p>Tres opciones: &lt;strong>rechazar&lt;/strong> la petición (mala UX), &lt;strong>bloquear&lt;/strong> hasta que algo se libere (mala latencia), o &lt;strong>expulsar&lt;/strong> alguna sesión existente para hacer sitio (preemption). vLLM elige la tercera, con dos estrategias seleccionables:&lt;/p>
&lt;h3 id="estrategia-1-recompute">Estrategia 1: recompute&lt;/h3>
&lt;p>Cuando vLLM expulsa una sesión, &lt;strong>libera todos sus bloques&lt;/strong> y la pone en cola de espera. Cuando vuelve a haber sitio (otras sesiones terminan), vLLM rehace el prefill entero de la sesión expulsada desde el prompt original. El KV cache se reconstruye desde cero.&lt;/p>
&lt;p>Ventaja: liberación instantánea, no consume bandwidth de PCIe.
Coste: la sesión rehace &lt;strong>todo el cómputo del prefill&lt;/strong>, segundos o decenas de segundos para prompts largos.&lt;/p>
&lt;h3 id="estrategia-2-swap">Estrategia 2: swap&lt;/h3>
&lt;p>vLLM mueve los bloques de la sesión expulsada &lt;strong>a RAM de CPU&lt;/strong> (vía PCIe), liberando la VRAM. Cuando la sesión vuelva a tocar, vLLM la trae de vuelta a VRAM.&lt;/p>
&lt;p>Ventaja: conserva el cache, no rehace cómputo.
Coste: tiempo de transferencia PCIe (~32 GB/s en PCIe gen4 x16). Mover 4 GB de KV cache cuesta ~125 ms ida y vuelta.&lt;/p>
&lt;p>vLLM elige entre las dos en función del tamaño del cache de la sesión y de la latencia esperada. Para sesiones cortas, recompute suele ganar; para sesiones largas con prompts grandes, swap. Es configurable con &lt;code>--swap-space&lt;/code>.&lt;/p>
&lt;h3 id="el-problema-de-la-preemption-agresiva">El problema de la preemption agresiva&lt;/h3>
&lt;p>Hay un fallo de modo: si el sistema está saturado y vLLM no para de expulsar y reincorporar las mismas sesiones, todas hacen poco progreso y el throughput se hunde. Este es &lt;strong>thrashing&lt;/strong>, exactamente el mismo problema que tiene un SO cuando la presión de paginación es muy alta.&lt;/p>
&lt;p>La solución operativa es la misma que en SO: &lt;strong>admission control&lt;/strong>. Configurar &lt;code>--max-num-seqs&lt;/code> para limitar cuántas sesiones puede atender vLLM simultáneamente. Si llegan más, esperan en la cola HTTP. Mejor tener 10 sesiones avanzando rápido que 100 thrasheando.&lt;/p>
&lt;h2 id="lo-que-vllm-hace-hoy-más-allá-del-paper-original">Lo que vLLM hace hoy: más allá del paper original&lt;/h2>
&lt;p>La documentación oficial de vLLM señala que el &lt;a href="https://docs.vllm.ai/en/latest/design/paged_attention/">paper de PagedAttention es ya un documento histórico&lt;/a> que &lt;strong>ya no describe la implementación actual&lt;/strong>. ¿Qué ha cambiado?&lt;/p>
&lt;h3 id="chunked-prefill-integrado-con-paged-kv">Chunked prefill integrado con paged KV&lt;/h3>
&lt;p>El kernel original asumía que el prefill ocupaba el batch entero un paso, y el decode ocupaba batches separados. El motor actual mezcla prefill (troceado en chunks) con decode en el mismo paso, usando el mismo paged KV cache para ambos. Esto mejora la utilización de tensor cores cuando hay pocas peticiones en prefill y muchas en decode.&lt;/p>
&lt;h3 id="prefix-caching-cross-session">Prefix caching cross-session&lt;/h3>
&lt;p>El paper original ya tenía copy-on-write para sampling paralelo en una sola petición. La extensión natural fue compartir bloques de prefijo entre &lt;strong>peticiones distintas&lt;/strong> que llegan con el mismo system prompt. En vLLM se activa con &lt;code>--enable-prefix-caching&lt;/code>. Es una versión más simple que la de SGLang (no usa radix tree explícito, hace hash de bloques) pero efectiva: 30-70% mejora de TTFT en cargas con prompts compartidos.&lt;/p>
&lt;h3 id="sliding-window-attention">Sliding window attention&lt;/h3>
&lt;p>Modelos como Mistral 7B usan atención con ventana deslizante: solo atienden a los últimos K tokens (4 096 en Mistral). El motor mantiene únicamente los bloques de la ventana activa, liberando los más viejos. Esto cambia la economía: para esos modelos, el cache no crece sin límite.&lt;/p>
&lt;h3 id="flashattention-3-paged">FlashAttention-3 paged&lt;/h3>
&lt;p>Las versiones recientes de FlashAttention (especialmente FA-3) tienen kernels paged-aware optimizados para Hopper (H100). vLLM los usa por defecto en H100 cuando están disponibles, con ganancias adicionales del 15-30% sobre el kernel paged original.&lt;/p>
&lt;h2 id="vattention-paging-sin-reescribir-el-kernel">vAttention: paging sin reescribir el kernel&lt;/h2>
&lt;p>El paper de &lt;a href="https://arxiv.org/abs/2405.04437">vAttention (Prabhu et al., arxiv 2405.04437)&lt;/a> hace una observación incómoda: el coste de PagedAttention no es solo el 5-10% del kernel. Hay dos costes ocultos:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Inadaptable a kernels nuevos&lt;/strong>: cada vez que sale una optimización de atención (FlashAttention-2, FlashAttention-3, kernel custom), hay que &lt;strong>reescribir su versión paged&lt;/strong>. Eso ha hecho que vLLM frecuentemente esté 1-2 versiones por detrás del frente de FlashAttention.&lt;/li>
&lt;li>&lt;strong>Block tables en VRAM&lt;/strong>: pequeño pero constante. Para muchas sesiones, las block tables ocupan VRAM y cuestan accesos.&lt;/li>
&lt;/ol>
&lt;p>La propuesta de vAttention: usar &lt;strong>CUDA Virtual Memory Management (VMM)&lt;/strong>, las primitivas de virtual memory que NVIDIA expone desde CUDA 11.2. Con VMM puedes &lt;strong>reservar un rango virtual contiguo enorme&lt;/strong> y &lt;strong>asignar memoria física bajo demanda&lt;/strong> en porciones, mapeándolas en posiciones del rango virtual. El kernel de atención ve un rango contiguo (no necesita ser paged-aware); el runtime mete el paging dentro de la API de CUDA.&lt;/p>
&lt;p>Resultado medido en el paper: hasta &lt;strong>1.99× decode throughput&lt;/strong> sobre vLLM con FlashAttention-2 original. Y el kernel de atención es el de FlashAttention estándar, sin modificar.&lt;/p>
&lt;p>La idea es disruptiva porque sugiere que &lt;strong>la abstracción del paper de PagedAttention era inadecuada&lt;/strong>: el problema nunca fue que el cache tenía que ser físicamente paginado, sino que la asignación tenía que ser dinámica. La forma de resolverlo es delegar el paging al hardware (MMU + VMM de CUDA), no implementarlo en software.&lt;/p>
&lt;p>vAttention no ha desplazado a PagedAttention en vLLM por inercia y por consideraciones de portabilidad (VMM no está disponible en GPUs AMD ni Intel; PagedAttention sí). Pero los runtimes nuevos —y algunos forks de vLLM— ya lo están adoptando. Es plausible que en 2027 sea el default.&lt;/p>
&lt;h2 id="compresión-y-evicción-inteligente-lo-que-ha-llegado-en-2025-2026">Compresión y evicción inteligente: lo que ha llegado en 2025-2026&lt;/h2>
&lt;p>PagedAttention y vAttention atacan &lt;strong>dónde&lt;/strong> vive el cache. Otra línea de trabajo ataca &lt;strong>qué&lt;/strong> vive en el cache: si no necesitas todo el KV de un contexto largo, ¿por qué guardarlo todo?&lt;/p>
&lt;h3 id="streamingllm-xiao-et-al-2024-los-attention-sinks">StreamingLLM (Xiao et al., 2024): los attention sinks&lt;/h3>
&lt;p>El precursor conceptual. Observación: los primeros 4 tokens de cualquier contexto reciben atención desproporcionada de los tokens posteriores, incluso cuando semánticamente no son relevantes (son &amp;ldquo;sinks&amp;rdquo; para que el softmax se normalice). Si descartas todo el cache excepto los primeros 4 tokens más una ventana deslizante de los últimos K, el modelo sigue generando con calidad razonable indefinidamente.&lt;/p>
&lt;p>Impacto: permite &lt;strong>contexto efectivamente infinito&lt;/strong> con cache acotado. Coste: olvido real del contenido medio.&lt;/p>
&lt;h3 id="h2o-snapkv-2024-eviction-por-attention-score">H2O, SnapKV (2024): eviction por attention score&lt;/h3>
&lt;p>Variantes que mantienen un score acumulado de atención por token y, cuando el cache se llena, descartan los tokens con menor score. Son métodos por sesión, no por sistema: cada sesión decide qué partes de su propio cache descartar.&lt;/p>
&lt;h3 id="evicpress-microsoft-research-2026">EvicPress (Microsoft Research, 2026)&lt;/h3>
&lt;p>El paper &lt;a href="https://arxiv.org/abs/2512.14946">EvicPress: Joint KV-Cache Compression and Eviction for Efficient LLM Serving&lt;/a> hace una observación elegante: hasta ahora, evicción y compresión se han tratado como técnicas separadas. &lt;strong>Si vas a expulsar un bloque, ¿por qué no comprimirlo y guardarlo en RAM o NVMe en lugar de tirarlo?&lt;/strong> Y si lo tienes comprimido en un tier más lento, ¿cuándo merece la pena descomprimirlo y volver a HBM?&lt;/p>
&lt;p>EvicPress modela el problema como &lt;strong>optimización conjunta&lt;/strong> sobre múltiples tiers de almacenamiento (HBM, RAM, NVMe), aplica compresión lossy a los bloques candidatos a evicción y mantiene metadata para decidir cuándo trasladar de un tier a otro. Resultados: &lt;strong>2.19× faster TTFT&lt;/strong> a igual calidad de generación.&lt;/p>
&lt;p>La idea importa porque cambia el framing: el KV cache deja de ser &amp;ldquo;está o no está&amp;rdquo; para pasar a ser &amp;ldquo;está, en qué tier, con qué fidelidad&amp;rdquo;. Es directamente análogo a la jerarquía de caches L1/L2/L3 en CPUs.&lt;/p>
&lt;h3 id="kv-cache-transform-coding-kvtc-2026">KV Cache Transform Coding (KVTC, 2026)&lt;/h3>
&lt;p>&lt;a href="https://arxiv.org/abs/2511.01815">KV Cache Transform Coding for Compact Storage in LLM Inference (arxiv 2511.01815)&lt;/a> aplica al KV cache una técnica clásica de compresión de imágenes y vídeo: &lt;strong>transform coding&lt;/strong>, similar a DCT en JPEG/MPEG. Descompone los bloques de KV en una base de transformadas, descarta los coeficientes de menor energía y guarda el resto. Testeado con Llama 3, Mistral NeMo y R1-Qwen 2.5, &lt;strong>supera a quantization (INT4) y a SVD&lt;/strong> como métodos de compresión del cache. Importante: el resultado es &lt;strong>un cache comprimido reutilizable&lt;/strong>, no comprimido on-the-fly cada vez.&lt;/p>
&lt;h3 id="laprox-2026">LaProx (2026)&lt;/h3>
&lt;p>&lt;a href="https://arxiv.org/abs/2605.07234">LaProx: Reformulating KV Cache Eviction Problem for Long-Context LLM Inference (arxiv 2605.07234)&lt;/a> reformula la evicción de KV cache. Hasta ahora la mayoría de métodos son &lt;strong>head-wise y por promedios&lt;/strong> —miran scores por cabeza de atención y los promedian para decidir qué descartar—. LaProx la convierte en un problema &lt;strong>output-aware&lt;/strong> y &lt;strong>layer-wise&lt;/strong>: aproximar la multiplicación entre los attention maps y los projected value states como una matriz que se puede comprimir minimizando el error en la salida real del modelo, no en métricas auxiliares.&lt;/p>
&lt;p>La consecuencia práctica: las decisiones de evicción mejoran porque están alineadas con lo que realmente afecta a la generación, no con un proxy.&lt;/p>
&lt;h2 id="disaggregated-serving-separar-prefill-de-decode">Disaggregated serving: separar prefill de decode&lt;/h2>
&lt;p>PagedAttention y derivados optimizan &lt;strong>un motor&lt;/strong> sirviendo peticiones mezcladas. La siguiente revolución conceptual fue darse cuenta de que &lt;strong>prefill y decode no deberían correr en la misma GPU&lt;/strong>.&lt;/p>
&lt;h3 id="el-problema-de-mezclarlos">El problema de mezclarlos&lt;/h3>
&lt;p>Prefill es &lt;em>compute-bound&lt;/em>: usa los tensor cores intensamente. Decode es &lt;em>memory-bound&lt;/em>: mueve el KV cache a través del HBM. Si los mezclas en el mismo batch, una de las dos fases siempre va a ralentizar a la otra. Si entra una petición con prompt de 32 K tokens mientras hay 50 sesiones en decode, el prefill pausa a todas durante un segundo o más. Si llega una avalancha de prefills, los decodes en curso ven su latencia de token siguiente subir.&lt;/p>
&lt;h3 id="distserve-zhong-et-al-2024">DistServe (Zhong et al., 2024)&lt;/h3>
&lt;p>&lt;a href="https://arxiv.org/abs/2401.09670">DistServe (arxiv 2401.09670)&lt;/a> propuso lo evidente: &lt;strong>dedicar GPUs distintas a prefill y a decode&lt;/strong>. Las peticiones llegan a una GPU de prefill, que procesa el prompt y produce el KV cache inicial; ese KV cache se &lt;strong>transfiere&lt;/strong> a una GPU de decode, que se encarga de generar los tokens uno a uno. Resultado: &lt;strong>7.4× más goodput&lt;/strong>, o el mismo throughput con SLO 12.6× más estrictos.&lt;/p>
&lt;p>El truco no obvio es la transferencia del KV cache entre nodos. En GPUs con NVLink/NVSwitch del mismo nodo es trivial (~300 GB/s). Entre nodos con InfiniBand, el coste es manejable pero no despreciable. DistServe asume topologías que lo soporten.&lt;/p>
&lt;h3 id="splitwise-microsoft-2024">Splitwise (Microsoft, 2024)&lt;/h3>
&lt;p>&lt;a href="https://www.microsoft.com/en-us/research/publication/splitwise-efficient-generative-llm-inference-using-phase-splitting/">Splitwise&lt;/a> llevó la idea un paso más allá: &lt;strong>GPUs heterogéneas&lt;/strong>. Los prefills, compute-bound, corren en H100 o A100 (compute-optimizadas). Los decodes, memory-bound, corren en GPUs con más memoria por dólar pero menor compute (algunas variantes datacenter). Ganancia: &lt;strong>1.4× más throughput por dólar&lt;/strong>.&lt;/p>
&lt;h3 id="2026-producción">2026: producción&lt;/h3>
&lt;p>Disaggregated serving es ya &lt;strong>producción mainstream&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>NVIDIA Dynamo&lt;/strong> (sucesor de Triton): primitivo nativo.&lt;/li>
&lt;li>&lt;strong>vLLM&lt;/strong>: soporta disaggregation con flags &lt;code>--disaggregation-prefill-instances&lt;/code> / &lt;code>--disaggregation-decode-instances&lt;/code>.&lt;/li>
&lt;li>&lt;strong>SGLang&lt;/strong>, &lt;strong>Ray Serve LLM&lt;/strong>, &lt;strong>llm-d&lt;/strong>, &lt;strong>LMCache&lt;/strong>, &lt;strong>Mooncake&lt;/strong>: idem.&lt;/li>
&lt;li>Operadores con stacks propios: Fireworks, Perplexity, Meta, Amazon, Modular, DeepInfra, Weka.&lt;/li>
&lt;/ul>
&lt;p>&lt;a href="https://haoailab.com/blogs/distserve-retro/">&lt;em>Disaggregated Inference: 18 Months Later&lt;/em> (Hao AI Lab, 2026)&lt;/a> hace una retrospectiva: lo que en 2024 era investigación es, en 2026, &amp;ldquo;como tener separados webservers de bases de datos&amp;rdquo;. Asumido.&lt;/p>
&lt;h3 id="ppd-no-todos-los-prefills-son-iguales-2026">PPD: no todos los prefills son iguales (2026)&lt;/h3>
&lt;p>El refinamiento más reciente: &lt;a href="https://arxiv.org/pdf/2603.13358">Not All Prefills Are Equal: PPD Disaggregation for Multi-turn LLM Serving (arxiv 2603.13358)&lt;/a>. Observación: en cargas multi-turn (asistentes conversacionales, agentes), los &amp;ldquo;prefills&amp;rdquo; sucesivos tienen estructura distinta: el primer turno es prompt nuevo, los siguientes son extensiones del cache anterior. PPD discrimina entre tipos de prefill y los enruta a clusters distintos, mejorando aún el aprovechamiento.&lt;/p>
&lt;h2 id="radixattention-el-camino-alternativo-sglang">RadixAttention: el camino alternativo (SGLang)&lt;/h2>
&lt;p>Mientras vLLM iteraba sobre PagedAttention con prefix caching basado en hashing, &lt;a href="https://github.com/sgl-project/sglang">SGLang&lt;/a> tomó otra ruta: &lt;strong>mantener un trie (radix tree) explícito de todos los prefijos que existen actualmente en el cache&lt;/strong>.&lt;/p>
&lt;h3 id="la-idea">La idea&lt;/h3>
&lt;p>Cuando llega una petición nueva con tokens &lt;code>[t1, t2, t3, ..., tN]&lt;/code>, SGLang baja por el trie tokens-a-tokens. Si los primeros K tokens del prompt coinciden con un camino del trie, esos K tokens &lt;strong>ya tienen su KV cache calculado&lt;/strong> y se reutilizan. Solo se procesa el prefill de los tokens N-K restantes.&lt;/p>
&lt;p>Esto es prefix caching, pero con una estructura de datos que captura &lt;strong>todas las relaciones de prefijo entre todas las sesiones activas simultáneamente&lt;/strong>, no solo los matches exactos de hash. Si dos peticiones comparten 137 tokens iniciales, RadixAttention lo encuentra; si una tercera comparte 89, también.&lt;/p>
&lt;h3 id="eviction-inteligente-del-trie">Eviction inteligente del trie&lt;/h3>
&lt;p>Los nodos del trie tienen un score basado en cuántas veces se han usado recientemente y cuántos descendientes tienen. Cuando hay presión de memoria, SGLang descarta los nodos menos valiosos primero, manteniendo los caminos más &amp;ldquo;calientes&amp;rdquo;. Esto es LRU + un peso por reutilización potencial.&lt;/p>
&lt;h3 id="resultados">Resultados&lt;/h3>
&lt;p>&lt;a href="https://arxiv.org/pdf/2312.07104">El paper de SGLang&lt;/a> y benchmarks posteriores reportan &lt;strong>hasta 6.4× throughput vs sin prefix caching&lt;/strong>, y un gap consistente del &lt;strong>29%&lt;/strong> sobre el prefix caching basado en hash de vLLM en cargas mixtas. En cargas con prefijos muy compartidos (agentes ReAct, multi-tenant SaaS, repo Q&amp;amp;A con system prompt común), los hit rates llegan al &lt;strong>60-85%&lt;/strong> y el ahorro de coste por petición es de &lt;strong>5-12×&lt;/strong>.&lt;/p>
&lt;h3 id="producción">Producción&lt;/h3>
&lt;p>SGLang está en producción en xAI (sirviendo Grok 3) y Microsoft Azure (DeepSeek R1 en GPUs AMD), entre otros. No es un experimento; es un sistema de inferencia maduro.&lt;/p>
&lt;h3 id="cuándo-elegirlo-sobre-vllm">Cuándo elegirlo sobre vLLM&lt;/h3>
&lt;p>Para cargas con prefijos compartidos masivos y predecibles, &lt;strong>SGLang gana claramente&lt;/strong>. Para cargas genéricas mezcladas, &lt;strong>vLLM rinde mejor por simplicidad operativa&lt;/strong>. El criterio operativo: si tu hit rate de prefix caching estimado en vLLM pasaría del 50%, plantéate SGLang.&lt;/p>
&lt;h2 id="speculative-decoding-la-dimensión-ortogonal">Speculative decoding: la dimensión ortogonal&lt;/h2>
&lt;p>PagedAttention y sus sucesores optimizan &lt;strong>dónde y cómo&lt;/strong> vive el cache. Speculative decoding ataca &lt;strong>cómo se generan los tokens&lt;/strong>, ortogonalmente al cache. La idea genérica: usar un modelo pequeño y rápido para &lt;em>adivinar&lt;/em> varios tokens por adelantado, validarlos en paralelo con el modelo grande y aceptar los que coinciden.&lt;/p>
&lt;h3 id="eagle-3-2025">EAGLE-3 (2025)&lt;/h3>
&lt;p>&lt;a href="https://huggingface.co/papers/2401.15077">EAGLE-3 (huggingface.co/papers/2401.15077, versión 3 de 2025)&lt;/a> entrena una cabeza auto-regresiva pequeña que se condiciona en &lt;strong>tres puntos del hidden state del modelo target&lt;/strong> (early, middle, late layers) en lugar de solo en el último. Esta fusión tri-layer es la razón por la que EAGLE-3 supera a EAGLE-2 en un &lt;strong>20-40%&lt;/strong>. Latencia medida: &lt;strong>2-6× speedup&lt;/strong> según tamaño de modelo y batch.&lt;/p>
&lt;h3 id="medusa-y-deepseek-mtp">Medusa y DeepSeek MTP&lt;/h3>
&lt;p>Medusa fija N cabezas de decodificación adicionales al modelo, cada una prediciendo posición +1, +2, +3. DeepSeek-V3 ships con MTP (Multi-Token Prediction) nativo, n=4, &lt;strong>entrenado conjuntamente&lt;/strong> con el modelo principal (no es un drafter externo). En inferencia, basta un flag en SGLang o vLLM (&lt;code>--speculative-model deepseek-v3-mtp&lt;/code>) y obtienes &lt;strong>1.8× speedup out of the box&lt;/strong>, sin entrenar nada adicional, sin pesos extras que hospedar.&lt;/p>
&lt;h3 id="mirror-speculative-decoding-2025">Mirror Speculative Decoding (2025)&lt;/h3>
&lt;p>&lt;a href="https://arxiv.org/pdf/2510.13161">Mirror Speculative Decoding (arxiv 2510.13161)&lt;/a> ataca un límite que se daba por dado: la verificación de los tokens especulados sigue siendo serial dentro del modelo target. Mirror Decoding reorganiza el cómputo para &lt;strong>paralelizar también la verificación&lt;/strong>, rompiendo la barrera serial del paradigma original. Las ganancias añadidas dependen del modelo y del batch, pero el paper lo posiciona como el próximo paso de la trayectoria EAGLE → EAGLE-2 → EAGLE-3.&lt;/p>
&lt;h3 id="estado-en-2026">Estado en 2026&lt;/h3>
&lt;p>Speculative decoding &lt;strong>dejó de ser optimización experimental en 2026&lt;/strong> para convertirse en &lt;strong>capa por defecto de cualquier stack serio&lt;/strong>. Combinado con KV cache optimizado, los números reportados son &lt;strong>2.8× menos latencia&lt;/strong> y &lt;strong>47% menos coste por token&lt;/strong>.&lt;/p>
&lt;p>Caveat operativo: speculative decoding es contraproducente en cargas de baja concurrencia. Si el modelo target tiene poco batch para llenar la GPU, las cabezas especulativas no compensan su overhead. Por debajo de ~4 sesiones simultáneas, suele bajar el throughput. Por encima, lo sube. Mídelo en tu carga antes de activarlo.&lt;/p>
&lt;h2 id="implicaciones-operativas-el-config-2026-para-vllm">Implicaciones operativas: el config 2026 para vLLM&lt;/h2>
&lt;p>Si en 2026 montas vLLM en producción sin pensar mucho, los flags razonables por defecto son:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">args&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- --&lt;span class="l">model=...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- --&lt;span class="l">tensor-parallel-size=N&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- --&lt;span class="l">max-model-len=...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- --&lt;span class="l">kv-cache-dtype=fp8 &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># cuantización del cache&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- --&lt;span class="l">enable-prefix-caching &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># ahorro fácil en cargas con prompts compartidos&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- --&lt;span class="l">enable-chunked-prefill &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># mejor mezcla prefill/decode&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- --&lt;span class="l">gpu-memory-utilization=0.92 &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># ya cubierto en el post anterior&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- --&lt;span class="l">speculative-model=... &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># SI batch sostenido &amp;gt;4&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- --&lt;span class="l">num-speculative-tokens=4 &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># acompaña al anterior&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- --&lt;span class="l">max-num-seqs=128 &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># admission control para evitar thrashing&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- --&lt;span class="l">preemption-mode=recompute &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># o swap si sesiones largas&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Para cargas con prefijos masivamente compartidos (agentes), considera &lt;strong>migrar a SGLang&lt;/strong>: el delta de eficiencia compensa la curva de aprendizaje. Para cargas de baja latencia con modelos estables (entrenados in-house, no cambias cada semana), &lt;strong>TensorRT-LLM&lt;/strong> sigue ganando en latencia pura. Para todo lo demás —que es la mayoría—, vLLM con los flags de arriba está dentro del 10% del óptimo en throughput.&lt;/p>
&lt;p>Para arquitecturas grandes (&amp;gt;100 sesiones concurrentes, SLO estricto), &lt;strong>disaggregated serving&lt;/strong> ya no es opcional. NVIDIA Dynamo o llm-d como orquestadores; vLLM o SGLang como motores debajo. La división típica: 1 nodo de prefill por cada 3-4 de decode, ajustando ratios según la longitud media de los prompts.&lt;/p>
&lt;h2 id="trampas-y-mitos-comunes">Trampas y mitos comunes&lt;/h2>
&lt;h3 id="pagedattention-vs-vattention-como-dilema">&amp;ldquo;PagedAttention vs vAttention&amp;rdquo; como dilema&lt;/h3>
&lt;p>No es un dilema. vAttention es una optimización de runtime; el modelo mental sigue siendo paging. La elección es entre dos implementaciones del mismo concepto. Operativamente: si tienes la versión de vLLM que lo soporta y CUDA VMM disponible, vAttention da más throughput; si no, paged va perfectamente.&lt;/p>
&lt;h3 id="cache-compression-sin-probar-calidad">&amp;ldquo;Cache compression sin probar calidad&amp;rdquo;&lt;/h3>
&lt;p>La industria de papers de compresión es prolífica y los benchmarks varían enormemente entre los del autor y los reales en producción. Compresión 8× &lt;em>parece&lt;/em> mágico hasta que mides degradación en tu corpus real. &lt;strong>Siempre evalúa con tus datos antes de activar compresión agresiva.&lt;/strong> Un FP8 cache es seguro casi siempre. Un INT4 cache requiere medir caso por caso.&lt;/p>
&lt;h3 id="prefix-caching-con-prompts-no-determinísticos">&amp;ldquo;Prefix caching con prompts no determinísticos&amp;rdquo;&lt;/h3>
&lt;p>Si tu pipeline inyecta timestamps, IDs únicos o cualquier variabilidad en el system prompt, &lt;strong>el hit rate de prefix caching se cae a cero&lt;/strong>. Es la trampa más común. Para que funcione, los prompts compartidos deben ser &lt;strong>bit-a-bit idénticos&lt;/strong>. Estructura los prompts en capas: parte estática primero, variable al final.&lt;/p>
&lt;h3 id="speculative-decoding-en-cargas-bajas">&amp;ldquo;Speculative decoding en cargas bajas&amp;rdquo;&lt;/h3>
&lt;p>Ya lo mencionamos: por debajo de ~4 sesiones simultáneas, speculative suele ser contraproducente. Si tu carga es batch puro o muy esporádica, &lt;strong>no la actives&lt;/strong>.&lt;/p>
&lt;h3 id="disaggregated-en-cluster-sin-red-rápida">&amp;ldquo;Disaggregated en cluster sin red rápida&amp;rdquo;&lt;/h3>
&lt;p>Si tu inter-nodo es Ethernet 25 GbE o peor, la transferencia del KV cache entre prefill y decode se convierte en cuello de botella. Disaggregation es para clusters con InfiniBand o RoCE 100/200/400 GbE. Sin eso, mejor colocated.&lt;/p>
&lt;h2 id="lo-que-no-hemos-cubierto">Lo que no hemos cubierto&lt;/h2>
&lt;p>Hay terreno suficiente para otra serie:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Mooncake (Kimi/Moonshot, 2024+)&lt;/strong>: KV cache como &lt;strong>pool compartido entre instancias&lt;/strong>, persistente en RAM/NVMe. Producción real con cientos de millones de queries.&lt;/li>
&lt;li>&lt;strong>LMCache&lt;/strong>: cache de KV persistente en disco entre arranques de vLLM. Reduce el coste de los primeros tokens en cargas con repetición temporal.&lt;/li>
&lt;li>&lt;strong>vLLM Production Stack&lt;/strong>: distribución k8s-native de vLLM con HPA, métricas, multi-modelo, ya probada en producción a escala.&lt;/li>
&lt;li>&lt;strong>Inference scheduling teórico&lt;/strong>: hay literatura aplicando CFS-like algorithms (el scheduler de Linux) al LLM serving. Promete fairness multi-tenant medible. Aún en fase académica.&lt;/li>
&lt;li>&lt;strong>Quantization del modelo combinada con quantization del cache&lt;/strong>: AWQ/GPTQ sobre los pesos + FP8 sobre el cache + INT4 sobre cache evictado. La pirámide completa.&lt;/li>
&lt;/ul>
&lt;h2 id="referencias">Referencias&lt;/h2>
&lt;p>Los papers fundacionales y las extensiones más leídas, en orden cronológico:&lt;/p>
&lt;ul>
&lt;li>Kwon et al., &lt;a href="https://arxiv.org/abs/2309.06180">&lt;em>Efficient Memory Management for Large Language Model Serving with PagedAttention&lt;/em>&lt;/a> (SOSP 2023) — paper original.&lt;/li>
&lt;li>Dao et al., &lt;a href="https://arxiv.org/abs/2307.08691">&lt;em>FlashAttention-2&lt;/em>&lt;/a> (2023) y &lt;em>FlashAttention-3&lt;/em> (2024) — kernels de atención sobre los que vLLM y vAttention apoyan.&lt;/li>
&lt;li>Xiao et al., &lt;a href="https://arxiv.org/abs/2309.17453">&lt;em>Efficient Streaming Language Models with Attention Sinks&lt;/em>&lt;/a> (StreamingLLM, 2024).&lt;/li>
&lt;li>Zhong et al., &lt;a href="https://arxiv.org/abs/2401.09670">&lt;em>DistServe: Disaggregating Prefill and Decoding for Goodput-optimized LLM Serving&lt;/em>&lt;/a> (OSDI 2024).&lt;/li>
&lt;li>Patel et al., &lt;a href="https://www.microsoft.com/en-us/research/publication/splitwise-efficient-generative-llm-inference-using-phase-splitting/">&lt;em>Splitwise: Efficient Generative LLM Inference Using Phase Splitting&lt;/em>&lt;/a> (Microsoft, 2024).&lt;/li>
&lt;li>Li et al., &lt;a href="https://huggingface.co/papers/2401.15077">&lt;em>EAGLE: Speculative Sampling Requires Rethinking Feature Uncertainty&lt;/em>&lt;/a> (2024) y EAGLE-2/3 (2024-2025).&lt;/li>
&lt;li>Prabhu et al., &lt;a href="https://arxiv.org/abs/2405.04437">&lt;em>vAttention: Dynamic Memory Management for Serving LLMs without PagedAttention&lt;/em>&lt;/a> (Microsoft, 2024-2025).&lt;/li>
&lt;li>Zheng et al., &lt;a href="https://arxiv.org/pdf/2312.07104">&lt;em>SGLang: Efficient Execution of Structured Language Model Programs&lt;/em>&lt;/a> (RadixAttention, 2024).&lt;/li>
&lt;li>DeepSeek-AI, &lt;a href="https://arxiv.org/abs/2412.19437">&lt;em>DeepSeek-V3 Technical Report&lt;/em>&lt;/a> (2024) — MTP nativo, base de speculative decoding del estado del arte.&lt;/li>
&lt;li>&lt;a href="https://arxiv.org/pdf/2510.13161">&lt;em>Mirror Speculative Decoding: Breaking the Serial Barrier in LLM Inference&lt;/em>&lt;/a> (2025).&lt;/li>
&lt;li>&lt;a href="https://arxiv.org/abs/2511.01815">&lt;em>KV Cache Transform Coding for Compact Storage in LLM Inference&lt;/em>&lt;/a> (KVTC, 2026).&lt;/li>
&lt;li>&lt;a href="https://arxiv.org/abs/2512.14946">&lt;em>EvicPress: Joint KV-Cache Compression and Eviction for Efficient LLM Serving&lt;/em>&lt;/a> (Microsoft Research, 2026).&lt;/li>
&lt;li>&lt;a href="https://arxiv.org/abs/2605.07234">&lt;em>LaProx: Reformulating KV Cache Eviction Problem for Long-Context LLM Inference&lt;/em>&lt;/a> (2026).&lt;/li>
&lt;li>&lt;a href="https://arxiv.org/pdf/2603.13358">&lt;em>Not All Prefills Are Equal: PPD Disaggregation for Multi-turn LLM Serving&lt;/em>&lt;/a> (2026).&lt;/li>
&lt;/ul>
&lt;p>Operacional:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://docs.vllm.ai/en/latest/design/paged_attention/">vLLM Paged Attention design doc&lt;/a> — la propia doc señala que el paper original es ya &amp;ldquo;historical&amp;rdquo;.&lt;/li>
&lt;li>&lt;a href="https://haoailab.com/blogs/distserve-retro/">Disaggregated Inference: 18 Months Later&lt;/a> — Hao AI Lab @ UCSD, retrospectiva de la transición a disaggregated.&lt;/li>
&lt;li>&lt;a href="https://www.marktechpost.com/2026/04/29/top-10-kv-cache-compression-techniques-for-llm-inference-reducing-memory-overhead-across-eviction-quantization-and-low-rank-methods/">Top 10 KV Cache Compression Techniques for LLM Inference&lt;/a> — survey reciente útil como mapa.&lt;/li>
&lt;li>Artículos anteriores en este blog: &lt;a href="https://blog.lo0.es/posts/kv-cache-fundamentos/">KV cache: la memoria de trabajo que sostiene la inferencia LLM&lt;/a> y &lt;a href="https://blog.lo0.es/posts/vllm-kubernetes/">vLLM en Kubernetes: la pieza de inferencia LLM que sí escala&lt;/a>.&lt;/li>
&lt;/ul></description></item></channel></rss>