calricon.

General

The vLLM PagedAttention KV Cache Trap: Why High Concurrency Triggers Preemption Storms

Oct 25, 2026/10 min read

calricon / field note

vLLM's PagedAttention revolutionized LLM serving by eliminating KV cache memory waste. But under enterprise agent workloads, high concurrency exhausts block allocation—triggering cascading request preemptions, PCIe thrashing, and timeout loops. Here is how to architect zero-preemption LLM clusters.

Oct 25, 2026/General
260
Advertisement
<h1>The vLLM PagedAttention KV Cache Trap: Why High Concurrency Triggers Preemption Storms</h1>

<p><strong>When engineering teams transition from commercial LLM APIs to self-hosted open-weights models (such as Llama 3.3 70B, DeepSeek-V3, or Qwen 2.5), vLLM is almost universally chosen as the serving backbone.</strong> Its breakthrough algorithm, <strong>PagedAttention</strong>, was hailed as a computer science triumph: by managing Key-Value (KV) cache memory in discrete, non-contiguous physical memory blocks inspired by OS virtual memory paging, vLLM eliminated up to 80% of GPU memory fragmentation and skyrocketed serving throughput by 2x to 4x. Benchmarks look breathtaking in synthetic load tests with uniform sequence lengths. Yet, when enterprise teams deploy vLLM into production to serve real-world agentic workflows—characterized by massive system prompts, multi-turn tool calling, dynamic RAG documents, and bursty token generation—the inference cluster routinely enters a catastrophic failure state: <strong>The KV Cache Preemption Storm</strong>.</p>

<p>The root cause is a fundamental architectural conflict between dynamic memory reservation and unpredictable token generation. When GPU High Bandwidth Memory (HBM) allocation approaches capacity, vLLM cannot allocate new KV cache blocks for in-flight requests. Rather than rejecting new arrivals at the edge, vLLM's default scheduling engine forcibly <em>preempts</em> running requests—either swapping their KV blocks out to host CPU RAM over the saturated PCIe bus or aborting their state and recomputing the entire prompt from scratch. Time-to-first-token (TTFT) and inter-token latency spike by 2,000%, upstream HTTP reverse proxies fire 504 timeouts, and client retries flood the already choked GPU cluster in an irreversible death spiral. Here is an architectural deep dive into why PagedAttention breaks under bursty enterprise concurrency and the engineering playbook required to run zero-preemption LLM infrastructure.</p>

<h2>The Physics of KV Cache: How PagedAttention Actually Works</h2>

<p>In autoregressive transformer models, generating each new token requires attending to the Key and Value vectors of all preceding tokens in the context window. Storing these tensors in GPU HBM is the primary memory bottleneck of LLM inference.</p>

<p>In legacy serving engines (like naive HuggingFace pipelines or early TensorRT-LLM), memory was allocated contiguously for the <em>maximum possible sequence length</em> (e.g., reserving memory for 8,192 tokens upfront even if the user prompt was only 100 tokens). This led to massive internal and external fragmentation, wasting 60% to 80% of expensive GPU VRAM.</p>

<p>PagedAttention solved this by introducing virtual memory paging to GPU tensors:</p>

<ul>
<li><strong>Logical vs. Physical Blocks:</strong> A request's context is divided into logical blocks of fixed size (typically 16 or 32 tokens).</li>
<li><strong>The Block Table:</strong> A centralized memory scheduler maintains a page table mapping each request's logical blocks to arbitrary, non-contiguous physical blocks allocated in a pre-reserved GPU memory pool.</li>
<li><strong>Dynamic On-Demand Allocation:</strong> As the model generates tokens, vLLM allocates new physical blocks only when the current block is filled. When a request completes, its physical blocks are immediately returned to the free pool.</li>
</ul>

<p>To maximize concurrency, platform engineers configure <code>gpu_memory_utilization: 0.90</code> or <code>0.95</code>, directing vLLM to reserve 95% of available GPU VRAM for the model weights and the KV block pool.</p>

<blockquote>
<p>"PagedAttention eliminated spatial memory fragmentation, but it introduced temporal memory vulnerability. When you allocate 95% of your VRAM to a dynamic token pool, a single concurrency spike converts a memory engine into an eviction furnace."</p>
</blockquote>

<h2>The Anatomy of a Preemption Storm</h2>

<p>To understand how a preemption storm ignites, consider an enterprise customer support agent cluster running on an 8x NVIDIA H100 (80GB SXM5) node serving Llama-3-70B-Instruct with FP8 quantization.</p>

