localaiforum
Log in
Guide โ€บ Fundamentals

Prefill vs decode: how generation runs

Press Enter and generation runs in two phases with completely different physics. Prefill processes your entire prompt in one parallel pass โ€” compute-heavy, fast per token, but you wait for all of it before the first word appears. Decode then emits the answer one token at a time โ€” every single step re-reads essentially all model weights from memory to produce one token. "First token slow, then it streams" is not a quirk of your setup; it is these two phases.

Phase 1: prefill โ€” the prompt

  • All prompt tokens go through the model simultaneously: big parallel matrix multiplies, exactly what GPUs are built for. Utilization is high; tokens per second are high.
  • You feel it as time-to-first-token (TTFT): the silence after you hit Enter.
  • The pass also fills the KV cache with the key/value vectors of every prompt token โ€” the scratchpad decode will read.
  • Longer prompts, linearly longer TTFT: big system prompts, RAG documents, and chat history all prefill every time unless your engine caches prefixes.

Phase 2: decode โ€” the answer

  • One token per forward pass. That pass needs every weight; producing a single token means streaming the entire model from VRAM.
  • The ceiling is memory bandwidth, not compute: roughly decode tok/s โ‰ˆ effective bandwidth รท bytes of weights. 8 GB of weights on a card with ~400 GB/s effective โ‰ˆ 50 tok/s ceiling.
  • This is why quantization speeds up decode: fewer bits per weight means fewer bytes to stream each step (see speed).

Why the asymmetry: prefill processes thousands of tokens per weight-read (huge matrices, arithmetic intensity high โ€” compute-bound), decode processes exactly one (every byte of weights fetched for a single token โ€” bandwidth-bound). Same weights, opposite bottlenecks.

total_time โ‰ˆ prompt_tokens / prefill_tok_s  +  output_tokens / decode_tok_s

Worked example

prompt:  1,000 tokens    prefill 2,000 tok/s โ†’ 0.5 s   (TTFT)
output:    300 tokens    decode    40 tok/s โ†’ 7.5 s
total โ‰ˆ 8.0 s โ€” 94% of wall-clock is decode
  • The first half-second processed 1,000 tokens; the next 7.5 seconds produce 300. Same model, same GPU, 50ร— difference โ€” different phase, not different effort.
  • 40 tok/s is roughly 160 English characters per second (โ‰ˆ4 chars/token): faster than anyone types, which is why streaming reads comfortably (tokens).
  • The same 40 tok/s in Korean is roughly 40โ€“60 chars/s: identical token rate, less text per token.
  • Output length is a decode-side dial: max_tokens caps how long generation runs, stop sequences end it early. Both change wall-clock linearly (engine parameters).
  • Streaming changes perception, not physics: you watch decode happen instead of waiting for it, at the same 40 tok/s.

Worked example 2: the RAG case

prompt: 12,000 tokens (system + 20 retrieved chunks)  prefill 1,500 tok/s โ†’ 8.0 s
output:   500 tokens                                    decode    40 tok/s โ†’ 12.5 s
total โ‰ˆ 20.5 s โ€” TTFT alone is 39% of the wait

This is the shape of retrieval-heavy local setups: fat prompts mean the model "thinks" for seconds before typing. Cures, in order of leverage:

  • Retrieve less โ€” top 3โ€“5 chunks usually beat top 20 (RAG).
  • Keep the system prompt stable so the engine can reuse a cached prefix.
  • Trim history: old turns can be summarized instead of replayed in full.

The KV cache: the growing scratchpad

Without a cache, generating token N would recompute attention over all Nโˆ’1 earlier tokens from scratch โ€” every step redoing identical work, total cost quadratic in length. Instead:

  1. Prefill computes keys and values for all prompt tokens and stores them once.
  2. Each decode step computes only the new token's query/key/value and appends its K/V to the cache.
  3. Attention for the new token is its query dotted against all stored keys.
  4. One decoded token, in order: embed it, run the layers reading the cache, project to vocab, sample โ€” append its K/V, repeat.

Now each decode step is linear in context length (read the cache) instead of quadratic (recompute everything). The price is the cache itself, which grows with every token:

KV bytes โ‰ˆ 2 ร— layers ร— kv_heads ร— head_dim ร— bytes_per_value ร— context

8B-class model (32 layers, 8 KV heads, 128 head dim, FP16):
  2 ร— 32 ร— 8 ร— 128 ร— 2 B โ‰ˆ 128 KiB per token
  โ†’ ~2.6 GB at 20k context, ~17 GB at 128k context

That cache competes with weights for VRAM โ€” long-context chats crash boxes that run the same model fine with short prompts (KV cache, VRAM math).

Batching changes the economics

Reading 8 GB of weights to emit one token is wasteful; the same reading can serve many sequences at once. Batch 8 requests and one weight-stream produces 8 tokens โ€” throughput climbs near-linearly until compute saturates. This is the entire trick behind serving (serving APIs, concurrency). Solo on a laptop you pay full freight per token; servers amortize it across simultaneous users.

Reading the meters

Meter Phase Main driver
TTFT prefill prompt length, compute, prefix cache hits
Prompt eval tok/s prefill compute, batch size
Generation tok/s decode memory bandwidth, weight bytes, batch
Throughput (req/s) both batch size, prefix cache reuse, parallel slots
Context memory both KV bytes per token, cache quantization

Measure before tuning: every serious engine logs prompt-eval and generation speeds per request. One minute of logs tells you which phase eats your latency; guessing does not.

Tuning follows the phase: shorter prompts and prefix caching fix TTFT; quantization and smaller weights fix decode (engine parameters, troubleshooting).

Related: speed ยท KV cache ยท concurrency ยท tokens