Paper Breakdowns  /  PagedAttention
Paper 53~11 min readSOSP 2023worked math + runnable code
Paper Breakdown

PagedAttention,
explained.

Here is a serving secret that sounds like a bug: most of an LLM server's precious GPU memory sits empty. Each request reserves room for the longest answer it might ever produce, then usually stops early — leaving the rest reserved but unused. PagedAttention borrowed a fifty-year-old operating-systems trick, virtual memory paging, and applied it to the KV cache. The wasted memory came back, batch sizes grew, and throughput multiplied. Here's the fragmentation problem, and the paging that fixes it.

Video breakdown
The animated walkthrough is in production.
Read the full breakdown below in the meantime ↓
01

The KV cache problem

When an LLM generates text, it caches the key and value vectors of every token it has seen so it doesn't recompute them — the KV cache. For long sequences and big models this cache dominates GPU memory, and how much of it fits directly sets how many requests you can batch, which sets throughput. Memory is the bottleneck of serving.

The old way to manage it was crude: give each request one contiguous buffer sized for its maximum possible length. But you don't know how long an answer will be, and most are short. So the server reserves room for 2,000 tokens and the request stops at 100 — the other 1,900 slots are reserved, untouchable by anyone else, and wasted. This is internal fragmentation, and measurements showed it could consume the large majority of the KV cache. Add the gaps left between requests as they come and go (external fragmentation) and a server could be sitting on mostly-empty memory while turning new requests away.

The one-sentence version

Reserving each request's maximum length up front wastes most of the KV cache; paging allocates it in small blocks on demand, so almost none is wasted.

02

Paging the cache

Operating systems solved exactly this problem for RAM decades ago, and PagedAttention imports the solution wholesale. Instead of one contiguous buffer per request, the KV cache is cut into fixed-size blocks (say 16 tokens each). A request is given blocks as it grows, and they can sit anywhere in memory — a block table maps the request's logical token positions to their scattered physical blocks, just like an OS page table.

Contiguous reservation vs paged blocks
ContiguousReserve max_len up front, one block of memory. Short requests waste the rest.
PagedAllocate fixed-size blocks on demand; grow one block at a time as tokens arrive.
Block tableMaps logical positions → physical blocks, so blocks can live anywhere — no external fragmentation.

The attention kernel is rewritten to gather keys and values through this table rather than assuming they are laid out contiguously — that is the "attention" half of PagedAttention. The result is that memory is handed out in small increments and reclaimed the instant a request finishes, keeping the cache densely packed.

03

The waste, bounded

The improvement is easy to quantify. With contiguous reservation, the wasted memory per request is everything you reserved but didn't use — max_len − seq_len, which for a short answer is nearly the whole buffer. With paging, a request uses ⌈seq_len / block_size⌉ blocks, so the only waste is the unfilled tail of its last block:

naive waste = max_len − seq_len (can be ~95%)  ·  paged waste = block·⌈seq/block⌉ − seq  < block_size

The paged waste is capped at less than one block regardless of sequence length — a handful of tokens, not thousands. Concretely, a 100-token request with a 2,048 max wastes 1,948 slots contiguously (95%) but only 12 with 16-token blocks. Reclaim that and the same GPU holds many times more requests: in the runnable below, roughly 18× more. Since decoding throughput is set by how many requests you can batch, more requests packed in means proportionally more tokens per second.

04

Copy-on-write sharing

Blocks addressed through a table unlock a second win for free: sharing. Because several sequences can point their block tables at the same physical blocks, a common prefix need only be stored once. This is everywhere in practice — parallel sampling (many completions of one prompt), beam search (many beams from one context), a shared system prompt across users. All of them share a prefix.

PagedAttention keeps the shared blocks read-only and applies copy-on-write: the moment one sequence needs to write into a shared block (it diverges), that block alone is copied so the write doesn't corrupt the others. Until then, the prefix costs memory once no matter how many sequences read it. For beam search over a long prompt, that can cut KV memory dramatically — the runnable shows two beams sharing a prefix using 6 blocks instead of 10.

RUN IT YOURSELF

Count the wasted memory

The whole argument is arithmetic on blocks. This models both schemes: a 100-token request with a 2,048 max wastes 95% of its buffer contiguously, but paging into 16-token blocks caps the waste below one block (12 slots). Reclaim it and the same 16,384-slot pool holds ~18× more requests. It also prices copy-on-write prefix sharing — two beams sharing a 64-token prefix cost 6 blocks instead of 10. Change the block size, max length, or sequence length and watch the waste and capacity move.

CPython · WebAssembly
05

The throughput win

Reclaimed memory doesn't just sit there — it becomes batch size. Decoding is memory-bandwidth-bound, so the way to more tokens per second is to process more requests together in each pass. PagedAttention's dense packing is what makes those bigger batches fit, and vLLM pairs it with continuous batching (swapping finished requests out and new ones in every step, rather than waiting for a whole batch to complete).

EffectCause
2–4× throughputReclaimed fragmentation → more requests batched at the same latency.
Bigger wins on long sequencesThe longer the max length, the more contiguous reservation wasted — and the more paging saves.
Extra wins on shared prefixesParallel sampling and beam search share prompt blocks via copy-on-write.
Small overheadBlock-table lookups and a paged attention kernel — cheap next to the memory reclaimed.

The trade is a little indirection: attention now gathers through a block table instead of reading contiguous memory. That cost is tiny compared to the memory it frees, which is why the net effect is a large, consistent throughput gain.

06

Why it spread

vLLM, built on PagedAttention, became one of the most widely deployed open-source LLM serving engines almost immediately, and paged KV caching is now standard across serving stacks. The reason is that it attacked the exact bottleneck of the moment — GPU memory during inference — with a fix that was principled, general, and required no change to the model. Cheaper serving is what let more teams run large models at all.

Its deeper lesson is a favorite of systems work: the right abstraction from one field often solves a fresh problem in another untouched. LLM serving had reinvented contiguous memory allocation and inherited its decades-old fragmentation pains; PagedAttention simply pointed out that operating systems had already solved this with paging, and ported the idea. Sometimes the breakthrough is recognizing which old, well-understood mechanism your new problem secretly is.

Worth knowing

Block size is a tuning knob: smaller blocks waste less on the tail but add more block-table overhead; larger blocks are cheaper to manage but waste a bit more per request. vLLM's default of 16 tokens is a practical balance.

Frequently asked

Quick answers

What does PagedAttention do?

Stores the KV cache in fixed-size blocks allocated on demand, mapped by a block table — like OS virtual memory — instead of one contiguous per-request buffer. It removes most KV-cache waste.

Why was memory being wasted?

Contiguous serving reserves each request's maximum length up front; short requests leave the rest reserved but unused (internal fragmentation), often the majority of the cache.

How does sharing work?

Multiple sequences can point their block tables at the same physical blocks, so a shared prefix is stored once, with copy-on-write when a sequence diverges.

How much faster is vLLM?

2–4× higher throughput than prior systems at the same latency, by fitting more requests in reclaimed memory and batching them — more on long sequences and shared prefixes.

Efficient Memory Management for Large Language Model Serving with PagedAttention · Kwon, Li, Zhuang, Sheng, Zheng, Yu, Gonzalez, Zhang, Stoica · SOSP 2023 · read the original paper on arXiv → · Vibe Engines · 2026
Finished this one? 0 / 101 Paper Breakdowns done

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

More Paper Breakdowns