TAI BUI
Back to curriculum
BuildPython

Diffusion Models — DDPM from Scratch

Diffusion destroys data with noise over many small steps (the DDPM paper uses about a thousand; the toy in this lesson uses 40), trains one neural net to predict that noise, and reverses the process at inference. Ho, Jain, Abbeel (2020) gave the field that recipe, and it stuck. Today most mainstream image, video, 3D, and music models run on this loop, sometimes with flow matching or consistency tricks on top.

TIME: ~75 minutesPREREQUISITES: Phase 3 · 02 (Backprop), Phase 8 · 02 (VAE)

Learning Objectives

  • Write the closed-form forward step `q(x_t | x_0)` and use it to produce a noisy `x_t` from `x_0` in one line.
  • Explain why the DDPM training loss regresses on the noise that was added, and why that gives one stable loss with no minimax.
  • Implement a 1-D DDPM in pure Python: the beta schedule, a hand-written backward pass, and the reverse sampling loop.
  • Choose between ε- and v-prediction, linear and cosine schedules, and DDIM-style fast samplers for a real diffusion setup.

The Problem

You want a sampler for p_data(x). GANs play a minimax game that often diverges. VAEs produce blurry samples from a Gaussian decoder. What you want is a training objective with three properties: (a) a single stable loss, with no saddle point and no minimax, (b) a lower bound on log p(x), so you have likelihoods, and (c) samples that match SOTA quality.

Sohl-Dickstein et al. (2015) had a theoretical answer: define a Markov chain q(x_t | x_{t-1}) that gradually adds Gaussian noise, and train a reverse chain p_θ(x_{t-1} | x_t) to denoise. Ho, Jain, Abbeel (2020) simplified the loss to one line, predict the noise, and cleaned up the math. In 2020 this was a curiosity. In 2021 it produced state-of-the-art samples. In 2022 it became Stable Diffusion. In 2026 it is the substrate.

Warm-up0/3
1. A VAE trained on faces produces blurry samples. What is the usual cause?Warm-up
Two sharp 8x8 grey-level face stand-ins on the left, labelled training images, and two blurry 8x8 stand-ins on the right, labelled samples from the trained VAE.
Typed stand-ins that show the symptom in the stem, not real VAE output.
2. Why does a VAE use the reparameterization trick?Warm-up
Block diagram of a VAE: input x goes into an encoder that outputs a mean and a standard deviation, which feed a sampling step z = mean + std times epsilon with epsilon drawn from N(0, 1), whose output z goes to a decoder that produces x-hat.
Typed standard VAE layout, not captured from code.
3. A model outputs y_hat and the loss is (y_hat - y)^2. What is the gradient of the loss with respect to y_hat?Warm-up
Parabola of the squared-error loss against y-hat with target y = 1, its minimum at y-hat = 1 and the current point at y-hat = 3.
Illustrative values y = 1 and y-hat = 3; the stem gives no numbers.

The Concept

A denoising diffusion probabilistic model (DDPM) is a generative model built from two chains: a fixed forward process that turns data into Gaussian noise in small steps, and a learned reverse process that removes the noise one step at a time. One network, trained to predict the noise that was added, drives the reverse chain.

DDPM: forward noise, reverse denoise

Forward process q. Add Gaussian noise in T small steps. The cumulative step also has a closed form because it is Gaussian, which is what makes the math tractable:

q(x_t | x_0) = N( sqrt(α̅_t) · x_0,  (1 - α̅_t) · I )

where α̅_t = ∏_{s=1..t} (1 - β_s) for a schedule of β_t. Pick β_t from 1e-4 to 0.02 linearly over T=1000 steps and x_T is approximately N(0, I). The toy in Build It is much shorter: it uses T=40 with the same β range, so α̅_T ≈ 0.67 and x_T still keeps about sqrt(0.67) ≈ 0.82 of the original signal. Its sample() starts from N(0, 1) anyway, a mismatch that a 1-D problem tolerates but real images would not.

