The Roofline Model & KV Cache Mechanics

Serving large language models presents an architectural paradox: the exact same model running on the exact same GPU behaves like two completely different computational workloads depending on whether it is processing input tokens or generating output tokens.

This chapter details the physics of GPU execution: the Roofline Model, the mathematical separation between the Prefill and Decode phases, the exact memory scaling of the KV Cache, and how PagedAttention eliminates memory fragmentation.

Imagine reading a 10-page essay versus writing a 10-page essay.

When you read the essay (Prefill), your brain takes in whole paragraphs at once. You are using 100% of your cognitive reading capacity (Compute-Bound).

When you write the essay word by word (Decode), your hand can only move so fast. You know what you want to write, but you have to write one letter at a time, repeatedly dipping your pen into the ink bottle. Your brain is barely working hard, but you are waiting on the physical speed of the pen (Memory-Bandwidth-Bound).

In LLM serving, we use the Roofline Model to map out where these two phases sit on physical chips.


1. The Roofline Model & Operational Regimes

The Roofline Model defines the attainable performance of any kernel or workload on hardware:

Systems & Hardware Roofline Analysis
The Roofline Model & Arithmetic Intensity
Target Hardware
MEMORY-BANDWIDTH BOUND
0.5 FLOPs/B (Memory Bound)Knee: 591 FLOPs/B800 FLOPs/B (Compute Bound)
Knee (591 FLOPs/B)Memory Slope (3,350 GB/s)Peak Compute (1,979 TFLOPs)Operational Intensity (FLOPs / Byte, Log Scale)Attainable Perf (TFLOPs/s)
Attainable Perf
107.2 TFLOPs
5.4% of Peak Capacity
Limiting Bottleneck
Memory Bandwidth
Saturated at 3350 GB/s
Optimization Move
Quantize (INT8/FP8) or Batch Requests
Reduces bytes read per token
The Roofline Model determines whether your AI workload is Memory-Bandwidth-Bound or Compute-Bound. In LLM serving: Prompt Prefill has high arithmetic intensity (Compute-Bound), while Autoregressive Token Generation reads gigabytes of weights to produce 1 token (Memory-Bandwidth-Bound).

Deriving Operational Intensity

For any neural network operation, the Operational Intensity (II) is:

I=Floating Point Operations (FLOPs)Bytes Read from Memory (Bytes)I = \frac{\text{Floating Point Operations (FLOPs)}}{\text{Bytes Read from Memory (Bytes)}}

Consider multiplying an activation matrix X∈RB×dX \in \mathbb{R}^{B \times d} by a weight matrix W∈Rd×doutW \in \mathbb{R}^{d \times d_{\text{out}}}:

  • Total FLOPs: 2×B×d×dout2 \times B \times d \times d_{\text{out}} (1 multiply and 1 add per element).
  • Bytes read (assuming weights dominate): d×dout×bytes_per_elemd \times d_{\text{out}} \times \text{bytes\_per\_elem}.

Thus, the arithmetic intensity is:

I=2×B×d×doutd×dout×bytes_per_elem=2×Bbytes_per_elemI = \frac{2 \times B \times d \times d_{\text{out}}}{d \times d_{\text{out}} \times \text{bytes\_per\_elem}} = \frac{2 \times B}{\text{bytes\_per\_elem}}

The Batch Size Relationship: Notice that dd and doutd_{\text{out}} cancel out! Arithmetic intensity is directly proportional to Batch Size (BB). At batch size B=1B=1 (single user decode), arithmetic intensity is roughly 2/2=1.02 / 2 = 1.0 FLOP/byte—deep inside the memory-bandwidth-bound region. At batch size B=64B=64, arithmetic intensity jumps to 6464 FLOPs/byte, moving close to the compute roof!

Predict First
Why does an NVIDIA H100 GPU (1,979 TFLOPs FP8, 3,350 GB/s HBM3) have a knee intensity of ~590 FLOPs/byte, while an older V100 GPU had a knee of only ~140 FLOPs/byte?

2. Prefill vs Decode Phases in LLMs

An LLM request is serviced in two fundamentally distinct phases:

MetricPrefill Phase (Prompt Processing)Decode Phase (Token Generation)
InputPrompt tokens (NN tokens at once)Exactly 1 token per sequence
OperationGeneral Matrix-Matrix Multiply (GEMM)General Matrix-Vector Multiply (GEMV)
BottleneckCompute-Bound (High FLOPs/byte)Memory-Bandwidth-Bound (Low FLOPs/byte)
Latency MetricTime-To-First-Token (TTFT)Time-Per-Output-Token (TPOT)
Hardware SaturationTensor Cores run at 70-85% MFUTensor Cores sit idle >80% of the time

3. The KV Cache Memory Formula

During autoregressive decoding at step tt, the attention calculation needs to attend to all past tokens 1…t−11 \dots t-1. Rather than recomputing earlier key and value vectors from raw embeddings, they are stored in the KV Cache.

