<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Model-Streamer on lo0 — Blog Técnico</title><link>https://blog.lo0.es/tags/model-streamer/</link><description>Recent content in Model-Streamer on lo0 — Blog Técnico</description><generator>Hugo -- gohugo.io</generator><language>es</language><lastBuildDate>Thu, 11 Jun 2026 13:00:00 +0000</lastBuildDate><atom:link href="https://blog.lo0.es/tags/model-streamer/index.xml" rel="self" type="application/rss+xml"/><item><title>Acelerar el cold start de modelos: de minutos a segundos</title><link>https://blog.lo0.es/posts/acelerar-cold-start-carga-modelos-tensorizer/</link><pubDate>Thu, 11 Jun 2026 13:00:00 +0000</pubDate><guid>https://blog.lo0.es/posts/acelerar-cold-start-carga-modelos-tensorizer/</guid><description>&lt;blockquote>
&lt;p>Esta es la &lt;strong>tercera tanda&lt;/strong> de una serie operativa sobre exprimir un cluster LLM on-premise genérico de &lt;strong>4×H100 SXM 80 GB con NVLink&lt;/strong>. Las hermanas de esta tanda son &lt;a href="https://blog.lo0.es/posts/multimodal-vlm-on-premise-vllm/">Multimodal: servir un VLM on-premise con vLLM&lt;/a> —añadir visión al mismo motor— y &lt;a href="https://blog.lo0.es/posts/finops-multi-tenancy-gpu-litellm/">FinOps y multi-tenancy de GPU con LiteLLM&lt;/a> —repartir y cobrar la GPU entre equipos—. Este post toma el problema que &lt;a href="https://blog.lo0.es/posts/del-disco-a-la-hbm-cold-start-carga-modelo/">Del disco a la HBM&lt;/a> dejó planteado en lo conceptual y lo convierte en runbook: cómo se baja de verdad el cold start, knob a knob, para que la elasticidad sea usable.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>El post &lt;a href="https://blog.lo0.es/posts/del-disco-a-la-hbm-cold-start-carga-modelo/">Del disco a la HBM&lt;/a> dejó una idea incómoda: &lt;strong>mover los pesos del disco a la HBM es solo una de las cinco partidas del cold start&lt;/strong>. Las otras cuatro —arrancar el proceso, crear el contexto CUDA, montar el allocator y, la más cara que casi nadie mira, &lt;strong>capturar los CUDA graphs y compilar los kernels JIT&lt;/strong>— no las toca ningún loader rápido. Por eso un servidor que carga los pesos en 6 s puede seguir tardando &lt;strong>90 s&lt;/strong> en estar listo. Este runbook ataca las cinco. Para los pesos: &lt;strong>safetensors&lt;/strong> (mmap, zero-copy, sin rebote por un buffer FP32 de host) frente al &lt;code>pickle&lt;/code> de &lt;code>torch.load&lt;/code>; &lt;strong>Tensorizer&lt;/strong> (CoreWeave) que serializa el modelo en un fichero y lo &lt;strong>streamea tensor a tensor directo a la GPU&lt;/strong> desde objetos/HTTP/S3; y el &lt;strong>Run:ai Model Streamer&lt;/strong> (de NVIDIA), que &lt;strong>lee en concurrencia y solapa la lectura con la copia H2D&lt;/strong> —los tres se activan con una bandera, &lt;code>--load-format&lt;/code>, en vLLM—. Para lo no-pesos: &lt;strong>caché de compilación de torch.compile/Inductor&lt;/strong> (que se persiste y se reusa entre arranques), &lt;strong>acotar la captura de CUDA graphs&lt;/strong> a los batch sizes que de verdad usas (la captura por defecto puede comerse ~54 s), y el &lt;strong>sleep mode&lt;/strong> como atajo que se salta las cinco partidas. Las matemáticas: $t = W/B$ para los pesos, el ahorro de &lt;strong>solapar&lt;/strong> lectura y H2D frente a hacerlo en serie, y un cold start de 70B descompuesto partida a partida. Y la economía: cuándo &lt;strong>scale-to-zero con pre-warm&lt;/strong> gana a &lt;strong>mantener una réplica caliente&lt;/strong>. Sobre el cluster genérico 4×H100 SXM 80 GB.&lt;/p>
&lt;h2 id="la-analogía-abrir-la-cocina-por-la-mañana">La analogía: abrir la cocina por la mañana&lt;/h2>
&lt;p>Un restaurante no abre con tocar un interruptor. Quien haya trabajado en uno lo sabe: la hora de apertura la decide &lt;strong>todo lo que pasa antes&lt;/strong>, y casi nada de ello es &amp;ldquo;traer la comida&amp;rdquo;.&lt;/p>
&lt;p>El primer cocinero llega a un local frío y a oscuras. Tiene que: &lt;strong>encender las luces y arrancar los sistemas&lt;/strong> (el init del proceso); &lt;strong>encender los hornos y dejar que cojan temperatura&lt;/strong> (el contexto CUDA, que tarda lo suyo en estar operativo); &lt;strong>sacar las tablas, ordenar la cámara, organizar el espacio de trabajo&lt;/strong> (el allocator de memoria, que reserva y estructura la HBM); &lt;strong>bajar al almacén y subir el género&lt;/strong> —las cajas de verdura, la carne, el pescado— &lt;strong>y colocarlo en su sitio&lt;/strong> (la carga de pesos del disco a la HBM, el tema entero de &lt;a href="https://blog.lo0.es/posts/del-disco-a-la-hbm-cold-start-carga-modelo/">Del disco a la HBM&lt;/a>); y, lo que más se olvida, &lt;strong>afilar todos los cuchillos, montar el mise en place, hacer los fondos, precalentar las salsas madre&lt;/strong> —toda la preparación que no es un ingrediente pero sin la cual no sale un solo plato— (la captura de CUDA graphs y la compilación JIT de los kernels).&lt;/p>
&lt;p>Aquí está la trampa que &lt;a href="https://blog.lo0.es/posts/del-disco-a-la-hbm-cold-start-carga-modelo/">Del disco a la HBM&lt;/a> ya anticipó: &lt;strong>si solo optimizas el género, sigues abriendo tarde&lt;/strong>. Puedes contratar el mejor servicio de reparto del mundo, montacargas rapidísimo, cajas que se colocan solas. Pero si el cocinero todavía está afilando cuchillos y esperando a que los hornos cojan temperatura, la cocina &lt;strong>no sirve el primer plato&lt;/strong>. El género llegó pronto y el restaurante sigue cerrado.&lt;/p>
&lt;p>El runbook de hoy es, literalmente, la lista de todo lo que hay que hacer para que la cocina abra a su hora —y cuál de esas tareas se puede acelerar, cuál se puede dejar hecha de la noche anterior (cachear), y cuándo sale más a cuenta no apagar nunca la cocina (réplica caliente) que volver a encenderla cada mañana (scale-to-zero).&lt;/p>
&lt;h2 id="las-cinco-partidas-del-cold-start">Las cinco partidas del cold start&lt;/h2>
&lt;p>Cuando un pod de inferencia nace y hasta que devuelve su primer token, el reloj corre por cinco sitios distintos. Conviene nombrarlos porque &lt;strong>cada optimización ataca a uno o dos, nunca a todos&lt;/strong>:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Init del proceso.&lt;/strong> Arrancar el intérprete de Python, importar el motor (vLLM, sus dependencias), parsear la configuración. Segundos, y crece con el tamaño del entorno y los &lt;code>import&lt;/code> de CUDA/PyTorch.&lt;/li>
&lt;li>&lt;strong>Contexto CUDA.&lt;/strong> El primer &lt;code>cudaSetDevice&lt;/code> / &lt;code>cudaFree(0)&lt;/code> inicializa el runtime de CUDA contra el driver: carga el contexto, mapea la GPU. No es instantáneo —del orden de uno a varios segundos por GPU según driver y número de dispositivos.&lt;/li>
&lt;li>&lt;strong>Allocator.&lt;/strong> vLLM monta su gestor de memoria sobre la HBM (caching allocator de PyTorch más el reparto del KV-cache). Reservar y estructurar decenas de GB tiene su coste.&lt;/li>
&lt;li>&lt;strong>Carga de pesos disco→HBM.&lt;/strong> El trayecto que &lt;a href="https://blog.lo0.es/posts/del-disco-a-la-hbm-cold-start-carga-modelo/">Del disco a la HBM&lt;/a> diseccionó: disco → page cache → buffer de host → PCIe → HBM. Lo que casi todo el mundo cree que &lt;strong>es&lt;/strong> el cold start, y es solo una de las cinco.&lt;/li>
&lt;li>&lt;strong>Captura de CUDA graphs + JIT de kernels.&lt;/strong> vLLM captura grafos de CUDA para las distintas formas de batch y compila kernels con &lt;code>torch.compile&lt;/code>/Inductor (y backends como DeepGEMM o FlashInfer). Esta partida es la gran ignorada: &lt;strong>la captura de graphs por defecto en vLLM tarda del orden de 54 s&lt;/strong> porque cubre un abanico amplio de batch sizes (&lt;a href="https://docs.vllm.ai/en/latest/design/torch_compile/">vLLM, &lt;em>torch.compile integration&lt;/em>&lt;/a>; &lt;a href="https://developers.redhat.com/articles/2025/09/03/vllm-torchcompile-efficient-llm-inference-pytorch">Red Hat, &lt;em>vLLM with torch.compile&lt;/em>&lt;/a>).&lt;/li>
&lt;/ol>
&lt;div class="diagram" style="max-width:760px;margin:1.4rem auto;">
&lt;svg viewBox="0 0 760 300" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Las cinco partidas del cold start como barras de tiempo apiladas">
&lt;text x="380" y="24" text-anchor="middle" font-family="sans-serif" font-size="14" font-weight="700" fill="currentColor">Las cinco partidas de un cold start (ejemplo 70B, loader por defecto)&lt;/text>
&lt;text x="380" y="44" text-anchor="middle" font-family="sans-serif" font-size="11" fill="currentColor" opacity="0.75">ancho proporcional al tiempo · escala 0–110 s&lt;/text>
&lt;!-- eje -->
&lt;line x1="40" y1="250" x2="720" y2="250" stroke="currentColor" stroke-width="1.2"/>
&lt;text x="40" y="268" text-anchor="middle" font-family="sans-serif" font-size="9.5" fill="currentColor">0 s&lt;/text>
&lt;text x="380" y="268" text-anchor="middle" font-family="sans-serif" font-size="9.5" fill="currentColor">55 s&lt;/text>
&lt;text x="720" y="268" text-anchor="middle" font-family="sans-serif" font-size="9.5" fill="currentColor">110 s&lt;/text>
&lt;!-- barras: x=40 base, 680px = 110s => 6.18 px/s -->
&lt;!-- init 4s = 25px -->
&lt;rect x="40" y="70" width="25" height="30" fill="#8b5cf6" fill-opacity="0.85"/>
&lt;text x="160" y="89" font-family="sans-serif" font-size="11" fill="currentColor">1 · init proceso — ~4 s&lt;/text>
&lt;!-- contexto CUDA 6s = 37px, start 65 -->
&lt;rect x="65" y="106" width="37" height="30" fill="#3b82f6" fill-opacity="0.85"/>
&lt;text x="160" y="125" font-family="sans-serif" font-size="11" fill="currentColor">2 · contexto CUDA — ~6 s&lt;/text>
&lt;!-- allocator 4s = 25px, start 102 -->
&lt;rect x="102" y="142" width="25" height="30" fill="#22c55e" fill-opacity="0.85"/>
&lt;text x="160" y="161" font-family="sans-serif" font-size="11" fill="currentColor">3 · allocator — ~4 s&lt;/text>
&lt;!-- pesos 40s = 247px, start 127 -->
&lt;rect x="127" y="178" width="247" height="30" fill="#f59e0b" fill-opacity="0.9"/>
&lt;text x="384" y="197" font-family="sans-serif" font-size="11" fill="currentColor">4 · carga de pesos disco→HBM — ~40 s&lt;/text>
&lt;!-- graphs+jit 50s = 309px, start 374 -->
&lt;rect x="374" y="214" width="309" height="30" fill="#ef4444" fill-opacity="0.9"/>
&lt;text x="690" y="233" text-anchor="end" font-family="sans-serif" font-size="11" fill="#ffffff" font-weight="600">5 · CUDA graphs + JIT — ~50 s&lt;/text>
&lt;text x="380" y="290" text-anchor="middle" font-family="sans-serif" font-size="10.5" fill="currentColor" opacity="0.85">total ≈ 104 s · optimizar solo la partida 4 deja la 5 intacta — y la 5 es la mayor aquí&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>La lección que el diagrama grita: en un servidor moderno con &lt;code>torch.compile&lt;/code> y CUDA graphs, la &lt;strong>partida 5 puede ser tan grande o mayor que la 4&lt;/strong>. Acelerar la carga de pesos de 40 s a 6 s es un avance enorme, pero si dejas la captura de graphs en 50 s, el cold start cae de 104 s a ~70 s, no a 12 s. &lt;strong>Hay que atacar las dos mitades.&lt;/strong>&lt;/p>
&lt;h2 id="acelerar-la-partida-4-la-carga-de-los-pesos">Acelerar la partida 4: la carga de los pesos&lt;/h2>
&lt;h3 id="safetensors-frente-a-pickle-el-formato-manda">safetensors frente a pickle: el formato manda&lt;/h3>
&lt;p>El primer knob es el formato en disco. El viejo &lt;code>torch.load&lt;/code> usa &lt;strong>&lt;code>pickle&lt;/code>&lt;/strong>, que tiene dos problemas. El de seguridad es conocido: deserializar un pickle &lt;strong>ejecuta código Python arbitrario&lt;/strong> —el protocolo &lt;code>__reduce__&lt;/code> permite que el fichero invoque cualquier callable al cargar, así que cada descarga de un modelo era un vector de ejecución remota (&lt;a href="https://huggingface.co/docs/safetensors/index">HuggingFace, &lt;em>Safetensors&lt;/em>&lt;/a>). El de rendimiento es el que nos ocupa: cargar un pickle reconstruye objetos Python y suele pasar por un &lt;strong>buffer de host&lt;/strong> intermedio antes de llegar a la GPU.&lt;/p>
&lt;p>&lt;strong>safetensors&lt;/strong> —ahora bajo la PyTorch Foundation (&lt;a href="https://huggingface.co/blog/safetensors-joins-pytorch-foundation">HuggingFace, &lt;em>Safetensors joins PyTorch Foundation&lt;/em>&lt;/a>)— resuelve ambos. El fichero es solo una &lt;strong>cabecera JSON + bytes de tensor en crudo&lt;/strong>: cargarlo no puede hacer nada salvo poblar buffers de tensores. Y la propiedad clave para nosotros es física: &lt;strong>la región de datos está alineada a frontera de página&lt;/strong> (la cabecera se rellena para que el primer tensor empiece en un múltiplo del tamaño de página del SO). Eso es lo que hace posible el &lt;strong>zero-copy&lt;/strong>: un loader puede hacer &lt;code>mmap&lt;/code> del fichero, &lt;code>cudaHostRegister&lt;/code> sobre la región mapeada y &lt;strong>DMA directo de la page cache a la VRAM&lt;/strong>, sin deserialización de &lt;code>torch.load&lt;/code> ni buffer FP32 temporal de host (&lt;a href="https://huggingface.co/docs/safetensors/index">HuggingFace, &lt;em>Safetensors&lt;/em>&lt;/a>). Es el formato base; todo lo demás se construye encima.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Trampa del &lt;code>mmap&lt;/code>&lt;/strong> (ya señalada en &lt;a href="https://blog.lo0.es/posts/del-disco-a-la-hbm-cold-start-carga-modelo/">Del disco a la HBM&lt;/a>): &lt;code>mmap&lt;/code> no lee nada de inmediato, difiere el coste al primer acceso a cada página. Si no fuerzas la lectura, el cold start parece corto y el &lt;strong>primer token&lt;/strong> paga los page faults. Y la &amp;ldquo;segunda carga rapidísima&amp;rdquo; es la page cache mintiéndote: en producción los pods nacen fríos, en nodos donde esos ficheros no están cacheados.&lt;/p>
&lt;/blockquote>
&lt;h3 id="tensorizer-streamear-tensor-a-tensor-directo-a-la-gpu">Tensorizer: streamear tensor a tensor directo a la GPU&lt;/h3>
&lt;p>&lt;strong>Tensorizer&lt;/strong>, de CoreWeave, serializa los pesos del modelo y sus tensores &lt;strong>en un único fichero&lt;/strong> y, en vez de cargar el modelo entero a RAM antes de moverlo a la GPU, &lt;strong>streamea los datos tensor a tensor&lt;/strong> desde disco, un endpoint HTTP/HTTPS o un bucket S3, &lt;strong>deserializándolos al vuelo directamente sobre la GPU&lt;/strong> (&lt;a href="https://docs.vllm.ai/en/stable/models/extensions/tensorizer/">vLLM, &lt;em>Loading Models with CoreWeave&amp;rsquo;s Tensorizer&lt;/em>&lt;/a>). La ventaja operativa: &lt;strong>carga casi instantánea y bajo uso de RAM de host&lt;/strong> durante la inicialización, lo que importa especialmente en escenarios serverless y de autoescalado —justo el caso de scale-to-zero—. Y desacopla los pesos de la imagen del contenedor: el modelo vive en almacenamiento de objetos, no inflando el image pull.&lt;/p>
&lt;p>En vLLM se activa con &lt;code>--load-format tensorizer&lt;/code> (o &lt;code>load_format=&amp;quot;tensorizer&amp;quot;&lt;/code> por API). Requiere serializar el modelo una vez al formato de Tensorizer; a partir de ahí, cualquier pod lo streamea.&lt;/p>
&lt;h3 id="runai-model-streamer-leer-y-copiar-a-la-vez">Run:ai Model Streamer: leer y copiar a la vez&lt;/h3>
&lt;p>El &lt;strong>Run:ai Model Streamer&lt;/strong> (de NVIDIA) ataca el cuello desde otro ángulo: &lt;strong>lee los tensores en concurrencia&lt;/strong> —N hilos del SO leyendo del almacenamiento al buffer de CPU— &lt;strong>mientras los streamea a la VRAM&lt;/strong>, de modo que la &lt;strong>lectura del almacenamiento y la copia H2D se solapan&lt;/strong> en lugar de hacerse en serie (&lt;a href="https://docs.vllm.ai/en/stable/models/extensions/runai_model_streamer/">vLLM, &lt;em>Loading models with Run:ai Model Streamer&lt;/em>&lt;/a>; &lt;a href="https://developer.nvidia.com/blog/reducing-cold-start-latency-for-llm-inference-with-nvidia-runai-model-streamer/">NVIDIA, &lt;em>Reducing Cold Start Latency&lt;/em>&lt;/a>). Lee safetensors directamente, sin reconversión.&lt;/p>
&lt;p>Los números publicados por NVIDIA dan el orden de magnitud: el streamer alcanza &lt;strong>4,88 s leyendo desde S3 a concurrencia 32&lt;/strong> y &lt;strong>7,53 s desde un SSD IO2 a concurrencia 8&lt;/strong>; integrado en vLLM, el tiempo total hasta &lt;em>ready&lt;/em> baja a &lt;strong>23,18 s desde S3&lt;/strong>, &lt;strong>28,28 s desde SSD IO2&lt;/strong> y &lt;strong>35,08 s desde GP3&lt;/strong> (&lt;a href="https://developer.nvidia.com/blog/reducing-cold-start-latency-for-llm-inference-with-nvidia-runai-model-streamer/">NVIDIA, &lt;em>Reducing Cold Start Latency&lt;/em>&lt;/a>). Fíjate en la brecha entre el tiempo de &lt;strong>carga de pesos&lt;/strong> (~5–8 s) y el total hasta &lt;em>ready&lt;/em> (~23–35 s): esos ~18–27 s de diferencia son &lt;strong>las otras cuatro partidas&lt;/strong>, sobre todo la 5. El streamer arregló la partida 4 y el resto sigue ahí —exactamente lo que advierte este post—.&lt;/p>
&lt;p>La concurrencia es el parámetro de ajuste: controla cuántos hilos del SO leen tensores al buffer de CPU (y, para S3, cuántas conexiones cliente abre el host) (&lt;a href="https://docs.vllm.ai/en/stable/models/extensions/runai_model_streamer/">vLLM, &lt;em>Run:ai Model Streamer&lt;/em>&lt;/a>). &lt;strong>16 suele bastar para NVMe local; 32 para almacenamiento de objetos de alto throughput.&lt;/strong> Un hilo no satura un NVMe Gen5; el solapamiento sí.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># safetensors por defecto (el más lento de los rápidos)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">vllm serve &amp;lt;model&amp;gt; --load-format safetensors
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Run:ai Model Streamer (lectura concurrente, solapa lectura + H2D)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">vllm serve &amp;lt;model&amp;gt; --load-format runai_streamer &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --model-loader-extra-config &lt;span class="s1">&amp;#39;{&amp;#34;concurrency&amp;#34;: 32}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Tensorizer (stream tensor a tensor directo a GPU desde objetos/S3)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">vllm serve &amp;lt;model&amp;gt; --load-format tensorizer
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="las-matemáticas-de-los-pesos-t--wb-y-el-ahorro-de-solapar">Las matemáticas de los pesos: $t = W/B$ y el ahorro de solapar&lt;/h2>
&lt;p>El suelo de la partida 4 es simple: mover $W$ gigabytes a un ancho de banda $B$ tarda&lt;/p>
&lt;p>$$t = \frac{W}{B}$$&lt;/p>
&lt;p>Tomemos un modelo grande, un &lt;strong>70B&lt;/strong>. En BF16 ($b=2$ bytes/parám) son $W = 70 \times 10^9 \cdot 2 = 140$ GB; en FP8 ($b=1$), $W = 70$ GB. El ancho de banda depende del tier (ver tabla más abajo): NVMe Gen5 local lee del orden de &lt;strong>~14 GB/s por disco&lt;/strong>; el PCIe Gen5 x16 copia host→GPU a &lt;strong>~50 GB/s&lt;/strong>; el almacenamiento de red, &lt;strong>~1–3 GB/s&lt;/strong>. El suelo teórico de leer 140 GB de un NVMe es $140/14 = 10$ s; de la red a 2 GB/s, &lt;strong>70 s&lt;/strong>. El cuello manda.&lt;/p>
&lt;p>&lt;strong>El truco del solapamiento.&lt;/strong> El loader por defecto hace los dos pasos —leer del disco y copiar H2D— &lt;strong>en serie&lt;/strong>: primero llena un buffer, luego copia, luego el siguiente. El tiempo es la &lt;strong>suma&lt;/strong>:&lt;/p>
&lt;p>$$t_{\text{serie}} = t_{\text{lectura}} + t_{\text{H2D}}$$&lt;/p>
&lt;p>Tensorizer y el Run:ai Model Streamer los &lt;strong>solapan&lt;/strong>: mientras un hilo copia un chunk a la VRAM, otro ya está leyendo el siguiente del disco. Con suficiente concurrencia, el tiempo total tiende al &lt;strong>máximo&lt;/strong> de los dos, no a la suma:&lt;/p>
&lt;p>$$t_{\text{solapado}} \approx \max(t_{\text{lectura}},, t_{\text{H2D}})$$&lt;/p>
&lt;p>Con los 70 GB del 70B en FP8, desde NVMe a 14 GB/s y PCIe a 50 GB/s: $t_{\text{lectura}} = 70/14 = 5{,}0$ s, $t_{\text{H2D}} = 70/50 = 1{,}4$ s. En serie, $5{,}0 + 1{,}4 = 6{,}4$ s; solapado, $\max(5{,}0,,1{,}4) = 5{,}0$ s. El ahorro aquí es modesto (~22%) porque el disco domina con holgura. &lt;strong>El solapamiento brilla cuando los dos pasos son comparables&lt;/strong>: desde almacenamiento de red a 5 GB/s, $t_{\text{lectura}} = 70/5 = 14$ s y $t_{\text{H2D}} = 1{,}4$ s, serie 15,4 s vs solapado 14 s; pero con muchos flujos concurrentes que saturen un disco rápido y un H2D igualado, pasar de la suma al máximo puede &lt;strong>casi doblar&lt;/strong> el throughput efectivo. La otra cara: el solapamiento solo te da lo que el &lt;strong>cuello físico&lt;/strong> permite. Si el disco solo da 14 GB/s, ningún streamer te baja de 5 s para 70 GB —para eso está la otra palanca, &lt;strong>mover menos bytes&lt;/strong> (FP8 frente a BF16 parte $W$ por la mitad)—.&lt;/p>
&lt;h3 id="el-tier-de-almacenamiento">El tier de almacenamiento&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Tier&lt;/th>
&lt;th>Ancho de banda típico&lt;/th>
&lt;th>140 GB (70B BF16)&lt;/th>
&lt;th>70 GB (70B FP8)&lt;/th>
&lt;th>Notas&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>NVMe Gen5 local&lt;/td>
&lt;td>~14 GB/s por disco&lt;/td>
&lt;td>~10 s&lt;/td>
&lt;td>~5 s&lt;/td>
&lt;td>el camino corto; un disco&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>NVMe Gen5 local (varios, RAID0)&lt;/td>
&lt;td>~28–50 GB/s&lt;/td>
&lt;td>~3–5 s&lt;/td>
&lt;td>~1,5–2,5 s&lt;/td>
&lt;td>si el loader satura varios flujos&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Almacenamiento de objetos (S3/RGW)&lt;/td>
&lt;td>~1–3 GB/s por flujo&lt;/td>
&lt;td>~47–140 s&lt;/td>
&lt;td>~23–70 s&lt;/td>
&lt;td>la concurrencia del streamer ayuda mucho&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>NFS / red compartida&lt;/td>
&lt;td>~1–2 GB/s&lt;/td>
&lt;td>~70–140 s&lt;/td>
&lt;td>~35–70 s&lt;/td>
&lt;td>mete la red y su contención en el camino&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>La conclusión operativa es la misma que en &lt;a href="https://blog.lo0.es/posts/del-disco-a-la-hbm-cold-start-carga-modelo/">Del disco a la HBM&lt;/a>: &lt;strong>los pesos que sirven el cold start viven en NVMe local del nodo&lt;/strong>, no en la red. El almacenamiento de red es el repositorio; el nodo de inferencia tiene una copia local caliente (pre-pull con un &lt;code>initContainer&lt;/code> o un DaemonSet de cache por nodo). Tensorizer es la excepción interesante: streamea tan eficientemente desde objetos que a veces hace viable servir desde S3/RGW sin copia local, a cambio de depender del ancho de banda de la red de almacenamiento.&lt;/p>
&lt;h2 id="acelerar-la-partida-5-todo-lo-que-no-son-pesos">Acelerar la partida 5: todo lo que no son pesos&lt;/h2>
&lt;p>Aquí está el medio cold start que los loaders rápidos no tocan. Tres frentes.&lt;/p>
&lt;h3 id="cachear-la-compilación-de-torchcompileinductor">Cachear la compilación de torch.compile/Inductor&lt;/h3>
&lt;p>&lt;code>torch.compile&lt;/code> tiene un &lt;strong>sistema de caché incorporado&lt;/strong>: los artefactos compilados se guardan tras el primer arranque y &lt;strong>se reusan entre arranques&lt;/strong> —incluso entre máquinas si se configura bien—. En vLLM, el resultado de la compilación de Dynamo se almacena en &lt;code>~/.cache/vllm/torch_compile_cache/&lt;/code> (&lt;a href="https://blog.vllm.ai/2025/08/20/torch-compile.html">vLLM Blog, &lt;em>Introduction to torch.compile&lt;/em>&lt;/a>; &lt;a href="https://docs.vllm.ai/en/latest/design/torch_compile/">vLLM, &lt;em>torch.compile integration&lt;/em>&lt;/a>). La consecuencia para el runbook: si ese directorio es &lt;strong>un volumen persistente compartido entre pods&lt;/strong> (un PVC, o un &lt;code>hostPath&lt;/code> en NVMe del nodo poblado por el primer arranque), los pods siguientes se saltan la compilación y arrancan más rápido. El primer pod paga la compilación; los demás heredan la caché.&lt;/p>
&lt;p>Es exactamente &amp;ldquo;afilar los cuchillos la noche anterior&amp;rdquo;: la preparación cara se hace una vez y se reutiliza. El matiz: la caché es sensible a la versión de vLLM/PyTorch, la GPU, la cuantización y la configuración —cambiar cualquiera invalida la caché y vuelves a pagar la compilación una vez—.&lt;/p>
&lt;h3 id="acotar-la-captura-de-cuda-graphs">Acotar la captura de CUDA graphs&lt;/h3>
&lt;p>vLLM captura CUDA graphs para un &lt;strong>abanico amplio de batch sizes&lt;/strong> por defecto, y eso es caro: la captura por defecto ronda los &lt;strong>54 s&lt;/strong>, y en configuraciones grandes se ha medido hasta &lt;strong>294 s&lt;/strong>. Limitando la captura a los batch sizes que tu carga &lt;strong>de verdad&lt;/strong> usa (p. ej. &lt;code>1, 2, 4, 8, 16, 24, 32, 64&lt;/code>) se reduce el cold start &lt;strong>más de un 70%&lt;/strong>, de 294 s a ~82 s en el caso medido (&lt;a href="https://developers.redhat.com/articles/2025/09/03/vllm-torchcompile-efficient-llm-inference-pytorch">Red Hat, &lt;em>vLLM with torch.compile&lt;/em>&lt;/a>). El knob es la configuración de compilación/CUDA graphs del engine; el principio es no capturar grafos para tamaños de batch que nunca verás.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Disyuntiva real:&lt;/strong> menos batch sizes capturados = arranque más rápido pero menos cobertura. Un batch fuera del conjunto capturado cae al camino eager (más lento por petición). Acotas a los tamaños frecuentes de &lt;strong>tu&lt;/strong> tráfico, no a cualquiera. Y siempre existe el extremo de &lt;strong>desactivar CUDA graphs&lt;/strong> (&lt;code>enforce_eager&lt;/code>): arranque casi instantáneo en la partida 5 a costa de throughput en estado caliente —válido para depurar o para servicios de muy bajo tráfico donde el cold start pesa más que el régimen permanente—.&lt;/p>
&lt;/blockquote>
&lt;h3 id="warm-pools-no-pagar-la-partida-5-en-el-camino-crítico">Warm pools: no pagar la partida 5 en el camino crítico&lt;/h3>
&lt;p>El atajo definitivo para las cinco partidas es &lt;strong>no ejecutarlas cuando el usuario espera&lt;/strong>. Un &lt;em>warm pool&lt;/em> —una réplica ya arrancada, con pesos cargados, graphs capturados y kernels compilados, esperando tráfico— convierte el cold start en cero para esa petición. Es coste de GPU (parcial o totalmente ociosa) a cambio de latencia de arranque; lo analizamos en la economía, más abajo.&lt;/p>
&lt;h3 id="el-sleep-mode-el-atajo-que-se-salta-las-cinco-partidas">El sleep mode: el atajo que se salta las cinco partidas&lt;/h3>
&lt;p>Y existe un atajo a medio camino entre &amp;ldquo;arrancar de cero&amp;rdquo; y &amp;ldquo;tener una réplica entera caliente&amp;rdquo;: el &lt;strong>sleep mode&lt;/strong> de vLLM, el tema de &lt;a href="https://blog.lo0.es/posts/servir-varios-modelos-una-gpu-swap-sleep/">Servir varios modelos en una sola GPU&lt;/a>. En vez de matar el proceso, lo &lt;strong>duerme&lt;/strong>: aparca los pesos en RAM del host (nivel 1) o los descarta (nivel 2), pero &lt;strong>mantiene el proceso vivo&lt;/strong> —y con él el contexto CUDA, el allocator, &lt;strong>los CUDA graphs y los kernels JIT ya compilados&lt;/strong>—. El &lt;em>wake&lt;/em> no paga las partidas 1, 2, 3 ni 5; solo vuelve a poner los pesos en VRAM (partida 4, y desde RAM, no desde disco). Por eso un wake es &lt;strong>18–200× más rápido que un cold start completo&lt;/strong>, e incluso el nivel 2 —que recarga los pesos del mismo disco— sigue siendo 23–45× más rápido, &lt;strong>porque se salta las otras cuatro partidas&lt;/strong> (&lt;a href="https://blog.vllm.ai/2025/10/26/sleep-mode.html">vLLM Blog, &lt;em>Sleep Mode&lt;/em>&lt;/a>). El sleep mode es la prueba viva de la tesis de este post: si preservar las partidas 1, 2, 3 y 5 da un 18–200×, es que &lt;strong>mover bytes nunca fue el coste entero&lt;/strong>.&lt;/p>
&lt;h2 id="un-cold-start-de-ejemplo-partida-a-partida">Un cold start de ejemplo, partida a partida&lt;/h2>
&lt;p>Pongamos números a un cold start de &lt;strong>70B en FP8 (70 GB)&lt;/strong> desde NVMe local y veamos qué optimización ataca cada partida:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>#&lt;/th>
&lt;th>Partida&lt;/th>
&lt;th>Por defecto&lt;/th>
&lt;th>Optimización&lt;/th>
&lt;th>Optimizado&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>Init proceso&lt;/td>
&lt;td>~4 s&lt;/td>
&lt;td>(poco margen; entorno mínimo)&lt;/td>
&lt;td>~3 s&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2&lt;/td>
&lt;td>Contexto CUDA&lt;/td>
&lt;td>~6 s&lt;/td>
&lt;td>(driver; fijo)&lt;/td>
&lt;td>~6 s&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3&lt;/td>
&lt;td>Allocator&lt;/td>
&lt;td>~4 s&lt;/td>
&lt;td>(fijo)&lt;/td>
&lt;td>~4 s&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>4&lt;/td>
&lt;td>Carga de pesos&lt;/td>
&lt;td>~25 s&lt;/td>
&lt;td>Run:ai streamer / Tensorizer (concurrencia + solapado)&lt;/td>
&lt;td>~5 s&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>5&lt;/td>
&lt;td>CUDA graphs + JIT&lt;/td>
&lt;td>~54 s&lt;/td>
&lt;td>caché de torch.compile + captura acotada&lt;/td>
&lt;td>~10 s&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;/td>
&lt;td>&lt;strong>Total&lt;/strong>&lt;/td>
&lt;td>&lt;strong>~93 s&lt;/strong>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;strong>~28 s&lt;/strong>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Dos lecturas. Primera: optimizar &lt;strong>solo&lt;/strong> la partida 4 (loader rápido y nada más) baja el total de 93 a ~73 s —una mejora real pero decepcionante, porque la 5 sigue intacta—. Optimizar &lt;strong>solo&lt;/strong> la 5 (caché + captura acotada) baja a ~49 s. Atacar &lt;strong>las dos&lt;/strong> baja a ~28 s, y es lo que convierte scale-to-zero teórico en usable. Segunda: las partidas 2 y 3 son prácticamente irreductibles —dependen del driver y del hardware—; constituyen el &lt;strong>suelo del cold start&lt;/strong>, del orden de 10 s, que ninguna optimización de software cruza. Por debajo de ese suelo, la única respuesta es &lt;strong>no arrancar de cero&lt;/strong>: sleep mode (wake sub-segundo a pocos segundos) o réplica caliente (cero).&lt;/p>
&lt;h2 id="la-economía-del-scale-to-zero">La economía del scale-to-zero&lt;/h2>
&lt;p>La pregunta de capacidad que todo esto sirve es: &lt;strong>¿apagar o mantener caliente?&lt;/strong> Scale-to-zero ahorra GPU mientras no hay tráfico, pero paga el cold start cuando vuelve. La decisión es un balance entre coste de GPU ociosa y SLA de latencia.&lt;/p>
&lt;p>El criterio básico: scale-to-zero solo es viable si el cold start &lt;strong>cabe dentro del SLA de arranque que tus usuarios toleran&lt;/strong>, o si tienes un mecanismo de &lt;strong>pre-warming&lt;/strong> que arranca la réplica &lt;strong>antes&lt;/strong> de que llegue la petición (predicción de carga, señal adelantada de la cola). Sin pre-warm, un cold start de 90 s significa que el primer usuario tras un período ocioso espera 90 s —inaceptable para casi cualquier SLO interactivo—. Con el cold start bajado a ~28 s, sigue siendo mucho para una petición interactiva, pero ya es tolerable para cargas batch o para un pre-warm disparado por &lt;a href="https://blog.lo0.es/posts/autoscaling-llm-kubernetes-keda/">autoscaling con KEDA&lt;/a> sobre la profundidad de cola.&lt;/p>
&lt;p>La regla de decisión, en una frase: &lt;strong>mantén una réplica caliente cuando el coste de la GPU ociosa durante los valles es menor que el coste de incumplir el SLA en cada arranque; haz scale-to-zero (con pre-warm) cuando los valles son largos y profundos y el cold start optimizado cabe en tu tolerancia&lt;/strong>. Para un servicio interactivo 24×7 con tráfico irregular pero continuo, el suelo de réplicas calientes casi siempre gana. Para un modelo grande que se invoca pocas veces al día (un 70B de tareas difíciles), scale-to-zero con cold start optimizado —o sleep mode si comparte GPU con otro modelo— es lo razonable. Es la misma frontera de &lt;a href="https://blog.lo0.es/posts/capacity-planning-inferencia-llm-on-premise/">capacity planning&lt;/a>: el cold start es un parámetro del colchón de réplicas, no un detalle de arranque.&lt;/p>
&lt;h2 id="aplicado-al-cluster-genérico-4h100">Aplicado al cluster genérico 4×H100&lt;/h2>
&lt;p>Bajemos el runbook a las &lt;strong>4 H100 SXM de 80 GB con NVLink&lt;/strong>. Las decisiones concretas:&lt;/p>
&lt;p>&lt;strong>Loader y formato.&lt;/strong> Pesos en &lt;strong>safetensors&lt;/strong> en disco (nunca pickle). Para el camino corto desde NVMe local, el &lt;strong>Run:ai Model Streamer&lt;/strong> (&lt;code>--load-format runai_streamer&lt;/code>, concurrencia 16–32) es la opción por defecto: lee concurrente, solapa lectura y H2D, y solo cambias una bandera. Reserva &lt;strong>Tensorizer&lt;/strong> para el caso en que sirvas desde almacenamiento de objetos (RGW/S3) y quieras desacoplar los pesos de la imagen sin copia local —su streaming tensor-a-tensor directo a GPU es lo que hace viable ese patrón—. En ambos casos, FP8 frente a BF16 parte por la mitad la partida 4 casi gratis (mide la calidad, no la asumas).&lt;/p>
&lt;p>&lt;strong>Tier de almacenamiento.&lt;/strong> Repositorio de modelos en la red (Ceph RGW), copia caliente en &lt;strong>NVMe local del nodo&lt;/strong> poblada por pre-pull. El cold start que cuenta es el frío, en un nodo donde los ficheros no están en page cache; servir ese arranque desde la red mete su latencia y contención en el camino crítico.&lt;/p>
&lt;p>&lt;strong>Caché de la partida 5.&lt;/strong> El directorio &lt;code>torch_compile_cache&lt;/code> en un volumen persistente o &lt;code>hostPath&lt;/code> NVMe compartido entre los pods del mismo modelo: el primer pod compila, los demás heredan. Y &lt;strong>acotar la captura de CUDA graphs&lt;/strong> a los batch sizes reales del tráfico de cada servicio.&lt;/p>
&lt;p>&lt;strong>Cuándo scale-to-zero con pre-warm vs réplica caliente.&lt;/strong> El &lt;strong>LLM de agentes&lt;/strong> (servicio principal, tráfico continuo) &lt;strong>nunca&lt;/strong> hace scale-to-zero: un suelo de réplicas calientes en una o dos GPUs. El &lt;strong>70B ocasional&lt;/strong> sí: scale-to-zero con cold start optimizado, o —si comparte GPU con un modelo mediano— &lt;strong>sleep mode&lt;/strong> para que el wake sea sub-segundo en vez de un arranque de 28 s (la jugada exacta de &lt;a href="https://blog.lo0.es/posts/servir-varios-modelos-una-gpu-swap-sleep/">Servir varios modelos en una sola GPU&lt;/a>). El cold start optimizado de este post es lo que hace que el &lt;strong>swap&lt;/strong> y el &lt;strong>failover&lt;/strong> entre réplicas sean tolerables: una réplica que cae y se reemplaza en 28 s degrada menos que una que tarda 90 s. Y conecta directo con la &lt;a href="https://blog.lo0.es/posts/finops-multi-tenancy-gpu-litellm/">multi-tenancy&lt;/a>: un cold start corto permite repartir GPUs entre equipos con scale-to-zero por tenant sin que el primer usuario de cada tenant pague un arranque eterno.&lt;/p>
&lt;p>El principio transversal: &lt;strong>el cold start es el techo de la elasticidad&lt;/strong>. Las cinco partidas, atacadas las dos mitades —loader rápido para los pesos, caché y captura acotada para los graphs—, lo bajan de minutos a segundos. Y por debajo del suelo irreductible de ~10 s (contexto CUDA + allocator), la única salida es no arrancar de cero: sleep mode o réplica caliente.&lt;/p>
&lt;h2 id="trampas-y-cosas-que-no-son-lo-que-parecen">Trampas y cosas que no son lo que parecen&lt;/h2>
&lt;p>&lt;strong>&amp;ldquo;Cambié a un loader rápido y el arranque casi no mejoró.&amp;rdquo;&lt;/strong> Probablemente tu cuello no era la partida 4. Si la captura de CUDA graphs se come 54 s, bajar la carga de pesos de 25 a 5 s te quita 20 s de 93 —se nota poco—. Mide &lt;strong>dónde&lt;/strong> se va el tiempo antes de optimizar: los logs de arranque de vLLM desglosan carga de pesos frente a captura de graphs.&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;La segunda vez arrancó volando.&amp;rdquo;&lt;/strong> La page cache (para los pesos) y la caché de torch.compile (para los graphs) mintiendo a la vez. El arranque que importa es el &lt;strong>frío&lt;/strong>, en un nodo limpio. Benchmarquear el segundo arranque mide una situación que casi nunca ocurre en el pico que dispara el autoescalado.&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;Tensorizer/streamer me dará el 18–200× del sleep mode.&amp;rdquo;&lt;/strong> No. Esos loaders aceleran &lt;strong>solo la partida 4&lt;/strong>. El 18–200× del &lt;a href="https://blog.lo0.es/posts/servir-varios-modelos-una-gpu-swap-sleep/">sleep mode&lt;/a> viene de &lt;strong>preservar las otras cuatro&lt;/strong> manteniendo el proceso vivo. Son herramientas para problemas distintos: el loader rápido es para un cold start de verdad (proceso nuevo); el sleep mode es para alternar modelos sin matar el proceso.&lt;/p>
&lt;p>&lt;strong>&amp;quot;&lt;code>enforce_eager&lt;/code> arregla el cold start.&amp;quot;&lt;/strong> Arregla la partida 5 (sin captura de graphs, arranque casi instantáneo) pero &lt;strong>degrada el throughput en estado caliente&lt;/strong> —los CUDA graphs existen porque aceleran el régimen permanente—. Es un intercambio: gana para servicios de bajísimo tráfico donde el arranque pesa más que el caliente; pierde para un servicio con carga sostenida.&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;Acoto los CUDA graphs a un solo batch size y listo.&amp;rdquo;&lt;/strong> Acotas a los tamaños &lt;strong>que tu tráfico usa&lt;/strong>, no a uno. Un batch fuera del conjunto capturado cae al camino eager y va más lento. Demasiado agresivo y penalizas el régimen permanente para ganar unos segundos de arranque.&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;Mover los pesos a FP8 también acelera la partida 5.&amp;rdquo;&lt;/strong> No directamente. FP8 parte por la mitad los &lt;strong>bytes&lt;/strong> (partida 4) y dobla el throughput de inferencia, pero la captura de graphs y la compilación de kernels no dependen del tamaño de los pesos sino de la &lt;strong>forma&lt;/strong> del grafo y los batch sizes. La partida 5 se ataca con caché y captura acotada, no con cuantización.&lt;/p>
&lt;h2 id="conclusión">Conclusión&lt;/h2>
&lt;p>&lt;a href="https://blog.lo0.es/posts/del-disco-a-la-hbm-cold-start-carga-modelo/">Del disco a la HBM&lt;/a> lo dejó dicho en lo conceptual: mover los pesos es una de las cinco partidas del cold start. Este runbook lo convierte en acción. Para los pesos —la partida 4— hay un menú claro: &lt;strong>safetensors&lt;/strong> como base (mmap, zero-copy, sin pickle), &lt;strong>Run:ai Model Streamer&lt;/strong> para el camino corto desde NVMe (lectura concurrente que solapa lectura y H2D), &lt;strong>Tensorizer&lt;/strong> para servir streaming desde objetos sin copia local, y la palanca transversal de &lt;strong>mover menos bytes&lt;/strong> con FP8. Pero la mitad olvidada del arranque es la &lt;strong>partida 5&lt;/strong> —captura de CUDA graphs y compilación JIT—, que en un servidor moderno puede ser tan grande como la carga de pesos: se ataca &lt;strong>cacheando la compilación&lt;/strong> entre pods y &lt;strong>acotando la captura de graphs&lt;/strong> a los batch sizes reales. Atacar solo una mitad deja el cold start a medias; atacar las dos lo baja de ~90 s a ~28 s. Y por debajo del suelo irreductible de ~10 s que imponen el contexto CUDA y el allocator, la única salida es &lt;strong>no arrancar de cero&lt;/strong>: el sleep mode (wake sub-segundo) o la réplica caliente (cero). La cocina abre a su hora cuando alguien se ocupó del género &lt;strong>y&lt;/strong> de afilar los cuchillos la noche anterior. Optimizar solo el reparto del almacén deja los hornos fríos y el restaurante cerrado.&lt;/p>
&lt;h2 id="ver-también">Ver también&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/posts/multimodal-vlm-on-premise-vllm/">Multimodal: servir un VLM on-premise con vLLM&lt;/a> — pieza hermana de esta tanda: añadir visión al mismo motor de inferencia; un VLM tiene su propio cold start (pesos del encoder de imagen además del LLM).&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/finops-multi-tenancy-gpu-litellm/">FinOps y multi-tenancy de GPU con LiteLLM&lt;/a> — pieza hermana: un cold start corto es lo que hace viable el scale-to-zero por tenant sin penalizar al primer usuario de cada equipo.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/del-disco-a-la-hbm-cold-start-carga-modelo/">Del disco a la HBM: el cold start y la carga del modelo&lt;/a> — el post conceptual que este runbook continúa: por qué mover bytes es solo una de cinco partidas y el camino disco→page cache→host→PCIe→HBM.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/servir-varios-modelos-una-gpu-swap-sleep/">Servir varios modelos en una sola GPU: swap y sleep mode&lt;/a> — el sleep mode como atajo que preserva las cuatro partidas no-pesos y da el wake 18–200×.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/autoscaling-llm-kubernetes-keda/">Autoscaling de LLM en Kubernetes con KEDA&lt;/a> — el cold start es el techo de la elasticidad; el pre-warming disparado por profundidad de cola es lo que hace usable el scale-to-zero.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/capacity-planning-inferencia-llm-on-premise/">Capacity planning de inferencia on-premise&lt;/a> — el cold start como parámetro de la decisión réplica caliente vs scale-to-zero y del colchón de réplicas.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/compartir-gpu-time-slicing-mps-mig/">Compartir una GPU: time-slicing, MPS y MIG&lt;/a> — repartir una GPU entre servicios; cada servicio co-residente tiene su propio coste de arranque a presupuestar.&lt;/li>
&lt;/ul>
&lt;h2 id="referencias">Referencias&lt;/h2>
&lt;ul>
&lt;li>HuggingFace, &lt;em>Safetensors&lt;/em> (formato, zero-copy, seguridad frente a pickle): &lt;a href="https://huggingface.co/docs/safetensors/index">https://huggingface.co/docs/safetensors/index&lt;/a>&lt;/li>
&lt;li>HuggingFace, &lt;em>Safetensors is Joining the PyTorch Foundation&lt;/em>: &lt;a href="https://huggingface.co/blog/safetensors-joins-pytorch-foundation">https://huggingface.co/blog/safetensors-joins-pytorch-foundation&lt;/a>&lt;/li>
&lt;li>vLLM, &lt;em>Loading Models with CoreWeave&amp;rsquo;s Tensorizer&lt;/em>: &lt;a href="https://docs.vllm.ai/en/stable/models/extensions/tensorizer/">https://docs.vllm.ai/en/stable/models/extensions/tensorizer/&lt;/a>&lt;/li>
&lt;li>vLLM, &lt;em>Loading models with Run:ai Model Streamer&lt;/em>: &lt;a href="https://docs.vllm.ai/en/stable/models/extensions/runai_model_streamer/">https://docs.vllm.ai/en/stable/models/extensions/runai_model_streamer/&lt;/a>&lt;/li>
&lt;li>NVIDIA, &lt;em>Reducing Cold Start Latency for LLM Inference with NVIDIA Run:ai Model Streamer&lt;/em> (benchmarks S3/SSD, concurrencia): &lt;a href="https://developer.nvidia.com/blog/reducing-cold-start-latency-for-llm-inference-with-nvidia-runai-model-streamer/">https://developer.nvidia.com/blog/reducing-cold-start-latency-for-llm-inference-with-nvidia-runai-model-streamer/&lt;/a>&lt;/li>
&lt;li>vLLM Blog, &lt;em>Introduction to torch.compile and How It Works with vLLM&lt;/em> (caché de compilación): &lt;a href="https://blog.vllm.ai/2025/08/20/torch-compile.html">https://blog.vllm.ai/2025/08/20/torch-compile.html&lt;/a>&lt;/li>
&lt;li>vLLM, &lt;em>torch.compile integration&lt;/em> (caché en &lt;code>~/.cache/vllm/torch_compile_cache/&lt;/code>): &lt;a href="https://docs.vllm.ai/en/latest/design/torch_compile/">https://docs.vllm.ai/en/latest/design/torch_compile/&lt;/a>&lt;/li>
&lt;li>Red Hat Developer, &lt;em>vLLM with torch.compile: Efficient LLM inference on PyTorch&lt;/em> (captura de CUDA graphs ~54 s, acotar batch sizes −70%): &lt;a href="https://developers.redhat.com/articles/2025/09/03/vllm-torchcompile-efficient-llm-inference-pytorch">https://developers.redhat.com/articles/2025/09/03/vllm-torchcompile-efficient-llm-inference-pytorch&lt;/a>&lt;/li>
&lt;li>vLLM Blog, &lt;em>Zero-Reload Model Switching with vLLM Sleep Mode&lt;/em> (wake 18–200×): &lt;a href="https://blog.vllm.ai/2025/10/26/sleep-mode.html">https://blog.vllm.ai/2025/10/26/sleep-mode.html&lt;/a>&lt;/li>
&lt;/ul></description></item></channel></rss>