Фаза 04 · урок 11

Stable Diffusion — архитектура и тонкая настройка

Цель урока: Обучать DDPM непосредственно на RGB-изображениях 512x512 дорого. На каждом шаге обучения обратное распространение проходит через U-Net, которая видит 3x512x512 = 786,432 входных значения, а сэмплирование требует 50+ прямых проходов…

Текущий релиз AlexBred.com: первые 100 уроков русскоязычной программы.

Курс
AI Engineering from Scratch
Фаза
Компьютерное зрение
Чтение
11 мин.
Проверено
Содержание урока
  1. Цели обучения
  2. Проблема
  3. Концепция
  4. Пайплайн
  5. Classifier-free guidance (CFG)
  6. Геометрия латентного пространства
  7. Архитектура U-Net
  8. Тонкая настройка LoRA
  9. Schedulers, которые вам встретятся
  10. Соберите
  11. Шаг 1: Text-to-image
  12. Шаг 2: Замена scheduler
  13. Шаг 3: Image-to-image
  14. Шаг 4: Inpainting
  15. Шаг 5: Загрузка LoRA
  16. Шаг 6: Обучение LoRA (набросок)
  17. Используйте
  18. Поставьте
  19. Упражнения
  20. Ключевые термины
  21. Дополнительные материалы

Stable Diffusion — это DDPM, работающая в латентном пространстве предобученного VAE, обусловленная текстом через cross-attention, сэмплируемая быстрым детерминированным ODE-решателем и направляемая classifier-free guidance.

Тип: Изучить + использовать Языки: Python Предварительные требования: Фаза 4, урок 10 (диффузия), фаза 7, урок 02 (самовнимание) Время: ~75 минут

Цели обучения

  • Проследить пять частей пайплайна Stable Diffusion: VAE, текстовый кодировщик, U-Net, scheduler, safety checker — и понять, что именно делает каждая из них
  • Объяснить латентную диффузию и почему обучение в латентном пространстве 4x64x64 (вместо изображения 3x512x512) снижает вычисления в 48 раз без потери качества
  • Использовать diffusers для генерации изображений, image-to-image, inpainting и генерации с направлением ControlNet
  • Выполнить тонкую настройку Stable Diffusion с LoRA на небольшом пользовательском наборе данных и загрузить адаптер LoRA при инференсе

Проблема

Обучать DDPM непосредственно на RGB-изображениях 512x512 дорого. На каждом шаге обучения обратное распространение проходит через U-Net, которая видит 3x512x512 = 786,432 входных значения, а сэмплирование требует 50+ прямых проходов через ту же U-Net. На уровне качества Stable Diffusion 1.5 (выпущенной в 2022 году) диффузии в пиксельном пространстве потребовалось бы примерно 256 GPU-месяцев обучения и 10–30 секунд на изображение на потребительском GPU.

Приём, сделавший практичной text-to-image модель с открытыми весами, — латентная диффузия (latent diffusion; Rombach и др., CVPR 2022). Обучите VAE, отображающий изображение 3x512x512 в латентный тензор 4x64x64 и обратно, а затем выполняйте диффузию в этом латентном пространстве. Вычисления уменьшаются в (3*512*512)/(4*64*64) = 48x. На том же GPU сэмплирование сокращается с десятков секунд до менее чем двух секунд.

Почти каждая современная модель генерации изображений — SDXL, SD3, FLUX, HunyuanDiT, Wan-Video — является моделью латентной диффузии с вариациями автоэнкодера, денойзера (U-Net или DiT) и текстового обусловливания. Освоив Stable Diffusion, вы освоите этот шаблон.

Концепция

Пайплайн

Диаграмма к уроку «Stable Diffusion — архитектура и тонкая настройка»

  • VAE — замороженный автоэнкодер. Кодировщик превращает изображение в латенты (используется для img2img и обучения). Декодировщик превращает латенты обратно в изображение.
  • Текстовый кодировщик — текстовый кодировщик CLIP (SD 1.x/2.x), CLIP-L + CLIP-G (SDXL) или T5-XXL (SD3/FLUX). Создаёт последовательность встраиваний токенов.
  • U-Net — денойзер. Имеет слои cross-attention, которые на каждом уровне разрешения сопоставляют латенты с текстовым встраиванием.
  • Scheduler — алгоритм сэмплирования (DDIM, Euler, DPM-Solver++). Выбирает sigma и смешивает предсказанный шум обратно в латент.
  • Safety checker — необязательный фильтр NSFW / незаконного контента для выходного изображения.

