在免费版 Google Colab 上用 🧨 diffusers 运行 IF

发布于 2023 年 4 月 26 日
在 GitHub 上更新
Open In Colab

摘要:我们展示了如何使用 🧨 diffusers 在免费版的 Google Colab 上运行最强大的开源文本到图像模型之一 IF

您也可以直接在 Hugging Face Space 中探索该模型的功能。

if-collage
图片压缩自官方 IF GitHub 仓库

简介

IF 是一种基于像素的文本到图像生成模型,由 DeepFloyd 于 2023 年 4 月下旬发布。该模型架构深受 Google 闭源模型 Imagen 的启发。

与现有的文本到图像模型(如 Stable Diffusion)相比,IF 有两个明显的优势

  • 该模型直接在“像素空间”(即未压缩的图像)上操作,而不是像 Stable Diffusion 那样在潜空间中运行去噪过程。
  • 该模型在 T5-XXL 的输出上进行训练,这是一个比 Stable Diffusion 用作文本编码器的 CLIP 更强大的文本编码器。

因此,IF 更擅长生成具有高频细节(例如人脸和手)的图像,并且是第一个能够可靠地生成带文本图像的开源图像生成模型。

在像素空间操作和使用更强大的文本编码器的缺点是 IF 的参数数量明显更多。T5、IF 的文本到图像 UNet 和 IF 的上采样器 UNet 分别具有 45 亿、43 亿和 12 亿个参数。相比之下,Stable Diffusion 2.1 的文本编码器和 UNet 分别只有 4 亿和 9 亿个参数。

尽管如此,如果针对低内存使用对模型进行优化,仍然可以在消费级硬件上运行 IF。在这篇博文中,我们将向您展示如何使用 🧨 diffusers 来实现这一点。

在第一部分中,我们解释了如何使用 IF 进行文本到图像的生成,在第二和第三部分中,我们介绍了 IF 的图像变体和图像修复功能。

💡 注意:我们在这里通过牺牲速度来换取内存的提升,以便能够在免费版的 Google Colab 中运行 IF。如果您有 A100 等高端 GPU,我们建议将所有模型组件都保留在 GPU 上以获得最大速度,就像官方 IF 演示中那样。

💡 注意:一些较大的图像已被压缩,以便在博客格式中加载得更快。使用官方模型时,它们的质量应该会更好!

让我们开始吧 🚀!


IF 的文本生成能力

目录

接受许可证

在使用 IF 之前,您需要接受其使用条件。为此:

    1. 确保拥有一个 Hugging Face 帐户并已登录
    1. DeepFloyd/IF-I-XL-v1.0 的模型卡上接受许可证。在第一阶段模型卡上接受许可证将自动为其他 IF 模型接受。
    1. 确保在本地登录。安装 huggingface_hub
pip install huggingface_hub --upgrade

在 Python shell 中运行登录函数

from huggingface_hub import login

login()

并输入您的 Hugging Face Hub 访问令牌

优化 IF 以在内存受限的硬件上运行

最先进的机器学习不应该只掌握在少数精英手中。机器学习的民主化意味着让模型能够在不仅仅是最先进的硬件上运行。

深度学习社区已经创造了世界一流的工具,用于在消费级硬件上运行资源密集型模型

Diffusers 无缝集成了上述库,以便在优化大型模型时提供简单的 API。

免费版的 Google Colab 在 CPU RAM(13 GB RAM)和 GPU VRAM(T4 为 15 GB RAM)方面都受到限制,这使得运行整个超过 10B 参数的 IF 模型具有挑战性!

让我们来计算一下 IF 模型组件在全 float32 精度下的大小

我们无法在 float32 精度下运行该模型,因为 T5 和阶段 1 UNet 的权重都比可用的 CPU RAM 大。

