Diffusers 文档

LoRA

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始使用

LoRA

LoRA (低秩适应) 是一种快速训练模型以适应新任务的方法。它通过冻结原始模型权重并添加少量*新的*可训练参数来实现。这意味着将现有模型适应新任务(例如以新样式生成图像)的速度和成本都大大降低。

LoRA 检查点通常只有几百 MB 大小,因此它们非常轻便且易于存储。使用 load_lora_weights() 将这些较小的权重集加载到现有基础模型中,并指定文件名。

文本到图像
文本到视频
import torch
from diffusers import AutoPipelineForText2Image

pipeline = AutoPipelineForText2Image.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    torch_dtype=torch.float16
).to("cuda")
pipeline.load_lora_weights(
    "ostris/super-cereal-sdxl-lora",
    weight_name="cereal_box_sdxl_v1.safetensors",
    adapter_name="cereal"
)
pipeline("bears, pizza bites").images[0]

load_lora_weights() 方法是加载 LoRA 权重到 UNet 和文本编码器的首选方法,因为它能够处理以下情况:

  • LoRA 权重没有单独的 UNet 和文本编码器标识符
  • LoRA 权重有单独的 UNet 和文本编码器标识符

load_lora_adapter() 方法用于直接在**模型级别**加载 LoRA 适配器,只要该模型是 `PeftAdapterMixin` 的子类 Diffusers 模型。它会构建并准备适配器所需的模型配置。此方法还会将 LoRA 适配器加载到 UNet 中。

例如,如果您只将 LoRA 加载到 UNet 中,load_lora_adapter() 会忽略文本编码器键。使用 `prefix` 参数过滤并加载适当的状态字典,例如设置为 `"unet"`。

import torch
from diffusers import AutoPipelineForText2Image

pipeline = AutoPipelineForText2Image.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    torch_dtype=torch.float16
).to("cuda")
pipeline.unet.load_lora_adapter(
    "jbilcke-hf/sdxl-cinematic-1",
    weight_name="pytorch_lora_weights.safetensors",
    adapter_name="cinematic"
    prefix="unet"
)
# use cnmt in the prompt to trigger the LoRA
pipeline("A cute cnmt eating a slice of pizza, stunning color scheme, masterpiece, illustration").images[0]

torch.compile

torch.compile 通过编译 PyTorch 模型以使用优化内核来加速推理。在编译之前,LoRA 权重需要先融合到基础模型中并卸载。

import torch
from diffusers import DiffusionPipeline

# load base model and LoRA
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"
)

# activate LoRA and set adapter weight
pipeline.set_adapters("ikea", adapter_weights=0.7)

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

通常,UNet 会被编译,因为它是管道中计算量最大的组件。

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

pipeline("A bowl of ramen shaped like a cute kawaii bear").images[0]

请参阅热插拔部分,了解如何在处理已编译模型和多个 LoRA 时避免重新编译。

权重缩放

`scale` 参数用于控制 LoRA 的应用程度。值为 `0` 等同于仅使用基础模型权重,值为 `1` 等同于完全使用 LoRA。

简单用例
更精细的控制

对于简单用例,您可以将 `cross_attention_kwargs={"scale": 1.0}` 传递给管道。

import torch
from diffusers import AutoPipelineForText2Image

pipeline = AutoPipelineForText2Image.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    torch_dtype=torch.float16
).to("cuda")
pipeline.load_lora_weights(
    "ostris/super-cereal-sdxl-lora",
    weight_name="cereal_box_sdxl_v1.safetensors",
    adapter_name="cereal"
)
pipeline("bears, pizza bites", cross_attention_kwargs={"scale": 1.0}).images[0]

缩放调度

在采样过程中动态调整 LoRA 比例可以更好地控制整体构图和布局,因为某些步骤可能更受益于增加或减少的比例。

下面示例中的角色 LoRA 以较高的比例开始,并在前 20 步逐渐衰减以建立角色生成。在后续步骤中,仅应用 0.2 的比例以避免向 LoRA 未训练的图像其他部分添加过多的 LoRA 特征。

import torch
from diffusers import FluxPipeline

pipeline = FluxPipeline.from_pretrained(
    "black-forest-labs/FLUX.1-dev", torch_dtype=torch.bfloat16
).to("cuda")

pipelne.load_lora_weights("alvarobartt/ghibli-characters-flux-lora", "lora")

num_inference_steps = 30
lora_steps = 20
lora_scales = torch.linspace(1.5, 0.7, lora_steps).tolist()
lora_scales += [0.2] * (num_inference_steps - lora_steps + 1)

pipeline.set_adapters("lora", lora_scales[0])

def callback(pipeline: FluxPipeline, step: int, timestep: torch.LongTensor, callback_kwargs: dict):
    pipeline.set_adapters("lora", lora_scales[step + 1])
    return callback_kwargs