Reverse process p_θ. Learn a neural net ε_θ(x_t, t) that predicts the noise that was added. Given x_t, denoise by:

x_{t-1} = (1 / sqrt(α_t)) · ( x_t - (β_t / sqrt(1 - α̅_t)) · ε_θ(x_t, t) )  +  σ_t · z

where σ_t is either sqrt(β_t) or a learned variance. The expression is ugly but it is just algebra, solving for x_{t-1} given the posterior q(x_{t-1} | x_t, x_0) and substituting x_0 with its noise-predicted estimate.

Training loss.

L_simple = E_{x_0, t, ε} [ || ε - ε_θ( sqrt(α̅_t) · x_0 + sqrt(1 - α̅_t) · ε,  t ) ||² ]

Sample x_0 from data, pick a random t, sample ε ~ N(0, I), compute the noisy x_t in one shot via the closed form, and regress on the noise. One loss, no minimax, no KL, no reparameterization tricks.

Sampling. Start x_T ~ N(0, I) and iterate the reverse step from t = T to 1. The final x is the sample.

Check: The Concept0/10
1. What is the forward process in a diffusion model?Define
Line chart of five runs of a chain over 40 steps, each starting at x_0 = 2.0, with the values wandering around 2 as the step number grows.
Computed with numpy using the toy schedule (T = 40, beta from 1e-4 to 0.02).
2. What is the reverse process in a diffusion model?Define
Line chart of five runs of the sampling loop over 40 steps, each starting at a different random draw from N(0, 1) at t = 39 and ending at a different value at t = 0.
Five runs of main.py's sample() with the net trained by main.py's recipe (seed 13, 4000 steps).
3. What is the β schedule in DDPM?Define
Line chart of the 40 values beta_t from the toy schedule, rising in a straight line from 0.0001 at t = 0 to 0.02 at t = 39.
Values from the lesson's make_schedule(40).
4. What is α̅_t in DDPM?Define
Line chart of alpha-bar_t for t = 0 to 39 in the toy schedule, falling smoothly from 0.9999 at t = 0 to about 0.67 at t = 39.
Values from the lesson's make_schedule(40).
5. What does the DDPM training loss L_simple regress on?Define
Diagram of one training step: x_0 = -2.211, t = 17, epsilon = +0.691 and x_t = -1.933 are listed on the left; the net takes x_t and t and outputs +0.459; a loss box compares the net output with an unfilled slot marked ?.
Last of the 4000 training steps of main.py (seed 13).
6. What is DDPM, as the Key Terms define it?Define
Four cards spelling the acronym: Denoising, Diffusion, Probabilistic, Models, above a chain x_0, x_1, ..., x_T with a note that one network epsilon-theta(x_t, t) is used and that the toy uses T = 40.
Typed, from the lesson's wording.
7. What is the closed-form forward distribution q(x_t | x_0) in DDPM?Define
Four horizontal strips of 100 dots each, for t = 0, 10, 20 and 39, showing where runs of a chain started at x_0 = 2.0 end up; the dots sit tightly around 2 at t = 0 and spread wider and shift left as t grows.
100 of 20000 simulated runs per strip, computed with numpy on the toy schedule.
8. Why does regressing on the added noise give one stable loss with no minimax?Explain
Line chart of the mean training loss per block of 250 steps over 4000 steps in the toy run, as one curve against the training step.
Single-sample losses from a replay of main.py's training loop (seed 13), averaged in blocks.
9. How does the DDPM reverse process compute x_{t-1} from x_t?Explain
Table of the values at reverse step t = 20 in the toy: x_t +1.6116, the net's output -0.1555, alpha_t 0.98969, beta_t 0.01031, alpha-bar_t 0.89616, and the next value x_(t-1) shown as a question mark.
Step t = 20 of one run of main.py's sample() with the trained net (seed 13).
10. The toy uses T=40 with β from 1e-4 to 0.02, giving α̅_T ≈ 0.67, yet sample() starts from N(0,1). What does that tell you?Analyze
Table comparing T = 1000 (alpha-bar_T = 0.000040, square root 0.0064) with the toy's T = 40 (alpha-bar_T = 0.67, square root 0.82), and a note that sample() starts from N(0, 1).
Computed from the lesson's schedule; the toy row matches the lesson's 0.67.

