Runbooks/LLM Inference RunbookTrack B · The machine and the two phasesRAG Runbook →0%
  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
LLM Inference Runbook · Document 05 of 15 · Track B — The machine and the two phases

Track B · Document 05 · The machine and the two phases

Prefill and Decode: Two Phases, Two 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.

Reads in about 25 minutes · 6 figures, two of them live · 7 interview questions · prints to clean A4

What is in this document

  1. Two machines in one
  2. Prefill, and where it stops being linear
  3. Decode, and what it actually reads
  4. Batching: the fix and its complications
  5. Workload shape decides everything
  6. Interview questions
  7. FAQ
  8. Cheat sheet

1 · Two machines in one

A single request runs in two phases with opposite performance characteristics. They use the same weights and the same kernels, and almost nothing else about them is the same.

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.

SAME WEIGHTS · SAME KERNELS · OPPOSITE BOTTLENECKS PREFILL DECODE 2,000 positions through the stack together one pass, fully parallel, produces exactly one token one position through the stack one pass per token, strictly sequential, 300 times 34.2 TFLOP ÷ 16.06 GB read 2,000 operations per byte — well above the ridge of 295 0.015 TFLOP ÷ 16.06 GB read 1 operation per byte — 295 times below the ridge COMPUTE-BOUND faster tensor cores help. More bandwidth does not. MEMORY-BANDWIDTH-BOUND more bandwidth helps. Faster tensor cores do not. sets time to first token fixed by: prefix caching, chunked prefill, shorter prompts, more FLOPs sets inter-token latency fixed by: bandwidth, a smaller cache, quantised weights, speculative decoding Almost every serving question is really the question “which of these two?” Naming the phase first is most of the answer, and it costs you nothing to say it.
  1. Two phases of one request, running the same weights through the same kernels.
  2. Prefill pushes 2,000 positions through the stack together in one parallel pass and produces exactly one token. Decode pushes one position, 300 times, strictly one after another.
  3. Prefill: 34.2 TFLOP against a 16.06 GB read — 2,000 operations per byte. Decode: 0.015 TFLOP against the same read — one operation per byte.
  4. So prefill is compute-bound (faster tensor cores help, bandwidth does not) and decode is memory-bandwidth-bound (bandwidth helps, tensor cores do not).
  5. Prefill sets time to first token; decode sets inter-token latency. They have entirely different fix lists, which is why naming the phase first is most of any answer.

The ridge point on an H100 is 295 operations per byte. Prefill sits at 2,000 — comfortably above it. Decode at batch 1 sits at 1. Two workloads, one machine, and the gap between them is a factor of two thousand.

PrefillDecode
What it doesReads the whole promptWrites the answer, one token at a time
PassesOne, for the entire promptOne per token generated
Bounded byArithmeticMemory bandwidth
Arithmetic intensity≈ prompt length. 2,000 at a 2,000-token prompt≈ batch size. 1 at batch 1
The user feels it asThe pause before anything appearsHow fast the text streams
MetricTime to first tokenInter-token latency, or tokens per second
Fixed byPrefix caching, chunked prefill, shorter prompts, more FLOPsBandwidth, smaller cache, bigger batch, quantised weights, speculation
Effect on the cacheFills it — 0.24 GiB for 2,000 tokensGrows it by 128 KiB per token, and re-reads all of it every step

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 lighter”.

2 · Prefill, and where it stops being linear

Prefill work has two terms. Almost everyone remembers the first and almost nobody remembers the second, and the second is why long prompts are so much worse than people expect.

