Part XIII — Generative Models: VAEs, Flow Matching, Score Models & Diffusion Transformers · Chapter 7

Deterministic sampling with DDIM

Hook

The last chapter's reverse process only ever moved one adjacent timestep at a time: xtxt1x_t \to x_{t-1}, over and over, a full Markov chain's worth of model calls to generate a single sample. What if the model's noise prediction let you jump straight from xtx_t to any earlier timestep — skipping most of the chain — and still land in exactly the same place?

Intuition
step 0/4 (t=4): x = 3.1305

Toggle between Deterministic (DDIM) and Stochastic (DDPM-style), then step through the same 4-step schedule with the same starting noise and the same (perfect) predictor. The deterministic path retraces a single fixed trajectory back to x0=5x_0=5 every time. The stochastic path, built from the exact same ingredients plus one injected random draw per step, wanders off that trajectory and never quite gets back.

Formalize

Every reverse step first estimates x0x_0 directly from the current xtx_t, then rebuilds xtprevx_{t_{\text{prev}}} from that estimate using the forward process's own closed form:

x^0=xt1αˉtϵ^αˉt,xtprev=αˉtprevx^0+1αˉtprev[(1η)ϵ^+ηz]\hat{x}_0 = \frac{x_t - \sqrt{1-\bar\alpha_t}\,\hat\epsilon}{\sqrt{\bar\alpha_t}}, \qquad x_{t_{\text{prev}}} = \sqrt{\bar\alpha_{t_{\text{prev}}}}\,\hat{x}_0 + \sqrt{1-\bar\alpha_{t_{\text{prev}}}}\Big[(1-\eta)\hat\epsilon + \eta z\Big]
  • αˉt\bar\alpha_t — the cumulative signal-retained fraction at timestep tt: how much of the original signal survives, all the way from t=0t=0.
  • ϵ^\hat\epsilon — the model's predicted noise direction at xtx_t, assumed here to be exactly correct.
  • x^0\hat{x}_0 — the estimate of the clean data, computed directly from xtx_t and ϵ^\hat\epsilon, regardless of which path reached (xt,t)(x_t, t).
  • η\eta — the noise-injection knob. η=0\eta=0 is DDIM: fully deterministic. η=1\eta=1 replaces the predicted direction with an injected draw zz, a DDPM-style stochastic step.
  • tprevt_{\text{prev}} — the next timestep in the schedule — any earlier timestep, not necessarily t1t-1.
  1. x0 is estimated the same way no matter how x_t was reached

    Because x^0\hat{x}_0 depends only on (xt,t,ϵ^)(x_t, t, \hat\epsilon) — never on the sequence of steps that produced xtx_t — a perfect predictor recovers the exact same x^0\hat{x}_0 whether tprevt_{\text{prev}} is one step back or ten.

  2. That's what makes skipping timesteps free

    Since tprevt_{\text{prev}} can be any earlier timestep in the formula above, a schedule can skip straight from t=4t=4 to t=2t=2 to t=0t=0, using the model only twice instead of four times, and land on the identical trajectory a full step-by-step schedule would trace.

  3. Injected noise breaks that guarantee

    Setting η>0\eta > 0 swaps part of the deterministic direction for a random draw zz. Once xtx_t is knocked off the clean marginal it was estimated from, the next step's x^0\hat{x}_0 estimate inherits that error — which is exactly why a stochastic path drifts even from identical starting noise and an identical predictor.

Play

Drag η\eta from 00 up to 11. At η=0\eta=0 (pure DDIM) the reconstruction is exact. As η\eta rises, more of each step's direction comes from the fixed random draws instead of the predictor, and the final reconstructed x0x_0 drifts further from the true value — even though the predictor itself never changes.

Worked example

