# Понимание конвейеров, моделей и планировщиков

🧨 Diffusers — это удобный и гибкий набор инструментов для создания диффузионных систем под ваши задачи. В основе набора лежат модели и планировщики. Хотя [DiffusionPipeline](/docs/diffusers/main/ru/api/pipelines/overview#diffusers.DiffusionPipeline) объединяет эти компоненты для удобства, вы также можете использовать модели и планировщики по отдельности, чтобы создавать новые диффузионные системы.

В этом руководстве вы узнаете, как использовать модели и планировщики для сборки диффузионной системы для инференса, начиная с базового конвейера и переходя к конвейеру Stable Diffusion.

## Разбор базового конвейера

Пайплайн — это быстрый и простой способ выполнить модель для инференса, для генерации изображения достаточно не более четырёх строк кода:

```py
>>> from diffusers import DDPMPipeline

>>> ddpm = DDPMPipeline.from_pretrained("google/ddpm-cat-256", use_safetensors=True).to("cuda")  # or "mps", "xpu", "cpu"
>>> image = ddpm(num_inference_steps=25).images[0]
>>> image
```

    

Это было очень просто, но как именно pipeline это сделал? Давайте разберём pipeline по частям и посмотрим, что происходит под капотом.

В примере выше пайплайн содержит модель [UNet2DModel](/docs/diffusers/main/ru/api/models/unet2d#diffusers.UNet2DModel) и планировщик [DDPMScheduler](/docs/diffusers/main/ru/api/schedulers/ddpm#diffusers.DDPMScheduler). Пайплайн очищает изображение от шума, беря случайный шум размером с желаемый вывод и пропуская его через модель несколько раз. На каждом шаге модель предсказывает *остаток шума*, а планировщик использует его для предсказания изображения с меньшим уровнем шума. Пайплайн повторяет этот процесс, пока не достигнет конца указанного количества шагов инференса.

Чтобы воссоздать конвейер с моделью и планировщиком по отдельности, напишем собственный процесс удаления шума.

1. Загрузите модель и планировщик:

```py
>>> from diffusers import DDPMScheduler, UNet2DModel

>>> scheduler = DDPMScheduler.from_pretrained("google/ddpm-cat-256")
>>> model = UNet2DModel.from_pretrained("google/ddpm-cat-256", use_safetensors=True).to("cuda")  # or "mps", "xpu", "cpu"
```

2. Укажите количество шагов для выполнения процесса удаления шума:

```py
>>> scheduler.set_timesteps(50)
```

3. Настройка шагов планировщика создаёт тензор с равномерно распределёнными элементами, в данном примере их 50. Каждый элемент соответствует шагу, на котором модель выполняет денуазинг изображения. При создании цикла денуазинга позже вы будете итерировать по этому тензору, чтобы денуазить изображение:

```py
>>> scheduler.timesteps
tensor([980, 960, 940, 920, 900, 880, 860, 840, 820, 800, 780, 760, 740, 720,
    700, 680, 660, 640, 620, 600, 580, 560, 540, 520, 500, 480, 460, 440,
    420, 400, 380, 360, 340, 320, 300, 280, 260, 240, 220, 200, 180, 160,
    140, 120, 100,  80,  60,  40,  20,   0])
```

4. Создайте случайный шум с той же формой, что и у желаемого вывода:

```py
>>> import torch

>>> sample_size = model.config.sample_size
>>> noise = torch.randn((1, 3, sample_size, sample_size), device="cuda")  # or "mps", "xpu", "cpu"
```

5. Теперь напишите цикл для итерации по шагам времени. На каждом шаге времени модель выполняет проход [UNet2DModel.forward()](/docs/diffusers/main/ru/api/models/unet2d#diffusers.UNet2DModel.forward) и возвращает зашумленный остаток. Метод [step()](/docs/diffusers/main/ru/api/schedulers/ddpm#diffusers.DDPMScheduler.step) планировщика принимает зашумленный остаток, шаг времени и входные данные, после чего предсказывает изображение на предыдущем шаге времени. Этот вывод становится следующим входом для модели в цикле удаления шума, и процесс повторяется до тех пор, пока не будет достигнут конец массива `timesteps`.

```py
>>> input = noise

>>> for t in scheduler.timesteps:
...     with torch.no_grad():
...         noisy_residual = model(input, t).sample
...     previous_noisy_sample = scheduler.step(noisy_residual, t, input).prev_sample
...     input = previous_noisy_sample
```

Это и есть весь процесс денойзинга, и вы можете использовать этот же шаблон для создания любой диффузионной системы.

6. Последний шаг — преобразовать результат после шумоподавления в изображение:

```py
>>> from PIL import Image
>>> import numpy as np

>>> image = (input / 2 + 0.5).clamp(0, 1).squeeze()
>>> image = (image.permute(1, 2, 0) * 255).round().to(torch.uint8).cpu().numpy()
>>> image = Image.fromarray(image)
>>> image
```

В следующем разделе вы проверите свои навыки и разберёте более сложный конвейер Stable Diffusion. Шаги в целом те же. Вы инициализируете необходимые компоненты и задаёте количество шагов, чтобы создать массив `timestep`. Массив `timestep` используется в цикле шумоподавления, и для каждого элемента этого массива модель предсказывает изображение с меньшим уровнем шума. Цикл шумоподавления итерируется по значениям `timestep`, и на каждом шаге выводит шумовой остаток, который планировщик использует для предсказания изображения с меньшим уровнем шума на предыдущем шаге. Этот процесс повторяется, пока не будет достигнут конец массива `timestep`.

Попробуем!

## Разбор пайплайна Stable Diffusion

Stable Diffusion — это модель *латентной диффузии* для генерации изображений по текстовому описанию. Она называется моделью латентной диффузии, потому что работает с представлением изображения в пространстве меньшей размерности, а не в фактическом пиксельном пространстве, что делает её более эффективной по памяти. Кодер сжимает изображение до более компактного представления, а декодер восстанавливает из этого сжатого представления исходное изображение. Для моделей генерации изображений по тексту вам понадобятся токенизатор и энкодер для создания текстовых эмбеддингов. Из предыдущего примера вы уже знаете, что вам нужны модель UNet и планировщик.

Как вы видите, это уже сложнее, чем конвейер DDPM, содержащий только модель UNet. Модель Stable Diffusion включает три отдельных предобученные модели.

> [!TIP]
> 💡 Подробнее о том, как работают модели VAE, UNet и текстового энкодера, читайте в блоге [How does Stable Diffusion work?](https://huggingface.co/blog/stable_diffusion#how-does-stable-diffusion-work).

Теперь, когда вы знаете, какие компоненты необходимы для конвейера Stable Diffusion, загрузите их все с помощью метода [from_pretrained()](/docs/diffusers/main/ru/api/models/overview#diffusers.ModelMixin.from_pretrained). Вы можете найти их в предобученном чекпоинте [`stable-diffusion-v1-5/stable-diffusion-v1-5`](https://huggingface.co/stable-diffusion-v1-5/stable-diffusion-v1-5), при этом каждый компонент хранится в отдельной подпапке:

```py
>>> from PIL import Image
>>> import torch
>>> from transformers import CLIPTextModel, CLIPTokenizer
>>> from diffusers import AutoencoderKL, UNet2DConditionModel, PNDMScheduler

>>> vae = AutoencoderKL.from_pretrained("CompVis/stable-diffusion-v1-4", subfolder="vae", use_safetensors=True)
>>> tokenizer = CLIPTokenizer.from_pretrained("CompVis/stable-diffusion-v1-4", subfolder="tokenizer")
>>> text_encoder = CLIPTextModel.from_pretrained(
...     "CompVis/stable-diffusion-v1-4", subfolder="text_encoder", use_safetensors=True
... )
>>> unet = UNet2DConditionModel.from_pretrained(
...     "CompVis/stable-diffusion-v1-4", subfolder="unet", use_safetensors=True
... )
```

Вместо стандартного [PNDMScheduler](/docs/diffusers/main/ru/api/schedulers/pndm#diffusers.PNDMScheduler) замените его на [UniPCMultistepScheduler](/docs/diffusers/main/ru/api/schedulers/unipc#diffusers.UniPCMultistepScheduler), чтобы увидеть, как легко подключить другой планировщик:

```py
>>> from diffusers import UniPCMultistepScheduler

>>> scheduler = UniPCMultistepScheduler.from_pretrained("CompVis/stable-diffusion-v1-4", subfolder="scheduler")
```

Чтобы ускорить инференс, переместите модели на GPU, поскольку, в отличие от планировщика, у них есть обучаемые веса:

```py
>>> torch_device = "cuda"  # or "mps", "xpu", "cpu"
>>> vae.to(torch_device)
>>> text_encoder.to(torch_device)
>>> unet.to(torch_device)
```

### Создание текстовых эмбеддингов

Следующий шаг — токенизация текста для генерации эмбеддингов. Текст используется для настройки модели UNet и управления процессом диффузии в направлении, напоминающем входной промпт.

> [!TIP]
> 💡 Параметр `guidance_scale` определяет, какой вес следует придавать промпту при генерации изображения.

Вы можете использовать любой промпт, если хотите сгенерировать что-то другое!

```py
>>> prompt = ["a photograph of an astronaut riding a horse"]
>>> height = 512  # default height of Stable Diffusion
>>> width = 512  # default width of Stable Diffusion
>>> num_inference_steps = 25  # Number of denoising steps
>>> guidance_scale = 7.5  # Scale for classifier-free guidance
>>> generator = torch.manual_seed(0)  # Seed generator to create the initial latent noise
>>> batch_size = len(prompt)
```

Токенизируйте текст и сгенерируйте эмбеддинги из промпта:

```py
>>> text_input = tokenizer(
...     prompt, padding="max_length", max_length=tokenizer.model_max_length, truncation=True, return_tensors="pt"
... )

>>> with torch.no_grad():
...     text_embeddings = text_encoder(text_input.input_ids.to(torch_device))[0]
```

Вам также потребуется сгенерировать *неусловные текстовые эмбеддинги*, которые являются эмбеддингами для токена отсечки. Они должны иметь ту же форму (`batch_size` и `seq_length`), что и условные `text_embeddings`:

```py
>>> max_length = text_input.input_ids.shape[-1]
>>> uncond_input = tokenizer([""] * batch_size, padding="max_length", max_length=max_length, return_tensors="pt")
>>> uncond_embeddings = text_encoder(uncond_input.input_ids.to(torch_device))[0]
```

Соберём условные и безусловные эмбеддинги в один батч, чтобы избежать двух проходов вперёд:

```py
>>> text_embeddings = torch.cat([uncond_embeddings, text_embeddings])
```

### Создание случайного шума

Далее сгенерируйте некоторый начальный случайный шум в качестве отправной точки для процесса диффузии. Это латентное представление изображения, которое будет постепенно очищаться от шума. На этом этапе изображение `latent` имеет меньший размер, чем итоговое изображение, но это не проблема, поскольку модель впоследствии преобразует его в итоговые размеры 512x512.

> [!TIP]
> 💡 Высота и ширина делятся на 8, так как модель `vae` содержит 3 слоя понижающей дискретизации. Вы можете проверить это, выполнив следующий код:
>
> ```py
> 2 ** (len(vae.config.block_out_channels) - 1) == 8
> ```

```py
>>> latents = torch.randn(
...     (batch_size, unet.config.in_channels, height // 8, width // 8),
...     generator=generator,
...     device=torch_device,
... )
```

### Удаление шума с изображения

Начните с масштабирования входных данных с помощью начального распределения шума, *sigma*, значения масштаба шума, которое требуется для улучшенных планировщиков, таких как [UniPCMultistepScheduler](/docs/diffusers/main/ru/api/schedulers/unipc#diffusers.UniPCMultistepScheduler):

```py
>>> latents = latents * scheduler.init_noise_sigma
```

Последний шаг — создать цикл шумоподавления, который будет постепенно превращать чистый шум в `latents` в изображение, описанное вашим промптом. Помните, что цикл шумоподавления должен выполнять три действия:

1. Установите шаги планировщика, используемые при денойзинге.
2. Итерируйте по временным шагам.
3. На каждом шаге вызовите модель UNet для предсказания остатка шума и передайте его планировщику для вычисления предыдущего зашумленного сэмпла.

```py
>>> from tqdm.auto import tqdm

>>> scheduler.set_timesteps(num_inference_steps)

>>> for t in tqdm(scheduler.timesteps):
...     # expand the latents if we are doing classifier-free guidance to avoid doing two forward passes.
...     latent_model_input = torch.cat([latents] * 2)

...     latent_model_input = scheduler.scale_model_input(latent_model_input, timestep=t)

...     # predict the noise residual
...     with torch.no_grad():
...         noise_pred = unet(latent_model_input, t, encoder_hidden_states=text_embeddings).sample

...     # perform guidance
...     noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
...     noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)

...     # compute the previous noisy sample x_t -> x_t-1
...     latents = scheduler.step(noise_pred, t, latents).prev_sample
```

### Декодирование изображения

Последний шаг — использовать `vae` для декодирования латентного представления в изображение и получить декодированный результат с помощью `sample`:

```py
# scale and decode the image latents with vae
latents = 1 / 0.18215 * latents
with torch.no_grad():
    image = vae.decode(latents).sample
```

Наконец, преобразуйте изображение в `PIL.Image`, чтобы увидеть сгенерированное изображение!

```py
>>> image = (image / 2 + 0.5).clamp(0, 1).squeeze()
>>> image = (image.permute(1, 2, 0) * 255).to(torch.uint8).cpu().numpy()
>>> image = Image.fromarray(image)
>>> image
```

    

## Дальнейшие шаги

От простых до сложных пайплайнов вы увидели, что для создания собственной диффузионной системы на самом деле нужен лишь цикл денойзинга. Цикл должен задавать временные шаги планировщика, итерировать по ним и чередовать вызовы модели UNet для предсказания шумового остатка и передачу этого остатка планировщику для вычисления предыдущего зашумлённого сэмпла.

Именно для этого и создан 🧨 Diffusers: чтобы интуитивно и легко создавать собственные диффузионные системы с использованием моделей и планировщиков.

Для следующих шагов вы можете:

* Узнайте, как [создать и внести вклад в пайплайн](../conceptual/contribution) для 🧨 Diffusers. Мы с нетерпением ждём, что вы придумаете!
* Изучите [существующие конвейеры](../api/pipelines/overview) в библиотеке и попробуйте разобрать их на части и собрать конвейер с нуля, используя модели и планировщики по отдельности.

