Diffusers documentation

LoRA

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

LoRA

LoRA(低秩适应)通过冻结原始权重并训练一组少量新参数,将预训练模型适配到新任务。与全量微调相比,这种适配方式速度更快、成本更低,因此适用于在新风格下生成图像等任务。

LoRA 检查点通常只有几百 MB。使用 pipeline.load_lora_weights 加载它们,并将 weight_name 设置为检查点文件名。有关共享参数,请参阅 load_lora_weights()。

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

pipeline = AutoPipelineForText2Image.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    dtype=torch.float16
).to("cuda")  # or "mps", "xpu", "cpu"
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]

本页中的 pipeline LoRA API 在不同模型系列中工作方式相同。在 Stable Diffusion 和 SDXL 中,去噪模块是 pipeline.unet;而在 Flux、Stable Diffusion 3、AuraFlow 和 LTX 等 DiT 风格模型中,则是 pipeline.transformer。同样的模式也适用于 CogVideoX、HunyuanVideo 和 Wan 等视频模型。模型级 PEFT 方法使用该属性以及对应的 prefix("unet" 或 "transformer")。

pipeline.load_lora_weights 是将 LoRA 权重加载到去噪器(UNet 或 transformer)和文本编码器中的推荐方式。有关共享参数,请参阅 load_lora_weights()。它可以处理以下情况:

  • 该检查点在去噪器和文本编码器之间共享键
  • 该检查点使用独立的去噪器和文本编码器键

load_lora_adapter() 方法会在任何继承自 PeftAdapterMixin 的 Diffusers 模型(例如 UNet 或 transformer)上,以模型级别加载 LoRA 适配器。它会构建并准备适配器配置,并将权重加载到该模块中。

使用 prefix 来筛选目标模块("unet" 或 "transformer")的 state dict 键。默认值为 "transformer",因此 Stable Diffusion 和 SDXL 必须传入 prefix="unet"。

import torch
from diffusers import AutoPipelineForText2Image

pipeline = AutoPipelineForText2Image.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    dtype=torch.float16
).to("cuda")  # or "mps", "xpu", "cpu"
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]
import torch
from diffusers import FluxPipeline

pipeline = FluxPipeline.from_pretrained(
    "black-forest-labs/FLUX.1-dev", dtype=torch.bfloat16
).to("cuda")  # or "mps", "xpu", "cpu"
pipeline.transformer.load_lora_adapter(
    "alvarobartt/ghibli-characters-flux-lora",
    adapter_name="lora",
    prefix="transformer",
)

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",
    dtype=torch.float16
).to("cuda")  # or "mps", "xpu", "cpu"
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
transformer
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]

请参阅 hotswapping 部分,了解在使用编译模型和多个 LoRA 时如何避免重新编译。

权重缩放

使用 set_adapters() 上的 adapter_weights 参数来控制应用多少 LoRA。权重为 0 相当于仅使用基础模型权重,而权重为 1 相当于完全使用 LoRA。

标量权重
按组件划分的缩放

在 set_adapters() 中向 adapter_weights 传入一个标量,以控制 LoRA 的应用强度。

import torch
from diffusers import AutoPipelineForText2Image

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

set_adapters 是主要模式,但某些流水线也接受在调用时传入 cross_attention_kwargs={"scale": 1.0} 作为缩放注意力的次要方式。不使用交叉注意力的流水线通常改为暴露 attention_kwargs(或在 Flux 和 Stable Diffusion 3 中暴露 joint_attention_kwargs)。

扩展调度

在采样过程中动态调整 LoRA 缩放系数,能让您更好地控制整体构图和布局,因为某些步骤可能从增大或减小缩放系数中获益更多。

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

import torch
from diffusers import FluxPipeline

pipeline = FluxPipeline.from_pretrained(
    "black-forest-labs/FLUX.1-dev", dtype=torch.bfloat16
).to("cuda")  # or "mps", "xpu", "cpu"

pipeline.load_lora_weights("alvarobartt/ghibli-characters-flux-lora", adapter_name="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",
    dtype=torch.float16
).to("cuda")  # or "mps", "xpu", "cpu"
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。如果在加载时省略 adapter_name,Diffusers 会将适配器命名为 default_0、default_1 等。使用命名适配器可以更明确地指定目标。

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 设置为您计划加载的适配器中的最高秩(默认值为 128)。

import torch
from diffusers import DiffusionPipeline

# load base model and LoRAs
pipeline = DiffusionPipeline.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    dtype=torch.float16
).to("cuda")  # or "mps", "xpu", "cpu"
# 1. enable_lora_hotswap
pipeline.enable_lora_hotswap(target_rank=128)
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。

如果您预期在使用此功能进行推理时会出现不同的分辨率,请确保在编译时设置 dynamic=True。有关更多详细信息,请参阅 动态形状编译。

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

Why `target_rank` and load order matter

