Stable Diffusion, from first principles

How a neural network learns to turn pure noise into images — and why doing it in a compressed latent space made it run on your GPU.

Who this is for. You're comfortable with algebra, basic probability, and a bit of calculus, and you want the actual mechanism — not a formula to memorize. We build diffusion models from scratch in one dimension, derive the training objective, then assemble the real Stable Diffusion v1.5 system piece by piece: the noise schedule, the U-Net, text cross-attention, the latent-space trick, DDIM sampling, and classifier-free guidance. Every quantity used in examples (schedules, probabilities, compression ratios) is computed exactly from the real model's configuration.

§1The problem: generating an image is sampling from a distribution

Why care. "Text-to-image" sounds like a UI feature, but underneath it is one of the hardest inference problems in statistics: draw a sample \(x_0\) from the distribution of natural images \(p(x)\), conditioned on a description \(c\). An image is a point in \(\mathbb{R}^{512\times512\times3}\) — 786,432 numbers — and the set of points that look like "a corgi surfing" is a vanishingly thin manifold inside that space. Classic generative families attack this differently: autoregressive models draw one pixel (or patch) at a time — flexible but slow; GANs draw everything in one shot from a latent code — fast but training-unstable (mode collapse, generator/discriminator arms race). Diffusion models take a third route: learn to destroy data with noise, then learn to undo it, one small step at a time.

Why "small steps at a time" helps: mapping pure noise to a sharp image in one shot requires the model to resolve global layout and fine texture simultaneously — an enormous jump. But the gap between "slightly noisy image" and "clean image" is a small, local, well-conditioned prediction. Chain a thousand of those together and the hard global decision gets made gradually, while uncertainty is still high, and details get filled in only at the end when the layout is already fixed.

"Stable Diffusion" specifically = the latent-space diffusion system released open-source in August 2022 by Stability AI, CompVis and Runway, built on the Latent Diffusion Model (LDM) architecture of Rombach et al. (CVPR 2022). The name comes from the company; the design's real claim to fame is that it moved the diffusion process out of pixels into a compressed latent space, cutting the compute by orders of magnitude. Here is the whole system on one map — everything after this section zooms into one box:

“a corgi
surfing a wave”
CLIP text encoder
frozen · 77 tokens × 768
U-Net \(\epsT\)
860M params · predict the noise
VAE
decoder \(\mathcal{D}\)
image \(512{\times}512\)
repeat 20–50 steps
\(z_T \sim \mathcal{N}(0, I)\)
\(4{\times}64{\times}64\) noise
\(z_T \to z_{T-1} \to \cdots \to z_0\)
Figure 1 — The whole system. Text becomes a conditioning vector; pure Gaussian noise becomes a latent image by iterating a denoising U-Net; a VAE decoder turns the latent into pixels. Only the U-Net is trained for generation; CLIP and the VAE are trained separately and frozen.
Takeaway — image generation is sampling from a high-dimensional distribution; diffusion models make it tractable by replacing one impossible jump with a chain of a thousand trivial ones.

§2 The forward process: destroying data with noise

Why start by destroying data? Because you can only learn to reverse a process you can simulate forward cheaply. The forward process is a fixed (untrained) Markov chain that gradually blends the image with Gaussian noise. At step \(t\) it does:

\[ q(x_t \mid x_{t-1}) \;=\; \mathcal{N}\!\big(x_t;\; \sqrt{1-\be{t}}\; x_{t-1},\; \be{t}\, I\big), \qquad t = 1,\dots,T. \]

Every symbol: \(x_0\) is the real image, thought of as a vector; each pixel channel is one coordinate. \(\be{t}\in(0,1)\) is a small noise variance schedule — how much variance of new noise to inject at step \(t\). The factor \(\sqrt{1-\be{t}}\) shrinks the old signal slightly so the overall pixel variance stays \(O(1)\) instead of exploding as noise accumulates. \(T=1000\) in Stable Diffusion. After \(T\) steps, \(x_T\) is statistically indistinguishable from pure \(\mathcal{N}(0, I)\) noise.

Why Gaussian noise, of all corruption? Three reasons. (1) Gaussians are closed under addition: summing many small Gaussian steps stays Gaussian, so the statistics of \(x_t\) at every \(t\) are exactly computable — no approximation. (2) A Gaussian's effect is fully described by two numbers (mean, variance), which makes the reverse process parameterizable. (3) The central limit: fine-grained pixel noise is the least structured corruption — it destroys only information, never adds structure the model could cheat with.

2.1 The shortcut: any noised version, in one step

Why we need it: during training we will want a randomly chosen \(x_t\) from a random image. Simulating 1..t steps each time would be wasteful — and unnecessary, because the chain has a closed-form marginal. Expand two steps by hand (each step: shrink by \(\sqrt{1-\be{}}\), add fresh noise; independent Gaussians add in variance):

\[ x_2 = \sqrt{1-\be{2}}\big(\sqrt{1-\be{1}}\, x_0 + \sqrt{\be{1}}\,\Eps_1\big) + \sqrt{\be{2}}\,\Eps_2 = \sqrt{\al{1}\al{2}}\, x_0 + \sqrt{1 - \al{1}\al{2}}\;\bar\Eps, \]

