Faza 04 · lecția 11

Stable Diffusion — arhitectură și ajustare fină

Scopul lecției: Stable Diffusion este un DDPM care rulează în spațiul latent al unui VAE preantrenat, condiționat de text prin atenție încrucișată, eșantionat cu un solver ODE determinist rapid și ghidat prin classifier-free guidance.

Versiunea curentă AlexBred.com: primele 100 de lecții ale programului în limba română.

Curs
AI Engineering from Scratch
Fază
Viziune computerizată
Lectură
16 min.
Verificat
Cuprinsul lecției
  1. Obiective de învățare
  2. Problema
  3. Conceptul
  4. Pipeline-ul
  5. Classifier-free guidance (CFG)
  6. Geometria spațiului latent
  7. Arhitectura U-Net
  8. Ajustarea fină LoRA
  9. Scheduler-ele pe care le veți întâlni
  10. Construiți-l
  11. Pasul 1: Text-la-imagine
  12. Pasul 2: Schimbați scheduler-ul
  13. Pasul 3: Image-to-image
  14. Pasul 4: Inpainting
  15. Pasul 5: Încărcarea LoRA
  16. Pasul 6: Antrenarea LoRA (schiță)
  17. Folosiți-l
  18. Livrați-l
  19. Exerciții
  20. Termeni-cheie
  21. Lecturi suplimentare

Stable Diffusion este un DDPM care rulează în spațiul latent al unui VAE preantrenat, condiționat de text prin atenție încrucișată, eșantionat cu un solver ODE determinist rapid și ghidat prin classifier-free guidance.

Tip: Învățați + utilizați Limbaje: Python Cerințe prealabile: Faza 4 Lecția 10 (Difuzie), Faza 7 Lecția 02 (Autoatenție) Timp: ~75 de minute

Obiective de învățare

  • Urmăriți cele cinci componente ale unui pipeline Stable Diffusion: VAE, codificatorul de text, U-Net, scheduler-ul, verificatorul de siguranță — și rolul efectiv al fiecăreia
  • Explicați difuzia latentă și de ce antrenarea într-un spațiu latent 4x64x64 (în locul unei imagini 3x512x512) reduce calculul cu 48x fără pierdere de calitate
  • Utilizați diffusers pentru a genera imagini, a rula image-to-image, inpainting și generare ghidată de ControlNet
  • Ajustați fin Stable Diffusion cu LoRA pe un set de date particular mic și încărcați adaptorul LoRA la inferență

Problema

Antrenarea unui DDPM direct pe imagini RGB 512x512 este costisitoare. Fiecare pas de antrenare propagă înapoi printr-un U-Net care vede 3x512x512 = 786.432 valori de intrare, iar eșantionarea necesită peste 50 de treceri înainte prin același U-Net. La nivelul de calitate al Stable Diffusion 1.5 (lansat în 2022), difuzia în spațiul pixelilor ar necesita aproximativ 256 de luni-GPU de antrenare și 10–30 de secunde per imagine pe un GPU de consum.

Trucul care a făcut practică generarea text-la-imagine cu ponderi deschise a fost difuzia latentă (Rombach et al., CVPR 2022). Antrenați un VAE care mapează o imagine 3x512x512 la un tensor latent 4x64x64 și invers, apoi realizați difuzia în acel spațiu latent. Calculul scade cu (3*512*512)/(4*64*64) = 48x. Eșantionarea scade de la zeci de secunde la mai puțin de două secunde pe același GPU.

Notă tehnică a traducerii: Raportul 48x compară doar numărul de valori din intrare; nu implică automat un câștig de timp sau memorie de exact 48x și nici absența pierderii de calitate. VAE-ul este cu pierderi, iar costul depinde de arhitectură, atenție, rezoluție, numărul de pași și implementare. Lucrarea despre latent diffusion raportează compromisuri măsurate, nu o garanție universală.

Aproape fiecare model modern de generare de imagini — SDXL, SD3, FLUX, HunyuanDiT, Wan-Video — este un model de difuzie latentă, cu variații ale autoencoderului, ale denoiserului (U-Net sau DiT) și ale condiționării pe text. Învățați Stable Diffusion și ați învățat șablonul.

