# Prefill, decode, and the roofline that explains everything
> An inference engine doesn't have one speed, it has two, and they obey opposite laws. The roofline model is the single most useful lens for reasoning about LLM performance.
Source: https://rfriedmann.de/blog/prefill-vs-decode-roofline/
Published: 2026-05-05 · Track: log · Level: Advanced
People quote "tokens per second" as if an engine has one speed. It has two, and
they're governed by opposite limits. Once you see why, most of the surprising
numbers in LLM inference stop being surprising. This is the lens I reach for
first, and it's worth installing properly.
If you need the gentle version of the two phases, the [engine
walkthrough](/blog/how-imp-works-model-to-token/) has it. Here we go straight for
the why.
## Two ceilings
A GPU has two separate speed limits. How fast it can do maths (measured in
TFLOP/s), and how fast it can move data in and out of memory (measured in TB/s).
For any given piece of work, you hit one of them first, and that's the one that
caps you. The deciding factor is a single ratio.
Plot speed against that ratio and you get the famous "roofline": a sloped line
(when you're memory-limited, more intensity buys more speed) that flattens into a
ceiling (once you're compute-limited, more intensity buys nothing).
The roofline: why prefill and decode live in different worlds
[diagram omitted — see the page for the chart]
Reading your prompt (prefill) reuses each loaded weight across many tokens, so it pushes against the compute ceiling. Writing the answer (decode) touches each weight once per token, so it sits far left, limited by memory speed, not math.
## Prefill lives on the right, decode on the left
Now place the two phases on that picture.
**Prefill** processes the whole prompt at once. Every weight it loads gets used
against many tokens before being thrown away, so it does a lot of maths per byte:
high arithmetic intensity. It sits on the flat part, pushing against the compute
ceiling. Hand it more tokens at once and it gets *more* efficient.
**Decode** writes one token at a time. Each weight is loaded, used exactly once,
and discarded. That's very low arithmetic intensity (about 1 FLOP/byte in fp16),
so decode is pinned to the sloped part, limited purely by memory bandwidth. The compute units
spend most of their time idle, waiting for the next weight to arrive.
## "Compute is free"
Put a hard number on it. For a typical decode step on a 5090 the arithmetic
intensity is about **1 FLOP/byte**, against a ridge point of roughly **60 to 120
FLOP/byte**. The ridge is just peak FLOP/s divided by peak bandwidth: with the
5090's 1.79 TB/s and roughly 105 TFLOP/s dense fp16 (fp32-accumulate) up to ~210
(fp16-accumulate), that lands around 60 to 120 FLOP/byte. Decode sits about two
orders of magnitude to the left of it. The docs phrase it bluntly: *compute is
free.* The chip is almost never doing maths; it is almost always waiting for
bytes.
That bandwidth ceiling translates straight into a token rate. An 8B model at fp16
is about 16 GB of weights, and you stream all of them once per token, so 1.79 TB/s
divided by 16 GB caps you near **110 tok/s** (and roughly **220 tok/s** at int8,
which halves the bytes). The math ceiling never enters the picture.
That has a sharp consequence. For single-stream decode, the only lever that
matters is moving fewer bytes, faster. It's exactly why
[quantisation](/blog/how-a-big-model-fits-on-one-card/) helps decode so much, and
why a whole list of clever-sounding optimisations do nothing for it. In imp's
testing, all three were duds for decode:
- **FP8 scores** in attention. The compute is already idle, so making it cheaper changes nothing.
- **Warp specialisation.** All the warps are already streaming memory at peak; there's no overlap left to win.
- **Fancier memory instructions for the KV cache.** The plain async copy is already near the bandwidth ceiling.
None of them move bytes, so none of them move decode.
## Why this lens matters
This is the whole reason an engine that's fast at prefill isn't automatically fast
at decode, and vice versa. They live under different ceilings and want different
kernels. It's why the [hard-won kernel work](/blog/optimizing-kernels-consumer-blackwell/)
splits cleanly along that line.
And it's why **batching** changes everything. Serve many users at once and their
decode streams share each loaded weight, dragging decode rightward into the
compute region where the idle maths finally gets used. That's the data centre's
trick. A single person running a model at home gets none of it, which is why, for
batch-1 decode of the weights, bandwidth is the dominant lever, and why a card's
[memory speed](/blog/what-is-a-gpu/) matters more than its raw compute. That holds
for streaming the weights specifically; it leaves out prefill (compute-bound) and
long-context attention, where the KV-cache traffic becomes its own bottleneck. The
[numbers](/blog/serving-30b-models-rtx-5090/) all fall out of this one picture.