where \(\al{t} := 1-\be{t}\) and \(\bar\Eps \sim \mathcal{N}(0,I)\) is the sum of the two noises, re-standardized (variances \(\al{1}\be{2} + \be{2}\) add to \(1-\al{1}\al{2}\) because \(\Eps_1\) was first shrunk by \(\sqrt{\al{2}}\)). The pattern generalizes — define the cumulative product

\[ \abar{t} \;:=\; \prod_{s=1}^{t} \al{s} = \prod_{s=1}^t (1-\be{s}), \qquad\text{then}\qquad q(x_t \mid x_0) \;=\; \mathcal{N}\!\big(x_t;\; \sqrt{\abar{t}}\; x_0,\; (1-\abar{t})\,I\big). \]

So one Gaussian sampling act gives you any timestep directly:

\[ x_t \;=\; \underbrace{\sqrt{\abar{t}}}_{\text{signal weight}}\, x_0 \;+\; \underbrace{\sqrt{1-\abar{t}}}_{\text{noise weight}}\; \Eps, \qquad \Eps\sim\mathcal{N}(0, I)\ \text{fixed}. \]

This one line is the workhorse of everything that follows: training samples \(t\) uniformly and applies it; the interactive demo below applies it; and the denoising network's job, given \(x_t\), is precisely to estimate the hidden \(\Eps\).

2.2 Watch it happen — the real SD schedule on a real image

clean image
\(t=0\) · \(\abar{t}=1\)
t=250
\(t=250\) · \(\abar{t}=0.675\)
t=500
\(t=500\) · \(\abar{t}=0.278\)
t=750
\(t=750\) · \(\abar{t}=0.057\)
t=1000
\(t=1000\) · \(\abar{t}=0.0047\)
Figure 2 — Forward diffusion. A synthetic image noised with the exact Stable Diffusion v1.x schedule (\(\be{1}=0.00085 \to \be{T}=0.012\), scaled-linear, \(T=1000\)), using one fixed noise draw \(\Eps\) for all frames — only the weights \(\sqrt{\abar{t}}\) and \(\sqrt{1-\abar{t}}\) change. At \(t=1000\), \(\sqrt{\abar{T}}=0.068\): only 6.8% of the original signal survives.

Interactive: drag the timestep

\(t=0\)\(t=1000\)

The same \(\Eps\) is reused for every \(t\) — exactly the closed form above. Note how the same noise pattern fades in as the image fades out: at every \(t\), \(x_t\) is a two-vector mix of "signal" and "noise", and nothing else.

2.3 The schedule is a design choice

Why the schedule matters: \(\abar{t}\) is the fraction of the original signal's variance that survives at step \(t\). The schedule decides how fast that fraction dies — i.e. how many timesteps are spent in the "mostly signal", "half-and-half", and "mostly noise" regimes. Stable Diffusion v1.x uses a scaled-linear schedule: \(\be{t}\) is the square of a line from \(\sqrt{0.00085}\) to \(\sqrt{0.012}\). Squaring bends the curve so \(\abar{t}\) stays high early and collapses late — more training/sampling steps land in the high-noise regime where global structure is decided.

noise schedule plot
Figure 3 — Signal survival \(\abar{t}\). Blue: SD v1.x scaled-linear schedule (computed exactly from the released configuration). Orange: the cosine schedule of Improved DDPM, an alternative that keeps more signal late. The SD schedule halves the signal around \(t\approx355\) and leaves only 0.47% of it at \(t=1000\).

Exact values from the SD v1.x schedule (computed by direct multiplication of the 1000 steps):

\(t\)\(\be{t}\)\(\abar{t}\)\(\sqrt{\abar{t}}\) (signal)\(\sqrt{1-\abar{t}}\) (noise)
10.000850.999150.99960.0292
2500.002420.67540.82180.5697
5000.004800.27770.52690.8499
7500.008000.05660.23800.9713
10000.01200.004660.06830.9977

Later work (Nichol & Dhariwal's Improved DDPM) proposed the cosine schedule \(\abar{t} = f(t)/f(0)\) with \(f(t)=\cos^2\!\big(\tfrac{\pi}{2}\cdot\tfrac{t/T+s}{1+s}\big)\), \(s=0.008\), which dies more evenly and is common in latent models (SDXL's refiner, for example, uses cosine-like schedules).

Takeaway — the forward process is a fixed, parameter-free Markov chain; thanks to Gaussian closure, \(x_t = \sqrt{\abar{t}}\,x_0 + \sqrt{1-\abar{t}}\,\Eps\) gives any timestep in one draw. All "difficulty" is deferred to the reverse direction.

§3 The reverse process: turning "undo noise" into a regression problem

Why this is the heart of the method: generation is the reverse process — we must start from \(x_T\sim\mathcal{N}(0,I)\) and walk back to an image. Bayes' rule gives the one-step reverse transition:

\[ q(x_{t-1} \mid x_t) \;=\; \int q(x_{t-1} \mid x_t, x_0)\; q(x_0 \mid x_t)\; dx_0. \]

The first factor is analytic (Gaussian × Gaussian, via Bayes). The second factor, \(q(x_0\mid x_t)\) — "which real images could have produced this noise blob?" — is exactly the intractable distribution over the entire dataset that we were trying to avoid characterizing. So the reverse transition cannot be computed in closed form. The move: approximate it with a Gaussian \(p_\theta(x_{t-1}\mid x_t) = \mathcal{N}(x_{t-1};\, \mu_\theta(x_t,t),\, \sigma^2_t I)\) whose mean \(\mu_\theta\) is a neural network we learn.

Why a Gaussian approximation is sane here: for small \(\be{t}\), one reverse step changes the distribution only slightly, and by the central-limit logic the mixture over the unknown \(x_0\) is well-approximated by its mean and variance. The posterior variance \(\sigma^2_t\) doesn't even need learning: matching the forward statistics gives \(\tilde\beta_t = \frac{1-\abar{t-1}}{1-\abar{t}}\be{t}\) (the variance of \(q(x_{t-1}\mid x_t, x_0)\)), used as a constant. Only the mean carries real information.

3.1 The variational objective, collapsed

Why variational: "make \(p_\theta\)'s reverse chain end up at images" needs a scalar objective. The diffusion model is a latent-variable model with the forward process as a fixed encoder, so the natural objective is the variational lower bound (ELBO) on the data log-likelihood — the same machinery as a VAE, just with \(T\) stochastic layers. Writing it out and using the Markov structure, the bound telescopes into a sum of one-step terms (each KL divergence between two Gaussians whose means differ only through \(\mu_\theta\)):

\[ -\log p_\theta(x_0) \;\le\; \underbrace{D_{\mathrm{KL}}\!\big(q(x_T \mid x_0)\,\|\,\mathcal{N}(0,I)\big)}_{\approx 0 \text{ by schedule design}} \;+\; \sum_{t=2}^{T} D_{\mathrm{KL}}\!\big(q(x_{t-1}\mid x_t, x_0)\,\|\,p_\theta(x_{t-1}\mid x_t)\big) \;+\; \underbrace{-\log p_\theta(x_0 \mid x_1)}_{\text{last step}}. \]

Each KL is between two Gaussians with the same variance \(\tilde\beta_t\), so it is just a scaled squared distance of means: \(\frac{\tilde\beta_t}{2\sigma^2}\|\mu^\star_t - \mu_\theta(x_t,t)\|^2\), where \(\mu^\star_t\) is the true posterior mean. Training = sample \(x_0\), sample \(t\), run the forward shortcut to get \(x_t\), and regress the network's mean toward the analytic target. That's already trainable — but the target reparametrizes beautifully. The true posterior mean can be written (solve the two-Gaussian Bayes product) as

\[ \mu^\star_t(x_t, x_0) \;=\; \frac{\sqrt{\abar{t-1}}\;\be{t}}{1-\abar{t}}\, x_0 \;+\; \frac{\sqrt{\al{t}}\,(1-\abar{t-1})}{1-\abar{t}}\, x_t, \]

and since \(x_t = \sqrt{\abar{t}}\,x_0 + \sqrt{1-\abar{t}}\,\Eps\), we can substitute \(x_0 = \frac{x_t - \sqrt{1-\abar{t}}\,\Eps}{\sqrt{\abar{t}}}\). The \(x_t\)-terms then cancel algebraically and the target mean becomes a function of the noise:

\[ \mu^\star_t(x_t, \Eps) \;=\; \frac{1}{\sqrt{\al{t}}}\Big( x_t \;-\; \frac{\be{t}}{\sqrt{1-\abar{t}}}\, \Eps \Big). \]

So instead of predicting a mean pixel-by-pixel, the network can predict the noise vector \(\Eps\) that was mixed in; the mean follows mechanically. Ho et al. (DDPM, 2020) found that training this parametrization with the KL weights dropped to 1 works better in practice, leaving the beautifully simple objective that Stable Diffusion actually trains with:

\[ L_{\text{simple}} \;=\; \mathbb{E}_{x_0,\; \Eps \sim \mathcal{N}(0,I),\; t \sim \mathcal{U}\{1..T\}} \Big[\;\big\| \Eps \;-\; \epsT\big(\,\underbrace{\textstyle\sqrt{\abar{t}}\,x_0 + \sqrt{1-\abar{t}}\,\Eps}_{x_t},\; t,\; c\,\big)\big\|_2^2 \Big]. \]

Read it as an algorithm, one training step: (1) grab an image–caption pair \((x_0, c)\); (2) roll a timestep \(t\) and a noise cube \(\Eps\); (3) mix them with the closed form; (4) ask the network "what was \(\Eps\)?" — also feeding it \(t\) and the text \(c\); (5) MSE gradient step. Nothing else: no adversary, no value function, no curriculum beyond uniform \(t\).

