ml-models / Generative modeling / Autoregressive models core

Autoregressive models

An autoregressive model writes one piece at a time (a word, a pixel, an audio sample), each time asking "given everything so far, what comes next?". The chain rule of probability says this loses nothing: any distribution over sequences can be written that way. This page works the chain rule on an exact toy distribution, trains a tiny model by teacher forcing, shows why a model fed its own mistakes drifts, and builds the causal convolutions behind WaveNet and PixelCNN.

On this page
  1. Problem
  2. Intuition
  3. Mechanics
  4. The chain rule factorization
  5. Maximum likelihood by teacher forcing
  6. Exposure bias
  7. Causal convolutions: WaveNet
  8. Masked convolutions: PixelCNN
  9. Language models: the main case
  10. Worked example
  11. The chain rule, exactly
  12. Teacher forcing on a toy corpus
  13. Errors compound when the teacher leaves
  14. A causal stack, checked
  15. Implementation
  16. Tradeoffs
  17. Check yourself
  18. Connections
  19. Read first
  20. Read next
  21. References

Problem

We want a model of a whole sequence x = (x1, …, xT): a sentence, an image read pixel by pixel, a second of audio. We want to assign it a probability (to score data and train by cross-entropy) and to draw new samples from it. A direct table of p(x) is hopeless. With a vocabulary of V tokens there are VT possible sequences; for a 50,000-token vocabulary and just three tokens that is already 125,000,000,000,000 entries. We need a way to break the joint probability into pieces a neural network can output, without assuming the pieces are independent.

Intuition

Read a sentence left to right and guess each next word. If you can say how likely every possible next word is, given the words so far, you have described the whole sentence's probability: multiply your guesses' probabilities together. Generating is the same game played forward: draw a word, write it down, and guess again with that word now part of the past. The model only ever answers one question, "what comes next?", but it answers it with the whole past in view.

Training with teacher forcing versus generating Top row, training: the true tokens start, the, cat, ran are the inputs and the model predicts the shifted targets the, cat, ran, full stop, all positions at once, each prediction compared to the true next token by cross-entropy. Bottom row, generation: the model predicts one token, it is drawn, appended to the input, and fed back for the next step, so the model now conditions on its own outputs. training (teacher forcing): all positions in parallel <s> → the the → cat cat → ran ran → . targets true inputs generation: one step at a time, output fed back prefix so far p(next | prefix) draw a token append

Training sees only true prefixes; generation sees the model's own. That gap is exposure bias.

Mechanics

The chain rule factorization

For any joint distribution, by the definition of conditional probability applied repeatedly,

p(x1, …, xT) = p(x1) · p(x2 | x1) · … · p(xT | x1, …, xT−1) = Πt p(xt | x<t).

This is an identity, not an approximation: an autoregressive model that represented every conditional exactly would represent the joint exactly. The approximations come from the model: a Markov model keeps only the last k tokens of the past; an RNN squeezes the past into a fixed-size state; a Transformer with a causal mask reads the whole past (up to its context length) and shares one set of weights across all positions. Any order works in principle (right to left, or a scan through image pixels), but the model must fix one. MADE builds the same factorization into a single feed-forward autoencoder by masking its weights so each output only sees earlier inputs.

The log of the product is a sum, so the log-likelihood of a sequence is Σt log p(xt | x<t): one classification loss per position. Sampling is ancestral: draw x1, then x2 given it, and so on. Scoring a sequence takes one parallel pass; sampling takes T sequential passes.

Maximum likelihood by teacher forcing

Training maximises the log-likelihood of the data, which is the same as minimising the summed cross-entropy between the model's next-token distribution and the true next token. During training the model is fed the true prefix at every position, never its own guesses. That is teacher forcing. It makes training parallel: the input is the sequence, the target is the same sequence shifted one step left, and with a causal model every position's loss comes out of one forward pass. For a model that is just a table of conditional probabilities, the maximum-likelihood solution is the count ratio: p(b | a) = count(a b) / count(a ·). The worked example checks that the gradient of the loss is zero there.

Exposure bias

At generation time there is no teacher. The model conditions on what it wrote, and if it wrote something unusual it is now in a prefix it never saw in training. Errors can compound: one bad token makes the next prediction worse. This mismatch between training on true prefixes and running on generated ones is called exposure bias. Remedies include scheduled sampling (gradually feeding the model its own predictions during training), sequence-level training objectives, and in practice simply much more data and capacity, so that the model's own outputs stay close to the data distribution. Decoding rules (see decoding and sampling) also matter: truncating the long tail keeps the model away from prefixes it handles badly.