在 float16 精度下,T5、阶段1 和阶段2 UNet 的组件大小分别为 11GB、8.6GB 和 1.25GB,这对于 GPU 来说是可行的,但我们在加载 T5 时仍然会遇到 CPU 内存溢出错误(部分 CPU 被其他进程占用)。

因此,我们通过使用 bitsandbytes 8位量化进一步降低 T5 的精度,这使得 T5 检查点可以仅用 8 GB 的空间来保存。

既然每个组件都能单独装入 CPU 和 GPU 内存,我们需要确保在需要时,组件能独占所有的 CPU 和 GPU 内存。

Diffusers 支持模块化加载单个组件,即我们可以加载文本编码器而不加载 UNet。这种模块化加载将确保我们只在管道的特定步骤中加载我们需要的组件,以避免耗尽可用的 CPU RAM 和 GPU VRAM。

让我们试一试 🚀

t2i_64

可用资源

免费版的 Google Colab 提供了大约 13 GB 的 CPU RAM

!grep MemTotal /proc/meminfo
MemTotal:       13297192 kB

以及一个带有 15 GB VRAM 的 NVIDIA T4

!nvidia-smi
Sun Apr 23 23:14:19 2023       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 525.85.12    Driver Version: 525.85.12    CUDA Version: 12.0     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  Tesla T4            Off  | 00000000:00:04.0 Off |                    0 |
| N/A   72C    P0    32W /  70W |   1335MiB / 15360MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
                                                                                
+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
+-----------------------------------------------------------------------------+

安装依赖项

一些优化可能需要最新版本的依赖项。如果您遇到问题,请仔细检查并升级版本。

! pip install --upgrade \
  diffusers~=0.16 \
  transformers~=4.28 \
  safetensors~=0.3 \
  sentencepiece~=0.1 \
  accelerate~=0.18 \
  bitsandbytes~=0.38 \
  torch~=2.0 -q

1. 文本到图像生成

我们将逐步介绍如何使用 Diffusers 通过 IF 进行文本到图像的生成。我们将简要解释 API 和优化,但更深入的解释可以在 DiffusersTransformersAcceleratebitsandbytes 的官方文档中找到。

1.1 加载文本编码器

我们将使用 8 位量化加载 T5。Transformers 直接通过 load_in_8bit 标志支持 bitsandbytes

标志 variant="8bit" 将下载预量化的权重。

我们还使用 device_map 标志,以允许 transformers 将模型层卸载到 CPU 或磁盘。Transformers 的大型模型支持任意设备映射,可用于将模型参数直接加载到可用设备上。传递 "auto" 将自动创建设备映射。有关更多信息,请参见 transformers 文档

from transformers import T5EncoderModel

text_encoder = T5EncoderModel.from_pretrained(
    "DeepFloyd/IF-I-XL-v1.0",
    subfolder="text_encoder", 
    device_map="auto", 
    load_in_8bit=True, 
    variant="8bit"
)

1.2 创建文本嵌入

用于访问扩散模型的 Diffusers API 是 DiffusionPipeline 类及其子类。DiffusionPipeline 的每个实例都是一个完全自包含的方法和模型集合,用于运行扩散网络。我们可以通过将替代实例作为关键字参数传递给 from_pretrained 来覆盖它使用的模型。

在这种情况下,我们为 unet 参数传递 None,因此不会加载 UNet。这使我们能够在不将 UNet 加载到内存中的情况下运行扩散过程的文本嵌入部分。

from diffusers import DiffusionPipeline

pipe = DiffusionPipeline.from_pretrained(
    "DeepFloyd/IF-I-XL-v1.0", 
    text_encoder=text_encoder, # pass the previously instantiated 8bit text encoder
    unet=None, 
    device_map="auto"
)

IF 还带有一个超分辨率管道。我们将保存提示嵌入,以便稍后可以直接将它们传递给超分辨率管道。这将允许超分辨率管道在没有文本编码器的情况下加载。

不仅仅是宇航员骑马,我们还给他们一个牌子吧!

