Diffusers 文件

可復現的流水線

Hugging Face's logo
加入 Hugging Face 社群

並獲得增強的文件體驗

開始使用

可復現的流水線

擴散模型本質上是隨機的,這使得它每次執行時都能生成不同的輸出。但有些時候,您希望每次都生成相同的輸出,例如在測試、復現結果,甚至提高影像質量時。雖然您不能期望在不同平臺之間獲得完全相同的結果,但您可以期望在一定容差範圍內,在不同版本和平臺之間獲得可復現的結果(儘管這可能也會有所不同)。

本指南將向您展示如何在 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
GPU

要在 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 分配一個種子(03)。然後生成一批影像並選擇一張進行迭代。

使用列表推導式,迭代 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)

讓我們改進第一張影像(您可以選擇任何您想要的影像),它對應於種子為 0Generator。在您的提示中新增一些額外的文字,然後確保您重新使用相同的種子為 0Generator。所有生成的影像都應該與第一張影像相似。

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)
< > 在 GitHub 上更新

© . This site is unofficial and not affiliated with Hugging Face, Inc.