the linear term 2 × N × P — two operations per parameter per token. This is the whole model, applied to every position
the attention term 4 × L × P² × d — every position compares itself against every other, twice: once to score, once to blend
the crossover they are equal when P = N ÷ (2Ld). For Llama 3.1 8B that is 8.03B ÷ (2 × 32 × 4,096) = 30,633 tokens
what that means below ~30k, doubling the prompt roughly doubles prefill. Above it, doubling the prompt nearly quadruples it
PREFILL IS NOT LINEAR IN PROMPT LENGTH, AND THE SECOND TERM IS THE REASON linear term · 2 × N × P 32.9 TFLOP 94% attention · 4 × L × P² × d 2.2 TFLOP 6% total prefill work 35.1 TFLOP at 40% of an H100’s dense peak time to first token 89 ms on one idle H100, nothing cached cache this prompt leaves 0.25 GiB held for the whole request, and it is per user THE CROSSOVER P = N ÷ (2 × L × d) = 8.03B ÷ (2 × 32 × 4,096) = 30,633 tokens Below that length attention is a rounding error and prefill scales linearly with the prompt. Above it, attention dominates and prefill scales with the square of the prompt. At 2,048 tokens you are comfortably in the linear regime — doubling the prompt roughly doubles the work.

Slide the prompt to 32,768 and watch the two bars change places. This is why a 32k prompt is not sixteen times a 2k prompt but roughly thirty times, why long-context time-to-first-token budgets look the way they do, and why chunked prefill exists.

Why the crossover moves with the model, and which way

The crossover is N ÷ (2Ld). 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: for the 70B it is 53,802 tokens and for the 405B it is 98,304. Larger models stay linear for longer, because their per-token work grows faster than their attention work.

The practical reading: a 32k prompt is deep into the quadratic regime on an 8B and still comfortably linear on a 405B. So “long context is expensive” is a statement about a particular model, not a universal one, and the arithmetic is two lines.

WHY LONG CONTEXT IS POSSIBLE AT ALL attention compares every position against every other, so the score matrix is sequence length squared — per head, per layer 5,000 tokens → 25M scores 32,768 → 1.07 billion 120,000 → 14.4 billion = 28.8 GB in bf16, per head The naive implementation writes that matrix to HBM and reads it back. At long context it cannot be materialised at all — there is no card with that much memory. FlashAttention computes it in tiles that stay in on-chip SRAM, accumulating the result as it goes. The full matrix never exists anywhere each tile is loaded, used and discarded — on-chip memory is roughly six times the bandwidth of HBM, and the tile never leaves the chip AND IT IS NOT AN APPROXIMATION The output is bit-for-bit the attention you would have computed. The contribution is IO-awareness — knowing that the bottleneck was moving the matrix rather than computing it. Say that precisely if asked; “it approximates attention” is the common wrong answer.
  1. The attention score matrix is sequence length squared, per head, per layer: 25 million numbers at 5,000 tokens, 14.4 billion at 120,000 — which is 28.8 GB in bf16 for a single head.
  2. A naive implementation writes that to HBM and reads it back. At long context it cannot be materialised at all.
  3. FlashAttention computes it in tiles that stay in on-chip SRAM, accumulating the result. The full matrix never exists anywhere.
  4. It is exact, not an approximation. The contribution is IO-awareness: recognising that the bottleneck was moving the matrix, not computing it.

This is the single reason a 128k context window is physically possible. It is also the cleanest example in the whole field of the runbook’s central theme: the bottleneck was never the arithmetic.

3 · Decode, and what it actually reads

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.

WHAT ONE DECODE STEP ACTUALLY READS · AND THE CACHE IS NOT A ROUNDING ERROR weights 16.06 GB 94% the whole KV cache 1.07 GB 6% read per step 17.13 GB and this crosses the bus once for every single token step time 6.4 ms at 80% of 3.35 TB/s — and this is a floor, not an estimate per user 157 tok/s against a 20 tok/s promise — 7.8× headroom across the batch 157 tok/s what the card actually delivers — this is the number the bill is divided by At short context the weights dominate and the cache is a rounding error. Push the sliders and watch that stop being true.