让我们定义一个合适的提示

prompt = "a photograph of an astronaut riding a horse holding a sign that says Pixel's in space"

并通过 8 位量化的 T5 模型运行它

prompt_embeds, negative_embeds = pipe.encode_prompt(prompt)

1.3 释放内存

一旦创建了提示嵌入,我们就不再需要文本编码器了。但是,它仍然在 GPU 的内存中。我们需要移除它,以便加载 UNet。

释放 PyTorch 内存并非易事。我们必须垃圾回收指向 GPU 上实际分配内存的 Python 对象。

首先,使用 Python 关键字 del 删除所有引用已分配 GPU 内存的 Python 对象

del text_encoder
del pipe

删除 python 对象不足以释放 GPU 内存。垃圾回收才是真正释放 GPU 内存的时候。

此外,我们将调用 torch.cuda.empty_cache()。这个方法并非严格必要,因为缓存的 cuda 内存将立即可用于进一步的分配。清空缓存可以让我们在 Colab UI 中验证内存是否可用。

我们将使用一个辅助函数 flush() 来刷新内存。

import gc
import torch

def flush():
    gc.collect()
    torch.cuda.empty_cache()

然后运行它

flush()

1.4 阶段 1:主扩散过程

利用我们现在可用的 GPU 内存,我们可以重新加载只包含 UNet 的 DiffusionPipeline 来运行主扩散过程。

varianttorch_dtype 标志由 Diffusers 用于以 16 位浮点格式下载和加载权重。

pipe = DiffusionPipeline.from_pretrained(
    "DeepFloyd/IF-I-XL-v1.0", 
    text_encoder=None, 
    variant="fp16", 
    torch_dtype=torch.float16, 
    device_map="auto"
)

通常,我们直接将文本提示传递给 DiffusionPipeline.__call__。然而,我们之前计算了文本嵌入,我们可以将其传递进去。

IF 还带有一个超分辨率扩散过程。设置 output_type="pt" 将返回原始的 PyTorch 张量而不是 PIL 图像。这样,我们可以将 PyTorch 张量保留在 GPU 上,并直接将它们传递给阶段 2 的超分辨率管道。

让我们定义一个随机生成器并运行阶段 1 扩散过程。

generator = torch.Generator().manual_seed(1)
image = pipe(
    prompt_embeds=prompt_embeds,
    negative_prompt_embeds=negative_embeds, 
    output_type="pt",
    generator=generator,
).images

让我们手动将原始张量转换为 PIL 图像,并先睹为快最终结果。阶段 1 的输出是一张 64x64 的图像。

from diffusers.utils import pt_to_pil

pil_image = pt_to_pil(image)
pipe.watermarker.apply_watermark(pil_image, pipe.unet.config.sample_size)

pil_image[0]

t2i_64

再次,我们移除 Python 指针并释放 CPU 和 GPU 内存

del pipe
flush()

1.5 阶段 2:超分辨率 64x64 到 256x256

IF 带有一个用于上采样的独立扩散过程。

我们用一个独立的管道运行每个扩散过程。

超分辨率管道可以在需要时加载文本编码器。然而,我们通常会从第一个 IF 管道中预先计算好文本嵌入。如果是这样,加载管道时不需要文本编码器。

创建管道

pipe = DiffusionPipeline.from_pretrained(
    "DeepFloyd/IF-II-L-v1.0", 
    text_encoder=None, # no use of text encoder => memory savings!
    variant="fp16", 
    torch_dtype=torch.float16, 
    device_map="auto"
)

然后运行它,重用预先计算的文本嵌入

image = pipe(
    image=image, 
    prompt_embeds=prompt_embeds, 
    negative_prompt_embeds=negative_embeds, 
    output_type="pt",
    generator=generator,
).images

我们再次可以检查中间结果。

pil_image = pt_to_pil(image)
pipe.watermarker.apply_watermark(pil_image, pipe.unet.config.sample_size)

