Stable Diffusion XL on diffusers
翻譯自:https://huggingface.co/docs/diffusers/using-diffusers/sdxl v0.24.0 非逐字翻譯
Stable Diffusion XL (SDXL) 是一個強大的圖像生成模型,其在上一代 Stable Diffusion 的基礎上主要做了如下優化:
- 參數量增加:SDXL 中 Unet 的參數量比前一代大了 3 倍,并且 SDXL 還引入了第二個 text-encoder(OpenCLIP ViT-bigG/14),整體參數量大幅增加。
- 引入了 size-conditioning 和 crop conditioning,在訓練階段有效利用起低分辨率圖像,并在推理對生成的圖片是否需要裁剪有更好的控制。
- 使用了兩階段的生成過程,除了第一階段的 base 模型生成之外,還加入了一個 refiner 模型,使得生成圖像的細節質量更高(其中 base 模型也可以單獨使用,直接生成)
本文將介紹如何使用 diffusers 進行 text-to-image、image-to-image 和 inpainting。
加載模型參數
模型參數分別保存在不同的子目錄中,可以使用 from_pretrained
方法來加載:
from diffusers import StableDiffusionXLPipeline, StableDiffusionXLImg2ImgPipeline
import torchpipeline = StableDiffusionXLPipeline.from_pretrained("stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16, variant="fp16", use_safetensors=True
).to("cuda")refiner = StableDiffusionXLImg2ImgPipeline.from_pretrained("stabilityai/stable-diffusion-xl-refiner-1.0", torch_dtype=torch.float16, use_safetensors=True, variant="fp16"
).to("cuda")
也可以使用 from_single_file
方法來從單個文件 ( .ckpt 或 .safetensors) 中加載:
from diffusers import StableDiffusionXLPipeline, StableDiffusionXLImg2ImgPipeline
import torchpipeline = StableDiffusionXLPipeline.from_single_file("https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/blob/main/sd_xl_base_1.0.safetensors", torch_dtype=torch.float16, variant="fp16", use_safetensors=True
).to("cuda")refiner = StableDiffusionXLImg2ImgPipeline.from_single_file("https://huggingface.co/stabilityai/stable-diffusion-xl-refiner-1.0/blob/main/sd_xl_refiner_1.0.safetensors", torch_dtype=torch.float16, use_safetensors=True, variant="fp16"
).to("cuda")
text-to-image
在進行 text-to-image 生成時,需要傳入文本 prompt。SDXL 默認生成分辨率為 1024 * 1024,也可以設置為 768 或 512,但不要再低于 512 了:
from diffusers import AutoPipelineForText2Image
import torchpipeline_text2image = AutoPipelineForText2Image.from_pretrained("stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16, variant="fp16", use_safetensors=True
).to("cuda")prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k"
image = pipeline_text2image(prompt=prompt).images[0]
image
image-to-image
在進行 image-to-image 生成時,SDXL 在 768 - 1024 這個分辨率區間工作的最好。此時需要輸入一張原始圖像,并給一段文本 prompt:
from diffusers import AutoPipelineForImage2Image
from diffusers.utils import load_image, make_image_grid# 使用 from_pipe,避免在加載 checkpoint 時消耗額外的內存
pipeline = AutoPipelineForImage2Image.from_pipe(pipeline_text2image).to("cuda")url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/sdxl-text2img.png"
init_image = load_image(url)
prompt = "a dog catching a frisbee in the jungle"
image = pipeline(prompt, image=init_image, strength=0.8, guidance_scale=10.5).images[0]
make_image_grid([init_image, image], rows=1, cols=2)
inpainting
在記性 inpainting 時,需要傳入一張原始圖片和原始圖片中你想要修改部分的 mask 圖,并給一個文本 prompt 來描述 mask 區域需要生成什么內容:
from diffusers import AutoPipelineForInpainting
from diffusers.utils import load_image, make_image_gridpipeline = AutoPipelineForInpainting.from_pipe(pipeline_text2image).to("cuda")img_url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/sdxl-text2img.png"
mask_url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/sdxl-inpaint-mask.png"init_image = load_image(img_url)
mask_image = load_image(mask_url)prompt = "A deep sea diver floating"
image = pipeline(prompt=prompt, image=init_image, mask_image=mask_image, strength=0.85, guidance_scale=12.5).images[0]
make_image_grid([init_image, mask_image, image], rows=1, cols=3)
refine image quality
SDXL 相比于之前的 SD 模型,一個很大的差別在于它包含了一個 refiner 模型。refiner 模型 可以接收 base 模型經過幾步去噪之后的低噪聲圖像,并為圖像生成更多高質量的細節。有兩種使用 refiner 模型的方式:
- 同時使用 base 模型和 refiner 模型,來生成高質量圖片
- 使用 base 模型生成一張圖片,然后使用 refiner 模型為圖片添加更多的細節(這是 SDXL 訓練時的方式)
接下來分別介紹這兩種方式的使用。
base + refiner model
當使用第一種方式,即同時使用 base 模型和 refiner 模型來生成圖片時,稱為 ensemble of expert denoisers。這種方式相比于第二種將 base 模型的輸出給 refiner 模型中的方式來說,整體需要的去噪步數更少,因此會快很多。但這種方式我們看到的 base 模型的輸出是帶有一些噪聲的。
在第一種方式中,base 模型負責高噪聲階段的去噪,refiner 模型負責低噪聲階段的去噪。首先加載 base 模型和 refiner 模型:
from diffusers import DiffusionPipeline
import torchbase = DiffusionPipeline.from_pretrained("stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16, variant="fp16", use_safetensors=True
).to("cuda")refiner = DiffusionPipeline.from_pretrained("stabilityai/stable-diffusion-xl-refiner-1.0",text_encoder_2=base.text_encoder_2,vae=base.vae,torch_dtype=torch.float16,use_safetensors=True,variant="fp16",
).to("cuda")
在使用 ensemble of expert denoisers 這種方式時,我們需要定義不同的模型在他們各自階段的去噪步數。對于 base 模型,需要 denoising_end
參數,對于 refiner 模型,需要 denoising_start
參數。
denoising_start
和 denoising_end
參數都時 0-1 之間的一個小數,用于表示當前 schduler 下步數的比例。如果同時還傳入了 strength
參數,它將被忽略,因為去噪步驟的數量是由模型訓練的離散時間步長和聲明的比例截止值決定的。
這里,我們設置 denoising_end
為 0.8,從而 base 模型會負責前 80% 的高噪聲階段的降噪,并設置 denoising_start
為 0.8,從而 refiner 模型會負責后 20% 的低噪聲階段的降噪。注意 base 模型的輸出是在隱層 latent 空間的,而非可見的圖片。
prompt = "A majestic lion jumping from a big stone at night"image = base(prompt=prompt,num_inference_steps=40,denoising_end=0.8,output_type="latent",
).images
image = refiner(prompt=prompt,num_inference_steps=40,denoising_start=0.8,image=image,
).images[0]
image
在 StableDiffusionXLInpaintPipeline 中,refiner 模型也可以用于進行 inpainting:
from diffusers import StableDiffusionXLInpaintPipeline
from diffusers.utils import load_image, make_image_grid
import torchbase = StableDiffusionXLInpaintPipeline.from_pretrained("stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16, variant="fp16", use_safetensors=True
).to("cuda")refiner = StableDiffusionXLInpaintPipeline.from_pretrained("stabilityai/stable-diffusion-xl-refiner-1.0",text_encoder_2=base.text_encoder_2,vae=base.vae,torch_dtype=torch.float16,use_safetensors=True,variant="fp16",
).to("cuda")img_url = "https://raw.githubusercontent.com/CompVis/latent-diffusion/main/data/inpainting_examples/overture-creations-5sI6fQgYIuo.png"
mask_url = "https://raw.githubusercontent.com/CompVis/latent-diffusion/main/data/inpainting_examples/overture-creations-5sI6fQgYIuo_mask.png"init_image = load_image(img_url)
mask_image = load_image(mask_url)prompt = "A majestic tiger sitting on a bench"
num_inference_steps = 75
high_noise_frac = 0.7image = base(prompt=prompt,image=init_image,mask_image=mask_image,num_inference_steps=num_inference_steps,denoising_end=high_noise_frac,output_type="latent",
).images
image = refiner(prompt=prompt,image=image,mask_image=mask_image,num_inference_steps=num_inference_steps,denoising_start=high_noise_frac,
).images[0]
make_image_grid([init_image, mask_image, image.resize((512, 512))], rows=1, cols=3)
這種 ensemble of expert denoisers 的方式對于所有 scheduler 都可用。
base to refiner model
第二種方式通過 base 模型先生成一張完全去噪的圖片,然后使用 refiner 模型以 image-to-image 的形式,為圖片添加更多的高質量細節,這使得 SDXL 的生成質量有了極大的提高。首先加載 base 和 refiner 模型:
from diffusers import DiffusionPipeline
import torchbase = DiffusionPipeline.from_pretrained("stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16, variant="fp16", use_safetensors=True
).to("cuda")refiner = DiffusionPipeline.from_pretrained("stabilityai/stable-diffusion-xl-refiner-1.0",text_encoder_2=base.text_encoder_2,vae=base.vae,torch_dtype=torch.float16,use_safetensors=True,variant="fp16",
).to("cuda")
先使用 base 模型生成一張圖片,注意將輸出形式設置為 latent:
prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k"image = base(prompt=prompt, output_type="latent").images[0]
將生成的圖片輸入到 refiner 模型中:
image = refiner(prompt=prompt, image=image[None, :]).images[0]
要進行 inpainting,在 StableDiffusionXLInpaintPipeline 中加載 base 和 refiner 模型,去掉 denoising_end
和 denoising_start
參數,并為 refiner 模型設置一個較小的步數。
micro-conditioning
SDXL 訓練時使用了許多額外的條件方式,即 micro-conditioning,包括 original_image_size、target_image_size 和 cropping parameters。在推理階段,合理地使用 micro-conditioning 可以生成高質量的、居中的圖片。
由于 classfier-free guidance 的存在,可以在 SDXL 相關的 pipeline 中使用 micro-conditioning 和 negative micro-conditioning 參數。
size conditioning
size conditioning 有兩種:
-
original size conditioning。訓練集中有許多圖片的分辨率是較低的,但又不能直接不用這些低分辨率圖像(占比達 40%,丟了太浪費了),因此通常會對這些圖像進行 resize,從而得到高分辨率的圖像。在這個過程中,不可避免得會引入插值這種人工合成的模糊痕跡,被 SDXL 學到,而在真正的高分辨率圖像中,是不該有這些痕跡的。因此訓練時會告訴模型,這張圖片實際是多少分辨率的,作為條件。
在推理階段,我們可以指定 original_size 來表示圖像的原始尺寸。使用默認的 1024,能生成出與原始數據集中高分辨率圖像類似的高質量圖像。而如果將這個值設得很低,如 256,模型還是會生成分辨率為 1024 的圖像,但就會帶有低分辨率圖像的特征(如模糊、模式簡單等)。
-
target size conditioning。SDXL 訓練時支持多種不同的長寬比。
推理時,如果使用默認的值 1024,生成的圖像會看起來像方形圖像(長寬比1:1)。這里建議將 target_size 和 original_size 設置為相同的值,但你也可以調一調這些參數實驗一下看看。
在 diffusers 中,我們還可以指定有關圖像大小的 negative 條件,從而引導生成遠離某些圖像分辨率:
from diffusers import StableDiffusionXLPipeline
import torchpipe = StableDiffusionXLPipeline.from_pretrained("stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16, variant="fp16", use_safetensors=True
).to("cuda")prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k"
image = pipe(prompt=prompt,negative_original_size=(512, 512),negative_target_size=(1024, 1024),
).images[0]
crop conditioning
SDXL 之前的 SD 模型的生成結果有時會看起來像是被裁剪過得。這是因為為了保證訓練時每個 batch 內的尺寸一致,輸入的訓練數據確實有很多是裁剪過的。因此訓練時,裁剪坐標也會作為條件給到模型。從而,在推理時,我們將裁剪坐標指定為 (0, 0) (也是 diffusers 默認值),就可以生成非裁剪的圖片了。你也可以試著調一下裁剪坐標這個參數,看模型的生成結果會是什么樣子,應該可以得到非居中的構圖。
from diffusers import StableDiffusionXLPipeline
import torchpipeline = StableDiffusionXLPipeline.from_pretrained("stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16, variant="fp16", use_safetensors=True
).to("cuda")prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k"
image = pipeline(prompt=prompt, crops_coords_top_left=(256, 0)).images[0]
image
同樣可以指定 negative 裁剪坐標以引導生成遠離某些裁剪參數:
from diffusers import StableDiffusionXLPipeline
import torchpipe = StableDiffusionXLPipeline.from_pretrained("stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16, variant="fp16", use_safetensors=True
).to("cuda")prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k"
image = pipe(prompt=prompt,negative_original_size=(512, 512),negative_crops_coords_top_left=(0, 0),negative_target_size=(1024, 1024),
).images[0]
image
Use a different prompt for each text-encoder
SDXL 有兩個 text encoder,所以給兩個 text encoder 傳入不同的文本 prompt 是可能的,這可以提高生成質量(參考)。將原本的 prompt 傳到 prompt
中,另一個 prompt 傳到 prompt_2
中。如果使用 negative prompt 也是類似的,分別傳到 negative_prompt
和 negative_prompt_2
。
from diffusers import StableDiffusionXLPipeline
import torchpipeline = StableDiffusionXLPipeline.from_pretrained("stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16, variant="fp16", use_safetensors=True
).to("cuda")# prompt is passed to OAI CLIP-ViT/L-14
prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k"
# prompt_2 is passed to OpenCLIP-ViT/bigG-14
prompt_2 = "Van Gogh painting"
image = pipeline(prompt=prompt, prompt_2=prompt_2).images[0]
image
SDXL 的雙 text encoder 同樣支持 textual inversion embeddings,需要分別加載,詳情見:SDXL textual inversion 。
Optimizations
SDXL 的模型還是很大的,可能在一些設備上運行會比較吃力,以下是一些節約內存和提高推理速度的技巧。
-
如果顯存不夠,可以臨時將模型 offload 到內存中
# base.to("cuda") # refiner.to("cuda") base.enable_model_cpu_offload() refiner.enable_model_cpu_offload()
-
如果你使用的 torch 版本 > 2.0,那么使用
torch.cmpile
可以提速約 20%base.unet = torch.compile(base.unet, mode="reduce-overhead", fullgraph=True) refiner.unet = torch.compile(refiner.unet, mode="reduce-overhead", fullgraph=True)
-
如果你使用的 torch 版本 < 2.0,記得要用 xFormers 來提供 flash attention
base.enable_xformers_memory_efficient_attention() refiner.enable_xformers_memory_efficient_attention()
Other resources
如果你想要研究下 SDXL 中 UNet2DConditionModel 的最小版本,可參考minSDXL 。