<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Contexto-Largo on lo0 — Tech Blog</title><link>https://blog.lo0.es/en/tags/contexto-largo/</link><description>Recent content in Contexto-Largo on lo0 — Tech Blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Fri, 12 Jun 2026 05:00:00 +0200</lastBuildDate><atom:link href="https://blog.lo0.es/en/tags/contexto-largo/index.xml" rel="self" type="application/rss+xml"/><item><title>Long context and KV offloading: when the notebook does not fit on the desk</title><link>https://blog.lo0.es/en/posts/long-context-kv-offloading-notebook-desk/</link><pubDate>Fri, 12 Jun 2026 05:00:00 +0200</pubDate><guid>https://blog.lo0.es/en/posts/long-context-kv-offloading-notebook-desk/</guid><description>&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>Serving long context &lt;strong>is not serving a smarter model, it is managing a notebook that does not fit on the desk&lt;/strong>. The context window is set by the model (and extended with RoPE scaling / YaRN), but the cost of serving it is set by the &lt;strong>KV cache&lt;/strong>, which grows linearly with sequence length and, in production, &lt;strong>explodes&lt;/strong>: a 300k-token contract on Llama 3 70B eats ~93 GB of KV, more than an entire H100, and a million tokens asks for ~125 GB. When the KV does not fit in HBM, there are only two ways out: &lt;strong>recompute&lt;/strong> (extremely expensive, attention is quadratic) or &lt;strong>offload&lt;/strong> the KV to a cheaper memory hierarchy: DRAM, NVMe, network. The 2026 OSS state of the art, &lt;strong>LMCache&lt;/strong>, &lt;strong>Mooncake&lt;/strong> (Kimi&amp;rsquo;s platform) and &lt;strong>NVIDIA Dynamo/KVBM&lt;/strong>, turns that offload into a &lt;strong>first-class KV layer&lt;/strong>, with reuse across requests, &lt;em>prefill/decode disaggregation&lt;/em> and cache-aware &lt;em>routing&lt;/em>. Reported results: &lt;strong>3×–10×&lt;/strong> less latency with LMCache and up to &lt;strong>+525 %&lt;/strong> throughput in long-context scenarios with Mooncake. The price: every memory hop adds transfer latency, so offloading &lt;strong>only pays off when what you save by not recomputing beats what it costs to move the bytes&lt;/strong>.&lt;/p>
&lt;hr>
&lt;h2 id="the-analogy">The analogy&lt;/h2>
&lt;p>A researcher works at a small desk. On it fit the papers they are pulling from &lt;strong>right now&lt;/strong>, which is the GPU&amp;rsquo;s HBM, blazingly fast but tiny. When the case is long (a thousand-page file, a million-token context), the papers do not fit. There are three options:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Throw papers away and ask the archive for them again every time they are needed.&lt;/strong> That is recomputing the KV: correct, but agonisingly slow, because &amp;ldquo;asking the archive&amp;rdquo; in an LLM means running the whole prompt through attention again, and attention is &lt;strong>quadratic&lt;/strong>.&lt;/li>
&lt;li>&lt;strong>Put a shelf next to the desk&lt;/strong> (the CPU&amp;rsquo;s DRAM) and, further out, &lt;strong>a store room in the basement&lt;/strong> (NVMe) and &lt;strong>a depot in another building&lt;/strong> (network/object). Moving papers between the desk and the shelf costs seconds, not hours. That is &lt;strong>hierarchical KV offloading&lt;/strong>.&lt;/li>
&lt;li>&lt;strong>Have two researchers split the work:&lt;/strong> one reads and marks up the whole file (prefill), then passes their notes to the second, who only drafts (decode). That is the &lt;strong>KVCache-centric disaggregated architecture&lt;/strong>.&lt;/li>
&lt;/ol>
&lt;p>This post is about options 2 and 3, the ones that make long context viable in production. Option 1 is what you pay by default if you do nothing.&lt;/p>
&lt;hr>
&lt;h2 id="part-1--why-long-context-is-a-memory-problem">Part 1 · Why long context is a memory problem&lt;/h2>
&lt;h3 id="the-kv-cache-grows-with-the-sequence">The KV cache grows with the sequence&lt;/h3>
&lt;p>For every token that goes in or out, the model stores its &lt;em>key&lt;/em> and &lt;em>value&lt;/em> vectors in each layer so it does not have to recompute them. The size is:&lt;/p>
$$\text{KV bytes} = 2 \times L \times h_{kv} \times d_{head} \times s \times b$$
&lt;p>with \(L\) layers, \(h_{kv}\) KV heads (few, with GQA), \(d_{head}\) the per-head dimension, \(s\) the sequence length and \(b\) the bytes per element. Everything is a model constant &lt;strong>except \(s\)&lt;/strong>: the KV is &lt;strong>linear in context length&lt;/strong>. Doubling the context doubles the KV. That is the basis of &lt;a href="https://blog.lo0.es/en/posts/kv-cache-working-memory-llm-inference/">KV cache: the working memory of inference&lt;/a>.&lt;/p>
&lt;h3 id="the-numbers-are-frightening">The numbers are frightening&lt;/h3>
&lt;p>At production scale, that linear term turns brutal:&lt;/p>
&lt;ul>
&lt;li>A &lt;strong>300k-token contract&lt;/strong> on &lt;strong>Llama 3 70B&lt;/strong> consumes &lt;strong>~93 GB&lt;/strong> of KV alone, &lt;strong>more than the 80 GB of an entire H100&lt;/strong> (&lt;a href="https://www.digitalocean.com/community/tutorials/long-context-inference-production-cost">DigitalOcean · Long-Context Inference Cost&lt;/a>).&lt;/li>
&lt;li>A &lt;strong>1M-token context&lt;/strong> needs &lt;strong>~125 GB&lt;/strong> of KV, which exceeds both an RTX 4090 (24 GB) and an 80 GB A100 (&lt;a href="https://introl.com/blog/long-context-llm-infrastructure-million-token-windows-guide">Introl · Long-Context LLM Infrastructure&lt;/a>).&lt;/li>
&lt;li>Even a &lt;strong>7B at 128k&lt;/strong> climbs to &lt;strong>~14 GB&lt;/strong> of KV (against ~6 GB at 4k).&lt;/li>
&lt;/ul>
&lt;p>The KV stops being an implementation detail and becomes &lt;strong>the resource that dictates your concurrency&lt;/strong>.&lt;/p>
&lt;h3 id="and-on-top-of-that-attention-is-quadratic">And on top of that, attention is quadratic&lt;/h3>
&lt;p>The KV is linear, but the &lt;strong>compute of attention is quadratic&lt;/strong> in length. When the prompt reaches 1M tokens, generating each token can take on the order of &lt;strong>1.765 seconds, with more than 96 % of the latency spent in attention&lt;/strong> (&lt;a href="https://introl.com/blog/long-context-llm-infrastructure-million-token-windows-guide">Introl&lt;/a>). The aggregate effect is a &lt;strong>throughput collapse of 10×–100×&lt;/strong> against short contexts. This is why long context is not &amp;ldquo;fixed&amp;rdquo; with more VRAM alone: there is a memory problem (KV) &lt;strong>and&lt;/strong> a compute problem (attention) at the same time.&lt;/p>
&lt;h3 id="extending-the-window--serving-it-cheaply">Extending the window ≠ serving it cheaply&lt;/h3>
&lt;p>A common confusion: &amp;ldquo;my model supports 1M of context&amp;rdquo; does not mean &amp;ldquo;I can serve 1M cheaply&amp;rdquo;. The window is &lt;strong>extended&lt;/strong> with &lt;em>RoPE scaling&lt;/em> techniques such as &lt;strong>YaRN&lt;/strong>, the practical option for &lt;em>fine-tuning&lt;/em> open source models to long context: it needs &lt;strong>10× fewer training tokens and 2.5× fewer steps&lt;/strong> than naive RoPE interpolation, and it took LLaMA-2 from 4k to 32k and 128k (&lt;a href="https://arxiv.org/html/2402.13753v1">YaRN/LongRoPE&lt;/a>). But that solves the model &lt;strong>attending&lt;/strong> to long context, not the KV &lt;strong>fitting on your GPU&lt;/strong>. According to the 2026 round-ups, May 2026 marked the first generation of open &lt;strong>million-token&lt;/strong> models (with families supporting 256k extensible to 1M via YaRN) (&lt;a href="https://letsdatascience.com/blog/long-context-models-working-with-1m-token-windows">letsdatascience&lt;/a>). Treat it as a trend, not a closed spec, and verify capabilities per concrete model.&lt;/p>
&lt;hr>
&lt;h2 id="part-2--the-kv-memory-hierarchy">Part 2 · The KV memory hierarchy&lt;/h2>
&lt;p>The core idea of offloading is old in systems: &lt;strong>hierarchical caching&lt;/strong>. &amp;ldquo;Hot&amp;rdquo; KV (what is in use) lives in HBM; &amp;ldquo;warm&amp;rdquo; KV drops to DRAM; &amp;ldquo;cold&amp;rdquo; KV to NVMe; &amp;ldquo;shared across nodes&amp;rdquo; to network or object storage. Every hop multiplies capacity and divides bandwidth.&lt;/p>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 300" role="img" aria-label="KV cache memory hierarchy: HBM, DRAM, NVMe and network/object, with increasing capacity and decreasing bandwidth" xmlns="http://www.w3.org/2000/svg">
&lt;style>.bx{fill:none;stroke:currentColor;stroke-width:1.3}.tl{font:600 13px sans-serif;fill:currentColor}.ts{font:11px sans-serif;fill:currentColor}.ar{fill:none;stroke:currentColor;stroke-width:1.2;stroke-dasharray:4 3;marker-end:url(#kvm)}&lt;/style>
&lt;defs>&lt;marker id="kvm" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="currentColor"/>&lt;/marker>&lt;/defs>
&lt;text x="20" y="26" class="tl">Hot · fast · small&lt;/text>
&lt;rect class="bx" x="20" y="36" width="430" height="44" rx="6"/>
&lt;text x="34" y="55" class="tl">GPU HBM&lt;/text>
&lt;text x="34" y="72" class="ts">~80 GB · several TB/s · the active KV lives here&lt;/text>
&lt;rect class="bx" x="20" y="92" width="500" height="44" rx="6"/>
&lt;text x="34" y="111" class="tl">CPU DRAM&lt;/text>
&lt;text x="34" y="128" class="ts">hundreds of GB–TB · tens–hundreds GB/s · warm KV, reuse across requests&lt;/text>
&lt;rect class="bx" x="20" y="148" width="570" height="44" rx="6"/>
&lt;text x="34" y="167" class="tl">NVMe SSD (local)&lt;/text>
&lt;text x="34" y="184" class="ts">TB · GB/s · cold KV, persistent prefixes, long sessions&lt;/text>
&lt;rect class="bx" x="20" y="204" width="640" height="44" rx="6"/>
&lt;text x="34" y="223" class="tl">Network / object (RDMA, S3, Redis)&lt;/text>
&lt;text x="34" y="240" class="ts">~unlimited · hundreds MB–GB/s · KV shared across cluster nodes&lt;/text>
&lt;path class="ar" d="M690,58 L690,226"/>
&lt;text x="700" y="120" class="ts" transform="rotate(90 700 120)">+capacity&lt;/text>
&lt;text x="700" y="200" class="ts" transform="rotate(90 700 200)">−bandwidth&lt;/text>
&lt;text x="20" y="276" class="ts">KV drops a level as it cools. Fetching it back from a lower level costs a transfer,&lt;/text>
&lt;text x="20" y="292" class="ts">but it is ALWAYS cheaper than recomputing it (quadratic attention) if the prefix is reused.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;h3 id="working-out-when-offloading-pays-off">Working out when offloading pays off&lt;/h3>
&lt;p>Offloading is not magic: moving KV from DRAM/NVMe back to HBM costs time. The rule is simple. &lt;strong>Offloading pays off when the cost of recomputing exceeds the cost of transferring&lt;/strong>:&lt;/p>
$$t_{recompute}(s) \;>\; t_{transfer} = \frac{\text{KV bytes}}{BW_{enlace}}$$
&lt;p>Since \(t_{recompute}\) grows with attention (quadratic) and \(t_{transfer}\) grows only with the size of the KV (linear) divided by the link bandwidth, &lt;strong>the longer the context, the more the balance tips in favour of offloading&lt;/strong>. That is why offloading is &lt;em>the&lt;/em> lever for long context: it is exactly where recomputing becomes unbearable. The LMCache documentation says it without hedging: offloading &lt;strong>only helps when the recomputation it avoids is larger than the overhead it introduces&lt;/strong> (&lt;a href="https://levelup.gitconnected.com/vllm-prefix-caching-vs-lmcache-benchmarking-kv-reuse-tradeoffs-944fbaf98b56">LMCache benchmark&lt;/a>).&lt;/p>
&lt;hr>
&lt;h2 id="part-3--kv-offloading-in-practice-oss">Part 3 · KV offloading in practice (OSS)&lt;/h2>
&lt;h3 id="the-limit-of-out-of-the-box-prefix-caching">The limit of out-of-the-box prefix caching&lt;/h3>
&lt;p>vLLM already caches prefixes in HBM (see &lt;a href="https://blog.lo0.es/en/posts/prefix-cache-hit-rate-engineering/">prefix cache: hit rate engineering&lt;/a> and &lt;a href="https://blog.lo0.es/en/posts/pagedattention-vllm-block-manager/">PagedAttention&lt;/a>). The problem: &lt;strong>it only lives in HBM&lt;/strong>, which is small. When the useful KV exceeds HBM, it is evicted and, on the next request, has to be recomputed. &lt;strong>There is no speedup even with prefix caching enabled&lt;/strong>, because the cache is no longer there (&lt;a href="https://levelup.gitconnected.com/vllm-prefix-caching-vs-lmcache-benchmarking-kv-reuse-tradeoffs-944fbaf98b56">vLLM vs LMCache benchmark&lt;/a>). Native prefix caching is necessary but &lt;strong>insufficient&lt;/strong> for long context.&lt;/p>
&lt;h3 id="lmcache-the-persistent-kv-layer">LMCache: the persistent KV layer&lt;/h3>
&lt;p>&lt;strong>LMCache&lt;/strong> adds persistent storage backends to vLLM&amp;rsquo;s prefix cache (and also supports SGLang and NVIDIA Dynamo as engines). It pulls the KV out of HBM and &lt;strong>shares it across engines and across queries&lt;/strong>, covering a &lt;strong>three-level hierarchy: GPU HBM, CPU DRAM and NVMe SSD&lt;/strong>, with additional backends for Redis/Valkey, Mooncake, InfiniStore, S3 and NIXL/GDS (&lt;a href="https://github.com/LMCache/LMCache">GitHub LMCache&lt;/a>, &lt;a href="https://arxiv.org/pdf/2510.09665">arXiv 2510.09665&lt;/a>). Because DRAM/NVMe hold &lt;strong>far more&lt;/strong> KV than HBM, the &lt;em>hit ratio&lt;/em> rises, and LMCache reports &lt;strong>3×–10× less latency&lt;/strong> combined with vLLM. Against vLLM&amp;rsquo;s basic CPU offload, LMCache loads the KV &lt;strong>at chunk level with high-performance CUDA kernels&lt;/strong>, which cuts transfer overhead.&lt;/p>
&lt;p>A minimal start, offloading to CPU:&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"># vLLM with LMCache as the KV layer (offload to DRAM)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pip install lmcache
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">LMCACHE_CONFIG_FILE&lt;/span>&lt;span class="o">=&lt;/span>lmcache.yaml &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>vllm serve meta-llama/Llama-3.1-70B-Instruct &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --tensor-parallel-size &lt;span class="m">4&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --kv-transfer-config &lt;span class="s1">&amp;#39;{&amp;#34;kv_connector&amp;#34;:&amp;#34;LMCacheConnector&amp;#34;,&amp;#34;kv_role&amp;#34;:&amp;#34;kv_both&amp;#34;}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&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="c"># lmcache.yaml — hierarchy HBM -&amp;gt; DRAM -&amp;gt; NVMe&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">chunk_size&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">256&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">local_cpu&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"># warm KV in DRAM&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">max_local_cpu_size&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">200&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># GB&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">local_disk&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;file:///nvme/lmcache&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># cold KV on NVMe&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">max_local_disk_size&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">2000&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># GB&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Offloading to CPU adds moderate overhead but &lt;strong>retains a good part of the benefit&lt;/strong> against recomputing (&lt;a href="https://docs.lmcache.ai/getting_started/quickstart/offload_kv_cache.html">LMCache docs&lt;/a>). Mind the physical path: that HBM↔DRAM↔NVMe traffic goes over PCIe and across NUMA nodes. It is worth reading &lt;a href="https://blog.lo0.es/en/posts/pcie-topology-gpudirect-p2p-acs-security-guard/">PCIe topology, GPUDirect and ACS&lt;/a> and &lt;a href="https://blog.lo0.es/en/posts/numa-hugepages-cpu-isolation-gpu-waits-kernel/">NUMA, hugepages and CPU isolation&lt;/a>, because a badly wired offload can eat its own gain.&lt;/p>
&lt;h3 id="combine-it-with-fp8">Combine it with FP8&lt;/h3>
&lt;p>Offloading reduces &lt;strong>where&lt;/strong> the KV lives; quantization reduces &lt;strong>how much&lt;/strong> it weighs. They are orthogonal and they add up: KV in FP8 halves the size (see &lt;a href="https://blog.lo0.es/en/posts/fp8-end-to-end-enable-measure-quality-decide/">FP8 end-to-end&lt;/a>), which means half the bytes to transfer on every offload. In long context, &lt;strong>FP8 + offload&lt;/strong> is the default combination.&lt;/p>
&lt;hr>
&lt;h2 id="part-4--kvcache-centric--disaggregated-architecture">Part 4 · KVCache-centric / disaggregated architecture&lt;/h2>
&lt;p>The next level is not just offloading KV, but &lt;strong>redesigning serving around the KV&lt;/strong>.&lt;/p>
&lt;h3 id="mooncake-kimis-platform">Mooncake (Kimi&amp;rsquo;s platform)&lt;/h3>
&lt;p>&lt;strong>Mooncake&lt;/strong>, the serving platform behind &lt;strong>Kimi&lt;/strong> (Moonshot AI), is the reference &lt;strong>KVCache-centric disaggregated&lt;/strong> architecture: it separates the &lt;strong>prefill&lt;/strong> and &lt;strong>decode&lt;/strong> clusters, and exploits the &lt;strong>underused CPU, DRAM and SSD resources&lt;/strong> of the GPU cluster to build a distributed KV cache. Its core is a &lt;strong>KVCache-centric scheduler&lt;/strong> that maximises throughput while respecting SLOs, with a &lt;strong>prediction-based early rejection policy&lt;/strong> for overloaded scenarios (&lt;a href="https://arxiv.org/abs/2407.00079">arXiv 2407.00079&lt;/a>, &lt;a href="https://www.usenix.org/conference/fast25/presentation/qin">USENIX FAST'25&lt;/a>). The long-context numbers are striking: up to &lt;strong>+525 % throughput&lt;/strong> in certain simulated scenarios while respecting the SLO, and in production Kimi handles &lt;strong>115 % and 107 % more requests&lt;/strong> on A800 and H800 clusters respectively against previous systems. The title of their FAST talk sums it up: &lt;em>&amp;ldquo;Trading More Storage for Less Computation&amp;rdquo;&lt;/em>, exactly the rule from Part 2.&lt;/p>
&lt;h3 id="nvidia-dynamo-and-kvbm">NVIDIA Dynamo and KVBM&lt;/h3>
&lt;p>&lt;strong>NVIDIA Dynamo&lt;/strong> is the open source distributed serving framework with &lt;strong>prefill/decode disaggregation&lt;/strong>, dynamic GPU scheduling and &lt;strong>KV-aware routing&lt;/strong>: the &lt;em>PrefillRouter&lt;/em> computes &lt;strong>overlap scores&lt;/strong> between the incoming request and the KV blocks already cached, and routes to the GPUs that &lt;strong>already have&lt;/strong> the relevant KV, avoiding recomputation (&lt;a href="https://docs.dynamo.nvidia.com/dynamo/design-docs/disaggregated-serving">Dynamo · Disaggregated Serving&lt;/a>, &lt;a href="https://docs.nvidia.com/dynamo/latest/user-guides/kv-cache-aware-routing">Dynamo · KV-aware routing&lt;/a>). KV transfer between prefill and decode is handled by &lt;strong>NIXL&lt;/strong> directly GPU-to-GPU over the best available transport (NVLink, InfiniBand). And its offload manager, &lt;strong>KVBM (KV Block Manager)&lt;/strong>, has a three-layer architecture (LLM runtime, logical block management, NIXL transport) that &lt;strong>offloads cold KV to CPU RAM, NVMe or network storage&lt;/strong> to free up HBM (&lt;a href="https://docs.nvidia.com/dynamo/backends/v-llm/kv-cache-offloading">Dynamo · KV Cache Offloading&lt;/a>). This connects with &lt;a href="https://blog.lo0.es/en/posts/disaggregated-serving-prefill-decode-specialised-pods/">disaggregated serving: prefill and decode in specialised pods&lt;/a>.&lt;/p>
&lt;p>The &lt;strong>llm-d&lt;/strong> project walks the same road, from vLLM&amp;rsquo;s prefix caching to KV-aware distributed scheduling (&lt;a href="https://llm-d.ai/blog/kvcache-wins-you-can-see">llm-d · KV-Cache Wins You Can See&lt;/a>).&lt;/p>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 250" role="img" aria-label="KVCache-centric disaggregated architecture: KV-aware router, prefill cluster, tiered distributed KV cache and decode cluster" xmlns="http://www.w3.org/2000/svg">
&lt;style>.bx{fill:none;stroke:currentColor;stroke-width:1.3}.dsh{fill:none;stroke:currentColor;stroke-width:1.3;stroke-dasharray:5 3}.tl{font:600 12px sans-serif;fill:currentColor}.ts{font:11px sans-serif;fill:currentColor}.ar{fill:none;stroke:currentColor;stroke-width:1.3;marker-end:url(#dgm)}&lt;/style>
&lt;defs>&lt;marker id="dgm" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="currentColor"/>&lt;/marker>&lt;/defs>
&lt;rect class="bx" x="20" y="40" width="120" height="46" rx="6"/>
&lt;text x="32" y="60" class="tl">KV-aware router&lt;/text>
&lt;text x="32" y="77" class="ts">overlap score&lt;/text>
&lt;path class="ar" d="M140,63 L185,63"/>
&lt;rect class="bx" x="185" y="40" width="150" height="46" rx="6"/>
&lt;text x="197" y="60" class="tl">PREFILL cluster&lt;/text>
&lt;text x="197" y="77" class="ts">reads+marks up context&lt;/text>
&lt;path class="ar" d="M260,86 L260,120"/>
&lt;rect class="dsh" x="120" y="120" width="430" height="50" rx="6"/>
&lt;text x="134" y="140" class="tl">Distributed KV cache (HBM · DRAM · NVMe · network)&lt;/text>
&lt;text x="134" y="158" class="ts">KVBM / LMCache / Mooncake store · reuse across requests and nodes&lt;/text>
&lt;path class="ar" d="M410,120 L410,86"/>
&lt;rect class="bx" x="430" y="40" width="150" height="46" rx="6"/>
&lt;text x="442" y="60" class="tl">DECODE cluster&lt;/text>
&lt;text x="442" y="77" class="ts">only generates tokens&lt;/text>
&lt;path class="ar" d="M335,63 L430,63"/>
&lt;text x="600" y="50" class="ts">NIXL: direct&lt;/text>
&lt;text x="600" y="66" class="ts">GPU↔GPU KV transfer&lt;/text>
&lt;text x="600" y="82" class="ts">(NVLink/IB)&lt;/text>
&lt;text x="20" y="200" class="ts">KV stops being a by-product of decode and becomes the first-class citizen: the router routes by&lt;/text>
&lt;text x="20" y="216" class="ts">KV overlap, prefill and decode are separated, and the distributed cache avoids recomputing shared prefixes.&lt;/text>
&lt;text x="20" y="232" class="ts">It is "trading storage for computation": you store more KV so you compute less attention.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;hr>
&lt;h2 id="part-5--reference-architecture">Part 5 · Reference architecture&lt;/h2>
&lt;p>On the blog&amp;rsquo;s example cluster, a node with &lt;strong>4×H100 SXM (80 GB, NVLink)&lt;/strong>, &lt;strong>local NVMe&lt;/strong> and plenty of DRAM, a sensible stack to serve long context without recomputing pointlessly:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>KV in FP8&lt;/strong> in the engine (vLLM), to start with half the bytes.&lt;/li>
&lt;li>&lt;strong>LMCache as the KV layer&lt;/strong> with a DRAM→NVMe hierarchy, so that long prefixes (a manual, a code base, a case file) &lt;strong>are cached once and reused&lt;/strong> across sessions without recomputation.&lt;/li>
&lt;li>&lt;strong>KV-aware routing&lt;/strong> (Dynamo or llm-d) if you have several nodes: send the request to the GPU that already holds the prefix, not to any GPU at all.&lt;/li>
&lt;li>&lt;strong>Prefill/decode disaggregation&lt;/strong> once the prefill of enormous contexts starts stealing decode from the other requests. Prefilling 300k tokens monopolises the GPU and kills everyone&amp;rsquo;s latency.&lt;/li>
&lt;li>&lt;strong>Metrics&lt;/strong> for KV &lt;em>hit ratio&lt;/em>, bytes transferred per level and prefill/decode ratio, exported to your observability stack (&lt;a href="https://blog.lo0.es/en/posts/instrumenting-vllm-otel-measuring-optimisations/">instrumenting vLLM with OTel&lt;/a>). Without those three metrics, offloading is faith, not engineering.&lt;/li>
&lt;/ol>
&lt;p>To &lt;strong>prototype&lt;/strong> the stack, validating the LMCache config, the backend formats, the hit/miss behaviour, an &lt;strong>RTX 5090 (Blackwell, 32 GB)&lt;/strong> with NVMe is more than enough to serve a 7–14B and watch offloading work at contexts of 32–128k. Do not expect to serve 1M on a consumer card: the long-context problem is precisely that neither the KV nor the attention fit on small hardware. Real sizing starts from the SLO and from the context-length distribution of your traffic, not from the model&amp;rsquo;s maximum window, and that is where it links to &lt;a href="https://blog.lo0.es/en/posts/capacity-planning-on-premise-llm-inference/">capacity planning&lt;/a>.&lt;/p>
&lt;hr>
&lt;h2 id="part-6--operational-pitfalls-and-honest-scepticism">Part 6 · Operational pitfalls (and honest scepticism)&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Believing that native prefix caching is enough.&lt;/strong> It only lives in HBM; in long context it is evicted and you recompute. You need a persistent layer (LMCache/KVBM) for the cache to survive.&lt;/li>
&lt;li>&lt;strong>Offloading without measuring the break-even.&lt;/strong> If your prefixes &lt;strong>are not reused&lt;/strong>, offloading only adds transfer latency without saving recomputation. Offloading shines with &lt;strong>shared prefixes&lt;/strong> (RAG over the same corpus, agents with the same system prompt, long sessions), not with one-off, never-repeated requests.&lt;/li>
&lt;li>&lt;strong>Forgetting that attention is still quadratic.&lt;/strong> Offloading fixes the &lt;strong>memory&lt;/strong> of the KV, not the &lt;strong>compute&lt;/strong> of attention. For real 1M tokens you also need efficient attention, &lt;em>sparse attention&lt;/em> or retrieval approaches (RetrievalAttention, ShadowKV); offloading on its own does not bring down the 1.765 s/token.&lt;/li>
&lt;li>&lt;strong>Wiring the offload badly.&lt;/strong> HBM↔DRAM↔NVMe crosses PCIe and NUMA. An offload that crosses the wrong NUMA node or saturates a PCIe lane can be &lt;strong>slower than recomputing&lt;/strong>. Measure the link&amp;rsquo;s real bandwidth, not the datasheet figure.&lt;/li>
&lt;li>&lt;strong>Treating the figures as constants.&lt;/strong> The 93 GB, the +525 %, the 3–10× all depend on model, length, hardware and reuse pattern. They are &lt;strong>orders of magnitude for design&lt;/strong>; measure on your own load.&lt;/li>
&lt;li>&lt;strong>Trusting million-token model specs without verifying.&lt;/strong> The landscape of open 1M models is very recent (2026) and moves fast; the specific names and limits change between versions. Verify the window &lt;strong>and the cost of serving it&lt;/strong> per model, do not take the headline on trust.&lt;/li>
&lt;/ol>
&lt;p>A note of caution for June 2026: the distributed KV layer (LMCache, Mooncake, KVBM, NIXL, llm-d) is &lt;strong>setting right now&lt;/strong>, with APIs and backends that change release by release. It is exactly the kind of piece worth assembling behind an abstraction (vLLM&amp;rsquo;s &lt;em>kv_connector&lt;/em>) rather than coupling to a single vendor.&lt;/p>
&lt;hr>
&lt;h2 id="closing">Closing&lt;/h2>
&lt;p>Long context is a problem of &lt;strong>memory and compute at the same time&lt;/strong>, and the KV cache is the bottleneck of both. The window is a gift from the model; serving it cheaply is systems engineering: &lt;strong>quantise the KV (FP8), offload it by tiers (HBM→DRAM→NVMe→network) with a persistent layer, and reorganise serving around the KV&lt;/strong> (aware routing, separated prefill/decode) when scale demands it. The rule that governs it all is Mooncake&amp;rsquo;s: &lt;strong>trade storage for computation&lt;/strong>. You store more KV in cheap places so you do not recompute expensive attention. Do it well and a 4×H100 node serves contexts that, by recomputing, would not even get started. Do it badly, or not at all, and every long request reads the whole case file from scratch again, while the GPU drowns in a quadratic attention you had already paid for once.&lt;/p>
&lt;h2 id="sources">Sources&lt;/h2>
&lt;ul>
&lt;li>LMCache · GitHub — &lt;a href="https://github.com/LMCache/LMCache">https://github.com/LMCache/LMCache&lt;/a>&lt;/li>
&lt;li>LMCache: An Efficient KV Cache Layer (arXiv 2510.09665) — &lt;a href="https://arxiv.org/pdf/2510.09665">https://arxiv.org/pdf/2510.09665&lt;/a>&lt;/li>
&lt;li>LMCache · Offload KV cache to CPU (docs) — &lt;a href="https://docs.lmcache.ai/getting_started/quickstart/offload_kv_cache.html">https://docs.lmcache.ai/getting_started/quickstart/offload_kv_cache.html&lt;/a>&lt;/li>
&lt;li>vLLM Prefix Caching vs. LMCache: Benchmarking KV Reuse Tradeoffs — &lt;a href="https://levelup.gitconnected.com/vllm-prefix-caching-vs-lmcache-benchmarking-kv-reuse-tradeoffs-944fbaf98b56">https://levelup.gitconnected.com/vllm-prefix-caching-vs-lmcache-benchmarking-kv-reuse-tradeoffs-944fbaf98b56&lt;/a>&lt;/li>
&lt;li>Mooncake: A KVCache-centric Disaggregated Architecture (arXiv 2407.00079) — &lt;a href="https://arxiv.org/abs/2407.00079">https://arxiv.org/abs/2407.00079&lt;/a>&lt;/li>
&lt;li>Mooncake · USENIX FAST'25 (Trading More Storage for Less Computation) — &lt;a href="https://www.usenix.org/conference/fast25/presentation/qin">https://www.usenix.org/conference/fast25/presentation/qin&lt;/a>&lt;/li>
&lt;li>Mooncake · GitHub — &lt;a href="https://github.com/kvcache-ai/Mooncake/">https://github.com/kvcache-ai/Mooncake/&lt;/a>&lt;/li>
&lt;li>NVIDIA Dynamo · Disaggregated Serving (docs) — &lt;a href="https://docs.dynamo.nvidia.com/dynamo/design-docs/disaggregated-serving">https://docs.dynamo.nvidia.com/dynamo/design-docs/disaggregated-serving&lt;/a>&lt;/li>
&lt;li>NVIDIA Dynamo · KV-aware routing (docs) — &lt;a href="https://docs.nvidia.com/dynamo/latest/user-guides/kv-cache-aware-routing">https://docs.nvidia.com/dynamo/latest/user-guides/kv-cache-aware-routing&lt;/a>&lt;/li>
&lt;li>NVIDIA Dynamo · KV Cache Offloading / KVBM (docs) — &lt;a href="https://docs.nvidia.com/dynamo/backends/v-llm/kv-cache-offloading">https://docs.nvidia.com/dynamo/backends/v-llm/kv-cache-offloading&lt;/a>&lt;/li>
&lt;li>llm-d · KV-Cache Wins You Can See — &lt;a href="https://llm-d.ai/blog/kvcache-wins-you-can-see">https://llm-d.ai/blog/kvcache-wins-you-can-see&lt;/a>&lt;/li>
&lt;li>Long-Context Inference at Scale: The Hidden Infrastructure Cost · DigitalOcean — &lt;a href="https://www.digitalocean.com/community/tutorials/long-context-inference-production-cost">https://www.digitalocean.com/community/tutorials/long-context-inference-production-cost&lt;/a>&lt;/li>
&lt;li>Long-Context LLM Infrastructure · Introl — &lt;a href="https://introl.com/blog/long-context-llm-infrastructure-million-token-windows-guide">https://introl.com/blog/long-context-llm-infrastructure-million-token-windows-guide&lt;/a>&lt;/li>
&lt;li>YaRN / LongRoPE (arXiv 2402.13753) — &lt;a href="https://arxiv.org/html/2402.13753v1">https://arxiv.org/html/2402.13753v1&lt;/a>&lt;/li>
&lt;/ul></description></item></channel></rss>