Runbooks/ LLM Inference Runbook Track B · The machine and the two phases Deep readQuick learn
  1. 00 Start
  2. /
  3. 01 Tokens
  4. 02 Anatomy
  5. 03 Journey
  6. /
  7. 04 Hardware
  8. 05 Phases
  9. 06 KV cache
  10. /
  11. 07 Attention
  12. 08 Position
  13. 09 Precision
  14. 10 Cache ops
  15. /
  16. 11 Many GPUs
  17. 12 Engines
  18. 13 Decode loop
  19. /
  20. 14 Planning
  21. 15 Production
Document 05 · Quick learn Compute vs bandwidth · The quadratic term · The cache read

Two phasesopposite bottlenecks

One request, two phases, opposite bottlenecks and disjoint fix lists. Plus the quadratic term nobody remembers, and the cache read that quietly becomes most of your bandwidth.

“Everything clever in serving is either ‘more words per trip’ or ‘a smaller filing cabinet’.”
30,633 tokens
where prefill goes quadratic, 8B
31×
the cost of 32k over 2k, not 16×
81%
of the decode read is cache at 8k × 64
95% / 75%
decode for chat, prefill for classification
Two machines in onesame weights, same kernels, opposite everything else

Nearly every serving question reduces to knowing which phase you are in and what it is starved of. Say the phase first and the rest of the answer follows.

Prefill — compute-bound

2,000 positions through the stack together, in one parallel pass.

  • 34.2 TFLOP against a 16.06 GB read — 2,000 operations per byte
  • Produces exactly one token, and fills 0.24 GiB of cache
  • The user feels it as the pause before anything appears
  • Faster tensor cores help. Bandwidth does not
Same weights.
Same kernels.
Disjoint fix lists.

Decode — bandwidth-bound

One position, 300 times, strictly one after another.

  • 0.015 TFLOP against the same read — one operation per byte
  • Grows the cache 128 KiB a token, and re-reads all of it every step
  • The user feels it as how fast the text streams
  • Bandwidth helps. Tensor cores do not

The comparison, line by line

Passes

One, for the entire prompt.

Decode: one per token generated
Arithmetic intensity

≈ prompt length. 2,000 at a 2,000-token prompt.

Decode: ≈ batch size. 1 at batch 1
Metric

Time to first token.

Decode: inter-token latency
Fixed by

Prefix caching, chunked prefill, shorter prompts, more FLOPs.

Decode: bandwidth, smaller cache, bigger batch, quantised weights, speculation
The analogy — reading a brief versus writing a reply by hand

Reading the brief is one sitting: you take it all in at once, and a longer brief is a proportionally longer sitting. Writing the reply is one word at a time, and before each word you get up, walk to the archive, and fetch the entire filing cabinet — because you need everything to decide one word.

The walking is identical whether you write one word or a thousand. Everything clever in serving is either “take more people’s words per trip” or “make the cabinet smaller”
Where prefill stops being linearthe term almost nobody remembers
prefill work = 2NP  +  4LP²d
the model applied to every position  +  every position compared against every other, twice — once to score, once to blend
They are equal when P = N ÷ (2Ld)
Llama 3.1 8B

8.03B ÷ (2 × 32 × 4,096)

Crossover at 30,633 tokens
Llama 3.1 70B

70.55B ÷ (2 × 80 × 8,192)

Crossover at 53,802 tokens
Llama 3.1 405B

405.85B ÷ (2 × 126 × 16,384)

Crossover at 98,304 tokens
What that means in practice

Below ~30k on an 8B, doubling the prompt roughly doubles prefill. Above it, doubling the prompt nearly quadruples it. At 2,048 tokens attention is 6% of the prefill; at 32,768 it is 52%.

Measured on an H100 at 40% of dense peak: 2,048 tokens is 89 ms and 32,768 is 2.8 seconds — not 16 times, but 31
Larger models stay linear for longer

A bigger model has more parameters in the numerator but also more layers and more width in the denominator, and the two do not cancel. Per-token work grows faster than attention work.

So a 32k prompt is deep into the quadratic regime on an 8B and still comfortably linear on a 405B. “Long context is expensive” is a statement about a particular model, not a universal one

FlashAttention, and why it is not optional

The score matrix

S² per head per layer. 25M numbers at 5,000 tokens; 14.4 billion at 120,000.

Naive

Write it to HBM and read it back. At long context it cannot be materialised at all.

FlashAttention

Compute it in tiles that stay in on-chip SRAM, accumulating the result.

The full matrix

Never exists anywhere.

It is exact, not an approximation

“It approximates attention” is the common wrong answer. The output is the attention you would have computed anyway. The contribution is IO-awareness — recognising that the bottleneck was moving the matrix, not computing it.

28.8 GB in bf16 for a single head at 120,000 tokens — which is why at long context it is the difference between possible and impossible
What decode actually readsthe cache term nobody puts in the estimate

Everyone knows decode reads the weights. The part that gets left out is that it reads the whole cache too — every active sequence, every step — and at scale that term is the larger of the two.

Weights dominate

Short sequences, small batch. The cache is under 15% of the read.

