Diffusers 文档
可复现的 pipelines
并获得增强的文档体验
开始使用
可复现的 pipelines
Diffusion 模型本质上是随机的,这使得它每次运行时都能生成不同的输出。但在某些情况下,您希望每次生成相同的输出,例如当您进行测试、复现结果,甚至提高图像质量时。虽然您不能期望在不同平台上获得完全相同的结果,但您可以期望在特定容差范围内,跨版本和平台获得可复现的结果(尽管即使这样也可能有所不同)。
本指南将向您展示如何在 CPU 和 GPU 上控制随机性以实现确定性生成。
我们强烈建议阅读 PyTorch 关于可复现性的声明
“不能保证在 PyTorch 版本、各个提交或不同平台之间获得完全可复现的结果。此外,即使使用相同的种子,CPU 和 GPU 执行之间的结果也可能不可复现。”
控制随机性
在推理过程中,pipelines 严重依赖随机采样操作,包括创建用于去噪的高斯噪声张量以及向调度步骤添加噪声。
查看两个推理步骤后 DDIMPipeline 中的张量值。
from diffusers import DDIMPipeline
import numpy as np
ddim = DDIMPipeline.from_pretrained( "google/ddpm-cifar10-32", use_safetensors=True)
image = ddim(num_inference_steps=2, output_type="np").images
print(np.abs(image).sum())
运行上面的代码会打印一个值,但如果您再次运行它,您将得到不同的值。
每次运行 pipeline 时,torch.randn 都会使用不同的随机种子来创建高斯噪声张量。这导致每次运行时结果都不同,并使 diffusion pipeline 每次都能生成不同的随机图像。
但是,如果您需要可靠地生成相同的图像,则取决于您是在 CPU 还是 GPU 上运行 pipeline。
将 Generator
对象传递给 pipeline 而不是表示种子的整数值,这似乎违反直觉。但是,当在 PyTorch 中使用概率模型时,这是推荐的设计,因为 Generator
是一种随机状态,可以按顺序传递给多个 pipelines。一旦 Generator
被消耗,状态就会就地更改,这意味着即使您将相同的 Generator
传递给不同的 pipeline,它也不会产生相同的结果,因为状态已经更改。
要在 CPU 上生成可复现的结果,您需要使用 PyTorch Generator 并设置种子。现在,当您运行代码时,它始终打印值 1491.1711
,因为带有种子的 Generator
对象会传递给 pipeline 中的所有随机函数。在您使用的任何硬件和 PyTorch 版本上,您都应该获得相似(如果不是相同)的结果。
import torch
import numpy as np
from diffusers import DDIMPipeline
ddim = DDIMPipeline.from_pretrained("google/ddpm-cifar10-32", use_safetensors=True)
generator = torch.Generator(device="cpu").manual_seed(0)
image = ddim(num_inference_steps=2, output_type="np", generator=generator).images
print(np.abs(image).sum())
确定性算法
您还可以配置 PyTorch 以使用确定性算法来创建可复现的 pipeline。缺点是确定性算法可能比非确定性算法慢,并且您可能会观察到性能下降。
当操作在多个 CUDA 流中启动时,会发生非确定性行为。为了避免这种情况,请将环境变量 CUBLAS_WORKSPACE_CONFIG 设置为 :16:8
,以便在运行时仅使用一个缓冲区大小。
PyTorch 通常会基准测试多种算法以选择最快的算法,但如果您想要可复现性,则应禁用此功能,因为基准测试每次可能会选择不同的算法。设置 Diffusers enable_full_determinism 以启用确定性算法。
enable_full_determinism()
现在,当您两次运行相同的 pipeline 时,您将获得相同的结果。
import torch
from diffusers import DDIMScheduler, StableDiffusionPipeline
pipe = StableDiffusionPipeline.from_pretrained("stable-diffusion-v1-5/stable-diffusion-v1-5", use_safetensors=True).to("cuda")
pipe.scheduler = DDIMScheduler.from_config(pipe.scheduler.config)
g = torch.Generator(device="cuda")
prompt = "A bear is playing a guitar on Times Square"
g.manual_seed(0)
result1 = pipe(prompt=prompt, num_inference_steps=50, generator=g, output_type="latent").images
g.manual_seed(0)
result2 = pipe(prompt=prompt, num_inference_steps=50, generator=g, output_type="latent").images
print("L_inf dist =", abs(result1 - result2).max())
"L_inf dist = tensor(0., device='cuda:0')"
确定性批量生成
创建可复现 pipelines 的一个实际应用是确定性批量生成。您生成一批图像,并选择一张图像以使用更详细的 prompt 进行改进。主要思想是将 Generator
列表传递给 pipeline,并将每个 Generator
绑定到一个种子,以便您可以重复使用它。
让我们使用 stable-diffusion-v1-5/stable-diffusion-v1-5 checkpoint 并生成一批图像。
import torch
from diffusers import DiffusionPipeline
from diffusers.utils import make_image_grid
pipeline = DiffusionPipeline.from_pretrained(
"stable-diffusion-v1-5/stable-diffusion-v1-5", torch_dtype=torch.float16, use_safetensors=True
)
pipeline = pipeline.to("cuda")
定义四个不同的 Generator
,并为每个 Generator
分配一个种子(0
到 3
)。然后生成一批图像,并选择一个进行迭代。
使用列表推导式,遍历 range()
中指定的批量大小,为批次中的每个图像创建一个唯一的 Generator
对象。如果将 Generator
乘以批量大小整数,则只会创建一个 Generator
对象,该对象按顺序用于批次中的每个图像。
[torch.Generator().manual_seed(seed)] * 4
generator = [torch.Generator(device="cuda").manual_seed(i) for i in range(4)]
prompt = "Labrador in the style of Vermeer"
images = pipeline(prompt, generator=generator, num_images_per_prompt=4).images[0]
make_image_grid(images, rows=2, cols=2)

让我们改进第一张图像(您可以选择任何您想要的图像),它对应于种子为 0
的 Generator
。向您的 prompt 添加一些额外的文本,然后确保您重复使用种子为 0
的相同 Generator
。所有生成的图像都应类似于第一张图像。
prompt = [prompt + t for t in [", highly realistic", ", artsy", ", trending", ", colorful"]]
generator = [torch.Generator(device="cuda").manual_seed(0) for i in range(4)]
images = pipeline(prompt, generator=generator).images
make_image_grid(images, rows=2, cols=2)
