<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Llm-Inference on Synaptic Radio</title><link>https://synapticradio.com/tags/llm-inference/</link><description>Recent content in Llm-Inference on Synaptic Radio</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><managingEditor>anshuman264@gmail.com (Anshuman Sahoo)</managingEditor><webMaster>anshuman264@gmail.com (Anshuman Sahoo)</webMaster><copyright>Anshuman Sahoo</copyright><lastBuildDate>Mon, 20 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://synapticradio.com/tags/llm-inference/index.xml" rel="self" type="application/rss+xml"/><item><title>The KV Cache Is an Allocator Problem</title><link>https://synapticradio.com/post/paged-kv-cache-allocation/</link><pubDate>Sun, 19 Jul 2026 00:00:00 +0000</pubDate><author>anshuman264@gmail.com (Anshuman Sahoo)</author><guid>https://synapticradio.com/post/paged-kv-cache-allocation/</guid><description>&lt;img src="https://synapticradio.com/images/paged-kv-cache-allocation/allocator-layout.svg" alt="Featured image of post The KV Cache Is an Allocator Problem" />&lt;p>A serving system can have enough GPU memory for every live token and still reject a new request. The failure is not necessarily attention, model size, or arithmetic intensity. It can be the allocator.&lt;/p>
&lt;p>This article rebuilds the memory-management argument behind paged KV caches with a small, executable simulator. The experiment is deliberately narrower than a serving benchmark: it asks how three allocation policies behave under the same arrival times, prompt lengths, generation lengths, and completion times. That separation matters. It lets us test the allocator before attributing throughput changes to kernels, schedulers, or batching.&lt;/p>
&lt;h2 id="one-request-two-kinds-of-waste">One request, two kinds of waste
&lt;/h2>&lt;p>During autoregressive decoding, each sequence grows one token at a time. Its key-value cache therefore grows dynamically. A simple implementation can reserve one contiguous region sized for the sequence&amp;rsquo;s maximum possible length. This makes addressing easy, but it creates two distinct losses:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Reservation waste:&lt;/strong> memory is held for tokens that have not been generated.&lt;/li>
&lt;li>&lt;strong>External fragmentation:&lt;/strong> free memory exists, but not as one sufficiently large contiguous interval.&lt;/li>
&lt;/ol>
&lt;p>PagedAttention was introduced to avoid requiring each sequence&amp;rsquo;s KV cache to occupy one contiguous physical region. It divides the cache into fixed-size blocks and uses a block table to map logical token positions to physical blocks. The original vLLM paper reports that reducing fragmentation and enabling sharing increased serving throughput in its evaluated systems, but those end-to-end results combine allocation, batching, kernels, and scheduling. This simulator isolates only the allocation claim.&lt;/p>
&lt;p>Consider a cache with 80 token slots. Three requests reserve 24, 16, and 16 contiguous slots. If the middle request finishes, 16 slots are free. A new request needing 20 slots still cannot start: the largest free interval is 16. A block allocator can use free blocks from multiple locations because the logical sequence is no longer tied to one physical interval.&lt;/p>
&lt;p>The featured diagram shows this contrast: contiguous allocation can have enough total free capacity while still lacking one large interval, while paged allocation can assemble a logical cache from smaller physical blocks.&lt;/p>
&lt;p>The block table adds indirection. A decode kernel must translate a logical token position into a block ID and an offset. That makes memory layout and access order performance concerns. NVIDIA&amp;rsquo;s CUDA guidance emphasizes coalesced global-memory access because scattered warp accesses require more memory transactions. Paging fixes capacity loss; it does not guarantee an efficient kernel.&lt;/p>
&lt;h2 id="the-experiment">The experiment
&lt;/h2>&lt;p>The simulator compares three policies:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Maximum reservation:&lt;/strong> reserve one contiguous interval for &lt;code>prompt + max_new_tokens&lt;/code>.&lt;/li>
&lt;li>&lt;strong>Exact contiguous growth:&lt;/strong> extend a sequence contiguously as tokens arrive; relocation is disallowed.&lt;/li>
&lt;li>&lt;strong>Paged growth:&lt;/strong> allocate fixed-size blocks on demand.&lt;/li>
&lt;/ul>
&lt;p>Every policy receives the same deterministic event trace. A request is admitted only if its initial prompt cache can be allocated. At each decode step, the allocator tries to grow every active request by one token. We record admitted and rejected requests, live tokens, reserved slots, internal waste, free capacity, largest contiguous free interval, and allocation failures despite sufficient total free capacity.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">python code/kv_allocator.py --scenario data/scenario.json --output outputs/results.json
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">python tests/test_allocator.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">python code/make_figure.py --results outputs/results.json --output figures/allocator-layout.svg
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The trace is synthetic. It is not evidence for a specific model, GPU, or production throughput number. Its role is to make allocator behavior inspectable and falsifiable.&lt;/p>
&lt;h2 id="what-the-trace-shows">What the trace shows
&lt;/h2>&lt;p>Across the supplied trace, maximum reservation fails first because it holds space for future tokens. Exact contiguous growth uses less reserved memory early, but it eventually encounters external fragmentation: total free capacity is sufficient while the largest free interval is too small. Paged growth with four-slot blocks admits the full trace. Larger blocks fail in this deliberately tight capacity because their tail waste consumes enough slots to block later growth. Internal waste remains bounded by the unfilled tail of each sequence&amp;rsquo;s final block, but that bound can still matter operationally.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Policy&lt;/th>
&lt;th style="text-align: right">Rejected requests&lt;/th>
&lt;th style="text-align: right">Growth failures&lt;/th>
&lt;th style="text-align: right">Peak reserved slots&lt;/th>
&lt;th style="text-align: right">Peak waste&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Maximum reservation&lt;/td>
&lt;td style="text-align: right">2&lt;/td>
&lt;td style="text-align: right">0&lt;/td>
&lt;td style="text-align: right">70&lt;/td>
&lt;td style="text-align: right">33&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Exact contiguous growth&lt;/td>
&lt;td style="text-align: right">0&lt;/td>
&lt;td style="text-align: right">4&lt;/td>
&lt;td style="text-align: right">31&lt;/td>
&lt;td style="text-align: right">0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Paged, block size 4&lt;/td>
&lt;td style="text-align: right">0&lt;/td>
&lt;td style="text-align: right">0&lt;/td>
&lt;td style="text-align: right">64&lt;/td>
&lt;td style="text-align: right">8&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Paged, block size 8&lt;/td>
&lt;td style="text-align: right">0&lt;/td>
&lt;td style="text-align: right">1&lt;/td>
&lt;td style="text-align: right">72&lt;/td>
&lt;td style="text-align: right">13&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Paged, block size 16&lt;/td>
&lt;td style="text-align: right">1&lt;/td>
&lt;td style="text-align: right">1&lt;/td>
&lt;td style="text-align: right">80&lt;/td>
&lt;td style="text-align: right">25&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The key result is not that paging makes memory free. It changes the unit of allocation:&lt;/p>
\[
\text{physical address} =
\text{block table}[\lfloor t/B \rfloor] \times B + (t \bmod B),
\]&lt;p>where \(t\) is a logical token position and \(B\) is the block size.&lt;/p>
&lt;p>That mapping removes the requirement that a sequence&amp;rsquo;s physical cache be contiguous. The cost is that block size now controls a trade-off.&lt;/p>
&lt;p>Small blocks reduce tail waste but enlarge block tables and increase allocation operations. Large blocks reduce metadata and allocation frequency but increase internal fragmentation. For a sequence with \(L\) cached tokens, a fixed block size \(B\) reserves&lt;/p>
\[
B\left\lceil \frac{L}{B}\right\rceil
\]&lt;p>slots, so tail waste is between zero and \(B-1\) slots per sequence.&lt;/p>
&lt;p>The simulator tests block sizes 4, 8, and 16. The four-slot policy completes the trace, while the larger blocks encounter capacity failures caused by greater tail waste. The smallest block size also has the lowest reserved-slot waste in this trace. That does &lt;strong>not&lt;/strong> establish that it would have the best serving throughput. Kernel efficiency, metadata traffic, prefix sharing, scheduler overhead, and hardware behavior can reverse the choice.&lt;/p>
&lt;h2 id="what-paging-does-not-solve">What paging does not solve
&lt;/h2>&lt;p>A block allocator is only one layer of an inference engine.&lt;/p>
&lt;p>It does not decide which request should decode next. It does not make attention computation sublinear in context length. It does not ensure that gathered KV reads are coalesced. It does not remove the need for admission control, preemption, prefix-cache policy, or latency objectives.&lt;/p>
&lt;p>There are also credible alternatives. vAttention keeps the KV cache contiguous in virtual address space while mapping physical memory dynamically through CUDA virtual-memory mechanisms. Its authors argue that this preserves compatibility with existing attention kernels and avoids some PagedAttention complexity. That comparison separates the goal-dynamic physical allocation-from one particular implementation.&lt;/p>
&lt;p>PyTorch&amp;rsquo;s scaled-dot-product-attention interface can dispatch among fused implementations, and its documentation notes backend-specific constraints. A production design therefore has to reconcile allocator layout with the kernels selected for the target shapes and hardware.&lt;/p>
&lt;h2 id="a-practical-design-rule">A practical design rule
&lt;/h2>&lt;p>Treat the KV cache as a memory subsystem with an explicit contract:&lt;/p>
&lt;ul>
&lt;li>the scheduler owns request admission and lifetime;&lt;/li>
&lt;li>the allocator owns logical-to-physical mapping and reclamation;&lt;/li>
&lt;li>the attention kernel owns efficient reads through that mapping;&lt;/li>
&lt;li>telemetry distinguishes internal waste, external fragmentation, allocation latency, and kernel cost.&lt;/li>
&lt;/ul>
&lt;p>Then test each layer separately before running an end-to-end throughput benchmark.&lt;/p>
&lt;p>The simulator in this bundle is a mastery artifact contract, not evidence of mastery. A stronger follow-up would implement the same trace against a real serving engine, collect allocator and kernel timelines, and test whether the block size that minimizes waste also minimizes time per output token. The likely answer is “not always”-which is exactly why allocator correctness should be established before performance is interpreted.&lt;/p>
&lt;h2 id="references">References
&lt;/h2>&lt;ul>
&lt;li>Woosuk Kwon et al., &lt;a class="link" href="https://doi.org/10.1145/3600006.3613165" target="_blank" rel="noopener"
>&amp;ldquo;Efficient Memory Management for Large Language Model Serving with PagedAttention&amp;rdquo;&lt;/a>, SOSP 2023.&lt;/li>
&lt;li>Ramya Prabhu et al., &lt;a class="link" href="https://arxiv.org/abs/2405.04437" target="_blank" rel="noopener"
>&amp;ldquo;vAttention: Dynamic Memory Management for Serving LLMs without PagedAttention&amp;rdquo;&lt;/a>, 2024.&lt;/li>
&lt;li>NVIDIA, &lt;a class="link" href="https://docs.nvidia.com/cuda/cuda-c-best-practices-guide/index.html" target="_blank" rel="noopener"
>CUDA C++ Best Practices Guide&lt;/a>.&lt;/li>
&lt;li>PyTorch, &lt;a class="link" href="https://docs.pytorch.org/docs/main/generated/torch.nn.functional.scaled_dot_product_attention.html" target="_blank" rel="noopener"
>&lt;code>torch.nn.functional.scaled_dot_product_attention&lt;/code>&lt;/a>.&lt;/li>
&lt;/ul></description></item></channel></rss>