prompt = """
Ghibli style The Grinch, a mischievous green creature with a sly grin, peeking out from behind a snow-covered tree while plotting his antics, 
in a quaint snowy village decorated for the holidays, warm light glowing from cozy homes, with playful snowflakes dancing in the air
"""
pipeline(
    prompt=prompt,
    guidance_scale=3.0,
    num_inference_steps=num_inference_steps,
    generator=torch.Generator().manual_seed(42),
    callback_on_step_end=callback,
).images[0]

热插拔

热插拔 LoRA 是一种高效处理多个 LoRA 的方式,同时避免了多次调用 load_lora_weights() 造成的内存累积,在某些情况下,如果模型已编译,还能避免重新编译。此工作流程需要加载一个 LoRA,因为新的 LoRA 权重会原地替换现有的已加载 LoRA。

import torch
from diffusers import DiffusionPipeline

# 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"
)

热插拔不支持以文本编码器为目标的 LoRA。

load_lora_weights() 中设置 `hotswap=True` 以交换第二个 LoRA。使用 `adapter_name` 参数指示要交换哪个 LoRA (默认名称为 `default_0`)。

pipeline.load_lora_weights(
    "lordjia/by-feng-zikai",
    hotswap=True,
    adapter_name="ikea"
)

编译模型

对于已编译的模型,使用 enable_lora_hotswap() 以避免在热插拔 LoRA 时重新编译。此方法应在加载第一个 LoRA *之前* 调用,并且 `torch.compile` 应在加载第一个 LoRA *之后* 调用。

如果第二个 LoRA 的目标与第一个 LoRA 的 LoRA 等级和比例相同,则 enable_lora_hotswap() 方法并不总是必需的。

enable_lora_hotswap() 中,`target_rank` 参数对于设置所有 LoRA 适配器的秩非常重要。将其设置为 `max_rank` 会将其设置为最高值。对于具有不同秩的 LoRA,您应将其设置为更高的秩值。默认秩值为 128。

import torch
from diffusers import DiffusionPipeline

# load base model and LoRAs
pipeline = DiffusionPipeline.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    torch_dtype=torch.float16
).to("cuda")
# 1. enable_lora_hotswap
pipeline.enable_lora_hotswap(target_rank=max_rank)
pipeline.load_lora_weights(
    "ostris/ikea-instructions-lora-sdxl",
    weight_name="ikea_instructions_xl_v1_5.safetensors",
    adapter_name="ikea"
)
# 2. torch.compile
pipeline.unet = torch.compile(pipeline.unet, mode="reduce-overhead", fullgraph=True)

# 3. hotswap
pipeline.load_lora_weights(
    "lordjia/by-feng-zikai",
    hotswap=True,
    adapter_name="ikea"
)

将您的代码移动到 `with torch._dynamo.config.patch(error_on_recompile=True)` 上下文管理器中,以检测模型是否已重新编译。如果模型尽管遵循上述所有步骤仍被重新编译,请提供可复现的示例并提出issue

在某些情况下,重新编译仍然不可避免,例如当热插拔的 LoRA 目标层多于初始适配器时。尝试*首先*加载目标层最多的 LoRA。有关此限制的更多详细信息,请参阅 PEFT 热插拔文档。

合并

每个 LoRA 的权重可以合并在一起,以产生多种现有风格的混合。有几种合并 LoRA 的方法,每种方法在权重合并方式上有所不同(可能会影响生成质量)。

set_adapters

set_adapters() 方法通过连接加权矩阵来合并 LoRA。将 LoRA 名称传递给 set_adapters() 并使用 `adapter_weights` 参数控制每个 LoRA 的缩放。例如,如果 `adapter_weights=[0.5, 0.5]`,则输出是两个 LoRA 的平均值。

`"scale"` 参数决定了合并后的 LoRA 应用的程度。有关更多详细信息,请参阅权重缩放部分。

import torch
from diffusers import DiffusionPipeline

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])
# use by Feng Zikai to activate the lordjia/by-feng-zikai LoRA
pipeline("A bowl of ramen shaped like a cute kawaii bear, by Feng Zikai", cross_attention_kwargs={"scale": 1.0}).images[0]

add_weighted_adapter

这是一个实验性方法,您可以参考 PEFT 的模型合并了解更多详细信息。如果您对这种集成的动机和设计感兴趣,请查看此问题

`~peft.LoraModel.add_weighted_adapter` 方法支持更高效的合并方法,例如 TIESDARE。这些合并方法会从合并模型中移除冗余和可能相互干扰的参数。请记住,LoRA 秩需要相同才能合并。

确保安装了最新稳定版本的 Diffusers 和 PEFT。

pip install -U -q diffusers peft

加载与 LoRA UNet 对应的 UNET。

import copy
import torch
from diffusers import AutoModel, DiffusionPipeline
from peft import get_peft_model, LoraConfig, PeftModel

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