Set the length to 131,072 and the batch to 1: the cache is now 52% of the read and per-token latency has doubled, for a single user. Set the length to 8,192 and the batch to 64: the cache is 81% of the read. At scale you are not serving a model, you are serving a cache — and that reframing is the argument of the next document.

The three regimes, and which one you are in

Weights dominate. Short sequences, small batch. The cache is under 15% of the read. Here decode speed is essentially model size divided by bandwidth, and the lever that helps is quantising the weights.

Mixed. Moderate batch at moderate length. The cache is a third to a half. Both levers work and you should use both.

Cache dominates. Large batch, long sequences, or both. The cache is 70–90% of every read. Here quantising the weights barely helps at all, and the things that do help are grouped-query attention, an fp8 cache, and a shorter context limit. Knowing which regime you are in tells you which half of this runbook to open.

The consequence people find surprising

At 8,192 tokens and batch 64, the cache is 68.7 GB against 16.06 GB of weights. A decode step reads 84.8 GB, so it takes 5.3 times longer than the weights alone would suggest — and 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, and why “the GPU is not compute-bound so we can add more users” is only true until it very much is not. The cache is a bandwidth problem long before it is a capacity problem.

4 · Batching: the 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.

FOUR REQUESTS, WILDLY DIFFERENT ANSWER LENGTHS — WHICH IS WHAT REAL TRAFFIC LOOKS LIKE STATIC BATCHING — the batch finishes when its slowest member does 2,000 tokenswasted — the slot is heldwastedwasted request 1request 2request 3request 4 three of four slots are idle for most of the run, and no new request can start until all four finish CONTINUOUS BATCHING — a freed slot is refilled after the very next token slot 1 slot 2 slot 3 7 requests served in the same time ↓ same wall clock, same hardware, one scheduling change
  1. Static batching. Four requests start together and the batch does not release until the longest finishes. Three of four slots sit idle for most of the run, and no new request can start.
  2. Continuous batching. The scheduler checks after every single generated token and admits a waiting request the moment a sequence emits its stop token.
  3. Same wall clock, same hardware: seven requests served instead of four.
  4. It is a scheduling change, not a mathematical one — and because real output lengths vary by two orders of magnitude, it is worth several times the throughput.

The analogy that lands: static batching is a bus that will not leave until every passenger has reached their stop. Continuous batching is a taxi rank — the moment somebody gets out, the next person gets in.

Why this is worth several times the throughput, not a few per cent

Because real output lengths vary by two orders of magnitude. A classification request emits five tokens; an essay emits two thousand. Under static batching the five-token request holds a slot for the entire duration of the two-thousand-token one, so utilisation is roughly “mean length divided by max length” — which on real traffic can be under 10%.

Continuous batching checks after every generated token and admits a waiting request the instant a sequence finishes. No maths changes. It is pure scheduling, it is free, and it is why a research library running a batch loop is 14–24× slower than a serving engine on the same hardware.

SOMEONE PASTES A 50,000-TOKEN DOCUMENT. WHAT HAPPENS TO EVERYONE ELSE? COLOCATED, UNCHUNKED — the prefill takes the GPU and holds it big request one 2.8-second prefill its decode user 1user 2user 3 every other stream is frozen for 2.8 seconds — and nothing anywhere logs an error CHUNKED PREFILL — the big prefill is sliced and interleaved with everyone’s decode steps big request user 1user 2 ↓ nobody stalls. The big prefill takes slightly longer; every other stream keeps flowing. THE DELIBERATE TRADE slightly worse worst-case TTFT for one, dramatically better stability for everyone
  1. Colocated and unchunked. A 50,000-token prefill takes 2.8 seconds of GPU and holds it. Every other user’s stream freezes for that whole time, and nothing logs an error.
  2. Chunked prefill. The long prefill is sliced into pieces and each piece is slipped in alongside the ongoing decode steps.
  3. Nobody stalls. The big prefill takes slightly longer in wall-clock; every other stream keeps flowing.
  4. It is a deliberate trade: slightly worse worst-case time-to-first-token for the large request, dramatically better streaming stability for everyone else. It is standard in modern servers — confirm it is on rather than assume it.