Why it works

Three intuitions:

  1. Denoising is easy; generating is hard. At t=T the data is pure noise, so the net has a trivial problem to solve. At t=0 it only has to clean up a few pixels. At intermediate t the problem is hard, but the same weights get gradients from all noise levels.

  2. Score matching in disguise. Vincent (2011) proved that predicting the noise is equivalent to estimating ∇_x log q(x_t | x_0), the score. The reverse SDE uses this score to walk up the density gradient, a guided random walk toward high-probability regions.

  3. The ELBO reduces to simple MSE. The full variational lower bound has a KL term per timestep. With DDPM's parameterization those KL terms simplify to MSE on noise prediction with specific coefficients. Ho dropped the coefficients (calling it the "simple" loss) and quality improved.

Check: Why it works0/3
1. Why is denoising a more tractable learning problem than generating from scratch?Explain
Four histograms of the toy's two-mode data after noising at t = 0, 10, 20 and 39: two sharp humps near -2 and 2 at t = 0 that widen and merge toward one hump as t grows.
20000 draws of the closed-form x_t from the toy's mixture, computed with numpy.
2. What did Vincent (2011) prove that connects noise prediction to score-based models?Explain
A bell-shaped density of x_t given x_0 = 2.0 at t = 20, with five arrows below the axis that start at different x_t values and all point toward the peak, longer the farther from it.
Computed from the closed-form distribution; arrow lengths proportional to the gradient of the log density.
3. What did Ho do with the per-timestep KL coefficients in the variational bound?Explain
A row of boxes w_1 times MSE, w_2 times MSE, w_3 times MSE, ellipsis, w_T times MSE, described as one term per timestep, each a mean squared error on the noise prediction multiplied by its coefficient w_t.
Typed schematic of the lesson's description; the coefficient values are not shown.

Build It

main.py is a 1-D DDPM. The data is a two-mode mixture, and the "net" is a tiny MLP that takes (x_t, t) and outputs the predicted noise. Training is the one-line loss, and sampling iterates the reverse chain. The snippets below are illustrative; they follow main.py closely but compress it.

Step 1: the forward schedule (closed form)

In main.py this lives in make_schedule(T), which returns (betas, alphas, alpha_bars).

python
betas = [1e-4 + (0.02 - 1e-4) * t / (T - 1) for t in range(T)]
alphas = [1 - b for b in betas]
alpha_bars = []
cum = 1.0
for a in alphas:
    cum *= a
    alpha_bars.append(cum)

Step 2: sample x_t in one shot

python
def forward_sample(x0, t, alpha_bars, rng):
    a_bar = alpha_bars[t]
    eps = rng.gauss(0, 1)
    x_t = math.sqrt(a_bar) * x0 + math.sqrt(1 - a_bar) * eps
    return x_t, eps

Step 3: one training step

train() runs this inline for each update: draw a fresh x0 and a random t, build x_t with the closed form, embed t, run the forward pass, backprop by hand, and apply plain SGD. The loss (eps_hat - eps)**2 is only computed for printing, every 500 steps. The gradient backward uses is 2 * (eps_hat - eps) directly.

python
x0 = sample_data(rng)
t = rng.randrange(T)
x_t, eps = forward_sample(x0, t, alpha_bars, rng)   # Step 2, inlined in main.py
t_emb = sin_embed(t, T, t_dim)
eps_hat, cache = forward([x_t], t_emb, net)
grads = backward([eps], eps_hat, cache, net)
apply_update(net, grads, lr)