Classifier-free guidance (CFG)

Обычное текстовое обусловливание обучает epsilon_theta(x_t, t, c) для каждого промпта c. CFG обучает ту же сеть с отброшенным c в 10% случаев (заменённым пустым встраиванием), что даёт одну модель, предсказывающую и условный, и безусловный шум. При инференсе:

eps = eps_uncond + w * (eps_cond - eps_uncond)

w — масштаб guidance. При w=0 модель безусловна, при w=1 используется обычное условие, при w>1 результат сильнее подталкивается к тому, чтобы быть «более обусловленным промптом», ценой разнообразия. По умолчанию в SD используется w=7.5.

Именно CFG позволяет text-to-image работать с качеством, пригодным для продакшена. Без неё промпты слабо смещают выход; с ней промпты доминируют.

Геометрия латентного пространства

Четырёхканальный латент VAE — не просто сжатое изображение. Это многообразие, в котором арифметика приблизительно соответствует семантическим редактированиям (здесь живут и prompt engineering, и интерполяция), и в котором диффузионная U-Net обучена тратить весь свой бюджет моделирования. Декодирование случайного латента 4x64x64 не создаёт случайно выглядящее изображение — оно создаёт мусор, поскольку лишь определённое подмногообразие латентов декодируется в допустимые изображения.

Два следствия:

  1. Img2img = закодировать изображение в латент, добавить частичный шум, запустить денойзер, декодировать. Структура изображения сохраняется, потому что кодирование почти обратимо; содержание меняется в соответствии с промптом.
  2. Inpainting = то же, что img2img, но денойзер обновляет только замаскированные области; незамаскированные области сохраняются в закодированном латенте.

Архитектура U-Net

U-Net SD — крупная версия TinyUNet из урока 10 с тремя дополнениями:

  • Блоки Transformer при каждом пространственном разрешении, содержащие self-attention + cross-attention к текстовому встраиванию.
  • Встраивание времени через MLP над синусоидальным кодированием.
  • Skip connections между кодировщиком и декодировщиком на соответствующих разрешениях.

Всего параметров в SD 1.5: ~860M. В SDXL: ~2.6B. В FLUX: ~12B. Рост числа параметров в основном приходится на слои внимания.

Тонкая настройка LoRA

Полная тонкая настройка Stable Diffusion требует 20+ GB VRAM и обновляет 860M параметров. LoRA (Low-Rank Adaptation) оставляет базовую модель замороженной и внедряет небольшие матрицы рангового разложения в слои внимания. Адаптер LoRA для SD обычно занимает 10–50 MB, обучается за 10–60 минут на одном потребительском GPU и загружается при инференсе как модификация без замены модели.

Original: W_q : (d_in, d_out)   frozen
LoRA:     W_q + alpha * (A @ B)   where A : (d_in, r), B : (r, d_out)

r is typically 4-32.

Именно так распространяется почти каждая community-тонкая настройка. CivitAI и Hugging Face размещают их миллионы.

Schedulers, которые вам встретятся

  • DDIM — детерминированный, ~50 шагов, простой.
  • Euler ancestral — стохастический, 30–50 шагов, даёт немного более творческие образцы.
  • DPM-Solver++ 2M Karras — детерминированный, 20–30 шагов, стандарт для продакшена.
  • LCM / TCD / Turbo — модели согласованности и дистиллированные варианты; 1–4 шага ценой некоторого качества.

Замена scheduler — это однострочное изменение в diffusers, которое иногда исправляет проблемы образцов без какого-либо переобучения.

Соберите

В этом уроке diffusers используется сквозным образом, а не для пересборки Stable Diffusion с нуля. Компоненты, которые потребовались бы для такой пересборки (VAE, текстовый кодировщик, U-Net, scheduler), — темы отдельных уроков; здесь цель — свободно владеть production API.

Шаг 1: Text-to-image

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 вдвое сокращает VRAM без видимой потери качества. num_inference_steps=25 со стандартным DPM-Solver++ соответствует num_inference_steps=50 с DDIM.

Шаг 2: Замена scheduler

from diffusers import DPMSolverMultistepScheduler, EulerAncestralDiscreteScheduler

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

Состояние scheduler отделено от весов U-Net. Вы можете обучать на DDPM и сэмплировать любым scheduler.

Шаг 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 задаёт, сколько шума добавить перед денойзингом (0.0 = без изменений, 1.0 = полная регенерация). Стандартный диапазон для переноса стиля — 0.5–0.7.