enable_lora_hotswap() 方法将 LoRA 缩放因子从浮点数转换为 torch.tensors,并将权重的形状填充至所需的最大形状,以便在替换权重中的数据时避免重新分配整个属性。

这正是 target_rank 参数重要的原因。即使使用零填充,结果也不会改变。不过,根据填充大小,计算速度可能会变慢。

由于不会添加新的 LoRA 属性,每个后续加载的 LoRA 只能针对第一个 LoRA 所针对的相同层,或这些层的一个子集。选择 LoRA 的加载顺序非常重要,因为如果各个 LoRA 针对的是互不重叠的层,最终可能会创建一个针对所有目标层并集的 dummy LoRA。

有关使用编译模型进行 LoRA 热切换的更多信息,请参阅 LoRA fast 博客文章;如需了解更多实现细节,请查看 hotswap.py 文件。

合并

可以将各个 LoRA 的权重合并,以生成多种现有风格的混合效果。合并 LoRA 有多种方法,每种方法在如何合并权重方面有所不同(这可能会影响生成质量)。

set_adapters

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

在 set_adapters() 中优先使用 adapter_weights 来控制每个合并后的 LoRA 的应用程度。有关更多详细信息,请参阅 Weight scale 部分。

import torch
from diffusers import DiffusionPipeline

pipeline = DiffusionPipeline.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    dtype=torch.float16
).to("cuda")  # or "mps", "xpu", "cpu"
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").images[0]

add_weighted_adapter

这是一种实验性方法,有关更多详细信息,请参阅 PEFT 的 模型合并。如果您对此次集成的动机和设计感兴趣,请查看此 issue。

add_weighted_adapter 方法支持更高效的合并方法,例如 TIES 或 DARE。这些合并方法会从合并后的模型中移除冗余且可能相互干扰的参数。请注意,要合并的 LoRA 秩必须具有相同的秩。下面的示例使用 SDXL 的 UNet。

请确保已安装最新稳定版的 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",
    dtype=torch.float16,
    use_safetensors=True,
    variant="fp16",
    subfolder="unet",
).to("cuda")  # or "mps", "xpu", "cpu"

加载一个 pipeline,将 UNet 传入其中,并加载一个 LoRA。

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

通过组合你加载的第一个 UNet 和流水线中的 LoRA UNet,从 LoRA 检查点创建一个 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 创建一个 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 pipeline.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",
    dtype=torch.float16,
    use_safetensors=True,
    variant="fp16",
    subfolder="unet",
).to("cuda")  # or "mps", "xpu", "cpu"

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 合并 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",
    dtype=torch.float16,
).to("cuda")  # or "mps", "xpu", "cpu"
pipeline("A bowl of ramen shaped like a cute kawaii bear, by Feng Zikai").images[0]

fuse_lora

fuse_lora() 方法会将 LoRA 权重直接融合到底层模型原始的 denoiser(UNet 或 transformer)以及文本编码器的权重中。由于底层模型只需加载一次,这减少了为每个 LoRA 加载底层模型的开销,从而降低了内存占用并提升了推理速度。

import torch
from diffusers import DiffusionPipeline

pipeline = DiffusionPipeline.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    dtype=torch.float16
).to("cuda")  # or "mps", "xpu", "cpu"
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 权重已经与底层模型融合,请卸载这些 LoRA 权重。使用 save_pretrained() 将融合后的 pipeline 保存到本地,或使用 ~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", dtype=torch.float16,
).to("cuda")  # or "mps", "xpu", "cpu"
pipeline("A bowl of ramen shaped like a cute kawaii bear, by Feng Zikai").images[0]

使用 unfuse_lora() 可以恢复底层模型的权重,例如当你想使用不同的 lora_scale 值时。只有在仅融合了一个 LoRA 的情况下才能执行取消融合操作。例如,对于上面提到的 pipeline,由于存在多个已融合的 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",
    dtype=torch.float16
).to("cuda")  # or "mps", "xpu", "cpu"
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() 在加载到(pipeline.unet 或 pipeline.transformer)的同一模块上保存适配器。

import torch
from diffusers import AutoPipelineForText2Image

pipeline = AutoPipelineForText2Image.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    dtype=torch.float16
).to("cuda")  # or "mps", "xpu", "cpu"
pipeline.unet.load_lora_adapter(
    "jbilcke-hf/sdxl-cinematic-1",
    weight_name="pytorch_lora_weights.safetensors",
    adapter_name="cinematic",
    prefix="unet"
)
pipeline.unet.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 中),并将 pipeline 恢复到底层模型权重。

pipeline.disable_lora()

get_active_adapters

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

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

get_list_adapters

get_list_adapters() 方法返回流水线中每个组件已加载的所有 LoRA。字典的键与已加载的模块相匹配,例如 unet 或 transformer 以及任何文本编码器。

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。
Update on GitHub