LLM DAILYA field guide to language models

Day 69 / 6 min read

The roofline model — why LLM inference runs out of memory before math

Yesterday we explored disaggregated prefill and decode: separate GPU pools can serve prompt processing and token generation because the two phases stress hardware differently.

Today we'll put numbers behind that claim using the roofline model, a simple way to ask whether a workload is limited by arithmetic or by moving data.

The intuition is:

A GPU can have enormous computational power and still spend most of its time waiting for numbers to arrive from memory.

That distinction explains why batching, KV-cache quantization, and speculative decoding can produce large speedups without making the model perform fewer mathematical operations.

Two ceilings on performance

Suppose a computation requires \(F\) floating-point operations and moves \(M\) bytes from a particular level of memory. A device offers peak compute throughput \(P\) FLOP/s and memory bandwidth \(W\) bytes/s.

Its execution time cannot be lower than either of these bounds:

\[ t \geq \max\left(\frac{F}{P},\frac{M}{W}\right). \]

Equivalently, achieved arithmetic throughput is bounded by

\[ \text{FLOP/s} \leq \min(P,\; W I), \qquad I=\frac{F}{M}. \]

Here \(I\) is arithmetic intensity: how much useful math we do for each byte moved. The crossover point is

\[ I^*=\frac{P}{W}. \]

Below \(I^*\), the memory-bandwidth ceiling is lower. Above it, the compute ceiling is lower. This is a lower-bound model, not a latency predictor: kernel launch overhead, communication, cache behavior, occupancy, and imperfect hardware utilization also matter.

Why decoding one token can be memory-bound

For a dense Transformer with \(N\) parameters, a rough forward-pass estimate is

\[ F_{\text{token}}\approx 2N \]

FLOPs per generated token, excluding the context-dependent attention work. Each multiply-add counts as roughly two FLOPs.

Take an illustrative 7-billion-parameter model stored in BF16:

\[ M_{\text{weights}}\approx 7\times10^9\times2 =14\text{ GB}. \]

Generating one token requires roughly

\[ F_{\text{token}}\approx14\text{ GFLOPs}. \]

If the model weights must be streamed once from high-bandwidth memory for that token, the arithmetic intensity of the weight computation is only

\[ I\approx\frac{14\text{ GFLOPs}}{14\text{ GB}} =1\text{ FLOP/byte}. \]

That's surprisingly low. A GPU may have hundreds of trillions of FLOPs per second available, but one request cannot use them efficiently if it must repeatedly read gigabytes of weights for relatively little computation.

Worked example: the same model at different batch sizes

Imagine a GPU with 300 TFLOP/s of usable peak BF16 compute and 3 TB/s of peak memory bandwidth. These are illustrative hardware ceilings, not measurements of a specific product.

Its crossover intensity is

\[ I^*=\frac{300\times10^{12}}{3\times10^{12}} =100\text{ FLOPs/byte}. \]

For a single token, the idealized lower bounds are:

\[ t_{\text{compute}}\geq\frac{14\text{ GFLOPs}}{300\text{ TFLOP/s}} \approx0.047\text{ ms}, \]
\[ t_{\text{weights}}\geq\frac{14\text{ GB}}{3\text{ TB/s}} \approx4.67\text{ ms}. \]

The memory bound is about 100 times larger than the compute bound. A faster arithmetic unit alone would barely help this simplified case.

Now batch 32 independent decoding requests together. Each request still needs about 14 GFLOPs, but a matrix multiplication can reuse the same model weights across 32 tokens:

\[ F_{32}\approx32\times14=448\text{ GFLOPs}, \qquad I_{32}\approx32\text{ FLOPs/byte}. \]

The idealized bounds become approximately 1.49 ms for computation and 4.67 ms for weight reads. Total work increased 32-fold, yet the weight-read floor did not. That is why batching can greatly improve tokens per second, even though an individual request may wait longer for its turn.

At batch size 128, the weight-only intensity approaches 128 FLOPs/byte, above our hypothetical crossover. But that does not prove real decode is compute-bound: the KV cache, activations, and other memory traffic have not yet been counted.

The KV cache changes the calculation

From Day 65, a BF16 KV cache with 32 layers, 8 KV heads, head dimension 128, and an 8,192-token context occupies 1 GiB per request.

A conventional full-attention decode step may need to read approximately that much historical KV data for each request. At batch size 32, that is roughly 32 GiB of KV reads, in addition to about 14 GB of shared weight reads:

\[ M_{\text{step}}\approx14\text{ GB}+32\text{ GiB} \approx48.4\text{ GB}. \]

At 3 TB/s, the combined traffic alone gives an idealized floor near

\[ t_{\text{memory}}\gtrsim\frac{48.4\text{ GB}}{3\text{ TB/s}} \approx16.1\text{ ms}. \]

This is not a full performance estimate: attention adds FLOPs, and optimized kernels may reuse or avoid some memory traffic. But it reveals the important shift: as context and batch size grow, KV traffic can dominate even when model-weight reuse is excellent.

It also explains why grouped-query attention (GQA) and KV-cache quantization matter. They reduce the bytes of attention history that decoding must retain and potentially read.

Why prefill looks different

During prefill, thousands of prompt tokens are known at once. The GPU can apply each weight matrix to a large matrix of token activations instead of to a single vector.

Ignoring attention's additional cost, a 4,096-token prefill for the same 7B model performs approximately

\[ F_{\text{prefill}}\approx2NT =2(7\times10^9)(4096) \approx57.3\text{ TFLOPs}. \]

If weights are read once and reused across those positions, the weight-only arithmetic intensity is roughly 4,096 FLOPs/byte—far above our 100-FLOP/byte crossover. Prefill can therefore make much better use of compute hardware.

There are important caveats: causal attention introduces context-length-dependent work, and intermediate activations, attention kernels, and memory limits complicate the picture. Still, the basic contrast is robust: large matrix-matrix operations in prefill versus smaller, often memory-hungry operations in decode.

How this connects to the last four lessons

Technique                  Resource pressure it addresses
------------------------   -----------------------------------------
KV-cache quantization      Fewer bytes of attention history
Continuous batching        More reuse of weights across requests
Speculative decoding       More output per expensive target pass
Prefill/decode separation  Different hardware and schedules per phase
Roofline analysis          Explains when each technique can help

The roofline model also warns against simplistic benchmarks. A change that doubles single-request tokens/sec may not double throughput at large batches. A quantized kernel might save memory bandwidth but spend extra time unpacking values. A larger batch may improve total throughput while worsening time to first token or time between tokens.

Mental model: a factory with fast machines and slow deliveries

Imagine a factory with a machine that can assemble 300 parts per second, but a delivery conveyor that supplies only 3 boxes per second.

If each box lets the machine assemble just one part, the machine sits idle. If the factory can reuse the contents of a box to assemble 100 parts, the machine finally becomes the bottleneck.

Batching lets many orders reuse one delivery of model weights. KV quantization makes each delivery of attention history smaller. Prefill resembles a long production run with extensive reuse; single-token decode resembles restarting production for one custom item.

The factory's fastest machine does not determine its output. The bottleneck does.

The deeper lesson

When evaluating an inference optimization, first ask which resource is limiting the workload at the actual batch size and context length.

\[ \boxed{\text{Useful speedups come from relieving the active bottleneck, not maximizing one hardware specification.}} \]

The practical question is not simply, “How many FLOPs does this GPU have?” It is: How many FLOPs can the workload perform for every byte it must move—and how does that ratio change as we batch requests or lengthen their contexts?

LLM Daily Last updated