Skip to content
AI360Xpert
Beta

VQ-VAE

VQ-VAE compresses an image into a grid of discrete codebook tokens instead of smooth numbers, giving generators a finite vocabulary of visual words.

VQ-VAE snaps a continuous encoder output to its nearest codebook vector, turning an image region into one discrete token
VQ-VAE snaps a continuous encoder output to its nearest codebook vector, turning an image region into one discrete token

Why Does This Exist?

Continuous latents are awkward for the strongest generators. Transformers predict discrete tokens, words, IDs, and a smooth Gaussian vector gives them nothing to classify. Researchers wanted the scaling power of next-token prediction applied to images, but images have no vocabulary.

Vector quantisation builds one. By forcing every encoder output through a finite codebook of learned visual words, VQ-VAE turns a photo into a sentence of token IDs. That discrete grid is exactly what powers autoregressive image models, as the owner page autoregressive image generation shows from the generator side. The continuous alternative it departs from is the variational autoencoder.

Think of It Like This

A paint-by-numbers kit with 512 mixed colours

A paint shop holds millions of blendable shades. A paint-by-numbers kit offers 512 premixed pots and a rule: every region of your painting must use the nearest premixed colour. The finished picture is coarser than a freehand blend, but anyone can reproduce it exactly by reading out pot numbers, and a machine can learn to predict those numbers one region at a time.

The codebook is the kit. Quantisation error is the visible gap between the shade you wanted and the nearest pot. It fails when the kit misses a colour family entirely, whole skies collapse into one pot, which is the codebook collapse pitfall below.

How It Actually Works

The encoder outputs a continuous vector ze(x)z_e(x) per spatial location. A codebook holds KK learned vectors e1,…,eKe_1, \dots, e_K, usually K=512K = 512. Quantisation snaps each output to its nearest entry:

k=arg⁡min⁡j∥ze(x)−ej∥2,zq=ekk = \arg\min_j \lVert z_e(x) - e_j \rVert_2, \qquad z_q = e_k

The decoder only ever sees zqz_q. Gradients cannot flow through the arg⁡min⁡\arg\min, so training copies decoder gradients straight through to the encoder, the straight-through estimator, and moves codebook vectors toward the encoder outputs assigned to them with a separate dictionary loss. A third term, the commitment loss β∥ze(x)−sg[ek]∥2\beta \lVert z_e(x) - \mathrm{sg}[e_k] \rVert^2 with β≈0.25\beta \approx 0.25, keeps the encoder loyal to its chosen entry instead of drifting between codes. The symbol sg\mathrm{sg} means stop-gradient: that term trains the encoder only.

Worked example

Encoder output ze=[0.6,−0.4]z_e = [0.6, -0.4]. Codebook has e1=[0.5,−0.5]e_1 = [0.5, -0.5] and e2=[−0.3,0.8]e_2 = [-0.3, 0.8]. Distances squared: to e1e_1, (0.1)2+(0.1)2=0.02(0.1)^2 + (0.1)^2 = 0.02; to e2e_2, (0.9)2+(−1.2)2=0.81+1.44=2.25(0.9)^2 + (-1.2)^2 = 0.81 + 1.44 = 2.25. Nearest is e1e_1, so the region becomes token k=1k = 1 and the decoder receives exactly [0.5,−0.5][0.5, -0.5]. Commitment loss is 0.25×0.02=0.0050.25 \times 0.02 = 0.005, a gentle tug keeping the encoder near its pick.

Code

Nearest-code lookup plus the commitment term, the heart of the forward pass.

def quantize(z_e: list[float], codebook: list[list[float]], beta: float = 0.25) -> tuple:    """Snap encoder output to nearest code; return token, vector, commitment loss."""    dists = [sum((a - b) ** 2 for a, b in zip(z_e, e)) for e in codebook]    k = min(range(len(dists)), key=dists.__getitem__)    return k, codebook[k], beta * dists[k]

z_e = [0.6, -0.4]book = [[0.5, -0.5], [-0.3, 0.8]]print(quantize(z_e, book))  # -> (1, [0.5, -0.5], 0.005)

Watch Out For

Codebook collapse leaves most entries unused

Early in training a few entries win every lookup, get all the gradient, and win more, while hundreds of codes sit frozen. Reconstructions then reuse a tiny visual vocabulary and look posterised.

Track the fraction of codes used per batch. If it drops under half, restart dead entries near active encoder outputs or use exponential moving-average codebook updates, which spread assignments far more evenly than plain gradient steps.

Expecting smooth interpolation between tokens

In a continuous latent space, averaging two codes gives a sensible in-between image. Token IDs have no arithmetic: the average of token 11 and token 99 is not token 55 in any meaningful sense.

Interpolate in encoder space before quantisation, or decode both endpoints and blend pixels. Never blend IDs.

The Quick Version

  • VQ-VAE replaces each continuous encoder output with its nearest learned codebook vector.
  • The image becomes a grid of discrete token IDs, a vocabulary transformers can predict.
  • Gradients bypass the lookup via straight-through estimation while a dictionary loss moves the codes.
  • Our example snaps [0.6,−0.4][0.6, -0.4] to token 11 at distance 0.020.02 with commitment cost 0.0050.005.
  • Watch for dead codebook entries and never interpolate raw token IDs.