Causal convolutions: WaveNet

A convolution can be autoregressive if output t only reads inputs at t and earlier. PyTorch's nn.Conv1d pads both sides by padding, so it is not causal by itself. The usual trick is to pad only the left with F.pad(x, (dilation·(k−1), 0)) and use padding=0. Stacking causal layers grows the receptive field, the window of past inputs an output can see, by (k−1)·d per layer for kernel size k and dilation d. With dilations 1, 2, 4, … the receptive field doubles per layer instead of growing by one. WaveNet used kernel size 2 with dilations 1, 2, …, 512 and repeated that block, so one block sees 1024 samples.

A stack of dilated causal convolutions Four rows of eight positions: the input at the bottom, then layers with dilation 1, 2 and 4. Each output connects to itself and one position to its left at the layer's dilation, never to the right. The top-right output depends on all eight inputs: the receptive field is 8. input 1 2 3 4 5 6 7 8 dilation 1 0.5 1.5 2.5 3.5 4.5 5.5 6.5 7.5 dilation 2 0.25 0.75 1.5 2.5 3.5 4.5 5.5 6.5 dilation 4 0.125 0.375 0.75 1.25 1.875 2.625 3.5 4.5

Kernel [0.5, 0.5] at every layer; each node shows its computed value. Missing left neighbours read the zero padding.

Masked convolutions: PixelCNN

For images, fix a raster order (rows top to bottom, left to right in a row) and predict each pixel from the ones before it. PixelCNN keeps a normal k×k convolution but multiplies its kernel by a mask that zeroes every tap at or after the centre in raster order. The first layer uses mask A, which also zeroes the centre, so a pixel never sees itself. Later layers use mask B, which keeps the centre, because the centre position there holds features computed only from earlier pixels. (With colour, the paper also orders R, G, B within a pixel.) A 5×5 mask A has 12 live taps and mask B has 13.

Stacked masked 3×3 layers leave a blind spot: some earlier pixels up and to the right never reach the output, however many layers you stack, because each layer's receptive field only grows up-left and sideways in a way that misses a wedge. Gated PixelCNN fixed this by splitting the network into a vertical stack (rows above) and a horizontal stack (the current row to the left).

PixelCNN masks and the blind spot Left: a 5 by 5 mask A with 12 live taps, the rows above the centre and the cells to its left. Middle: mask B, the same plus the centre, 13 taps. Right: a 7 by 7 image after three masked 3 by 3 layers, one mask A and two mask B. The target pixel depends on 21 earlier pixels, but 3 earlier pixels up and to its right are blind: they come before it in raster order yet never affect it. Pixels after it are future and must not be seen. mask A centre mask B centre 3 layers on a 7×7 image ? ? ? T solid = tap used / pixel seen, dashed = zeroed / future or out of range T = target pixel, ? = blind spot (earlier in raster order but never seen)

Language models: the main case

A modern decoder-only language model is an autoregressive model over tokens: a causal Transformer outputs logits for p(xt | x<t) at every position, trained with teacher forcing and cross-entropy on shifted targets, and sampled one token at a time. The KV cache exists because generation is sequential: it saves the past's keys and values so each new step does not recompute them.

ComponentShape/ParamsMeaningSource
Categorical(logits=…) logits [B, T, V] One next-token distribution per position; logits are normalised with logsumexp. Categorical.__init__
Categorical.log_prob value [B, T] → [B, T] log p(xt | x<t) by gathering the target's log-probability; sum over T for log p(x). lines 151-157
Categorical.sample [B, V] → [B] One ancestral-sampling step (calls torch.multinomial). lines 144-149
nn.CrossEntropyLoss input [N, V], target [N]; ignore_index=-100 Teacher-forced loss on shifted targets; padding positions set to −100 are skipped. loss.py
nn.Conv1d [B, C, T]; kernel_size, dilation, padding Pads both sides symmetrically, so use padding=0 and pad the left by hand for causality. Conv1d
F.pad(x, (left, right)) pad tuple starts with the last dimension (dilation·(k−1), 0) left-pads time by exactly the receptive-field reach. functional.pad
nn.Conv2d + mask weight [Cout, Cin, k, k] PixelCNN multiplies weight by a fixed 0/1 mask before each forward. Conv2d
torch.triu [T, T], diagonal=1 The causal mask for attention: positions above the diagonal (the future) are blocked. _torch_docs.py

