Buckets:
| # Понимание конвейеров, моделей и планировщиков | |
| 🧨 Diffusers — это удобный и гибкий набор инструментов для создания диффузионных систем под ваши задачи. В основе набора лежат модели и планировщики. Хотя [DiffusionPipeline](/docs/diffusers/v0.43.0/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/v0.43.0/ru/api/models/unet2d#diffusers.UNet2DModel) и планировщик [DDPMScheduler](/docs/diffusers/v0.43.0/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/v0.43.0/ru/api/models/unet2d#diffusers.UNet2DModel.forward) и возвращает зашумленный остаток. Метод [step()](/docs/diffusers/v0.43.0/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/v0.43.0/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/v0.43.0/ru/api/schedulers/pndm#diffusers.PNDMScheduler) замените его на [UniPCMultistepScheduler](/docs/diffusers/v0.43.0/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/v0.43.0/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) в библиотеке и попробуйте разобрать их на части и собрать конвейер с нуля, используя модели и планировщики по отдельности. | |
Xet Storage Details
- Size:
- 19.8 kB
- Xet hash:
- 4a66239727d36b822a906f1726a8722b5eec6e96b6019369435618811c6950c5
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.