Type at least 2 letters. Enter opens the first result, Esc closes.

    Generative Models · Lesson 9 · Diffusion and flow matching

    Reverse SDEs, the probability-flow ODE and samplers

    Advanced 115 min 5 interactive figures 3 videos Colab notebook

    Before this lesson: lesson 06 (the score, Langevin dynamics, denoising score matching), lesson 07 (the forward process, ancestral sampling, βt\beta_t vs β~t\tilde\beta_t) and lesson 08 (conversions between ε\varepsilon, x0x_0 and the score; Tweedie's formula; the EDM form σ=σt/at\sigma = \sigma_t/a_t). For the order of a numerical method, see time stepping in the ML for science track. The lesson gives the minimum about SDEs that you need; the ML for science track treats Itô calculus and the Fokker–Planck equation in depth. Time: about 115 minutes, plus the notebook.


    Why this matters #

    Training fixes the network. The sampler decides how often you call it, and part of the sample quality. The original DDPM used 1,000 network evaluations per sample. With the same trained network and no retraining, DDIM, Heun or DPM-Solver reach similar quality with 10–50 evaluations. Every image, video and audio diffusion library (k-diffusion, diffusers) offers these samplers by name.

    In science the cost multiplies. GenCast, a diffusion model for 15-day global weather ensembles, calls its denoiser 39 times per 12-hour forecast step (a second-order solver on 20 noise levels), for 30 steps and many ensemble members. For a diffusion or flow emulator of physical fields, the sampler is the main control of inference cost. "How do you make sampling 20 times faster without retraining?" is a standard interview question. This lesson is the answer, and it is also the basis of flow matching (lesson 10), guidance (lesson 11) and few-step generation (lesson 12).

    1. Predict first #

    2. The picture #

    Sampling solves a differential equation backward in noise level #

    At a noisy point xx with noise level σ\sigma, the trained network gives the denoiser D(x;σ)≈E[x0∣x]D(x; \sigma) \approx \mathbb E[x_0 \mid x] (lesson 08), and so the score ∇xlog⁡pσ(x)=(D(x;σ)−x)/σ2\nabla_x \log p_\sigma(x) = (D(x;\sigma) - x)/\sigma^2 by Tweedie's formula. A sampler uses these values to move a point from high noise to low noise. It makes three choices:

    • the solver: how to move from one noise level to the next with the help of DD (Euler, Heun, ...);
    • the noise: deterministic steps (an ODE) or fresh random noise in every step (an SDE);
    • the step schedule: at which noise levels to call the network.

    The cost of a sampler is its number of network function evaluations (NFE). The network is by far the most expensive part, so we always compare samplers at equal NFE, not at equal numbers of steps.

    Same distributions, different paths #

    The figure below shows the central fact of this lesson in one dimension. The data are two narrow Gaussians at −1-1 and +1+1. The blue background is the density ptp_t of the noised data at each time tt (time runs from data at t=0t = 0 to noise at t=1t = 1). Both samplers start from the same noise points at t=1t = 1 and run backward to t=0t = 0. The score is computed exactly, so no network is involved.

    Interactive figure: loading…

    Move the time slider and compare the two histograms with the dashed exact density ptp_t. Both the ODE particles (magenta) and the SDE particles (orange) follow ptp_t at every time, and their standard deviations agree with the exact value in the panel. But single paths differ: each magenta path is fixed by its start point, while the orange paths are random. Two orange paths from nearby start points can end at different modes. Press New noise to draw new start points. The score is exact (computed from the known mixture), not learned.

    So the two samplers agree on everything a sampler must get right (the distribution at each time) and disagree on the paths. Section 3 proves this with the Fokker–Planck equation.

    Three samplers in one sentence each #

    • Ancestral sampling (DDPM): take one small step down with the posterior mean, then add posterior noise. This is a discretization of the reverse SDE.
    • DDIM: predict x^0\hat x_0, then put it back at the next, lower noise level, mixing the predicted noise and some fresh noise. With no fresh noise the step is deterministic.
    • ODE solvers (Euler, Heun, DPM-Solver): integrate the probability-flow ODE with a standard numerical method. DDIM without fresh noise is one of them: Euler's method.

    The next figure runs these samplers on the 2D "8 Gaussians" data with the exact score. All differences you see come from the sampler, because the score has no error.

    Interactive figure: loading…

    Choose a sampler, an NFE budget and a step schedule. The blue dots are data, the orange dots are 500 samples, the magenta lines are a few sampling paths (drawn in VP coordinates, which stay bounded). Start with Euler at 10 NFE on the Karras schedule: about 85% of the samples land on a mode. Now switch the schedule to uniform: fewer than 15% of the samples reach a mode even at 50 NFE. Try the reverse SDE at 10 NFE (about 60%), then at 50 NFE. Try Heun: below about 15 NFE its second slope is taken where the denoiser changes fast, and it fails; from 20 NFE it works. The score is exact, not learned.

    Quick check

    The reverse SDE and the probability-flow ODE start from the same noise point xTx_T. Which statement is true?

    3. Definitions and main results #

    The forward process as an SDE #

    Lesson 07 adds noise in TT discrete steps: xt=1−βt xt−1+βt ztx_t = \sqrt{1 - \beta_t}\,x_{t-1} + \sqrt{\beta_t}\,z_t. Let the number of steps grow and each step shrink. Write βt=β(t) Δt\beta_t = \beta(t)\,\Delta t for a smooth function β(t)\beta(t) and a small time step Δt\Delta t. Then 1−β(t)Δt≈1−12β(t)Δt\sqrt{1 - \beta(t)\Delta t} \approx 1 - \tfrac12\beta(t)\Delta t, so

    xt−xt−Δt≈−12β(t) xt−Δt Δt+β(t)  Δt zt.x_{t} - x_{t - \Delta t} \approx -\tfrac12 \beta(t)\, x_{t-\Delta t}\, \Delta t + \sqrt{\beta(t)}\;\sqrt{\Delta t}\, z_t .

    A Gaussian increment with variance Δt\Delta t is an increment of Brownian motion ww: dw=dt zdw = \sqrt{dt}\,z. In the limit we get a stochastic differential equation (SDE):

    dx=f(x,t) dt+g(t) dw,heref(x,t)=−12β(t) x,g(t)=β(t).dx = f(x, t)\,dt + g(t)\,dw, \qquad \text{here} \quad f(x, t) = -\tfrac12\beta(t)\,x, \quad g(t) = \sqrt{\beta(t)} .

    The drift ff is the deterministic part of the motion; the diffusion coefficient gg scales the random part. This is the variance-preserving (VP) SDE (Song et al. 2021). Its marginals are the ones you know: xt=atx0+σtεx_t = a_t x_0 + \sigma_t\varepsilon with at=exp⁡(−12∫0tβ(s) ds)a_t = \exp\big(-\tfrac12\int_0^t \beta(s)\,ds\big) and σt2=1−at2\sigma_t^2 = 1 - a_t^2.

    The variance-exploding (VE) SDE has no drift, only noise: xt=x0+σ(t)εx_t = x_0 + \sigma(t)\varepsilon, which is the SDE

    dx=dσ2(t)dt  dw.dx = \sqrt{\frac{d\sigma^2(t)}{dt}}\;dw .

    (Variances of independent increments add, so the noise added between tt and t+dtt + dt must have variance dσ2d\sigma^2.) EDM uses the VE SDE with σ(t)=t\sigma(t) = t, so g(t)=2tg(t) = \sqrt{2t}: the time is the noise level.

    Euler–Maruyama is the simplest way to simulate an SDE. On a grid with step hh:

    xn+1=xn+f(xn,tn) h+g(tn) h  zn,zn∼N(0,I).x_{n+1} = x_n + f(x_n, t_n)\,h + g(t_n)\,\sqrt{h}\; z_n, \qquad z_n \sim \mathcal N(0, I).

    It is Euler's method for the drift plus a Gaussian kick with the variance g2hg^2 h that the noise adds in time hh. Exercise 1 of the notebook checks that it reproduces the closed-form marginals of the VP SDE.

    Theorem 1 (reverse-time SDE, Anderson 1982) #

    Intuition first. Run the forward SDE backward. The noise must now remove the spread it added, which no random kick can do on average. The fix is a drift toward high density: the score ∇log⁡pt\nabla\log p_t points from a point toward where the density of ptp_t is larger. A drift of size g2∇log⁡ptg^2\nabla\log p_t exactly undoes the spreading that noise of strength gg causes, while a fresh noise term keeps the spread right.

    Theorem. Let xx solve the forward SDE dx=f(x,t) dt+g(t) dwdx = f(x, t)\,dt + g(t)\,dw on [0,T][0, T] with x(0)∼p0x(0) \sim p_0 and marginals ptp_t (smooth and positive). Then the process that runs from t=Tt = T to t=0t = 0,

    dx=[f(x,t)−g(t)2 ∇xlog⁡pt(x)] dt+g(t) dwˉ,x(T)∼pT,dx = \big[f(x, t) - g(t)^2\,\nabla_x \log p_t(x)\big]\,dt + g(t)\,d\bar w , \qquad x(T) \sim p_T ,

    has marginal ptp_t at every time. Here dt<0dt < 0 (time runs backward) and wˉ\bar w is a Brownian motion in reversed time.

    In practice, one Euler–Maruyama step from tt down to t−ht - h (with h>0h > 0) is

    xt−h=xt−[f(xt,t)−g(t)2 s(xt,t)] h+g(t)h  z.x_{t - h} = x_t - \big[f(x_t, t) - g(t)^2\, s(x_t, t)\big]\,h + g(t)\sqrt{h}\; z .

    The score enters with a plus sign: +g2s h+g^2 s\,h pushes the point toward high density. For the VP SDE the step is xt+[12β(t) xt+β(t) s(xt,t)]h+β(t)h  zx_t + \big[\tfrac12\beta(t)\,x_t + \beta(t)\,s(x_t, t)\big]h + \sqrt{\beta(t)h}\;z.

    Proof (with the Fokker–Planck equation)

    We prove that the marginals agree, which is all a sampler needs. (Anderson proved more: the whole reversed path has the law of the forward path run backward.)

    1. The Fokker–Planck equation. The density ptp_t of the solution of dx=f dt+g dwdx = f\,dt + g\,dw satisfies
    ∂tp=−∇⋅(f p)+12g2Δp.\partial_t p = -\nabla\cdot(f\,p) + \tfrac12 g^2 \Delta p .

    The first term moves mass along the drift; the second spreads it like heat. (A derivation is in the ML for science track; here we use it as a known fact.) 2. Reverse the clock. Let τ=T−t\tau = T - t and qτ=pT−τq_\tau = p_{T - \tau}. Then ∂τq=−∂tp=∇⋅(f q)−12g2Δq\partial_\tau q = -\partial_t p = \nabla\cdot(f\,q) - \tfrac12 g^2 \Delta q. 3. A candidate. An SDE dy=b dτ+g dwτdy = b\,d\tau + g\,dw_\tau in the clock τ\tau has the Fokker–Planck equation ∂τq=−∇⋅(b q)+12g2Δq\partial_\tau q = -\nabla\cdot(b\,q) + \tfrac12 g^2\Delta q. 4. Match the two. We need −∇⋅(b q)+12g2Δq=∇⋅(f q)−12g2Δq-\nabla\cdot(b\,q) + \tfrac12 g^2 \Delta q = \nabla\cdot(f\,q) - \tfrac12 g^2\Delta q, that is −∇⋅(b q)=∇⋅(f q)−g2Δq-\nabla\cdot(b\,q) = \nabla\cdot(f\,q) - g^2\Delta q. 5. The key identity. Δq=∇⋅(∇q)=∇⋅(q ∇log⁡q)\Delta q = \nabla\cdot(\nabla q) = \nabla\cdot(q\,\nabla\log q), because ∇log⁡q=∇q/q\nabla \log q = \nabla q / q. So the choice b=−f+g2∇log⁡qb = -f + g^2\nabla\log q gives −∇⋅(b q)=∇⋅(fq)−g2∇⋅(q∇log⁡q)=∇⋅(fq)−g2Δq-\nabla\cdot(b\,q) = \nabla\cdot(f q) - g^2\nabla\cdot(q\nabla\log q) = \nabla\cdot(fq) - g^2\Delta q. ✓ 6. Uniqueness. Both processes start from pTp_T and their densities solve the same equation, so (under the usual uniqueness of solutions) they are equal at every τ\tau. 7. Written in the original clock (dτ=−dtd\tau = -dt), the drift b=−f+g2∇log⁡ptb = -f + g^2\nabla\log p_t becomes f−g2∇log⁡ptf - g^2\nabla\log p_t multiplying a negative dtdt: the formula of the theorem. ■\blacksquare

    Quick check

    For the VE SDE (f=0f = 0), one Euler–Maruyama step of the reverse SDE goes from noise level tt down to t−ht - h. Which drift term does it add to xtx_t?

    Theorem 2 (the probability-flow ODE, Song et al. 2021) #

    Theorem. The ordinary differential equation

    dxdt=f(x,t)−12g(t)2 ∇xlog⁡pt(x)\frac{dx}{dt} = f(x, t) - \tfrac12 g(t)^2\,\nabla_x\log p_t(x)

    has the same marginals ptp_t as the forward SDE: if x(T)∼pTx(T) \sim p_T and you solve it backward, then x(t)∼ptx(t) \sim p_t for every tt (and the same holds forward from x(0)∼p0x(0) \sim p_0).

    Proof.

    1. Write the diffusion term of the Fokker–Planck equation as a flux, with the identity of step 5 above: 12g2Δp=∇⋅(12g2 p ∇log⁡p)\tfrac12 g^2\Delta p = \nabla\cdot\big(\tfrac12 g^2\,p\,\nabla\log p\big).
    2. Then the Fokker–Planck equation reads ∂tp=−∇⋅((f−12g2∇log⁡p) p)\partial_t p = -\nabla\cdot\Big(\big(f - \tfrac12 g^2\nabla\log p\big)\,p\Big).
    3. This is a continuity equation ∂tp+∇⋅(p u)=0\partial_t p + \nabla\cdot(p\,u) = 0 with the velocity u=f−12g2∇log⁡pu = f - \tfrac12 g^2\nabla\log p, and no diffusion term. A continuity equation is exactly the equation for the density of points that move by the ODE dx/dt=u(x,t)dx/dt = u(x, t) (lesson 04, continuous flows).
    4. Same equation and same initial density, so the same marginals. ■\blacksquare

    The ODE moves mass like a fluid: it never adds randomness, it only pushes points along a velocity field. The factor 12\tfrac12 is not optional: without it the ODE removes the spread twice as fast as the noise added it. (The notebook shows this for Gaussian data: from σ=80\sigma = 80, the right ODE ends with standard deviation 1, the ODE without the 12\tfrac12 with standard deviation 0.0125.)

    A whole family of samplers. The same flux trick shows that for every γ(t)≥0\gamma(t) \ge 0 the backward process

    dx=[f−12(g2+γ2)∇log⁡pt] dt+γ(t) dwˉdx = \Big[f - \tfrac12\big(g^2 + \gamma^2\big)\nabla\log p_t\Big]\,dt + \gamma(t)\,d\bar w

    has the marginals ptp_t. With γ=g\gamma = g you get the reverse SDE; with γ=0\gamma = 0 the probability-flow ODE. (Check: its Fokker–Planck equation in reversed time has the drift term ∇⋅(fq)−12(g2+γ2)Δq\nabla\cdot(fq) - \tfrac12(g^2 + \gamma^2)\Delta q and the diffusion term +12γ2Δq+\tfrac12\gamma^2\Delta q, which add up to ∇⋅(fq)−12g2Δq\nabla\cdot(fq) - \tfrac12 g^2\Delta q, as in step 2 of the proof of Theorem 1.) The amount of noise is a free design choice. One way to see the extra noise: it is Langevin dynamics (lesson 06) at a fixed noise level, added on top of the ODE. Langevin dynamics keeps ptp_t unchanged, so it can be added in any amount.

    The probability-flow ODE in EDM coordinates. Take the VE SDE with σ(t)=t\sigma(t) = t: f=0f = 0 and g2=2tg^2 = 2t. The ODE is dx/dt=−t ∇log⁡pt(x)dx/dt = -t\,\nabla\log p_t(x). Tweedie's formula for x=x0+σεx = x_0 + \sigma\varepsilon is D(x;σ)=x+σ2∇log⁡pσ(x)D(x; \sigma) = x + \sigma^2\nabla\log p_\sigma(x), so ∇log⁡pσ=(D−x)/σ2\nabla\log p_\sigma = (D - x)/\sigma^2 and

      dxdσ=x−D(x;σ)σ  \boxed{\;\frac{dx}{d\sigma} = \frac{x - D(x; \sigma)}{\sigma}\;}

    The slope points away from the denoiser output. Read backward (decreasing σ\sigma), the point moves toward DD. This form is the one used by EDM and by most modern samplers.

    Ancestral sampling is a discretization of the reverse SDE #

    The DDPM step of lesson 07 is xt−1=11−βt(xt−βt1−αˉtε^)+βt zx_{t-1} = \frac{1}{\sqrt{1 - \beta_t}}\big(x_t - \frac{\beta_t}{\sqrt{1 - \bar\alpha_t}}\hat\varepsilon\big) + \sqrt{\beta_t}\,z. The score is s=−ε^/1−αˉts = -\hat\varepsilon/\sqrt{1 - \bar\alpha_t}, so the step is xt−1=(xt+βt s)/1−βt+βt zx_{t-1} = (x_t + \beta_t\,s)/\sqrt{1 - \beta_t} + \sqrt{\beta_t}\,z. With 1/1−βt=1+12βt+O(βt2)1/\sqrt{1 - \beta_t} = 1 + \tfrac12\beta_t + O(\beta_t^2):

    xt−1=xt+12βt xt+βt s(xt,t)+βt z+O(βt2).x_{t-1} = x_t + \tfrac12\beta_t\,x_t + \beta_t\,s(x_t, t) + \sqrt{\beta_t}\,z + O(\beta_t^2).

    With βt=β(t)Δt\beta_t = \beta(t)\Delta t this is the Euler–Maruyama step of the reverse VP SDE above. So DDPM sampling is a first-order discretization of Theorem 1. It is accurate only when every βt\beta_t is small, which is why DDPM needs many steps.

    DDIM: a family of samplers for one trained model #

    Song, Meng and Ermon (2021) asked which samplers can use a network trained with the DDPM loss. The loss E∥ε−εθ(xt,t)∥2\mathbb E\|\varepsilon - \varepsilon_\theta(x_t, t)\|^2 only sees pairs (x0,xt)(x_0, x_t) at single times, through q(xt∣x0)=N(atx0,σt2I)q(x_t \mid x_0) = \mathcal N(a_t x_0, \sigma_t^2 I). Any process with these same marginals has the same optimal network E[ε∣xt]\mathbb E[\varepsilon \mid x_t]. They found a family of such processes with one free noise scale νt\nu_t per step. Sampling from it gives the DDIM update (Song et al. 2021, eq. 12). From level tt to any lower level t′t' (written t−1t - 1):

    x^0=xt−σtε^at,xt−1=at−1x^0⏟predicted data+σt−12−νt2  ε^⏟direction to xt+νt z⏟fresh noise,\hat x_0 = \frac{x_t - \sigma_t\hat\varepsilon}{a_t}, \qquad x_{t-1} = \underbrace{a_{t-1}\hat x_0}_{\text{predicted data}} + \underbrace{\sqrt{\sigma_{t-1}^2 - \nu_t^2}\;\hat\varepsilon}_{\text{direction to } x_t} + \underbrace{\nu_t\, z}_{\text{fresh noise}} ,

    with z∼N(0,I)z \sim \mathcal N(0, I) and, with a parameter η∈[0,1]\eta \in [0, 1] (eq. 16 of the paper),

    νt(η)=η  σt−1σt 1−at2at−12.\nu_t(\eta) = \eta\;\frac{\sigma_{t-1}}{\sigma_t}\,\sqrt{1 - \frac{a_t^2}{a_{t-1}^2}} .

    (Song et al. call νt\nu_t "σt\sigma_t"; we keep σt\sigma_t for the noise scale.)

    • η=0\eta = 0: no fresh noise. The step is deterministic, and so is the whole sample once xTx_T is fixed.
    • η=1\eta = 1: νt2\nu_t^2 equals the DDPM posterior variance β~\tilde\beta of this step, and the mean equals the DDPM posterior mean. So η=1\eta = 1 is ancestral DDPM sampling with variance β~\tilde\beta. (Worked example (a) checks this with numbers.)
    • Any subsequence of levels. Nothing in the derivation needs consecutive steps, so the same formula jumps between any two levels, for example 50 levels out of 1,000.
    Why every value of η keeps the DDPM marginals (the non-Markovian forward process)

    Song et al. define a forward process that runs down in tt and may look at x0x_0:

    qν(xt−1∣xt,x0)=N(at−1x0+σt−12−νt2  xt−atx0σt, νt2I),q_\nu(x_{t-1} \mid x_t, x_0) = \mathcal N\Big(a_{t-1}x_0 + \sqrt{\sigma_{t-1}^2 - \nu_t^2}\;\frac{x_t - a_t x_0}{\sigma_t},\ \nu_t^2 I\Big),

    started from q(xT∣x0)=N(aTx0,σT2I)q(x_T \mid x_0) = \mathcal N(a_T x_0, \sigma_T^2 I).

    Claim. qν(xt∣x0)=N(atx0,σt2I)q_\nu(x_t \mid x_0) = \mathcal N(a_t x_0, \sigma_t^2 I) for every tt and every choice of ν\nu.

    1. Assume it holds at level tt. Write xt=atx0+σtεx_t = a_t x_0 + \sigma_t\varepsilon; then (xt−atx0)/σt=ε(x_t - a_t x_0)/\sigma_t = \varepsilon.
    2. A draw from the kernel is xt−1=at−1x0+σt−12−νt2  ε+νtzx_{t-1} = a_{t-1}x_0 + \sqrt{\sigma_{t-1}^2 - \nu_t^2}\;\varepsilon + \nu_t z, with zz independent of ε\varepsilon.
    3. Given x0x_0, this is a sum of independent Gaussians: mean at−1x0a_{t-1}x_0, variance (σt−12−νt2)+νt2=σt−12(\sigma_{t-1}^2 - \nu_t^2) + \nu_t^2 = \sigma_{t-1}^2.
    4. The claim holds at t=Tt = T by definition. Induction downward gives every tt. ■\blacksquare

    Consequences. The process is non-Markovian (xt−1x_{t-1} depends on x0x_0, not only on xtx_t), but the training loss never sees this: it uses only q(xt∣x0)q(x_t \mid x_0). Song et al. (Theorem 1) also show that the ELBO of every member is a weighted LsimpleL_{\text{simple}} plus a constant. Sampling replaces the unknown x0x_0 by x^0\hat x_0 and ε\varepsilon by ε^\hat\varepsilon, which gives the update above.

    η=1\eta = 1 is DDPM. Let β′=1−at2/at−12\beta' = 1 - a_t^2/a_{t-1}^2 be the β\beta of the step. Then νt2=σt−12σt2β′=β~′\nu_t^2 = \frac{\sigma_{t-1}^2}{\sigma_t^2}\beta' = \tilde\beta', the posterior variance of lesson 07. The coefficient of xtx_t in the mean becomes σt−12−β~′/σt\sqrt{\sigma_{t-1}^2 - \tilde\beta'}/\sigma_t, and its square is σt−12σt2(1−β′σt2)=σt−12σt4(σt2−β′)=σt−14(1−β′)σt4\frac{\sigma_{t-1}^2}{\sigma_t^2}\big(1 - \frac{\beta'}{\sigma_t^2}\big) = \frac{\sigma_{t-1}^2}{\sigma_t^4}(\sigma_t^2 - \beta') = \frac{\sigma_{t-1}^4 (1 - \beta')}{\sigma_t^4}, using σt2−β′=1−at2−1+at2/at−12=(1−β′) σt−12\sigma_t^2 - \beta' = 1 - a_t^2 - 1 + a_t^2/a_{t-1}^2 = (1 - \beta')\,\sigma_{t-1}^2. So the coefficient is 1−β′ σt−12/σt2\sqrt{1 - \beta'}\,\sigma_{t-1}^2/\sigma_t^2: exactly the DDPM coefficient of xtx_t in μ~\tilde\mu.

    Theorem 3 (DDIM with η=0\eta = 0 is Euler's method for the probability-flow ODE) #

    In the EDM coordinates x~=x/a\tilde x = x/a and σ~=σ/a\tilde\sigma = \sigma/a, the η=0\eta = 0 DDIM step is

    x~t−1=x~t+(σ~t−1−σ~t) x~t−x^0σ~t,\tilde x_{t-1} = \tilde x_t + (\tilde\sigma_{t-1} - \tilde\sigma_t)\,\frac{\tilde x_t - \hat x_0}{\tilde\sigma_t} ,

    one Euler step of the probability-flow ODE dx~dσ~=x~−D(x~;σ~)σ~\dfrac{d\tilde x}{d\tilde\sigma} = \dfrac{\tilde x - D(\tilde x; \tilde\sigma)}{\tilde\sigma}, with the network's x^0\hat x_0 in place of DD.

    Proof.

    1. Set νt=0\nu_t = 0 in the DDIM update and divide by at−1a_{t-1}: x~t−1=x^0+σt−1at−1ε^=x^0+σ~t−1ε^\tilde x_{t-1} = \hat x_0 + \frac{\sigma_{t-1}}{a_{t-1}}\hat\varepsilon = \hat x_0 + \tilde\sigma_{t-1}\hat\varepsilon.
    2. Rearrange the definition of x^0\hat x_0: xt=atx^0+σtε^x_t = a_t\hat x_0 + \sigma_t\hat\varepsilon. Divide by ata_t: x~t=x^0+σ~tε^\tilde x_t = \hat x_0 + \tilde\sigma_t\hat\varepsilon and ε^=(x~t−x^0)/σ~t\hat\varepsilon = (\tilde x_t - \hat x_0)/\tilde\sigma_t.
    3. Put this into step 1: x~t−1=x^0+σ~t−1(x~t−x^0)/σ~t\tilde x_{t-1} = \hat x_0 + \tilde\sigma_{t-1}(\tilde x_t - \hat x_0)/\tilde\sigma_t. Add and subtract x~t\tilde x_t: x~t−1=x~t+(σ~t−1−σ~t)(x~t−x^0)/σ~t\tilde x_{t-1} = \tilde x_t + (\tilde\sigma_{t-1} - \tilde\sigma_t)(\tilde x_t - \hat x_0)/\tilde\sigma_t.
    4. In these coordinates x~=x0+σ~ε\tilde x = x_0 + \tilde\sigma\varepsilon is the VE form, whose probability-flow ODE is dx~/dσ~=(x~−D)/σ~d\tilde x/d\tilde\sigma = (\tilde x - D)/\tilde\sigma (the boxed equation). Step 3 is one Euler step of it from σ~t\tilde\sigma_t to σ~t−1\tilde\sigma_{t-1}, with the slope evaluated at the start. ■\blacksquare
    Interactive figure: loading…

    Move xtx_t, ε^\hat\varepsilon and the two noise levels. The yellow point is the current state in the coordinates (σ~,x~)(\tilde\sigma, \tilde x). The violet dashed line joins it to x^0\hat x_0 (the blue point on the axis σ~=0\tilde\sigma = 0). The Euler step moves along this line to the next level (magenta ring); the DDIM step (orange dot) lands inside the ring, and the panel shows a difference of 00 up to rounding. Now raise η\eta: the step becomes random, and its mean leaves the line. Switch to exact, Gaussian data to see the true ODE path (thin magenta curve) and the Euler error.

    What Theorem 3 tells you.

    • DDIM is a first-order method: its global error is O(h)O(h) in the step size hh. Second-order methods on the same ODE need fewer NFE for the same error.
    • In these coordinates the slope points at the denoiser output: one Euler step to σ~=0\tilde\sigma = 0 returns D(x~;σ~)D(\tilde x; \tilde\sigma). A step is exact when DD does not change along it. If the data are a single point x∗x^\ast, then D≡x∗D \equiv x^\ast, every path is a straight line, and one DDIM step is exact.
    • The coordinates matter. Euler steps in tt on the VP form of the ODE give a different, worse sampler. In (σ~,x~)(\tilde\sigma, \tilde x) the paths are nearly straight at high noise, which is why DDIM works with few steps. Lesson 10 builds on the same idea: flow matching chooses a path that is as straight as possible.

    The Gaussian toy: exact paths #

    Take 1D data x0∼N(0,σdata2)x_0 \sim \mathcal N(0, \sigma_{\text{data}}^2) in EDM coordinates. The denoiser is linear, D(x;σ)=σdata2x/(σdata2+σ2)D(x; \sigma) = \sigma_{\text{data}}^2 x/(\sigma_{\text{data}}^2 + \sigma^2) (lesson 08), so the ODE is

    dxdσ=x−Dσ=σ xσdata2+σ2,with solutionx(σ)=x(σstart)σdata2+σ2σdata2+σstart2.\frac{dx}{d\sigma} = \frac{x - D}{\sigma} = \frac{\sigma\,x}{\sigma_{\text{data}}^2 + \sigma^2}, \qquad\text{with solution}\qquad x(\sigma) = x(\sigma_{\text{start}})\sqrt{\frac{\sigma_{\text{data}}^2 + \sigma^2}{\sigma_{\text{data}}^2 + \sigma_{\text{start}}^2}} .

    (Separate the variables: dx/x=σ dσ/(σdata2+σ2)dx/x = \sigma\,d\sigma/(\sigma_{\text{data}}^2 + \sigma^2), so ln⁡x=12ln⁡(σdata2+σ2)+C\ln x = \tfrac12\ln(\sigma_{\text{data}}^2 + \sigma^2) + C.) Each start value gives exactly one data value: the ODE is a deterministic, invertible map from noise to data. For σ≫σdata\sigma \gg \sigma_{\text{data}}, x(σ)x(\sigma) is almost proportional to σ\sigma: a straight line that one large step follows almost exactly. Near σ≈σdata\sigma \approx \sigma_{\text{data}} the path bends. Easy at high noise, hard at low noise: this is the reason for the step schedules below.

    ODE or SDE? #

    • ODE (η=0\eta = 0; Euler, Heun, DPM-Solver). Deterministic and invertible: DDIM inversion maps an image to its noise (used for editing and interpolation). It gives exact likelihoods with the instantaneous change of variables of lesson 04. It is usually the best choice at low NFE. But errors (network error plus discretization error) are carried along, never corrected.
    • SDE (reverse SDE, ancestral, DDIM with η>0\eta > 0). The fresh noise and the extra score drift act like Langevin steps at a fixed noise level. They pull samples back toward ptp_t and so partly correct earlier errors. The price: every step must remove the noise it adds, so it needs more steps.
    • Numbers (Song et al. 2021, Table 1, CIFAR-10 FID, lower is better): with 10 steps, η=0\eta = 0 gives 13.36 and η=1\eta = 1 gives 41.07; with 100 steps, 4.16 and 5.78; with 1,000 steps, 4.04 and 4.73. Ancestral sampling with the larger variance βt\beta_t is the best at 1,000 steps (3.17) and useless at 10 steps (367).
    • EDM's stochastic sampler (Karras et al. 2022, Algorithm 2) adds a controlled amount of noise ("churn") in a band of noise levels, then takes a deterministic Heun step. Too much churn loses detail.

    In the 8-Gaussians figure the score is exact, so there is no network error for the noise to correct. There, at 10 NFE on the Karras schedule, Euler puts about 84% of the samples on a mode and the reverse SDE about 62%; at 50 NFE both are above 95%.

    Solvers: accuracy per NFE #

    From here on we work in EDM coordinates and write xx and σ\sigma (no tildes). The ODE is dx/dσ=d(x,σ)dx/d\sigma = d(x, \sigma) with the slope d(x,σ)=(x−D(x;σ))/σd(x, \sigma) = (x - D(x;\sigma))/\sigma, solved on a decreasing grid σ0>σ1>⋯>σN=0\sigma_0 > \sigma_1 > \dots > \sigma_N = 0 with step hi=σi+1−σi<0h_i = \sigma_{i+1} - \sigma_i < 0.

    • Euler (= DDIM): xi+1=xi+hi d(xi,σi)x_{i+1} = x_i + h_i\,d(x_i, \sigma_i). One NFE per step. Global error O(h)O(h): halving all steps halves the error.
    • Heun (EDM, Algorithm 1): an Euler predictor x′=xi+hidix' = x_i + h_i d_i, then the trapezoidal corrector
    xi+1=xi+hi d(xi,σi)+d(x′,σi+1)2.x_{i+1} = x_i + h_i\,\frac{d(x_i, \sigma_i) + d(x', \sigma_{i+1})}{2}.

    Two NFE per step. Global error O(h2)O(h^2): halving the steps divides the error by about 4.

    • The last step. The slope (x−D)/σ(x - D)/\sigma is undefined at σ=0\sigma = 0, so the last step into σ=0\sigma = 0 is a plain Euler step (it returns D(x;σN−1)D(x; \sigma_{N-1})). So NN Heun steps that end at 0 cost 2N−12N - 1 NFE: 35 for EDM's 18 levels, which reach FID 1.79 on class-conditional CIFAR-10.

    The order of a method is the power pp in "global error ≈Chp\approx C h^p". On log–log axes, error against NFE is a line with slope −p-p.

    Interactive figure: loading…

    Compare the two lines on log–log axes. Each point integrates the exact Gaussian-toy ODE from σ=80\sigma = 80 to σ=0.002\sigma = 0.002 with Euler (orange) or Heun (violet) and plots the relative error against the NFE used. At large NFE the slopes approach −1-1 and −2-2 (dashed reference lines). The yellow rings mark about 30 NFE: there the two errors are still close. At about 250 NFE Heun is more than ten times more accurate: halving its steps divides its error by 4, not by 2. Switch to the uniform grid: both methods need hundreds of NFE before their order shows, because almost all steps are spent at high noise.

    Quick check

    A Heun sampler uses the noise levels σ0>σ1>⋯>σ9>σ10=0\sigma_0 > \sigma_1 > \dots > \sigma_9 > \sigma_{10} = 0. How many network evaluations does one sample cost?

    DPM-Solver: solve the linear part exactly

    Lu et al. (2022) change the variable to u=log⁡σu = \log\sigma. Then dx/du=σ dx/dσ=x−D(x;eu)dx/du = \sigma\,dx/d\sigma = x - D(x; e^u). The term xx is linear and can be integrated exactly (variation of constants):

    x(ui+1)=σi+1σi x(ui)−∫uiui+1e ui+1−u D(x(u);eu) du.x(u_{i+1}) = \frac{\sigma_{i+1}}{\sigma_i}\,x(u_i) - \int_{u_i}^{u_{i+1}} e^{\,u_{i+1} - u}\,D\big(x(u); e^{u}\big)\,du .

    Only DD is approximated, by a polynomial in uu. A constant D=x^0D = \hat x_0 gives

    xi+1=σi+1σi xi+(1−σi+1σi)x^0,x_{i+1} = \frac{\sigma_{i+1}}{\sigma_i}\,x_i + \Big(1 - \frac{\sigma_{i+1}}{\sigma_i}\Big)\hat x_0 ,

    which is the DDIM step again (DPM-Solver-1 = DDIM, Lu et al., section 4.1). Check: the Euler step xi+(σi+1−σi)(xi−x^0)/σix_i + (\sigma_{i+1} - \sigma_i)(x_i - \hat x_0)/\sigma_i is the same expression. DPM-Solver-2 and -3 use 2 and 3 NFE per step for orders 2 and 3. DPM-Solver++(2M) makes DD linear in uu from the current and the previous denoiser outputs: order 2 for 1 NFE per step. Lu et al. write the time as λ=−log⁡σ\lambda = -\log\sigma (half the log-SNR) and report good samples with 10–20 NFE.

    Step schedules #

    Karras et al. (2022, eq. 5) choose NN levels between σmax⁡\sigma_{\max} and σmin⁡\sigma_{\min} with a parameter ρ\rho:

    σi=(σmax⁡1/ρ+iN−1(σmin⁡1/ρ−σmax⁡1/ρ))ρ,i=0,…,N−1,σN=0.\sigma_i = \Big(\sigma_{\max}^{1/\rho} + \tfrac{i}{N-1}\big(\sigma_{\min}^{1/\rho} - \sigma_{\max}^{1/\rho}\big)\Big)^{\rho}, \qquad i = 0, \dots, N - 1, \qquad \sigma_N = 0 .
    • ρ=1\rho = 1: uniform in σ\sigma.
    • ρ→∞\rho \to \infty: uniform in log⁡σ\log\sigma (a geometric grid). Proof: with u=i/(N−1)u = i/(N-1) and c1/ρ=1+(ln⁡c)/ρ+O(ρ−2)c^{1/\rho} = 1 + (\ln c)/\rho + O(\rho^{-2}), the bracket is 1+[(1−u)ln⁡σmax⁡+uln⁡σmin⁡]/ρ+O(ρ−2)1 + [(1-u)\ln\sigma_{\max} + u\ln\sigma_{\min}]/\rho + O(\rho^{-2}), and its ρ\rho-th power tends to σmax⁡1−uσmin⁡u\sigma_{\max}^{1-u}\sigma_{\min}^{u}.
    • EDM default: σmin⁡=0.002\sigma_{\min} = 0.002, σmax⁡=80\sigma_{\max} = 80, ρ=7\rho = 7, for data scaled to σdata=0.5\sigma_{\text{data}} = 0.5. EDM found that ρ=3\rho = 3 nearly equalizes the error per step, but ρ\rho between 5 and 10 gives better images: errors near σmin⁡\sigma_{\min} have a large effect on what you see.
    Interactive figure: loading…

    Move ρ\rho and NN. The top panel shows the levels for ρ=1\rho = 1, for your ρ\rho (orange) and for ρ→∞\rho \to \infty, on a log axis; the yellow line is σ=1\sigma = 1. The bottom panel shows the local error that one Euler step makes on the Gaussian toy, at the level where the step starts. With ρ=1\rho = 1 one long last step from σ≈80/N\sigma \approx 80/N to almost 0 makes nearly all of the error. Larger ρ\rho moves levels to low noise, where the path bends, and the errors become more even. Press EDM for the default 18 levels with ρ=7\rho = 7: 7 of them lie below σ=1\sigma = 1.

    Why the steps crowd at low noise.

    1. The local error of a step of size hh is about 12h2∣x′′∣\tfrac12 h^2 |x''| for Euler (and ∝h3\propto h^3 for Heun): it is large where the path bends.
    2. In the Gaussian toy, x(σ)=cσdata2+σ2x(\sigma) = c\sqrt{\sigma_{\text{data}}^2 + \sigma^2} gives x′′(σ)=c σdata2/(σdata2+σ2)3/2x''(\sigma) = c\,\sigma_{\text{data}}^2/(\sigma_{\text{data}}^2 + \sigma^2)^{3/2}. It is largest at σ=0\sigma = 0 and decays like σ−3\sigma^{-3}. For σdata=0.5\sigma_{\text{data}} = 0.5 the curvature at σ=0\sigma = 0 is (100.25/0.25)3/2≈8,000(100.25/0.25)^{3/2} \approx 8{,}000 times the curvature at σ=10\sigma = 10. For the same Euler error, a step at σ=10\sigma = 10 may be 8000≈90\sqrt{8000} \approx 90 times longer.
    3. Real data have structure at many scales. The path bends whenever σ\sigma passes a length scale of the data: the distance between modes, the width of a mode, fine detail. At high noise the denoiser output stays near the data mean, so the path is nearly straight.

    An adaptive ODE solver finds this spacing by itself (section 5): with no schedule given, it chooses an almost geometric grid.

    Where the schedule starts. The first level must be the highest level the model was trained at (σmax⁡\sigma_{\max}, or t=Tt = T). With T=1000T = 1000 and 10 steps, "leading" spacing uses t=901,801,…,1t = 901, 801, \dots, 1 and never visits t=Tt = T; "trailing" spacing uses t=1000,900,…,100t = 1000, 900, \dots, 100 (Lin et al. 2024). Use trailing spacing; see lesson 08 for the related zero-terminal-SNR problem.

    Quick check

    Why does the EDM schedule (ρ=7\rho = 7) put 7 of its 18 noise levels below σ=1\sigma = 1, about 1% of the range from 0.002 to 80?

    Quick check

    You sample with DDIM, η=1\eta = 1, on the full grid of 1,000 DDPM levels. How does this compare with ancestral DDPM sampling?

    Answers to section 1. (1) No retraining. Every η\eta keeps the DDPM marginals q(xt∣x0)q(x_t \mid x_0), and the loss sees only these, so the same network is optimal. With η=0\eta = 0 the output is a deterministic function of xTx_T: an ODE solve (Theorem 3). (2) Not the same point: the SDE path is random. But the same distribution: both have the marginals ptp_t at every time (Theorems 1 and 2). (3) Not uniformly. Most levels go to low noise, where the paths bend. With ρ=7\rho = 7, 7 of the 18 levels lie below σ=1\sigma = 1; a uniform grid puts only its last level there.

    4. Worked example #

    (a) One DDIM step by hand. 1D, αˉt=0.5\bar\alpha_t = 0.5, αˉt−1=0.8\bar\alpha_{t-1} = 0.8, xt=1.0x_t = 1.0, network output ε^=0.6\hat\varepsilon = 0.6. So at=σt=0.5=0.70711a_t = \sigma_t = \sqrt{0.5} = 0.70711, at−1=0.8=0.89443a_{t-1} = \sqrt{0.8} = 0.89443, σt−1=0.2=0.44721\sigma_{t-1} = \sqrt{0.2} = 0.44721.

    1. x^0=(xt−σtε^)/at=(1−0.70711⋅0.6)/0.70711=0.57574/0.70711=0.81421\hat x_0 = (x_t - \sigma_t\hat\varepsilon)/a_t = (1 - 0.70711 \cdot 0.6)/0.70711 = 0.57574/0.70711 = 0.81421.
    2. η=0\eta = 0: xt−1=at−1x^0+σt−1ε^=0.89443⋅0.81421+0.44721⋅0.6=0.72825+0.26833=0.99658x_{t-1} = a_{t-1}\hat x_0 + \sigma_{t-1}\hat\varepsilon = 0.89443 \cdot 0.81421 + 0.44721 \cdot 0.6 = 0.72825 + 0.26833 = 0.99658.
    3. Check as Euler. σ~t=0.70711/0.70711=1\tilde\sigma_t = 0.70711/0.70711 = 1, σ~t−1=0.44721/0.89443=0.5\tilde\sigma_{t-1} = 0.44721/0.89443 = 0.5, x~t=1/0.70711=1.41421\tilde x_t = 1/0.70711 = 1.41421. The slope is (x~t−x^0)/σ~t=(1.41421−0.81421)/1=0.6=ε^(\tilde x_t - \hat x_0)/\tilde\sigma_t = (1.41421 - 0.81421)/1 = 0.6 = \hat\varepsilon. Euler: x~t−1=1.41421+(0.5−1)⋅0.6=1.11421\tilde x_{t-1} = 1.41421 + (0.5 - 1) \cdot 0.6 = 1.11421. Back to VP: 0.89443⋅1.11421=0.996580.89443 \cdot 1.11421 = 0.99658 ✓.
    4. η=1\eta = 1. The step has β′=1−0.5/0.8=0.375\beta' = 1 - 0.5/0.8 = 0.375. ν=0.447210.707110.375=0.63246⋅0.61237=0.38730\nu = \frac{0.44721}{0.70711}\sqrt{0.375} = 0.63246 \cdot 0.61237 = 0.38730, so ν2=0.15\nu^2 = 0.15. The DDPM posterior variance is β~′=1−0.81−0.5⋅0.375=0.4⋅0.375=0.15\tilde\beta' = \frac{1 - 0.8}{1 - 0.5}\cdot 0.375 = 0.4 \cdot 0.375 = 0.15 ✓.
    5. The η=1\eta = 1 mean: 0.72825+0.2−0.15⋅0.6=0.72825+0.22361⋅0.6=0.862420.72825 + \sqrt{0.2 - 0.15}\cdot 0.6 = 0.72825 + 0.22361 \cdot 0.6 = 0.86242. The DDPM posterior mean (lesson 07) is at−1β′1−αˉtx^0+1−β′ (1−αˉt−1)1−αˉtxt=0.67082⋅0.81421+0.31623⋅1=0.86242\frac{a_{t-1}\beta'}{1 - \bar\alpha_t}\hat x_0 + \frac{\sqrt{1 - \beta'}\,(1 - \bar\alpha_{t-1})}{1 - \bar\alpha_t}x_t = 0.67082 \cdot 0.81421 + 0.31623 \cdot 1 = 0.86242 ✓.

    (b) Euler vs Heun for one step, with the exact answer. Data N(0,1)\mathcal N(0, 1) (σdata=1\sigma_{\text{data}} = 1). Go from σ=2\sigma = 2, x=1x = 1 to σ=1\sigma = 1, so h=−1h = -1. The slope is d(x,σ)=σx/(1+σ2)d(x, \sigma) = \sigma x/(1 + \sigma^2).

    • Exact: x(1)=1⋅(1+1)/(1+4)=0.4=0.63246x(1) = 1 \cdot \sqrt{(1 + 1)/(1 + 4)} = \sqrt{0.4} = 0.63246.
    • Euler (1 NFE): d0=2⋅1/5=0.4d_0 = 2 \cdot 1/5 = 0.4, so xE=1+(−1)(0.4)=0.6x_E = 1 + (-1)(0.4) = 0.6. Error −0.0325-0.0325.
    • Heun (2 NFE): d0′=d(0.6,1)=0.6/2=0.3d_0' = d(0.6, 1) = 0.6/2 = 0.3, so xH=1−(0.4+0.3)/2=0.65x_H = 1 - (0.4 + 0.3)/2 = 0.65. Error +0.0175+0.0175.

    (c) Karras grids for N=5N = 5 (σmax⁡=80\sigma_{\max} = 80, σmin⁡=0.002\sigma_{\min} = 0.002). For ρ=7\rho = 7: 801/7=1.870180^{1/7} = 1.8701 and 0.0021/7=0.41160.002^{1/7} = 0.4116; the middle level is ((1.8701+0.4116)/2)7=1.140857=2.515\big((1.8701 + 0.4116)/2\big)^7 = 1.14085^7 = 2.515. Then σ5=0\sigma_5 = 0:

    ρ\rho σ0\sigma_0 σ1\sigma_1 σ2\sigma_2 σ3\sigma_3 σ4\sigma_4
    1 (uniform) 80 60.0005 40.001 20.0015 0.002
    3 80 34.75 10.90 1.609 0.002
    7 (EDM) 80 17.53 2.515 0.1698 0.002
    ∞\infty (geometric) 80 5.657 0.4 0.02828 0.002

    The geometric middle level is 80⋅0.002=0.4\sqrt{80 \cdot 0.002} = 0.4, the geometric mean of the end points.

    (d) Schedule and solver together. Data 12N(−1,0.12)+12N(1,0.12)\tfrac12\mathcal N(-1, 0.1^2) + \tfrac12\mathcal N(1, 0.1^2) with the exact denoiser; 500 paths from σ=80\sigma = 80 to σ=0.002\sigma = 0.002 on 18 levels (17 steps, no step to 0). The table gives the largest error against an accurate reference solve:

    grid Euler (17 NFE) Heun (34 NFE)
    ρ=1\rho = 1 0.86 8.5
    ρ=3\rho = 3 0.46 0.14
    ρ=7\rho = 7 0.26 0.048
    geometric 0.25 0.039
    • The schedule comes first. On the uniform grid the last step, from σ=4.7\sigma = 4.7 to 0.0020.002, is far too long, and Heun is even worse than Euler. At σ=4.7\sigma = 4.7 the denoiser output lies between the modes, so the Euler predictor stops short of the nearest mode; the slope at the predicted point then points the other way, and the average of the two slopes ends far from the data.
    • On a good grid Heun wins at equal cost. With ρ=7\rho = 7, Heun on 10 levels (18 NFE) has error 0.18, against 0.26 for Euler on 18 levels (17 NFE); Heun on 18 levels (34 NFE) has 0.048, against 0.11 for Euler on 40 levels (39 NFE).

    5. In code #

    NumPy checks the worked examples (a)–(c). The notebook has these cells and the exercises.

    import numpy as np
    np.set_printoptions(suppress=True)
    
    # (a) One DDIM step (VP), and the same step as Euler in the EDM coordinates
    ab_t, ab_p, xt, eps_hat = 0.5, 0.8, 1.0, 0.6          # alpha_bar at t and t-1, x_t, network output
    a_t, s_t, a_p, s_p = np.sqrt(ab_t), np.sqrt(1 - ab_t), np.sqrt(ab_p), np.sqrt(1 - ab_p)
    x0_hat = (xt - s_t * eps_hat) / a_t
    x_prev = a_p * x0_hat + s_p * eps_hat                  # eta = 0
    st_t, st_p = s_t / a_t, s_p / a_p                      # sigma tilde = sigma_t / a_t
    euler = xt / a_t + (st_p - st_t) * (xt / a_t - x0_hat) / st_t
    print(round(x0_hat, 5), round(x_prev, 5), round(euler * a_p, 5))   # 0.81421 0.99658 0.99658
    
    # eta = 1: the DDIM noise and mean equal the DDPM posterior of this (respaced) step
    beta = 1 - ab_t / ab_p
    nu = 1.0 * (s_p / s_t) * np.sqrt(1 - ab_t / ab_p)
    mean_ddim = a_p * x0_hat + np.sqrt(s_p**2 - nu**2) * eps_hat
    mean_ddpm = a_p * beta / (1 - ab_t) * x0_hat + np.sqrt(1 - beta) * (1 - ab_p) / (1 - ab_t) * xt
    print(round(nu**2, 5), round((1 - ab_p) / (1 - ab_t) * beta, 5))   # 0.15 0.15
    print(round(mean_ddim, 5), round(mean_ddpm, 5))                    # 0.86242 0.86242
    
    # (b) Euler and Heun for one step of dx/dsigma = sigma x / (1 + sigma^2) (data N(0, 1))
    d = lambda x, s: s * x / (1 + s**2)
    x_e = 1 + (1 - 2) * d(1, 2)
    x_h = 1 + (1 - 2) * (d(1, 2) + d(x_e, 1)) / 2
    print(x_e, x_h, round(np.sqrt(2 / 5), 5))                          # 0.6 0.65 0.63246
    
    # (c) Karras grids
    def karras(n, rho, smax=80.0, smin=0.002):
        u = np.arange(n) / (n - 1)
        if np.isinf(rho):
            return smax * (smin / smax) ** u
        return (smax ** (1 / rho) + u * (smin ** (1 / rho) - smax ** (1 / rho))) ** rho
    
    for rho in (1, 3, 7, np.inf):
        print(rho, np.round(karras(5, rho), 4))
    # 1 [80.     60.0005 40.001  20.0015  0.002 ]
    # 3 [80.     34.7465 10.9031  1.6087  0.002 ]
    # 7 [80.     17.5278  2.5152  0.1698  0.002 ]
    # inf [80.      5.6569  0.4     0.0283  0.002 ]
    print((karras(18, 7) < 1).sum(), (karras(18, 1) < 1).sum())        # 7 1
    

    Second, give SciPy's adaptive Runge–Kutta solver (RK45) the probability-flow ODE of the two-mode mixture of example (d), with no schedule at all, and look at where it puts its steps. The denoiser is exact (Tweedie for a Gaussian mixture, with responsibilities in log space):

    import numpy as np
    from scipy.integrate import solve_ivp
    
    mu, sd = np.array([-1.0, 1.0]), 0.1         # data: 0.5 N(-1, 0.1^2) + 0.5 N(1, 0.1^2)
    
    def denoiser(x, sigma):
        """D(x; sigma) = E[x0 | x0 + sigma * eps = x] for the mixture."""
        v = sd**2 + sigma**2
        logw = -(x[:, None] - mu) ** 2 / (2 * v)
        w = np.exp(logw - logw.max(1, keepdims=True))
        w /= w.sum(1, keepdims=True)
        return (w * (mu + sd**2 * (x[:, None] - mu) / v)).sum(1)
    
    def pf_ode(sigma, x):                       # dx/dsigma = (x - D(x; sigma)) / sigma
        return (x - denoiser(x, sigma)) / sigma
    
    x_start = 80.0 * np.random.default_rng(0).standard_normal(500)
    sol = solve_ivp(pf_ode, (80.0, 0.002), x_start, method="RK45", rtol=1e-5, atol=1e-5)
    levels = sol.t[:-1]                         # sigma at the start of each accepted step
    print("steps:", len(levels), " NFE:", sol.nfev)                       # steps: 25  NFE: 224
    print(np.sum(levels >= 1), np.sum((levels >= 0.1) & (levels < 1)), np.sum(levels < 0.1))   # 18 5 2
    print(round(np.median(sol.t[1:] / sol.t[:-1]), 2))                    # 0.75
    print(np.mean(np.abs(np.abs(sol.y[:, -1]) - 1) < 0.4))               # 1.0: every path ends near a mode
    

    The solver takes 25 steps. Their median ratio σnext/σ\sigma_{\text{next}}/\sigma is 0.75, so it chooses an almost geometric grid by itself: 7 of its 25 steps lie below σ=1\sigma = 1, where a uniform grid would put none. The last two steps are long again: below σ≈0.1\sigma \approx 0.1, the width of each mode, the denoiser barely changes xx and the path is straight. But 224 NFE is a lot (RK45 uses 6 evaluations per step, plus rejected steps). EDM tried RK45 and found that its cost outweighs its benefits; a fixed Heun grid is cheaper.

    6. Where it shows up #

    • Image and video models. Libraries offer these samplers by name: k-diffusion has sample_euler, sample_heun, sample_euler_ancestral, sample_dpmpp_2m and get_sigmas_karras; diffusers has DDIM, Euler, Heun and DPM-Solver++ schedulers. 20–50 steps is a common default.
    • Weather. GenCast samples with a second-order DPM-Solver++ variant on a Karras grid (ρ=7\rho = 7) with 20 noise levels (39 NFE) plus EDM-style churn. The ensemble cost is members × forecast steps × NFE.
    • Flow matching and rectified flow (lesson 10). Sampling integrates dx/dt=vθ(x,t)dx/dt = v_\theta(x, t) with Euler or Heun; the NFE accounting is the same. Straighter paths need fewer steps, for the reason of Theorem 3: a step is exact when the path is straight.
    • Inversion, editing and likelihood. DDIM inversion runs the ODE from data to noise. Likelihoods (bits per dimension, lesson 14) need an accurate, usually adaptive ODE solve with a Hutchinson trace estimate (lesson 04).
    • Guidance (lesson 11) changes the denoiser inside the same samplers; classifier-free guidance doubles the NFE of each step.
    • Below 10–20 NFE, retrain: progressive distillation and consistency models (lesson 12).

    7. Common mistakes #

    • Comparing samplers at equal steps instead of equal NFE. A Heun step costs 2 NFE; classifier-free guidance doubles every count.
    • Skipping steps in ancestral sampling but keeping the original βt\beta_t. On a subsequence, recompute β′=1−αˉt/αˉtprev\beta' = 1 - \bar\alpha_t/\bar\alpha_{t_{\text{prev}}} for each step. DDIM's formula does this automatically.
    • Forgetting the 12\tfrac12 in the probability-flow ODE. dx/dt=f−g2∇log⁡ptdx/dt = f - g^2\nabla\log p_t without noise shrinks the spread twice as fast as it should. The reverse SDE has g2g^2; the ODE has 12g2\tfrac12 g^2.
    • Heun's corrector on the last step into σ=0\sigma = 0. It divides by zero; use Euler there.
    • Mixing coordinates. DDIM is Euler in (σ~,x~)(\tilde\sigma, \tilde x). Euler on the VP ODE in tt is a different sampler with more curved paths.
    • Starting below the top level ("leading" spacing) or with a nonzero terminal SNR (lesson 08).
    • An SDE sampler at very low NFE. At 10 steps, η=1\eta = 1 gives FID 41 where η=0\eta = 0 gives 13 (CIFAR-10).
    • A uniform σ\sigma grid. Most steps are then spent where the path is straight (example (d)).
    • Clipping x^0\hat x_0 on unbounded data. Image samplers often clip x^0\hat x_0 to [−1,1][-1, 1] (in diffusers, clip_sample=True is the default of the DDPM and DDIM schedulers). On physical fields this silently cuts the extremes; turn it off.

    8. Check yourself #

    1. One DDIM step with η=0\eta = 0: αˉt=0.36\bar\alpha_t = 0.36, αˉt−1=0.64\bar\alpha_{t-1} = 0.64, xt=0.8x_t = 0.8, ε^=0.5\hat\varepsilon = 0.5. Compute xt−1x_{t-1} and check it as an Euler step.
      Answerat=0.6a_t = 0.6, σt=0.8\sigma_t = 0.8, at−1=0.8a_{t-1} = 0.8, σt−1=0.6\sigma_{t-1} = 0.6. x^0=(0.8−0.8⋅0.5)/0.6=0.6667\hat x_0 = (0.8 - 0.8 \cdot 0.5)/0.6 = 0.6667; xt−1=0.8⋅0.6667+0.6⋅0.5=0.8333x_{t-1} = 0.8 \cdot 0.6667 + 0.6 \cdot 0.5 = 0.8333. EDM coordinates: σ~t=4/3\tilde\sigma_t = 4/3, σ~t−1=3/4\tilde\sigma_{t-1} = 3/4, x~t=1.3333\tilde x_t = 1.3333, slope (x~t−x^0)/σ~t=0.5(\tilde x_t - \hat x_0)/\tilde\sigma_t = 0.5; x~t−1=1.3333+(0.75−1.3333)⋅0.5=1.0417\tilde x_{t-1} = 1.3333 + (0.75 - 1.3333) \cdot 0.5 = 1.0417, and 0.8⋅1.0417=0.83330.8 \cdot 1.0417 = 0.8333 ✓.
    2. Write the reverse-time SDE and the probability-flow ODE for the VP SDE with β(t)\beta(t).
      AnswerReverse SDE: dx=[−12β(t)x−β(t)∇log⁡pt(x)]dt+β(t) dwˉdx = \big[-\tfrac12\beta(t)x - \beta(t)\nabla\log p_t(x)\big]dt + \sqrt{\beta(t)}\,d\bar w (with dt<0dt < 0). PF-ODE: dx/dt=−12β(t)[x+∇log⁡pt(x)]dx/dt = -\tfrac12\beta(t)\big[x + \nabla\log p_t(x)\big].
    3. Why may the DDIM forward process be non-Markovian?
      AnswerThe training loss uses only the pairs (x0,xt)(x_0, x_t) at single times, through q(xt∣x0)q(x_t \mid x_0). Every member of the DDIM family has the DDPM marginals q(xt∣x0)q(x_t \mid x_0), so the optimal εθ(xt,t)=E[ε∣xt]\varepsilon_\theta(x_t, t) = \mathbb E[\varepsilon \mid x_t] is the same, whatever the joint law across different times.
    4. For N=3N = 3 levels between σmax⁡=80\sigma_{\max} = 80 and σmin⁡=0.002\sigma_{\min} = 0.002, what is the middle level for ρ=1\rho = 1, ρ=7\rho = 7 and ρ→∞\rho \to \infty?
      Answerρ=1\rho = 1: 40.00140.001. ρ=7\rho = 7: ((1.8701+0.4116)/2)7=2.515\big((1.8701 + 0.4116)/2\big)^7 = 2.515. ρ→∞\rho \to \infty: 80⋅0.002=0.4\sqrt{80 \cdot 0.002} = 0.4, the geometric mean.
    5. The data are a single point x∗x^\ast. Show that DDIM with η=0\eta = 0 reaches x∗x^\ast exactly in one step from any σ\sigma.
      AnswerD(x;σ)=x∗D(x; \sigma) = x^\ast for every input, so the Euler step to σ=0\sigma = 0 gives x+(0−σ)(x−x∗)/σ=x∗x + (0 - \sigma)(x - x^\ast)/\sigma = x^\ast. The true path x(σ)=x∗+σεx(\sigma) = x^\ast + \sigma\varepsilon is a straight line, so Euler has no discretization error.
    6. In the reverse SDE, the score term is g2∇log⁡ptg^2\nabla\log p_t; in the probability-flow ODE it is 12g2∇log⁡pt\tfrac12 g^2\nabla\log p_t. Explain the factor in one or two sentences.
      AnswerThe forward Fokker–Planck equation has the spreading term 12g2Δp\tfrac12 g^2\Delta p. The ODE has no noise, so its drift must undo exactly this term: 12g2∇log⁡p\tfrac12 g^2\nabla\log p. The reverse SDE adds noise of strength gg again (another 12g2Δp\tfrac12 g^2\Delta p), so its drift must undo twice as much: g2∇log⁡pg^2\nabla\log p.
    Recall cardsAnswer in your head first, then reveal. Add the cards to your review deck to see them again just before you would forget.
    1. Write the reverse-time SDE for the forward SDE dx=f dt+g dwdx = f\,dt + g\,dw.

    2. Write the probability-flow ODE for the forward SDE dx=f dt+g dwdx = f\,dt + g\,dw.

    3. Give the VP SDE and say what discrete process it is the limit of.

    4. Why do the reverse SDE and the PF-ODE have the same marginals?

    5. Write the DDIM update with η=0\eta = 0.

    6. In which coordinates is DDIM (η=0\eta = 0) exactly Euler's method, and for which ODE?

    7. What are the order and the NFE count of Heun's method with NN steps ending at σ=0\sigma = 0?

    8. Give the Karras step schedule.

    9. ODE or SDE sampler: what does each do better?

    Open the review →

    9. Interview angle #

    • "Make sampling 20 times faster without retraining." — DDIM, Heun or DPM-Solver++(2M) on a Karras grid: 10–50 NFE instead of 1,000. If retraining is allowed: distillation or consistency models.
    • "What is the probability-flow ODE, and why does it give the right distribution?" — dx/dt=f−12g2∇log⁡ptdx/dt = f - \tfrac12 g^2\nabla\log p_t. The Fokker–Planck equation of the forward SDE can be written as a continuity equation with this velocity.
    • "Is DDIM deterministic? Why is that useful?" — With η=0\eta = 0, yes: inversion, interpolation in noise space, reproducible samples, likelihoods.
    • "What is DDIM in ODE terms?" — Euler on the PF-ODE in (σt/at,xt/at)(\sigma_t/a_t, x_t/a_t), so first order; it is also the first-order exponential integrator (DPM-Solver-1).
    • "ODE or SDE sampler?" — ODE at low NFE. Noise corrects earlier errors when you can afford more steps; tune the amount empirically.
    • "Heun with 18 noise levels: how many network calls?" — 35; the last step is Euler.
    • "Why are the noise levels not uniform?" — The paths bend at low noise; Karras ρ=7\rho = 7 puts short steps there.

    10. Practice #

    The notebook for this lesson has five exercises, each with a check cell and a folded solution:

    1. Simulate the VP SDE with Euler–Maruyama and compare the mean and variance of xtx_t with the closed form.
    2. Sample the 8 Gaussians with the reverse SDE and with the probability-flow ODE (exact score); compare the counts per mode and the spread.
    3. Write the DDIM step with η\eta; check that η=0\eta = 0 equals an Euler step in (σ~,x~)(\tilde\sigma, \tilde x) to rounding error, and that η=1\eta = 1 has the DDPM posterior variance.
    4. Euler vs Heun on the Gaussian toy: error against NFE; fit the slopes of the log–log lines.
    5. Karras ρ\rho vs a uniform grid: sample quality at 10 NFE with the exact 8-Gaussians denoiser.

    11. Watch and read #

    Watch how flows and diffusion models are both defined by an ODE or SDE from noise to data, and how they are simulated with Euler and Euler–Maruyama.
    Open on YouTube · the player loads from youtube-nocookie.com when you press play
    Follow the non-Markovian forward process and the role of the noise parameter; compare with the deeper box of section 3.
    Open on YouTube · the player loads from youtube-nocookie.com when you press play
    A step-by-step derivation of score-based models, the SDE view and the probability-flow ODE.
    Open on YouTube · the player loads from youtube-nocookie.com when you press play
    • Song, Sohl-Dickstein, Kingma, Kumar, Ermon, Poole, Score-Based Generative Modeling through Stochastic Differential Equations (ICLR 2021): section 3 (VE and VP SDEs, the reverse SDE), section 4.3 (the probability-flow ODE), Appendix D (the family of samplers).
    • Song, Meng, Ermon, Denoising Diffusion Implicit Models (ICLR 2021): eq. 7 (the non-Markovian forward process), Theorem 1, eq. 12 (sampling), eq. 16 (η\eta), section 4.3 (the ODE view) and Table 1.
    • Karras, Aittala, Aila, Laine, Elucidating the Design Space of Diffusion-Based Generative Models (EDM, 2022): section 3 (eq. 5 time steps, Algorithm 1 Heun, Figure 3 curvature), section 4 (Algorithm 2, the stochastic sampler).
    • Lu, Zhou, Bao, Chen, Li, Zhu, DPM-Solver (NeurIPS 2022): section 3 (the exact solution of the linear part), section 4.1 (DPM-Solver-1 = DDIM).
    • Anderson, Reverse-time diffusion equation models, Stochastic Processes and their Applications 12 (1982): the original time-reversal result.
    • Holderrieth and Erives, An Introduction to Flow Matching and Diffusion Models (MIT 6.S184 lecture notes, 2025): chapters on SDEs, the Fokker–Planck equation and sampling.
    • k-diffusion sampling.py: short reference implementations of get_sigmas_karras, sample_euler, sample_heun, sample_euler_ancestral and sample_dpmpp_2m.