Diffusers 文档
可复现的流水线
并获得增强的文档体验
开始使用
可复现的流水线
扩散模型本质上是随机的,这使得它每次运行时都能生成不同的输出。但有些时候,您希望每次都生成相同的输出,例如在测试、复现结果,甚至提高图像质量时。虽然您不能期望在不同平台之间获得完全相同的结果,但您可以期望在一定容差范围内,在不同版本和平台之间获得可复现的结果(尽管这可能也会有所不同)。
本指南将向您展示如何在 CPU 和 GPU 上控制随机性以实现确定性生成。
我们强烈建议阅读 PyTorch 关于可复现性的声明
“PyTorch 的不同版本、独立提交或不同平台之间不能保证完全可复现的结果。此外,即使使用相同的种子,CPU 和 GPU 执行之间也可能无法复现结果。”
控制随机性
在推理过程中,流水线严重依赖随机采样操作,包括创建用于去噪的高斯噪声张量和在调度步骤中添加噪声。
在两次推理步骤后,查看 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())
运行上面的代码会打印一个值,但如果再次运行,您会得到一个不同的值。
每次运行流水线时,torch.randn 都会使用不同的随机种子来创建高斯噪声张量。这导致每次运行都会得到不同的结果,并使扩散流水线每次都能生成不同的随机图像。
但如果您需要可靠地生成相同的图像,则取决于您是在 CPU 还是 GPU 上运行流水线。
将 Generator
对象传递给流水线而不是表示种子的整数值可能看起来不直观。但是,这是在 PyTorch 中使用概率模型时推荐的设计,因为 Generator
是一个随机状态,可以按顺序传递给多个流水线。一旦 Generator
被消耗,状态就会就地改变,这意味着即使您将相同的 Generator
传递给不同的流水线,它也不会产生相同的结果,因为状态已经改变了。
要在 CPU 上生成可复现的结果,您需要使用 PyTorch Generator 并设置一个种子。现在,当您运行代码时,它总是打印值 1491.1711
,因为带有种子的 Generator
对象被传递给流水线中的所有随机函数。您应该在您正在使用的任何硬件和 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 以使用确定性算法来创建可复现的流水线。缺点是确定性算法可能比非确定性算法慢,您可能会观察到性能下降。
当操作在多个 CUDA 流中启动时,会发生非确定性行为。为了避免这种情况,请将环境变量 CUBLAS_WORKSPACE_CONFIG 设置为 :16:8
,以便在运行时只使用一个缓冲区大小。
PyTorch 通常会对多种算法进行基准测试以选择最快的算法,但如果您需要可复现性,则应禁用此功能,因为基准测试每次可能会选择不同的算法。将 Diffusers enable_full_determinism 设置为启用确定性算法。
enable_full_determinism()
现在,当您两次运行相同的流水线时,您将获得相同的结果。
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')"
确定性批次生成
创建可复现流水线的一个实际应用是确定性批次生成。您生成一批图像并选择一张图像通过更详细的提示进行改进。主要思想是向流水线传递一个 Generator 列表,并将每个 Generator
与一个种子绑定,以便您可以重复使用它。
让我们使用 stable-diffusion-v1-5/stable-diffusion-v1-5 检查点并生成一批图像。
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
。在您的提示中添加一些额外的文本,然后确保您重新使用相同的种子为 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)
