skip to content

What happens to a running request when the KV cache runs out of free blocks?

level: seniorimportance: must knowfreq 52%

answer

  1. admission is a bet, not a reservation
  2. someone's blocks must be reclaimed
  3. newest requests preempted first
  4. recompute pays GPU, swap pays PCIe
  5. the client stalls, it does not fail

basics

~20 s

The scheduler preempts a running sequence: it frees that sequence's blocks and returns it to the waiting queue, either discarding its cache to recompute later or copying it to host memory. The client sees a stall, not an error, and tokens already streamed are never taken back.

solid answer

~60 s

Decode needs a new block each time a sequence's tail block fills. If the free list is empty, the scheduler must take blocks from somewhere, and the only place left is another running sequence. It picks a victim — typically the most recently admitted one, so the oldest work finishes rather than everyone starving equally — frees its blocks, and moves it back to the waiting queue to be rescheduled when memory frees up. There are two ways to give the blocks back. **Recompute** throws the cache away and re-runs prefill over the sequence's prompt *plus the tokens it already generated* when it resumes; with prefix caching on, much of that prefill can come back from the cache for free. **Swap** copies the blocks to host memory over PCIe and copies them back on resume. vLLM's V1 engine uses recompute. Either way the request is not failed and streamed tokens are not lost — the user sees a pause, and under sustained pressure the server thrashes: work is repeated, goodput falls faster than load rises.

go deeper

for a junior

Know that a request can be paused mid-generation when the GPU's cache fills, and that this appears to the user as a delay rather than as an error.

for a middle

Be able to describe the mechanism: blocks are reclaimed from a chosen victim, the victim returns to the waiting queue, and resuming costs either a recompute or a transfer back from host memory.

for a senior

Demonstrate the operational read — occasional preemption means healthy admission pressure, sustained preemption means repeated work and falling goodput while utilization still looks high — and name concurrency caps and generation-length caps as the first levers.

for a principal

Own the policy question: whether saturation should present as an unbounded stall or as explicit shedding at the edge is a product and SLO decision, and it determines how much headroom you buy per replica.

## Why running out is normal, not exceptional A paged engine admits requests based on the blocks they need *now*, not on the blocks they will need by the end of generation. That is the whole point — reserving for the worst case is what paging eliminated. The consequence is that admission is a bet: the scheduler may admit sequences that collectively cannot be carried to completion. When they all keep generating, the free list drains, and some sequence's next block allocation fails. This is a designed-for condition with a defined response, not a crash. ## The response: preemption The scheduler selects a victim sequence and reclaims its blocks. Selection policy matters: engines generally preempt the most recently scheduled requests first. That is deliberately unfair, and deliberately correct — under overload, finishing the oldest requests and delaying the newest yields more completed work than slowing everyone uniformly, which is the shape that turns a queue into a crowd of half-finished requests all holding memory. The victim goes back to the front of the waiting queue, so it resumes as soon as blocks free up — usually when some other sequence emits its stop token and releases everything at once. ## Recompute versus swap **Recompute** simply drops the victim's KV blocks. On resume, the engine re-runs prefill over the original prompt *plus every token the sequence had already generated* — those tokens are now part of its input, and their keys and values must exist before decoding can continue. The cost is one prefill of the current length, which is compute-efficient batched work the GPU is good at, and prefix caching can serve much of it from blocks that were never evicted. The cost is paid in GPU time. **Swap** copies the victim's blocks to pinned host memory and copies them back on resume. Nothing is recomputed, but the transfer crosses PCIe in both directions, competing with everything else on that bus, and the host-side buffer is itself a fixed budget that can fill. Swap wins when recomputing would be expensive relative to the transfer — very long sequences — and loses when PCIe is the scarcer resource, which on a modern GPU it usually is. vLLM's V1 engine preempts by recompute. Naming swap as vLLM's mode today is a version error worth avoiding; it belonged to the older V0 engine. The concept still matters because offloading KV state to host or remote memory is an active direction, exposed through connector interfaces rather than as an in-scheduler swap mode. ## What the client experiences Nothing fails. The connection stays open, tokens already streamed stay streamed, and the stream simply pauses until the request is rescheduled. To a user this looks like the model thinking mid-sentence; to a p99 inter-token-latency chart it looks like a cliff. The distinction interviewers are testing is that an LLM server under memory pressure does not shed load the way a stateless service does — it silently redistributes latency and, with recompute, does *more* total work per completed request. Some deployments prefer explicit shedding to that behaviour, rejecting or queueing at the edge when the engine is saturated, so callers get a fast failure they can retry or route elsewhere instead of an unbounded stall. ## Reading and fixing it The signals are a preemption counter that is nonzero at all, cache utilization pinned near its ceiling, and inter-token latency variance far worse than the median. Occasional preemption is healthy — it means admission is aggressive enough to keep the GPU full. Sustained preemption is thrash: the same tokens are prefilled repeatedly, so effective throughput drops while the GPU stays busy, a signature that misleads anyone looking only at utilization. The fixes run in order of bluntness: cap concurrency so fewer sequences are admitted; cap the maximum generation length so a few runaway requests cannot monopolize blocks; free memory for the cache by shrinking other consumers or quantizing the cache; add replicas. Raising concurrency limits to "use the GPU better" while preemptions climb makes throughput worse, and that inversion is the most useful thing to be able to say out loud.

  • Why preempt the most recently scheduled request rather than the largest or the oldest?
    Because finishing old work releases memory soonest and keeps completed-request throughput high. Preempting the oldest would waste the most already-spent compute and could starve it indefinitely, while sizing-based selection makes the scheduler's decisions depend on state that changes every step. Newest-first is cheap to evaluate and gives the queue a clear ordering under overload, at the deliberate cost of fairness to late arrivals.
  • Isn't recomputing strictly wasteful compared with swapping the blocks out?
    Not usually. Recompute re-runs prefill, which is dense batched matrix work the GPU does efficiently, and prefix caching can serve a large part of it from blocks that were never evicted. Swapping moves gigabytes across PCIe twice while competing with everything else on the bus, and needs a host-side buffer that is its own fixed budget. Swap wins mainly for very long sequences where the recompute is large relative to the transfer.
  • What does the client see, and should the server instead return an error?
    By default the client sees a stall: the stream pauses and resumes, with no tokens retracted and no error. Whether that is right depends on the product — an interactive UI usually prefers a pause to a failure, but a latency-SLO service may prefer explicit shedding at the edge so callers can retry or route elsewhere rather than hold an open connection through an unbounded delay.
  • Preemptions are climbing and GPU utilization looks great. What is actually happening?
    Thrash. Utilization stays high because preempted requests are re-prefilling the same tokens, so the GPU is busy doing repeated work rather than new work, and completed requests per second falls while the dashboard looks healthy. Judge the server on goodput under your latency target, then reduce admitted concurrency, cap maximum generation length, or add cache capacity.

A restaurant that seats diners without asking how long they will stay: when it runs out of tables it asks the party that arrived last to wait in the lobby, and their half-eaten meal is either kept warm in the back or cooked again.

saying these in an interview costs you the question

  • Says the server returns an out-of-memory error to the client
  • Thinks preemption discards tokens already streamed to the user
  • Claims high GPU utilization proves the server is healthy
  • Assumes swapping to host memory is always cheaper than recomputing
  • Believes admission reserves all the blocks a request will ever need

context