Step 4: reverse sampling

We need the full alphas list as well as alpha_bars, and the net needs the same time embedding it saw in training. The last step (t = 0) adds no fresh noise.

python
def sample(net, alphas, alpha_bars, T, t_dim, rng):
    x = rng.gauss(0, 1)
    for t in range(T - 1, -1, -1):
        t_emb = sin_embed(t, T, t_dim)
        eps_hat, _ = forward([x], t_emb, net)
        beta_t = 1 - alphas[t]
        mean = (x - beta_t / math.sqrt(1 - alpha_bars[t]) * eps_hat[0]) / math.sqrt(alphas[t])
        if t > 0:
            x = mean + math.sqrt(beta_t) * rng.gauss(0, 1)
        else:
            x = mean
    return x

With 40 timesteps and a 24-unit MLP on this 1-D problem, main() trains for 4000 single-sample SGD updates (learning rate 0.01, one fresh x0 per step, no epochs). That is enough for 500 samples to land on both sides of zero, roughly 230 on the negative mode and 270 on the positive one.

Check: Build It0/5
1. In the toy training loop you draw t = 30. How do you get x_t?Apply
A strip of 40 timestep cells with t = 30 highlighted, above two boxes: a known clean x_0 on the left and an unknown x_t for t = 30 on the right, with a note that one noise draw is available.
Typed schematic of the stem's situation.
2. In forward_sample, a_bar = 0.64, x0 = 1.0 and eps = 0.5. What does it return?Apply
The lesson's forward_sample function in a code card, below it the values a_bar = 0.64, x0 = 1.0 and a noise draw of 0.5, and a return value shown as a question mark.
Code copied from the lesson; the values are the stem's.
3. At t = 0 in sample(), alphas[0] = alpha_bars[0] = 0.64, x = 1.0 and eps_hat = 0.5. What does the loop set x to?Apply
The lesson's reverse-loop body in a code card, with the values for t = 0: alphas[0] and alpha_bars[0] both 0.64, x = 1.0, eps_hat[0] = 0.5, and the resulting x shown as a question mark.
Code copied from the lesson, one long line wrapped; the values are the stem's.
4. In make_schedule with T = 40, what is alpha_bars[0] and what does it mean for x_t at t = 0?Apply
The lesson's make_schedule code in a code card, with betas[0] = 0.0001 and betas[1] = 0.00061 listed below and alpha_bars[0] shown as a question mark.
Code copied from the lesson; betas computed from it.
5. In train(), eps = 0.3 and eps_hat = 0.8. What gradient does backward start from?Apply
Two lines of the lesson's training step calling forward and backward, with eps = 0.3, eps_hat = [0.8] and a squared-error loss listed below, and the value backward starts from shown as a question mark.
Code copied from the lesson; the values are the stem's.

Time conditioning

The net needs to know which timestep it is denoising. Two standard options:

  • Sinusoidal embedding. Like Transformer positional encoding: embed(t) = [sin(t/ω_0), cos(t/ω_0), sin(t/ω_1), ...]. The usual recipe passes it through a small MLP, then broadcasts the result into the net.
  • FiLM / group-norm conditioning. Project the embedding to per-channel scale and bias (FiLM) at each block.

The toy code skips the MLP: it concatenates the raw 8-dimensional sinusoidal vector (t_dim=8) onto x_t as the input to the first layer. Production U-Nets use FiLM.