pil_image[0]

t2i_upscaled

再次,我们删除 Python 指针并释放内存

del pipe
flush()

1.6 阶段 3:超分辨率 256x256 到 1024x1024

IF 的第二个超分辨率模型是之前发布的 Stability AI 的 x4 上采样器

让我们创建管道并使用 device_map="auto" 直接加载到 GPU 上。

pipe = DiffusionPipeline.from_pretrained(
    "stabilityai/stable-diffusion-x4-upscaler", 
    torch_dtype=torch.float16, 
    device_map="auto"
)

🧨 diffusers 使独立开发的扩散模型可以轻松组合,因为管道可以链接在一起。这里我们可以直接取前一个 PyTorch 张量输出并将其作为 image=image 传递给阶段 3 管道。

💡 注意:x4 上采样器不使用 T5,并且有自己的文本编码器。因此,我们不能使用之前创建的提示嵌入,而必须传递原始提示。

pil_image = pipe(prompt, generator=generator, image=image).images

与 IF 管道不同,Stable Diffusion x4 上采样器管道的输出默认不会添加 IF 水印。

我们可以手动应用水印。

from diffusers.pipelines.deepfloyd_if import IFWatermarker

watermarker = IFWatermarker.from_pretrained("DeepFloyd/IF-I-XL-v1.0", subfolder="watermarker")
watermarker.apply_watermark(pil_image, pipe.unet.config.sample_size)

查看输出图像

pil_image[0]

t2i_upscaled_2

瞧!在免费版的 Google Colab 中生成了一张漂亮的 1024x1024 图像。

我们已经展示了 🧨 diffusers 如何轻松分解和模块化加载资源密集型的扩散模型。

💡 注意:我们不建议在生产环境中使用上述设置。8位量化、手动释放模型权重和磁盘卸载都以牺牲时间(即推理速度)为代价来换取内存。如果扩散管道被重复使用,这一点尤其明显。在生产环境中,我们建议使用一块 40GB 的 A100,并将所有模型组件都保留在 GPU 上。请参阅官方 IF 演示

2. 图像变体

相同的 IF 检查点也可用于文本引导的图像变体和图像修复。核心扩散过程与文本到图像生成相同,只是初始的噪声图像是根据要变化或修复的图像创建的。

要运行图像变体,请使用 IFImg2ImgPipeline.from_pretrained()IFImg2ImgSuperResolution.from_pretrained() 加载相同的检查点。

用于内存优化的 API 都是相同的!

让我们释放上一节的内存。

del pipe
flush()

对于图像变体,我们从一张我们想要调整的初始图像开始。

在这一节中,我们将改编著名的“拍车顶”表情包。让我们从网上下载它。

import requests

url = "https://i.kym-cdn.com/entries/icons/original/000/026/561/car.jpg"
response = requests.get(url)

并将其加载到 PIL 图像中

from PIL import Image
from io import BytesIO

original_image = Image.open(BytesIO(response.content)).convert("RGB")
original_image = original_image.resize((768, 512))
original_image

iv_sample

图像变体管道既接受 PIL 图像也接受原始张量。有关预期输入的更深入文档,请查看此处的文档字符串。

2.1 文本编码器

图像变体由文本引导,因此我们可以定义一个提示并用 T5 的文本编码器对其进行编码。

我们再次将文本编码器加载为 8 位精度。

from transformers import T5EncoderModel

text_encoder = T5EncoderModel.from_pretrained(
    "DeepFloyd/IF-I-XL-v1.0",
    subfolder="text_encoder", 
    device_map="auto", 
    load_in_8bit=True, 
    variant="8bit"
)

对于图像变体,我们使用 IFImg2ImgPipeline 加载检查点。当使用 DiffusionPipeline.from_pretrained(...) 时,检查点会加载到它们的默认管道中。IF 的默认管道是文本到图像的 IFPipeline。当使用非默认管道加载检查点时,必须显式指定管道。

