Naively, each sequence's KV cache is pre-allocated as one contiguous buffer sized for the maximum sequence length, wasting huge amounts of memory whenever actual generations are shorter than the max — extremely common. PagedAttention (as used in vLLM) borrows the operating-system idea of paged virtual memory: the KV cache is divided into fixed-size physical blocks, and each sequence gets a logical-to-physical block table it appends to as it grows, allocating new physical blocks on demand and freeing them when the sequence finishes.
n_blocks_needed = ceil(L / block_size) -- L = sequence length so far
used_bytes = (sum of allocated_blocks over live sequences) * bytes_per_block
('append', seq_id) adds one token to that sequence (allocating a new block only if the current last block is full or the sequence has none yet), and ('free', seq_id) releases all of that sequence's blocks back to the free pool.Only allocate a new physical block when the sequence's current block is full AND it needs to grow further — don't allocate ahead of need. A sequence needs a fresh block exactly when its token count is an exact multiple of block_size (its current last block just filled up, or it has none yet).
Track a free list (e.g. a list of unused block ids). Popping/pushing from it is all "allocate"/"free" really do — no more complex data structure is needed for this simplified version.
Click "Run Tests" to test your implementation