Notă tehnică a traducerii: Această generalizare este utilă ca orientare, dar nu toate modelele enumerate folosesc același tip de spațiu latent, de obiectiv de difuzie sau de condiționare. Verificați documentația și lucrarea fiecărei familii înainte de a transfera detalii de implementare.

Conceptul

Pipeline-ul

Диаграмма к уроку «Stable Diffusion — arhitectură și ajustare fină»

  • VAE — autoencoder înghețat. Codificatorul transformă imaginea în reprezentări latente (folosite pentru img2img și antrenare). Decodorul transformă reprezentările latente înapoi într-o imagine.
  • Codificator de text — codificatorul de text CLIP (SD 1.x/2.x), CLIP-L + CLIP-G (SDXL) sau T5-XXL (SD3/FLUX). Produce o secvență de reprezentări vectoriale ale tokenurilor.
  • U-Net — denoiserul. Are straturi de atenție încrucișată care se raportează de la reprezentările latente la reprezentarea vectorială a textului la fiecare nivel de rezoluție.
  • Scheduler — algoritmul de eșantionare (DDIM, Euler, DPM-Solver++). Alege valorile sigma și combină zgomotul prezis înapoi în latent.
  • Verificator de siguranță — filtru opțional pentru conținut NSFW / ilegal pe imaginea de ieșire.

Classifier-free guidance (CFG)

Condiționarea simplă pe text învață epsilon_theta(x_t, t, c) pentru fiecare prompt c. CFG antrenează aceeași rețea cu c omis 10% din timp (înlocuit cu o reprezentare vectorială goală), oferind un singur model care prezice atât zgomotul condiționat, cât și pe cel necondiționat. La inferență:

eps = eps_uncond + w * (eps_cond - eps_uncond)

w este scala de ghidare. w=0 este necondiționat, w=1 este condiționare simplă, iar w>1 împinge ieșirea spre a fi „mai condiționată de prompt”, cu prețul diversității. Valoarea implicită SD este w=7.5.

CFG este motivul pentru care text-la-imagine funcționează la calitate de producție. Fără el, prompturile deplasează slab ieșirea; cu el, prompturile domină.

Notă tehnică a traducerii: Proporția de abandon a condiționării, scala implicită și efectul asupra diversității depind de model, de scheduler și de implementare. CFG este important pentru multe sisteme, însă afirmația că este singurul motiv al calității de producție este prea puternică; calitatea depinde și de date, arhitectură, antrenare și eșantionare.

Geometria spațiului latent

Latentul VAE cu 4 canale nu este doar o imagine comprimată. Este o varietate unde aritmetica corespunde aproximativ editărilor semantice (aici trăiesc atât prompt engineering, cât și interpolarea) și unde U-Net-ul de difuzie a fost antrenat să își cheltuiască întregul buget de modelare. Decodificarea unui latent aleator 4x64x64 nu produce o imagine cu aspect aleator — produce artefacte, deoarece numai o subvarietate specifică de latenți se decodifică în imagini valide.

Două consecințe:

  1. Img2img = codificați imaginea într-un latent, adăugați zgomot parțial, rulați denoiserul, decodificați. Structura imaginii supraviețuiește fiindcă codificarea este aproape inversabilă; conținutul se schimbă pe baza promptului.
  2. Inpainting = la fel ca img2img, însă denoiserul actualizează numai regiunile mascate; regiunile nemascate sunt păstrate la latentul codificat.

Arhitectura U-Net

U-Net-ul SD este o versiune mare a TinyUNet din Lecția 10, cu trei adăugiri:

  • Blocuri Transformer la fiecare rezoluție spațială, care conțin autoatenție + atenție încrucișată spre reprezentarea vectorială a textului.
  • Reprezentare vectorială a timpului prin MLP aplicat codificării sinusoidale.
  • Conexiuni de salt între codificator și decodor la rezoluții corespondente.

Numărul total de parametri în SD 1.5: ~860M. SDXL: ~2,6B. FLUX: ~12B. Creșterea numărului de parametri se află în principal în straturile de atenție.

Notă tehnică a traducerii: Numărul de parametri și distribuția lor pe submodule depind de varianta concretă a modelului și de versiune. De exemplu, familia FLUX folosește o arhitectură de tip transformer de difuzie, astfel că nu trebuie interpretată ca un U-Net SD 1.5 mărit.

Ajustarea fină LoRA

