GO TO A SECTION

↑ ↓ to navigate · Enter to openExplore all sections →
FDEInterviews logo
← 🧠 Foundations of LLMs & GenAI
Foundational

Autoregressive Decoding

LLMs generate one token at a time: each step feeds the whole sequence back in to predict the next token, which is why generation is sequential and cannot be parallelized the way a forward pass over a known prompt can. This splits inference into a parallel prefill phase and a sequential, memory-bound decode phase, and it is why the KV cache exists and why long outputs cost what they cost.

WATCH THE EXPLAINER3:22
Transcript

Here's the follow-up interviewers hold in reserve. Your time to first token is fine, but tokens per second is poor. Why? To answer it, you need one fact: a language model doesn't write a sentence in one shot.

It's trained to do one thing: given a sequence, output a probability distribution over the next token. Generation just calls that in a loop. Pick a token, glue it onto the end, and feed the whole thing back in, over and over, until it emits a stop token or hits the cap. That's what autoregressive means: each output becomes part of the next input.

Here's the consequence that trips people up. You can't generate token 50 until you've generated token 49, because token 49 is part of the context that produces it. There's no way to parallelize across output positions. Training is the opposite: the whole target is known up front, so every position is scored in one parallel pass, behind a causal mask.

So without help, every turn of the loop would reprocess the whole growing sequence. Generating a thousand tokens that way processes one plus two, all the way up to a thousand: 500,500 token-positions of work. With a cache, it's 1,000. At each step the model needs the keys and values of every earlier token, and those never change once a token is emitted. So you compute them once and store them. The cache turns quadratic into linear.

You can see it in the loop itself. The first call gets the whole prompt. Once the cache exists, only the newest token goes in. The price is memory: the cache grows with sequence length times batch size, and that growth is the real serving constraint.

This splits inference into two phases. Prefill processes your whole prompt once, all tokens in parallel, so the GPU's matrix units stay busy. It's compute-bound. Decode emits tokens one by one, and each step must read the entire model's weights and the whole KV cache from memory, and because the math is tiny next to the bytes moved, the GPU sits waiting on memory.

Put numbers on it for a 13-billion-parameter model. Prefill of a 2,000-token prompt finishes in roughly 130 milliseconds. Each decode step reads 26 gigabytes of weights, about 7.8 milliseconds at 3.35 terabytes a second, so a 500-token reply spends about 3.9 seconds in decode. These are order-of-magnitude figures, and real ones depend on the card and the kernels.

That's the interview answer. Time to first token is the prefill number, and it's bound by compute. Tokens per second is the decode number, bound by memory bandwidth. So they have different fixes, and it's why continuous batching matters: it groups many requests' decode steps, so each read of the model serves all of them.

So when the reply is slow and the first token isn't, say it plainly: generation is a sequential loop, and decode is memory-bound. Fix the two numbers separately. The loop diagram and the full calculation are on fdeinterviews.com.

TL;DR: A language model does not write a sentence in one shot. It predicts one token, appends it to the input, and runs again to predict the next, looping until it emits a stop token. That loop is inherently sequential, which makes decode the slow, memory-bound half of inference (prefill, where it reads your prompt, runs in parallel). The KV cache exists precisely to keep each step of that loop cheap.

What "autoregressive" actually means

The model is trained to do one thing: given a sequence of tokens, output a probability distribution over the next token. Generation is just calling that function in a loop. You feed the prompt, get a distribution over the vocabulary, pick a token, glue it onto the end of the sequence, and feed the whole thing back in. "Autoregressive" means each output becomes part of the next input.

The consequence that trips people up: you cannot generate token 50 until you have generated token 49, because token 49 is part of the context that produces token 50. In standard decoding there is no way to parallelize across output positions. This is the opposite of training, where the entire target sequence is known up front and every position is scored in one parallel pass using a causal mask.

python
def generate(model, prompt_ids, max_new=128, eos_id=2):
    ids = list(prompt_ids)
    cache = None                          # KV cache, empty at first
    for _ in range(max_new):
        logits, cache = model(ids[-1:] if cache else ids, cache)
        next_id = sample(logits[-1])      # greedy, temperature, or top-p
        ids.append(next_id)
        if next_id == eos_id:
            break
    return ids

That cache argument is the whole game. Without it, every iteration would re-process the entire growing sequence from scratch. In a toy example starting with a one-token prompt, generating 1,000 tokens without a cache processes 1 + 2 + ... + 1,000 tokens, which is 500,500 token-positions of work, against 1,000 with one (computed). These totals include the initial prompt pass and count processed input positions. A longer prompt changes both totals; they are not counts of attention operations or elapsed time. Caching makes the count of newly processed positions linear in output length, which is why the loop above passes only ids[-1:] once the cache exists.

Prefill vs decode

