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.

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

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.

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