This is the clearest example of the two phases fighting over one machine. At larger scale the more radical answer is to stop sharing: run prefill on one pool of GPUs and decode on another, and ship the cache between them.

Chunked prefillDisaggregation
What it isSlice a long prefill and interleave the slices with decode steps on the same GPURun prefill on one pool of GPUs and decode on another, shipping the cache between them
GainsRemoves stalls. One pool of machines. On by default in modern enginesNo interference at all. The two pools scale independently and can even use different hardware
CostsLarge prefills get a bit slower in wall-clockMuch more complexity, and you must move KV cache across the network or NVLink
WhenAlways. Confirm it is enabledLarge fleets where prefill and decode demand genuinely differ, and where the interference is measured rather than assumed
The hardware anglePrefill wants compute, decode wants bandwidth — so in principle you can buy different cards for each. That is the real argument for it

5 · Workload shape decides everything

The prefill-to-decode ratio is not a property of the model. It is a property of your traffic, and it changes which levers do anything at all.

WorkloadPrompt / outputWhere the time goesWhat actually helps
Chat2,000 / 300Decode 95% Bandwidth, smaller cache, bigger batch. Speculative decoding at low concurrency. Prefix caching helps TTFT and throughput but barely moves total time
RAG question answering8,000 / 200Prefill ~40% Both matter. Chunked prefill is essential because prompts are long and bursty; prefix reuse is weak because retrieved passages differ per query
Classification / extraction1,000 / 20Prefill 75%+ Compute, not bandwidth. Push the batch hard — nobody is watching text appear. Speculative decoding is pointless here
Agent loop6,000 / 150, × many stepsPrefill-heavy, and it repeats Prefix caching is the single biggest lever in the runbook for this shape, because every step resends the same tool schemas and history
Long-document summarisation60,000 / 800Prefill dominates, and quadratically You are past the crossover: attention is more than half the prefill. Chunked prefill, and seriously consider whether the whole document needs to be in context
Code completion, inline3,000 / 30Mixed, and the budget is brutal Both, hard. TTFT of a few hundred milliseconds means prefix caching on the open files, and speculative decoding shines because code drafts extremely well

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, and without that distribution “make it faster” has no defined meaning.

6 · Interview questions

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

Both read the same weights. Prefill spreads that one read across every position in the prompt, so the arithmetic intensity is roughly the prompt length — two thousand operations per byte on a 2,000-token prompt, against an H100 ridge point of 295. Comfortably compute-bound. Decode spreads the same read across one token, so the intensity is about one, or the batch size once you are batching. Two orders of magnitude below the ridge, so it waits on memory.

The practical consequence is the fix lists have no overlap. If prefill is slow you want more FLOPs, shorter prompts, or prefix reuse. If decode is slow you want bandwidth, a smaller cache, or a bigger batch. Buying a card with more tensor cores does nothing at all for the second case, which is the most common wrong answer in this area.

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

Because prefill has a quadratic term. The work is 2NP for the model itself plus 4LP²d for attention, where every position compares itself against every other. For Llama 3.1 8B those two are equal at P = N/(2Ld) = 30,633 tokens.

So at 2,000 tokens attention is 6% of the prefill and you can ignore it; at 32,768 it is 52%, and doubling the prompt from there nearly quadruples the work. Measured on an H100 at 40% of dense peak, 2,048 tokens is 89 milliseconds and 32,768 is 2.8 seconds — not 16 times but 31.

Worth adding that this scales with the model in a non-obvious direction: the crossover for the 70B is about 54,000 tokens and for the 405B about 98,000, because per-token work grows faster than attention work. Larger models stay linear for longer.

ArchitectWhat does FlashAttention actually do, and is it an approximation?