With x0=5x_0=5, ϵ^=1\hat\epsilon=1 always correct, and αˉ=[1,0.8,0.6,0.4,0.2]\bar\alpha = [1, 0.8, 0.6, 0.4, 0.2] for t=0,1,2,3,4t=0,1,2,3,4:

  1. Full schedule: every step, t = 4 -> 3 -> 2 -> 1 -> 0

    Each hop estimates x^0=(xt1αˉt)/αˉt\hat x_0=(x_t-\sqrt{1-\bar\alpha_t})/\sqrt{\bar\alpha_t} (since ϵ^=1\hat\epsilon=1), then rebuilds xtprev=αˉtprevx^0+1αˉtprevx_{t_{\text{prev}}}=\sqrt{\bar\alpha_{t_{\text{prev}}}}\hat x_0+\sqrt{1-\bar\alpha_{t_{\text{prev}}}}:

    • t=43t=4\to3: x^0=(3.13050.8)/0.2=(3.13050.8944)/0.4472=5.0000\hat x_0=(3.1305-\sqrt{0.8})/\sqrt{0.2}=(3.1305-0.8944)/0.4472=5.0000; x3=0.4(5)+0.6(1)=3.1623+0.7746=3.9369x_3=\sqrt{0.4}(5)+\sqrt{0.6}(1)=3.1623+0.7746=3.9369
    • t=32t=3\to2: x^0=(3.93690.6)/0.4=(3.93690.7746)/0.6325=5.0000\hat x_0=(3.9369-\sqrt{0.6})/\sqrt{0.4}=(3.9369-0.7746)/0.6325=5.0000; x2=0.6(5)+0.4(1)=3.8730+0.6325=4.5054x_2=\sqrt{0.6}(5)+\sqrt{0.4}(1)=3.8730+0.6325=4.5054
    • t=21t=2\to1: x^0=(4.50540.4)/0.6=(4.50540.6325)/0.7746=5.0000\hat x_0=(4.5054-\sqrt{0.4})/\sqrt{0.6}=(4.5054-0.6325)/0.7746=5.0000; x1=0.8(5)+0.2(1)=4.4721+0.4472=4.9193x_1=\sqrt{0.8}(5)+\sqrt{0.2}(1)=4.4721+0.4472=4.9193
    • t=10t=1\to0: x^0=(4.91930.2)/0.8=(4.91930.4472)/0.8944=5.0000\hat x_0=(4.9193-\sqrt{0.2})/\sqrt{0.8}=(4.9193-0.4472)/0.8944=5.0000; x0=1(5)+0(1)=5x_0=\sqrt{1}(5)+\sqrt{0}(1)=5

    Every hop's x^0\hat x_0 lands exactly on 55 — the perfect predictor recovers the true x0x_0 regardless of which timestep it's called from.

  2. Skip schedule: t = 4 -> 2 -> 0, two model calls instead of four

    Starting from the identical x43.1305x_4 \approx 3.1305:

    • t=42t=4\to2: x^0=(3.13050.8)/0.2=5.0000\hat x_0=(3.1305-\sqrt{0.8})/\sqrt{0.2}=5.0000 (same computation as the full schedule's first hop, since it depends only on x4x_4); x2=0.6(5)+0.4(1)=4.5054x_2=\sqrt{0.6}(5)+\sqrt{0.4}(1)=4.5054 — the exact same value the full schedule passed through
    • t=20t=2\to0: x^0=(4.50540.4)/0.6=5.0000\hat x_0=(4.5054-\sqrt{0.4})/\sqrt{0.6}=5.0000; x0=1(5)+0(1)=5x_0=\sqrt{1}(5)+\sqrt{0}(1)=5 exactly
  3. Single jump: t = 4 -> 0

    x^0=(3.13050.8)/0.2=5.0000\hat x_0=(3.1305-\sqrt{0.8})/\sqrt{0.2}=5.0000 (the same estimate every schedule above computed from x4x_4); x0=1(5)+0(1)=5x_0=\sqrt{1}(5)+\sqrt{0}(1)=5 exactly. With a perfect predictor, the number of steps only changes the compute cost — never the destination.

  4. Now inject noise: eta = 1, fixed draws

    Running the same full schedule with η=1\eta=1 (direction =z=z instead of ϵ^\hat\epsilon) and fixed draws z=[0.5,0.5,0.5,0.3]z=[0.5,-0.5,0.5,0.3]:

    • t=43t=4\to3: x^0=5.0000\hat x_0=5.0000 (unchanged, from x4x_4); direction =z=0.5=z=0.5; x3=0.4(5.0000)+0.6(0.5)=3.1623+0.3873=3.5496x_3=\sqrt{0.4}(5.0000)+\sqrt{0.6}(0.5)=3.1623+0.3873=3.5496
    • t=32t=3\to2: x^0=(3.54960.6)/0.4=(3.54960.7746)/0.63254.3876\hat x_0=(3.5496-\sqrt{0.6})/\sqrt{0.4}=(3.5496-0.7746)/0.6325\approx4.3876 — already off the true 55, since x3x_3 was knocked off the clean marginal; direction =z=0.5=z=-0.5; x2=0.6(4.3876)+0.4(0.5)3.39900.3162=3.0828x_2=\sqrt{0.6}(4.3876)+\sqrt{0.4}(-0.5)\approx3.3990-0.3162=3.0828
    • t=21t=2\to1: x^0=(3.08280.4)/0.63.1629\hat x_0=(3.0828-\sqrt{0.4})/\sqrt{0.6}\approx3.1629; direction =z=0.5=z=0.5; x1=0.8(3.1629)+0.2(0.5)2.8295+0.2236=3.0526x_1=\sqrt{0.8}(3.1629)+\sqrt{0.2}(0.5)\approx2.8295+0.2236=3.0526
    • t=10t=1\to0: x^0=(3.05260.2)/0.82.9129\hat x_0=(3.0526-\sqrt{0.2})/\sqrt{0.8}\approx2.9129; direction =z=0.3=z=0.3; x0=1(2.9129)+0(0.3)2.913x_0=\sqrt{1}(2.9129)+\sqrt{0}(0.3)\approx2.913

    Final x02.913x_0\approx2.913 — an error of 52.9132.095-2.913\approx2.09, because each step's injected noise knocks the next step's estimate off the clean marginal it assumes.

Checkpoint

Bring η down until the reconstructed x0 is within 0.1 of the true value, 5.

Drag η to try it
Summary
x^0=xt1αˉtϵ^αˉt,xtprev=αˉtprevx^0+1αˉtprevϵ^(η=0)\hat{x}_0 = \frac{x_t - \sqrt{1-\bar\alpha_t}\,\hat\epsilon}{\sqrt{\bar\alpha_t}}, \qquad x_{t_{\text{prev}}} = \sqrt{\bar\alpha_{t_{\text{prev}}}}\,\hat{x}_0 + \sqrt{1-\bar\alpha_{t_{\text{prev}}}}\,\hat\epsilon \quad (\eta=0)

DDIM doesn't change what the model learns — it's trained exactly like the DDPM from the last chapter, to predict noise. What changes is sampling: because the reverse update estimates x0x_0 directly and is path-independent, the same trained model can generate in 50 steps instead of 1000, trading a small amount of quality for a large amount of speed, purely by choosing a different schedule at inference time. The next chapter asks what happens when the U-Net doing that noise prediction is replaced with a plain transformer.