April 21, 2026
We’ve built a complete text-to-image pipeline:
This is DALL-E 1. It works. But it has fundamental limitations that prevented it from scaling to the quality we expect from modern image generators.
Today: the alternative paradigm that replaced it. Diffusion models.
By the end of today, you’ll understand why diffusion won — and it comes down to fixing a problem we’ve been fighting all semester.
Discrete codebooks bottleneck generation in two ways:
1. Finite vocabulary for an infinite visual world. A codebook with 512 entries divides the continuous visual manifold into 512 bins. Fine details between bins are lost.
2. No interpolation between concepts. In a discrete space, there’s no “halfway between code 237 and code 238.” The space between codebook entries is empty.
Continuous VAEs solve this — they map images to a dense, low-dimensional manifold. No discretization, no codebook limits. With perceptual and adversarial losses (L19), reconstructions can be sharp and detailed.
So why didn’t we just use a continuous VAE all along?
The fatal flaw of continuous VAEs: the KL penalty.
The VAE loss has two terms fighting each other:
\[\mathcal{L}_{\text{VAE}} = \underbrace{\text{Reconstruction}}_{\text{wants complex, detailed latents}} + \beta \cdot \underbrace{D_{KL}(q(\mathbf{z}|\mathbf{x}) \| \mathcal{N}(\mathbf{0}, \mathbf{I}))}_{\text{wants simple Gaussian latents}}\]
Reconstruction wants the encoder to preserve every detail — spread the latents out, use all dimensions, be expressive.
The KL term wants the encoder to collapse everything onto a unit Gaussian — simple, smooth, undifferentiated.
These goals are fundamentally in conflict.
The true posterior \(q(\mathbf{z}|\mathbf{x})\) looks like Swiss cheese — dense regions where training data maps to, surrounded by empty gaps where nothing lives.
The problem appears at generation time. When we sample from \(\mathcal{N}(\mathbf{0}, \mathbf{I})\), we frequently land in the holes — regions of the latent space that the decoder has never seen during training.
The result: blurry, incoherent, artifacted generations. The decoder doesn’t know what to do with latent codes from the holes. It guesses, and it guesses badly.
We want:
The KL penalty was our tool for making the prior matchable. But it fights reconstruction quality.
What if we dropped the strict KL penalty entirely and let the latent space be as complex as it needs to be?
New problem: how do we sample from an unknown, highly complex continuous distribution?
This is the question diffusion answers.
Diffusion doesn’t assume a shape for the prior. It learns it.
Instead of forcing the encoder to produce \(\mathcal{N}(\mathbf{0}, \mathbf{I})\) directly — which creates the prior hole — diffusion takes a completely different approach:
Gradually transform ANY distribution into \(\mathcal{N}(\mathbf{0}, \mathbf{I})\) through a fixed noising process. Then learn to reverse it.
The prior is still Gaussian at \(t = T\) — we haven’t abandoned that. We just don’t force the encoder to get there in one impossible step. Instead, we spread that work across \(T\) tiny, manageable steps.
The VAE asks the encoder to teleport the data to a Gaussian in one jump.
Diffusion says: walk there slowly, one small step at a time, and learn to retrace your steps.
Two processes:
Forward process (fixed, no learning): gradually add noise to data until it becomes pure Gaussian noise. This is the “walk to the Gaussian.”
Reverse process (learned): gradually remove noise from Gaussian noise until it becomes data. This is “retracing your steps.”
If we can learn the reverse process, we can:
No prior holes. The Gaussian at \(t = T\) is reached by actually walking there, so the reverse path exists and is learnable.
Visualize what the forward process does to the data distribution:
The manifold gets smeared from a concentrated surface into a diffuse cloud that fills the entire space. Every point in the space is now reachable.
Diffusion seems to come out of left field. Why would “add noise then learn to remove it” be a good generative model?
The idea comes from three independent intellectual threads that converged around 2020:
1. Physics — non-equilibrium thermodynamics.
A drop of ink in water spreads until it’s uniformly dissolved. This is thermodynamic diffusion — a structured state dissolving into maximum entropy equilibrium. The process is gradual, Markovian, and well-understood mathematically.
Sohl-Dickstein et al. (2015) asked: if the forward process (structure \(\to\) noise) follows known physics, can we learn the reverse (noise \(\to\) structure)?
This is why it’s called “diffusion.” It’s not a metaphor — the forward process is literally thermodynamic diffusion applied to data.
2. Statistics — score matching and Langevin dynamics.
The score function \(\nabla_\mathbf{x} \log p(\mathbf{x})\) is the gradient of the log-density at any point. Think of it as a vector field that points “uphill” toward higher-density regions of the data.
If you know the score everywhere, you can generate samples by following the gradient uphill — this is Langevin dynamics, a random walk biased toward high-density regions.
Song & Ermon (2019) showed: train a neural network to estimate the score at multiple noise levels.
The deep connection we’ll see later: the noise prediction \(\boldsymbol{\epsilon}_\theta(\mathbf{x}_t, t)\) that diffusion learns IS the score function, up to a scaling factor. Predicting noise and estimating the gradient of the log-density are mathematically equivalent.
3. Representation learning — denoising autoencoders.
Vincent et al. (2008) showed that training a network to denoise corrupted inputs implicitly learns the data distribution. The optimal denoising function points toward the data manifold. You saw this in your own autoencoders early in the semester!
Diffusion extends this from one noise level to a continuum of noise levels — it’s a denoising autoencoder trained at every scale simultaneously.
Ho et al. (2020) — the DDPM paper — unified all three threads into one clean framework. That’s the formulation we’re learning today.
Define the forward process as a Markov chain — each step only depends on the previous step:
\[q(\mathbf{x}_t | \mathbf{x}_{t-1}) = \mathcal{N}(\mathbf{x}_t; \sqrt{1 - \beta_t}\,\mathbf{x}_{t-1},\; \beta_t \mathbf{I})\]
Why shrink?
Without it, the image would get brighter and brighter as we add noise.
The shrinking keeps the overall magnitude constant while the noise gradually overwhelms the signal.
\[q(\mathbf{x}_t | \mathbf{x}_{t-1}) = \mathcal{N}(\mathbf{x}_t; \sqrt{1 - \beta_t}\,\mathbf{x}_{t-1},\; \beta_t \mathbf{I})\]
\(\beta_t\) is the noise schedule — it controls how much noise we add at each step.
Typical values: \(\beta_1 = 0.0001\), \(\beta_T = 0.02\), linearly spaced. Tiny amounts of noise, applied 1000 times.
The key trick: we don’t need to iterate \(T\) steps to get to timestep \(t\).
Define: \(\alpha_t = 1 - \beta_t\) (the “signal retention” at step \(t\))
Define: \(\bar{\alpha}_t = \prod_{s=1}^{t} \alpha_s\) (the cumulative signal survival rate — how much of the original image survives after \(t\) steps)
Then we can jump directly to any timestep in one shot:
\[q(\mathbf{x}_t | \mathbf{x}_0) = \mathcal{N}(\mathbf{x}_t;\; \sqrt{\bar{\alpha}_t}\,\mathbf{x}_0,\; (1 - \bar{\alpha}_t)\mathbf{I})\]
Or equivalently, using the reparameterization trick from VAEs:
\[\mathbf{x}_t = \sqrt{\bar{\alpha}_t}\,\mathbf{x}_0 + \sqrt{1 - \bar{\alpha}_t}\,\boldsymbol{\epsilon}, \quad \boldsymbol{\epsilon} \sim \mathcal{N}(\mathbf{0}, \mathbf{I})\]
In plain English: the noisy image at step \(t\) is a weighted mix of the clean image and pure noise.
\(\sqrt{\bar{\alpha}_t}\) controls how much signal survives; \(\sqrt{1 - \bar{\alpha}_t}\) controls how much noise is added.
The weights are designed so the total variance stays constant — we’re not making the image “louder,” we’re replacing signal with noise.
Why this matters for training: to create a noisy image at step 500, we don’t have to add noise 500 times sequentially. We just plug in \(\bar{\alpha}_{500}\), generate one noise sample, and mix. One multiplication, one addition, done.
The forward process is fixed and known. The reverse process is what we need to learn.
In plain English: given a noisy image, make it slightly less noisy. Do this 1000 times in a row, and you go from static to a photograph.
The true reverse step \(q(\mathbf{x}_{t-1} | \mathbf{x}_t)\) is intractable — computing it requires knowing the entire data distribution \(p(\mathbf{x}_0)\), which is the thing we’re trying to learn. So we approximate it with a neural network.
The question: what should the neural network predict?