Worked example

The chain rule, exactly

Take a distribution over three bits with these probabilities: 000 = 0.3, 001 = 0.05, 010 = 0.05, 011 = 0.1, 100 = 0.05, 101 = 0.05, 110 = 0.1, 111 = 0.3. Each row factors it into first-bit, second-given-first and third-given-both probabilities, and the product rebuilds the joint exactly.

Sequencep(x1)p(x2|x1)p(x3|x1x2)ProductSampled (1000 draws)
0000.50000.70000.85710.30000.311
0010.50000.70000.14290.05000.051
0100.50000.30000.33330.05000.054
0110.50000.30000.66670.10000.105
1000.50000.20000.50000.05000.057
1010.50000.20000.50000.05000.049
1100.50000.80000.25000.10000.091
1110.50000.80000.75000.30000.282

The last column draws 1000 sequences by ancestral sampling (bit by bit from the conditionals, seed 0); the frequencies land near the joint. A first-order Markov model, which drops x1 from the last factor, gives 000 probability 0.2722 instead of 0.3 and 101 0.0222 instead of 0.05; the KL divergence from the true joint is 0.0395 bits. Free parameters: the full joint needs 7, the chain rule's conditionals also 7 (the same distribution, re-expressed) and the Markov model 5. With V = 50,000 the joint and full conditionals need 124,999,999,999,999 each and the Markov model 4,999,949,999. Neural models escape this by sharing weights across all prefixes.

Teacher forcing on a toy corpus

Corpus: "the cat sat .", "the cat ran .", "the dog sat .". The maximum-likelihood bigram model is the table of count ratios, e.g. p(cat | the) = 0.6667. Teacher forcing scores "the cat ran ." from the true prefixes:

InputTargetp(target | input)−log p
<s>the1.00000.0000
thecat0.66670.4055
catran0.50000.6931
ran.1.00000.0000

Total negative log-likelihood 1.0986 nats (that is ln 3), mean 0.2747 per token, perplexity 1.316. Treating each row of the table as a softmax over logits, the gradient of the corpus loss with respect to every logit is p − 1[target], summed over occurrences; at the count ratios its largest magnitude is 1e-16, zero up to rounding, so counting really is the maximum-likelihood fit.

Errors compound when the teacher leaves

A toy: at every step the model picks the right next token with probability 0.9 given a correct prefix, and otherwise one of the 3 wrong tokens (vocabulary of 4). Under teacher forcing every step is scored at 0.9. Free running, the chance the whole prefix is still right after t steps is 0.9t. In the toy, each slip shifts the sequence by a random offset, and it is back on track only when the slips cancel out; the chance of being on track decays toward chance (1/4):

Step t124812
Teacher-forced accuracy0.9000.9000.9000.9000.900
Whole prefix correct0.9000.8100.6560.4300.282
On track (free running)0.9000.8130.6730.4890.385

Training loss looks the same at every step; generation quality does not. Real models are not this simple: a strong model can recover from a slip, which this toy cannot.

A causal stack, checked

Input [1, 2, 3, 4, 5, 6, 7, 8], kernel [0.5, 0.5] (an average of the current value and the one d steps back), dilations 1, 2, 4. The layers give:

  • dilation 1: [0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5]
  • dilation 2: [0.25, 0.75, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5]
  • dilation 4: [0.125, 0.375, 0.75, 1.25, 1.875, 2.625, 3.5, 4.5]

Receptive field 1 + (2−1)(1+2+4) = 8. Now add 100 to input position 5 (zero-based). The top layer becomes [0.125, 0.375, 0.75, 1.25, 1.875, 15.125, 16, 17]: outputs before position 5 do not move, so nothing reads the future. WaveNet's receptive fields with kernel size 2: one causal conv, kernel 2: 2; 10 layers, no dilation: 11; 10 layers, dilations 1, 2, …, 512: 1024; 3 such blocks: 3070.

Implementation

import torch
import torch.nn as nn
import torch.nn.functional as F

class CausalConv1d(nn.Conv1d):
    def __init__(self, c_in, c_out, kernel_size, dilation=1):
        super().__init__(c_in, c_out, kernel_size, dilation=dilation, padding=0)
        self.left = dilation * (kernel_size - 1)

    def forward(self, x):                     # x: [B, C, T]
        return super().forward(F.pad(x, (self.left, 0)))   # output [B, C_out, T]

