Runbooks/ LLM Inference Runbook Track C · Shrinking the footprint Deep readQuick learn
  1. 00 Start
  2. /
  3. 01 Tokens
  4. 02 Anatomy
  5. 03 Journey
  6. /
  7. 04 Hardware
  8. 05 Phases
  9. 06 KV cache
  10. /
  11. 07 Attention
  12. 08 Position
  13. 09 Precision
  14. 10 Cache ops
  15. /
  16. 11 Many GPUs
  17. 12 Engines
  18. 13 Decode loop
  19. /
  20. 14 Planning
  21. 15 Production
Document 10 · Quick learn OS virtual memory for attention · The prefix from yesterday · One flag worth 2×

Waste, andthree ways to reclaim it

Operating-system virtual memory applied to attention, the prefix that was already computed yesterday, and one flag worth exactly two times.

“Booking every guest a whole floor, in case their family shows up.”
60–80%
wasted before paging — now a few per cent
6.4×
RadixAttention on reuse-heavy traffic
2×
an fp8 cache, for one flag
13 → 382 users
the stack, on one card
Three ways to waste itand the three mechanisms that reclaim it

Cache memory caps concurrency, and concurrency caps throughput. None of what follows makes the model better. All of it makes the same hardware serve several times as many people — which is why a serving engine is 14 to 24 times a research library on identical silicon.

Reserved and not used

Each request books a block sized to the maximum sequence length.

Fixed by paging · earlier systems wasted 60–80%
Duplicated across requests

Five hundred users, five hundred copies of the same system prompt.

Fixed by block sharing · one physical copy, many block tables
Recomputed from scratch

Yesterday’s prefill thrown away and done again today.

Fixed by prefix caching · up to 6.4× on reuse-heavy traffic
The hotel

The naive way is booking every guest an entire floor in case their family shows up. Paging gives each guest one room and hands them another only when someone actually arrives.

Same hotel, many times more guests
The photocopier

Block sharing is noticing that two people are photocopying the same document at the same moment and giving them one copy.

The filing cabinet

Prefix caching goes further: keeping the photocopy so tomorrow’s person does not have to make it at all.

PagedAttentionoperating-system virtual memory, applied to attention

The paper draws the analogy itself: blocks are pages, tokens are bytes, requests are processes. The idea is sixty years old; applying it here is what was new.

Contiguous allocation

Four requests using 300, 2,000, 600 and 4,400 tokens of an 8,192 limit.

The waste

63% of the reserved memory untouched — and no fifth request can be admitted.

Paged allocation

Fixed 16-token blocks handed out on demand, anywhere in memory.

An indirection table

Maps each sequence’s logical positions to physical blocks.

Uniform block size

Kills external fragmentation entirely — every free block fits every need.

Small block size

Caps internal waste at 15 tokens per sequence.

Under two megabytes
Block as the unit

Identical blocks can be shared between requests rather than duplicated.

Which is what makes the next section possible at all
Why this is a throughput win, not a tidiness win

Free memory becomes more cache. More cache means more sequences resident. More sequences means a bigger batch. A bigger batch means the weights are read once and used for far more tokens.

That is the entire chain from document 05, and paging is what unblocks its last step
And note what it does not change

The results are bit-for-bit identical. It is a memory-management change with a matching attention kernel that knows how to read from scattered blocks.

Nothing about model quality is affected — which is why it is not a trade-off and there is nothing to evaluate before enabling it
Sharing, and what makes a prefix reusableblock-aligned, exact, and from the very start
Two requests, same prompt

The same 2,000-token system prompt. Byte-for-byte identical.

One physical copy

Both sequences’ block tables point at it.

The saving

0.24 GiB per additional request — and the prefill too.

Copy on write

A sequence that needs to write into a shared block gets its own copy first.

Worked500 concurrent requests on one shared prompt would otherwise need 120 GiB — more than the card haswhich is why sharing is not an optimisation, it is the design

vLLM shares live. SGLang keeps it.

vLLM — block sharing

Shares blocks between requests running at the same time, and has prefix caching available.

Saves memory
SGLang — RadixAttention

When a request finishes, its cache is kept rather than discarded, filed in a tree organised by shared beginnings. The tree branches where the traffic branches — one system prompt at the root, then tool schemas, few-shot blocks and chat histories hanging off it. A new request walks down from the root, matching as far as it can, and prefills only the remainder.

Saves memory and prefill work. Up to 6.4× on heavy structural reuse — and nothing at all without it

What reuse is worth — a property of your traffic, not your engine