from diffusers import IFImg2ImgPipeline

pipe = IFImg2ImgPipeline.from_pretrained(
    "DeepFloyd/IF-I-XL-v1.0", 
    text_encoder=text_encoder, 
    unet=None, 
    device_map="auto"
)

让我们把我们的销售员变成一个动漫角色。

prompt = "anime style"

和之前一样,我们用 T5 创建文本嵌入

prompt_embeds, negative_embeds = pipe.encode_prompt(prompt)

并释放 GPU 和 CPU 内存。

首先,移除 Python 指针

del text_encoder
del pipe

然后释放内存

flush()

2.2 阶段 1:主扩散过程

接下来,我们只将阶段 1 的 UNet 权重加载到管道对象中,就像我们在上一节中所做的那样。

pipe = IFImg2ImgPipeline.from_pretrained(
    "DeepFloyd/IF-I-XL-v1.0", 
    text_encoder=None, 
    variant="fp16", 
    torch_dtype=torch.float16, 
    device_map="auto"
)

图像变体管道需要原始图像和提示嵌入。

我们可以选择使用 strength 参数来配置变体的程度。strength 直接控制添加的噪声量。更高的强度意味着更多的噪声,也意味着更多的变体。

generator = torch.Generator().manual_seed(0)
image = pipe(
    image=original_image,
    prompt_embeds=prompt_embeds,
    negative_prompt_embeds=negative_embeds, 
    output_type="pt",
    generator=generator,
).images

让我们再次检查一下中间的 64x64 图像。

pil_image = pt_to_pil(image)
pipe.watermarker.apply_watermark(pil_image, pipe.unet.config.sample_size)

pil_image[0]

iv_sample_1

看起来不错!我们可以释放内存并再次上采样图像了。

del pipe
flush()

2.3 阶段 2:超分辨率

对于超分辨率,使用 IFImg2ImgSuperResolutionPipeline 和与之前相同的检查点加载。

from diffusers import IFImg2ImgSuperResolutionPipeline

pipe = IFImg2ImgSuperResolutionPipeline.from_pretrained(
    "DeepFloyd/IF-II-L-v1.0", 
    text_encoder=None, 
    variant="fp16", 
    torch_dtype=torch.float16, 
    device_map="auto"
)

💡 注意:图像变体超分辨率管道需要生成的图像以及原始图像。

你也可以在这张图片上使用 Stable Diffusion x4 上采样器。欢迎使用 1.6 节中的代码片段进行尝试。

image = pipe(
    image=image,
    original_image=original_image,
    prompt_embeds=prompt_embeds,
    negative_prompt_embeds=negative_embeds, 
    generator=generator,
).images[0]
image

iv_sample_2

不错!让我们释放内存,看看最后的图像修复管道。

del pipe
flush()

3. 图像修复

IF 修复管道与图像变体相同,只是只对图像的选定区域进行去噪。

我们用图像掩码指定要修复的区域。

让我们展示一下 IF 惊人的“文字生成”能力。我们可以用不同的口号替换这个牌子上的文字。

首先让我们下载图像

import requests

url = "https://i.imgflip.com/5j6x75.jpg"
response = requests.get(url)

并将其转换为 PIL 图像

from PIL import Image
from io import BytesIO

original_image = Image.open(BytesIO(response.content)).convert("RGB")
original_image = original_image.resize((512, 768))
original_image

inpainting_sample

我们将遮盖标志,以便替换其文本。

为方便起见,我们已预先生成掩码并将其加载到 HF 数据集中。

让我们下载它。

from huggingface_hub import hf_hub_download

mask_image = hf_hub_download("diffusers/docs-images", repo_type="dataset", filename="if/sign_man_mask.png")
mask_image = Image.open(mask_image)

mask_image

masking_sample

💡 注意:您可以通过手动创建灰度图像来自己创建掩码。