One token at a time, and why that is slow once per token 1 Your prompt read in one parallel pass 2 Distribution over vocab a score for every token 3 Pick one token the sampler decides, not the model 4 Append it the output becomes the input 5 Feed it all back minus what the cache holds 6 Stop token, or the cap the only way out Prefill reads the whole prompt at once, which is why it scales with length but not with time. Everything below this line is the part that cannot. This is what autoregressive means, and it is the whole constraint: token 50 cannot be produced until token 49 exists, because token 49 is part of the context that produces it. Without a cache every turn would reprocess the entire growing sequence from scratch. The KV cache is what makes each turn of this loop cheap rather than quadratically more expensive than the last. Training is the opposite shape: the whole target is known up front and every position is scored in one parallel pass behind a causal mask.

The dashed arrow is the constraint the whole page is about. Everything above it happens once; everything inside it happens again for every single token the model writes.

Inference splits into two phases with very different performance profiles.

PhaseWhat it doesParallelismBottleneck
PrefillProcess the full prompt onceAll prompt tokens in parallelCompute (GEMMs are large)
DecodeEmit output tokens one by oneNone across positionsMemory bandwidth

Prefill is compute-bound: a 2,000-token prompt is processed in a single parallel forward pass, so the GPU's matrix units stay busy and it scales well with prompt length. Decode is memory-bound: each step computes one token's worth of math but must read the entire model's weights and the whole KV cache from memory to do it. The arithmetic is tiny relative to the bytes moved, so the GPU sits waiting on memory rather than maxing out its FLOPs. This is why a long output feels slow even on a fast card, and why throughput tricks like continuous batching matter: you batch many requests' decode steps together to amortize the weight reads. Put numbers on the two phases for a 13-billion-parameter model. Prefill of a 2,000-token prompt is about 52 trillion floating-point operations, which a modern accelerator sustaining 400 trillion a second finishes in roughly 130 ms, all positions at once. Each decode step then reads the model's 26 GB of fp16 weights, which at a listed bandwidth of about 3.35 TB/s takes 7.8 ms, so a 500-token reply spends about 3.9 seconds in decode against 0.13 in prefill (computed; both rates are order-of-magnitude figures and the real numbers depend on the card and the kernels). Time-to-first-token is the prefill number; tokens per second is the decode number; and that is why the two have different fixes.

The KV cache, in one breath

At step t the model needs to attend over the keys and values of every earlier token. Those vectors never change once a token is emitted, so you compute them once and store them. Each new step then computes only the new token's query and attends over the cached keys and values, turning per-token cost from "recompute the whole history" into "compute one token, read the cache." The price is memory: the cache grows with sequence length times batch size, and that growth is the real serving constraint. See kv-cache.

How the next token gets picked

The model hands you logits; the decoding strategy turns them into a choice.

  • Greedy: take the argmax every step. Deterministic, fast, and prone to dull or repetitive text.
  • Sampling (temperature, top-k, top-p): draw from the distribution, optionally sharpened or flattened by temperature and truncated to the top tokens. This is the default for open-ended generation. See temperature-and-sampling.
  • Beam search: keep the B highest-probability partial sequences and expand all of them each step. Good for short, high-precision outputs like translation; rarely used for chat because it produces bland, "safe" text and multiplies the already-sequential cost by B.

A subtle point interviewers like: the strategy only changes which token you pick, not how the model runs. The forward pass and the cache are identical whether you decode greedily or sample.

Why interviewers probe this

It is the cleanest test of whether you understand why LLM serving is hard. A candidate who only knows "it predicts the next token" misses that the prediction loop is sequential and memory-bound, which is the root cause of decode latency, the reason for the KV cache, and the motivation behind continuous batching and speculative decoding. The follow-up they hold in reserve: "your time-to-first-token is fine but tokens-per-second is poor, why?" The answer is that TTFT is dominated by prefill (compute) and per-token speed is dominated by decode (memory bandwidth), so they have different fixes.

Common misconceptions

  • "The model generates the whole response at once." It generates one token per forward pass, in a loop.
  • "Prefill and decode are the same speed per token." Prefill is parallel and compute-bound; decode is sequential and memory-bound. They scale differently.
  • "Sampling is slower than greedy." The forward pass dominates; choosing the token is negligible either way.
  • "Beam search is the best default." For chat it tends to produce bland text and multiplies cost; sampling with top-p is the usual choice.

Key takeaways

  • Generation is a sequential loop: predict a token, append it, repeat until a stop token. Output positions cannot be parallelized.
  • Inference splits into parallel, compute-bound prefill and sequential, memory-bound decode; they have different bottlenecks and different fixes.
  • The KV cache makes each decode step cheap by storing past keys and values, at the cost of memory that grows with context times batch.
  • Greedy, sampling, and beam search only change token selection, not how the model runs.

Related concepts

Practise this in questions