LLM Inference Is a Memory Bandwidth Problem
The path from prompt to token runs through matrix operations on an accelerator, and the surprise is which resource binds. Generating tokens one at a time reads the entire model from memory per token, so inference is usually bandwidth-bound rather than compute-bound — which is why model size and memory bandwidth dominate the conversation.
Software view, hardware view
The gap between what you wrote and what the machine does is where this whole domain lives.
The path, end to end
A request arrives over the network, is tokenized, and becomes a sequence of vectors. Those pass through the model's layers as a series of matrix operations executed on an accelerator, with the weights living in the device's high-bandwidth memory. The output is a distribution over the vocabulary, one token is chosen, appended, and the process repeats for the next token.
The repetition is the crucial structural fact. Generating a hundred tokens is not one pass through the model; it is a hundred passes, each of which needs the weights again. Since the weights do not fit in any on-chip memory, each pass reads them from device memory afresh.
That makes the arithmetic stark. For a single sequence, generating one token performs a modest amount of arithmetic against every weight in the model — which is an extremely low arithmetic intensity, exactly the profile When the Memory Bus Is the Bottleneck describes. The accelerator has enormous arithmetic capacity and spends most of it waiting for weights to arrive.
Prefill and decode are different problems
Processing the prompt — prefill — handles all input tokens simultaneously. That is a wide matrix multiplication with high arithmetic intensity, the shape accelerators are built for, and it is typically compute-bound. It is also why time to first token scales with prompt length in a way that feels like real work, because it is.
Generating each output token — decode — processes a single position. The matrix operations become narrow, arithmetic intensity collapses, and the same full sweep of weights is required. Decode is therefore bandwidth-bound for a single sequence, and per-token latency is governed roughly by how long it takes to stream the model's weights out of memory.
This explains a set of otherwise puzzling observations directly: a long prompt costs a lot up front but subsequent tokens arrive at a steady rate; a model twice the size roughly halves the token rate even on hardware with plenty of spare arithmetic; and running one request at a time wastes most of the accelerator. It also explains why Inside One Model Call: Queue, First Token, Generation separates time-to-first-token from inter-token latency — they are two different hardware regimes, not two parts of one.
| Prefill (prompt) | Decode (each output token) | |
|---|---|---|
| Work shape | All prompt tokens at once — wide | One position at a time — narrow |
| Arithmetic intensity | High | Very low: full weight sweep for little arithmetic |
| Usual bottleneck | Compute | Memory bandwidth |
| Scales with | Prompt length | Output length × model size |
| User-visible as | Time to first token | Inter-token latency / tokens per second |
| What helps | More arithmetic throughput; better parallelism | Fewer bytes: smaller model, lower precision, batching |
| What does not help | Batching, mostly — it is already wide | More arithmetic capacity; it is already idle |
Batching: why serving is cheaper than chatting
If decode is bandwidth-bound because one weight read serves one sequence, the fix follows immediately: make one weight read serve many sequences. Batching several requests so their decode steps proceed together reads the weights once and applies them to every sequence in the batch, so arithmetic intensity rises with batch size and the accelerator moves back towards being compute-bound.
This is why the economics of serving many concurrent users differ so sharply from running a model for one user. The marginal cost of an additional sequence in an existing batch is small, because the dominant cost — streaming the weights — was already paid. It is also why a locally-run model on a single machine gets a small fraction of the tokens per second that a served deployment achieves with the same hardware.
The trade is latency against throughput, the same shape as Throughput Improved, Latency Did Not. Larger batches use the hardware better and increase total tokens per second, while any individual request may wait to be batched and shares the device with others. Continuous batching — adding and removing sequences as they arrive and finish rather than waiting for a fixed batch — is the standard way of getting most of the throughput without most of the latency penalty.
Key points
- Inference is two phases: prefill is wide and compute-bound, decode is narrow and bandwidth-bound.
- Decode reads every model weight from memory for every generated token, giving very low arithmetic intensity.
- Per-token latency for a single sequence is governed by how fast the weights can be streamed, not by arithmetic.
- Batching makes one weight read serve many sequences, which is why serving many users is far cheaper per token.
- The two phases need opposite fixes; optimising the wrong one produces no improvement at all.
Where the Data Is
Change an input and watch which number moves — and which one refuses to.
The exact ratios vary by machine and the absolute times vary far more, which is why none are shown. What is stable enough to build intuition on is the shape: each level is several times the one above, and the gap between the last cache level and memory is the one that decides most program performance.
Follow the mechanism
The path through the machine, hop by hop — and the conclusions it invites that are wrong.
- 1Prompt → tokenizer: text becomes token ids, then vectors the model can operate on.
- 2Runtime → accelerator: layers become matrix operations dispatched to the device.
- 3Accelerator → device memory: weights are read for the current step; for decode this is the full model per token.
- 4Device memory → accelerator: the bandwidth of this path sets the token rate whenever the batch is small.
- 5Output token → runtime: the token is appended to the sequence and the whole sweep repeats for the next one.
- • "The accelerator shows low utilisation, so it is oversized" — during decode it is bandwidth-starved; the arithmetic units genuinely have nothing to do.
- • "A faster accelerator will speed up generation" — only if it also has more memory bandwidth, which is the binding resource.
- • "Batching adds latency, so avoid it" — it adds queueing latency and multiplies throughput; for serving, that is usually the correct trade.
- • "Prompt processing and generation are the same work" — they are different shapes with different bottlenecks and different fixes.
Consequences, controls and cost
- • Token rate falls roughly in proportion to model size, even when arithmetic capacity is plentiful.
- • Single-user local inference achieves a small fraction of the throughput the same hardware reaches when serving.
- • Time to first token and inter-token latency respond to different optimisations and must be measured separately.
- • Memory capacity, not arithmetic, is frequently what decides whether a model can be served at all.
- • Batch concurrent requests — continuous batching where possible — so one weight read serves many sequences.
- • Reduce bytes per weight through quantization, which directly raises the token rate in the decode phase.
- • Keep the model resident on the device and avoid any per-request weight movement.
- • Measure prefill and decode separately so effort goes to whichever phase actually dominates your traffic.
- • Report time to first token and inter-token latency separately; a single average conflates the two regimes.
- • Compare achieved memory bandwidth against device peak during decode — near peak confirms bandwidth-bound.
- • Sweep batch size and plot tokens per second against per-request latency to find the operating point you want.
- • Track device memory occupancy of weights versus KV cache, since the latter grows with context and concurrency.
- • Batching raises throughput and per-request latency at the same time; the balance is a product decision.
- • Quantization reduces bytes moved at some cost in output quality, and the cost is workload-dependent.
- • Keeping large models resident consumes device memory that could otherwise hold KV cache for more concurrent sequences.
Scope
§224 — what these claims are specific to.
- GPU-SPECIFICDescribes transformer inference on accelerators with separate high-bandwidth device memory; architectures with different memory topologies, and non-transformer models, shift the balance.
- SIMPLIFIEDOmits attention's own scaling with context length, KV cache growth, speculative decoding, mixture-of-experts routing and multi-device sharding — each of which changes where the bottleneck sits.
Misconceptions
Where the rest of this lives
Runtimes fuse adjacent operations to avoid writing intermediate activations back to device memory, which is a compiler technique applied directly to the bandwidth problem described here.