Diffusion models look intimidating for an unusual reason: the ideas are not that hard, but the equations are long. A single line can contain a product over a thousand timesteps, an expectation over a Gaussian, and a gradient of a log-density, all at once.
These are my notes on what those lines actually mean. I have tried to write every important equation out in full, rather than hiding the details behind “after some algebra”.
Throughout, let x0∈Rd be a data point, x1,…,xT be a sequence of increasingly noisy versions of it, and θ be the parameters of a neural network.
The forward process
The forward process is a fixed Markov chain that adds a little Gaussian noise at each step:
where 0<β1<⋯<βT<1 is a variance schedule chosen by hand. Each step keeps the signal slightly smaller and the noise slightly larger, so the chain drifts from data towards a standard normal distribution.
Because every step is Gaussian and linear, the composition of all the steps is also Gaussian. Define
αt=1−βt,αˉt=s=1∏tαs.
Then the distribution of xt given the original data has a closed form:
Equivalently, and this is the form used in code, we can sample xt directly by reparameterisation:
xt=αˉtx0+1−αˉtε,ε∼N(0,Id).
The product αˉt is the only thing we need to remember about the schedule. When αˉT is close to zero, xT is almost pure noise and carries almost no information about x0.
The reverse direction is the hard part
Generation means running the chain backwards. If we knew the true reverse distribution, we could sample xT∼N(0,Id) and denoise step by step. Bayes’ rule gives an expression for it:
That is a perfectly correct formula and a completely useless one, because both integrals run over the unknown data distribution. We cannot evaluate it, let alone sample from it.
So we approximate it. Every conditional in the reverse chain is chosen to be Gaussian,
pθ(xt−1∣xt)=N(xt−1;μθ(xt,t),Σθ(xt,t)),
and a neural network learns the mean and variance. The question is what objective to train it with.
The evidence lower bound
We would like to maximise the log-likelihood of the data, logpθ(x0). That is also intractable, so we maximise a lower bound instead. The bound comes from introducing the forward chain as a variational distribution and applying Jensen’s inequality:
The first term has no learnable parameters: it measures how far the final noisy state is from the prior, and it is small by construction if the schedule is good. The third term is a single denoising step at the end. The interesting part is the sum in the middle.
Each term in that sum is a KL divergence between two Gaussians, because the forward posterior q(xt−1∣xt,x0) is also Gaussian. Deriving it from Bayes’ rule gives
In our case both covariances are multiples of the identity, so the log-determinant and trace terms are constants with respect to θ. Minimising the KL divergence therefore reduces to matching the means:
That is the whole trick. A scary sum of KL divergences collapses into a handful of mean-squared errors.
Predicting noise instead of means
The mean μ~t depends on x0, which we do not have at sampling time. But the reparameterisation lets us trade x0 for the noise ε that produced xt:
x0=αˉtxt−1−αˉtε.
Substituting that into μ~t and simplifying gives the mean in terms of xt and ε:
μθ(xt,t)=αt1(xt−1−αˉtβtεθ(xt,t)).
So instead of training a network to output a mean, we train it to output the noise that was added. After dropping the per-timestep weighting — which empirically works better than the exact ELBO — the objective becomes the now-familiar denoising loss:
for x0 in loader: t = torch.randint(1, T + 1, (x0.shape[0],), device=x0.device) eps = torch.randn_like(x0) abar = alpha_bar[t].view(-1, 1) xt = abar.sqrt() * x0 + (1 - abar).sqrt() * eps loss = ((eps - model(xt, t)) ** 2).mean() loss.backward() optimizer.step() optimizer.zero_grad()
Everything else in a diffusion paper is, in some sense, an elaboration of this loop.
The score function and the continuous limit
There is a second way to view the same objective, and it generalises more cleanly. The score of a distribution is the gradient of its log-density:
st(x)=∇xlogqt(x).
The score points in the direction in which the density grows fastest, which is exactly the direction a sample should move to become more likely. For a Gaussian perturbation of data, the score has a closed form in terms of the noise:
This identity is often called Tweedie’s formula. It says that denoising and score estimation are the same task up to a scale factor. Training a network sθ(x,t) to match the score gives the denoising score matching objective:
where λ(t) is a positive weighting. With λ(t)=1−αˉt this is equivalent to Lsimple.
The continuous-time limit is where the notation becomes genuinely heavy. Let the number of steps go to infinity and replace the discrete schedule with a continuous function β(t). The forward process becomes a stochastic differential equation,
dx=−21β(t)xdt+β(t)dw,
where w is a standard Wiener process. The density qt(x) then evolves according to the Fokker–Planck equation:
∂t∂qt(x)=2β(t)[∇⋅(xqt(x))+Δqt(x)],
with Δ the Laplacian. Anderson’s reverse-time result says that the time-reversed process is again a diffusion, with an extra drift term built from the score:
dx=[−21β(t)x−β(t)∇xlogqt(x)]dt+β(t)dwˉ,
where wˉ is a Wiener process running backwards in time. This is the continuous version of the reverse chain, and it makes the role of the score explicit: it is the only unknown quantity in the drift.
There is also a deterministic counterpart. Removing the noise from the reverse SDE leaves the probability flow ODE,
dtdx=−21β(t)[x+∇xlogqt(x)],
which transports the same marginal densities qt(x) without any stochasticity. Sampling with a numerical ODE solver is often faster and more reproducible than simulating the SDE, at the cost of some sample diversity.
Guidance
Most practical systems do not sample from the unconditional model. They condition on a prompt c and then exaggerate the conditioning. Classifier-free guidance does this by combining a conditional and an unconditional prediction:
which makes the geometry clearer: guidance adds a multiple of the difference between the conditional and unconditional score fields. Large w pushes samples towards regions the condition considers likely, while shrinking diversity and eventually producing oversaturated artifacts.
Sampling
The reverse chain can be sampled with the ancestral rule implied by pθ, but many implementations use DDIM, which allows larger strides and a deterministic path. One DDIM update is
where ε∼N(0,Id) and σt controls how much randomness is injected. Setting σt=0 gives the deterministic DDIM sampler; setting σt to the forward posterior standard deviation recovers the ancestral sampler.
What the equations are really saying
Read quickly, the formulas above look like a pile of Gaussian integrals. Read slowly, they say something simple.
The forward process is a fixed, known corruption. The reverse process is a learned repair. The ELBO turns the impossible problem of matching a whole distribution into a sequence of easy mean-matching problems. Reparameterisation turns those mean-matching problems into noise prediction. Tweedie’s formula turns noise prediction into score estimation. The continuous limit replaces the discrete chain with an SDE, and the score becomes the drift. Guidance is just arithmetic on two score fields.
Every long line in a diffusion paper is one of those steps, written without shortcuts. Once the steps are visible, the length stops being intimidating and starts being useful.