Check: Time conditioning0/3
1. How does the toy code give the network the timestep t?Explain
Block diagram of the toy network: input x_t and the unresolved question of how t enters, two layers of 24 tanh units, and one output; weight shapes W1 24 by unknown, W2 24 by 24, W3 1 by 24.
Layer sizes from main.py's init_net with hidden = 24 and x_dim = 1.
2. How does FiLM conditioning differ from the toy's way of passing t?Explain
Two sketches. Left, the toy: x_t and an 8-value sinusoidal t embedding both go into layer 1. Right, a production U-Net with blocks 1, 2 and N and a t embedding box below with a dashed question-mark link asking how it reaches the blocks.
Typed schematic from the lesson's Time conditioning section.
3. You scale the toy to images and quality stalls. Which plan follows the lesson's advice?Design
A card of six rows listing the toy's recipe from main.py (1-D two-mode mixture, linear beta with T = 40, target epsilon, closed-form x_t, simple MSE loss, 8-dim sinusoidal time embedding concatenated at the input), with a note that scaling to images stalls quality.
Recipe read from main.py and the lesson text.

Pitfalls

  • Schedule matters a lot. Linear β is the DDPM default, but the cosine schedule (Nichol & Dhariwal, 2021) gives better FID for the same compute. Switch schedules if quality plateaus.
  • Timestep embedding is fragile. Feeding raw t as a float is a trap, which is why even the 1-D toy uses a sinusoidal embedding. Images need a proper embedding too, injected at every block.
  • V-prediction vs ε-prediction. For narrow regimes (very small or very large t), ε has poor signal-to-noise. V-prediction (v = α·ε - σ·x) is more stable, and Stable Diffusion 2.x (768-v) and Imagen Video use it. SDXL mostly predicts ε, while SD3 and Flux predict a related velocity in a rectified-flow parameterization.
  • Classifier-free guidance. At inference, compute both conditional and unconditional ε, then ε_cfg = (1 + w) · ε_cond - w · ε_uncond with w ≈ 3-7. Covered in Lesson 08.
  • 1000 steps is a lot. Production uses DDIM (20-50 steps), DPM-Solver (10-20 steps), or distillation (1-4 steps). See Lesson 12.
Check: Pitfalls0/9
1. What does ε-prediction mean in a diffusion model?Define
Diagram: a clean value x_0 = 1.0 and a noise draw epsilon = 0.5 combine with alpha-bar 0.64 into x_t = 1.1, which goes into the network epsilon-theta(x_t, t) whose output is marked with a question mark.
Numbers computed from the closed-form step with alpha-bar 0.64 (the same values as another question in this lesson).
2. What does V-prediction mean in a diffusion model?Define
Table of the weight on x_0 and the weight on epsilon in x_t at t = 0, 10, 20 and 39 in the toy: 1.000 and 0.010 at t = 0, falling to 0.817 and 0.577 on the x_0 side and rising on the noise side by t = 39.
Computed from the lesson's make_schedule(40).
3. What does classifier-free guidance compute at inference, and with what typical w?Define
Diagram of one sampling step: x_t, t and a label c feed a network run with the label (output epsilon-cond) and with the label dropped (output epsilon-uncond); both go into a combine step with weight w whose result epsilon-cfg is a question mark.
Typed schematic, no numbers.
4. Why does the Pitfalls section prefer v-prediction over ε-prediction in some regimes?Explain
Two curves over t = 0 to 999: the weight on x_0 in x_t falls from about 1 to under 0.01, and the weight on the noise epsilon rises from 0.01 to about 1.
Computed from the lesson's linear schedule with T = 1000.
5. Which β schedule is reported to give better FID than the linear default at the same compute?Explain
Curve of alpha-bar_t against t for the default linear beta schedule with T = 1000, falling from about 1 at t = 0 to nearly 0 at t = 999.
Computed from the lesson's make_schedule(1000).
6. With w = 3, ε_cond = 1.0 and ε_uncond = 0.4, what is ε_cfg?Apply
A number line with the unconditional prediction at 0.4 and the conditional prediction at 1.0, and the guidance formula written below with w = 3.
Values from the stem.
7. A colleague feeds the raw float t into the network and samples look poor. What does the lesson say about this?Analyze
Histogram of 500 samples from the toy net when t is fed in as a raw float: one wide hump centred near 0 instead of two humps near -2 and 2, with a count of samples below and above zero.
Real run of main.py with sin_embed replaced by the raw float t (input size 1 instead of 8), same training seed, 4000 steps.
8. A linear-β, ε-prediction run plateaus in quality. Which change fits the lesson's advice?Analyze
A chart of alpha-bar_t falling from 1 to near 0 across T = 1000 for a linear beta schedule, next to a card that lists the run: schedule linear beta, target epsilon, quality plateaued.
Setup from the stem; the curve is illustrative, computed from the lesson's default linear schedule (T = 1000).
9. You need class-conditional sampling with one network, no second model, and a knob for conditioning strength. What design fits?Design
Three boxes joined left to right: one network with no second model, a class label c, and a strength knob at sampling time, above a line describing the wanted output.
Requirements from the stem, typed.

