Your LLM Is Two Different Machines

Prefill and decode have opposite performance characteristics, and almost everything confusing about LLM latency, cost and context limits falls out of that one split.

Here is a question with a genuinely interesting answer: why do LLM APIs stream?

The convenient explanation is that it feels nicer. The real one is that the model cannot do anything else. Generating the hundredth token requires having generated the ninety-ninth, so the tokens arrive one at a time whether or not you display them that way. Streaming is not a UX choice layered on top of the model — it is the model’s execution shape leaking through the API.

That shape has a name. Inference runs in two phases, prefill and decode, and they behave so differently that treating them as one workload is the root of most confusion about LLM latency, cost and context limits.

Two phases, opposite bottlenecks

Prefill processes your entire prompt at once. Every input token is known upfront, so the model can push all of them through the network in parallel. NVIDIA describes it as “a matrix-matrix operation that’s highly parallelized,” and it “effectively saturate[s] GPU utilization.”1

Decode produces output tokens one at a time, each conditioned on everything before it. There is exactly one new token to process per step, so the same computation becomes “a matrix-vector operation that underutilizes the GPU compute ability compared to the prefill phase.”1

That is the whole asymmetry, and it has a sharp consequence. Prefill is compute-bound: the GPU’s arithmetic units are the constraint. Decode is memory-bandwidth-bound: the GPU spends most of its time waiting for weights and cached state to arrive from memory, with the arithmetic units mostly idle.1

Prefill runs once over the whole prompt; decode runs once per output tokenPREFILL — once, parallel, compute-boundwhole prompt at oncen tokens in, KV cache outDECODE — once per token, sequential, memory-boundt1t2t3t4t5t6t7time to first tokenper-token latencyWHAT LIMITS ITarithmetic unitsscales with prompt lengthmemory bandwidthscales with output length × model size
One request, two workloads. Prefill does a large parallel pass over the whole prompt and produces the first token; decode then runs one small sequential step per token. Time to first token is dominated by prefill; everything after it is decode.

Once you have this split, a pile of unrelated-looking observations collapse into one explanation. A long prompt delays the first token but not the ones after it. A long output costs time roughly linearly, regardless of prompt size. Batching helps throughput enormously but makes any single request slower to start. All three are the same fact seen from different angles.

The KV cache is the thing that actually fills up

Decode is memory-bound partly because of the weights, and increasingly because of the KV cache.

Attention needs the key and value vectors for every previous token. Recomputing them at each step would be quadratic and absurd, so they are computed once and kept. NVIDIA gives the size directly:1

KV cache bytes per token = 2 × num_layers × (num_heads × dim_head) × precision_in_bytes

The leading 2 is the K and the V. Everything else is fixed by the architecture, which means the cache grows linearly with context length and there is nothing clever you can do about the slope — only the constant.

That constant is what grouped-query attention attacks. Multi-query attention collapses all key-value heads into one, which is fast but “can lead to quality degradation”; GQA uses “an intermediate (more than one, less than number of query heads) number of key-value heads” and reaches “quality close to multi-head attention with comparable speed to MQA.”2 It is not a minor tuning knob:

Show data table
Context length64 KV heads (MHA)8 KV heads (GQA)
1K2.50.31
4K101.25
16K405
64K16020
128K32040
KV cache for one sequence on a 70B-class model (80 layers, 128-dim heads, fp16), with 64 key-value heads versus 8. The 8× gap is constant — GQA moves the line down, it cannot flatten it. Computed from the formula above; a real deployment also holds the weights, roughly 140 GB at fp16.

This is the honest answer to “why is the context window that size and not bigger.” The window is not a number someone picked. It is where the KV cache stops fitting next to the weights at a batch size that still makes economic sense.

Why batching is not optional

If decode is bandwidth-bound, the fix follows mechanically. The expensive part of a decode step is reading the weights, and that read is the same whether you are generating one token or thirty-two.

One weight read produces one token at batch 1, or many tokens at batch 32BATCH = 1read ~140 GBof weights1token per readBATCH = 32read ~140 GBof weights — unchanged…32 tokens per readThroughput scales with batch size. Each individual request waits longer to be scheduled.
A decode step reads the entire weight set from memory regardless of how many sequences are in flight. Batching does not make the read cheaper — it makes the read produce more tokens. This is why serving throughput and single-request latency pull in opposite directions.

