Vocabulary for this course

The terms the chapters assume, in plain words. You can open this from any chapter.

closed form
A formula that gives a value directly, without stepping through every earlier stage.
denoising
Removing some random corruption from a sample in one step, as the model was trained to do.
diffusion model
A generator trained to turn random noise into data by undoing a chain of small corruptions.
forward process
The fixed chain that adds a little random corruption to a clean sample at each step.
Gaussian
A bell-shaped spread of values around a mean, described by that mean and a width.
guidance
Pushing each sampling step harder toward the text you asked for, trading variety for a closer match.
latent space
A smaller space of numbers where a compressor places images, so the model works on short codes.
noise
Random values added to a sample to corrupt it, drawn from a Gaussian.
noise schedule
The rule that decides how much corruption each step adds, and how fast the real pattern fades.
reverse process
The learned chain that removes corruption step by step, starting from pure randomness.
sampler
The procedure that runs the reverse process to produce a finished sample.
signal-to-noise ratio
How much real structure remains compared with the corruption at a given step.
timestep
One position in the corruption chain, numbered from clean to fully random.
VAE
A model that squeezes an image into a small code and rebuilds it, trained to keep what matters.

Diffusion models from the ground up

Latent diffusion

Run diffusion inside a compressed autoencoder latent instead of pixels. The maths is unchanged; the tensors are smaller and the expensive attention becomes affordable.

Part 7 of 10

Advanced17 min readBack to Courses

Pixels are expensive

A 512 by 512 RGB image has 786,432 values. A diffusion model that works on all of them spends most of its compute on detail the eye will never resolve. Attention over 262,144 pixel tokens grows with the square of that number. Most of those values are redundant. Neighbouring pixels are highly correlated, and the eye is far more sensitive to some frequencies than others. Latent diffusion moves the whole process into a compressed representation. An autoencoder (a network that squeezes an input down and then rebuilds it) maps the image to a small tensor. Diffusion runs there, and a decoder turns the result back into pixels.

The compression is typically a factor of eight in each spatial dimension, and a change from three channels to four. A 512 image becomes a 64 by 64 by 4 latent (the small tensor the model works in), which is 16,384 values. That is forty-eight times smaller. The diffusion model never sees a pixel. It sees a tensor whose statistics are close to a standard normal, and the same UNet or transformer architecture works on it unchanged.

The autoencoder is not a detail

The autoencoder is trained first, on reconstruction, and then frozen. Its job is to keep what matters and discard what does not. A good one preserves edges, text and faces. A bad one turns them into mush that no amount of diffusion can recover. The reconstruction loss is usually a mix of pixel error, perceptual loss (a loss measured on features rather than raw pixels) and a patch discriminator. Pure pixel error produces blurry latents, which is why the mix is used.

The encoder’s output is scaled by a constant before diffusion, usually around 0.18, so that the latent distribution has roughly unit variance. The decoder divides by the same constant. That constant is part of the checkpoint, not a universal law. Using the wrong one gives washed-out or blown-out images. It is a small number with a large blast radius, and it belongs in the model card. The scaling factor is fitted on the training set and then frozen with the rest of the autoencoder.

Encode, diffuse, decode

import torch
from diffusers import AutoencoderKL

vae = AutoencoderKL.from_pretrained("stabilityai/sd-vae-ft-mse")
image = torch.randn(1, 3, 512, 512)

latent = vae.encode(image).latent_dist.sample()
latent = latent * vae.config.scaling_factor
print(latent.shape)

decoded = vae.decode(latent / vae.config.scaling_factor).sample
print(decoded.shape)

Count the tensor

Compare pixel values, latent values and attention tokens for three resolutions. Then see how much the token count falls when the same image is patched in latent space.

Output
Click "Run" to execute your code

What changes in the sampler

The diffusion maths is identical. The schedule, the reverse update, guidance and the solver all work on tensors. None of them care that the tensor is a latent rather than an image. What changes is the cost: attention over 4,096 latent tokens instead of 65,536 pixel patches, and a memory footprint that fits on a single accelerator. The decoder runs once, at the end, and it is the only part that knows about pixels.

Two consequences follow. First, the schedule is tuned for latents, not pixels. A pixel-space schedule moved into a latent model will be wrong at both ends. Second, the VAE’s blind spots become the model’s blind spots. The autoencoder here is a variational autoencoder, or VAE. Small text, fine patterns and regular grids are where reconstruction fails, and no sampler can fix a latent that never encoded them. Judge the autoencoder separately before you blame the diffusion model. The decoder is also where most of the perceptual quality lives. So latent models are judged on the whole pipeline, not only on the denoiser.

The scaling factor is part of the autoencoder, not a constant to guess. Encoding with one value and decoding with another is a common cause of washed-out or oversaturated output that looks like a sampler bug.

Transformers and patches

Latent diffusion does not need a UNet. A transformer can treat the latent as a sequence of patches, exactly as a language model treats tokens, and predict noise for each patch. This is the diffusion transformer, and it scales with the same tools as a language model: more layers, more width, more data. The patch size is the new resolution knob, and it trades sequence length against detail.

At a patch size of two, a 64 by 64 latent becomes 1,024 tokens. At a patch size of one it becomes 4,096. Attention cost grows with the square, so the choice is not cosmetic. Most modern systems use a mix: local attention within patches, global attention at low resolution, or a convolutional stem before the transformer. The principle is unchanged: do the expensive work where the tensor is small.

What compression costs

Compress a signal by averaging blocks and rebuild it by repeating each value. Watch the reconstruction error grow with the factor, then replace the decoder with linear interpolation.

Output
Click "Run" to execute your code

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

Why run diffusion in a latent space?