FS
Few-shot classificationthe examples; only the item under test changes
95%
AG
Agent looptool schemas, instructions, scratchpad — resent every step
~90%
CH
Chat, multi-turnsystem prompt plus the whole conversation so far
90%+ by turn 6–8
CO
Coding assistantopen files and project context — within a session
high
RG
RAG question answeringthe system prompt only; passages differ per query
5–15%
Read the improvement as a prefill lever, not a total-latency lever

On a chat workload with a 300-token answer, prefill is under 5% of the request. Cutting an 86-millisecond prefill to 21 is a real 4× on that stage and moves total request time by about three per cent. Users perceive the total.

The value shows up in two other places: time to first token, which users perceive as responsiveness, and fleet prefill capacity, which is a cost win. Be precise about which one you are claiming
Quantising the cacheone flag, exactly two times, no new checkpoint

Paging removes waste. Quantisation makes what is left smaller. It is the cleanest capacity lever in the whole runbook.

What it is

--kv-cache-dtype fp8. One byte per element instead of two.

Exactly 2×, and it needs no new checkpoint
What it costs

Error that accumulates over a generation — a key quantised at token 10 is still read at token 10,000.

Test on long outputs, not a 200-token evaluation. The failure mode is drift: coherent early, degrading late
Why it stacks

It acts on bytes_per_element, which no other lever touches.

Independent levers multiply

The stacking argument, and why to volunteer it

13
53
106
382
MHA, bf16, no paging+ GQA ×4+ fp8 cache ×2+ paging ×3.6 — 382 users

Saying “these are independent levers, so they compose” in a capacity answer shows you understand them as a stack rather than as a menu — which is the distinction most candidates miss.

When it runs out anywaythree responses, and only one is a fix

Cache memory is finite and conversations grow. Eventually the server holds more than it can fit.

Recompute

Drop the evicted request’s cache and redo its prefill later.

421 ms at 8k — pure GPU work, taken from everyone else
Swap to host memory

Ship the blocks over PCIe and bring them back.

34 ms for 1.07 GB — 12× cheaper than recompute, and it uses a different resource
Admission control

Do not admit the request in the first place.

The only actual fix. Better to queue a request and serve it well than admit it and thrash
The isolation question, worth raising unprompted

Sharing blocks means one request’s cached content can be reused by another that happens to start with the same tokens. For a shared system prompt that is exactly the intent. For anything containing user data it deserves thought. Two distinct concerns: access control — a shared cache should be keyed per tenant so one tenant’s blocks cannot be matched by another’s request; and a timing side channel — someone measuring TTFT can in principle infer that a prefix was already cached.

Default: enable it, key the cache per tenant, and document the timing observation as accepted risk with the reasoning written down. Raising it unprompted is more useful than having a strong opinion about it
Answer it out loudthe ninety-second version, then six questions
“Cache memory caps concurrency, and it gets wasted three ways: reserved and not used, duplicated across requests, and recomputed from scratch. PagedAttention fixes the first by storing the cache in fixed sixteen-token blocks handed out on demand — operating-system virtual memory applied to attention — and it takes waste from sixty or eighty per cent down to a few. Because the unit of allocation is a block, identical blocks can be shared rather than duplicated, which fixes the second. And keeping blocks after a request finishes, in an LRU radix tree, fixes the third — up to 6.4 times on workloads with heavy structural reuse, and nothing at all without it. On top of that, an fp8 cache is exactly two times for one flag.”

Six questions, and the spine of each answer

Architect

What does PagedAttention do, and why does it help?

  • Stores each request’s cache in small fixed-size blocks handed out on demand, anywhere in memory
  • Uniform size removes external fragmentation; small size caps internal waste at fifteen tokens
  • Earlier systems wasted 60–80%; vLLM under a few per cent
The chain matters more than the mechanism: freed cache → more resident sequences → bigger batch → the weight read amortised over more tokens. A memory change that shows up as throughput — and outputs are identical, so there is nothing to evaluate.
Architect

vLLM or SGLang, and why?

  • vLLM for general high-throughput serving; SGLang when the workload has heavy prefix reuse — agents, few-shot, long fixed system prompts
  • Both do continuous batching and paged memory. The difference is what happens to the cache after a request ends
  • SGLang keeps it in an LRU radix tree keyed on shared beginnings
Two honest caveats: both projects move fast and copy each other, so a feature gap may already have closed; and the right answer comes from measuring what fraction of your prompts is a repeated prefix.
Eng manager

Prefix caching gave 4× on prefill and users say nothing changed.

  • Prefill was never the dominant term — decode is 95% of a chat request
  • 86 ms → 21 ms is a genuine 4× on that stage and moves the total by ~3%
  • Users perceive the total
The value landed elsewhere: freed prefill capacity took our fleet from four GPUs to one at the same request rate. Organisationally, write down which metric a change targets before doing it — “feel faster” and “cost less” are different halves of this system.
Architect

