← logbook

The Equations of Diffusion, Written Out in Full

·9 min read·technology

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∈Rdx_0 \in \mathbb{R}^d be a data point, x1,…,xTx_1, \ldots, x_T be a sequence of increasingly noisy versions of it, and θ\theta 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:

q(xt∣xt−1)=N ⁣(xt; 1−βt xt−1, βtId)=1(2πβt)d/2exp⁡ ⁣(−12βt∥xt−1−βt xt−1∥22),q\left(x_t \mid x_{t-1}\right) = \mathcal{N}\!\left(x_t;\ \sqrt{1-\beta_t}\,x_{t-1},\ \beta_t I_d\right) = \frac{1}{\left(2\pi\beta_t\right)^{d/2}} \exp\!\left( -\frac{1}{2\beta_t} \left\lVert x_t-\sqrt{1-\beta_t}\,x_{t-1}\right\rVert_2^2 \right),

where 0<β1<⋯<βT<10 < \beta_1 < \cdots < \beta_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=1tαs.\alpha_t = 1-\beta_t, \qquad \bar\alpha_t = \prod_{s=1}^{t}\alpha_s .

Then the distribution of xtx_t given the original data has a closed form:

q(xt∣x0)=N ⁣(xt; αˉt x0, (1−αˉt)Id)=1(2π(1−αˉt))d/2exp⁡ ⁣(−∥xt−αˉt x0∥222(1−αˉt)).q\left(x_t \mid x_0\right) = \mathcal{N}\!\left(x_t;\ \sqrt{\bar\alpha_t}\,x_0,\ \left(1-\bar\alpha_t\right)I_d\right) = \frac{1}{\left(2\pi\left(1-\bar\alpha_t\right)\right)^{d/2}} \exp\!\left( -\frac{\left\lVert x_t-\sqrt{\bar\alpha_t}\,x_0\right\rVert_2^2} {2\left(1-\bar\alpha_t\right)} \right).

Equivalently, and this is the form used in code, we can sample xtx_t directly by reparameterisation:

xt=αˉt x0+1−αˉt ε,ε∼N(0,Id).x_t = \sqrt{\bar\alpha_t}\,x_0 + \sqrt{1-\bar\alpha_t}\,\varepsilon, \qquad \varepsilon \sim \mathcal{N}\left(0, I_d\right).

The product αˉt\bar\alpha_t is the only thing we need to remember about the schedule. When αˉT\bar\alpha_T is close to zero, xTx_T is almost pure noise and carries almost no information about x0x_0.

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)x_T \sim \mathcal{N}(0,I_d) and denoise step by step. Bayes’ rule gives an expression for it:

q(xt−1∣xt)=q(xt∣xt−1) q(xt−1)q(xt)=q(xt∣xt−1)∫q(xt−1∣x0)q(x0)dx0∫q(xt∣x0)q(x0)dx0.q\left(x_{t-1}\mid x_t\right) = \frac{q\left(x_t\mid x_{t-1}\right)\,q\left(x_{t-1}\right)}{q\left(x_t\right)} = \frac{ q\left(x_t\mid x_{t-1}\right) \displaystyle\int q\left(x_{t-1}\mid x_0\right)q\left(x_0\right)\mathrm{d}x_0 }{ \displaystyle\int q\left(x_t\mid x_0\right)q\left(x_0\right)\mathrm{d}x_0 }.

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)),p_\theta\left(x_{t-1}\mid x_t\right) = \mathcal{N}\!\left(x_{t-1};\ \mu_\theta\left(x_t,t\right),\ \Sigma_\theta\left(x_t,t\right)\right),

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, log⁡pθ(x0)\log p_\theta(x_0). 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:

log⁡pθ(x0)=log⁡Eq(x1:T∣x0)[pθ(x0:T)q(x1:T∣x0)]≥Eq(x1:T∣x0)[log⁡pθ(x0:T)q(x1:T∣x0)].\log p_\theta\left(x_0\right) = \log \mathbb{E}_{q\left(x_{1:T}\mid x_0\right)} \left[ \frac{p_\theta\left(x_{0:T}\right)}{q\left(x_{1:T}\mid x_0\right)} \right] \ge \mathbb{E}_{q\left(x_{1:T}\mid x_0\right)} \left[ \log \frac{p_\theta\left(x_{0:T}\right)}{q\left(x_{1:T}\mid x_0\right)} \right].

Writing the joint distributions out and grouping terms by timestep gives the full expansion:

LELBO(θ)=Eq(x1:T∣x0)[log⁡pθ(x0:T)−log⁡q(x1:T∣x0)]=Eq[log⁡p(xT)+∑t=1Tlog⁡pθ(xt−1∣xt)−∑t=1Tlog⁡q(xt∣xt−1)]=Eq[log⁡p(xT)−log⁡q(xT∣x0)]⏟prior term−∑t=2TEq[log⁡q(xt−1∣xt,x0)pθ(xt−1∣xt)]⏟denoising term+Eq[log⁡pθ(x0∣x1)]⏟reconstruction term.\begin{aligned} \mathcal{L}_{\mathrm{ELBO}}\left(\theta\right) &= \mathbb{E}_{q\left(x_{1:T}\mid x_0\right)} \left[ \log p_\theta\left(x_{0:T}\right) -\log q\left(x_{1:T}\mid x_0\right) \right] \\ &= \mathbb{E}_{q}\left[ \log p\left(x_T\right) +\sum_{t=1}^{T}\log p_\theta\left(x_{t-1}\mid x_t\right) -\sum_{t=1}^{T}\log q\left(x_t\mid x_{t-1}\right) \right] \\ &= \underbrace{ \mathbb{E}_{q}\left[\log p\left(x_T\right)-\log q\left(x_T\mid x_0\right)\right] }_{\text{prior term}} -\sum_{t=2}^{T} \underbrace{ \mathbb{E}_{q}\left[ \log \frac{q\left(x_{t-1}\mid x_t,x_0\right)} {p_\theta\left(x_{t-1}\mid x_t\right)} \right] }_{\text{denoising term}} +\underbrace{ \mathbb{E}_{q}\left[\log p_\theta\left(x_0\mid x_1\right)\right] }_{\text{reconstruction term}} . \end{aligned}

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)q(x_{t-1}\mid x_t,x_0) is also Gaussian. Deriving it from Bayes’ rule gives

q(xt−1∣xt,x0)=N ⁣(xt−1; μ~t(xt,x0), β~tId),q\left(x_{t-1}\mid x_t,x_0\right) = \mathcal{N}\!\left(x_{t-1};\ \tilde\mu_t\left(x_t,x_0\right),\ \tilde\beta_t I_d\right),

with

μ~t(xt,x0)=αˉt−1 βt1−αˉt x0+αt(1−αˉt−1)1−αˉt xt,β~t=1−αˉt−11−αˉt βt.\tilde\mu_t\left(x_t,x_0\right) = \frac{\sqrt{\bar\alpha_{t-1}}\,\beta_t}{1-\bar\alpha_t}\,x_0 + \frac{\sqrt{\alpha_t}\left(1-\bar\alpha_{t-1}\right)}{1-\bar\alpha_t}\,x_t, \qquad \tilde\beta_t = \frac{1-\bar\alpha_{t-1}}{1-\bar\alpha_t}\,\beta_t .

For reference, the KL divergence between two multivariate Gaussians is

DKL ⁣(N(μq,Σq) ∥ N(μp,Σp))=12[log⁡det⁡Σpdet⁡Σq−d+tr⁡ ⁣(Σp−1Σq)+(μp−μq)⊤Σp−1(μp−μq)].D_{\mathrm{KL}}\!\left( \mathcal{N}\left(\mu_q,\Sigma_q\right) \ \Vert\ \mathcal{N}\left(\mu_p,\Sigma_p\right) \right) = \frac{1}{2}\left[ \log\frac{\det\Sigma_p}{\det\Sigma_q} - d + \operatorname{tr}\!\left(\Sigma_p^{-1}\Sigma_q\right) + \left(\mu_p-\mu_q\right)^{\top} \Sigma_p^{-1} \left(\mu_p-\mu_q\right) \right].