Why predict \(\Eps\) rather than \(x_0\) (or \(x_{t-1}\))? Three reasons. (1) Scale-stability: at \(t=T\), \(x_0\) is nearly erased (\(\sqrt{\abar{T}} = 0.068\)) so its values are barely visible in \(x_t\), while \(\Eps\) is by construction \(O(1)\) everywhere — a regression target of constant scale across all \(t\). (2) It is exactly equivalent to mean-prediction under the parametrization above — same function class, nicer conditioning. (3) The dropped KL weights amount to a specific reweighting of timesteps that emphasizes the harder, information-rich mid/high-noise regimes.
\(x_0, c\)
dataset pair
mix: \(x_t = \sqrt{\abar{t}} x_0 + \sqrt{1-\abar{t}}\,\Eps\)
U-Net \(\epsT(x_t, t, c)\)
predict the noise
\(\|\Eps - \hat\Eps\|^2\)
loss
gradient step · repeat over the dataset
Figure 4 — One training step. The forward shortcut manufactures the noisy input for free; the network's only job is to name the noise that was mixed in.
Takeaway — the variational bound collapses to: add known noise, ask the network to name it, take an MSE step. Generative modeling disguised as supervised regression.

§4 What the network actually learns: the score, i.e. where probability increases

Why a second viewpoint helps: "predict the noise" is operational but opaque. There is a deeper object hiding inside \(\epsT\): the score \(s(x,t) := \nabla_{x}\log q_t(x)\) — the gradient of the log-density of the noised marginal. It points from any \(x_t\) toward regions of higher probability under \(q_t\). The identity tying noise prediction to the score needs one computation: differentiate \(\log q_t\) using the mixture form \(q_t(x) = \int \mathcal{N}(x;\sqrt{\abar{t}}x_0, (1-\abar{t})I)\, p_{\text{data}}(x_0)\, dx_0\). Differentiating under the integral and recognizing the posterior expectation:

\[ \nabla_{x}\log q_t(x) \;=\; \mathbb{E}\!\left[\left. \frac{\sqrt{\abar{t}}\, x_0 - x}{1-\abar{t}} \;\right|\; x_t = x \right] \quad\Longrightarrow\quad \mathbb{E}[x_0 \mid x_t = x] \;=\; \frac{x + (1-\abar{t})\, s(x,t)}{\sqrt{\abar{t}}}. \]

The left side of the implication is Tweedie's formula for a Gaussian channel: the posterior-mean clean image equals the noisy input plus a variance-scaled push up the log-probability gradient. Comparing with the definition \(x_t = \sqrt{\abar{t}} x_0 + \sqrt{1-\abar{t}}\,\Eps\), i.e. \(\hat\Eps = \frac{x_t - \sqrt{\abar{t}}\,\hat x_0}{\sqrt{1-\abar{t}}}\), we get the punchline:

\[ \epsT(x_t, t) \;=\; -\,\sqrt{1-\abar{t}}\;\; s_\theta(x_t, t). \]

Predicting the noise and estimating the score are the same job, up to a known scale factor. This unlocks the geometric picture: denoising = taking a small step along \(s_\theta\), the empirical "which-way-is-more-image-like" compass. At \(t\) large, \(q_t\) is nearly \(\mathcal{N}(0,I)\): the score is weak and smooth, pointing at coarse data structure — any image-ish blob. At \(t\) small, the score has sharp, local basins around actual images: it only fixes fine detail. The chain of steps is therefore a curriculum: global layout first, texture last. The figure below computes the exact score of a 2-D mixture of three Gaussians at three noise levels — the toy is analytic, but the shape of the field is exactly what a trained \(\epsT\) implements for images.

score fields at three noise levels
Figure 5 — The learned vector field, made exact. Arrows show \(\nabla_{x}\log q_\sigma(x)\) for a 3-mode Gaussian mixture convolved with noise of width \(\sigma = 0.15, 0.6, 1.5\) (left→right); gray contours are the corresponding densities; arrow scale is shared across panels. At low noise the field splits into three strong attractors (fine detail); at high noise it is one weak pull toward the data mass as a whole (global layout). A diffusion sampler walks downhill in this field while the field itself anneals.

This also explains the sampler we will derive in §8: the DDPM update is (up to constants) score ascent + fresh noise — annealed Langevin dynamics. And it clarifies what the network must not do: it never produces an image directly; it only produces a direction field, and the iteration does the rest.

Takeaway — \(\epsT\) is a noise-conditioned score estimator: at every noise level it knows which infinitesimal edit makes \(x_t\) more probable — coarse geometry at high noise, fine texture at low noise.

§5 The denoiser is a U-Net — same size in, same size out, at every scale

Why a U-Net and not a plain CNN or transformer: the network's contract from §3 is odd by deep-learning standards: it takes a full image-shaped tensor \(x_t\) and must return a full image-shaped noise estimate \(\hat\Eps\), while being able to reason about structure at every scale — from global composition (is this a beach?) to pixel texture (is this foam?). Two architectural facts deliver that. (1) Convolutions make the output shape free: a conv stack with same-padding maps \(H{\times}W\) to \(H{\times}W\) — the input/output contract costs nothing. (2) The encoder–decoder "U" with skip connections gives multi-scale reasoning with clean detail: the down-sampling path compresses spatially into a semantic summary (small spatial size, many channels), the up-sampling path re-expands it, and the horizontal "skip" arrows paste high-resolution features from the encoder directly across — so fine detail never has to survive the bottleneck. Diffusion U-Nets add two ingredients:

\(z_t\) in
\(\hat\Eps\) out
ResBlock
64² · 320ch
ResBlock
32² · 640ch
bottleneck
8² · 1280ch · attn
ResBlock
32² · 640ch
ResBlock
64² · 320ch
skip: copy fine detail across
skip
time embedding \((\gamma_t, \beta_t\))
scale-shifts every ResBlock
\(\gamma_t\odot h + \beta_t\)
\(\gamma_t\odot h + \beta_t\)
Figure 6 — U-Net skeleton of SD v1.5. Solid arrows: down/up-sampling path (schematic; the real net stacks two ResBlocks per level). Dashed green: skip connections carrying encoder features to the decoder. Orange: the timestep embedding enters every block as scale-and-shift. Text enters through the attention blocks (§6). Total: ≈860M parameters.
Why residuals everywhere: the network's output \(\hat\Eps\) must often be almost equal to its input (at low \(t\), the noise is a small correction). Residual blocks whose default behavior is the identity make "do almost nothing" the easy solution — the same reason ResNets won depth races. A useful mental model: the U-Net computes a correction field, not a picture.
Takeaway — the U-Net is shaped exactly like its job: image-shaped in and out, multi-scale by construction, conditioned on \(t\) via scale/shift so one set of weights serves all 1000 noise levels.

§6 Text enters through cross-attention: every image patch reads the prompt

Why not just concatenate the prompt: generation must bind which words affect which image regions — "red hat" touches only the hat; "surfing" sets the pose everywhere. A single global vector cannot express that routing. The mechanism that can is cross-attention, already the standard binding operator in transformers. The prompt is first encoded by a frozen CLIP ViT-L/14 text transformer into a sequence of 77 token embeddings, each 768-dimensional: \(c \in \mathbb{R}^{77 \times 768}\) (prompts are padded/truncated to exactly 77 tokens — the reason prompts "run out" around ~75 words). Inside each attention block of the U-Net:

\[ \text{Attention}(Q, K, V) \;=\; \text{softmax}\!\Big( \frac{Q K^{\!\top}}{\sqrt{d_k}} \Big) V, \qquad Q = h\,W_Q,\;\; K = c\,W_K,\;\; V = c\,W_V. \]

with \(h\) = the U-Net's spatial features (flattened to \(hw\) rows) and \(c\) = the text tokens. The softmax rows are per-image-position probability distributions over prompt tokens: each patch literally asks "which words are relevant to me right now?" and receives the corresponding \(V\)-values mixed accordingly. Through thousands of such layers during training, token↔region bindings ("corgi" ↔ furry blob here) become consistent across the dataset. Note the asymmetry — this is why attention beats concatenation: the same text embedding is re-queried at every position and every layer, so one token can influence many regions and one region can consult many tokens.

spatial feature
\(h\,(=Q)\)
one highlighted patch
softmax over tokens
tokens of “a corgi surfing”
<sos> a corgi surfing …
\(K, V \in \mathbb{R}^{77\times768}\)
0.10
a·0.02
corgi·0.55
surfing·0.30
…·0.10
Figure 7 — Cross-attention routing (one query patch). Line width = attention weight from one spatial position to each prompt token. That patch mostly reads "corgi", borrows its \(V\)-vector, and updates itself accordingly — at every U-Net level, for every patch, in every attention head.
Why CLIP specifically as the text encoder: CLIP was trained so that images and their captions land near each other in a shared embedding space. Diffusion training then starts from text embeddings that are already image-aligned — geometry like "surfing" vs "sleeping" is separated before the U-Net is ever switched on. CLIP is frozen during diffusion training: its job is to be a stable, meaningful dictionary of meanings; the U-Net learns only how to realize them.
Takeaway — prompting is attention routing: the U-Net consults the prompt through cross-attention at every scale, so words steer regions and the binding is learned, not hard-coded.

§7 The "Stable" in Stable Diffusion: run the whole thing in a compressed latent space

Why pixels are the enemy: take the loss of §3 and the architecture of §5 at their word and train on \(512{\times}512\) RGB images. The self-attention blocks alone are fatal: attention cost scales quadratically with the number of tokens, and a \(512{\times}512\) image has \(262{,}144\) positions, versus \(4{,}096\) for a \(64{\times}64\) grid — \(4096\times\) more attention entries (6.9×10¹⁰ vs 1.7×10⁷), and hundreds of forward/backward passes per training step besides. Rombach et al.'s insight (LDM): most of those pixels are perceptually irrelevant detail. So first train a small autoencoder \((\mathcal{E}, \mathcal{D})\) to compress images into a latent \(z = \mathcal{E}(x)\), then run the entire diffusion machinery of §2–6 on \(z\) instead of \(x\). Decode at the very end with \(\mathcal{D}\).

