FDEInterviews logoFDE/Interviews
🧠 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.

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

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.

Prefill vs decode

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.

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
PRACTICE THIS IN REAL QUESTIONS