Diffusion models are a new class of generative models that create realistic content through an iterative process. Unlike GANs or Autoregressive models, they don't require adversarial training and are more stable.
Core Idea: Noise is Reversible
Fundamental Concept: If you can overwrite an image with noise, you can also reverse noise back into an image.
Training Process (Forward Diffusion):
Real Image → (+Noise) → Image with 10% Noise → ... → 100% Noise (Gaussian)
Generation (Reverse Diffusion):
100% Noise → (-Predict Noise) → 90% Noise Image → ... → Real Image
The model learns to predict the noise added at each step.
Forward Diffusion Process (Training Phase)
In the forward pass, a real image is gradually "destroyed" with noise.
Mathematics (Simplified)
x_t = sqrt(α_t) * x_0 + sqrt(1 - α_t) * ε
where:
- x_0 = Original image
- x_t = Image after t diffusion steps
- α_t = Schedule value (controls noise amount)
- ε = Gaussian noise ~ N(0, 1)
Practical Example
t=0: Clear image
t=50: Image with light noise (still recognizable)
t=500: Image with heavy noise (barely recognizable)
t=999: Pure Gaussian noise (no information)
Noise Schedule
The schedule defines how much noise is added at each step.
# Linear Schedule (simple)
alphas = torch.linspace(0.999, 0.001, num_steps)
# Cosine Schedule (better, from Improved DDPM)
def cosine_schedule(t, T=1000):
return torch.cos((t / T + 0.008) / 1.008 * π / 2) ** 2
Reverse Diffusion Process (Generation)
The neural network learns to predict noise backward.
Training the Model
# Training loop (pseudocode)
for x_0 in real_images:
t = random_timestep(1, T)
ε = torch.randn_like(x_0) # Random noise
x_t = sqrt(α_t) * x_0 + sqrt(1 - α_t) * ε
# Network predicts noise
ε_predicted = unet(x_t, t)
# Loss: How close is prediction to actual noise?
loss = MSE(ε_predicted, ε)
loss.backward()
Sampling (Generation)
# Sampling
x_T = torch.randn(batch_size, 3, 256, 256) # Pure noise
for t in range(T-1, 0, -1): # Backward through timesteps
z = torch.randn_like(x_T) if t > 1 else 0 # Variance
# Model predicts noise
ε = unet(x_T, t)
# Remove noise
x_T = (x_T - sqrt(1 - α_t) * ε) / sqrt(α_t)
# Add controlled noise
x_T += sqrt(β_t) * z
return x_T # Final image
DDPM (Denoising Diffusion Probabilistic Models)
DDPM was the first practical implementation (Ho et al. 2020).
Characteristics
| Aspect | DDPM |
|---|---|
| Steps | 1000 (very many) |
| Sampling Time | 20-30 seconds per image |
| Quality | Very good |
| Efficiency | Not practical for real-time |
| Application | Academic reference |
Architecture
Input: Noise x_t + Timestep t
↓
Sinusoidal Timestep Embedding (time-aware)
↓
U-Net with ResNet Blocks
↓
Attention (Self-Attention on smaller features)
↓
Noise Prediction ε
DDIM (Denoising Diffusion Implicit Models)
DDIM accelerates generation without quality loss (Song et al. 2021).
Idea: Skip Fewer Steps
DDPM: 1000 → 999 → 998 → ... → 1 → 0 (1000 steps!)
DDIM: 1000 → 900 → 800 → ... → 100 → 0 (100 steps, 10x faster!)
Speed Comparison
DDPM 1000 steps: 30 seconds
DDIM 50 steps: 1-2 seconds (15-30x faster!)
Latent Diffusion Models (Stable Diffusion)
Latent Diffusion works in compressed latent space — much faster.
Architecture
Real Image (512x512)
↓
VAE Encoder (compress)
↓
Latent Space (64x64) — 8x compression
↓
Diffusion in Latent Space (fast!)
↓
VAE Decoder (decompress)
↓
Generated Image (512x512)
Why It's Faster
Pixel-Level Diffusion: 512x512 = 262.144 pixels per step
Latent Diffusion: 64x64 = 4.096 dimensions per step
Speedup: ~64x faster at same quality
Classifier-Free Guidance (CFG)
Guidance controls how strongly the model follows the text prompt.
Problem Without Guidance
The model must switch between "follow text" and "ignore text", causing quality loss.
Solution: CFG
# Sampling with Guidance
ε_uncond = unet(z_t, t, embedding=None) # Without condition
ε_cond = unet(z_t, t, embedding=text_emb) # With condition
# Apply Guidance
ε = ε_uncond + guidance_scale * (ε_cond - ε_uncond)
# guidance_scale:
# 1.0 = ignore text completely
# 7.5 = standard (good balance)
# 15.0 = very strong text following
Practical Implementation
Stable Diffusion with Diffusers
from diffusers import StableDiffusionPipeline
import torch
# Load model
pipe = StableDiffusionPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5",
torch_dtype=torch.float16
)
pipe = pipe.to("cuda")
# Text → Image
prompt = "A futuristic castle on a mountain peak"
image = pipe(
prompt,
height=512,
width=512,
num_inference_steps=50, # DDIM steps
guidance_scale=7.5, # CFG strength
generator=torch.Generator().manual_seed(42)
).images[0]
image.save("output.png")