<h3>Phase 1: The Concurrency Burst</h3>
<p>Under steady load, vLLM concurrently decodes 64 streams. Total KV cache memory hover around 75% utilization. Then, a traffic surge occurs: twenty new agent tasks arrive simultaneously, each injecting an 8,000-token prompt containing RAG context, tool definitions, and historical conversation logs.</p>

<h3>Phase 2: KV Cache Exhaustion</h3>
<p>vLLM's scheduler accepts the new requests and processes their prefill phases. Suddenly, free physical KV blocks in GPU HBM drop to zero. However, thirty existing decoding requests are actively generating output tokens. Every time a running request attempts to emit its 17th or 33rd token, it requires a new physical block from the pool.</p>

<h3>Phase 3: The Preemption Decision</h3>
<p>The scheduler cannot allocate memory. Under vLLM's core scheduling algorithm (<code>vllm/core/scheduler.py</code>), the engine evaluates two preemption strategies:</p>

<ol>
<li><strong>Swapping (Swap to CPU):</strong> The scheduler evicts the KV cache blocks of lower-priority in-flight requests across the PCIe bus into host system RAM (DDR5). When GPU blocks eventually free up, the evicted blocks must be read back across PCIe. But PCIe Gen5 x16 bandwidth (64 GB/s) is a glacial trickle compared to H100 HBM3 bandwidth (3.35 TB/s)! Transferring gigabytes of KV blocks stalls GPU compute, spiking inter-token generation latency from 15ms to over <strong>1,800ms</strong>.</li>
<li><strong>Recomputation (Drop and Re-prefill):</strong> If swapping is disabled or host CPU memory is exhausted, vLLM simply drops the request's KV blocks entirely! When GPU memory clears, vLLM re-executes the prefill pass for the entire 8,000-token prompt from scratch, throwing away hundreds of generated tokens and consuming massive compute FLOPs for zero progress.</li>
</ol>

<h3>Phase 4: The Cascading Death Spiral</h3>
<p>Because preempted requests pause for 2 to 4 seconds, upstream ingress proxies (Envoy, NGINX, or AWS ALB) hit their HTTP request or idle connection timeouts. The proxy closes the connection with an HTTP 504 Gateway Timeout. The client application (e.g., LangChain, AutoGen, or an enterprise frontend) immediately fires an automatic retry. The retry submits another 8,000-token prompt to the cluster, adding fresh demand to a GPU engine that is already drowning in preemption loops!</p>

<h2>Trap 2: Automatic Prefix Caching (APC) LRU Thrashing</h2>

<p>To optimize TTFT for agentic workflows, engineers enable Automatic Prefix Caching (<code>enable_prefix_caching: true</code>). This allows vLLM to reuse KV cache blocks across requests that share identical prefixes (such as a common 4,000-token system prompt containing enterprise instructions and tool schemas).</p>

<p>However, vLLM manages cached prefix blocks using a standard <strong>Least Recently Used (LRU)</strong> eviction policy:</p>

<ul>
<li>Under KV memory pressure, the scheduler views cached prefix blocks as expendable free memory.</li>
<li>To satisfy immediate generation requests, vLLM evicts the cached prefix blocks of your primary system prompt.</li>
<li>A fraction of a second later, the next incoming request arrives. Instead of experiencing a 20ms cached prefill, it suffers a full 1,200ms cold prefill as the GPU recomputes the exact same system prompt tensors.</li>
<li>Under heavy traffic, prefix caching degrades into continuous cache thrashing: blocks are allocated, evicted, and recomputed in an endless, expensive loop.</li>
</ul>

<h2>The Performance Comparison: Naive vLLM vs. Standard API vs. Calricon Zero-Preemption Cluster</h2>

<table>
<thead>
<tr>
<th>Dimension</th>
<th>Naive Default vLLM Serving</th>
<th>Commercial Closed API</th>
<th>Calricon Zero-Preemption Cluster</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>GPU Memory Utilization</strong></td>
<td>0.95 (Aggressive / Fragile)</td>
<td>Opaque / Proprietary</td>
<td><strong>0.82 (Calibrated Dynamic Headroom)</strong></td>
</tr>
<tr>
<td><strong>Preemption Strategy</strong></td>
<td>CPU Swapping / Recompute</td>
<td>Server-side queuing</td>
<td><strong>Zero Preemption (Edge Admission Queue)</strong></td>
</tr>
<tr>
<td><strong>Inter-Token Latency P99</strong></td>
<td>2,400ms (During memory bursts)</td>
<td>350ms – 700ms</td>
<td><strong>28ms (Strictly deterministic)</strong></td>
</tr>
<tr>
<td><strong>Prefix Cache Retention</strong></td>
<td>Thrashing under high memory</td>
<td>Opaque LRU</td>
<td><strong>Static Prefix Pinning in HBM</strong></td>
</tr>
<tr>
<td><strong>Serving Architecture</strong></td>
<td>Monolithic (Prefill + Decode combined)</td>
<td>Disaggregated</td>
<td><strong>Disaggregated Prefill/Decode Split</strong></td>
</tr>
</tbody>
</table>