It is exact — that is the first thing to say, because “it approximates attention” is the common wrong answer. The output is the attention you would have computed anyway.

The contribution is IO-awareness. The score matrix is sequence length squared per head per layer: at 120,000 tokens that is 14.4 billion numbers, 28.8 GB in bf16, for a single head. It cannot be materialised. FlashAttention computes it in tiles that stay in on-chip SRAM — roughly six times the bandwidth of HBM — accumulating the result as it goes, so the full matrix never exists anywhere.

It is the cleanest example of the theme that runs through all of this: the bottleneck was moving the data, not computing on it. And it is not optional at long context, it is the reason long context is possible.

Eng managerOne user pastes a huge document and everyone else’s chat freezes. Explain that to me and tell me the fix.

Prefill and decode share the GPU, and a 50,000-token prefill occupies it for seconds in one unbroken block. During those seconds no decode step runs for anybody, so every other user’s text stops mid-sentence. Nothing errors, nothing retries, and the logs look healthy — which is why it usually reaches us as “the product feels broken sometimes” rather than as an incident.

The cheap fix is chunked prefill: slice the long prefill into pieces and interleave them with decode steps. The big request gets slightly slower and nobody stalls. It is standard in modern engines, so the first action is to confirm it is actually enabled rather than assume it — that is a ten-minute check.

The structural fix, if we grow into it, is disaggregation: prefill on one pool of GPUs and decode on another, with the cache shipped between them. No interference at all, and the two pools scale independently. It is significantly more complex and I would not reach for it until we have measured that chunking is no longer enough.

ArchitectOur throughput stopped improving when we raised the batch limit. What happened?

Two candidates, and they are distinguishable. The first is the ridge point: decode arithmetic intensity in bf16 is essentially the batch size, so past about 150 on an H100 you are compute-bound and extra sequences buy latency rather than throughput. If that is it, inter-token latency will be rising roughly linearly with batch.

The second is more likely and less discussed: the cache read. Every decode step reads every active sequence’s cache, not just the weights. At 8,192 tokens and batch 64 that is 68.7 GB of cache against 16.06 GB of weights — the step now takes 5.3 times longer, and raising the batch further raises the read proportionally. You are not compute-bound; you are re-bottlenecked on the same bandwidth from a different direction.

The measurement that separates them is achieved bandwidth. If it is near peak, it is the cache and the fixes are an fp8 cache, a shorter context limit, or a model with fewer KV heads. If it is far below peak while compute is saturated, it is the ridge point and the fix is admission control.

ArchitectWhy does prefill produce only one token when it processes thousands of positions?

Because the other positions are the prompt — we already know what those tokens are. Their pass exists to compute and store their keys and values so future tokens can attend to them without recomputing. Only the last position has an unknown successor.

Which is also why the output head runs on one position rather than all of them. If you computed logits for every position on an 8,192-token prefill, that tensor would be 3.9 GiB, because it is 128,256 scores per position. Asking for log-probabilities on prompt tokens is expensive for exactly this reason.

Eng managerWe run both a chat product and a nightly classification job on the same cluster. Same config?

No, and they want close to opposite configurations — which is a good argument for separating them rather than tuning a compromise.

Chat is 2,000 in and 300 out, so 95% of the time is decode. It is bandwidth-bound, it wants to sit below the ridge point to protect per-user streaming speed, and speculative decoding is worth considering at low concurrency. Classification is 1,000 in and 20 out, so prefill is three-quarters of the work. It is compute-bound, nobody is watching text appear, so you push the batch far past the ridge and optimise purely for throughput. Speculative decoding would actively hurt.

Practically I would give them separate deployments with separate batch caps and separate SLOs, and if the hardware budget ever allows it, different cards — the classification job would happily run on something with less bandwidth and more compute per pound. The failure mode of not separating them is that the nightly job’s large batches push chat latency over budget, and the on-call engineer spends a night discovering why.