pixels \(x\): \(3 \times 512 \times 512\)
= 786,432 values · 262,144 attention tokens
encoder \(\mathcal{E}\)
8× down
latent \(z\): \(4 \times 64 \times 64\)
= 16,384 values · 4,096 tokens
48× fewer numbers, 4096× cheaper attention
perceptual loss + tiny KL regularizer → \(z\) stays image-like
Figure 8 — The compression that made it feasible. The VAE squeezes \(512^2\) RGB into a \(64^2{\times}4\) latent (48× fewer values) by discarding imperceptible high-frequency detail. The diffusion model of §2–6 then operates entirely here; \(\mathcal{D}\) reconstructs pixels at the end.

What the VAE must guarantee. Two failure modes to design away: (1) an autoencoder free to minimize reconstruction error could cram information into microscopic pixel differences that the diffusion model then can't model smoothly; (2) an over-regularized latent would be blurry. LDM's recipe: reconstruction + a perceptual loss (deep-feature similarity, so "looks the same" beats "is bit-identical") plus a very small KL penalty that keeps \(z\)'s statistics tame (variance ≈ 1, roughly Gaussian) without forcing a bottleneck. SD's VAE downsamples by \(f=8\) with 4 latent channels. A constant \(\approx 0.18215\) rescales \(\mathcal{E}\)'s output to unit variance — the magic number in every SD code snippet.

What the diffusion model loses by design. Anything the VAE cannot reconstruct, SD can never generate — look closely at VAE decodings of fingers, fine text, or intricate jewelry: that ceiling is the autoencoder's, not the U-Net's. Everything else works verbatim: the closed form becomes \(z_t = \sqrt{\abar{t}}\,z_0 + \sqrt{1-\abar{t}}\,\Eps\), the loss is \(\|\Eps - \epsT(z_t, t, c)\|^2\), and all of §2–6 carries over with \(x \to z\). The trained system: U-Net 860M params, frozen CLIP ViT-L/14, frozen VAE — trained on the LAION-2B-en image–text subset (and fine-tuned on the aesthetics-filtered LAION-aesthetics v2 5+ subset for v1.5) at \(512{\times}512\).

Takeaway — "Stable" Diffusion = diffusion in a learned, perceptual latent space: the math is unchanged, the tensors are 48× smaller, attention is 4096× cheaper — the difference between a supercomputer demo and a consumer-GPU product.

§8 Sampling: DDPM's 1000 steps, DDIM's 25, and the ODE hiding underneath

Why sampling needs its own section: training gave us \(\epsT\) — a one-step denoiser. Generation must call it repeatedly, and how we chain the calls determines speed, diversity, and even determinism. The ancestral DDPM sampler follows the true reverse chain one step at a time. Using \(\hat x_0(z_t) = \frac{z_t + (1-\abar{t})\, s_\theta(z_t,t)}{\sqrt{\abar{t}}}\) (Tweedie, §4) it is easiest to read as: estimate the clean image, take a noise-blurred partial step toward it:

z ~ N(0, I)                                  # start from pure noise,  t = T
for t = T, T-1, ..., 1:                      # 1000 iterations, 1000 U-Net calls
    ŝ   = score_θ(z_t, t)                    # (or ε̂ from the U-Net: ŝ = -ε̂/√(1-ᾱ_t))
    x̂₀  = (z_t + (1-ᾱ_t)·ŝ) / √ᾱ_t          # best current guess of the clean image
    μ   = (√ᾱ_{t-1}·β_t·x̂₀ + √α_t·(1-ᾱ_{t-1})·z_t) / (1-ᾱ_t)   # posterior mean
    z_{t-1} = μ + √β̃_t · η,  η ~ N(0, I)     # β̃_t = (1-ᾱ_{t-1})/(1-ᾱ_t)·β_t
return z₀                                    # last step has zero added noise

Each step re-injects fresh noise \(\eta\) — that stochasticity is what lets one model produce different images per seed, and it is exactly the annealed-Langevin dynamics predicted by the score view of §4. The figure below runs this exactly: the data is a two-mode 1-D distribution (gray, bottom), the score is computed analytically at every step, and 16 sampled trajectories are drawn from \(t=1000\) (noise, top) to \(t=0\) (data, bottom):