Decode speed ≈ model size ÷ bandwidth. The lever is quantising the weights
Mixed

Moderate batch at moderate length. The cache is a third to a half.

Both levers work. Use both
Cache dominates

Large batch, long sequences, or both. The cache is 70–90%.

Quantising the weights barely helps. GQA, fp8 cache and a shorter limit do
Worked8,192 tokens × batch 64 → cache 68.7 GB against weights 16.06 GB = 84.8 GB read — 5.3× longer per step81% of every byte is cache
The consequence people find surprising

Per-user streaming speed drops from 157 tokens a second to about 30. This is why throughput curves bend down at high concurrency for reasons that have nothing to do with compute.

“The GPU is not compute-bound so we can add more users” is true until it very much is not. The cache is a bandwidth problem long before it is a capacity problem
KV cache · 81%
Weights · 19%
Batchingthe fix, and its two complications

Decode wastes its weight read on one token. The whole answer is to make that read serve many tokens at once. Everything below is a refinement of that one sentence.

Complication one: static versus continuous

Static batching

Four requests start together; the batch does not release until the longest finishes.

The waste

Three of four slots idle for most of the run. No new request can start.

Continuous batching

The scheduler checks after every single generated token.

The result

Same wall clock, same hardware: seven requests served instead of four.

It is a scheduling change, not a mathematical one

Nothing about the arithmetic changed. But because real output lengths vary by two orders of magnitude, refilling the slot the moment a sequence stops is worth several times the throughput.

Complication two: the person who pastes a novel

Unchunked

A 50,000-token prefill takes 2.8 seconds of GPU and holds it.

Everyone else

Every other stream freezes for that whole time — and nothing logs an error.

Chunked prefill

The long prefill is sliced and each piece slipped in alongside ongoing decode steps.

The result

Nobody stalls. The big prefill takes slightly longer in wall clock.

Chunked prefill versus disaggregation

Chunked prefill

Slice a long prefill and interleave the slices with decode steps on the same GPU.

Gains: removes stalls, one pool of machines, on by default.
Costs: large prefills get a bit slower.
When: always — confirm it is enabled rather than assume it
Disaggregation

Run prefill on one pool of GPUs and decode on another, shipping the cache between them.

Gains: no interference at all; the pools scale independently and can use different cards — prefill wants compute, decode wants bandwidth.
Costs: much more complexity, and you must move cache across the network.
When: large fleets where the interference is measured, not assumed
Workload shape decides everythingthe prefill-to-decode ratio is a property of your traffic
Chat

2,000 in, 300 out.

95% decode. Bandwidth-bound. Sit below the ridge point to protect streaming speed; consider speculation at low concurrency
Classification

1,000 in, 20 out.

75% prefill. Compute-bound. Nobody is watching text appear — push the batch far past the ridge and optimise purely for throughput
RAG question answering

8,000 in, 200 out.

Prefill-heavy. Every decode optimisation in the runbook would move its p95 by a few per cent
The question to ask before any tuning

“What is the prompt and output length distribution on real traffic?” Not the mean — the distribution, because prefill cost is driven by the tail.

Two products on the same model and the same hardware can want opposite configurations. Without that distribution, “make it faster” has no defined meaning
Answer it out loudthe ninety-second version, then seven questions
“A request has two phases that behave like different machines. Prefill pushes the whole prompt through in one parallel pass: compute-bound, intensity roughly the prompt length, and it sets time to first token. Decode produces one token per pass, reading every weight and the entire cache each time: bandwidth-bound, intensity roughly the batch size, and it sets streaming speed. Prefill has a quadratic attention term that overtakes the linear one at about thirty thousand tokens on an 8B, which is why a 32k prompt is thirty-one times a 2k one rather than sixteen. And at batch 64 and 8k context, the cache is 81% of every byte a decode step reads — so the cache is a bandwidth problem long before it is a capacity problem.”

Seven questions, and the spine of each answer

Architect

Why is prefill compute-bound and decode memory-bound?

  • Both read the same weights. Prefill spreads that read across every position
  • Intensity ≈ prompt length — 2,000 against an H100 ridge of 295. Comfortably compute-bound
  • Decode spreads the same read across one token — intensity ~1, or the batch size
The fix lists have no overlap. Buying a card with more tensor cores does nothing at all for decode, which is the most common wrong answer in this area.
Architect

A 32k prompt takes far more than sixteen times a 2k prompt. Why?

  • Prefill has a quadratic term: 2NP for the model plus 4LP²d for attention
  • For the 8B those are equal at P = N/(2Ld) = 30,633 tokens
  • At 2,000 attention is 6% of prefill; at 32,768 it is 52%
Measured: 89 ms at 2,048 and 2.8 s at 32,768 — 31×, not 16×. And it scales non-obviously: the 70B crosses at 54k, the 405B at 98k.
Architect

What does FlashAttention do, and is it an approximation?

  • It is exact — say that first, because “it approximates attention” is the common wrong answer
  • It tiles the score matrix in on-chip SRAM so the S×S matrix never reaches HBM
  • At 120,000 tokens that matrix is 14.4 billion numbers — 28.8 GB for a single head