The catch is that batching in the obvious way — collect n requests, run them together, return them together — wastes most of the benefit, because sequences finish at different times and the whole batch waits for the slowest. Production servers use continuous batching instead, admitting and retiring sequences at the granularity of a single decode step.

Doing that requires solving a memory problem. Every sequence’s KV cache grows unpredictably, so reserving a contiguous block per sequence means reserving for the worst case and wasting the rest. vLLM’s PagedAttention borrows virtual memory paging: the KV cache lives in fixed-size blocks that need not be contiguous. The paper reports “near-zero waste in KV cache memory” and 2–4× throughput at the same latency versus the prior state of the art.3

flowchart LR
R["Request<br/>arrives"] --> C{"Prompt prefix<br/>already cached?"}
C -- "Yes" --> S["Skip prefill<br/>for that prefix"]
C -- "No" --> P["Prefill<br/>compute-bound"]
S --> Q["Scheduler<br/>continuous batching"]
P --> Q
Q --> D["Decode step<br/>memory-bound"]
D --> K["Paged KV cache"]
K --> D
D --> V["Speculative decoding<br/>draft + verify"]
V --> O["Tokens<br/>stream out"]
Where each optimisation sits. Everything on the left attacks prefill; everything on the right attacks decode. They are largely independent, which is why a stack can adopt them one at a time.

When prefill and decode fight

Continuous batching creates a scheduling problem the two-phase split makes inevitable. A decode step is tiny; a prefill for a 100K-token prompt is enormous. Put them on the same device and the long prefill occupies the GPU while every in-flight generation stalls — one arriving request adds a visible stutter to everyone else’s stream.

Chunked prefill is the fix: split a large prefill into pieces and batch those pieces alongside decode steps, so generation keeps advancing. vLLM prioritises decode requests and batches pending decodes before scheduling prefill work.4

It is a redistribution, not a free win, and the direction is exactly what you would predict. Smaller chunks protect inter-token latency for sequences already generating, at the cost of time to first token for the request being admitted; larger chunks do the reverse.4 The knob does not remove the conflict between the two phases — it just decides which request absorbs it.

Speculative decoding attacks the same bottleneck from another direction. A small draft model proposes several tokens; the large model verifies them in a single parallel pass — which it can do cheaply, because verification looks like prefill rather than decode. The original paper reports 2–3× acceleration “with identical outputs,” and this matters more than the speedup: the sampling scheme is constructed so the output distribution is unchanged.5 You are not trading quality for latency. You are exploiting the fact that the GPU was idle anyway.

Prompt caching: the part you actually control

Everything above happens inside someone else’s serving stack. Prompt caching is the one lever exposed to you, and it makes sense only in terms of prefill: if a prefix has been prefilled before, its KV cache can be reused instead of recomputed.

The pricing shows how large the saving is. On Claude Opus 5, base input is $5.00/MTok, a 5-minute cache write is 1.25× that, a 1-hour write is 2×, and a cache read is 0.1× — $0.50/MTok.6

Show data table
Price per MTok
Base input$5.00
5-min cache write$6.25
1-hour cache write$10.00
Cache read$0.50
Published Claude Opus 5 input pricing per million tokens. A cache read costs a tenth of a fresh read, which is the price of skipping prefill. The write premium is paid once; every subsequent hit collects the discount.

The mechanism is a prefix match, and that word does the work. Caching is keyed on a cumulative hash of everything up to a breakpoint, rendered in the order tools → system → messages.6 Any byte that changes anywhere in the prefix changes the hash, and everything after it is recomputed.

This produces the single most common way caching silently fails: putting the breakpoint after something that varies.

# Broken — the timestamp changes every request, so the prefix hash does too.
# You pay the 1.25x write premium on every call and never get a hit.
system = [
    {"type": "text", "text": LONG_STABLE_CONTEXT},
    {"type": "text",
     "text": f"Current time: {datetime.now()}",
     "cache_control": {"type": "ephemeral"}},
]

