Guide · AI and GenAI
PagedAttention Explained: How vLLM Pages the KV Cache to Stop Memory Waste
24 min readUpdated By Saket Bhatnagar
Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.
In short
- PagedAttention is the method vLLM, an open-source engine for serving language models, uses to store each request's KV cache in small fixed-size blocks. The KV cache is the saved keys and values of every earlier token.
- Without paging, a server reserves room for the longest possible reply in one unbroken strip. Most of that room sits reserved but empty, and when sizes vary, gaps form that nothing fits. Together this is fragmentation. In our simulator only 12.3% of reserved memory held real tokens.
- A block table, a short per-request list, maps each request's blocks to free blocks anywhere in GPU memory. Waste per request shrinks to the unused part of one last block.
- Blocks carry a reference count, the number of requests using them, so requests can share a prompt. Copy-on-write copies a shared block only when one request must change it.
- Beginners can stop after the batch size and cost section. Engineers then get vLLM 0.32.0 defaults read from source, preemption by recompute (pausing a request and rebuilding its memory later), block size trade-offs and a runnable simulator.
What goes wrong when every request gets its own KV cache?
On a busy model server, GPU memory often fills up before the GPU's compute is fully used. Each request keeps a KV cache that grows with every token, and storing it naively wastes most of the memory it reserves. PagedAttention fixes how that memory is handed out.
Picture a common setup. A team in Pune serves a customer-support chat assistant with vLLM on one 80 GB data-centre GPU. The setup is made up, but every number below comes from our own runs or a cited source.
After the model weights load, about 60 GiB is left for the KV cache (a made-up round number). GiB means gibibyte, 1,073,741,824 bytes. A typical chat uses under 1,000 tokens, but the server allows up to 8,192 per request.
If the server reserves a full 8,192-token strip for every request, only 60 requests fit at once. In our simulation of the same memory, paging fitted about 498 at once on average. This guide explains why that gap exists and how paging closes it.
Which words do you need before reading about PagedAttention?
You need six words. Each one is tied to the support assistant.
- Token: a small piece of text, often a word or part of a word. The model reads the prompt as tokens and writes its reply one token at a time.
- KV cache: for every token already seen, each layer of the model saves keys and values, which are lists of numbers. Later tokens look back at them instead of recomputing the whole chat.
- Prefill and decode: prefill processes the whole prompt in one go. Decode then produces one new token per step, and each step adds one more key and value per layer.
- Batch: the set of requests the GPU works on in the same step. More requests per step means more tokens for the same GPU time.
- Fragmentation: memory that is free in principle but cannot be used, because it is reserved, stuck inside someone's allocation or split into gaps that are too small.
- Block: a fixed-size chunk of KV cache that holds the keys and values of a fixed number of tokens. vLLM uses 16 tokens per block by default.
What is PagedAttention and how does it page the KV cache?
PagedAttention stores each request's KV cache in fixed-size blocks that can sit anywhere in GPU memory. Its attention kernel, a function that runs on the GPU, finds those blocks through a lookup table.
It comes from a 2023 paper on memory management for model serving, arXiv 2309.06180, which also introduced vLLM. The paper borrows from operating systems: think of blocks as pages, tokens as bytes and requests as processes.
Each request sees its KV cache as logical blocks numbered 0, 1, 2 and so on, filled left to right. Its block table records which physical block in GPU memory holds each logical block. A new physical block is taken from the free list only when the last one is full.
One detail for engineers: vLLM's design page on the original PagedAttention kernel is now marked historical. In 0.32.0, the attention backends read the same block tables, so the paging idea outlived that first kernel.
So the support assistant never reserves room for a reply that has not been written. A 900-token chat holds 57 blocks: 56 full ones and one with 4 of its 16 slots used.
Block tables map logical blocks to physical blocks
Drag to exploreNotice that request A's blocks are scattered and out of order, and only the last block of each request is partly empty. That last block is the only waste paging leaves.
Diagram connections:
- Request A: 40 tokens to A's block table (looks up)
- A's block table to Physical block 7 (logical 0)
- A's block table to Physical block 1 (logical 1)
- A's block table to Physical block 3 (logical 2)
- Request B: 20 tokens to B's block table (looks up)
- B's block table to Physical block 5 (logical 0)
- B's block table to Physical block 2 (logical 1)
- Free block list to B's block table (next block when B fills)
- Request A: 40 tokens: Chat with a long question
- A's block table: Logical 0, 1, 2 to physical 7, 1, 3
- Physical block 7: A's tokens 0 to 15, full
- Physical block 1: A's tokens 16 to 31, full
- Physical block 3: A's tokens 32 to 39, 8 of 16 slots used
- Request B: 20 tokens: Short follow-up chat
- B's block table: Logical 0, 1 to physical 5, 2
- Physical block 5: B's tokens 0 to 15, full
- Physical block 2: B's tokens 16 to 19, 4 of 16 slots used
- Free block list: Blocks 4 and 6 wait for any request
An everyday picture helps. In a university exam, the old way hands every student a bound 32-page answer booklet, though most write 6 pages. The unused pages cannot go to anyone else.
The paged way hands out loose 4-page sheets. A student asks for another sheet only when the current one is full, and writes the sheet numbers in order on the cover. The cover list is the block table, and the only waste is the unused part of the last sheet.
Where the picture breaks: exam sheets are never shared, but KV blocks can be. Two requests with the same prompt can point at the same physical block, which the copy-on-write section explains.
How much GPU memory does the KV cache need per token?
Bytes per token = 2 x layers x KV heads x head size x bytes per number. The 2 counts one key and one value. Everything else comes from the model's shape and the number format of the cache.
A head is one of the parallel attention units in a layer, and head size is the length of its key vector. Many models use grouped-query attention, where several query heads share one key-value head, so KV heads can be fewer than attention heads.
Take a made-up but realistic shape: 32 layers, 8 KV heads, head size 128, and a 16-bit cache, which is 2 bytes per number. That gives 131,072 bytes, or 128 KiB, per token. KiB means 1,024 bytes.
def kv_bytes_per_token(layers, kv_heads, head_dim, dtype_bytes):
# 2 = one key vector and one value vector per token, per layer
return 2 * layers * kv_heads * head_dim * dtype_bytes
per_token = kv_bytes_per_token(layers=32, kv_heads=8, head_dim=128, dtype_bytes=2)
print(per_token, "bytes =", per_token // 1024, "KiB per token")
print(per_token * 8192 / 2**30, "GiB for one 8,192-token request")
print(60 * 2**30 // (per_token * 16), "blocks of 16 tokens fit in 60 GiB")Running it prints 131072 bytes, 128 KiB per token, 1.0 GiB for one 8,192-token request and 30,720 blocks of 16 tokens in 60 GiB. That is 491,520 tokens of room in total.
| Model shape | Bytes per token | One 8,192-token request | Tokens that fit in 60 GiB |
|---|---|---|---|
| 32 layers, 8 KV heads, 16-bit cache | 128 KiB | 1 GiB | 491,520 |
| Same, but 32 KV heads (no grouped-query attention) | 512 KiB | 4 GiB | 122,880 |
| Same 8 KV heads, 8-bit (fp8) cache | 64 KiB | 0.5 GiB | 983,040 |
The paper gives its own worked example for a 13-billion-parameter model with 40 layers and a hidden size of 5,120. It needs 2 x 5,120 x 40 x 2 bytes, about 800 KB per token, and up to 1.6 GB for a 2,048-token request.
Our script reproduces that: 819,200 bytes, which is exactly 800 KiB, and 1.56 GiB for 2,048 tokens. The cache is per request, so 60 chats of 8,192 tokens fill our whole 60 GiB budget.
Why does contiguous KV cache allocation waste so much memory?
Because the server must reserve memory before it knows how long the reply will be. The naive design, shown here because it is wrong for this workload, gives each request one contiguous strip sized for the maximum length.
The paper names three kinds of waste in this design. Earlier serving systems, it reports, used only 20.4% to 38.2% of their KV cache memory for actual token states.
- Reserved: slots held for tokens the request has not generated yet. They are empty for most of the request's life.
- Internal fragmentation: slots inside a request's strip that are never used, because the reply ended well before the maximum length.
- External fragmentation: free gaps between strips that are too small for the next request, even when their total is large enough.
Pause and predict: in our simulated support traffic, what share of the KV memory held real tokens when every request reserved 8,192 slots? Make a guess before reading on.
It was 12.3%. The workload had 3,000 made-up chats with a median of 886 tokens and a longest of 7,067. The other 87.7% was reserved but empty, and only 60 requests could run at once.
An oracle that knew each reply's final length and reserved exactly that did much better, but still not well. While requests were waiting, 13.3% of memory sat free on average, in gaps too small for the next request in line. On 2,630 steps the total free space was big enough, but no single gap was.
There is a quieter lesson in the reserve-8,192 run. When every reservation is the same size, a freed strip always fits the next request, so external fragmentation disappears. The price is massive internal fragmentation instead.
How much does paging raise batch size and cut cost per token?
In our simulation paging raised the average batch from 60 to about 498 requests and finished the same work in 3,334 decode steps instead of 17,663. Fewer steps for the same tokens means a lower cost per token.
Each simulated step is one decode iteration, where every running request adds one token. The paged allocator hands out 16-token blocks on demand. When no block is free, it preempts the newest request: it frees its blocks and queues it to be rebuilt later.
| Allocator | Average batch | Memory holding tokens | Reserved but empty | Steps to finish | Output tokens per step |
|---|---|---|---|---|---|
| Contiguous, reserve 8,192 | 60.0 | 12.3% | 87.7% | 17,663 | 57.2 |
| Contiguous, exact length (oracle) | 340.5 | 68.6% | 18.1% | 4,105 | 246.0 |
| Paged, 16-token blocks | 498.3 | 99.1% | 0.8% | 3,334 | 302.9 |
| Paged, 128-token blocks | 470.5 | 93.8% | 6.1% | 3,431 | 294.4 |
| Paged, 512-token blocks | 397.1 | 79.7% | 20.2% | 3,737 | 270.3 |
What just happened? The paged runs keep almost every slot full of real tokens, so far more chats fit in each step. The oracle row shows exact sizing alone is not enough, because gaps still form between strips.
Now turn that into money with made-up figures. Suppose the GPU costs ₹250 an hour and every step takes 25 ms. Contiguous allocation then costs about ₹30 per million output tokens, and paging about ₹5.7.
That is roughly 0.19 times the cost, but treat it as an upper bound on the saving. The simulator assumes a step takes the same time at any batch size, and real decode steps slow down as the batch grows. It also treats prefill as free, so recomputing preempted requests adds no steps here.
Even if a step at batch 500 took twice as long, paging would still cost about 0.38 times as much per token. Measure your own step times before quoting a number.
How does copy-on-write let requests share KV blocks?
Copy-on-write lets several sequences point at the same physical block and copies it only when one of them needs to write into it. Each block keeps a reference count, the number of sequences currently using it.
The paper uses this for parallel sampling, where one prompt produces several different replies, and for beam search. Beam search keeps several candidate continuations and drops the weak ones as it goes.
For parallel sampling, all samples first share the prompt's blocks, so each block's count equals the number of samples. When a sample writes into the shared, partly filled last block, the server takes a new block, copies the data and lowers the old block's count by one.
B = 16 # tokens per block
free = list(range(1000)) # free physical block ids
ref = {} # physical block id: reference count
def new_block():
b = free.pop(0)
ref[b] = 1
return b
def append_token(table, length):
"""Write one token's keys and values; return the new length."""
if length % B == 0: # last block is full: get a new one
table.append(new_block())
elif ref[table[-1]] != 1: # last block is shared: copy on write
ref[table[-1]] -= 1
table[-1] = new_block() # a real server also copies the block's data here
return length + 1
prompt = []
n = 0
for _ in range(1000): # 1,000-token prompt fills 63 blocks
n = append_token(prompt, n)
samples = []
for _ in range(4): # fork 4 samples: share every block
for b in prompt:
ref[b] += 1
samples.append((list(prompt), n))
for b in prompt: # the parent itself is done
ref[b] -= 1
for step in range(200): # each sample writes 200 tokens
samples = [(t, append_token(t, ln)) for t, ln in samples]
print("blocks in use:", sum(1 for c in ref.values() if c))
print("first block shared by", ref[prompt[0]], "samples")
print("last prompt block per sample:", [t[62] for t, _ in samples])It prints 114 blocks in use, the first block shared by 4 samples, and last prompt blocks 63, 64, 65 and 62. Three samples got copies; the last writer kept the original, because by then its count had dropped to 1.
Without sharing, the same four replies needed 300 blocks in our cow_sim.py run, so sharing saved 62%. That figure is high because the prompt is long and the replies short.
The paper measured smaller but real savings on real datasets. Parallel sampling saved 6.1% to 9.8% of memory on a dataset of short instructions, and 16.2% to 30.5% on a dataset of real chats. Beam search saved 37.6% to 66.3% across the two.
Copy-on-write for one shared prompt block
Drag to exploreNotice that no data is copied when the samples fork. The copy happens once per sample, and only for the shared block being written into.
Diagram connections:
- Prompt prefilled to Fork 4 samples (parent done)
- Fork 4 samples to Reference counts (share, no copy)
- Reference counts to Sample writes a token (decode starts)
- Sample writes a token to Count above 1? (check block 62)
- Count above 1? to Copy then write (Yes)
- Count above 1? to Write in place (No)
- Prompt prefilled: Parent fills blocks 0 to 62
- Fork 4 samples: Each block table points at the same blocks
- Reference counts: Every prompt block now has count 4
- Sample writes a token: Into block 62, which is only half full
- Count above 1?: Is someone else still reading this block?
- Copy then write: New block, copy data, old count minus 1
- Write in place: Last sharer keeps the original block
How vLLM shares blocks today
vLLM 0.32.0 reaches the same goal by a different route. A request asking for n samples becomes n child requests, each with n set to 1, in vllm/v1/engine/parallel_sampling.py.
The children can then reuse the prompt through automatic prefix caching, which is on by default. Each full block gets a hash, a short fingerprint, built from its parent block's hash, its own tokens and extra keys. One such key is a cache salt, which keeps different tenants' caches apart.
A new request whose prompt starts with the same tokens finds those hashes and simply increases the blocks' reference counts. In the BlockPool source this is the touch method, and a freed block returns to the free list only when its count reaches 0.
The prefix caching docs say vLLM caches full blocks only. So for a typical model, a partly filled last block is never shared, and each request writes its own.
What full-block sharing means for a system prompt
Suppose all support chats start with the same 1,000-token system prompt. With 16-token blocks, 62 full blocks hold 992 tokens and can be shared. The last 8 tokens are computed again for every chat.
For 50 chats, that is 62 shared blocks plus 50 private tail blocks, 112 in all, instead of 3,150 without sharing. With 128-token blocks only 896 of the 1,000 tokens fall in full blocks, so less is reused.
What happens when the KV cache runs out: swap or recompute?
The server preempts a running request: it frees that request's blocks and resumes it later. The paper offers two ways to resume, swapping blocks to CPU memory or recomputing them. vLLM 0.32.0 has no swap mode and recomputes.
Preemption happens because decode keeps growing every request. With paging, the server admits requests based on what they use now, so a busy step can need a new block when none is free.
The paper evicts all of a request's blocks or none of them, because a request needs all its KV cache to continue. It serves requests first come, first served, so the latest arrivals are preempted first.
- Swapping copies the evicted blocks to CPU RAM over PCIe, the link between the CPU and the GPU, and copies them back later. The paper bounds swap space by the GPU memory given to the KV cache.
- Recomputation drops the blocks. When the request resumes, its generated tokens are joined to the original prompt and the whole thing is prefilled again in one pass.
The paper found swapping slow with small blocks, because many small transfers use the PCIe link poorly. Recomputation did better at small block sizes, and the two were comparable from 16 to 64 tokens per block.
How vLLM 0.32.0 preempts a request
Drag to exploreNotice that the victim loses its blocks, not its generated text. It returns to the front of the queue and rebuilds its KV cache with one prefill.
Diagram connections:
- Decode step needs a block to Free block available? (ask the block pool)
- Free block available? to Allocate and continue (Yes)
- Free block available? to Pick a victim (No)
- Pick a victim to Free victim's blocks (preempt)
- Free victim's blocks to Front of waiting queue (prepend)
- Front of waiting queue to Recompute by prefill (blocks free again)
- Decode step needs a block: A running request filled its last block
- Free block available?: Checked in allocate_slots
- Allocate and continue: Block table gets one more entry
- Pick a victim: Default: newest running request
- Free victim's blocks: Computed token count reset to 0
- Front of waiting queue: Scheduled again when blocks free up
- Recompute by prefill: Prompt plus generated tokens in one pass
What the vLLM 0.32.0 source does
In vllm/v1/core/sched/scheduler.py, the default policy picks the request most recently added to the running list. The priority policy picks the lowest-priority request, where a larger number means lower priority, breaking ties by latest arrival.
The _preempt_request method frees the request's blocks, sets its computed token count to 0 and puts it at the front of the waiting queue. The engine arguments in 0.32.0 have no swap-space or preemption-mode option. This victim choice is read from source and undocumented as of 27 October 2026; pin your version.
With prefix caching on, freed blocks that carry a hash go to the back of the free list. So a preempted request can sometimes find part of its own prompt still cached when it resumes, and skip that part. A configured KV offloading or transfer connector can also supply cached blocks at that point.
In our simulator, the paged run with 16-token blocks preempted 522 times. They recomputed 149,240 tokens, about 4.6% of the 3.2 million tokens processed. That is the price of admitting requests on what they use now.
If preemptions are frequent, the docs suggest raising gpu_memory_utilization, lowering max_num_seqs or max_num_batched_tokens, or splitting the model across more GPUs with tensor or pipeline parallelism. KV cache offloading to CPU is a separate vLLM feature with its own settings.
Which vLLM settings control paging in version 0.32.0?
Five settings decide how much KV memory you get and how it is cut into blocks. We read each default from the vLLM 0.32.0 source, released on PyPI on 18 October 2026, and checked the published engine arguments page where it lists them.
| Flag | Default in 0.32.0 | What it controls | When to change it |
|---|---|---|---|
| --block-size | 16 tokens | Tokens per KV block. An attention backend may change it and logs the new size. | Rarely. Leave it unless a backend or a measured test says otherwise. |
| --gpu-memory-utilization | 0.92 | Share of total GPU memory this vLLM instance may use. KV gets what is left after weights, activations and CUDA graphs, which are pre-recorded GPU work. | Lower it when other processes share the GPU. Raise it to get more blocks. |
| --kv-cache-memory-bytes | None | Fixed KV cache size per GPU in bytes. When set, it ignores gpu_memory_utilization. | When you want the same KV size on every boot. |
| --enable-prefix-caching | On | Reuse of full blocks across requests with the same prefix. | Turn off only to measure without it. |
| --kv-cache-dtype | auto (model's own type) | Number format of the cache. An 8-bit format halves bytes per token compared with a 16-bit cache. | When memory, not quality, limits your batch. Test quality first. |
The cache config also has a hash algorithm for prefix caching, sha256 by default. Block 0 of the pool is a reserved null block, so a pool of 8 blocks has 7 free for requests. Both details come from the 0.32.0 source and are undocumented as of 27 October 2026.
How to check it worked
Start the server
Run vllm serve with your model and any flags above. Keep the startup log; the KV sizing lines appear once the memory profile finishes.
Read the KV budget
Find the line Available KV cache memory: X GiB. It is what remained of the gpu_memory_utilization share after weights, activations and CUDA graphs.
Read the capacity
Find GPU KV cache size: N tokens, Maximum concurrency for M tokens per request: Yx. N divided by your block size is roughly the block count.
Cross-check the maths
On a single GPU, divide the available bytes by your model's bytes per token. The result should be close to N.
Watch it under load
The periodic stats line shows GPU KV cache usage as a percentage and, in intervals where any happen, Preemptions: N for that interval. The metric vllm:kv_cache_usage_perc, exposed for the Prometheus monitoring system, reports usage from 0 to 1.
How do you choose a block size for PagedAttention?
Keep the default unless you have measured a reason. Small blocks waste less memory and share prefixes more finely, but very small blocks give the GPU too little work per block to read efficiently.
The paper tested this directly. Block sizes from 16 to 128 worked best on real chat traffic, while short requests did best at 16 or 32, because many requests were shorter than the larger blocks.
Our simulator shows the memory side. Average unused space in the last block grew from 7.4 tokens at size 16 to 252.7 tokens at size 512. At 512, 20.2% of memory sat reserved but empty, and the average batch fell to 397.
vLLM 0.32.0 also lets the attention backend overrule the default. When the chosen backend needs a different size, the log prints Setting kv cache block size to N.
What does PagedAttention not fix?
Paging removes waste, not need. A request with 8,192 real tokens still needs 1 GiB in our made-up model, so long contexts still limit how many requests fit.
- Weights are a fixed cost. Paging only manages the memory left after the model loads.
- Reading through a block table costs time. The paper measured 20% to 26% higher attention kernel latency than an optimised contiguous kernel, which end-to-end gains outweighed.
- Paging decides where tokens live, not which requests run together. Batching policy is a separate topic.
- Preemption still hurts latency for the request that is pushed out, even though no work is lost permanently.
The SevyDevy guide on LLM inference system design covers continuous batching and the wider serving pipeline. This guide stays on the memory mechanics underneath it.
How do you explain PagedAttention in a system design interview?
Start from the memory maths, then show why contiguous reservations fail, then describe blocks, sharing and preemption. Interviewers want the trade-offs, not the paper's name.
Size the KV cache
Bytes per token is 2 x layers x KV heads x head size x bytes per number. For our made-up model, 128 KiB per token and 1 GiB per 8,192-token request.
Show the naive design failing
Reserving the maximum length per request leaves most memory reserved but empty. Exact-size strips still leave gaps between requests.
Introduce blocks and block tables
Fixed 16-token blocks from a free list, mapped per request by a block table. Waste is at most one partly filled block per request.
Add sharing
Reference counts let parallel samples and common prefixes share blocks. Copy-on-write copies a block only when a sharer writes into it.
Handle running out
Preempt the newest request, free its blocks and recompute it later with one prefill. Swapping to CPU is the alternative, slower with small blocks.
Close with the payoff
More requests per step means more tokens per GPU-hour. Note the costs: kernel indirection, preemption latency and the block size trade-off.
Questions people ask
- What is PagedAttention in simple words?
- It is a way to store a model's KV cache in small fixed-size blocks instead of one long reserved strip per request. A block table tells the attention kernel where each block lives. Memory then grows one block at a time as tokens are written, so waste stays within one block per request and many more requests fit on the GPU.
- What is the default block size in vLLM?
- It is 16 tokens in vLLM 0.32.0, as of 27 October 2026, set by DEFAULT_BLOCK_SIZE in vllm/config/cache.py. An attention backend can choose a different size when it needs one, and vLLM logs the change at startup. You can set it yourself with the block-size flag, but measure before changing it.
- Does vLLM still swap the KV cache to CPU on preemption?
- Not in vLLM 0.32.0, as of 27 October 2026. A preempted request has its blocks freed and its computed token count reset, and it is recomputed later with one prefill. The engine arguments have no swap-space or preemption-mode option. KV cache offloading to CPU is a separate feature with its own settings.
- How do I calculate KV cache memory per token?
- Multiply 2 by the number of layers, the number of key-value heads, the head size and the bytes per number. A model with 32 layers, 8 KV heads, head size 128 and a 16-bit cache needs 131,072 bytes, or 128 KiB, per token. Multiply by tokens per request and by concurrent requests for the total.
- What does gpu_memory_utilization mean in vLLM?
- It is the share of the GPU's total memory that one vLLM instance may use, 0.92 by default in 0.32.0. Weights, activation memory and CUDA graphs come out of that share first. Whatever remains becomes the KV cache, which the startup log reports as Available KV cache memory in GiB.
- Is PagedAttention the same as prefix caching?
- No. PagedAttention is the block-based memory layout and the kernel that reads it. Prefix caching is a feature built on top of it: vLLM hashes full blocks and lets a new request reuse blocks whose hash matches its prompt. Paging makes that reuse possible, because blocks can be shared by reference count.
- Why does a larger block size waste memory?
- Because each request's last block is usually only partly full, and the unused part grows with the block size. In our simulation the average unused space was 7.4 tokens per request at size 16 and 252.7 tokens at size 512. Larger blocks also share prefixes less finely.
Sources and further reading
- Paper: Efficient Memory Management for Large Language Model Serving with PagedAttention (arXiv 2309.06180)
- vLLM 0.32.0 docs: Optimization and Tuning, Preemption section
- vLLM 0.32.0 docs: Automatic Prefix Caching design (full blocks only, hashing, eviction)
- vLLM 0.32.0 docs: Engine arguments (gpu-memory-utilization, block-size, kv-cache-memory-bytes)
- vLLM docs source at v0.32.0: design/paged_attention.md (marked a historical document)
- vLLM source at v0.32.0: vllm/config/cache.py (CacheConfig defaults)
- vLLM source at v0.32.0: vllm/v1/core/block_pool.py (reference counts, free list, null block)
- vLLM source at v0.32.0: vllm/v1/core/sched/scheduler.py (preemption victim and _preempt_request)
- vLLM source at v0.32.0: vllm/v1/engine/parallel_sampling.py (n samples as child requests)
- vLLM source at v0.32.0: vllm/v1/worker/gpu_worker.py (KV memory budget and startup log)
- PyPI: vllm 0.32.0 release (18 October 2026)