Use It

RoleTypical stack in 2026
Image pixel-space diffusion (small, toy)DDPM + U-Net
Image latent diffusionVAE encoder + U-Net or DiT (Lesson 07)
Video latent diffusionSpatiotemporal DiT (Sora, Veo, WAN)
Audio latent diffusionEncodec + diffusion transformer
Science (molecules, proteins, physics)Equivariant diffusion (EDM, RFdiffusion, AlphaFold3)

Diffusion is the generative backbone across all of these rows. Flow matching (Lesson 13) is the 2024-2026 competitor that usually wins on inference speed for the same quality.

Check: Use It0/1
1. How does the Use It section position flow matching against diffusion?Explain
Five rows from the lesson's Use It table, each with a role and its typical stack: image pixel-space, image latent, video latent, audio latent and science.
Copied from the lesson's Use It section.

Ship It

Save outputs/skill-diffusion-trainer.md. The skill takes a dataset and a compute budget and outputs a schedule (linear/cosine/sigmoid), prediction target (ε/v/x), number of steps, guidance scale, sampler family, and an eval protocol.

Author's Note

I'd run main.py before rereading the math. It trains on a 1-D two-mode mixture and prints a histogram plus a count of samples on each side of zero, so you can check by eye whether the reverse chain recovered both modes. The backward pass is hand-written, which means there is no framework between you and the loss.

My bet is that the closed-form forward step is the part worth rereading until it feels boring. In the training loop, x_t comes from x0, a_bar, and one draw of noise in a single line, with no loop over earlier steps. Once that clicks, the loss reads as plain regression on the noise you just added.

I'd also keep the toy in proportion. It uses the linear schedule and concatenates a sinusoidal embedding onto the input, which is a fine choice for a 1-D problem. On something bigger I'd try the cosine schedule and v-prediction from the Pitfalls list once quality plateaus, and use FiLM for time conditioning the way a real U-Net does.

Exercises

  1. Easy. Change T from 40 to 10 in main() of main.py (it is a local variable there, passed to make_schedule, train, and sample). How does sample quality (visual histogram of outputs) degrade? At what T does the two-mode structure collapse?
  2. Medium. Switch from ε-prediction to v-prediction. Re-derive the reverse step. Compare final sample quality.
  3. Hard. Add classifier-free guidance. Condition on a class label c ∈ {0, 1}, drop it 10% of the time during training, and at sampling time use ε = (1+w)·ε_cond - w·ε_uncond. Measure the conditional-mode-hit rate at w = 0, 1, 3, 7.

Key Terms

TermWhat people sayWhat it actually means
Forward process"Adding noise"Fixed Markov chain q(x_t | x_{t-1}) that destroys the data.
Reverse process"Denoising"Learned chain p_θ(x_{t-1} | x_t) that reconstructs the data.
β schedule"The noise ladder"Per-step variance; linear, cosine, or sigmoid.
α̅"Alpha bar"Cumulative product ∏(1 - β); gives closed-form x_t from x_0.
Simple loss"MSE on noise"||ε - ε_θ(x_t, t)||²; all variational derivations collapse to this.
ε-prediction"Predict noise"Output is the noise added; standard DDPM.
V-prediction"Predict velocity"Output is α·ε - σ·x; better conditioning across t.
DDPM"The paper"Ho et al. 2020; linear β, 1000 steps, U-Net.
DDIM"Deterministic sampler"Non-Markov sampler, 20-50 steps, same training objective.
Classifier-free guidance"CFG"Mix conditional and unconditional noise predictions to amplify conditioning.