class MaskedConv2d(nn.Conv2d):                # PixelCNN, single channel ordering
    def __init__(self, mask_type, *args, **kwargs):
        super().__init__(*args, **kwargs)
        k = self.kernel_size[0]
        mask = torch.ones_like(self.weight)
        mask[:, :, k // 2, k // 2 + (mask_type == "B"):] = 0
        mask[:, :, k // 2 + 1:] = 0
        self.register_buffer("mask", mask)

    def forward(self, x):
        return F.conv2d(x, self.weight * self.mask, self.bias,
                        self.stride, self.padding, self.dilation, self.groups)

# teacher forcing for a language model: inputs and shifted targets
def lm_loss(model, ids):                      # ids: [B, T+1]
    logits = model(ids[:, :-1])               # [B, T, V], causal model
    return F.cross_entropy(logits.reshape(-1, logits.size(-1)),
                           ids[:, 1:].reshape(-1), ignore_index=-100)

@torch.no_grad()
def generate(model, ids, steps):
    for _ in range(steps):
        probs = model(ids)[:, -1].softmax(-1) # only the last position
        nxt = torch.multinomial(probs, 1)     # [B, 1]
        ids = torch.cat([ids, nxt], dim=1)    # fed back: no teacher now
    return ids

The causal convolution relies on Conv1d's default padding_mode="zeros" being irrelevant once padding=0; all padding comes from F.pad, whose tuple starts with the last dimension's (left, right). The generate loop recomputes the whole prefix each step for clarity; real implementations keep a cache.

Tradeoffs

  • Exact likelihood. Autoregressive models give an exact log p(x), so they can be trained and compared by cross-entropy or perplexity directly. Many other generative models (diffusion models, VAEs) only give a bound or none.
  • Sequential sampling. Scoring is parallel, but generating T tokens takes T steps. That is the cost behind the KV cache and speculative decoding, and why parallel generators exist.
  • A fixed order. The model must commit to an order. Text has a natural one; images and audio do not, and the order affects what is easy to learn (PixelCNN's blind spot is an artefact of order plus architecture).
  • Exposure bias. Teacher forcing is efficient and is maximum likelihood, but it never shows the model its own mistakes. Scheduled sampling breaks the parallelism of training; most large models live with the bias.
  • Context length. Convolutions see a fixed receptive field; RNNs see everything through a bottleneck; Transformers see the whole window at quadratic attention cost.

Check yourself

Is the chain-rule factorization an approximation?

No. It holds for every joint distribution. Approximation enters only when a model cannot represent the true conditionals, for example a Markov model that forgets the far past (KL 0.0395 bits in the toy).

Why does the first PixelCNN layer use mask A but later layers mask B?

The first layer reads raw pixels, so its centre is the pixel being predicted and must be hidden. After that, each position holds features built only from earlier pixels, so keeping the centre leaks nothing.

Why can training run all positions in parallel but generation cannot?

Training feeds the true sequence, known in advance, and a causal model computes every position's prediction in one pass. Generation needs each token before it can predict the next.

Connections

Read first

Read next

No page builds on this one yet.

References

  • Bengio, Ducharme, Vincent and Jauvin. A Neural Probabilistic Language Model. JMLR 3, 2003 (JMLR).
  • van den Oord, Kalchbrenner and Kavukcuoglu. Pixel Recurrent Neural Networks (PixelCNN, masks A and B). ICML, 2016 (arXiv 1601.06759).
  • van den Oord, Kalchbrenner, Vinyals, Espeholt, Graves and Kavukcuoglu. Conditional Image Generation with PixelCNN Decoders (Gated PixelCNN, the blind spot). NeurIPS, 2016 (arXiv 1606.05328).
  • van den Oord, Dieleman, Zen, Simonyan, Vinyals, Graves, Kalchbrenner, Senior and Kavukcuoglu. WaveNet: A Generative Model for Raw Audio. 2016 (arXiv 1609.03499).
  • Bengio, Vinyals, Jaitly and Shazeer. Scheduled Sampling for Sequence Prediction with Recurrent Neural Networks. NeurIPS, 2015 (arXiv 1506.03099).
  • Ranzato, Chopra, Auli and Zaremba. Sequence Level Training with Recurrent Neural Networks (names exposure bias). ICLR, 2016 (arXiv 1511.06732).
  • Germain, Gregor, Murray and Larochelle. MADE: Masked Autoencoder for Distribution Estimation. ICML, 2015 (arXiv 1502.03509).
  • PyTorch v2.15.0: categorical.py, CrossEntropyLoss, Conv1d/Conv2d, F.pad.