Is prefix caching safe across different users?

  • Two separate questions. Access control: if prompts carry per-user data, the cache needs keying per tenant
  • Most engines expose a per-request or per-tenant cache key — confirm yours does
  • Timing side channel: measuring TTFT can reveal that a prefix was already cached
Default: enable, key per tenant, document the timing observation as accepted risk. For an internal tool it almost never matters; for a public API with sensitive prompts it might.
Architect

What happens when the GPU runs out of cache mid-generation?

  • The scheduler preempts — evicts a mid-generation request to free blocks
  • Then either recomputes its prefill later (421 ms at 8k, pure GPU) or swaps the cache to host memory (34 ms over PCIe)
  • Swap is 12× cheaper and uses a different resource
But both are undoing and redoing work. The actual fix is admission control — cap running sequences below where preemption starts, and alarm on preemption count.
Eng manager

Our cache hit rate dropped from 85% to 12% overnight.

  • Usual culprits in order: a timestamp, request id or user name added near the top of the system prompt
  • A tool schema serialised in non-deterministic order
  • A feature flag injecting a variant string into the header
  • All three are invisible — nothing errors, quality is unchanged
The fix is one line in all three cases: stable content first, variable content last, and serialise structured data deterministically. Then log the rendered prompt once per deploy, and alarm on hit rate — a leading indicator that costs nothing to watch and nobody watches.
Fast answersthe ten that come up most

QDoes PagedAttention change the maths of attention?

No. The results are identical. It is a memory-management change with a matching kernel that reads from scattered blocks. Which is why there is nothing to evaluate before enabling it.

QWhat block size should I use?

16 is the vLLM default and rarely worth changing. Smaller wastes less on the final partial block and adds bookkeeping; larger is the reverse. The one place it matters is prefix matching, which is block-aligned — a larger block makes the rounding coarser.

QWhy does my prefix cache hit rate not match what I expect?

Matching is on exact token ids, block-aligned, from the very start. Anything that shifts the sequence invalidates everything after the difference. Log the rendered prompt for two consecutive requests and diff them — the cause is usually obvious in seconds.

QDoes prefix caching help RAG?

Much less than people expect. Retrieved passages differ per query, so the only reusable part is the system prompt — typically 5 to 15 per cent. Still free to enable, but do not build a capacity plan on it. Agents and multi-turn chat are where the large wins are.

QIs an fp8 cache safe?

Generally, at 8 bits. What makes it different from weight quantisation is that the error accumulates over a generation. Test it on long outputs and long conversations. The failure mode to look for is drift: coherent early, degrading late.

QCan I cache prefixes to disk or across machines?

There is active work on exactly this — tiering to host memory and beyond, and sharing across a fleet. The arithmetic is what the hierarchy predicts: host memory is 52× slower than HBM, so it pays only when the alternative is a full recompute. A real but maturing capability.

QBlock sharing versus prefix caching?

Block sharing works between requests running at the same time. Prefix caching keeps blocks after a request finishes. The first saves memory; the second saves memory and prefill work.

QShould I worry about the timing side channel?

Know that it exists and decide deliberately. For an internal tool it is almost always acceptable; for a public API handling sensitive prompts it may not be. Mitigations are per-tenant cache keys, or disabling cross-request sharing for the sensitive route.

QWhy is throughput falling while I add load?

You are almost certainly in a preemption loop: the server is evicting mid-generation requests and then redoing their work. Cache utilisation pinned at 100% with a growing queue is the signature. Adding load makes it worse.

QWhat single change would you make first?

Measure the repeated-prefix fraction of real traffic, because it decides whether the biggest lever here applies to you at all. 90% and prefix caching is transformational; 10% and you should spend the effort on the decode loop. An afternoon, and it redirects everything else.

None of this makes the model better. All of it makes the same hardware serve several times as many people. Paging takes waste from sixty or eighty per cent to a few; sharing removes the duplicates; keeping blocks after a request finishes removes the recomputation. Stack an fp8 cache on top and thirteen users on an MHA bf16 baseline becomes three hundred and eighty-two on the same card. And the first thing to measure is what fraction of your prompts actually repeats — because that one number decides whether any of it applies to you.

Where this goes next

Questions to ask them

What fraction of a typical prompt is a prefix that repeats? Has anyone measured it?

Is prefix caching enabled, and what is the hit rate?

Is there anything variable near the start of your system prompt?

Is the cache in fp8, and if not, why not?

Is preemption count monitored, or only latency?

In a multi-tenant deployment, is the prefix cache keyed per tenant?

← 09 · Precision Read the full chapter → 11 · Many GPUs →