The exact memory footprint for a single request of length LL tokens is:

MemoryKV=2×nlayers×nkv_heads×dhead×L×bytes_per_elem\text{Memory}_{\text{KV}} = 2 \times n_{\text{layers}} \times n_{\text{kv\_heads}} \times d_{\text{head}} \times L \times \text{bytes\_per\_elem}

Where:

  • The factor of 22 accounts for both Key and Value tensors.
  • nlayersn_{\text{layers}} is the number of transformer blocks.
  • nkv_headsn_{\text{kv\_heads}} is the number of key/value heads (in Multi-Head Attention, nkv_heads=nheadsn_{\text{kv\_heads}} = n_{\text{heads}}; in Grouped-Query Attention, nkv_heads≪nheadsn_{\text{kv\_heads}} \ll n_{\text{heads}}).
  • dheadd_{\text{head}} is the dimension of each attention head.
  • LL is sequence length.

4. PagedAttention: Virtual Memory for LLM Serving

In traditional serving frameworks, memory for a request was allocated contiguously for the user's max_tokens setting.

Explore how PagedAttention partitions KV blocks into non-contiguous physical pages:

vLLM & High-Throughput Serving Architecture
PagedAttention vs Contiguous KV Cache Allocation
Memory Allocation Strategy
Active Concurrent Requests
3 of 3 Serving
Near-zero memory fragmentation
Memory Waste / Fragmentation
0% (Internal tail only)
Only partial last block is unused
Serving Throughput Gain
2.4x - 3.8x Speedup
Batches more requests into the same VRAM
Physical GPU KV Cache Memory (32 Blocks = 512 Tokens)Each cell = 1 Physical Block (16 Tokens)
B#0A
B#1C
B#2B
B#3FREE
B#4A
B#5C
B#6FREE
B#7B
B#8C
B#9FREE
B#10FREE
B#11A
B#12C
B#13FREE
B#14FREE
B#15FREE
B#16FREE
B#17FREE
B#18FREE
B#19FREE
B#20FREE
B#21FREE
B#22FREE
B#23FREE
B#24FREE
B#25FREE
B#26FREE
B#27FREE
B#28FREE
B#29FREE
B#30FREE
B#31FREE
Req A ("Translate code"):48 tokens
Req B ("Summarize paper"):32 tokens
Req C ("Write PyTorch test"):64 tokens
PagedAttention (vLLM, Kwon et al.) brings virtual memory paging to LLM serving. In traditional contiguous allocation, 60% to 80% of VRAM is wasted on over-reservation and external fragmentation. PagedAttention eliminates external fragmentation completely, enabling 2x to 4x higher serving throughput.

The PagedAttention Mechanism

  1. Logical to Physical Mapping: The KV Cache of a sequence is split into fixed-size logical blocks (e.g. 16 tokens per block).
  2. Block Table: The serving engine maintains a routing table that maps logical block numbers to arbitrary physical memory frames allocated from a global GPU memory pool.
  3. Zero External Fragmentation: Because every physical block has the exact same size, the memory allocator never needs to find contiguous blocks.
  4. Copy-on-Write for Parallel Sampling: When an agent or user branches into multiple outputs (e.g. Speculative Decoding, beam search, or tree-of-thought), child sequences share the parent prompt's physical blocks until one of them writes a new token.

5. Python Simulation: KV Cache Profiler

Run this live in-browser Python implementation to compute the exact KV Cache memory scaling across sequence lengths and batch sizes:

Run it yourself
def calculate_kv_cache(
batch_size: int,
seq_len: int,
n_layers: int = 32,
n_kv_heads: int = 8, # GQA (e.g. LLaMA-3-8B)
d_head: int = 128,
precision_bytes: int = 2 # FP16
) -> dict:
bytes_per_token = 2 * n_layers * n_kv_heads * d_head * precision_bytes
total_bytes = bytes_per_token * seq_len * batch_size
total_mb = total_bytes / (1024 * 1024)
total_gb = total_mb / 1024
return {
"bytes_per_token": bytes_per_token,
"total_mb": round(total_mb, 2),
"total_gb": round(total_gb, 2)
}

print("=== KV Cache Memory Scaling (LLaMA-3-8B GQA) ===")
for seq in [512, 2048, 4096, 8192, 16384]:
res = calculate_kv_cache(batch_size=32, seq_len=seq)
print(f"Batch=32 | SeqLen={seq:5d} -> KV Cache: {res['total_gb']:5.2f} GB ({res['total_mb']} MB)")


Roofline Rule of Thumb

Always balance TTFT and TPOT. When serving interactive conversational agents, separate your clusters or workers into specialized Prefill Nodes (optimized for dense compute throughput) and Decode Nodes (optimized for memory bandwidth and PagedAttention batch concurrency).

Next in Curriculum

Learn how iteration-level scheduling and draft models accelerate generation in Continuous Batching & Speculative Decoding.

Last updated Oct 30, 2026Edit this pageReport an issue