# Correct — the breakpoint sits on the last block whose prefix is identical
# across requests. Volatile content moves after it, uncached.
system = [
    {"type": "text", "text": LONG_STABLE_CONTEXT,
     "cache_control": {"type": "ephemeral"}},
]
messages = [{"role": "user", "content": f"Current time: {datetime.now()}\n{query}"}]

Three further details decide whether caching works at all:

  • There is a minimum. Prefixes below the model’s threshold are not cached even when marked — 512 tokens on Claude Opus 5, up to 4,096 on other models.6 Below it, cache_control is silently a no-op.
  • You get four breakpoints. Automatic caching consumes one of them.6
  • The TTL runs from the start of the request, not the end. A response that streams for four minutes leaves about one minute of a five-minute window for the follow-up.6

Verification is one line and worth wiring into your logs permanently: if usage.cache_read_input_tokens is zero across requests that share a prefix, something is invalidating it.

What this costs you

Every technique above trades something. Being specific about what separates engineering from cargo-culting.

Throughput and latency are in tension, structurally. Larger batches mean more tokens per weight read and a longer wait for any given request to be scheduled. There is no batch size that optimises both, which is why serving stacks expose it as a knob rather than choosing for you — and why a p99 latency target and a cost-per-token target are negotiating with each other, not merely coexisting.

The advertised context window is not a promise about your batch. A million-token window is a statement about what the model can attend to, not about how many concurrent million-token sequences a deployment can hold. The KV cache is the binding constraint, and it is per sequence. Long-context and high-concurrency are the same budget spent twice.

Prefill and decode want different hardware. One is starved for arithmetic, the other for bandwidth. Running both on the same device means at least one is being under-served at any moment, and that they interleave badly under load — chunked prefill manages the collision but cannot remove it. This is why disaggregating the two phases onto separate pools is now a serious deployment pattern rather than an exotic one.

Speculative decoding is a bet, and losing costs real compute. When the draft model’s proposals are rejected, the verification pass is discarded work. On workloads where the draft model predicts poorly — unusual domains, heavy tool-call syntax, low-temperature outputs it was not trained on — acceptance rates fall and the technique can be net negative. It pays off on predictable text and degrades quietly on the rest.

Cache writes cost more than no caching at all. A 1.25× write that never gets a hit is a 25% surcharge, and a 2× one-hour write that never gets a hit is worse. Caching is only a saving if the prefix is genuinely reused within the TTL — so the traffic pattern, not the prompt size, decides whether it is worth enabling.

Takeaways

  • Prefill and decode are different workloads on the same hardware. One is compute-bound and parallel; the other is memory-bandwidth-bound and sequential. Almost every LLM performance question resolves to which one you are asking about.
  • Prompt length buys time to first token; output length buys everything after. They are separate budgets and should be optimised separately.
  • The KV cache grows linearly with context and cannot be made sublinear — GQA changes the constant, not the slope. It, not the advertised window, is what limits concurrency.
  • Batching is how decode is made economical, and it works precisely because the weight read is fixed cost. That is also why it raises per-request latency.
  • Prompt caching is prefill reuse, matched on an exact prefix. One varying byte early in the prompt turns the whole thing into a surcharge. Log cache_read_input_tokens and watch it.
  • Speculative decoding is free latency only when the draft model is right. Measure acceptance on your own traffic before assuming the published speedup.

References

Footnotes

  1. NVIDIA, Mastering LLM Techniques: Inference Optimization — prefill/decode characterisation and the KV cache size formula. 2 3 4

  2. Ainslie et al., GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints (arXiv:2305.13245).

  3. Kwon et al., Efficient Memory Management for Large Language Model Serving with PagedAttention (arXiv:2309.06180) — vLLM; near-zero KV cache waste and 2–4× throughput.

  4. vLLM, Optimization and tuning — chunked prefill, decode prioritisation and the ITL/TTFT tradeoff. 2

  5. Leviathan et al., Fast Inference from Transformers via Speculative Decoding (arXiv:2211.17192) — 2–3× with identical outputs.

  6. Anthropic, Prompt caching — pricing multipliers, minimum cacheable prefix, breakpoint limit, TTL semantics and prefix invalidation. 2 3 4 5