Шаг 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]

Белые пиксели маски — область для регенерации. Чёрные пиксели сохраняются.

Шаг 5: Загрузка 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 управляет силой: 0.0 = нет эффекта, 1.0 = полный эффект. fuse_lora для скорости встраивает адаптер в веса на месте, но не позволяет менять адаптеры. Вызовите pipe.unfuse_lora() до загрузки другого адаптера.

Шаг 6: Обучение LoRA (набросок)

Реальное обучение LoRA находится в peft или diffusers.training. Схема:

# 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()

Градиент получают только матрицы LoRA; базовые U-Net, VAE и текстовый кодировщик заморожены. При batch size 1 и gradient checkpointing это помещается в 8 GB VRAM.

Используйте

В продакшене решения, которые вы действительно принимаете:

  • Семейство модели: SD 1.5 для community-тонких настроек с открытым исходным кодом, SDXL для более высокой точности, SD3 / FLUX для передового уровня и строгих лицензионных требований.
  • Scheduler: DPM-Solver++ 2M Karras для 20–30 шагов, LCM-LoRA, когда задержка должна быть менее 1 секунды.
  • Точность: float16 на 4080/4090, bfloat16 на A100 и новее, int8 (через bitsandbytes или compel) при дефиците VRAM.
  • Обусловливание: простой текст работает; для более строгого контроля добавьте ControlNet (canny, depth, pose) поверх базового пайплайна.

Для пакетной генерации AUTO1111 / ComfyUI — инструменты сообщества; для production API используйте diffusers + accelerate или optimum-nvidia с компиляцией TensorRT.

Поставьте

Этот урок создаёт:

  • outputs/prompt-sd-pipeline-planner.md — промпт, выбирающий SD 1.5 / SDXL / SD3 / FLUX, а также scheduler и точность по бюджету задержки, целевому качеству и лицензионному ограничению.
  • outputs/skill-lora-training-setup.md — навык, записывающий полную конфигурацию обучения LoRA для пользовательского набора данных, включая подписи, ранг, batch size и learning rate.

Упражнения

  1. (Легко) Сгенерируйте один и тот же промпт с guidance_scale в [1, 3, 5, 7.5, 10, 15]. Опишите, как меняется изображение. При каком значении guidance появляются артефакты?
  2. (Средне) Возьмите любую настоящую фотографию, пропустите её через StableDiffusionImg2ImgPipeline со значением strength в [0.2, 0.4, 0.6, 0.8, 1.0]. Какое значение сохраняет композицию, меняя стиль? Почему при 1.0 ввод полностью игнорируется?
  3. (Сложно) Обучите LoRA на 10–20 изображениях одного объекта (питомца, логотипа, персонажа) и сгенерируйте новые сцены с этим объектом. Сообщите ранг LoRA и число шагов обучения, давшие лучшее сохранение идентичности без переобучения на входных изображениях.

Ключевые термины

Термин Как обычно говорят Что это на самом деле означает
Latent diffusion «Диффузия в латентах» Запускает весь DDPM в латентном пространстве VAE (4x64x64), а не в пиксельном пространстве (3x512x512); экономия вычислений в 48 раз
VAE scale factor «0.18215» Константа, масштабирующая необработанный латент VAE примерно до единичной дисперсии; жёстко задана в каждом SD-пайплайне
Classifier-free guidance «CFG» Смешивает условное и безусловное предсказания шума; самый значимый параметр инференса
Scheduler «Sampler» Алгоритм, превращающий шум + предсказания модели в траекторию денойзинга латента
LoRA «Low-rank adapter» Небольшие матрицы рангового разложения, тонко настраивающие слои внимания без изменения базовых весов
Cross-attention «Text-image attention» Внимание от латентных токенов к текстовым токенам; внедряет информацию промпта на каждом уровне U-Net
ControlNet «Structure conditioning» Отдельно обученный адаптер, направляющий SD дополнительным входом (canny, depth, pose, segmentation)
DPM-Solver++ «Стандартный scheduler» Детерминированный ODE-решатель второго порядка; лучшее качество при малом числе шагов (20–30) в 2026 году

Дополнительные материалы


Источник: Stable Diffusion — Architecture & Fine-Tuning 04.10 — Image Generation — Diffusion Models · Фаза 04 — Компьютерное зрение · 04.12 — Video Understanding — Temporal Modeling · Полный каталог