Step 1: Load SD, inspect components, generate baseline
Stable Diffusion dropped in August 2022 (Rombach et al., 2022, open-sourced by Stability AI). For the first time a competitive text-to-image model had weights you could download and run locally. Within weeks the community had invented the whole modern customization stack: DreamBooth (Google, 2022), Textual Inversion (2022), LoRA for SD (2023), ControlNet (2023). Every image AI product since — Midjourney fine-tunes, Canva avatars, Adobe Firefly LoRAs — runs on this machinery.
This lab: load Stable Diffusion, inspect it, attach a LoRA adapter, run a tiny fine-tune so you can see the mechanics end-to-end.
Why LoRA for image models
SD 1.5's U-Net has ~860M parameters. Full fine-tuning:
- needs ~16 GB VRAM for weights + grads + optimizer state (fp16)
- produces a ~1.7 GB checkpoint per variant
- catastrophically forgets the base capability
LoRA freezes the 860M and adds trainable low-rank adapters on attention layers:
- ~1–3M trainable parameters (99.7% reduction)
- ~5 MB adapter per style/character — shareable, mixable
- fits comfortably in 8 GB VRAM
References to read alongside
- High-Resolution Image Synthesis (Rombach et al., 2022) — the SD paper.
- LoRA (Hu et al., 2021) — original adapter paper.
- DreamBooth (Ruiz et al., 2022) — fine-tuning recipe typically combined with LoRA.
- ControlNet (Zhang et al., 2023) — stacks with LoRA.
- SDXL (Podell et al., 2023) — modern 2.6B-param backbone.
An SD pipeline is three networks glued together:
- CLIP text encoder — prompt → embedding sequence
- U-Net — iteratively denoises a latent conditioned on text embeddings (~860M params, bulk of compute)
- VAE decoder — 64×64×4 latent → 512×512×3 RGB
import torch
from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler
device = torch.device('cuda')
MODEL_ID = 'stable-diffusion-v1-5/stable-diffusion-v1-5'
print('Loading SD 1.5 (first run downloads ~2 GB of weights)...')
pipe = StableDiffusionPipeline.from_pretrained(
MODEL_ID,
torch_dtype=torch.float16,
safety_checker=None,
requires_safety_checker=False,
).to(device)
pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config)
print(f'\nComponents:')
print(f' Text encoder (CLIP): {sum(p.numel() for p in pipe.text_encoder.parameters())/1e6:.0f}M params')
print(f' U-Net : {sum(p.numel() for p in pipe.unet.parameters())/1e6:.0f}M params')
print(f' VAE : {sum(p.numel() for p in pipe.vae.parameters())/1e6:.0f}M params')
PROMPT = 'a photograph of an astronaut riding a horse'
SEED = 42
generator = torch.Generator(device=device).manual_seed(SEED)
baseline_image = pipe(
PROMPT, num_inference_steps=25, guidance_scale=7.5,
generator=generator, height=512, width=512,
).images[0]
print(f'Generated {baseline_image.size[0]}x{baseline_image.size[1]} baseline')
baseline_image