L
learnllm
0% Completed
Catalog/LLM Foundations/PagedAttention & Memory Footprint Calculations
Architecture28 Minutes

PagedAttention & Memory Footprint Calculations

Calculate the exact VRAM required for KV cache at batch size B and sequence length L. See why naive contiguous allocation wastes up to 80% of GPU memory.

[ ENGINEERING OBJECTIVES ]
  • •Compute KV Cache bytes = 2 * 2 * n_layers * n_heads * d_head * seq_len * batch_size
  • •Understand physical vs virtual memory blocks in vLLM PagedAttention
  • •Mitigate memory fragmentation during continuous streaming

The Arithmetic Intensity Wall & KV Cache Formula

During the autoregressive decode phase, an LLM generates exactly one token per forward step. Each new token requires streaming the entire preceding KV cache across all 32-80 layers from High-Bandwidth Memory (HBM) into on-chip SRAM. The arithmetic intensity drops to less than 1 FLOP per byte transferred! At this point, GPU compute cores sit 90% idle, waiting on memory bus bandwidth.

INTERACTIVE ENGINEERING SIMULATOR

KV Cache Memory Wall & Hardware Fit Calculator

Formula: 2 × L × H × D × S × B × P
Concurrent Batch Size (B):16 requests
1 (Single user)32 (Production serving)64 (High throughput)
Context Length (Tokens):4,096 tokens
5128,192 (Standard)32,768 (Long Context)
Live VRAM Allocation Footprint
Model Weights (Base):16.0 GB
Effective KV Cache (PagedAttn):8.00 GB
Naive Allocation (Waste):13.20 GB
Total VRAM Required:24.00 GB
Hardware Deployment Check
RTX 4090 (24GB): Fits
A100 (80GB): Fits

Notice in the interactive simulator above: Increasing batch size amortizes model weight loading overhead, but causes KV cache memory to grow linearly until hardware VRAM is completely saturated.

PagedAttention: Eliminating Fragmentation via Virtual Memory Pages

Standard PyTorch allocations require contiguous memory chunks. Because generative output lengths are unknown in advance, naive systems pre-allocate memory for the maximum possible sequence length (e.g. 8k or 32k tokens), resulting in 60% to 80% wasted VRAM due to internal and external fragmentation. Inspired by OS virtual memory paging, vLLM's PagedAttention partitions the KV cache into fixed-size physical blocks (typically 16 tokens), indexing them through a dynamic Block Table.

PAGED MEMORY MAPPING

PagedAttention Virtual-to-Physical Block Mapping

Logical Request Sequence (Continuous Virtual Address)
Logical Block 0"Tokens 0 - 15 ('The quick brown...')"
Logical Block 1"Tokens 16 - 31 ('...jumped over the...')"
Logical Block 2"Tokens 32 - 47 ('...lazy sleeping dog...')"
Logical Block 3"Tokens 48 - 63 ('...in the quiet park.')"
Physical GPU Memory (Non-Contiguous HBM Allocation)
Physical Page 104HBM Chunk @ 0x7FA2
Physical Page 12HBM Chunk @ 0x11B0
Physical Page 89HBM Chunk @ 0x58D4
Physical Page 231HBM Chunk @ 0x93E0
Zero Internal Fragmentation: Tokens are batched into 16-token physical blocks. GPU allocations occur dynamically as generation proceeds without pre-allocating the maximum sequence length.

Logical token sequences are perceived by the transformer as contiguous, while the physical blocks reside anywhere across non-contiguous GPU HBM pages. Zero memory is pre-reserved, reclaiming up to 4x throughput capacity.

Production Block Allocator Implementation

Here is an end-to-end, runnable implementation of a PagedAttention memory manager that dynamically allocates and frees 16-token physical pages on GPU during continuous streaming.

Production Implementation Suite (2 Files)
Physical block table memory manager allocating non-contiguous 16-token KV slots39 lines • python
import torch
from dataclasses import dataclass
from typing import Dict, List

BLOCK_SIZE = 16  # Tokens per page

class PagedKVCacheManager:
    def __init__(self, num_blocks: int, num_layers: int, num_heads: int, head_dim: int, device="cuda"):
        self.num_blocks = num_blocks
        self.device = device
        
        # Pre-allocate non-contiguous physical page pool: [num_blocks, 2, num_layers, num_heads, BLOCK_SIZE, head_dim]
        self.kv_storage = torch.zeros(
            (num_blocks, 2, num_layers, num_heads, BLOCK_SIZE, head_dim),
            dtype=torch.float16,
            device=device
        )
        self.free_blocks: List[int] = list(range(num_blocks))
        self.block_tables: Dict[str, List[int]] = {}

    def allocate_page_for_request(self, request_id: str) -> int:
        if not self.free_blocks:
            raise MemoryError("CUDA OOM: KV-Cache Physical Page Pool Exhausted!")
        block_id = self.free_blocks.pop(0)
        if request_id not in self.block_tables:
            self.block_tables[request_id] = []
        self.block_tables[request_id].append(block_id)
        return block_id

    def free_request(self, request_id: str):
        if request_id in self.block_tables:
            for b_id in self.block_tables[request_id]:
                self.free_blocks.append(b_id)
            del self.block_tables[request_id]

# Initialize 10,000 physical blocks (160,000 tokens)
manager = PagedKVCacheManager(num_blocks=10000, num_layers=32, num_heads=8, head_dim=128, device="cpu")
req_block = manager.allocate_page_for_request("req_user_42")
print(f"Allocated physical block ID: {req_block} for req_user_42")
Ready to mark finished?
Track your technical progress.