Skip to content

合并 LoRAs

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

本指南将向你展示如何使用 [~loaders.PeftAdapterMixin.set_adapters] 和 add_weighted_adapter 方法合并 LoRAs。为了提高推理速度并减少合并 LoRAs 的内存使用量,你还会看到如何使用 [~loaders.StableDiffusionLoraLoaderMixin.fuse_lora] 方法将 LoRA 权重与底层模型的原始权重融合。

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

py
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.PeftAdapterMixin.set_adapters] 方法通过连接加权矩阵来合并 LoRA 适配器。使用适配器名称指定要合并的 LoRA,并使用 adapter_weights 参数控制每个 LoRA 的缩放比例。例如,如果 adapter_weights=[0.5, 0.5],则合并后的 LoRA 输出是两个 LoRA 的平均值。尝试调整适配器权重以查看它如何影响生成的图像!

py
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

WARNING

这是一个实验性方法,它将 PEFTs add_weighted_adapter 方法添加到 Diffusers 中,以实现更有效的合并方法。如果你想了解更多关于此集成背后的动机和设计,请查看此 issue

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

bash
pip install -U diffusers peft

将 LoRAs 与 add_weighted_adapter 方法合并有三个步骤:

  1. 从基础模型和 LoRA 检查点创建一个 PeftModel
  2. 加载一个基础 UNet 模型和 LoRA 适配器。
  3. 使用 add_weighted_adapter 方法和您选择的合并方法合并适配器。

让我们更深入地了解这些步骤的含义。

  1. 加载一个与 LoRA 检查点中的 UNet 相对应的 UNet。在本例中,两个 LoRAs 都使用 SDXL UNet 作为其基础模型。
python
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 开始。

python
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 检查点创建一个 PeftModel

python
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)

TIP

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

重复此过程,从 lordjia/by-feng-zikai LoRA 创建一个 PeftModel

python
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 模型,然后将适配器加载到它上面。
python
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")

使用 add_weighted_adapter 方法和您选择的合并方法(在 这篇博文 中了解其他合并方法)合并适配器。在本例中,让我们使用 "dare_linear" 方法来合并 LoRAs。

WARNING

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

python
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 生成图像。

python
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.PeftAdapterMixin.set_adapters] 和 add_weighted_adapter 方法都需要分别加载基础模型和 LoRA 适配器,这会带来一些开销。[~loaders.lora_base.LoraBaseMixin.fuse_lora] 方法允许你将 LoRA 权重直接与底层模型的原始权重融合。这样,你只需要加载一次模型,从而可以提高推理速度并降低内存使用量。

你可以使用 PEFT 通过 [~loaders.lora_base.LoraBaseMixin.fuse_lora] 方法轻松地将多个适配器直接融合/分离到模型权重中(包括 UNet 和文本编码器),这可以加快推理速度并降低 VRAM 使用量。

例如,如果你有一个基础模型和适配器,并且它们已加载并设置为活动状态,并且具有以下适配器权重:

py
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])

使用 [~loaders.lora_base.LoraBaseMixin.fuse_lora] 方法将这些 LoRA 与 UNet 融合。lora_scale 参数控制使用 LoRA 权重缩放输出的程度。在 [~loaders.lora_base.LoraBaseMixin.fuse_lora] 方法中进行 lora_scale 调整非常重要,因为如果你尝试将 scale 传递给管道中的 cross_attention_kwargs,它将无法正常工作。

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

然后,你应该使用 [~loaders.StableDiffusionLoraLoaderMixin.unload_lora_weights] 卸载 LoRA 权重,因为它们已经与底层基础模型融合。最后,调用 [~DiffusionPipeline.save_pretrained] 在本地保存融合后的管道,或者你可以调用 [~DiffusionPipeline.push_to_hub] 将融合后的管道推送到 Hub。

py
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 适配器。

py
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

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

py
pipeline.unfuse_lora()

torch.compile

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

py
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 欢迎新的合并方法 博客文章!