How DDPM learns to run the noising chain backwards. The exact likelihood is an intractable integral over all noise-to-image paths, Jensen's inequality turns it into a bound made of KL terms, and matching Gaussian posteriors reduces training to predicting the added noise at a random timestep, with one U-Net for all steps that receives the timestep as a sinusoidal embedding.
The forward process gave us a diffusion process: a fixed chain that turns an image into Gaussian noise. To generate images we have to invert it, and that is the job of a deep neural network. This note derives what the network is trained to do. The route is the same as for the VAE: the likelihood is intractable, so we minimise an upper bound on the negative log-likelihood instead. The bound then simplifies, step by step, into a plain mean squared error on noise. A first reading can skip the callouts (the algebra of the KL split, the 2015 form of the bound, the discretized decoder); the recap at the end collects the chain in six lines.
Notation. The forward step adds noise with variance βt∈(0,1) at step t=1,…,T. We write
αt=1−βt,αˉt=s=1∏tαs,αˉ0=1,
so the forward step and its closed-form shortcut from the previous note are
The second argument of N(μ,σ2) is always the variance. q denotes the fixed forward process and q(x0) the data distribution where it starts, pθ the learned reverse process, θ the parameters of the neural network, and p(xT)=N(0,I) the noise distribution where generation starts.
The image x0 sits at one end of the chain and pure noise xT at the other. Going forward, the step q(xt∣xt−1) is known. Going backward we need pθ(xt−1∣xt): the probability density that we have to learn in order to invert the process.
Figure
The forward step is known; the backward step must be learned
The fixed forward step q(xₜ | xₜ₋₁) adds noise and moves from the image x₀ towards pure noise at the last step T. The reverse step pθ(xₜ₋₁ | xₜ) is the probability density we have to learn to invert the process; θ are the parameters of the neural network. Tiles are real samples √ᾱ x₀ + √(1 − ᾱ) ε with one shared noise draw (ᾱ = 1, 0.6, 0.42 and 0 from x₀ to the last step).
What should θ optimise? The usual answer for a generative model: make real images likely. We minimise the negative log-likelihood, averaged over the data,
Ex0∼q(x0)[−logpθ(x0)]→θmin,
which is the same as maximising the likelihood, the density the model assigns to real images. The derivation below works with one image x0; the average over images is added back at the end.
The reverse process starts from p(xT)=N(0,I). This factor has no parameters and needs no knowledge about the data: it is the fixed standard normal, the same for every dataset. All learning sits in the reverse steps pθ(xt−1∣xt).
To get the exact log-likelihood of an image, take the joint reverse distribution and integrate out all intermediate states. This is marginalisation, extracting a marginal from the joint distribution:
−logpθ(x0)=−log∫pθ(x0:T)dx1:T.
Can we use it? In DDPM each reverse step is a Gaussian whose mean is computed from xt by a neural network (made precise below). The network is nonlinear in xt, so, unlike the forward chain of linear Gaussian steps, the composition of reverse steps is not Gaussian and the integral has no closed form. This is the same obstacle as for the VAE decoder. What remains is numerical integration, so look at what the integral means. Every value of x1:T is one path from a noise sample xT to the image x0, and the integral adds up the probabilities of all such paths. For images, each xt has as many dimensions as there are pixels, and there are T of them (hundreds or thousands of steps), so the integral runs over T×(number of pixels) dimensions. We cannot really compute all of those paths.
Figure
The likelihood integral sums over every path from noise to the image
The exact likelihood integrates the joint reverse density over all intermediate states, from x₁ to the last, pure-noise state. Every value of these states is one path from a noise sample to the image x₀ (the last state is integrated over too, so paths start anywhere in the noise). With image-sized states and hundreds of steps, we cannot compute all of these paths. The paths are smooth random bridges drawn with a fixed seed.
The trick is the one from the VAE. Multiply and divide by the complete forward process q(x1:T∣x0), which we know and can sample from (it is a product of Gaussians, positive everywhere, so the division is safe):
The right-hand side is an upper bound on the negative log-likelihood. It is the negative ELBO: equivalently, the ELBO is a lower bound on the log-likelihood. The bound only involves quantities we can sample and evaluate. Minimising it caps the negative log-likelihood from above, but the two are not the same. The gap is DKL(q(x1:T∣x0)∥pθ(x1:T∣x0)), exactly as in the VAE (how loose is the bound), and it also depends on θ. Write B for the bound and G for the gap. Then −logpθ(x0)=B−G, so a training step changes the negative log-likelihood by ΔB−ΔG. A falling bound alone therefore guarantees nothing: if B drops from 10 to 9 while G shrinks from 3 to 1, the negative log-likelihood rises from 7 to 8. Minimising the bound improves the likelihood only when the bound falls by more than the gap shrinks; if the gap stays the same or grows, the likelihood improves at least as much as the bound.
The algebra is in Appendix A of the DDPM paper (Ho, Jain and Abbeel, 2020, Eq. 5) and is sketched in the callout below. The bound becomes a sum of three kinds of terms:
Eq[does not depend on θDKL(q(xT∣x0)∥p(xT))+t>1∑DKL(q(xt−1∣xt,x0)∥pθ(xt−1∣xt))reconstruction−logpθ(x0∣x1)].
Here Eq averages over the noisy states x1:T∼q(x1:T∣x0) of one image. Each term needs only some of them: the t-th KL depends only on xt, so its average is over xt∼q(xt∣x0), and the reconstruction term depends only on x1. In training we also average over images x0∼q(x0).
Read the three terms one by one.
First term.q(xT∣x0) is the fixed forward process and p(xT) is the fixed standard normal. Nothing here depends on θ, so for training it is a constant. (It is also tiny for a well-chosen schedule: DDPM’s linear schedule reaches αˉ1000≈4×10−5, so q(xT∣x0)≈N(0,I). A toy schedule with T=100 steps and βt rising from 10−5 to 10−2 ends near αˉ100≈0.6 and does not get there.)
Last term.−logpθ(x0∣x1) measures how likely the model is to recreate the image from the slightly noisier x1. At x1 barely any noise is left, so for the derivation we set this term aside. This is a simplification: DDPM keeps the term as a discretized decoder for 8-bit pixel values (Ho, Jain and Abbeel, 2020, §3.3, Eq. 13; callout below), and in its simplified loss the t=1 noise-regression term approximates it (§3.4).
Middle terms. These do the work. Each compares the “true posterior” q(xt−1∣xt,x0) with the model’s reverse diffusion steppθ(xt−1∣xt).
The three states and the three conditional distributions fit into one small graph. xt−1 is noisy, xt is more noisy, and x0 is the original image.
Figure
Forward step, model step and the true posterior
x₀ is the original image, xₜ₋₁ is noisy and xₜ is more noisy. The forward step q(xₜ | xₜ₋₁) adds noise. The model's reverse diffusion step pθ(xₜ₋₁ | xₜ) only sees xₜ. The true posterior q(xₜ₋₁ | xₜ, x₀) goes back to xₜ₋₁ too, but it also uses x₀; it has a closed form, yet it cannot be used for generation, where no image is available. The dotted line groups xₜ and x₀: the true posterior conditions on both.
The forward step q(xt∣xt−1) goes right. The model’s step pθ(xt−1∣xt) goes left, using only xt. The true posterior q(xt−1∣xt,x0) also goes left, but it looks at both xt and the original image x0.
The true posterior has a closed-form expression (derived below). So why do we need a neural network at all? Because the true posterior requires knowing x0. At generation time we have no image; we have to produce one starting from pure noise alone. The network pθ(xt−1∣xt) must therefore imitate the true posterior without seeing x0.
So we train the model’s reverse step to match the true posterior:
Small steps keep the reverse step Gaussian. For a diffusion with small steps, the reverse of a Gaussian Markov chain is also (approximately) Gaussian: as βt→0, the reverse step q(xt−1∣xt) has the same Gaussian functional form as the forward step (Feller, 1949; used by Sohl-Dickstein et al., 2015, §2.2). For finite steps q(xt−1∣xt) is not exactly Gaussian, because it mixes over all images x0 in the data. The posterior conditioned on x0, q(xt−1∣xt,x0), is exactly Gaussian for any βt when t≥2. At t=1 it collapses to the point mass at x0 (β~1=0), which is why that step is handled by the reconstruction term.
Everything is just simpler. KL divergences between Gaussians have closed forms, and sampling from a Gaussian is trivial.
The closed form. Let t≥2. By Bayes’ theorem, q(xt−1∣xt,x0)∝q(xt∣xt−1)q(xt−1∣x0). Both factors are Gaussian in xt−1. The first has the exponent −(xt−αtxt−1)2/(2βt)=−αt(xt−1−xt/αt)2/(2βt) (per coordinate), so as a function of xt−1 it has mean xt/αt and precision αt/βt. The second is N(αˉt−1x0,1−αˉt−1). Completing the square in the product, the precisions add and the mean is the precision-weighted average of the two means:
The numerator simplifies by αt(1−αˉt−1)+βt=αt−αˉt+βt=1−αˉt, using αˉt=αtαˉt−1 and αt+βt=1. Inverting the precision and multiplying out the mean gives
We would still have to predict both μθ and σθ. DDPM simplifies further: fix σθ to a schedule σt, so it is not learned, and only learn the mean. Ho et al. use σt2=βt or σt2=β~t and report similar results (Ho, Jain and Abbeel, 2020, §3.2); Nichol and Dhariwal (2021), §3.1, later learn a variance that interpolates between the two.
With fixed variances, the KL divergence between the two Gaussians depends on θ only through the distance between the means. For q=N(μ~t,β~tI) and pθ=N(μθ,σt2I) in d dimensions, the KL divergence between two 1-D Gaussians, summed over the d coordinates, gives
DKL(q∥pθ)=2σt21μ~t−μθ2+const; 0 if σt2=β~t2d(σt2β~t−1−logσt2β~t).
The same result follows from the general formula for two Gaussians in d dimensions with positive definite covariances,
with Σq=β~tI and Σp=σt2I: the trace gives dβ~t/σt2, the log-determinant ratio dln(σt2/β~t), and the quadratic form ∥μ~t−μθ∥2/σt2.
So in fact the KL term is an L2 loss, 2σt21∥μ~t−μθ∥2, plus a constant. For one pair (xt,x0) the L2 part would be zero with μθ on μ~t, and the whole KL term would vanish only if, in addition, σt2=β~t. But μ~t depends on x0, which the network does not see, and many images are compatible with the same xt. Since μθ sees only xt, the best it can do is the average of μ~t over all images x0 compatible with xt, because the conditional mean minimises the expected squared error:
μθ∗(xt,t)=Eq[μ~t(xt,x0)∣xt],
the average over x0∼q(x0∣xt). By the tower property this is also Eq[xt−1∣xt], the mean of the true reverse step q(xt−1∣xt). At the optimum the KL term is therefore not zero: what remains reflects how much xt leaves the image undetermined.
Figure
With equal variances, the KL divergence is an L2 loss on the means
The model's step pθ = N(μθ, σₜ²) against the true posterior q, drawn for the choice where σₜ² equals the true posterior variance, so both bells have the same width. Then their KL divergence depends only on the gap between the means: KL = (gap)² / (2σₜ²). With the other choice, σₜ² = βₜ, a constant that does not depend on θ is added. The figure shows one fixed pair (xₜ, x₀); training moves μθ towards the true posterior mean, down the parabola on the right. Because μθ does not see x₀, over all images compatible with xₜ it settles on the average of their posterior means. Here σₜ² = β̃ₜ = 1 and the gap is 2.7, so KL = 3.645 nats.
using βt+αt=1. The coefficient of ε is −αt(1−αˉt)βt1−αˉt=−αt1−αˉtβt. Hence
μ~t=αt1(xt−1−αˉtβtε).
The denoiser has access to xt and t, but not to ε. So give the model’s mean exactly the same form and let the network predict only the missing piece, the noise (Ho, Jain and Abbeel, 2020, Eq. 11):
μθ(xt,t)=αt1(xt−1−αˉtβtεθ(xt,t)).
Now both μ~t and μθ(xt,t) are expressed using only xt and a noise. In the difference xt cancels, leaving μ~t−μθ=αt1−αˉtβt(εθ−ε), and the loss becomes
This is Eq. 12 of Ho, Jain and Abbeel (2020), summed over t. Here εθ(xt,t) is the network’s prediction of the noise. As for the means, it cannot recover the particular ε that produced xt, because it does not see x0. The optimum is εθ∗(xt,t)=E[ε∣xt], the average noise consistent with xt; this is the same optimum as μθ∗, since μ~t is an affine function of ε for fixed xt. The denoiser is a noise predictor.
Final simplification: a random timestep per sample#
Summing over all T−1 terms for every training image is too expensive: it costs T−1 network evaluations per image. Instead, pick a random timestep for each sample. Since ∑t=2Tf(t)=(T−1)Et∼Uniform{2,…,T}[f(t)], one uniformly drawn t gives an unbiased estimate of the sum divided by T−1, and a constant factor does not change the minimiser. Averaging also over images x0∼q(x0) and noises ε∼N(0,I), with t∼Uniform{2,…,T}, the final loss is
Pairing timesteps. The estimate stays unbiased however the timesteps of a batch depend on each other, as long as each one on its own is uniform. A variance-reduction trick used in the public DDIM code (Song, Meng and Ermon, 2021) draws t for half of the batch and gives the other half the mirrored steps, t′=T+1−t when t runs over 1,…,T, which are uniform too. Let f(t) be the expected loss at step t. A mirrored pair estimates the mean of f with variance 21[Varf(t)+Cov(f(t),f(t′))], two independent draws with 21Varf(t). If f is monotone in t, then f(t) and f(T+1−t) move in opposite directions, the covariance is negative by Chebyshev’s sum inequality, and the pair wins. Toy example:T=4 and f(t)=1/t, a loss that is largest at the least noisy step. Two independent draws give variance 0.0423, a mirrored pair 0.0109, about 3.9 times less. The per-step loss of a real network is only roughly monotone in t, so the gain in practice is an empirical matter.
The training step. One training step needs no chain at all: draw an image, a timestep and a noise, jump straight to xt with the shortcut, and regress the noise.
Figure
One training step: draw, jump, predict the noise
Each training step draws an image x₀, a random timestep t and a noise ε, jumps straight to xₜ with the closed-form shortcut, asks the network for its estimate εθ(xₜ, t) of the noise, and takes a gradient step on the weighted squared error. The bound-derived objective keeps the weight wₜ; DDPM's unweighted simple loss and the course notebook set wₜ = 1 (and sample t from 1 to T).
Dropping the weight is a deliberate reweighting, not a cosmetic change. With the linear schedule of the paper (T=1000, βt from 10−4 to 0.02) and σt2=βt, the weight simplifies to βt/(2αt(1−αˉt)). It is 0.273 at t=2, falls to a minimum of 0.0050 at t=349, and climbs back to 0.0102 at t=T. The true bound puts about 55 times more weight at t=2 than at the minimum. Lsimple weights every step equally, which shifts effort towards the harder, noisier steps. Ho et al. found that this gives better samples at the price of a slightly worse likelihood (§3.4 and Table 1: FID 3.17 against 13.51 for the true bound, but 3.75 against 3.70 bits per dimension).
Figure
The bound's weight favours the nearly clean steps
Weight wₜ = βₜ² / (2σₜ² αₜ (1 − ᾱₜ)) of the weighted noise-prediction loss for the linear schedule (T = 1000, β from 10⁻⁴ to 0.02); both axes are logarithmic. With σₜ² = βₜ it is 0.273 at t = 2, has its minimum 0.0050 at t = 349 and ends at 0.0102. With σₜ² set to the true posterior variance (dashed) it is always larger, by the ratio of the two variances (it is 0.600 at t = 2), but the gap matters only for the first few dozen steps. DDPM's simple loss uses wₜ = 1 for every step (only its relative shape matters), so compared with the bound it shifts weight towards the noisier steps.
Training never runs the chain, but generation does. Start from pure noise xT∼N(0,I) and apply the learned reverse step T times. Each step samples from pθ(xt−1∣xt)=N(μθ(xt,t),σt2I), using the noise form of the mean derived above, with fresh noise z∼N(0,I) for t>1 and z=0 for t=1 (Ho, Jain and Abbeel, 2020, Algorithm 2):
xt−1=αt1(xt−1−αˉtβtεθ(xt,t))+σtz.
Each step removes the network’s estimate of the noise, rescales, and adds a little fresh noise; the last step returns the mean without extra noise. Generation therefore costs T network evaluations, one per step, which is the main practical drawback of DDPM. Later samplers such as DDIM (Song, Meng and Ermon, 2021) reuse the same trained εθ with far fewer steps.
The derivation treats εθ(xt,t) as a function of two arguments. In practice it is one network for all steps, a U-Net, that receives the timestep as an embedding added inside every block.
Figure
The DDPM denoiser: a U-Net that receives the step t
All 5 steps · press ▶ to go one at a time
The denoising network of DDPM for 32 × 32 images, in the colours of the sequence-model figures: inputs green, internal states blue, learned maps amber, the output red. The encoder (left) halves the resolution from 32 × 32 down to 4 × 4 and the decoder (right) restores it; dashed skip connections join the levels of equal size. The step t enters through a sinusoidal code and an MLP, and its projection is added in every block. Each box stands for two residual blocks; DDPM’s CIFAR-10 model of this shape has 35.7 million parameters.
The noisy image xt enters the encoder. Each level halves the resolution; DDPM adds self-attention at 16×16.
The timestep t becomes a vector of sines and cosines e(t).
A small MLP turns e(t) into m(t), and every residual block adds its own linear projection Akm(t) to its channels.
The decoder doubles the resolution level by level; skip connections bring in the encoder’s features of the same size.
The output εθ(xt,t) has the shape of xt and is compared with the true noise ε.
A separate network per step would mean T=1000 networks, each trained on a thousandth of the updates. Neighbouring steps solve almost the same problem: xt−1 and xt differ by one small step: a rescaling by 1−βt≥0.99 and noise of variance βt≤0.02. DDPM therefore shares the parameters across time and tells the network which step it is at (Ho, Jain and Abbeel, 2020, §4). The step is needed because the same input calls for different outputs at different noise levels.
Example. For data N(0,0.52) in one dimension, ε and xt are jointly Gaussian with covariance 1−αˉt, so the optimal prediction is
E[ε∣xt]=0.25αˉt+1−αˉt1−αˉtxt.
With the linear schedule and xt=1 it is 0.17 at t=10 (αˉt=0.998), where xt is mostly image, and 1.02 at t=500 (αˉt=0.079), where xt is mostly noise.
The output εθ(xt,t) has the shape of the input, one noise value per pixel and channel, so the network solves a dense prediction problem, like segmentation. To tell noise from image detail at one pixel it needs context from far away and precision at the pixel itself. A U-Net (Ronneberger, Fischer and Brox, 2015) provides both. An encoder halves the resolution level by level, so that the coarse levels see the whole image; a decoder doubles it back; and skip connections pass the encoder’s features at each resolution to the decoder level of the same size, where they are concatenated with the upsampled features, so that fine detail reaches the output. Noise-conditional score networks use a U-Net variant for the same reason (Song and Ermon, 2019, §4.1 and App. A.3).
the backbone of an unmasked PixelCNN++, a U-Net built from Wide ResNet residual blocks;
group normalization (Wu and He, 2018) instead of weight normalization: statistics over groups of channels within one image, so that, unlike batch normalization, they do not depend on the other images of the batch;
four feature-map resolutions for 32×32 images (32×32 down to 4×4) and six for 256×256, with two residual blocks per resolution;
self-attention at the 16×16 resolution, between the convolutional blocks;
the timestep added to every residual block through the Transformer sinusoidal embedding;
35.7 million parameters for CIFAR-10, 114 million for LSUN and CelebA-HQ at 256×256, and about 256 million for a larger LSUN Bedroom variant.
A small MLP turns the code into a feature vector, m(t)=W2SiLU(W1e(t)) with SiLU(z)=z⋅sigmoid(z), and every residual block k has its own linear map Ak to its number of channels Ck. The result is added to the block’s feature maps as one bias per channel, the same at every pixel:
hk[c,i,j]←hk[c,i,j]+(Akm(t))c.
Why not feed t as a plain number? A scalar enters the first layer as one more input, and the network has to build every dependence on t from it. The sinusoidal code is a vector whose components turn at rates from 1 down to about 10−4 radians per step, so linear combinations of it can follow both fast and slow changes in t, and nearby steps get nearby codes. The order of the components, interleaved or sines first, does not matter: the next layer is linear and can permute its inputs.
Other ways to give a network the step or another condition:
Scale and shift. The embedding sets a scale and a shift per channel after normalization, h←ys(t)⊙GroupNorm(h)+yb(t): adaptive group normalization (Dhariwal and Nichol, 2021, §3.1). It is the diffusion counterpart of the adaptive instance normalization (AdaIN) with which StyleGAN injects its style vector (Karras, Laine and Aila, 2019, Eq. 1). On ImageNet 128×128 it improved FID from 15.08, for addition followed by group normalization as in DDPM, to 13.06 (their Table 3). Noise-conditional score networks use the same idea with one learned scale and shift per noise level (score matching note).
An extra input channel. Append t/T to the image as a constant channel. This is the simplest option, but the step then enters only the first convolution and has to be carried through every later layer.
A learned vector per step. A table with one trainable vector per step, added to or multiplied with a hidden layer, works for small networks and short chains. The T vectors share nothing, though: each is trained only by the examples drawn at its own step.
Other conditions. A class label or a pooled text embedding can be projected to the size of the time embedding and added to it, as Imagen does (Saharia et al., 2022, §2.5); a whole sequence of text vectors enters through cross-attention (latent diffusion).
DDPM maps the pixel values 0,…,255 linearly to [−1,1] so that the reverse process works on inputs of a consistent scale, starting from the standard normal prior (Ho, Jain and Abbeel, 2020, §3.3). Three reasons make this matter:
The chain must end at the prior. The forward process removes the image only by shrinking it, xT=αˉTx0+1−αˉTε, and with DDPM’s schedule αˉT=0.0064. For values in [−1,1] the leftover image is at most 0.0064, negligible next to noise of standard deviation 1. For raw values in [0,255] it would reach 1.6: xT would still show the image, and sampling from N(0,I) would start from the wrong distribution.
Every step sees inputs of the same size. By the variance recursionVar(xt)=αˉtVar(x0)+1−αˉt, which lies between Var(x0) and 1 when Var(x0)≤1, as values in [−1,1] guarantee. One network can then serve all steps; for raw pixel values the variance would fall from thousands to 1 along the chain.
The data are centred like the prior, and the discretized decoder has bins of width 2/255 with its open edge bins at ±1.
To display an image, x is mapped back by 255(x+1)/2 after clipping to [−1,1]. Noisy states leave the interval routinely: a pixel with x0=0.9 at a step with αˉt=0.5 has xt∼N(0.64,0.5), which falls outside [−1,1] with probability 0.31. Without the clipping, the conversion to 8-bit integers turns such values into wrong colours.
Training in practice: averaged weights and clipped gradients#
Two stabilisers are common in diffusion training loops.
An exponential moving average of the weights. Keep a second copy θˉ of the parameters and update it after every optimizer step:
θˉ←μθˉ+(1−μ)θ.
Unrolled over n steps, θˉ is a weighted average of the past iterates with weights (1−μ)μn−k, plus μn times its starting value: roughly an average over the last 1/(1−μ) steps. Training continues with θ; samples are drawn with θˉ. The reason is the noise of the gradient: every example brings its own image, timestep and noise, so the iterates θ jitter around a good region, and their average lies closer to its centre. DDPM uses μ=0.9999 (Appendix B), an average over about 10,000 steps. Nichol and Dhariwal (2021) swept the rate on class-conditional ImageNet 64×64 and found 0.9999 and 0.99995 best (their Figure 17). Worked example: the starting value keeps the weight μn, which is 0.999910000=0.37 after ten thousand steps and 0.999950000=0.0067 after fifty thousand. The averaged weights are therefore worth using only after training has run several times longer than the window. With μ=0.9 the window is about 10 steps, too short to average out much.
Gradient clipping. Before each step, rescale the gradient g when its norm exceeds a threshold c, g←g⋅min(1,c/∥g∥) (training background). The direction is kept and only rare large gradients are shortened: with c=1, a gradient of norm 4 is divided by 4, and one of norm 0.5 is left as it is. The loss of a batch varies with the timesteps it happens to contain, and clipping keeps an unusual batch from moving the weights far.
DDPM’s settings (Appendix B): Adam with learning rate 2×10−4 (2×10−5 at 256×256), batch size 128 for CIFAR-10 and 64 for larger images, dropout 0.1 on CIFAR-10 and none elsewhere, random horizontal flips, the EMA rate 0.9999 and T=1000. The CIFAR-10 model trained for 800K steps at 21 steps per second on a TPU v3-8, about 10.6 hours; sampling a batch of 256 images took 17 seconds.
Bound.−logpθ(x0) is at most the negative ELBO; the gap is a KL divergence, so lowering the bound does not by itself lower the negative log-likelihood.
Posterior targets. The bound splits into KL terms that compare each reverse step pθ(xt−1∣xt) with the Gaussian true posterior q(xt−1∣xt,x0), whose mean μ~t is known in closed form.
Weighted noise loss. With fixed variances each KL term is an L2 distance between means plus a constant, and with μθ written through a noise predictor it becomes 2σt2αt(1−αˉt)βt2∥ε−εθ(xt,t)∥2.
Reweighting.Lsimple drops that weight. It is a different objective, not the bound: it shifts effort towards the noisier steps and gives better samples at a slightly worse likelihood.
One training step. Draw x0, t and ε, form xt=αˉtx0+1−αˉtε, and take a gradient step on ∥ε−εθ(xt,t)∥2.
Sampling. From xT∼N(0,I) apply xt−1=μθ(xt,t)+σtz for t=T,…,1, with no noise at the last step.
The diffusion notebook of the HSE-LAMBDA Deep Generative Models repository (pinned commit; cell numbers count every cell, markdown included, from 1; no saved outputs) trains two versions of the reverse process on a 2-D Swiss roll of 10,000 points with T=100 and a sigmoid schedule between 10−5 and 10−2 (β1≈3.5×10−5, β100≈1.0×10−2). Code indices are 0-based, so code step t = 0 is t=1 in the formulas.
Cells 17–18 (restated in cell 37): the true posterior. q_posterior_mean_variance uses the coefficients βtαˉt−1/(1−αˉt) and (1−αˉt−1)αt/(1−αˉt) and the variance β~t, as derived above. At code step t = 0 the variance is β~1=0, the degenerate case of the closed form, and the “clipped” log-variance still takes log0=−∞. In loss_likelihood_bound (cell 31) this makes the KL infinite whenever a batch contains code step t = 0, so the loss printed in cell 33 can be inf; the gradients stay finite, because the infinite part does not depend on θ. Using β~2 for the first entry avoids this.
Cells 19–24: the original diffusion model of Sohl-Dickstein et al. One network is shared by all steps and predicts a mean and a log-variance. Its ConditionalLinear layers multiply the output of a linear layer by a learned vector per step, γt⊙(Wh+b), with γ an nn.Embedding(n_steps, 128) initialised uniformly on [0,1]: the learned vector per step, not a sinusoidal code. p_sample and p_sample_loop run the reverse chain.
Cells 25–29: the bound. Cell 26 writes the bound as the per-step log-ratios of the first callout. Cell 27 then gives two more forms, neither quite right: the first (a prior term, all T KL terms and one forward entropy) is not the standard bound, and the second puts the entropy terms of the 2015 form with the signs they have in K, the lower bound on the log-likelihood, into a loss that should be −K, next to an extra prior term. normal_kl (cell 28) is the KL between diagonal Gaussians and entropy the Gaussian entropy 21(1+ln2π)+21lnσ2 per dimension. compute_loss (cell 29) gets the signs right: it returns (T⋅KL+Hq(X1∣X0)−Hq(XT∣X0)+Hp(XT)−HN(0,1))/ln2, averaged over the batch and the two coordinates: a one-step estimate of −K minus the entropy of a unit Gaussian, in bits per dimension, i.e. Sohl-Dickstein’s −(K−Lnull). The factor T scales a single random step up to the sum, which in the bound has T−1 terms (t=2,…,T) and does not include code step t = 0.
Cells 30–33: training at random timesteps. Cell 30 explains the mirrored timesteps with t=5↔94 in code indices (t=6↔95 in the formulas): loss_likelihood_bound draws 65 steps and appends their partners n_steps - t - 1, keeping 128. The loop of cell 33 uses Adam with learning rate 10−3, batches of 128 (79 per pass over the 10,000 points), 5001 passes, and clips the gradient norm at 1.0.
Cells 34–36: DDPM.ConditionalModelDDPM (three ConditionalLinear layers) predicts the noise, and p_sample_ddpm implements the sampling step above with σt=βt. It adds fresh noise at every step, including the last one, where Algorithm 2 adds none. The text of cell 34 says that β~t is usually preferred for its better results; Ho et al. found the two choices similar (§3.2).
Cells 38–41: the variational loss with the discretized decoder at the first step. Cell 38 gives the KL between Gaussians with full covariances. discretized_gaussian_log_likelihood (cell 39) implements Eq. 13, with Φ approximated by 21(1+tanh(2/π(x+0.044715x3))) (largest error 1.8×10−4) and the open edge bins applied for x<−0.999 and x>0.999. loss_variational (cell 41) averages the KL and the decoder term over dimensions and divides by ln2, bits per dimension, and picks the decoder term for code step t = 0 with torch.where. The loss is shown for illustration and never trained. As written it calls p_mean_variance with the global model, the mean-and-variance network of cell 33, since the DDPM model returns only a noise estimate. The decoder also assumes 8-bit pixels in [−1,1], while the Swiss roll is continuous and about 31% of its points have a coordinate outside [−1,1], where the edge rule assigns them a whole tail.
Cells 42–43: noise_estimation_loss, the unweighted Lsimple, (noise - predicted_noise).square().mean(), with the same mirrored timesteps. The text of cell 42 calls Lsimple a direct minimisation of the KL terms; as shown above, it reweights them.
Cells 44–48: the moving average of the weights and the DDPM training loop (Adam 10−3, batch 128, 1000 passes, gradient norm clipped at 1.0). The EMA class defaults to μ=0.999, but the loop uses μ=0.9, a window of about 10 updates, shorter than one pass over the data. apply_shadow would copy θˉ into the model but is never called, so sampling uses the raw weights. The plotting cells (33 and 48) show x_seq[10*j], the reverse chain after 10j steps (from x90 down to the final sample), but title the panels q(x100j): wrong scale, wrong direction, and q instead of pθ.
The homework notebook 04_hw_diffusion of the same repository (same cell convention) trains a DDPM on CelebA face photos at 32×32. Most of its code is a template with blanks to fill in, but it keeps saved outputs of a reference run on a Tesla T4 GPU; the numbers below are those outputs.
Cells 9–11: data. Batches of 128 images normalised to [−1,1], as above. Cell 10 prints the image shape as 3×64×64 although image_size = 32, while the grids of cells 11 and 26 show 32×32 images: the saved outputs come from more than one run.
Cells 13–14 restate the forward process, the noise-prediction mean, the two choices σt2=βt or β~t and Algorithms 1 and 2, and name a U-Net or Imagen’s Efficient U-Net as the network. The Efficient U-Net was designed for Imagen’s super-resolution stages: it moves parameters to the low-resolution blocks, scales skip connections by 1/2 and reverses the order of the downsampling and upsampling operations to speed up the forward pass (Saharia et al., 2022, App. B.1).
Cell 16: the DDPM class holds the schedule (n_steps, min_beta, max_beta, left blank), forward draws xt from x0 in one step, and backward calls the network for εθ(xt,t).
Cells 18–19.show_forward noises a batch to 25, 50, 75 and 100% of T. generate_new_images runs Algorithm 2 from xT∼N(0,I), adds noise only while code step t > 0, so not at the last step, as Algorithm 2 prescribes, and writes the chain to a GIF.
Cells 20–21: the U-Net. The markdown lists the options for feeding t (AdaIN as in StyleGAN, an extra channel, a sinusoidal encoding) and takes the sinusoidal one, adding that a projected text embedding could be added the same way (not used here). sinusoidal_embedding(n, d) fills a frozen nn.Embedding table with d=100 sines and cosines for each of the n=1000 steps. _make_te is the Linear–SiLU–Linear MLP that maps the code to the channel count of a block; forward adds the output of te1 to the input image, and the blocks left blank can do the same with their own _make_te maps.
Cells 22–23: the reference model has 650,430 parameters; the count includes the frozen embedding table.
Cells 26–27: forward grids at 25–100% of T, and 16 samples of the untrained network, which are noise.
Cells 29–31: training.training_loop implements Algorithm 1 with nn.MSELoss, i.e. Lsimple, and saves the checkpoint with the lowest training loss of an epoch. The saved log has 1272 batches of 128 per epoch, consistent with the 162,770 images of the CelebA training split. The loss fell from 0.037 after epoch 1 to 0.033 after epoch 16; 200 epochs were planned and the log stops at the start of epoch 17.
Cells 33–36 reload the best checkpoint and generate 100 faces into a GIF; there are no saved samples.
Issues:
show_images (cell 7) maps x to 255(x+1)/2 and casts to uint8 without clipping. Noised images leave [−1,1], so the forward grids of cell 26 show saturated, noise-like colours already at 25% of T; clip to [−1,1] before the cast. How much of the face would remain depends on the schedule, which the template leaves blank; with DDPM’s linear schedule αˉ250=0.52, so more than half of the signal variance is left at 25%.
The dataset class of cell 10 returns bare images, so a batch from the loader is a tensor of images, and batch[0] in show_first_batch, show_forward and training_loop picks the first image instead of the batch. The template comes from a dataset that returns (image, label) pairs; with this dataset, use the batch itself.
Cell 33 rebuilds the model as DDPM(UNet(), n_steps=n_steps, device=device): min_beta and max_beta are not passed, so the class defaults are used, and UNet() takes its default 1000 steps. A schedule other than the defaults has to be passed again by hand.
sinusoidal_embedding computes d frequencies wk=10000−2k/d, k=0,…,d−1, but fills only d values. The Transformer encoding uses the first d/2 frequencies, each once in a sine and once in a cosine. Taking the cosines from the second half instead gives frequencies of at most 10−4, for which cos(wkt)≥0.995 over all t≤1000: those components barely change.
DDPM reaches its loss through a likelihood bound. The next note reaches the same loss without one: it learns the gradient of the log-density of the noised data and samples by following it, and shows that the noise predictor of DDPM is that gradient up to a known scale.
C. Saharia, W. Chan, S. Saxena, L. Li, J. Whang, E. Denton, S. K. S. Ghasemipour, B. K. Ayan, S. S. Mahdavi, R. G. Lopes, T. Salimans, J. Ho, D. J. Fleet, M. Norouzi. Photorealistic text-to-image diffusion models with deep language understanding. NeurIPS, 2022. Imagen: §2.5 (conditioning) and Appendix B.1 (Efficient U-Net).