\(t{=}1000\) noise
\(t{=}0\) data
−2
−1
0
1
2
250
500
750
DDPM: stochastic, 1000 U-Net calls
DDIM: deterministic, 100 U-Net calls
Figure 9 — Two samplers on the true score of a bimodal distribution. Blue: exact reverse-diffusion chains — the random walk re-injects noise at every step and the basin decision is made gradually. Orange: DDIM jumps on the same score field with 10× fewer calls — smooth, deterministic. Two honest quirks of determinism are visible: starts near the basin boundary \(x\!\approx\!0\) stay mid-valley (DDIM's famous diversity loss), and far-tail starts move sluggishly where the field is weak.

8.1 DDIM: big deterministic steps on the same field

Why 1000 calls is absurd: 25 images/minute on a good GPU. Song et al. (DDIM, 2021) asked which family of reverse processes has the same marginals \(q(x_t)\) but doesn't need step-by-step noise. Answer: a family indexed by a variance parameter \(\sigma^2\); DDPM sits at one end (\(\sigma^2 = \tilde\beta_t\)), and the deterministic end \(\sigma^2 = 0\) gives the DDIM update — which, beautifully, is just "project the current Tweedie estimate \(\hat x_0\) onto the next noise level":

\[ z_{t-1} \;=\; \sqrt{\abar{t-1}}\;\hat x_0(z_t) \;+\; \sqrt{1-\abar{t-1}}\;\hat\Eps(z_t), \qquad \hat\Eps = -\sqrt{1-\abar{t}}\; s_\theta. \]

No new noise is added: the trajectory is a function of the seed alone. And nothing forces \(t{-}1 = t{-}\)1: you may jump \(1000 \to 960 \to 900 \to \cdots\) — the model was never told the steps are adjacent. In practice SD ships with 20–50 DDIM steps and the quality loss is small. The deeper reason this works: the deterministic process traces the probability-flow ODE — the noiseless vector field whose flow maps \(\mathcal{N}(0,I)\) onto the data distribution with the same marginals as the SDE (§4's score is its right-hand side up to constants). DDIM is a coarse numerical integrator of that ODE; stochastic DDPM integrates the SDE. Same learned field, two integrators — and the ODE view is what makes lossless img2img possible: encode a real image, run the ODE forward to intermediate \(t_0 < T\), then decode with a new prompt — a deterministic round trip through "noise" that keeps the composition.

Why the toy figure shows DDIM landing mid-valley: the probability-flow ODE sends mass to quantiles. For bimodal data, the median of the noise maps to the median of the data — which lies in the empty gap between modes. The stochastic chain never has this problem (noise kicks samples off the boundary), and this is a real, observed SD behavior: at low guidance and few steps, deterministic sampling reduces diversity. Practical cures: more steps, some stochasticity, or the guidance of §9.
Takeaway — sampling = integrating a learned field. DDPM: 1000 small stochastic SDE steps. DDIM: fewer large deterministic ODE steps with the same marginals — the seed, not the path, decides the picture.

§9 Classifier-free guidance: extrapolating toward the prompt

Why guidance at all: a sampler that draws from \(p(x \mid c)\) faithfully draws typical images for the prompt — including the bland majority of the dataset. Users want on-prompt, striking images: a knob that trades diversity for prompt-adherence. The original trick (classifier guidance, Dhariwal & Nichol 2021) trained a separate image classifier on noised images and pushed samples along \(\nabla_x \log p(c \mid x_t)\) — extra model, extra gradients. Ho & Salimans (2022) noticed the classifier is unnecessary: train one conditional model that occasionally ignores its condition, and the classifier's score can be assembled from the difference of two predictions it already makes. Concretely: during SD training, the text embedding \(c\) is replaced by a learned empty-prompt embedding \(\varnothing\) with probability 10%. Then \(\epsT(x_t,t,c)\) and \(\epsT(x_t,t,\varnothing)\) estimate the conditional and unconditional scores, and since (Bayes) \(\nabla \log p(x\mid c) = \nabla\log p(x) + \nabla \log p(c \mid x)\), the difference of the two scores is the classifier's gradient for free. At sample time, overweight it:

\[ \tilde\Eps \;=\; \epsT(x_t, t, \varnothing) \;+\; \gamma\,\big(\epsT(x_t, t, c) - \epsT(x_t, t, \varnothing)\big), \qquad \gamma = 1 \text{ is plain sampling; SD default } \gamma = 7.5. \]

This is a linear extrapolation: at \(\gamma=1\) you recover the conditional score; \(\gamma > 1\) pushes beyond it along the direction "what the prompt adds over no prompt". The two U-Net evaluations are batched, so guidance costs ~2× compute per step — the single most important quality knob in the entire system.

\(x_t\): where the sample sits now
\(\epsT(\varnothing)\): "no prompt" score
\(\epsT(c)\): "with prompt" (\(\gamma{=}1\))
\(\gamma = 3\): extrapolate along \(\Delta\)
\(\Delta = \epsT(c) - \epsT(\varnothing)\)
\(\gamma = 7.5\) → off-canvas
Figure 10 — The geometry of guidance. At each sampling step the model computes two directions; guidance extrapolates their difference. Larger \(\gamma\) = further beyond the conditional score: sharper prompt-adherence, less diversity, and eventually oversaturated, "deep-fried" images.
Why \(\gamma > 1\) sharpens instead of breaking things: formally, \(\tilde\Eps\) is the score of the distribution \(p(x\mid c) \propto p(x \mid c)^{\gamma} / p(x)^{\gamma - 1}\) — sharpening the conditional and flattening the unconditional at once. The extrapolated direction is exactly the component of the score that agrees with the prompt, so amplifying it suppresses everything the prompt doesn't explain. The failure mode at large \(\gamma\) is the same mechanism: low-probability subtlety is suppressed too — colors saturate, compositions simplify, diversity collapses. (Negative prompting, the SD community's favorite trick, is this same equation with \(\varnothing\) replaced by a description of what you don't want — the difference vector then actively pushes away from it.)
Takeaway — CFG = free classifier guidance from one network: difference of two scores it already computes, extrapolated by \(\gamma\). It converts "a faithful sample" into "an insistent illustration of the prompt".

§10 The family tree: where SD sits, and what came after

Why zoom out: every piece you now know is one node in a larger, coherent framework — and the lineage explains the models you'll meet next.

yearmodelwhat it added
2015Sohl-Dickstein et al.diffusion as generative modeling: learn to reverse gradual noising
2020DDPM (Ho et al.)\(\Eps\)-prediction, the simple MSE loss — made diffusion work (§3)
2021Improved DDPM / score SDE (Song et al.)cosine schedules, learned variances; SDE/ODE unification (§4, §8)
2021DDIMdeterministic few-step sampling via the probability-flow ODE (§8)
2022classifier-free guidanceprompt-adherence knob from one network (§9)
2022LDM / Stable Diffusiondiffusion in a VAE latent — consumer-GPU scale (§7)
2023SDXLbigger U-Net, two text encoders, 1024², aspect-ratio buckets, refiner stage
2023LCM / Turbo distillationsstudent models sampling in 1–4 steps (consistency/distillation of the ODE)
2024SD3 / Flux (rectified flow)MM-DiT architecture trained with flow matching: learn velocity \(v = x_{\text{data}} - \Eps\) on straight paths (§ below)

10.1 The bigger framework — three equivalent descriptions

Why the same object has three names: it clarifies what is essential. (1) Hierarchical VAE view: diffusion is a VAE with \(T\) fixed Gaussian "encoder" layers (the forward process) and one learned decoder chain — §3's ELBO is literally the VAE bound. (2) Score-based SDE view: as \(T \to \infty\), forward diffusion becomes a stochastic differential equation \(dx = -\tfrac{1}{2}\beta(t)\,x\,dt + \sqrt{\beta(t)}\,dW\); sampling its time-reversal is generation, and the network estimates the reversal's drift (the score) — §4. (3) ODE / flow view: the same marginals are produced by a deterministic ODE (the probability-flow equation), which DDIM integrates and img2img exploits. Modern rectified flow (SD3, Flux) trains the ODE view directly: regress the constant velocity \(v = x_{\text{data}} - \Eps\) along straight interpolation lines from noise to data — straighter paths integrate in fewer steps, which is the same reason DDIM beats DDIM-free samplers on curved VP trajectories.

Takeaway — VAE bound, score matching, and flow ODE are three views of one machinery: a learned vector field from noise to data. SD1.5's specific choices (VP schedule in a VAE latent, U-Net + cross-attention, CFG) are one point in that design space — the part that matters is the field, not the plumbing.

Appendix — Stable Diffusion v1.5, the concrete configuration

componentspecification
text encoderCLIP ViT-L/14 (frozen) · 77 tokens · 768-dim embeddings
denoiserU-Net, ≈860M params · ResBlocks + self/cross-attention at 64², 32², 16², 8²
autoencoderKL + perceptual VAE (frozen) · 8× downsample · latent \(4{\times}\frac{H}{8}{\times}\frac{W}{8}\) · scale 0.18215
schedule\(T=1000\) · scaled-linear \(\be{t}\): \(0.00085 \to 0.012\) · \(\abar{1000} = 0.0047\)
objective\(\|\Eps - \epsT(z_t, t, c)\|_2^2\) · \(t \sim \mathcal{U}\{1..1000\}\) · text dropped 10% (CFG)
dataLAION-2B-en (+ LAION-aesthetics v2 5+ for v1.5) · 512×512 crops
samplingDDIM/PLMS, 20–50 steps · guidance \(\gamma = 7.5\) · seed → latent \(z_T\)

Further reading (the primary sources, in reading order)

  1. Ho, Jain, Abbeel — Denoising Diffusion Probabilistic Models (2020): §3's derivation, in full.
  2. Song et al. — Score-Based Generative Modeling through SDEs (2021): the unifying SDE/ODE view of §4/§8.
  3. Song, Meng, Ermon — DDIM (2021): non-Markovian forward processes and fast sampling.
  4. Ho & Salimans — Classifier-Free Diffusion Guidance (2022): §9.
  5. Rombach et al. — High-Resolution Image Synthesis with Latent Diffusion Models (CVPR 2022): §7 — the SD architecture.
  6. Esser et al. — Scaling Rectified Flow Transformers (SD3) (2024): where the lineage went next.
  7. Sander Dieleman — "The diffusion sampler" (2024): an excellent second pass on everything above.

All numeric claims computed from the released v1.5 configuration: \(\abar{t}\) by direct multiplication of the 1000 \(\be{t}\) values; compression and attention ratios from \(512{\times}512 \to 64{\times}64{\times}4\); Figure 2 and the interactive demo use the exact schedule; Figure 5 uses the analytic score of a Gaussian mixture; Figure 9 simulates the true reverse chain and the DDIM map with that score. Sources for model facts: SD v1.5 model card, LDM paper, CFG paper.