Ajustarea fină completă a Stable Diffusion necesită peste 20 GB de VRAM și actualizează 860M de parametri. LoRA (Low-Rank Adaptation) păstrează modelul de bază înghețat și injectează matrice mici de descompunere de rang redus în straturile de atenție. Un adaptor LoRA pentru SD are de obicei 10–50 MB, se antrenează în 10–60 de minute pe un singur GPU de consum și se încarcă la inferență ca modificare directă.

Original: W_q : (d_in, d_out)   înghețat
LoRA:     W_q + alpha * (A @ B)   unde A : (d_in, r), B : (r, d_out)

r este de obicei 4–32.

Notă tehnică a traducerii: Cerințele de memorie, dimensiunea adaptorului și durata de antrenare sunt estimări dependente de rezoluție, precizie, batch size, rank, numărul de imagini, optimizer și hardware. În notația LoRA, orientarea matricilor poate fi transpusă în funcție de convenția stratului; esențial este că actualizarea are rang cel mult r.

LoRA este modul în care este distribuită aproape fiecare ajustare fină din comunitate. CivitAI și Hugging Face găzduiesc milioane dintre ele.

Scheduler-ele pe care le veți întâlni

  • DDIM — determinist, ~50 de pași, simplu.
  • Euler ancestral — stocastic, 30–50 de pași, eșantioane ușor mai creative.
  • DPM-Solver++ 2M Karras — determinist, 20–30 de pași, valoare implicită pentru producție.
  • LCM / TCD / Turbo — modele de consistență și variante distilate; 1–4 pași, cu prețul unei părți din calitate.

Schimbarea scheduler-ului este o modificare de o linie în diffusers și uneori rezolvă problemele eșantioanelor fără reantrenare.

Notă tehnică a traducerii: Nu există un scheduler implicit universal pentru producție. Numărul adecvat de pași și calitatea depind de checkpoint, sampler, ghidare, rezoluție, prompt și ținta de latență; evaluați combinația aleasă pe sarcina concretă.

Construiți-l

Această lecție utilizează diffusers cap-coadă, în loc să reconstruiască Stable Diffusion de la zero. Componentele pe care ar trebui să le reconstruiți (VAE, codificatorul de text, U-Net, scheduler-ul) sunt subiectele unor lecții proprii; aici scopul este fluența cu API-ul de producție.

Pasul 1: Text-la-imagine

import torch
from diffusers import StableDiffusionPipeline

pipe = StableDiffusionPipeline.from_pretrained(
    "runwayml/stable-diffusion-v1-5",
    torch_dtype=torch.float16,
).to("cuda")

image = pipe(
    prompt="a dog riding a skateboard in tokyo, studio ghibli style",
    guidance_scale=7.5,
    num_inference_steps=25,
    generator=torch.Generator("cuda").manual_seed(42),
).images[0]
image.save("dog.png")

float16 înjumătățește consumul VRAM fără pierdere de calitate vizibilă. num_inference_steps=25 cu DPM-Solver++ implicit se potrivește cu num_inference_steps=50 cu DDIM.

Notă tehnică a traducerii: float16 reduce de regulă memoria pentru ponderi și activări, însă poate afecta stabilitatea numerică sau calitatea pentru unele modele și dispozitive. Scheduler-ul implicit și echivalența calității între 25 și 50 de pași se pot schimba între versiunile diffusers și checkpoint-uri; validați rezultatul în mediul curent.

Pasul 2: Schimbați scheduler-ul

from diffusers import DPMSolverMultistepScheduler, EulerAncestralDiscreteScheduler

pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config)
pipe.scheduler = EulerAncestralDiscreteScheduler.from_config(pipe.scheduler.config)

Starea scheduler-ului este decuplată de ponderile U-Net-ului. Puteți antrena cu DDPM și eșantiona cu orice scheduler.

Notă tehnică a traducerii: Conversia din configurația existentă este adesea posibilă în diffusers, dar nu garantează compatibilitate sau calitate echivalentă pentru orice model și scheduler. Unele schedulere cer setări specifice de sigma, predicție sau pași de timp.

Pasul 3: Image-to-image

from diffusers import StableDiffusionImg2ImgPipeline
from PIL import Image

img2img = StableDiffusionImg2ImgPipeline.from_pretrained(
    "runwayml/stable-diffusion-v1-5",
    torch_dtype=torch.float16,
).to("cuda")