加载一个管道,将 UNet 传递给它,然后加载一个 LoRA。

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

通过组合您加载的第一个 UNet 和管道中的 LoRA UNet,从 LoRA 检查点创建 `~peft.PeftModel`。

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`,如下所示将其推送到 Hub。

ikea_peft_model.push_to_hub("ikea_peft_model", token=TOKEN)

重复此过程并为第二个 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 模型并加载适配器。

base_unet = AutoModel.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` 合并 LoRA,并使用 `combination_type` 指定合并方式。以下示例使用 `"dare_linear"` 方法(请参阅此博客文章了解更多关于这些合并方法的信息),它会随机剪枝一些权重,然后根据 `weights` 中每个 LoRA 的设定权重对张量执行加权和。

使用 set_adapters() 激活合并后的 LoRA。

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

pipeline = DiffusionPipeline.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    unet=model,
    variant="fp16",
    torch_dtype=torch.float16,
).to("cuda")
pipeline("A bowl of ramen shaped like a cute kawaii bear, by Feng Zikai").images[0]

fuse_lora

fuse_lora() 方法将 LoRA 权重直接与底层模型的原始 UNet 和文本编码器权重融合。这减少了每次 LoRA 加载底层模型的开销,因为它只加载模型一次,从而降低了内存使用并提高了推理速度。

import torch
from diffusers import DiffusionPipeline

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() 将它们融合。`lora_scale` 参数控制 LoRA 权重对输出的缩放程度。现在进行此调整很重要,因为将 `scale` 传递给 `cross_attention_kwargs` 在管道中不起作用。

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

由于 LoRA 权重已与底层模型融合,因此卸载它们。使用 save_pretrained() 将融合后的管道保存到本地,或使用 `~PushToHubMixin.push_to_hub` 将其保存到 Hub。

本地保存
保存到 Hub
pipeline.unload_lora_weights()
pipeline.save_pretrained("path/to/fused-pipeline")

融合后的管道现在可以快速加载进行推理,而无需单独加载每个 LoRA。

pipeline = DiffusionPipeline.from_pretrained(
    "username/fused-ikea-feng", torch_dtype=torch.float16,
).to("cuda")
pipeline("A bowl of ramen shaped like a cute kawaii bear, by Feng Zikai").images[0]

使用 `unfuse_lora()` 恢复底层模型权重,例如,如果您想使用不同的 `lora_scale` 值。只有在融合了单个 LoRA 时才能取消融合。例如,它不适用于上述管道,因为存在多个融合的 LoRA。在这种情况下,您需要重新加载整个模型。

pipeline.unfuse_lora()

管理

Diffusers 提供了多种方法来帮助您管理 LoRA 的使用。这些方法在您处理多个 LoRA 时特别有用。

set_adapters

set_adapters() 也激活当前要使用的 LoRA,如果存在多个活动 LoRA。这允许您通过指定其名称在不同的 LoRA 之间切换。

import torch
from diffusers import DiffusionPipeline

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"
)
# activates the feng LoRA instead of the ikea LoRA
pipeline.set_adapters("feng")

save_lora_adapter

使用 save_lora_adapter() 保存适配器。

import torch
from diffusers import AutoPipelineForText2Image

pipeline = AutoPipelineForText2Image.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    torch_dtype=torch.float16
).to("cuda")
pipeline.unet.load_lora_adapter(
    "jbilcke-hf/sdxl-cinematic-1",
    weight_name="pytorch_lora_weights.safetensors",
    adapter_name="cinematic"
    prefix="unet"
)
pipeline.save_lora_adapter("path/to/save", adapter_name="cinematic")

unload_lora_weights

unload_lora_weights() 方法会卸载管道中的所有 LoRA 权重,以恢复底层模型权重。

pipeline.unload_lora_weights()

disable_lora

disable_lora() 方法会禁用所有 LoRA(但它们仍保留在管道中),并将管道恢复为底层模型权重。

pipeline.disable_lora()

get_active_adapters

get_active_adapters() 方法返回附加到管道的活动 LoRA 列表。

pipeline.get_active_adapters()
["cereal", "ikea"]

get_list_adapters

get_list_adapters() 方法返回管道中每个组件的活动 LoRA。

pipeline.get_list_adapters()
{"unet": ["cereal", "ikea"], "text_encoder_2": ["cereal"]}

delete_adapters

delete_adapters() 方法将 LoRA 及其层从模型中完全移除。

pipeline.delete_adapters("ikea")

资源

浏览 LoRA Studio 以使用不同的 LoRA,或者您可以通过下面的 Space 将您最喜欢的 Civitai LoRA 上传到 Hub。

您可以在 FLUX LoRA the ExplorerLoRA the Explorer Space 中找到其他 LoRA。

< > 在 GitHub 上更新

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