Production note: diffusion inference is a step-count problem

The DDPM paper runs T=1000 reverse steps. Production stacks rarely ship that. Real inference setups pick from three strategies, and each maps to a place the latency comes from:

  1. Faster sampler, same model. DDIM (20-50 steps), DPM-Solver++ (10-20), UniPC (8-16). It is a drop-in replacement for the reverse loop, and the trained ε_θ weights are untouched. Cuts latency 20-50×.
  2. Distillation. Train a student to match the teacher in fewer steps: Progressive Distillation (2 → 1), Consistency Models (arbitrary → 1-4), LCM, SDXL-Turbo, SD3-Turbo. Cuts latency another 5-10× and requires retraining.
  3. Caching and compilation. torch.compile(unet, mode="reduce-overhead"), TensorRT-LLM's diffusion backends, xformers/SDPA attention, bf16 weights. Cuts per-step latency ~2×. Stacks with (1) and (2).

The budget conversation for a diffusion server mirrors the one for LLMs: latency is num_steps × step_cost + VAE_decode, and throughput is batch_size × (num_steps × step_cost)^-1. TTFT is small (one step). The TPOT equivalent is the full response time, because image generation is "all-at-once" from the user's perspective.

Check: Production note: diffusion inference is a step-count problem0/7
1. What is DDIM?Define
Two rows of word cards: DDPM spelled Denoising Diffusion Probabilistic Models and DDIM spelled Denoising Diffusion Implicit Models, with a note that DDIM is from Song, Meng and Ermon 2021.
Typed from the lesson's reference list.
2. Why can DDIM replace the DDPM reverse loop without retraining the network?Explain
Pipeline diagram: training with the L_simple loss produces trained epsilon-theta weights, which feed the DDPM reverse loop of T = 1000 steps; a dashed box below the reverse loop asks whether DDIM goes there.
Typed schematic from the lesson's production section.
3. Which is NOT one of the three production strategies for cutting diffusion inference latency?Explain
Boxes num_steps, step_cost and VAE_decode joined by a multiplication sign and a plus sign, with the formula latency = num_steps times step_cost plus VAE_decode written below.
Typed from the lesson's production section.
4. A server runs 50 steps at 0.04 s per step, then a 0.2 s VAE decode. What is the latency by the lesson's formula?Apply
A horizontal bar made of 50 narrow blue blocks labelled 50 steps at 0.04 s each, followed by one wider orange block labelled VAE decode 0.2 s, drawn to scale in time.
Drawn to scale from the stem's numbers.
5. torch.compile and bf16 halve the per-step cost. A teammate says a faster sampler is now pointless. What is wrong with that?Analyze
Two rows of eight blocks, each block one sampler step; the top row labelled before has wide blocks and the bottom row labelled after has blocks half as wide, with the same number of blocks in both.
Illustrative: 8 blocks stand in for the steps, widths are in the ratio the stem gives.
6. Your trained DDPM needs 1000 reverse steps per image, which is too slow, and you cannot retrain. What is the first fix?Design
A box for the trained network with fixed weights and no retraining, an arrow to a reverse-loop box reading 1000 steps per image, and a row of 50 blocks each standing for 20 net calls.
Typed schematic from the stem: 50 blocks of 20 calls make 1000.
7. A product needs images in 1-4 steps, and you have budget to retrain. Which approach fits?Design
A log-scale line from 1 to 1000 steps per image with the band from 1 to 4 steps marked as needed and the DDPM paper's T = 1000 marked at the right end, and a note that a retraining budget is available.
Values from the stem and the lesson.

Further Reading

Code

Ships With