7 · FAQ

What are TTFT and ITL, precisely?

Time to first token is queue wait plus the CPU front end plus prefill — the pause before anything appears. Inter-token latency, sometimes called time per output token, is the gap between successive tokens once streaming starts, set by decode. They are set by different subsystems and they trade against each other: a large batch improves throughput and worsens both.

Why can a model not generate several tokens at once?

Each token depends on the one before it — you cannot choose token three until you know what token two turned out to be. 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 is speculative decoding, and it works precisely because verification is prefill-shaped.

Does batching make my own answer faster?

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

Is chunked prefill free?

Nearly. The long prefill is split into pieces, each 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, which is why modern engines default to it.

What 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. They differ because some admitted requests are prefilling, some are queued for blocks, and the scheduler interleaves. The cache figure bounds concurrency; the ridge point bounds the useful batch size.

Why does my long conversation get slower over time?

Because 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 tokens it is 52%, so per-token latency has roughly doubled for that user. This is a genuine and often unexplained user complaint, and the fix is either a cache-quantisation setting or a limit on conversation length.

Should 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, so a timestamp at the top of the system prompt destroys it. And it has an isolation question worth thinking about before enabling it across tenants, which is document 10.

Does the quadratic term affect decode too?

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

Where 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. Achieving dense peak is impossible — there are kernel launches, non-matmul layers, imperfect shapes and scheduler overhead. 35–50% is the realistic band. Use 40% to plan and say you would confirm it by measuring; both halves of that sentence matter.

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

Because it is 95% of a chat request and 75% of a classification one. Workload shape decides which phase owns your latency, and the two are not just different weights on the same fix — they have disjoint fix lists. A RAG product with 8,000-token prompts and 200-token answers is prefill-heavy, and every decode optimisation in the runbook would move its p95 by a few per cent.

8 · Cheat sheet

prefill whole prompt, one parallel pass, compute-bound, intensity ≈ prompt length, fills the cache, produces one token, sets TTFT
decode one token per pass, bandwidth-bound, intensity ≈ batch size, grows and re-reads the cache, sets inter-token latency
prefill work 2NP + 4LP²d. Crossover at P = N ÷ (2Ld): 30,633 for the 8B, 53,802 for the 70B, 98,304 for the 405B
decode read all weights plus every active sequence’s whole cache. At 8k × batch 64 the cache is 81% of it
FlashAttention exact, not approximate. Tiles the score matrix in SRAM so the S×S matrix never reaches HBM. Mandatory at long context
continuous batching refill a slot the moment a sequence stops, checked every token. Pure scheduling, several times the throughput
chunked prefill slice a long prefill and interleave it with decode steps. Slightly worse worst-case TTFT, dramatically better stability
disaggregation separate GPU pools for the two phases. No interference, independent scaling, different cards possible — at real complexity cost
the question to ask first what is the prompt and output length distribution? Chat is 95% decode; classification is 75% prefill; they want opposite configurations

The ninety-second version

“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 times a 2k one rather than sixteen. Decode is fixed by batching, but the batch is capped by cache memory — and at high concurrency the cache read itself becomes most of the bandwidth, which is a second bottleneck people do not expect. The two phases also fight: one long prefill stalls every other user’s stream unless it is chunked.”

Where this connects

Thread started herePicked up in
The twelve stages these two phases sit inside 03 · Journey of a token
The ridge point, and where the batch stops being free 04 · The GPU and the roofline
The cache that prefill fills and decode re-reads, in full 06 · The KV cache
Shrinking the cache read at source 07 · Attention variants
Making prefill disappear when the prefix repeats 10 · Paging and prefix reuse
Continuous batching and chunked prefill as engine features 12 · Serving engines
Getting more than one token out of a decode pass 13 · The decode loop
Disaggregation, and the throughput-latency curve as a product decision 15 · Production

Questions to ask them