合并 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-style 和 Norod78/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 有三个步骤
- 从底层模型和 LoRA 检查点创建
~peft.PeftModel
。 - 加载基本 UNet 模型和 LoRA 适配器。
- 使用
~peft.LoraModel.add_weighted_adapter
方法和您选择的合并方法合并适配器。
让我们深入了解这些步骤的具体内容。
- 加载与 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)
- 加载基本 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")
- 使用
~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 上