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

The forward process

The forward chain adds a little noise at every step, and Gaussian noise composes. That gives a closed form for any timestep, which is what makes training cheap.

Part 2 of 10

Advanced15 min readBack to Courses

One step, then many

The forward process is a Markov chain with one rule. A Markov chain is a sequence of steps where the next state depends only on the current one. At each step, scale the current sample by the square root of one minus beta, then add Gaussian noise with variance beta. The scaling is what keeps the variance from growing. Without it, the sample would inflate a little at every step and end far from a standard normal. With it, a unit-variance input stays unit-variance throughout. That is the variance-preserving form used by almost every image model.

Beta is small, typically between one in ten thousand and two hundredths, so a single step is barely visible. That is the point. The chain destroys structure little by little, and each transition is close enough to invertible that a network can learn the inverse. The forward process has no parameters at all. It is a fixed recipe chosen before training, and it is the same for every image.

The closed form

You never have to run the chain to get a noisy sample. Because Gaussian noise adds up cleanly, the marginal at step t has a closed form (an exact formula you can evaluate in one go). The marginal is the distribution at that step, on its own. x_t equals the square root of alpha_bar times x_0, plus the square root of one minus alpha_bar times a fresh noise sample. Alpha_bar is the product of every one minus beta up to t. One draw, any timestep, no loop. Training uses this constantly, because it lets a batch sample a different t for every example at no cost.

The two coefficients are a trade. Alpha_bar is the share of the original signal that survives. One minus alpha_bar is the share that is noise. Their squares sum to one, so the variance is preserved exactly. At t equal to zero, alpha_bar is essentially one and the sample is clean. At the final step it is near zero and the sample is almost pure noise. Everything between is a blend, and the blend is what the model learns to separate.

Build the cumulative table

import torch
from diffusers import DDPMScheduler

scheduler = DDPMScheduler(num_train_timesteps=1000, beta_schedule="linear")
betas = scheduler.betas
alphas = 1.0 - betas
alphas_cumprod = torch.cumprod(alphas, dim=0)

print("first beta :", betas[0].item())
print("last beta  :", betas[-1].item())
print("alpha_bar at 0   :", alphas_cumprod[0].item())
print("alpha_bar at 999 :", alphas_cumprod[-1].item())

One draw at any timestep

Sample twenty thousand noisy values at five timesteps and compare the empirical mean and standard deviation with the closed form. Then change x_0 to minus two and confirm the mean moves with it.

Output
Click "Run" to execute your code

Signal to noise

The ratio alpha_bar over one minus alpha_bar is the signal-to-noise ratio, and it is the most useful single number about a schedule. It falls steadily from a very large value to almost zero. When it crosses one, the sample holds equal parts signal and noise. Below that, the noise dominates. Most of the interesting denoising happens in a narrow band around that crossing. Schedules and samplers spend a lot of effort on where it sits for that reason.

Alpha_bar is cumulative, not per step. Confusing the two is the most common off-by-one in a diffusion implementation. A beta of 0.02 does not mean two percent of the signal is gone at that step. It means two percent of the current variance is replaced, and the effect compounds. Print the curve before you trust a schedule. Check the last value too: a schedule that ends at alpha_bar of 0.01 has not quite reached pure noise, and a sampler that assumes it has will leave structure behind.

Index alpha_bar with the same convention everywhere. A table of length 1000 is indexed 0 to 999, while a sampler that counts down from 1000 to 1 is off by one at both ends, and the error is silent.

The forward process is not the model

It is tempting to think the forward process is something the model learns. It is not. It is a fixed corruption, chosen by you, and the model only ever learns its inverse. That separation is what makes the maths tractable. The training distribution at every timestep is known exactly, so the loss can be computed in closed form. It also means a checkpoint is tied to the schedule it was trained on. Change the betas and the same weights are denoising a different problem.

In practice you precompute alpha_bar once, store it as a tensor, and index it by t. The chain itself is only used when you want to visualise the corruption or check an implementation. Everything else reads the cumulative table: training, and the closed-form sampling updates. Keep that table in the checkpoint metadata. It is part of the model in every way that matters.

Where the signal disappears

Print the signal-to-noise ratio across a linear schedule and find the first timestep where less than half the signal remains. Then count how many of the thousand steps have more noise than signal.

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

What does one forward step do to a sample?