init_image = Image.open("dog.png").convert("RGB").resize((512, 512))
out = img2img(
    prompt="a dog riding a skateboard, oil painting",
    image=init_image,
    strength=0.6,
    guidance_scale=7.5,
).images[0]

strength reprezintă cât zgomot se adaugă înainte de denoising (0,0 = neschimbat, 1,0 = regenerare completă). Intervalul 0,5–0,7 este standard pentru transferul de stil.

Notă tehnică a traducerii: Efectul precis al lui strength, inclusiv cazul-limită 0.0, depinde de implementarea pipeline-ului și de scheduler; intervalul potrivit pentru transfer de stil nu este universal. Consultați documentația versiunii diffusers utilizate.

Pasul 4: Inpainting

from diffusers import StableDiffusionInpaintPipeline

inpaint = StableDiffusionInpaintPipeline.from_pretrained(
    "runwayml/stable-diffusion-inpainting",
    torch_dtype=torch.float16,
).to("cuda")

image = Image.open("dog.png").convert("RGB").resize((512, 512))
mask = Image.open("dog_mask.png").convert("L").resize((512, 512))

out = inpaint(
    prompt="a cat",
    image=image,
    mask_image=mask,
    guidance_scale=7.5,
).images[0]

Pixelii albi din mască sunt zona de regenerat. Pixelii negri sunt păstrați.

Pasul 5: Încărcarea LoRA

pipe.load_lora_weights("sayakpaul/sd-lora-ghibli")
pipe.fuse_lora(lora_scale=0.8)

image = pipe(prompt="a village square in ghibli style").images[0]

lora_scale controlează intensitatea; 0,0 = fără efect, 1,0 = efect complet. fuse_lora încorporează adaptorul în ponderi pentru viteză, dar împiedică înlocuirea lui. Apelați pipe.unfuse_lora() înainte de a încărca un alt adaptor.

Notă tehnică a traducerii: API-urile LoRA și efectul exact al fuziunii depind de versiunea diffusers și de pipeline. Verificați compatibilitatea adaptorului și documentația curentă înainte de a presupune că un adaptor poate fi fuzionat sau înlocuit direct.

Pasul 6: Antrenarea LoRA (schiță)

Antrenarea LoRA reală se află în peft sau diffusers.training. Schița:

# Pseudocode
for step, batch in enumerate(dataloader):
    images, prompts = batch
    latents = vae.encode(images).latent_dist.sample() * 0.18215

    t = torch.randint(0, num_train_timesteps, (batch_size,))
    noise = torch.randn_like(latents)
    noisy_latents = scheduler.add_noise(latents, noise, t)

    text_emb = text_encoder(tokenizer(prompts))

    pred_noise = unet(noisy_latents, t, text_emb)  # LoRA weights injected here

    loss = F.mse_loss(pred_noise, noise)
    loss.backward()
    optimizer.step()

Numai matricele LoRA primesc gradient; U-Net-ul de bază, VAE-ul și codificatorul de text sunt înghețate. Cu un batch size de 1 și gradient checkpointing, aceasta încape în 8 GB de VRAM.

Notă tehnică a traducerii: Constanta de scalare latentă nu trebuie presupusă universală: ea este definită de configurația VAE/checkpoint-ului. De asemenea, 8 GB pot fi insuficienți în funcție de rezoluție, precizie, optimizer, rank și pipeline; folosiți configurația și ghidul de antrenare ale modelului concret.

Notă tehnică a traducerii: Schița omite optimizer.zero_grad() înainte de loss.backward(). În PyTorch, gradienții se acumulează implicit; pentru pași de optimizare independenți, resetați gradientele înaintea retropropagării. Consultați documentația PyTorch pentru Optimizer.zero_grad.

Folosiți-l

În producție, deciziile pe care le luați efectiv:

  • Familia de modele: SD 1.5 pentru ajustări fine ale comunității open-source, SDXL pentru fidelitate mai mare, SD3 / FLUX pentru performanță de vârf și cerințe stricte de licențiere.
  • Scheduler: DPM-Solver++ 2M Karras pentru 20–30 de pași, LCM-LoRA când latența este sub 1 s.
  • Precizia: float16 pe 4080/4090, bfloat16 pe A100 și versiuni mai noi, int8 (prin bitsandbytes) când VRAM-ul este limitat.
  • Condiționarea: textul simplu funcționează; pentru control mai puternic, adăugați ControlNet (canny, adâncime, poziție) peste pipeline-ul de bază.

