Latent Diffusion Models
Diffusion is moved out of pixel space into a compressed latent space learned by an autoencoder, cutting compute costs by orders of magnitude while preserving fine detail.
Why Does This Exist?
Early diffusion models like DDPM and score-based models operated directly in pixel space. Generating a photorealistic RGB image requires evaluating a massive neural network over distinct numerical coordinates at every single timestep.
Digital images exhibit massive spatial redundancy: neighboring pixels in a blue sky or wooden table share nearly identical values. In pixel-space diffusion, the denoising U-Net spends the vast majority of its floating-point operations (FLOPs) learning high-frequency imperceptible details—such as microscopic noise textures in smooth background surfaces—rather than learning the high-level semantic composition and global layout of the scene.
To train high-resolution models, earlier practitioners were forced to build cumbersome cascades of separate models: a base model generating blurry images, followed by two separate super-resolution diffusion models upscaling to and . This cascaded architecture required hundreds of GPU-years to train and made interactive consumer inference impossible.
Latent Diffusion Models (LDM) (Rombach et al., 2022)—the foundational breakthrough behind Stable Diffusion—solve this by explicitly decoupling perceptual compression from semantic generation. An autoencoder compresses high-dimensional images into a perceptually equivalent, low-dimensional latent space ( spatial compression). The diffusion model then operates entirely inside this compact latent representation, reducing memory and compute requirements by nearly while enabling flexible conditioning on text, depth maps, and segmentation masks. Prerequisite knowledge connects directly to diffusion-score-based-models.
Think of It Like This
An architect drafting blueprints instead of hauling concrete bricks
Imagine an architect designing a cathedral.
Pixel-space diffusion is like hiring a team of bricklayers to test structural layouts by physically stacking 800,000 full-sized granite bricks on an open construction lot. If the roof looks slightly unbalanced, the crew must dismantle and restack all 800,000 heavy bricks, spending 99% of their physical energy moving stone and only 1% contemplating aesthetic proportion.
Latent diffusion is how modern architecture actually works. An expert draughtsman (the VAE encoder) converts the messy physical site into a clean, compact scale blueprint on a drafting board. The architect (the latent diffusion U-Net) sits at the desk, easily sketching, erasing, and refining the structural arches on the lightweight blueprint without lifting a single stone.
Beside the desk sits a client (the text prompt) providing instructions like "add stained-glass rose windows" (cross-attention). Once the blueprint is perfectly rendered and balanced, a master builder (the VAE decoder) reads the finished blueprint and constructs the real-world stone cathedral in a single pass.
How It Actually Works
Two-Stage Separation and Cross-Attention Conditioning
Latent Diffusion Models divide image synthesis into two distinct stages:
Stage 1: Perceptual Compression (Autoencoder)
A regularized autoencoder trained with perceptual and patch-based adversarial losses consists of an encoder and a decoder . Given an image , the encoder compresses it into a latent code , where the downsampling factor is . In Stable Diffusion, , compressing an input of shape into a latent tensor of shape .
To prevent arbitrarily high-variance latent spaces, the autoencoder is regularized either via a mild Kullback-Leibler penalty toward a standard normal distribution (KL-reg) or via vector quantization (VQ-reg). The decoder reconstructs the image from the latent: .
Stage 2: Latent Denoising and Conditioning
The diffusion model operates entirely in latent space . The forward process adds Gaussian noise to the clean latent :
A time-conditional U-Net is trained to predict the added noise vector:
where is the conditioning input (such as a text prompt), and is a domain-specific encoder (such as a frozen CLIP ViT-L/14 or T5 text encoder).
Cross-Attention Fusion
The conditioning representation is injected into the intermediate layers of the U-Net via cross-attention. For an intermediate feature map :
Classifier-Free Guidance (CFG)
To steer generation strongly toward the text prompt, the model is trained with conditional dropout: 10% of training steps drop the prompt , replacing it with an empty null token . During inference, the model evaluates both the unconditioned and conditioned noise predictions:
where (typically ) is the guidance scale. Values of extrapolate away from generic images toward compositions that strictly adhere to the prompt.
Worked Example
Let us examine the data reduction and a concrete Classifier-Free Guidance step:
-
Dimensional Compression Ratio:
- Pixel image : floats.
- Latent representation : floats.
- Memory and representation reduction: For self-attention layers with quadratic spatial complexity , compressing spatial dimensions from tokens to tokens achieves a theoretical reduction in attention matrix memory!
-
Classifier-Free Guidance Extrapolation: Suppose at latent pixel coordinate in channel 0:
- Unconditioned noise prediction (empty prompt):
- Conditioned noise prediction ("a red lighthouse"):
- Guidance scale:
Calculate extrapolated noise update: The guidance formula amplifies the model's text-aligned direction eight-fold relative to the unconditioned drift.
Code
import torchimport torch.nn as nnimport torch.nn.functional as F
class CrossAttentionBlock(nn.Module): """Computes cross-attention between spatial latent features and text embeddings.""" def __init__(self, query_dim: int = 320, context_dim: int = 768, num_heads: int = 8): super().__init__() self.num_heads = num_heads self.head_dim = query_dim // num_heads self.to_q = nn.Linear(query_dim, query_dim, bias=False) self.to_k = nn.Linear(context_dim, query_dim, bias=False) self.to_v = nn.Linear(context_dim, query_dim, bias=False) self.to_out = nn.Linear(query_dim, query_dim)
def forward(self, x: torch.Tensor, context: torch.Tensor) -> torch.Tensor: # x: (batch, seq_len_latent, query_dim) e.g., (2, 4096, 320) # context: (batch, seq_len_text, context_dim) e.g., (2, 77, 768) b, n, _ = x.shape q = self.to_q(x).view(b, n, self.num_heads, self.head_dim).transpose(1, 2) k = self.to_k(context).view(b, -1, self.num_heads, self.head_dim).transpose(1, 2) v = self.to_v(context).view(b, -1, self.num_heads, self.head_dim).transpose(1, 2) # Scaled dot-product attention scores = torch.matmul(q, k.transpose(-2, -1)) / (self.head_dim ** 0.5) attn = F.softmax(scores, dim=-1) out = torch.matmul(attn, v).transpose(1, 2).contiguous().view(b, n, -1) return self.to_out(out)
def apply_classifier_free_guidance( noise_uncond: torch.Tensor, noise_cond: torch.Tensor, guidance_scale: float) -> torch.Tensor: """Extrapolates noise prediction toward prompt direction.""" return noise_uncond + guidance_scale * (noise_cond - noise_uncond)
# Verificationlatent_features = torch.randn(2, 64, 320) # 64 spatial tokenstext_embeddings = torch.randn(2, 77, 768) # 77 CLIP tokenscross_attn = CrossAttentionBlock()
output = cross_attn(latent_features, text_embeddings)print("Conditioned latent feature shape:", output.shape)# -> Conditioned latent feature shape: torch.Size([2, 64, 320])
# CFG Verificationeps_uncond = torch.tensor([0.20, -0.15])eps_cond = torch.tensor([0.60, -0.45])guided_eps = apply_classifier_free_guidance(eps_uncond, eps_cond, guidance_scale=7.5)print("Guided noise vector:", guided_eps.tolist())# -> Guided noise vector: [3.200000047683716, -2.4000000953674316]Watch Out For
Latent scaling factor mismatch between VAE encoder and diffusion noise schedule
When an image is passed through a pretrained VAE encoder , the resulting latent tensor does not naturally have unit variance (). In Stable Diffusion v1, the latent codes have an empirical standard deviation of approximately .
If you feed these unscaled latents directly into a diffusion model whose noise schedule assumes unit variance inputs (), the added noise at small timesteps is completely overwhelmed by the large variance of the latent code. To fix this, always scale the latent codes by a fixed scaling factor before diffusion: z = z * 0.18215 (and divide by 0.18215 before passing to the VAE decoder). Forgetting this scaling factor destroys image generation, causing the model to output blurry, contrast-saturated noise artifacts.
The Quick Version
- Latent Diffusion Models decouple perceptual compression (handled by a pretrained VAE) from semantic generation (handled by a latent U-Net).
- Compressing images by an factor into latents cuts data volume by and attention computation by over .
- Text conditioning is injected via cross-attention between latent U-Net features and CLIP text tokens, with Classifier-Free Guidance amplifying adherence to prompts.