In our case both covariances are multiples of the identity, so the log-determinant and trace terms are constants with respect to θ\theta. Minimising the KL divergence therefore reduces to matching the means:

min⁡θ DKL ⁣(q(xt−1∣xt,x0) ∥ pθ(xt−1∣xt))⟺min⁡θ ∥μθ(xt,t)−μ~t(xt,x0)∥22.\min_\theta\ D_{\mathrm{KL}}\!\left( q\left(x_{t-1}\mid x_t,x_0\right) \ \Vert\ p_\theta\left(x_{t-1}\mid x_t\right) \right) \quad\Longleftrightarrow\quad \min_\theta\ \left\lVert \mu_\theta\left(x_t,t\right) -\tilde\mu_t\left(x_t,x_0\right) \right\rVert_2^2 .

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\tilde\mu_t depends on x0x_0, which we do not have at sampling time. But the reparameterisation lets us trade x0x_0 for the noise ε\varepsilon that produced xtx_t:

x0=xt−1−αˉt εαˉt.x_0 = \frac{x_t-\sqrt{1-\bar\alpha_t}\,\varepsilon}{\sqrt{\bar\alpha_t}} .

Substituting that into μ~t\tilde\mu_t and simplifying gives the mean in terms of xtx_t and ε\varepsilon:

μθ(xt,t)=1αt(xt−βt1−αˉt εθ(xt,t)).\mu_\theta\left(x_t,t\right) = \frac{1}{\sqrt{\alpha_t}}\left( x_t - \frac{\beta_t}{\sqrt{1-\bar\alpha_t}}\, \varepsilon_\theta\left(x_t,t\right) \right).

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:

Lsimple(θ)=Et∼U{1,…,T} Ex0∼q(x0) Eε∼N(0,Id)[∥ε−εθ ⁣(αˉt x0+1−αˉt ε, t)∥22].\mathcal{L}_{\mathrm{simple}}\left(\theta\right) = \mathbb{E}_{t\sim\mathcal{U}\{1,\ldots,T\}}\, \mathbb{E}_{x_0\sim q\left(x_0\right)}\, \mathbb{E}_{\varepsilon\sim\mathcal{N}\left(0,I_d\right)} \left[ \left\lVert \varepsilon - \varepsilon_\theta\!\left( \sqrt{\bar\alpha_t}\,x_0 +\sqrt{1-\bar\alpha_t}\,\varepsilon,\ t \right) \right\rVert_2^2 \right].

In code, that is almost embarrassingly short:

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)=∇xlog⁡qt(x).s_t\left(x\right) = \nabla_x \log q_t\left(x\right).

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:

∇xlog⁡qt(x)=αˉt E[x0∣xt=x]−x1−αˉt=−E[ε∣xt=x]1−αˉt.\nabla_x\log q_t\left(x\right) = \frac{\sqrt{\bar\alpha_t}\, \mathbb{E}\left[x_0\mid x_t=x\right]-x} {1-\bar\alpha_t} = -\frac{\mathbb{E}\left[\varepsilon\mid x_t=x\right]} {\sqrt{1-\bar\alpha_t}} .

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)s_\theta(x,t) to match the score gives the denoising score matching objective:

LDSM(θ)=Et[λ(t)Ex0Eε[∥sθ ⁣(αˉt x0+1−αˉt ε, t)+ε1−αˉt∥22]],\mathcal{L}_{\mathrm{DSM}}\left(\theta\right) = \mathbb{E}_{t}\left[ \lambda\left(t\right) \mathbb{E}_{x_0} \mathbb{E}_{\varepsilon} \left[ \left\lVert s_\theta\!\left( \sqrt{\bar\alpha_t}\,x_0 +\sqrt{1-\bar\alpha_t}\,\varepsilon,\ t \right) +\frac{\varepsilon}{\sqrt{1-\bar\alpha_t}} \right\rVert_2^2 \right] \right],