Pentru generare în loturi, AUTO1111 / ComfyUI sunt instrumentele comunității; pentru API-uri de producție, diffusers + accelerate sau optimum-nvidia cu compilare TensorRT.

Notă tehnică a traducerii: Aceste alegeri sunt orientative, nu recomandări universale. Disponibilitatea modelelor, licențele, cerințele de memorie, suportul pentru cuantizare și performanța se schimbă cu versiunile software și hardware; verificați licența modelului și benchmark-urile actuale pentru produsul concret.

Notă tehnică a traducerii: bitsandbytes oferă mecanisme de cuantizare, însă compel este o bibliotecă de ponderare și combinare a prompturilor/reprezentărilor textuale, nu un instrument de cuantizare int8. Consultați documentația bitsandbytes și depozitul Compel.

Livrați-l

Această lecție produce:

  • outputs/prompt-sd-pipeline-planner.md — un prompt care alege SD 1.5 / SDXL / SD3 / FLUX plus scheduler-ul și precizia, având un buget de latență, o țintă de fidelitate și o constrângere de licențiere.
  • outputs/skill-lora-training-setup.md — o abilitate care scrie o configurație completă de antrenare LoRA pentru un set de date particular, inclusiv descrieri, rank, batch size și rată de învățare.

Exerciții

  1. (Ușor) Generați același prompt cu guidance_scale în [1, 3, 5, 7.5, 10, 15]. Descrieți cum se schimbă imaginea. La ce valoare de ghidare apar artefactele?
  2. (Mediu) Luați orice fotografie reală, rulați-o prin StableDiffusionImg2ImgPipeline la strength în [0.2, 0.4, 0.6, 0.8, 1.0]. Care valoare păstrează compoziția în timp ce schimbă stilul? De ce 1.0 ignoră complet intrarea?
  3. (Dificil) Antrenați un LoRA pe 10–20 de imagini ale unui singur subiect (un animal de companie, o siglă, un personaj) și generați scene noi cu acel subiect. Raportați rank-ul LoRA și pașii de antrenare care au oferit cea mai bună păstrare a identității fără supraînvățarea imaginilor de intrare.

Termeni-cheie

Termen Ce spun oamenii Ce înseamnă de fapt
Difuzie latentă „Difuzează în latenți” Rulează întregul DDPM în spațiul latent VAE (4x64x64) în locul spațiului pixelilor (3x512x512); economie de calcul de 48x
Factorul de scalare VAE „0.18215” Constantă care rescalează latentul brut al VAE spre varianță unitară aproximativă; codificată rigid în fiecare pipeline SD
Classifier-free guidance „CFG” Combină predicțiile de zgomot condiționate și necondiționate; cel mai influent control de inferență
Scheduler „Sampler” Algoritmul care transformă zgomotul + predicțiile modelului într-o traiectorie latentă denoizată
LoRA „Adaptor de rang redus” Matrice mici de descompunere de rang redus care ajustează fin straturile de atenție fără a modifica ponderile de bază
Atenție încrucișată „Atenție text–imagine” Atenție de la tokenurile latente spre tokenurile textului; injectează informația promptului la fiecare nivel U-Net
ControlNet „Condiționare structurală” Adaptor antrenat separat care ghidează SD cu o intrare suplimentară (canny, adâncime, poziție, segmentare)
DPM-Solver++ „Scheduler-ul implicit” Solver ODE determinist de ordinul al doilea; cea mai bună calitate la număr mic de pași (20–30) în 2026

Notă tehnică a traducerii: Factorul de scalare VAE și alegerea „cea mai bună” a scheduler-ului nu sunt universale; ele depind de checkpoint, configurație și protocolul de evaluare. În particular, nu fiecare pipeline SD folosește constanta 0.18215.

Lecturi suplimentare

Sursă: Originalul în limba engleză

Navigare: ← Lecția 04.10 — Generarea imaginilor — modele de difuzie · Faza 4 — Viziune computerizată · Lecția 04.12 — Înțelegerea videoclipurilor — modelare temporală → · Catalog complet