The contribution is IO-awareness: the bottleneck was moving the matrix, not computing it. At long context it is the difference between possible and impossible.
Architect

Throughput stopped improving when we raised the batch limit.

  • Two candidates, and they are distinguishable
  • The ridge point: past ~150 on an H100 you are compute-bound; ITL rises roughly linearly with batch
  • The cache read (more likely, less discussed): at 8k × 64 that is 68.7 GB against 16.06 of weights
The measurement that separates them is achieved bandwidth. Near peak → it is the cache (fp8, shorter limit, fewer KV heads). Far below while compute saturates → the ridge point, and the fix is admission control.
Architect

Why does prefill produce only one token from thousands of positions?

  • The other positions are the prompt — we already know those tokens
  • Their pass exists to compute and store keys and values, not to predict
  • Only the last position has an unknown successor
Which is why the head runs on one position. All 8,192 would be a 3.9 GiB tensor — and why log-probs on prompt tokens are expensive.
Eng manager

Chat and a nightly classification job on one cluster. Same config?

  • No — they want close to opposite configurations
  • Chat: 95% decode, bandwidth-bound, sit below the ridge to protect streaming
  • Classification: 75% prefill, compute-bound, push the batch far past the ridge
Separate deployments, separate batch caps, separate SLOs — and different cards if budget allows. The failure mode of not separating them is the nightly job pushing chat latency over budget, and on-call spending a night finding out why.
Architect

Someone pastes a 50,000-token document and everything freezes.

  • Unchunked, that prefill holds the GPU for 2.8 seconds and every other stream stops dead
  • Cheap fix: chunked prefill. Standard in modern engines — first action is a ten-minute check that it is actually on
  • Structural fix: disaggregation, if we grow into it
I would not reach for disaggregation until we have measured that chunking is no longer enough. It is significantly more complex and moves cache across the network.
Fast answersthe ten that come up most

QWhat are TTFT and ITL, precisely?

TTFT is queue wait plus the CPU front end plus prefill — the pause before anything appears. ITL is the gap between successive tokens once streaming starts, set by decode. Different subsystems, and they trade against each other: a large batch improves throughput and worsens both.

QWhy can a model not generate several tokens at once?

Each token depends on the one before it. That dependency forces one-at-a-time generation and is the root of the whole bandwidth problem. You can guess ahead and verify cheaply, which works precisely because verification is prefill-shaped.

QDoes batching make my own answer faster?

No. It serves far more people at roughly the same speed; your own tokens arrive slightly slower in a large batch. Batching buys throughput, not single-user latency — and saying that plainly is the right answer rather than a hedge.

QIs chunked prefill free?

Nearly. Each piece is slightly less efficient than one big pass because the batch shapes are smaller, so the big request takes a bit longer end to end. In exchange nobody else stalls. On any interactive workload that is an easy trade.

QWhat is the difference between concurrency and batch size?

Concurrency is how many requests the server is holding; batch size is how many are in the same forward pass right now. The cache figure bounds concurrency; the ridge point bounds the useful batch size.

QWhy does my long conversation get slower over time?

The cache is read at every step and grows at every step. At 2,000 tokens it is 6% of the read; at 131,072 it is 52%, so per-token latency has roughly doubled for that user. A genuine and often unexplained user complaint.

QShould I always turn on prefix caching?

On any workload with a shared prefix, yes — it is the largest TTFT lever available. Two caveats: it only helps if the shared part is at the start of the token sequence, and it raises an isolation question across tenants (document 10).

QDoes the quadratic term affect decode too?

Not in the same way. At decode there is one query attending to S cached keys, so attention work per step is linear in sequence length. But that linear term is a bandwidth cost rather than a compute cost, and at long context it becomes the majority of the read.

QWhere does the 40% MFU assumption come from?

It is a planning convention for prefill on a well-tuned server, not a measurement of your system. Dense peak is impossible — kernel launches, non-matmul layers, imperfect shapes. 35–50% is the realistic band. Use 40% to plan and say you would confirm it.

QIf decode is the problem, why does anyone care about prefill?

Because it is 5% of a chat request and 75% of a classification one. Workload shape decides which phase owns your latency, and the two have disjoint fix lists. A RAG product with 8,000-token prompts and 200-token answers is prefill-heavy.

Name the phase first, and the rest of the answer follows. Prefill is compute-bound, intensity roughly the prompt length, and it sets the pause. Decode is bandwidth-bound, intensity roughly the batch size, and it sets the streaming. They share weights and kernels and nothing else — their fix lists have no overlap at all, which is why “the model is slow” is not yet a question.

Where this goes next

Questions to ask them

What is the prompt and output length distribution — p50, p95 and p99?

Is chunked prefill enabled? How do you know?

What fraction of decode-step bandwidth is cache rather than weights at your typical batch?

Are TTFT and inter-token latency budgeted separately, and by whom?

Do prefill-heavy and decode-heavy workloads share a deployment?

Has anyone measured where throughput stops improving as the batch cap rises?

← 04 · Hardware Read the full chapter → 06 · KV cache →