Diffusers 文档

合并 LoRAs

Hugging Face's logo
加入 Hugging Face 社区

并获得增强文档体验

开始

合并 LoRAs

将多个 LoRAs 组合在一起以生成全新且独特的内容既有趣又有创意。通过合并多个 LoRA 权重来实现,以生成融合不同风格的图像。扩散器提供几种合并 LoRAs 的方法,具体取决于您希望 *如何* 合并它们的权重,这会影响图像质量。

本指南将向您展示如何使用 ~loaders.UNet2DConditionLoadersMixin.set_adapters~peft.LoraModel.add_weighted_adapter 方法合并 LoRAs。为了提高合并 LoRAs 的推理速度并降低内存使用量,您还将了解如何使用 fuse_lora() 方法将 LoRA 权重与底层模型的原始权重融合。

在本指南中,使用 load_lora_weights() 方法加载 Stable Diffusion XL (SDXL) 检查点以及 KappaNeuro/studio-ghibli-styleNorod78/sdxl-chalkboarddrawing-lora LoRAs。您需要为每个 LoRA 分配一个 adapter_name 以便稍后合并它们。

from diffusers import DiffusionPipeline
import torch

pipeline = DiffusionPipeline.from_pretrained("stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16).to("cuda")
pipeline.load_lora_weights("ostris/ikea-instructions-lora-sdxl", weight_name="ikea_instructions_xl_v1_5.safetensors", adapter_name="ikea")
pipeline.load_lora_weights("lordjia/by-feng-zikai", weight_name="fengzikai_v1.0_XL.safetensors", adapter_name="feng")

set_adapters

~loaders.UNet2DConditionLoadersMixin.set_adapters 方法通过连接它们的加权矩阵来合并 LoRA 适配器。使用适配器名称指定要合并的 LoRAs,并使用 adapter_weights 参数控制每个 LoRA 的缩放。例如,如果 adapter_weights=[0.5, 0.5],则合并后的 LoRA 输出是两个 LoRAs 的平均值。尝试调整适配器权重,看看它如何影响生成的图像!

pipeline.set_adapters(["ikea", "feng"], adapter_weights=[0.7, 0.8])

generator = torch.manual_seed(0)
prompt = "A bowl of ramen shaped like a cute kawaii bear, by Feng Zikai"
image = pipeline(prompt, generator=generator, cross_attention_kwargs={"scale": 1.0}).images[0]
image

add_weighted_adapter

这是一种实验性方法,它将 PEFT 的 ~peft.LoraModel.add_weighted_adapter 方法添加到 Diffusers 中,以实现更高效的合并方法。如果您有兴趣详细了解这种集成的动机和设计,请查看此 问题

~peft.LoraModel.add_weighted_adapter 方法提供对更有效合并方法的访问,例如 TIES 和 DARE。要使用这些合并方法,请确保您已安装最新稳定版本的 Diffusers 和 PEFT。

pip install -U diffusers peft

使用 ~peft.LoraModel.add_weighted_adapter 方法合并 LoRAs 有三个步骤

  1. 从底层模型和 LoRA 检查点创建 ~peft.PeftModel
  2. 加载基本 UNet 模型和 LoRA 适配器。
  3. 使用 ~peft.LoraModel.add_weighted_adapter 方法和您选择的合并方法合并适配器。

让我们深入了解这些步骤的具体内容。

  1. 加载与 LoRA 检查点中的 UNet 相对应的 UNet。在本例中,两个 LoRAs 都使用 SDXL UNet 作为其基本模型。
from diffusers import UNet2DConditionModel
import torch

unet = UNet2DConditionModel.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    torch_dtype=torch.float16,
    use_safetensors=True,
    variant="fp16",
    subfolder="unet",
).to("cuda")

加载 SDXL 管道和 LoRA 检查点,从 ostris/ikea-instructions-lora-sdxl LoRA 开始。

from diffusers import DiffusionPipeline

