DALL-E 3, Midjourney v7, Stable Diffusion XL, FLUX.1, Sora — all the same core algorithm. Add noise step-by-step, then learn to remove it step-by-step. A single A100 trains a competitive model in days; a $0.001 inference call generates a 1024×1024 image in 2 seconds. By the end of this lesson, you'll understand exactly how Sora and Veo 3 work under the hood.
Learning Objectives
After this lesson, you will be able to:
Walk through the forward diffusion process — gradually destroying an image with Gaussian noise — and the reparameterization that lets you sample any timestep in one step
Train a denoising network with the simplified loss L_simple from Ho 2020 — the practical objective that powers every modern diffusion model
Use classifier-free guidance to control fidelity vs diversity, and DDIM to cut sampling steps from 1000 to 20 without quality loss
Pick the right sampler for your task — DDPM for theory, DDIM for speed, DPM-Solver++ / Euler for production
Don't worry if the math feels heavy — once you see the model work in code (a 100-line training loop on MNIST), the equations stop being intimidating and start being mechanical recipes.
The forward process q(x_t | x_) adds Gaussian noise according to a variance schedule β_t (a small positive number, growing from ~10⁻⁴ at t=1 to ~0.02 at t=T):
q(xt∣xt−1)=N(xt;1−βtxt−1,βtI)
The killer trick: thanks to the Gaussian closure under composition, you can sample any x_t directly from x_0 in one step (no need to iterate t times):
Variance schedules matter. Linear (used in DDPM) is simple but spends too many steps near pure noise. Cosine schedule (Nichol & Dhariwal 2021) keeps signal alive longer; sigmoid is yet another option. Modern training cares about the signal-to-noise ratio at each step.
Drag the timestep slider to watch the forward process destroy an image and the reverse process rebuild it.
The reverse process is parameterized by a neural network θ:
pθ(xt−1∣xt)=N(xt−1;μθ(xt,t),Σθ(xt,t))
The architecture is almost always a U-Net: encoder downsamples through a few stages, decoder upsamples back, with skip connections preserving spatial detail. Time t is injected via sinusoidal embedding + MLP into every block. For text-to-image, the conditioning text is injected via cross-attention.
The simplicity is what made diffusion practical. No GAN training instability, no mode collapse, no balance between generator and discriminator. Just minimize MSE.
What Do You Think?
You train a tiny diffusion model on MNIST with T=1000 timesteps. Training loss converges nicely. What should sampling produce?
The answer: pure noise after 1 step, clean digit after ~1000 reverse steps. Each step removes a tiny slice of noise; you need to walk the entire chain. This is exactly what DDIM (next section) cuts down.
The MSE loss above looks magical — generative modeling reduced to noise prediction. It is not magic; it is a chain of three deliberate simplifications applied to a variational lower bound. This section walks the full derivation. The math-track lesson on stochastic calculus and SDEsStochastic CalculusStochastic calculus extends derivatives to random processes — Brownian motion, Itô integrals, and stochastic differential equations. The math diffusion models, flow matching, and score-based generative models all live in.Learn more → already establishes the forward kernel q(x_t | x_0) and the score-matching objective ∇_x log p_t(x), so we leverage those results rather than re-derive them. For the architectural side — U-Net backbones, time-conditioning, and the engineering tricks that make denoisers train at scale — see the deep-learning track's diffusion-architecture lessonDiffusion Architectures (DL view)The architectural side of diffusion models — U-Net backbones, time-conditioning blocks, attention placement, and the engineering tricks that make denoisers actually train at scale.Learn more →.
The forward process is a fixed Markov chain whose transition kernel is q(x_t | x_{t-1}) = N(√(1-β_t) x_{t-1}, β_t I). Because every step is linear-Gaussian, the marginal at any timestep t admits a closed form (Sohl-Dickstein 2015, Eq. 2.5; Ho 2020, Eq. 4):
Two corollaries we use below: (i) as t → T, ᾱ_t → 0, so q(x_T | x_0) ≈ N(0, I) — independent of x_0; (ii) the forward posterior q(x_{t-1} | x_t, x_0) is also Gaussian in closed form, with mean and variance given by Bayes' rule applied to the two Gaussians q(x_t | x_{t-1}) and q(x_{t-1} | x_0).
Maximum likelihood asks us to maximize log p_θ(x_0), which is intractable because it integrates over all noise trajectories. Following Sohl-Dickstein 2015 (Eq. 11) and the standard variational argument, we lower-bound it:
So minimizing the ELBO reduces to minimizing the KL divergences L_{t-1} for t = 2, …, T (plus the small L_0 term). That is the equation we need to simplify.
3. Collapsing L_ to a squared-norm
The forward posterior q(x_{t-1} | x_t, x_0) is Gaussian (a direct consequence of Bayes' rule on linear-Gaussian conditionals; Ho 2020 Eq. 6–7):
So L_{t-1} reduces to predicting the posterior mean. This is the first big simplification: we no longer match distributions, we match means.
#4. The noise-prediction reparameterization (Ho 2020)
Here is the key move from the DDPM paper. Using the forward identity x_t = √(ᾱ_t) x_0 + √(1-ᾱ_t) ε, we can solve for x_0 = (x_t - √(1-ᾱ_t) ε) / √(ᾱ_t). Substituting into μ̃_t and simplifying (Ho 2020 Eq. 10), the posterior mean becomes a clean function of x_t and ε:
The ELBO has now reduced to a weighted sum of MSEs on noise prediction across timesteps. Every term has the same form w_t · ||ε - ε_θ(x_t, t)||²; only the scalar weight w_t varies with t.
Ho 2020 ran the obvious ablation: keep the noise-prediction parameterization, but drop the w_t weights and uniformly sample t. The result is the loss you saw in the previous section:
A research team trains a DDPM with the principled ELBO (full L_T + Σ w_t · L_{t-1} + L_0) on CIFAR-10. They compare to the same model trained with L_simple. What do they observe?
The noise predictor ε_θ is, up to a deterministic scaling, the score function ∇_x log p_t(x) introduced in the math-track stochastic-calculus lesson. From the forward identity x_t = √(ᾱ_t) x_0 + √(1-ᾱ_t) ε, the marginal density q(x_t | x_0) is a Gaussian with mean √(ᾱ_t) x_0 and variance (1-ᾱ_t) I, so its score is:
So L_simple is also (a weighted version of) denoising score matching — the exact training objective covered in the math-track lesson on SDEs. Same loss, same network, two derivations.
#Classifier-Free Guidance: The Sampling Superpower
For conditional generation (text → image), the model takes both x_t and a condition c (e.g., text embedding). Classifier-free guidance trains a single model that handles both conditional and unconditional inputs (by sometimes setting c = ∅ during training, ~10% of the time). At sampling time, you blend the two:
ε~(xt,c)=(1+w)⋅εθ(xt,c)−w⋅εθ(xt,∅)
This is THE knob users tune. Stable Diffusion default is cfg_scale=7.5. Push to 15+ for stronger prompt adherence; drop to 3-5 for more creative variations.
Vanilla DDPM sampling needs ~1000 steps. DDIM (Song 2020) reformulates the reverse process so you can take larger steps and still get coherent results:
With DDIM you can use 20-50 steps (skipping the rest) and get quality close to 1000-step DDPM. Even better: modern samplers like DPM-Solver++ (Lu 2022), Euler-A, and Heun push down to 15-25 steps with no perceptible loss. ComfyUI / A1111 expose dozens of samplers — they're all variants of solving the same probability-flow ODE more efficiently.
Decision rubric: DDIM for reproducibility experiments. DPM-Solver++ for production speed. Euler-A for slight artistic randomness. LCM/Turbo for real-time interactive use cases.
DDPM, DDIM, SMLD, VP-SDE, VE-SDE — six years of diffusion papers each invented its own notation. Karras et al. 2022, "Elucidating the Design Space of Diffusion-Based Generative Models" (EDM), collapsed all of them into one framework where the noise level σ is the fundamental variable, not the discrete timestep t. Every modern repo — k-diffusion, ComfyUI sampling, EDM2, Stable Diffusion 3 / FLUX preconditioning — uses EDM-style parameterization. Learning it once unlocks the rest of the field.
#1. Reparameterize: predict the clean image directly
In DDPM the network predicts noise ε at timestep t. EDM instead asks the network to predict the clean imageD_θ(x; σ) directly, where σ ∈ ℝ₊ is the current noise level. There are no discrete timesteps — σ is a continuous knob from σ_min ≈ 0.002 to σ_max ≈ 80. This single change makes every previous diffusion variant a special case of EDM by a coordinate change.
The input to the network is x = x_0 + σ · n (clean image plus noise of standard deviation σ, with n ~ N(0, I)), and the prediction target is x_0 itself.
A raw network operating across σ ∈ [0.002, 80] would see wildly different input magnitudes and have to predict wildly different output magnitudes. EDM wraps a raw network F_θ with four σ-dependent scalars so the network always sees unit-variance inputs and always predicts a unit-variance target. The full denoiser is:
The four scalars are not hyperparameters — they fall out of one constraint: the input to F_θ should have unit variance for every σ, and the residual (target - c_skip · x) / c_out should also have unit variance. Solve that and you get the formulas above.
The weighting λ(σ) = (σ² + σ_data²) / (σ · σ_data)² is the Karras choice that makes the effective signal-to-noise gradient constant across σ — every noise level contributes the same amount of gradient signal, so the network spends training compute uniformly rather than blowing up on easy low-σ cases.
Instead of sampling discrete timesteps uniformly (DDPM's choice, which over-trains on high-noise steps that are visually trivial), EDM samples log σ ~ N(P_mean, P_std²) with P_mean = -1.2, P_std = 1.2 for natural images. Plotting that distribution puts most of the training mass right around σ ≈ 0.3 — the "interesting" mid-noise regime where most of the perceptual structure is decided. This is one of the largest free quality wins in modern training; SDXL, SD3, FLUX, and EDM2 all use it.
#5. Heun's 2nd-order sampler: halving the discretization error
For sampling, EDM uses Heun's method, a predictor-corrector ODE solver. Each step takes a forward Euler prediction at σ_i, evaluates the denoiser again at the predicted σ_, and averages the two derivatives — cutting local truncation error from O(Δσ²) to O(Δσ³).
EDM also specifies the σ schedule: σ_i = (σ_max^(1/ρ) + (i / (N-1)) · (σ_min^(1/ρ) - σ_max^(1/ρ)))^ρ with ρ = 7. This polynomial spacing concentrates steps where the model is most sensitive (low σ near the end of sampling).
#6. Why every modern repo uses EDM-style parameterization
When you open k-diffusion, ComfyUI's sampling code, EDM2 (Karras 2024 follow-up), Stable Diffusion 3, FLUX, or any 2023+ diffusion codebase, you see the same fingerprints: σ-conditioned networks, c_skip/c_out/c_in/c_noise preconditioning, log-normal σ sampling at train time, Heun or DPM-Solver++ sampling at inference, ρ=7 noise schedule. The EDM framework wasn't a new model — it was a coordinate system that made all the existing diffusion math composable, debuggable, and improvable. Pre-EDM papers had to invent custom training tricks per architecture; post-EDM repos just dial in σ_min, σ_max, σ_data and reuse the same machinery.
Tests · Verify training loss decreases. Verify samples are tensors of shape (n, 1, 16, 16) with values in [-1, 1]. Compare DDPM-1000 vs DDIM-20 visual quality.
Diffusion = forward noising + learned reverse denoising. The forward process is fixed Gaussian noise addition; the reverse process is a U-Net (or DiT) trained to predict the noise that was added at each step
L_simple is the entire training objective. Pick random t, sample noise ε, construct x_t, predict ε from x_t — pure MSE loss, no GAN instability, no balance to maintain
The reparameterization x_t = √(ᾱ_t)·x_0 + √(1-ᾱ_t)·ε is the practical magic. Lets you sample any timestep in one shot, making training dataset-efficient and embarrassingly parallel
Classifier-free guidance is the most important sampling trick. Train one model that handles both conditional and unconditional, then linearly extrapolate at sampling. The CFG scale is the knob every user adjusts
DDIM and modern samplers cut steps from 1000 to 20-50 with no quality loss. DPM-Solver++, Euler-A, and Heun all solve the same probability-flow ODE more efficiently. LCM and Turbo distill further to 1-4 steps with mild quality cost
Why does the simplified DDPM loss L_simple work despite the original formulation involving a complex variational bound?
You now understand the most important generative-modeling paradigm of the 2020s. Next up: Flow Matching — the elegant generalization that replaced DDPM training in FLUX, SD3, and Stable Video Diffusion.