Training needs at a random between 1 and 1000. Do we have to run the chain times?
No: a composition of Gaussians is Gaussian.
Draw , draw , jump. Each example in a batch can sit at a different noise level.
The forward process is fixed: nothing in it is learned, and inference never uses it.
: the clean image · : the same image after noise steps, out of · : how much noise step adds (the schedule) · : Gaussian over with mean and covariance · : the identity, independent noise per pixel · : standard Gaussian noise.
The loss is one line
The target is noise you drew yourself; is the text.
Ordinary supervised regression: no adversary, stable training, a loss that means something.
That, more than image quality, is why diffusion displaced the GAN.
, and predictions are interchangeable given and the schedule. They differ in how the loss weighs the noise levels.
: the network (the U-Net, with weights ) · : average over drawn images, noises and · : a mix of the two targets.
The way back: from noise prediction to a step
The network outputs . How do you get its current guess of the clean image?
Invert the closed form:
Near , : any error in is amplified. Hence and velocity objectives.
The scheduler (the rule that turns the prediction into a step) moves a little towards . Repeat.
One network, two loops
Training and sampling share one U-Net. Left, training: a batch in which every image draws its own t, one jump to x t in closed form, and a loss between the drawn noise and the predicted one that updates the weights. Right, sampling: start from noise x T, take one step back with the predicted noise, and repeat T times until x0.TRAINING · ONE PASS PER IMAGESAMPLING · T PASSES, IN ORDERεεθupdate θεθ× Tt = 1 doneA batch: each image draws its own tt = 14t = 350t = 721Jump to that noise level=√ᾱt+√(1−ᾱt)xtx0εLoss: ‖ε − εθ(xt, t)‖²one gradient step on θ, then the next batchU-Net εθ(xt, t)θStart: xT ~ 𝒩(0, I), t = Tnoise drawn from the seedOne step backxt−1 = μθ(xt, t) + σt zμθ removes a scaled share of εθσt z: fresh noise in DDPM; 0 in DDIMAfter the last step: x0a new image
The noise schedule is not a detail
SD 1.x: "scaled-linear" ( on a straight line) from 0.00085 to 0.012, steps.
How much of the image survives at the very last training step, ?
, so of the image is still there — not zero.
Sampling starts from pure noise: a train/test mismatch.
Consequence: SD 1.x cannot make a truly black or truly white image. The mean brightness leaks in.
The fix: rescale the schedule to zero terminal SNR (Lin et al., 2024).
SNR (signal-to-noise ratio) , the variance of the signal over that of the noise in . Terminal: at . In SD 1.x, . The lab reads from the scheduler, which counts 0 to 999: .
Simulator · the signal-to-noise ratio along t
Latent diffusion: where the 48 comes from
pixels
SD 1.5 latent
shape (height × width × channels)
512 × 512 × 3
64 × 64 × 4
numbers
512 · 512 · 3 = 786,432
64 · 64 · 4 = 16,384
786,432 / 16,384 = 48. Where does the factor 48 come from?
Each side shrinks 8×: one latent cell sums up 8 × 8 pixels × 3 colours = 192 numbers in 4. .
Paid at every sampling step. The VAE costs one encode or one decode.
The VAE round trip, measured
Lab, section 2: one 512 × 512 photo, encode, decode.
latent
1 × 4 × 64 × 64
std, raw latent
4.12
std × 0.18215
0.75
reconstruction PSNR
27.7 dB
linear latent → RGB, R²
0.92
The scaling factor 0.18215 brings the latent to unit variance over the training set, the scale of the noise. For this photo: 4.12 × 0.18215 = 0.75.
The decoder puts back the fine detail; the latent keeps layout and colour.
A linear map from 4 channels to RGB already gives a recognizable preview — the trick behind the step previews of the Diffusion Explainer.
Shape: (images, channels, height, width). PSNR = 10 log₁₀(peak² / mean squared error), in dB: higher is more faithful. R²: the share of the variance of the photo's colours, downscaled to 64 × 64, that the linear map explains.
Latent diffusion, end to end
Latent diffusion in three spaces. Pixels: the image x, the VAE encoder E and decoder D, and the generated image x̃. Latent, 64 × 64 × 4: in training, z0 from the encoder, noise added at a random t, z t into the U-Net and the loss; in generation, z T from noise, the U-Net and the scheduler repeated T times, then z0 to the decoder. Text: the prompt, the CLIP text encoder and the embeddings c, which reach the U-Net through cross-attention.PIXELS · 512 × 512 × 3LATENT · 64 × 64 × 4TEXT · 77 TOKENSzt, tεθ× TK, Vxa photoℰencoder(frozen)𝒟decoder(frozen)x̃the imagez0add noiserandom t, one jumpztloss‖ε − εθ‖²U-Net εθ(zt, t, c)cross-attention:Q from zt; K, V from czT𝒩(0, I)schedulerzt → zt−1z0“a cat in space”prompttext encoderCLIP · frozenc · 77 × 768
Three components, one of which does the work
component
role
parameters
calls per image
CLIP text encoder
prompt → (tokens × numbers)
123.1 M
2 (prompt and empty prompt)
U-Net
predicts in the latent
859.5 M
50 (25 steps × 2)
VAE decoder
latent → pixels
49.5 M
1
The text encoder and the VAE are frozen; only the U-Net was trained for diffusion.
Every speed-up you will see acts on the U-Net row: fewer steps, distillation, no guidance.
M = millions of parameters, counted in section 1 of the lab. The whole VAE has 83.7 M, but only the decoder (49.5 M) runs at generation. The U-Net is 81% of the total: 859.5 / (123.1 + 859.5 + 83.7).
The prompt, as the U-Net sees it
The tokenizer pads or truncates every prompt to 77 tokens: <start>, the words, <end>, then padding.
The text encoder returns one vector per token: (768 is the width of the CLIP ViT-L/14 text encoder).
"No prompt" is the encoding of the empty string — not zeros.
cosine between the whole matrices, flattened (lab, section 3)
"a lighthouse on a cliff at sunset" vs "…, watercolor painting"
0.78
"a lighthouse on a cliff at sunset" vs "a bowl of ramen on a wooden table"
0.24
"a lighthouse on a cliff at sunset" vs ""
0.20
The only door: cross-attention
from the latent features, at each resolution of the U-Net: 64², 32², 16² and 8² (the side halves per level).
from the 77 text vectors. : the dimension of each query and key.
The image asks, the text answers: the cross-attention of a Transformer decoder.
Two prompts that differ by one word, same seed. What is identical in the two runs, and what differs?
Identical: , the U-Net weights, the scheduler.
Different: only and .
The sampling loop, written out
latents = randn(1, 4, 64, 64, generator=seed) # z_T: the seed decides it
emb = cat([encode(""), encode(prompt)]) # ∅ and cfor t in scheduler.timesteps: # 25 steps
eps_u, eps_c = unet(cat([latents] * 2), t, emb).chunk(2)
eps = eps_u + w * (eps_c - eps_u) # guidance: one line
latents = scheduler.step(eps, t, latents).prev_sample
image = vae.decode(latents / 0.18215)
The lab writes this loop and checks it against the library pipeline: largest pixel difference 1 (out of 255).
50 U-Net calls (25 steps × 2); 1.41 s per step on an Apple M2 (a T4 is faster).
chunk(2): splits the batch of 2 into the two predictions (∅ and c) · prev_sample: the latent of the next step · / 0.18215: undoes the VAE's scaling factor.
Simulator · one image, in numbers
iteration
Classifier-free guidance
Train with the caption dropped 10% of the time: one network learns and .
What do and give? And the pipelines' default, ?
is the pipelines' guidance_scale. : the unconditional model, which ignores the prompt. : plain conditional sampling.
extrapolates past the conditional: no distribution the model learned. It works anyway.
Cost: two U-Net passes per step, for any other than 0 or 1.
: the U-Net's noise prediction with the prompt (the step left implicit) · : the empty prompt.
Simulator · guidance on a toy distribution
Guidance on the real model, measured
Lab, section 6: "a red fox sitting in a snowy forest, photograph", seeds 0, 1, 2, 20 DDIM steps.
CLIP score (prompt)
saturation
diversity across seeds
0
13.2
0.20
0.259
1
33.0
0.20
0.184
3
34.7
0.22
0.064
7.5
33.4
0.31
0.057
15
33.9
0.40
0.041
Adherence rises fast and then flattens; saturation keeps climbing.
Diversity across seeds falls at every step, 0.259 → 0.041: at large the three seeds draw the same centred fox.
CLIP score: 100 × cosine between CLIP ViT-B/32 embeddings of the image and the prompt. Diversity: mean pairwise cosine distance between the CLIP embeddings of the three seeds. Saturation: mean HSV saturation.
Seed and prompt: who decides what
Lab, section 6: "a lighthouse on a cliff at sunset" ± ", watercolor painting", DDIM, .
pair
correlation between the two images, grey and downscaled to 64 × 64 (the layout)
same prompt, seeds 0 and 1
0.15
same seed 0, with and without the modifier
0.85
The seed fixes , and with it most of the layout, decided in the first steps.
The modifier changes style and colour through cross-attention.
The two trajectories' already differ at step 1 (), yet the layouts end up correlated at 0.85.
Go and try: Diffusion Explainer
Map its three boxes onto the chapter: text encoder → refiner (U-Net + scheduler) → decoder.
Timestep slider (50 steps): when can you name the object? When does only detail change?
Guidance 0, 1, 7, 20: predict each from the formula first.
Seed: which boxes change, and which do not?
Compare two prompts that differ by one modifier, same seed and guidance: when do they diverge?
Images are precomputed: no prompt of your own — that is the lab. Lee et al., IEEE VIS 2024.
Training gives ; turning it into an image is a separate numerical problem.
Below ~10 steps no solver saves you: that regime needs distillation.
Ancestral: draws fresh noise at every step · ODE: a differential equation with no draws, so the seed fixes the image · higher order: uses several evaluations (or those of earlier steps) to take a more accurate step.
Distillation: from fifty steps to one
method
idea
steps
progressive distillation
a student takes two teacher steps at once; repeat
8192 → 4 (halved per round, 11 rounds)
LCM-LoRA
jump from any point of the trajectory to its end; ships as a LoRA
2–8
ADD (SDXL-Turbo)
add a discriminator: the GAN loss returns
1–4
Lab, section 7, same prompt and seed:
U-Net calls
CLIP score
time (M2)
DDIM, 1 step,
2 (1 × 2)
13.1
2.4 s
DDIM, 4 steps,
8 (4 × 2)
32.5
6.1 s
DDIM, 25 steps,
50 (25 × 2)
34.1
34.1 s
LCM-LoRA, 4 steps,
4 (4 × 1: no guidance)
33.9
4.5 s
Control: what people actually do with it
mechanism
what it does
how
img2img
start from an image
encode, noise to some , denoise; strength = which
inpainting
redo a masked region
keep the noised original outside the mask at every step
ControlNet
pose, depth, edges
trainable copy of the encoder half, joined by zero convolutions
IP-Adapter
a reference image
extra cross-attention reading CLIP image embeddings
LoRA
a style, a character
low-rank update of the attention layers (the transfer-learning class)
All of them freeze the base and add a small path beside it. ControlNet and LoRA start it at zero, so training starts exactly at the base model. That is why they compose.
What changed after SD 1.5
SD 1.5 (2022)
SDXL (2023)
SD3 / FLUX (2024)
backbone
U-Net, 860M
U-Net, 2.6B + refiner
MMDiT (multimodal diffusion Transformer)
text encoder
CLIP ViT-L
two CLIP
T5-XXL + CLIP
latent
4 channels
4 channels
16 channels
objective
, DDPM
rectified flow
native size
512²
1024²
1024²+, shifted schedule
Four deep changes (Transformer, T5, 16 channels, flow); none of them is "more parameters".
Rectified flow is the next class; the Transformer backbone, the one after.
Where the training data came from
SD 1.x was trained on subsets of LAION-5B: image–URL pairs scraped from Common Crawl, filtered by CLIP similarity. Nobody whose work is in it was asked.
Litigation over training data, still open.
A named living artist's style, reproduced on request.
Documented memorization of individual training images.
Illegal material found in an audit, which took the dataset offline.
Provenance is an engineering property of your system, not a footnote.
Lab · Stable Diffusion, opened up
Colab notebook, ~2 h, T4 GPU (free tier), Stable Diffusion 1.5 with diffusers. Each section ends in a checkpoint.
Diffusion = many easy denoising problems; the closed form makes training parallel.
The loss is an MSE against noise you drew: stable, and the reason diffusion beat GANs.
The schedule decides what is learned: is why SD 1.x cannot render black.
Latent diffusion: 48× fewer numbers per step; the VAE handles detail.
Guidance: one line, two U-Net passes, adherence bought with diversity.
The sampler is a separate choice; below ~10 steps, distillation (and the GAN returns).
ControlNet, IP-Adapter, LoRA: frozen base + small path. Since SD 1.5: Transformer, T5, 16 channels, flow.
Outline: (1) diffusion: forward, loss, reverse; (2) the noise schedule; (3) latent diffusion and the
three components; (4) classifier-free guidance; (5) Diffusion Explainer; (6) samplers, distillation,
control; (7) what changed after SD 1.5. Closes with the Colab lab.
Answer at the guidance slides: prompt adherence is disappointing; w = 7.5 is not "honest" sampling.
Step through the six stages: the two directions, one forward step, the jump in closed form (the next slide), the true reverse step, which is intractable, the U-Net's guess and sampling. Hover a frame to see its arrows. The reverse row ends at a different picture: sampling makes a new image.
The lab decodes x̂0 at every step; the Diffusion Explainer shows the same progression.
Training (left): a batch where each image has its own t, the jump, the loss; one pass per image, all in parallel. Sampling (right): start from noise, one step back, repeat T times in a row. Same U-Net, same weights.
Start on the scaled-linear schedule: red dot at t = 1. Tick "force zero terminal SNR". Then raise
the resolution to 1024: the curve moves up, the image is under-noised. Raise the shift to see SD3's
correction (the dashed line is the unshifted curve at the same resolution).
Click Training, then Generation. Training uses the encoder and never the decoder; generation uses the decoder and never the encoder. Both use the U-Net and the text encoder. The next slide counts the parameters.
Numbers printed by section 1 of the lab.
The loop of the previous slide on a latent of 4 numbers, with an exact toy U-Net (each caption's latents
are a Gaussian) and SD 1.5's real schedule. Default: "a red apple", seed 3, w = 7.5, 25 steps. Step with
Next: 5 real tokens + 5 padding; ‖c‖ = 1.18, ‖∅‖ = 1.11 (not zeros); t = 961 … 1, ᾱ_961 = 0.00728.
Iteration 1: ε_∅ and ε_c almost equal (π ≈ 0.25 each), ε̃₁ = 1.2481, x̂₀₁ = −5.36 (÷ 0.0853: errors × 11.7).
Trajectory: x̂₀ converges, 10.5 → 0.23 → 0.008. Then change one control at a time: w = 1 (an honest sample,
0.41 from μ_c), w = 0 (seed 3 draws a snowy field), w = 15 (0.98 past μ_c, 64 of 192 values clipped),
N = 1 (t = 1: the noise itself), N = 4 (starts at t = 751, ends 0.18 from the 25-step result).
Exact scores, so w = 1 is already 99% on-prompt here; a learned conditional is not. With 32 steps:
spread 1.03σ at w = 1, 0.76σ at w = 3, 0.63σ at w = 7.5; off-manifold 1% at w = 3, 17% at w = 10,
63% at w = 15.