pipeline = DiffusionPipeline.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    variant="fp16",
    torch_dtype=torch.float16,
    unet=unet
).to("cuda")
pipeline.load_lora_weights("ostris/ikea-instructions-lora-sdxl", weight_name="ikea_instructions_xl_v1_5.safetensors", adapter_name="ikea")

现在,您将通过将 SDXL UNet 和管道中的 LoRA UNet 组合起来,从加载的 LoRA 检查点创建 ~peft.PeftModel

from peft import get_peft_model, LoraConfig
import copy

sdxl_unet = copy.deepcopy(unet)
ikea_peft_model = get_peft_model(
    sdxl_unet,
    pipeline.unet.peft_config["ikea"],
    adapter_name="ikea"
)

original_state_dict = {f"base_model.model.{k}": v for k, v in pipeline.unet.state_dict().items()}
ikea_peft_model.load_state_dict(original_state_dict, strict=True)

您可以选择通过调用 ikea_peft_model.push_to_hub("ikea_peft_model", token=TOKEN) 将 ikea_peft_model 推送到 Hub。

重复此过程,从 lordjia/by-feng-zikai LoRA 创建 ~peft.PeftModel

pipeline.delete_adapters("ikea")
sdxl_unet.delete_adapters("ikea")

pipeline.load_lora_weights("lordjia/by-feng-zikai", weight_name="fengzikai_v1.0_XL.safetensors", adapter_name="feng")
pipeline.set_adapters(adapter_names="feng")

feng_peft_model = get_peft_model(
    sdxl_unet,
    pipeline.unet.peft_config["feng"],
    adapter_name="feng"
)

original_state_dict = {f"base_model.model.{k}": v for k, v in pipe.unet.state_dict().items()}
feng_peft_model.load_state_dict(original_state_dict, strict=True)
  1. 加载基本 UNet 模型,然后将适配器加载到模型上。
from peft import PeftModel

base_unet = UNet2DConditionModel.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    torch_dtype=torch.float16,
    use_safetensors=True,
    variant="fp16",
    subfolder="unet",
).to("cuda")

model = PeftModel.from_pretrained(base_unet, "stevhliu/ikea_peft_model", use_safetensors=True, subfolder="ikea", adapter_name="ikea")
model.load_adapter("stevhliu/feng_peft_model", use_safetensors=True, subfolder="feng", adapter_name="feng")
  1. 使用 ~peft.LoraModel.add_weighted_adapter 方法和您选择的合并方法合并适配器(在 这篇博文 中了解有关其他合并方法的更多信息)。在本例中,让我们使用 "dare_linear" 方法合并 LoRAs。

请记住,要合并的 LoRAs 必须具有相同的等级!

model.add_weighted_adapter(
    adapters=["ikea", "feng"],
    weights=[1.0, 1.0],
    combination_type="dare_linear",
    adapter_name="ikea-feng"
)
model.set_adapters("ikea-feng")

现在,您可以使用合并后的 LoRA 生成图像。

model = model.to(dtype=torch.float16, device="cuda")

pipeline = DiffusionPipeline.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0", unet=model, variant="fp16", torch_dtype=torch.float16,
).to("cuda")

image = pipeline("A bowl of ramen shaped like a cute kawaii bear, by Feng Zikai", generator=torch.manual_seed(0)).images[0]
image

fuse_lora

~loaders.UNet2DConditionLoadersMixin.set_adapters~peft.LoraModel.add_weighted_adapter 方法都需要分别加载基本模型和 LoRA 适配器,这会带来一些开销。fuse_lora() 方法允许您将 LoRA 权重直接与底层模型的原始权重融合。这样,您只需加载一次模型,这可以提高推理速度并降低内存使用量。

您可以使用 PEFT 使用 fuse_lora() 方法轻松地将多个适配器直接融合/分离到模型权重(UNet 和文本编码器)中,这会导致推理速度加快和 VRAM 使用量降低。

