PagedAttention and continuous batching
Manage KV cache as fixed-size pages mapped to logical blocks (PagedAttention), and use continuous batching to saturate GPU throughput across requests of different lengths.
In production LLM serving, requests arrive at different times and have different prompt and generation lengths. Naive serving allocates a contiguous block of GPU memory per request -- large enough for the maximum possible output length. This wastes memory and limits concurrency.
PagedAttention (Kwon et al., 2023, vLLM) and continuous batching are the two key techniques that make production LLM serving efficient[1].
The memory waste problem
A 70B model at 4-bit quantization uses ~35GB for weights. On a single 80GB A100:
- Weights: 35GB
- Available for KV cache: 45GB
At sequence length 4096, one 70B LLaMA-3 request uses ~320MB of KV cache (see kv-cache lesson). Serving 140 concurrent requests would need 44.8GB -- just barely fitting.
The problem: a request that will generate 200 tokens but was allocated space for 4096 wastes 3896 token slots. With fragmented, variable-length sequences, effective utilization of the KV cache is typically 20-40%.
PagedAttention: OS-inspired memory management
PagedAttention treats KV cache memory like an OS virtual memory system:
Physical KV blocks: the GPU memory is divided into fixed-size blocks (typically 16 or 32 token slots). Each block stores the K and V tensors for a fixed number of tokens.
Logical sequence: each request has a logical sequence of KV entries. Logical sequence positions are mapped to physical blocks via a block table (like a page table in virtual memory).
Non-contiguous storage: consecutive logical tokens can reside in non-contiguous physical blocks. The attention kernel is modified to follow the block table when loading K and V.
Benefits:
- No pre-allocation waste: blocks are allocated on-demand as tokens are generated.
- Block sharing: multiple requests that share the same prompt prefix can share the same physical KV blocks (copy-on-write if they diverge). This is critical for chatbot system prompts shared across thousands of sessions.
- Fragmentation 0: less than one block of waste per sequence.
Block table example
For a sequence with 5 tokens, block size 2:
Logical sequence: [t0, t1, t2, t3, t4]
Block table: {0 -> block 7, 1 -> block 3, 2 -> block 11}
(logical block 0 = tokens 0-1 stored in physical block 7, etc.)The attention kernel uses the block table to locate and load the correct K,V tensors during each attention computation.
Attention kernel modification
Standard causal attention computes:
With PagedAttention, and are loaded block-by-block, following the block table. Each block is fetched from its physical location and the attention accumulates across blocks using a running softmax numerically.
Continuous batching
Without continuous batching (static batching): a batch of requests runs together until the longest request finishes. Short requests in the batch complete early but hold their GPU slots, blocking new requests.
Continuous batching (Orca, 2022): replace finished requests with new ones at the end of each decode step, without waiting for the entire batch to finish.
Step 1: [req_A, req_B, req_C, req_D] -- all decoding
Step 2: [req_A, req_B, req_C, req_D] -- req_C finishes
Step 3: [req_A, req_B, req_E, req_D] -- req_E immediately starts (infill)
Step 4: [req_A, req_F, req_E, req_D] -- req_B finishes, req_F startsThis "in-flight batching" keeps the GPU fully saturated. In combination with PagedAttention, it enables fine-grained scheduling of token generation across hundreds of concurrent requests.
Prefill-decode disaggregation
A newer architectural pattern: separate the prefill and decode compute onto different GPU pools.
- Prefill workers: compute-bound; process full prompts in batches. Can run at high utilization because all tokens process in parallel.
- Decode workers: memory-bandwidth bound; generate tokens autoregressively. Continuous batching maximizes their utilization.
This disaggregation avoids the "prefill-decode interference" problem (a long prefill blocks all decode steps in the same batch) and allows independent scaling of each phase.
Impact
In the original vLLM paper, PagedAttention + continuous batching achieved:
- 2-4x higher throughput than HuggingFace Transformers' naive batching.
- Near-zero KV cache fragmentation vs. 60-80% waste with static allocation.
These techniques are now standard in all production LLM serving frameworks (vLLM, TGI, SGLang, TensorRT-LLM).
Exercises
Exercise 1. A request generates 200 tokens but naive serving pre-allocated KV slots for the model's 4096-token maximum. Compute the fraction of allocated KV memory actually used, then the effective utilization if the GPU runs 100 such requests in parallel with the same pattern.
Solution
Step 1. The request uses of pre-allocated slots:
about utilization. The other slots are reserved and wasted for the life of the request.
Step 2. Utilization is a per-slot ratio, so 100 identical requests each waste the same proportion; the aggregate utilization is still . Static pre-allocation to the maximum length is what makes naive serving waste to of the KV cache in practice, and the fix is to allocate on demand rather than to the worst case.
Exercise 2. PagedAttention uses fixed blocks of 16 token slots. A sequence has 70 tokens. How many blocks does it occupy, how many slots are wasted in the last block, and what is the internal fragmentation as a fraction of the sequence length?
Solution
Step 1. Blocks needed is the ceiling of . Since and , the sequence occupies blocks ( slots).
Step 2. The last block holds live tokens, so slots are wasted.
Step 3. Fragmentation as a fraction of the sequence is , but the meaningful bound is that waste never exceeds one block per sequence regardless of length: at tokens the same wasted slots are . Paging caps internal fragmentation at under one block per request, versus the thousands of wasted slots under max-length pre-allocation.
Exercise 3. A chat service has 500 concurrent sessions that all share an identical 1000-token system prompt, then diverge. Compare the KV cache used for the shared prefix with and without PagedAttention's copy-on-write block sharing (use the MiB-per-token figure from the kv-cache lesson), and state the assumption that makes the sharing safe.
Solution
Step 1. Without sharing, each session stores its own copy of the 1000-token prefix: MiB GiB of KV cache for the prefix alone.
Step 2. With block sharing, the identical prefix is stored once and all 500 block tables point to the same physical blocks: MiB GiB, a reduction on the shared region.
Step 3. The sharing is safe only because the prefix blocks are read-only while shared: keys and values of a generated token never change once written (the kv-cache lesson's reusability argument). When a session generates its first divergent token it triggers copy-on-write, allocating a private block from that point on, so correctness is preserved while the common prefix stays shared.
References
- [1]Kwon, W., et al. (2023). "Efficient memory management for large language model serving with PagedAttention". SOSP. arXiv:2309.06180. The vLLM paper: paged KV blocks, copy-on-write prefix sharing, and continuous batching.↩