<h2>The Calricon Production Playbook: Building Zero-Preemption LLM Clusters</h2>

<p>To eliminate preemption storms and deliver rock-solid, single-digit millisecond token generation under bursty enterprise workloads, implement this four-pillar architecture:</p>

<h3>1. Enforce External Token-Budgeted Admission Control</h3>
<p>Never allow raw client traffic to hit vLLM directly. Deploy an intelligent gateway (e.g., an Envoy proxy with a custom Lua/WASM filter or an intelligent API gateway) in front of the vLLM pod fleet. The gateway tracks active concurrent sequences and calculates the exact token budget:</p>

<pre><code>// GATEWAY ADMISSION FILTER: Token-budgeted semaphore
function on_request(request) {
local prompt_tokens = estimate_tokens(request.body.prompt);
local max_tokens = request.body.max_tokens or 1024;
local required_blocks = math.ceil((prompt_tokens + max_tokens) / 16);

if (active_reserved_blocks + required_blocks &gt; CLUSTER_SAFE_BLOCK_LIMIT) {
// Queue request in Redis priority queue; NEVER admit to vLLM if it risks preemption!
return queue_request(request);
}

active_reserved_blocks = active_reserved_blocks + required_blocks;
forward_to_vllm(request);
}
</code></pre>
<p>By enforcing admission at the gateway layer, requests queue in cheap CPU memory at the edge. Admitted requests are guaranteed to complete generation without ever experiencing mid-stream preemption.</p>

<h3>2. Calibrate GPU Memory Utilization and Disable CPU Swapping</h3>
<p>Stop setting <code>gpu_memory_utilization: 0.95</code>. In dynamic agent workloads with variable generation lengths, reserve a minimum of <strong>15% to 18% memory headroom</strong>:</p>

<pre><code># vLLM Production Launch Arguments
python3 -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-3.3-70B-Instruct \
--tensor-parallel-size 8 \
--gpu-memory-utilization 0.82 \
--max-num-seqs 128 \
--max-model-len 16384 \
--swap-space 0 \ # DISABLE CPU SWAP ENTIRELY!
--block-size 16 \
--enable-prefix-caching
</code></pre>
<p>Setting <code>--swap-space 0</code> is essential: CPU swapping across PCIe is an architectural illusion that creates false hope and unbounded latency spikes. When swapping is disabled and gateway admission control is active, the engine operates purely within ultra-fast HBM.</p>

<h3>3. Adopt Disaggregated Prefill and Decode (Split Serving)</h3>
<p>Prefill and Decode have fundamentally opposing hardware demands: Prefill is compute-bound (saturating GPU Tensor Cores), while Decode is memory-bandwidth-bound (sequential memory accesses). Running both on the same GPU guarantees that heavy prefill requests starve ongoing decode streams.</p>

<p>Deploy a disaggregated architecture:</p>

<ul>
<li><strong>Prefill Workers:</strong> Optimized for high-throughput batch matrix multiplication with short KV retention.</li>
<li><strong>Decode Workers:</strong> Optimized for low-latency memory bandwidth, holding persistent KV caches.</li>
<li>Connect workers using high-speed RDMA or NVSwitch KV-transfer protocols (such as Mooncake or vLLM disaggregated serving), decoupling initial prompt ingestion from token generation.</li>
</ul>

<h3>4. Pin Foundational Agent Prefixes in Memory</h3>
<p>If your enterprise runs agents with standardized system prompts (e.g., corporate compliance rules, database schemas, tool declarations), prevent LRU eviction from dropping these critical prefixes. Use prefix-pinning patches or persistent pre-warmed virtual sessions to mark foundational blocks as immutable, ensuring that high-concurrency bursts never force costly recomputation of your core agent instructions.</p>

<h2>Conclusion</h2>

<p>vLLM's PagedAttention is a revolutionary building block for modern artificial intelligence, but deploying it with default configurations under enterprise agent workloads is an invitation to production gridlock. Memory paging solves spatial fragmentation, but it cannot defy the laws of memory saturation. By enforcing strict admission control at the gateway, disabling PCIe swap thrashing, calibrating memory headroom, and separating prefill from decode, engineering teams can banish preemption storms forever and achieve true enterprise-grade inference performance.</p>
Advertisement
Talk to Calricon