例如,如果您有基本模型和适配器已加载并设置为活动状态,并且具有以下适配器权重

from diffusers import DiffusionPipeline
import torch

pipeline = DiffusionPipeline.from_pretrained("stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16).to("cuda")
pipeline.load_lora_weights("ostris/ikea-instructions-lora-sdxl", weight_name="ikea_instructions_xl_v1_5.safetensors", adapter_name="ikea")
pipeline.load_lora_weights("lordjia/by-feng-zikai", weight_name="fengzikai_v1.0_XL.safetensors", adapter_name="feng")

pipeline.set_adapters(["ikea", "feng"], adapter_weights=[0.7, 0.8])

使用 fuse_lora() 方法将这些 LoRAs 融合到 UNet 中。lora_scale 参数控制 LoRA 权重输出的缩放比例。在 fuse_lora() 方法中进行 lora_scale 调整非常重要,因为如果您尝试将 scale 传递给管道中的 cross_attention_kwargs,则它将不起作用。

pipeline.fuse_lora(adapter_names=["ikea", "feng"], lora_scale=1.0)

然后,您应该使用 unload_lora_weights() 卸载 LoRA 权重,因为它们已经与底层基本模型融合在一起。最后,调用 save_pretrained() 在本地保存融合后的管道,或者您可以调用 push_to_hub() 将融合后的管道推送到 Hub。

pipeline.unload_lora_weights()
# save locally
pipeline.save_pretrained("path/to/fused-pipeline")
# save to the Hub
pipeline.push_to_hub("fused-ikea-feng")

现在,您可以快速加载融合后的管道并使用它进行推理,而无需分别加载 LoRA 适配器。

pipeline = DiffusionPipeline.from_pretrained(
    "username/fused-ikea-feng", torch_dtype=torch.float16,
).to("cuda")

image = pipeline("A bowl of ramen shaped like a cute kawaii bear, by Feng Zikai", generator=torch.manual_seed(0)).images[0]
image

您可以调用 unfuse_lora() 恢复原始模型的权重(例如,如果您想使用不同的 lora_scale 值)。但是,这只在您仅将一个 LoRA 适配器融合到原始模型时才有效。如果您融合了多个 LoRAs,则需要重新加载模型。

pipeline.unfuse_lora()

torch.compile

torch.compile 可以进一步加快管道速度,但必须先融合 LoRA 权重,然后卸载它们。通常,UNet 会被编译,因为它在管道中是一个非常计算密集型的组件。

from diffusers import DiffusionPipeline
import torch

# load base model and LoRAs
pipeline = DiffusionPipeline.from_pretrained("stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16).to("cuda")
pipeline.load_lora_weights("ostris/ikea-instructions-lora-sdxl", weight_name="ikea_instructions_xl_v1_5.safetensors", adapter_name="ikea")
pipeline.load_lora_weights("lordjia/by-feng-zikai", weight_name="fengzikai_v1.0_XL.safetensors", adapter_name="feng")

# activate both LoRAs and set adapter weights
pipeline.set_adapters(["ikea", "feng"], adapter_weights=[0.7, 0.8])

# fuse LoRAs and unload weights
pipeline.fuse_lora(adapter_names=["ikea", "feng"], lora_scale=1.0)
pipeline.unload_lora_weights()

# torch.compile
pipeline.unet.to(memory_format=torch.channels_last)
pipeline.unet = torch.compile(pipeline.unet, mode="reduce-overhead", fullgraph=True)

image = pipeline("A bowl of ramen shaped like a cute kawaii bear, by Feng Zikai", generator=torch.manual_seed(0)).images[0]

加速文本到图像扩散模型的推理 指南中了解有关 torch.compile 的更多信息。

下一步

要了解更多关于每种合并方法的工作原理的概况,请查看 🤗 PEFT 欢迎新的合并方法 博客文章!

< > 更新 在 GitHub 上