Skip to content
AILinkDeepTech
Go back
Reinforcement Learning Medium

Stable Diffusion: Latent Diffusion Models, Cross-Attention U-Net, and PyTorch Architecture

Abstract

Master Stable Diffusion: latent diffusion models (LDM), perceptual VAE compression, cross-attention U-Net, classifier-free guidance, and PyTorch pipelines.

Latent Space Compression and Perceptual Redundancy

Pixel-space generative diffusion models (e.g., DDPM, Imagen) execute the forward and reverse Markov diffusion chains directly across high-resolution image grids . In pixel space, high-frequency imperceptible details (micro-textures, sensor noise) consume the overwhelming majority of model capacity and computational complexity ( in convolutions and in spatial self-attention).

Latent Diffusion Models (LDM) (Rombach et al., CVPR 2022; commercialized as Stable Diffusion) decouple the generative process into two orthogonal phases:

  1. Perceptual Compression: An autoencoder compresses high-dimensional images into a lower-dimensional latent space ( spatial-channel reduction), discarding high-frequency imperceptible variance.
  2. Semantic Generative Diffusion: A conditional U-Net learns the reverse diffusion process over the regularized continuous latent manifold , conditioned on multimodal prompts (e.g., text, bounding boxes, depth maps) via cross-attention.

Generative Architectures Comparison

Generative ParadigmOperating ManifoldCompute Complexity per StepParameter ScaleText Conditioning MethodTraining Stability
Pixel Diffusion (DDPM / Imagen)Pixel Space Extremely High ()Cascaded Super-ResolutionHigh (ELBO / Score Matching)
Autoregressive Pixel (DALL-E 1)Quantized Codebook Sequential Token DecodingPrefix TransformerHigh (Cross-Entropy)
GANs (StyleGAN3)Continuous Vector Low (Single Feed-Forward Step)Modulated ConvolutionsLow (Minimax Instabilities)
Latent Diffusion (SD 1.5 / SDXL)Latent Space Low ( vs Pixel Space) (1.5) / (XL)Spatial Cross-AttentionHigh (Score-Matching MSE)
Diffusion Transformer (SD3 / FLUX)Patch Latents Moderate (Tiled Attention)Multimodal DiT (MMDiT)High (Rectified Flow)

Mathematical Foundations

flowchart LR IMAGE["Pixel Image x in R^(3 x 512 x 512)"] --> VAE_ENC["Frozen VAE Encoder E(x)"] VAE_ENC --> LATENT["Clean Latent z_0 in R^(4 x 64 x 64)\n(Scaled by factor 0.18215)"] NOISE["Gaussian Noise epsilon ~ N(0, I)"] --> FORWARD["Forward Diffusion:\nz_t = sqrt(alpha_bar_t) * z_0 + sqrt(1 - alpha_bar_t) * epsilon"] LATENT --> FORWARD FORWARD --> NOISY_Z["Noisy Latent z_t"] PROMPT["Text Prompt y"] --> CLIP["Frozen CLIP Text Encoder tau(y)"] CLIP --> TEXT_EMB["Context Embeddings c in R^(77 x 768)"] NOISY_Z --> UNET["Time-Conditional Cross-Attention U-Net\nepsilon_theta(z_t, t, c)"] TEXT_EMB --> UNET T_STEP["Timestep t in [1, 1000]"] --> UNET UNET --> PRED_NOISE["Predicted Noise epsilon_hat"] PRED_NOISE --> LOSS["MSE Loss: || epsilon - epsilon_hat ||^2"] NOISE --> LOSS

Figure 1: Stable Diffusion training pipeline compressing pixels to latents via a frozen VAE and training a cross-attention U-Net denoiser.

1. Two-Stage Perceptual Autoencoding (VAE)

The autoencoder consists of an encoder and decoder :

To prevent arbitrarily high-variance latent representations, the autoencoder is trained under a Kullback-Leibler (KL) divergence penalty alongside perceptual (LPIPS) and patch-based adversarial objectives:

Once trained, the VAE weights are completely frozen. The latent representations are normalized by an empirical scaling factor (for SD 1.5) to ensure unit variance :


2. Forward Latent Diffusion Process

The forward diffusion process defines a Markov chain corrupting the clean latent with Gaussian noise over discrete timesteps according to variance schedule :

Using the reparameterization trick with and :


3. Cross-Attention Conditional U-Net Denoising Objective

The reverse denoising process parameterizes a noise-prediction network using a time-conditioned U-Net. The training objective is the simplified mean squared error (MSE) score-matching loss:

where represents the sequence of token embeddings generated by the frozen CLIP text encoder for text prompt .

Spatial Cross-Attention Layer Formulation:

Inside the intermediate layers of the U-Net, spatial feature maps are flattened into visual tokens and projected against text features :

flowchart TD FEAT["Spatial Feature Map phi_i(z_t)\nin R^(C x H_i x W_i)"] --> FLATTEN["Flatten to N = H_i * W_i Tokens"] FLATTEN --> PROJ_Q["Query Projection: Q = F_i * W_Q (N x d_k)"] TEXT["CLIP Text Embeddings tau(y)\nin R^(77 x 768)"] --> PROJ_K["Key Projection: K = tau(y) * W_K (77 x d_k)"] TEXT --> PROJ_V["Value Projection: V = tau(y) * W_V (77 x d_v)"] PROJ_Q --> MATMUL["Q * K^T / sqrt(d_k) in R^(N x 77)"] PROJ_K --> MATMUL MATMUL --> SOFTMAX["Softmax Attention Map (N x 77)"] SOFTMAX --> ATTN_OUT["Attn_Map * V in R^(N x d_v)"] PROJ_V --> ATTN_OUT ATTN_OUT --> RESHAPE["Reshape to (C x H_i x W_i) + Residual Add"]

Figure 2: Spatial Cross-Attention mechanism routing semantic text features to spatial U-Net pixel coordinates.


4. Classifier-Free Guidance (CFG) Mathematical Derivation

To enforce prompt alignment without training auxiliary classifier networks, Classifier-Free Guidance (Ho & Salimans, 2022) trains a single model unconditionally with probability by substituting prompt with empty null tokens .

During inference, the score estimate is extrapolated along the guidance vector:

where is the guidance scale parameter.

Score-Matching Equivalence:

Using Tweedie’s formula, the noise estimate relates directly to the data log-likelihood score: . Substituting into the CFG equation:

\nabla_{\mathbf{z}_t} \log \tilde{p}(\mathbf{z}_t \mid \mathbf{c}) &= \nabla_{\mathbf{z}_t} \log p(\mathbf{z}_t) + s \left[ \nabla_{\mathbf{z}_t} \log p(\mathbf{z}_t \mid \mathbf{c}) - \nabla_{\mathbf{z}_t} \log p(\mathbf{z}_t) \right] \\ &= \nabla_{\mathbf{z}_t} \log p(\mathbf{z}_t) + s \nabla_{\mathbf{z}_t} \log p(\mathbf{c} \mid \mathbf{z}_t) \\ &= \nabla_{\mathbf{z}_t} \log \left[ p(\mathbf{z}_t) \cdot p(\mathbf{c} \mid \mathbf{z}_t)^s \right] \end{aligned}$$ CFG implicitly sharpens the conditional posterior distribution $p(\mathbf{c} \mid \mathbf{z}_t)$ by exponent power $s$, drastically reducing sample diversity in exchange for higher semantic fidelity. --- ## Production PyTorch Implementation Below is a complete, modular PyTorch implementation of the **Spatial Cross-Attention block** and an end-to-end **Latent Diffusion Generation Pipeline** using batched Classifier-Free Guidance. ### Step 1: Spatial Cross-Attention Layer ```python title="src/cross_attention.py" from __future__ import annotations import torch import torch.nn as nn import torch.nn.functional as F class SpatialCrossAttention(nn.Module): """Spatial 2D Cross-Attention Block mapping text context to spatial latents.""" def __init__(self, query_dim: int, context_dim: int = 768, num_heads: int = 8, head_dim: int = 64) -> None: super().__init__() self.inner_dim = num_heads * head_dim self.num_heads = num_heads self.head_dim = head_dim self.scale = 1.0 / (head_dim ** 0.5) self.to_q = nn.Linear(query_dim, self.inner_dim, bias=False) self.to_k = nn.Linear(context_dim, self.inner_dim, bias=False) self.to_v = nn.Linear(context_dim, self.inner_dim, bias=False) self.to_out = nn.Sequential( nn.Linear(self.inner_dim, query_dim), nn.Dropout(0.0), ) def forward(self, x: torch.Tensor, context: torch.Tensor | None = None) -> torch.Tensor: # x: [B, C, H, W], context: [B, Seq_Len, Context_Dim] b, c, h, w = x.shape residual = x # 1. Flatten spatial grid to sequence: [B, H*W, C] x_flat = x.permute(0, 2, 3, 1).contiguous().view(b, h * w, c) context = x_flat if context is None else context # 2. Project Q, K, V q = self.to_q(x_flat) k = self.to_k(context) v = self.to_v(context) # 3. Reshape for Multi-Head Attention: [B, Heads, Seq_Len, Head_Dim] q = q.view(b, -1, self.num_heads, self.head_dim).transpose(1, 2) k = k.view(b, -1, self.num_heads, self.head_dim).transpose(1, 2) v = v.view(b, -1, self.num_heads, self.head_dim).transpose(1, 2) # 4. Scaled Dot-Product Attention out = F.scaled_dot_product_attention(q, k, v) out = out.transpose(1, 2).contiguous().view(b, -1, self.inner_dim) # 5. Output projection and reshape back to [B, C, H, W] out = self.to_out(out).view(b, h, w, c).permute(0, 3, 1, 2).contiguous() return out + residual ``` --- ### Step 2: Full Stable Diffusion Generation Pipeline ```python title="src/sd_pipeline.py" from __future__ import annotations import torch from diffusers import AutoencoderKL, DPMSolverMultistepScheduler, UNet2DConditionModel from transformers import CLIPTextModel, CLIPTokenizer class StableDiffusionPipelineEngine: """Production Stable Diffusion Inference Engine with Batched CFG.""" def __init__(self, model_id: str = "runwayml/stable-diffusion-v1-5", device: str = "cuda") -> None: self.device = torch.device(device) self.dtype = torch.float16 # 1. Load components self.tokenizer = CLIPTokenizer.from_pretrained(model_id, subfolder="tokenizer") self.text_encoder = CLIPTextModel.from_pretrained(model_id, subfolder="text_encoder", torch_dtype=self.dtype).to(self.device) self.vae = AutoencoderKL.from_pretrained(model_id, subfolder="vae", torch_dtype=self.dtype).to(self.device) self.unet = UNet2DConditionModel.from_pretrained(model_id, subfolder="unet", torch_dtype=self.dtype).to(self.device) self.scheduler = DPMSolverMultistepScheduler.from_pretrained(model_id, subfolder="scheduler") # Freeze evaluation mode self.vae.eval().requires_grad_(False) self.text_encoder.eval().requires_grad_(False) self.unet.eval().requires_grad_(False) self.scaling_factor = self.vae.config.scaling_factor # 0.18215 @torch.no_grad() def generate( self, prompt: str, negative_prompt: str = "blurry, low quality, distorted, extra limbs", num_inference_steps: int = 25, guidance_scale: float = 7.5, height: int = 512, width: int = 512, seed: int = 42, ) -> torch.Tensor: generator = torch.Generator(device=self.device).manual_seed(seed) # 1. Encode Positive and Negative Text Prompts text_inputs = self.tokenizer([negative_prompt, prompt], padding="max_length", max_length=77, return_tensors="pt") text_embeddings = self.text_encoder(text_inputs.input_ids.to(self.device))[0] # [2, 77, 768] # 2. Initialize Random Latent Tensor latents = torch.randn( (1, self.unet.config.in_channels, height // 8, width // 8), generator=generator, device=self.device, dtype=self.dtype, ) self.scheduler.set_timesteps(num_inference_steps) latents = latents * self.scheduler.init_noise_sigma # 3. Iterative Latent Reverse Denoising Loop for t in self.scheduler.timesteps: # Duplicate latents for batched conditional + unconditional forward pass latent_model_input = torch.cat([latents] * 2) latent_model_input = self.scheduler.scale_model_input(latent_model_input, t) # Predict Noise noise_pred = self.unet(latent_model_input, t, encoder_hidden_states=text_embeddings).sample # Classifier-Free Guidance Extrapolation noise_pred_uncond, noise_pred_text = noise_pred.chunk(2) noise_pred_guided = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond) # Compute Previous Latent State z_(t-1) latents = self.scheduler.step(noise_pred_guided, t, latents).prev_sample # 4. Decode Clean Latents via VAE Decoder latents = latents / self.scaling_factor image = self.vae.decode(latents).sample # [1, 3, 512, 512] image = (image / 2.0 + 0.5).clamp(0.0, 1.0) return image ``` --- ## Empirical Benchmark Evaluation Quantitative quality, text alignment, and compute throughput benchmarks across open diffusion models: | Model Backbone | Architecture | Parameters | Resolution | COCO Zero-Shot FID-30K ($\downarrow$) | CLIP Score ($\uparrow$) | GenEval ($0\text{--}1 \uparrow$) | Inference Time (4090) | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | **Stable Diffusion 1.5** | Latent U-Net + CLIP-H | $860\text{M}$ | $512 \times 512$ | $12.82$ | $0.298$ | $0.44$ | $1.4\text{ s}$ ($25\text{ steps}$) | | **Stable Diffusion 2.1** | Latent U-Net + OpenCLIP-bigG | $865\text{M}$ | $768 \times 768$ | $11.21$ | $0.312$ | $0.50$ | $2.1\text{ s}$ ($25\text{ steps}$) | | **SDXL 1.0 (Base)** | Latent U-Net + Dual CLIP | $2.6\text{B}$ | $1024 \times 1024$ | $9.62$ | $0.324$ | $0.62$ | $4.2\text{ s}$ ($30\text{ steps}$) | | **SDXL Lightning (4-step)**| Distilled U-Net | $2.6\text{B}$ | $1024 \times 1024$ | $10.15$ | $0.319$ | $0.59$ | $\mathbf{0.6\text{ s}}$ ($4\text{ steps}$) | | **SD 3.5 Large** | Multimodal DiT + T5-XXL | $8.0\text{B}$ | $1024 \times 1024$ | $7.84$ | $0.338$ | $0.74$ | $8.6\text{ s}$ ($28\text{ steps}$) | | **FLUX.1 [dev]** | Flow-Matching DiT | $12.0\text{B}$ | $1024 \times 1024$ | $\mathbf{7.21}$ | $\mathbf{0.345}$ | $\mathbf{0.81}$ | $12.4\text{ s}$ ($28\text{ steps}$) | --- ## Troubleshooting Common Stable Diffusion Faults ### 1. High Guidance Scale Color Saturation ("Fried Image" Artifacts) - **Symptom**: Generated images display unnatural, hyper-saturated neon colors, heavy edge halos, and burnt skin tones. - **Root Cause**: Setting `guidance_scale` $\ge 12.0$ causes score-matching vectors to point into unobserved regions with inflated activation norms. - **Remedy**: Lower `guidance_scale` to $7.0\text{--}8.0$ (for SD 1.5) or apply standard deviation CFG rescaling: $s_{\text{rescale}} = 0.7$. ### 2. Washed Out or Static Noise Outputs (VAE Scaling Omission) - **Symptom**: Output images display faint, washed-out silhouettes covered in high-frequency static noise. - **Root Cause**: Forgetting to multiply by `vae.config.scaling_factor` ($0.18215$) when feeding latents into the U-Net, or forgetting to divide before VAE decoding. - **Remedy**: Ensure scaling is strictly applied at boundaries: $\mathbf{z}_{\text{unet}} = \mathbf{z}_{\text{vae}} \times 0.18215$ and $\mathbf{z}_{\text{decode}} = \mathbf{z}_{\text{denoised}} / 0.18215$. ### 3. Black Image / NaN Output in Half-Precision (FP16) - **Symptom**: Generated tensor contains `NaN` values, rendering completely black images when running in `torch.float16`. - **Root Cause**: The standard SD 1.5 VAE decoder encounters numerical overflow in intermediate GroupNorm / convolution layers in FP16 precision. - **Remedy**: Cast the VAE exclusively to `torch.float32` while keeping the U-Net in `torch.float16` / `torch.bfloat16`: `vae.to(dtype=torch.float32)`. --- ## References 1. Rombach, R., Blattmann, A., Lorenz, D., Esser, P., & Ommer, B. (2022). *High-Resolution Image Synthesis with Latent Diffusion Models*. IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR 2022). 2. Ho, J., & Salimans, T. (2022). *Classifier-Free Diffusion Guidance*. NeurIPS 2022 Workshop on NeurIPS. 3. Podell, D., English, Z., Lacey, K., Blattmann, A., Dockhorn, T., MΓΌller, J., Penna, N., & Rombach, R. (2023). *SDXL: Improving Latent Diffusion Models for High-Resolution Image Synthesis*. arXiv:2307.01952. 4. Radford, A., et al. (2021). *Learning Transferable Visual Models From Natural Language Supervision (CLIP)*. ICML 2021. 5. Esser, P., et al. (2024). *Scaling Rectified Flow Transformers for High-Resolution Image Synthesis (SD3)*. arXiv:2403.03206.


Cite this Explanation

@article{ailinkdeeptech2025stablediffusion,
  title={Stable Diffusion: Latent Diffusion Models, Cross-Attention U-Net, and PyTorch Architecture},
  author={AILinkDeepTech},
  journal={AILinkDeepTech Algorithm Explanations},
  year={2025},
  url={https://ailinkdeeptech.com/research/stable_diffusion}
}

Related Explanations