from PIL import Image
import numpy as np

height = 64
width = 64

example_mask = np.zeros((height, width), dtype=np.int8)

# Set masked pixels to 255
example_mask[20:30, 30:40] = 255

# Make sure to create the image in mode 'L'
# meaning single channel grayscale
example_mask = Image.fromarray(example_mask, mode='L')

example_mask

masking_by_hand

现在我们可以开始修复了 🎨🖌

3.1. 文本编码器

再次,我们首先加载文本编码器

from transformers import T5EncoderModel

text_encoder = T5EncoderModel.from_pretrained(
    "DeepFloyd/IF-I-XL-v1.0",
    subfolder="text_encoder", 
    device_map="auto", 
    load_in_8bit=True, 
    variant="8bit"
)

这一次,我们使用文本编码器权重初始化 IFInpaintingPipeline 修复管道。

from diffusers import IFInpaintingPipeline

pipe = IFInpaintingPipeline.from_pretrained(
    "DeepFloyd/IF-I-XL-v1.0", 
    text_encoder=text_encoder, 
    unet=None, 
    device_map="auto"
)

好吧,让我们让这个人转而为更多的层做广告。

prompt = 'the text, "just stack more layers"'

定义了提示后,我们可以创建提示嵌入

prompt_embeds, negative_embeds = pipe.encode_prompt(prompt)

就像之前一样,我们释放内存

del text_encoder
del pipe
flush()

3.2 阶段 1:主扩散过程

和之前一样,我们现在只加载 UNet 来加载阶段 1 管道。

pipe = IFInpaintingPipeline.from_pretrained(
    "DeepFloyd/IF-I-XL-v1.0", 
    text_encoder=None, 
    variant="fp16", 
    torch_dtype=torch.float16, 
    device_map="auto"
)

现在,我们需要传递输入图像、掩码图像和提示嵌入。

image = pipe(
    image=original_image,
    mask_image=mask_image,
    prompt_embeds=prompt_embeds,
    negative_prompt_embeds=negative_embeds, 
    output_type="pt",
    generator=generator,
).images

让我们看一下中间输出。

pil_image = pt_to_pil(image)
pipe.watermarker.apply_watermark(pil_image, pipe.unet.config.sample_size)

pil_image[0]

inpainted_output

看起来不错!文字非常连贯!

让我们释放内存,以便上采样图像

del pipe
flush()

3.3 阶段 2:超分辨率

对于超分辨率,使用 IFInpaintingSuperResolutionPipeline 加载检查点。

from diffusers import IFInpaintingSuperResolutionPipeline

pipe = IFInpaintingSuperResolutionPipeline.from_pretrained(
    "DeepFloyd/IF-II-L-v1.0", 
    text_encoder=None, 
    variant="fp16", 
    torch_dtype=torch.float16, 
    device_map="auto"
)

修复超分辨率管道需要生成的图像、原始图像、掩码图像和提示嵌入。

让我们进行最后一次去噪运行。

image = pipe(
    image=image,
    original_image=original_image,
    mask_image=mask_image,
    prompt_embeds=prompt_embeds,
    negative_prompt_embeds=negative_embeds, 
    generator=generator,
).images[0]
image

inpainted_final_output

太棒了,模型生成的文本没有一个拼写错误!

结论

IF 在 32 位浮点精度下总共使用 40 GB 的权重。我们展示了如何仅使用开源模型和库,在免费版的 Google Colab 实例上运行 IF。

ML 生态系统从开放工具和开放模型的共享中获益匪浅。仅此笔记本就使用了来自 DeepFloyd、StabilityAI 和 Google 的模型。所使用的库——Diffusers、Transformers、Accelerate 和 bitsandbytes——都受益于来自不同组织的无数贡献者。

非常感谢 DeepFloyd 团队创建并开源 IF,并为推动优秀机器学习的民主化做出贡献 🤗。

社区

注册登录 发表评论