<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Tensorizer on lo0 — Tech Blog</title><link>https://blog.lo0.es/en/tags/tensorizer/</link><description>Recent content in Tensorizer on lo0 — Tech Blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Thu, 11 Jun 2026 13:00:00 +0000</lastBuildDate><atom:link href="https://blog.lo0.es/en/tags/tensorizer/index.xml" rel="self" type="application/rss+xml"/><item><title>Speeding up model cold start: from minutes to seconds</title><link>https://blog.lo0.es/en/posts/speeding-up-model-cold-start-minutes-to-seconds/</link><pubDate>Thu, 11 Jun 2026 13:00:00 +0000</pubDate><guid>https://blog.lo0.es/en/posts/speeding-up-model-cold-start-minutes-to-seconds/</guid><description>&lt;blockquote>
&lt;p>This is the &lt;strong>third batch&lt;/strong> of an operational series on squeezing a generic on-premise LLM cluster of &lt;strong>4×H100 SXM 80 GB with NVLink&lt;/strong>. The sister pieces in this batch are &lt;a href="https://blog.lo0.es/en/posts/multimodal-on-premise-serving-vlm-vllm/">Multimodal: serving a VLM on-premise with vLLM&lt;/a>, adding vision to the same engine, and &lt;a href="https://blog.lo0.es/en/posts/finops-multi-tenancy-gpu-cluster-who-pays/">FinOps and GPU multi-tenancy with LiteLLM&lt;/a>, sharing out and charging for the GPU across teams. This post takes the problem that &lt;a href="https://blog.lo0.es/en/posts/pantry-lift-from-disk-to-hbm-why-the-kitchen-opens-late/">From disk to HBM&lt;/a> laid out conceptually and turns it into a runbook: how you actually bring the cold start down, knob by knob, so that elasticity is usable.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>The post &lt;a href="https://blog.lo0.es/en/posts/pantry-lift-from-disk-to-hbm-why-the-kitchen-opens-late/">From disk to HBM&lt;/a> left an uncomfortable idea behind: &lt;strong>moving the weights from disk to HBM is only one of the five line items of cold start&lt;/strong>. The other four, starting the process, creating the CUDA context, setting up the allocator and, the most expensive one that hardly anyone looks at, &lt;strong>capturing the CUDA graphs and compiling the JIT kernels&lt;/strong>, are untouched by any fast loader. That is why a server that loads the weights in 6 s can still take &lt;strong>90 s&lt;/strong> to be ready. This runbook attacks all five. For the weights: &lt;strong>safetensors&lt;/strong> (mmap, zero-copy, no bounce through a host FP32 buffer) versus the &lt;code>pickle&lt;/code> of &lt;code>torch.load&lt;/code>; &lt;strong>Tensorizer&lt;/strong> (CoreWeave), which serialises the model into one file and &lt;strong>streams it tensor by tensor straight to the GPU&lt;/strong> from object storage/HTTP/S3; and the &lt;strong>Run:ai Model Streamer&lt;/strong> (from NVIDIA), which &lt;strong>reads concurrently and overlaps the read with the H2D copy&lt;/strong>. All three are switched on with one flag, &lt;code>--load-format&lt;/code>, in vLLM. For the non-weight part: &lt;strong>torch.compile/Inductor compilation cache&lt;/strong> (persisted and reused across start-ups), &lt;strong>bounding the CUDA graph capture&lt;/strong> to the batch sizes you really use (the default capture can eat ~54 s), and &lt;strong>sleep mode&lt;/strong> as a shortcut that skips all five line items. The maths: $t = W/B$ for the weights, the saving from &lt;strong>overlapping&lt;/strong> read and H2D instead of doing them in series, and a 70B cold start broken down item by item. And the economics: when &lt;strong>scale-to-zero with pre-warm&lt;/strong> beats &lt;strong>keeping a warm replica&lt;/strong>. On the generic 4×H100 SXM 80 GB cluster.&lt;/p>
&lt;h2 id="the-analogy-opening-the-kitchen-in-the-morning">The analogy: opening the kitchen in the morning&lt;/h2>
&lt;p>A restaurant does not open by flicking a switch. Anyone who has worked in one knows it: opening time is decided by &lt;strong>everything that happens beforehand&lt;/strong>, and almost none of it is &amp;ldquo;bringing in the food&amp;rdquo;.&lt;/p>
&lt;p>The first cook arrives at a cold, dark place. They have to: &lt;strong>turn on the lights and start the systems&lt;/strong> (the process init); &lt;strong>fire up the ovens and let them come up to temperature&lt;/strong> (the CUDA context, which takes its time to become operational); &lt;strong>get the boards out, sort the cold room, organise the work space&lt;/strong> (the memory allocator, which reserves and structures the HBM); &lt;strong>go down to the store and bring up the produce&lt;/strong>, the crates of vegetables, the meat, the fish, &lt;strong>and put it in its place&lt;/strong> (loading the weights from disk to HBM, the entire subject of &lt;a href="https://blog.lo0.es/en/posts/pantry-lift-from-disk-to-hbm-why-the-kitchen-opens-late/">From disk to HBM&lt;/a>); and, the most forgotten of all, &lt;strong>sharpen every knife, set up the mise en place, make the stocks, pre-heat the mother sauces&lt;/strong>, all the preparation that is not an ingredient but without which not a single dish goes out (CUDA graph capture and JIT compilation of the kernels).&lt;/p>
&lt;p>Here is the trap that &lt;a href="https://blog.lo0.es/en/posts/pantry-lift-from-disk-to-hbm-why-the-kitchen-opens-late/">From disk to HBM&lt;/a> already anticipated: &lt;strong>if you only optimise the produce, you still open late&lt;/strong>. You can hire the best delivery service in the world, a blazingly fast service lift, crates that put themselves away. But if the cook is still sharpening knives and waiting for the ovens to come up to temperature, the kitchen &lt;strong>does not serve the first dish&lt;/strong>. The produce arrived early and the restaurant is still shut.&lt;/p>
&lt;p>Today&amp;rsquo;s runbook is, literally, the list of everything that has to be done for the kitchen to open on time, and which of those tasks can be sped up, which can be left done from the night before (cached), and when it pays better never to switch the kitchen off (warm replica) than to light it again every morning (scale-to-zero).&lt;/p>
&lt;h2 id="the-five-line-items-of-cold-start">The five line items of cold start&lt;/h2>
&lt;p>From the moment an inference pod is born until it returns its first token, the clock runs in five different places. It is worth naming them, because &lt;strong>each optimisation attacks one or two, never all of them&lt;/strong>:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Process init.&lt;/strong> Starting the Python interpreter, importing the engine (vLLM and its dependencies), parsing the configuration. Seconds, and it grows with the size of the environment and the CUDA/PyTorch &lt;code>import&lt;/code>s.&lt;/li>
&lt;li>&lt;strong>CUDA context.&lt;/strong> The first &lt;code>cudaSetDevice&lt;/code> / &lt;code>cudaFree(0)&lt;/code> initialises the CUDA runtime against the driver: it loads the context and maps the GPU. It is not instant, on the order of one to several seconds per GPU depending on driver and number of devices.&lt;/li>
&lt;li>&lt;strong>Allocator.&lt;/strong> vLLM sets up its memory manager over the HBM (PyTorch&amp;rsquo;s caching allocator plus the KV-cache layout). Reserving and structuring tens of GB has its cost.&lt;/li>
&lt;li>&lt;strong>Weight loading disk→HBM.&lt;/strong> The journey that &lt;a href="https://blog.lo0.es/en/posts/pantry-lift-from-disk-to-hbm-why-the-kitchen-opens-late/">From disk to HBM&lt;/a> dissected: disk → page cache → host buffer → PCIe → HBM. What almost everyone believes cold start &lt;strong>is&lt;/strong>, and it is only one of the five.&lt;/li>
&lt;li>&lt;strong>CUDA graph capture + kernel JIT.&lt;/strong> vLLM captures CUDA graphs for the various batch shapes and compiles kernels with &lt;code>torch.compile&lt;/code>/Inductor (and backends such as DeepGEMM or FlashInfer). This item is the great ignored one: &lt;strong>the default graph capture in vLLM takes on the order of 54 s&lt;/strong> because it covers a wide range of 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="The five line items of cold start as stacked time bars">
&lt;text x="380" y="24" text-anchor="middle" font-family="sans-serif" font-size="14" font-weight="700" fill="currentColor">The five line items of a cold start (example 70B, default loader)&lt;/text>
&lt;text x="380" y="44" text-anchor="middle" font-family="sans-serif" font-size="11" fill="currentColor" opacity="0.75">width proportional to time · scale 0–110 s&lt;/text>
&lt;!-- axis -->
&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;!-- bars: 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 · process init — ~4 s&lt;/text>
&lt;!-- CUDA context 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 · CUDA context — ~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;!-- weights 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 · weight loading disk→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 · optimising only item 4 leaves item 5 intact — and 5 is the largest here&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>The lesson the diagram is shouting: on a modern server with &lt;code>torch.compile&lt;/code> and CUDA graphs, &lt;strong>item 5 can be as big as or bigger than item 4&lt;/strong>. Speeding up weight loading from 40 s to 6 s is a huge advance, but if you leave graph capture at 50 s, the cold start falls from 104 s to ~70 s, not to 12 s. &lt;strong>You have to attack both halves.&lt;/strong>&lt;/p>
&lt;h2 id="speeding-up-item-4-loading-the-weights">Speeding up item 4: loading the weights&lt;/h2>
&lt;h3 id="safetensors-versus-pickle-the-format-rules">safetensors versus pickle: the format rules&lt;/h3>
&lt;p>The first knob is the on-disk format. The old &lt;code>torch.load&lt;/code> uses &lt;strong>&lt;code>pickle&lt;/code>&lt;/strong>, which has two problems. The security one is well known: deserialising a pickle &lt;strong>executes arbitrary Python code&lt;/strong>, since the &lt;code>__reduce__&lt;/code> protocol lets the file invoke any callable on load, so every model download was a remote execution vector (&lt;a href="https://huggingface.co/docs/safetensors/index">HuggingFace, &lt;em>Safetensors&lt;/em>&lt;/a>). The performance one is what concerns us: loading a pickle rebuilds Python objects and usually passes through an intermediate &lt;strong>host buffer&lt;/strong> before reaching the GPU.&lt;/p>
&lt;p>&lt;strong>safetensors&lt;/strong>, now under the PyTorch Foundation (&lt;a href="https://huggingface.co/blog/safetensors-joins-pytorch-foundation">HuggingFace, &lt;em>Safetensors joins PyTorch Foundation&lt;/em>&lt;/a>), solves both. The file is just a &lt;strong>JSON header + raw tensor bytes&lt;/strong>: loading it cannot do anything except populate tensor buffers. And the key property for us is physical: &lt;strong>the data region is aligned to a page boundary&lt;/strong> (the header is padded so that the first tensor starts at a multiple of the OS page size). That is what makes &lt;strong>zero-copy&lt;/strong> possible: a loader can &lt;code>mmap&lt;/code> the file, &lt;code>cudaHostRegister&lt;/code> over the mapped region and &lt;strong>DMA straight from the page cache into VRAM&lt;/strong>, with no &lt;code>torch.load&lt;/code> deserialisation and no temporary host FP32 buffer (&lt;a href="https://huggingface.co/docs/safetensors/index">HuggingFace, &lt;em>Safetensors&lt;/em>&lt;/a>). It is the base format; everything else is built on top of it.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>The &lt;code>mmap&lt;/code> trap&lt;/strong> (already flagged in &lt;a href="https://blog.lo0.es/en/posts/pantry-lift-from-disk-to-hbm-why-the-kitchen-opens-late/">From disk to HBM&lt;/a>): &lt;code>mmap&lt;/code> reads nothing immediately, it defers the cost to the first access to each page. If you do not force the read, the cold start looks short and the &lt;strong>first token&lt;/strong> pays the page faults. And the &amp;ldquo;blazingly fast second load&amp;rdquo; is the page cache lying to you: in production pods are born cold, on nodes where those files are not cached.&lt;/p>
&lt;/blockquote>
&lt;h3 id="tensorizer-streaming-tensor-by-tensor-straight-to-the-gpu">Tensorizer: streaming tensor by tensor straight to the GPU&lt;/h3>
&lt;p>&lt;strong>Tensorizer&lt;/strong>, from CoreWeave, serialises the model&amp;rsquo;s weights and its tensors &lt;strong>into a single file&lt;/strong> and, instead of loading the whole model into RAM before moving it to the GPU, &lt;strong>streams the data tensor by tensor&lt;/strong> from disk, an HTTP/HTTPS endpoint or an S3 bucket, &lt;strong>deserialising on the fly straight onto the 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>). The operational advantage: &lt;strong>near-instant loading and low host RAM usage&lt;/strong> during initialisation, which matters especially in serverless and autoscaling scenarios, exactly the scale-to-zero case. And it decouples the weights from the container image: the model lives in object storage, not inflating the image pull.&lt;/p>
&lt;p>In vLLM it is enabled with &lt;code>--load-format tensorizer&lt;/code> (or &lt;code>load_format=&amp;quot;tensorizer&amp;quot;&lt;/code> via the API). It requires serialising the model once into Tensorizer&amp;rsquo;s format; from then on, any pod streams it.&lt;/p>
&lt;h3 id="runai-model-streamer-reading-and-copying-at-the-same-time">Run:ai Model Streamer: reading and copying at the same time&lt;/h3>
&lt;p>The &lt;strong>Run:ai Model Streamer&lt;/strong> (from NVIDIA) attacks the bottleneck from another angle: it &lt;strong>reads the tensors concurrently&lt;/strong>, N OS threads reading from storage into the CPU buffer, &lt;strong>while streaming them to VRAM&lt;/strong>, so that the &lt;strong>storage read and the H2D copy overlap&lt;/strong> instead of happening in series (&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>). It reads safetensors directly, with no reconversion.&lt;/p>
&lt;p>The numbers published by NVIDIA give the order of magnitude: the streamer reaches &lt;strong>4.88 s reading from S3 at concurrency 32&lt;/strong> and &lt;strong>7.53 s from an SSD IO2 at concurrency 8&lt;/strong>; integrated into vLLM, the total time to &lt;em>ready&lt;/em> drops to &lt;strong>23.18 s from S3&lt;/strong>, &lt;strong>28.28 s from SSD IO2&lt;/strong> and &lt;strong>35.08 s from 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>). Note the gap between the &lt;strong>weight loading&lt;/strong> time (~5–8 s) and the total to &lt;em>ready&lt;/em> (~23–35 s): those ~18–27 s of difference are &lt;strong>the other four line items&lt;/strong>, above all item 5. The streamer fixed item 4 and the rest is still there, exactly what this post warns about.&lt;/p>
&lt;p>Concurrency is the tuning parameter: it controls how many OS threads read tensors into the CPU buffer (and, for S3, how many client connections the host opens) (&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 is usually enough for local NVMe; 32 for high-throughput object storage.&lt;/strong> One thread does not saturate a Gen5 NVMe; overlapping does.&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"># default safetensors (the slowest of the fast ones)&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 (concurrent reads, overlaps read + 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 (tensor-by-tensor stream straight to GPU from object storage/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="the-maths-of-the-weights-t--wb-and-the-saving-from-overlapping">The maths of the weights: $t = W/B$ and the saving from overlapping&lt;/h2>
&lt;p>The floor of item 4 is simple: moving $W$ gigabytes at a bandwidth $B$ takes&lt;/p>
$$t = \frac{W}{B}$$
&lt;p>Take a large model, a &lt;strong>70B&lt;/strong>. In BF16 ($b=2$ bytes/param) that is $W = 70 \times 10^9 \cdot 2 = 140$ GB; in FP8 ($b=1$), $W = 70$ GB. Bandwidth depends on the tier (see the table below): local Gen5 NVMe reads on the order of &lt;strong>~14 GB/s per disk&lt;/strong>; PCIe Gen5 x16 copies host→GPU at &lt;strong>~50 GB/s&lt;/strong>; network storage, &lt;strong>~1–3 GB/s&lt;/strong>. The theoretical floor for reading 140 GB from an NVMe is $140/14 = 10$ s; from the network at 2 GB/s, &lt;strong>70 s&lt;/strong>. The bottleneck rules.&lt;/p>
&lt;p>&lt;strong>The overlapping trick.&lt;/strong> The default loader does the two steps, reading from disk and copying H2D, &lt;strong>in series&lt;/strong>: first it fills a buffer, then it copies, then the next one. The time is the &lt;strong>sum&lt;/strong>:&lt;/p>
$$t_{\text{series}} = t_{\text{read}} + t_{\text{H2D}}$$
&lt;p>Tensorizer and the Run:ai Model Streamer &lt;strong>overlap&lt;/strong> them: while one thread copies a chunk to VRAM, another is already reading the next one from disk. With enough concurrency, the total time tends towards the &lt;strong>maximum&lt;/strong> of the two, not the sum:&lt;/p>
$$t_{\text{overlapped}} \approx \max(t_{\text{read}},\, t_{\text{H2D}})$$
&lt;p>With the 70 GB of the 70B in FP8, from NVMe at 14 GB/s and PCIe at 50 GB/s: $t_{\text{read}} = 70/14 = 5.0$ s, $t_{\text{H2D}} = 70/50 = 1.4$ s. In series, $5.0 + 1.4 = 6.4$ s; overlapped, $\max(5.0,\,1.4) = 5.0$ s. The saving here is modest (~22%) because the disk dominates comfortably. &lt;strong>Overlapping shines when the two steps are comparable&lt;/strong>: from network storage at 5 GB/s, $t_{\text{read}} = 70/5 = 14$ s and $t_{\text{H2D}} = 1.4$ s, series 15.4 s vs overlapped 14 s; but with many concurrent streams saturating a fast disk and a matched H2D, going from the sum to the maximum can &lt;strong>almost double&lt;/strong> the effective throughput. The flip side: overlapping only gives you what the &lt;strong>physical bottleneck&lt;/strong> allows. If the disk only delivers 14 GB/s, no streamer brings you below 5 s for 70 GB, and that is what the other lever is for, &lt;strong>moving fewer bytes&lt;/strong> (FP8 versus BF16 halves $W$).&lt;/p>
&lt;h3 id="the-storage-tier">The storage tier&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Tier&lt;/th>
&lt;th>Typical bandwidth&lt;/th>
&lt;th>140 GB (70B BF16)&lt;/th>
&lt;th>70 GB (70B FP8)&lt;/th>
&lt;th>Notes&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Local Gen5 NVMe&lt;/td>
&lt;td>~14 GB/s per disk&lt;/td>
&lt;td>~10 s&lt;/td>
&lt;td>~5 s&lt;/td>
&lt;td>the short path; one disk&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Local Gen5 NVMe (several, 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>if the loader saturates several streams&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Object storage (S3/RGW)&lt;/td>
&lt;td>~1–3 GB/s per stream&lt;/td>
&lt;td>~47–140 s&lt;/td>
&lt;td>~23–70 s&lt;/td>
&lt;td>streamer concurrency helps a great deal&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>NFS / shared network&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>puts the network and its contention in the path&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The operational conclusion is the same as in &lt;a href="https://blog.lo0.es/en/posts/pantry-lift-from-disk-to-hbm-why-the-kitchen-opens-late/">From disk to HBM&lt;/a>: &lt;strong>the weights that serve the cold start live on the node&amp;rsquo;s local NVMe&lt;/strong>, not on the network. Network storage is the repository; the inference node has a hot local copy (pre-pull with an &lt;code>initContainer&lt;/code> or a per-node cache DaemonSet). Tensorizer is the interesting exception: it streams from object storage so efficiently that it sometimes makes serving from S3/RGW viable with no local copy, at the price of depending on the bandwidth of the storage network.&lt;/p>
&lt;h2 id="speeding-up-item-5-everything-that-is-not-weights">Speeding up item 5: everything that is not weights&lt;/h2>
&lt;p>Here is the half of the cold start that fast loaders do not touch. Three fronts.&lt;/p>
&lt;h3 id="caching-the-torchcompileinductor-compilation">Caching the torch.compile/Inductor compilation&lt;/h3>
&lt;p>&lt;code>torch.compile&lt;/code> has a &lt;strong>built-in cache system&lt;/strong>: the compiled artefacts are saved after the first start-up and &lt;strong>reused across start-ups&lt;/strong>, even across machines if it is configured properly. In vLLM, the result of the Dynamo compilation is stored in &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>). The consequence for the runbook: if that directory is &lt;strong>a persistent volume shared between pods&lt;/strong> (a PVC, or a &lt;code>hostPath&lt;/code> on the node&amp;rsquo;s NVMe populated by the first start-up), subsequent pods skip the compilation and start faster. The first pod pays for the compilation; the rest inherit the cache.&lt;/p>
&lt;p>It is exactly &amp;ldquo;sharpening the knives the night before&amp;rdquo;: the expensive preparation is done once and reused. The nuance: the cache is sensitive to the vLLM/PyTorch version, the GPU, the quantisation and the configuration, and changing any of them invalidates the cache and you pay for the compilation once again.&lt;/p>
&lt;h3 id="bounding-the-cuda-graph-capture">Bounding the CUDA graph capture&lt;/h3>
&lt;p>vLLM captures CUDA graphs for a &lt;strong>wide range of batch sizes&lt;/strong> by default, and that is expensive: the default capture is around &lt;strong>54 s&lt;/strong>, and on large configurations up to &lt;strong>294 s&lt;/strong> has been measured. By limiting the capture to the batch sizes your workload &lt;strong>really&lt;/strong> uses (for example &lt;code>1, 2, 4, 8, 16, 24, 32, 64&lt;/code>) the cold start is cut by &lt;strong>more than 70%&lt;/strong>, from 294 s to ~82 s in the measured case (&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>). The knob is the engine&amp;rsquo;s compilation/CUDA graphs configuration; the principle is not to capture graphs for batch sizes you will never see.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>A real trade-off:&lt;/strong> fewer captured batch sizes = faster start-up but less coverage. A batch outside the captured set falls back to the eager path (slower per request). You bound to the frequent sizes of &lt;strong>your&lt;/strong> traffic, not of any traffic. And there is always the extreme of &lt;strong>disabling CUDA graphs&lt;/strong> (&lt;code>enforce_eager&lt;/code>): near-instant start-up on item 5 at the cost of throughput in the hot state, valid for debugging or for very low-traffic services where cold start weighs more than steady state.&lt;/p>
&lt;/blockquote>
&lt;h3 id="warm-pools-not-paying-for-item-5-on-the-critical-path">Warm pools: not paying for item 5 on the critical path&lt;/h3>
&lt;p>The definitive shortcut for all five items is &lt;strong>not to run them while the user waits&lt;/strong>. A &lt;em>warm pool&lt;/em>, a replica already started, with weights loaded, graphs captured and kernels compiled, waiting for traffic, turns the cold start into zero for that request. It is GPU cost (partly or wholly idle) in exchange for start-up latency; we analyse it in the economics section below.&lt;/p>
&lt;h3 id="sleep-mode-the-shortcut-that-skips-all-five-items">Sleep mode: the shortcut that skips all five items&lt;/h3>
&lt;p>And there is a shortcut halfway between &amp;ldquo;start from scratch&amp;rdquo; and &amp;ldquo;keep a whole replica warm&amp;rdquo;: vLLM&amp;rsquo;s &lt;strong>sleep mode&lt;/strong>, the subject of &lt;a href="https://blog.lo0.es/en/posts/serving-several-models-single-gpu-co-residency-swapping-sleep-mode/">Serving several models on a single GPU&lt;/a>. Instead of killing the process, it &lt;strong>puts it to sleep&lt;/strong>: it parks the weights in host RAM (level 1) or discards them (level 2), but &lt;strong>keeps the process alive&lt;/strong>, and with it the CUDA context, the allocator, &lt;strong>the CUDA graphs and the already compiled JIT kernels&lt;/strong>. The &lt;em>wake&lt;/em> pays for neither item 1, 2, 3 nor 5; it only puts the weights back into VRAM (item 4, and from RAM, not from disk). That is why a wake is &lt;strong>18–200× faster than a full cold start&lt;/strong>, and even level 2, which reloads the weights from the same disk, is still 23–45× faster, &lt;strong>because it skips the other four items&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>). Sleep mode is living proof of this post&amp;rsquo;s thesis: if preserving items 1, 2, 3 and 5 gives you 18–200×, then &lt;strong>moving bytes was never the whole cost&lt;/strong>.&lt;/p>
&lt;h2 id="an-example-cold-start-item-by-item">An example cold start, item by item&lt;/h2>
&lt;p>Let us put numbers on a cold start of &lt;strong>70B in FP8 (70 GB)&lt;/strong> from local NVMe and see which optimisation attacks each item:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>#&lt;/th>
&lt;th>Line item&lt;/th>
&lt;th>Default&lt;/th>
&lt;th>Optimisation&lt;/th>
&lt;th>Optimised&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>Process init&lt;/td>
&lt;td>~4 s&lt;/td>
&lt;td>(little room; minimal environment)&lt;/td>
&lt;td>~3 s&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2&lt;/td>
&lt;td>CUDA context&lt;/td>
&lt;td>~6 s&lt;/td>
&lt;td>(driver; fixed)&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>(fixed)&lt;/td>
&lt;td>~4 s&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>4&lt;/td>
&lt;td>Weight loading&lt;/td>
&lt;td>~25 s&lt;/td>
&lt;td>Run:ai streamer / Tensorizer (concurrency + overlap)&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>torch.compile cache + bounded capture&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>Two readings. First: optimising &lt;strong>only&lt;/strong> item 4 (a fast loader and nothing else) brings the total down from 93 to ~73 s, a real but disappointing improvement, because item 5 is still intact. Optimising &lt;strong>only&lt;/strong> item 5 (cache + bounded capture) brings it down to ~49 s. Attacking &lt;strong>both&lt;/strong> brings it down to ~28 s, and that is what turns theoretical scale-to-zero into usable scale-to-zero. Second: items 2 and 3 are practically irreducible, they depend on the driver and the hardware; they constitute the &lt;strong>floor of the cold start&lt;/strong>, on the order of 10 s, which no software optimisation crosses. Below that floor, the only answer is &lt;strong>not to start from scratch&lt;/strong>: sleep mode (wake from sub-second to a few seconds) or a warm replica (zero).&lt;/p>
&lt;h2 id="the-economics-of-scale-to-zero">The economics of scale-to-zero&lt;/h2>
&lt;p>The capacity question all of this serves is: &lt;strong>switch off or keep warm?&lt;/strong> Scale-to-zero saves GPU while there is no traffic, but pays the cold start when it comes back. The decision is a balance between the cost of an idle GPU and the latency SLA.&lt;/p>
&lt;p>The basic criterion: scale-to-zero is only viable if the cold start &lt;strong>fits inside the start-up SLA your users tolerate&lt;/strong>, or if you have a &lt;strong>pre-warming&lt;/strong> mechanism that starts the replica &lt;strong>before&lt;/strong> the request arrives (load prediction, an early signal from the queue). Without pre-warm, a 90 s cold start means the first user after an idle period waits 90 s, unacceptable for almost any interactive SLO. With the cold start brought down to ~28 s it is still a lot for an interactive request, but it is already tolerable for batch workloads or for a pre-warm triggered by &lt;a href="https://blog.lo0.es/en/posts/llm-inference-autoscaling-kubernetes-hpa-keda/">autoscaling with KEDA&lt;/a> on queue depth.&lt;/p>
&lt;p>The decision rule, in one sentence: &lt;strong>keep a warm replica when the cost of the idle GPU during the troughs is lower than the cost of missing the SLA on every start-up; do scale-to-zero (with pre-warm) when the troughs are long and deep and the optimised cold start fits in your tolerance&lt;/strong>. For a 24×7 interactive service with irregular but continuous traffic, a floor of warm replicas almost always wins. For a large model invoked a few times a day (a 70B for hard tasks), scale-to-zero with an optimised cold start, or sleep mode if it shares a GPU with another model, is the sensible option. It is the same &lt;a href="https://blog.lo0.es/en/posts/capacity-planning-on-premise-llm-inference/">capacity planning&lt;/a> boundary: cold start is a parameter of the replica cushion, not a start-up detail.&lt;/p>
&lt;h2 id="applied-to-the-generic-4h100-cluster">Applied to the generic 4×H100 cluster&lt;/h2>
&lt;p>Let us bring the runbook down to the &lt;strong>4 H100 SXM 80 GB with NVLink&lt;/strong>. The concrete decisions:&lt;/p>
&lt;p>&lt;strong>Loader and format.&lt;/strong> Weights in &lt;strong>safetensors&lt;/strong> on disk (never pickle). For the short path from local NVMe, the &lt;strong>Run:ai Model Streamer&lt;/strong> (&lt;code>--load-format runai_streamer&lt;/code>, concurrency 16–32) is the default option: concurrent reads, overlapping read and H2D, and you only change one flag. Keep &lt;strong>Tensorizer&lt;/strong> for the case where you serve from object storage (RGW/S3) and want to decouple the weights from the image with no local copy, since its tensor-by-tensor streaming straight to the GPU is what makes that pattern viable. In both cases, FP8 versus BF16 halves item 4 almost for free (measure the quality, do not assume it).&lt;/p>
&lt;p>&lt;strong>Storage tier.&lt;/strong> Model repository on the network (Ceph RGW), hot copy on the &lt;strong>node&amp;rsquo;s local NVMe&lt;/strong> populated by pre-pull. The cold start that counts is the cold one, on a node where the files are not in page cache; serving that start-up from the network puts its latency and contention into the critical path.&lt;/p>
&lt;p>&lt;strong>Caching item 5.&lt;/strong> The &lt;code>torch_compile_cache&lt;/code> directory on a persistent volume or a &lt;code>hostPath&lt;/code> NVMe shared between pods of the same model: the first pod compiles, the rest inherit. And &lt;strong>bound the CUDA graph capture&lt;/strong> to the real batch sizes of each service&amp;rsquo;s traffic.&lt;/p>
&lt;p>&lt;strong>When scale-to-zero with pre-warm vs a warm replica.&lt;/strong> The &lt;strong>agent LLM&lt;/strong> (main service, continuous traffic) &lt;strong>never&lt;/strong> does scale-to-zero: a floor of warm replicas on one or two GPUs. The &lt;strong>occasional 70B&lt;/strong> does: scale-to-zero with an optimised cold start, or, if it shares a GPU with a mid-sized model, &lt;strong>sleep mode&lt;/strong> so the wake is sub-second instead of a 28 s start-up (exactly the play in &lt;a href="https://blog.lo0.es/en/posts/serving-several-models-single-gpu-co-residency-swapping-sleep-mode/">Serving several models on a single GPU&lt;/a>). The optimised cold start in this post is what makes &lt;strong>swap&lt;/strong> and &lt;strong>failover&lt;/strong> between replicas tolerable: a replica that falls over and is replaced in 28 s degrades less than one that takes 90 s. And it connects directly with &lt;a href="https://blog.lo0.es/en/posts/finops-multi-tenancy-gpu-cluster-who-pays/">multi-tenancy&lt;/a>: a short cold start lets you share GPUs between teams with per-tenant scale-to-zero without the first user of each tenant paying for an endless start-up.&lt;/p>
&lt;p>The cross-cutting principle: &lt;strong>cold start is the ceiling on elasticity&lt;/strong>. The five line items, with both halves attacked, a fast loader for the weights and cache plus bounded capture for the graphs, bring it down from minutes to seconds. And below the irreducible floor of ~10 s (CUDA context + allocator), the only way out is not to start from scratch: sleep mode or a warm replica.&lt;/p>
&lt;h2 id="traps-and-things-that-are-not-what-they-seem">Traps and things that are not what they seem&lt;/h2>
&lt;p>&lt;strong>&amp;ldquo;I switched to a fast loader and start-up barely improved.&amp;rdquo;&lt;/strong> Your bottleneck probably was not item 4. If CUDA graph capture eats 54 s, bringing weight loading down from 25 to 5 s takes 20 s off 93, which is barely noticeable. Measure &lt;strong>where&lt;/strong> the time goes before optimising: vLLM&amp;rsquo;s start-up logs break down weight loading versus graph capture.&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;The second time it started in a flash.&amp;rdquo;&lt;/strong> The page cache (for the weights) and the torch.compile cache (for the graphs) lying to you at the same time. The start-up that matters is the &lt;strong>cold&lt;/strong> one, on a clean node. Benchmarking the second start-up measures a situation that almost never occurs in the peak that triggers the autoscaler.&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;Tensorizer/streamer will give me the 18–200× of sleep mode.&amp;rdquo;&lt;/strong> No. Those loaders speed up &lt;strong>only item 4&lt;/strong>. The 18–200× of &lt;a href="https://blog.lo0.es/en/posts/serving-several-models-single-gpu-co-residency-swapping-sleep-mode/">sleep mode&lt;/a> comes from &lt;strong>preserving the other four&lt;/strong> by keeping the process alive. They are tools for different problems: a fast loader is for a real cold start (a new process); sleep mode is for alternating models without killing the process.&lt;/p>
&lt;p>&lt;strong>&amp;quot;&lt;code>enforce_eager&lt;/code> fixes the cold start.&amp;quot;&lt;/strong> It fixes item 5 (no graph capture, near-instant start-up) but &lt;strong>degrades throughput in the hot state&lt;/strong>, since CUDA graphs exist because they speed up the steady state. It is a trade: it wins for very low traffic services where start-up weighs more than the hot path; it loses for a service with sustained load.&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;I bound the CUDA graphs to a single batch size and that is it.&amp;rdquo;&lt;/strong> You bound to the sizes &lt;strong>your traffic uses&lt;/strong>, not to one. A batch outside the captured set falls back to the eager path and runs slower. Too aggressive and you penalise the steady state to gain a few seconds of start-up.&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;Moving the weights to FP8 also speeds up item 5.&amp;rdquo;&lt;/strong> Not directly. FP8 halves the &lt;strong>bytes&lt;/strong> (item 4) and doubles inference throughput, but graph capture and kernel compilation do not depend on the size of the weights, they depend on the &lt;strong>shape&lt;/strong> of the graph and the batch sizes. Item 5 is attacked with caching and bounded capture, not with quantisation.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/pantry-lift-from-disk-to-hbm-why-the-kitchen-opens-late/">From disk to HBM&lt;/a> said it conceptually: moving the weights is one of the five line items of cold start. This runbook turns it into action. For the weights, item 4, there is a clear menu: &lt;strong>safetensors&lt;/strong> as the base (mmap, zero-copy, no pickle), &lt;strong>Run:ai Model Streamer&lt;/strong> for the short path from NVMe (concurrent reads overlapping read and H2D), &lt;strong>Tensorizer&lt;/strong> for streaming from object storage with no local copy, and the cross-cutting lever of &lt;strong>moving fewer bytes&lt;/strong> with FP8. But the forgotten half of the start-up is &lt;strong>item 5&lt;/strong>, CUDA graph capture and JIT compilation, which on a modern server can be as big as the weight loading: you attack it by &lt;strong>caching the compilation&lt;/strong> across pods and &lt;strong>bounding the graph capture&lt;/strong> to the real batch sizes. Attacking only one half leaves the cold start half done; attacking both brings it from ~90 s to ~28 s. And below the irreducible floor of ~10 s imposed by the CUDA context and the allocator, the only way out is &lt;strong>not to start from scratch&lt;/strong>: sleep mode (sub-second wake) or a warm replica (zero). The kitchen opens on time when somebody took care of the produce &lt;strong>and&lt;/strong> of sharpening the knives the night before. Optimising only the delivery from the store leaves the ovens cold and the restaurant shut.&lt;/p>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/multimodal-on-premise-serving-vlm-vllm/">Multimodal: serving a VLM on-premise with vLLM&lt;/a> — a sister piece in this batch: adding vision to the same inference engine; a VLM has its own cold start (the image encoder weights on top of the LLM).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/finops-multi-tenancy-gpu-cluster-who-pays/">FinOps and GPU multi-tenancy with LiteLLM&lt;/a> — a sister piece: a short cold start is what makes per-tenant scale-to-zero viable without penalising the first user of each team.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/pantry-lift-from-disk-to-hbm-why-the-kitchen-opens-late/">From disk to HBM: cold start and model loading&lt;/a> — the conceptual post this runbook continues: why moving bytes is only one of five items, and the path disk→page cache→host→PCIe→HBM.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/serving-several-models-single-gpu-co-residency-swapping-sleep-mode/">Serving several models on a single GPU: swap and sleep mode&lt;/a> — sleep mode as the shortcut that preserves the four non-weight items and delivers the 18–200× wake.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/llm-inference-autoscaling-kubernetes-hpa-keda/">LLM autoscaling on Kubernetes with KEDA&lt;/a> — cold start is the ceiling on elasticity; pre-warming triggered by queue depth is what makes scale-to-zero usable.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/capacity-planning-on-premise-llm-inference/">Capacity planning for on-premise inference&lt;/a> — cold start as a parameter of the warm replica vs scale-to-zero decision and of the replica cushion.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/sharing-one-gpu-time-slicing-mps-mig/">Sharing a GPU: time-slicing, MPS and MIG&lt;/a> — sharing out one GPU between services; every co-resident service has its own start-up cost to budget for.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/model-chain-of-trust-where-the-bytes-live-registry-oci-artefacts/">Model chain of trust (2/4): registry, OCI artefacts and distribution&lt;/a> — the link before the cold start: which registry the bytes come from, how they are cached near the node and what mounting the model as an OCI artefact adds.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="references">References&lt;/h2>
&lt;ul>
&lt;li>HuggingFace, &lt;em>Safetensors&lt;/em> (format, zero-copy, security versus 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> (S3/SSD benchmarks, concurrency): &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> (compilation cache): &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> (cache in &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> (CUDA graph capture ~54 s, bounding 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>