where λ(t)\lambda(t) is a positive weighting. With λ(t)=1−αˉt\lambda(t) = 1-\bar\alpha_t this is equivalent to Lsimple\mathcal{L}_{\mathrm{simple}}.

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)\beta(t). The forward process becomes a stochastic differential equation,

dx=−12β(t)x dt+β(t) dw,\mathrm{d}x = -\frac{1}{2}\beta\left(t\right)x\,\mathrm{d}t + \sqrt{\beta\left(t\right)}\,\mathrm{d}w,

where ww is a standard Wiener process. The density qt(x)q_t(x) then evolves according to the Fokker–Planck equation:

∂∂tqt(x)=β(t)2[∇⋅(x qt(x))+Δqt(x)],\frac{\partial}{\partial t}q_t\left(x\right) = \frac{\beta\left(t\right)}{2} \left[ \nabla\cdot\left(x\,q_t\left(x\right)\right) +\Delta q_t\left(x\right) \right],

with Δ\Delta 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=[−12β(t)x−β(t)∇xlog⁡qt(x)]dt+β(t) dwˉ,\mathrm{d}x = \left[ -\frac{1}{2}\beta\left(t\right)x -\beta\left(t\right)\nabla_x\log q_t\left(x\right) \right]\mathrm{d}t + \sqrt{\beta\left(t\right)}\,\mathrm{d}\bar w,

where wˉ\bar 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,

dxdt=−12β(t)[x+∇xlog⁡qt(x)],\frac{\mathrm{d}x}{\mathrm{d}t} = -\frac{1}{2}\beta\left(t\right) \left[ x +\nabla_x\log q_t\left(x\right) \right],

which transports the same marginal densities qt(x)q_t(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 cc and then exaggerate the conditioning. Classifier-free guidance does this by combining a conditional and an unconditional prediction:

ε~θ(xt,t,c)=(1+w)εθ(xt,t,c)−w εθ(xt,t,∅),w≥0,\tilde{\varepsilon}_\theta\left(x_t,t,c\right) = \left(1+w\right)\varepsilon_\theta\left(x_t,t,c\right) - w\,\varepsilon_\theta\left(x_t,t,\varnothing\right), \qquad w \ge 0,

where ∅\varnothing denotes the empty condition and ww is the guidance scale. In score form the same operation reads

∇~xlog⁡qt(x∣c)=(1+w)∇xlog⁡qt(x∣c)−w ∇xlog⁡qt(x),\tilde{\nabla}_x\log q_t\left(x\mid c\right) = \left(1+w\right)\nabla_x\log q_t\left(x\mid c\right) - w\,\nabla_x\log q_t\left(x\right),

which makes the geometry clearer: guidance adds a multiple of the difference between the conditional and unconditional score fields. Large ww 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θp_\theta, but many implementations use DDIM, which allows larger strides and a deterministic path. One DDIM update is

xt−1=αˉt−1 xt−1−αˉt εθ(xt,t)αˉt⏟x^0+1−αˉt−1−σt2 εθ(xt,t)+σt ε,x_{t-1} = \sqrt{\bar\alpha_{t-1}}\, \underbrace{ \frac{x_t-\sqrt{1-\bar\alpha_t}\, \varepsilon_\theta\left(x_t,t\right)} {\sqrt{\bar\alpha_t}} }_{\hat{x}_0} + \sqrt{1-\bar\alpha_{t-1}-\sigma_t^2}\, \varepsilon_\theta\left(x_t,t\right) + \sigma_t\,\varepsilon,

where ε∼N(0,Id)\varepsilon\sim\mathcal{N}(0,I_d) and σt\sigma_t controls how much randomness is injected. Setting σt=0\sigma_t=0 gives the deterministic DDIM sampler; setting σt\sigma_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.

thanks for reading.

more logs →