☰
Diffusers PEFT 适配器加载详解:PeftAdapterMixin 与 LoRA 集成实践
2026/10/11 0:10:09 网站建设 项目流程

Diffusers PEFT 适配器加载详解:PeftAdapterMixin 与 LoRA 集成实践

【免费下载链接】diffusers🤗 Diffusers: State-of-the-art diffusion models for image, video, and audio generation in PyTorch.项目地址: https://gitcode.com/GitHub_Trending/di/diffusers

本文以 🤗 Diffusers 仓库中的 PEFT API 文档 为核心,结合src/diffusers/loaders/peft.py的源码实现,系统讲解 Diffusers 如何通过PeftAdapterMixin与 PEFT 库集成,在UNet2DConditionModel、SD3Transformer2DModel等模型类上加载、切换、缩放、融合与保存 LoRA 适配器。读完本文,你将掌握模型级适配器的完整生命周期管理,并能在实际推理与训练场景中直接落地使用。

一、背景:Diffusers 与 PEFT 的适配器生态

Diffusers 支持通过 PEFT 之类的适配器(adapter),其核心入口就是loaders.peft.PeftAdapterMixin这个混合类(mixin)。它让 Diffusers 中像UNet2DConditionModel、SD3Transformer2DModel这样的模型类可以直接与一个适配器协同工作,而不需要改动原有模型结构。

[!TIP] 有关 PEFT 在 Diffusers 中用于推理的整体使用概览,请参见仓库内的 Inference with PEFT 教程。本文聚焦于 API 层的PeftAdapterMixin及其背后的源码实现。

PEFT(Parameter-Efficient Fine-Tuning)的核心思想是冻结原模型权重,只注入少量可训练参数。LoRA 通过在权重矩阵旁附加低秩分解矩阵lora_A @ lora_B实现这一点,使得一个适配器 checkpoint 通常只有几百 MB 甚至更小,加载轻量、易于分发。Diffusers 在模型层与 Pipeline 层提供了两套入口:

  • Pipeline 层:StableDiffusionLoraLoaderMixin/LoraBaseMixin,负责把 LoRA 同时分发到 UNet 和 text encoder;
  • 模型层:PeftAdapterMixin,直接在单个模型(如 UNet 或 Transformer)上加载、管理适配器。

两者共享同一套 PEFT 后端与工具函数(见 src/diffusers/utils/peft_utils.py),本文重点剖析模型层的PeftAdapterMixin。

二、PeftAdapterMixin 类总览

PeftAdapterMixin定义于 src/diffusers/loaders/peft.py,其文档字符串明确声明了它提供的四类能力:

  1. 附加新适配器到模型中;
  2. 附加多个适配器,并可迭代式地激活/停用它们;
  3. 激活/停用模型中的所有适配器;
  4. 获取当前激活的适配器列表。

从源码结构看,该类实现的方法可归为以下几组:

能力分组方法核心作用
加载load_lora_adapter从 Hub / 本地目录 / state_dict 加载 LoRA 适配器
训练add_adapter注入一个空适配器(配合 PEFT 配置)用于训练
激活切换set_adapter/set_adapters/active_adapters设置单个或多个激活适配器及权重
启停enable_adapters/disable_adapters/enable_lora/disable_lora临时启用或停用适配器
融合fuse_lora/unfuse_lora将 LoRA 权重合并进基础权重,或撤销融合
卸载删除unload_lora/delete_adapters完全移除适配器
保存save_lora_adapter序列化指定适配器权重
热替换enable_lora_hotswap为 torch.compile 模型准备 LoRA 热替换

三、load_lora_adapter:模型级 LoRA 加载的完整流程

load_lora_adapter是PeftAdapterMixin的核心方法(peft.py#L79-L367),它把「加载适配器」这个高层语义拆解为一条清晰的流水线。

3.1 参数说明

参数类型/默认值说明
pretrained_model_name_or_path_or_dictstr/os.PathLike/dict可以是 Hub 上的模型 id(如google/ddpm-celebahq-256)、本地目录路径(配合ModelMixin.save_pretrained保存的权重),或一个 PyTorch state dict
prefixstr,可选用于过滤 state dict 的前缀。例如只加载 UNet 相关键时传"unet"
cache_dirstr,可选自定义模型缓存目录
force_downloadbool,默认False强制重新下载权重,忽略缓存
proxiesdict,可选按协议/端点配置代理服务器
local_files_onlybool,默认False仅加载本地文件,不从 Hub 下载
tokenstr/bool,可选Hub 鉴权 token,True时使用diffusers-cli login生成的 token
revisionstr,默认"main"指定模型版本(分支名、tag 或 commit id)
subfolderstr,默认""大模型仓库中模型文件的子目录
network_alphasdict[str, float]网络 alpha 值,语义等同于 kohya-ss 训练脚本中的--network_alpha,用于稳定训练、防止下溢
low_cpu_mem_usagebool,默认False只加载 LoRA 权重而不初始化随机权重,加快加载速度
hotswapbool,默认False是否用新适配器就地替换已有适配器,避免 torch.compile 模型重编译
metadatadict,可选LoRA 适配器元数据;提供时优先用它初始化LoraConfig,不再从 state dict 推断

3.2 内部流水线(源码级拆解)

  1. 获取 state dict:通过_fetch_state_dict(定义于 src/diffusers/loaders/lora_base.py)下载或读取权重,user_agent标记为{"file_type": "attn_procs_weights", "framework": "pytorch"},并默认禁止 pickle(allow_pickle = False,即优先使用 safetensors)。
  2. 前缀过滤:若指定了prefix,则用k.removeprefix(f"{prefix}.")过滤出对应模块的键(peft.py#L194-L197)。
  3. 格式归一化:检查第一个键,若不含"lora_A",则调用convert_unet_state_dict_to_peft把 diffusers 格式转换为 PEFT 格式;若检测到"lora_controlnet"键(即 Stability AI 的 Control LoRA),则调用convert_sai_sd_control_lora_state_dict_to_peft专门处理(peft.py#L210-L220)。
  4. 推断 rank:遍历 state dict 中所有"lora_B"且维度大于 1 的键,用val.shape[1]收集每个模块的 rank(peft.py#L222-L231)。注意注释中提到 PEFT 的^符号约定,用于区分具有公共前缀的模块名(例如proj_out.weight与blocks.transformer.proj_out.weight可能 rank 不同)。
  5. 自动命名:未传adapter_name时,通过get_adapter_name生成default_0、default_1之类的默认名(依据已加载适配器数量计数)。
  6. 构建 LoraConfig:调用_create_lora_config(见 src/diffusers/utils/peft_utils.py#L371-L391)生成 PEFT 的LoraConfig。
  7. 注入与赋值:调用 PEFT 的inject_adapter_in_model注入 LoRA 层,再用set_peft_model_state_dict写入权重;失败时回滚peft_config并清理已注入的BaseTunerLayer(peft.py#L294-L344)。
  8. 兼容 offload:通过_optionally_disable_offloading在加载前临时移除模型的 CPU offload hook(模型 offload、顺序 offload 或 group offload),加载完成后再恢复(peft.py#L348-L357)。
  9. 未匹配键告警:_maybe_warn_for_unhandled_keys会对.lora_相关的 unexpected/missing keys 给出警告,帮助排查适配器与模型不匹配的问题(peft_utils.py#L394-L418)。

3.3 实际使用示例

load_lora_adapter是模型级方法,直接从 UNet 上调用。若只加载到 UNet,text encoder 的键会被忽略;此时用prefix="unet"过滤对应的 state dict:

import torch from diffusers import AutoPipelineForText2Image pipeline = AutoPipelineForText2Image.from_pretrained( "stabilityai/stable-diffusion-xl-base-1.0", dtype=torch.float16, ).to("cuda") # 也支持 "mps"、"xpu"、"cpu" pipeline.unet.load_lora_adapter( "jbilcke-hf/sdxl-cinematic-1", weight_name="pytorch_lora_weights.safetensors", adapter_name="cinematic", prefix="unet", ) # 在提示词中使用 "cnmt" 触发该 LoRA pipeline("A cute cnmt eating a slice of pizza, stunning color scheme, masterpiece, illustration").images[0]

要点:load_lora_adapter构建并准备了适配器所需的完整模型配置,这也是它与 Pipeline 层load_lora_weights(需要处理 UNet 与 text encoder 双标识符)的主要区别——前者更聚焦、更底层。

四、LoraConfig 自动推导:rank 与 alpha 的推断逻辑

从源码看,PeftAdapterMixin的一个关键设计是从 state dict 自动推导 LoraConfig,而不是要求用户手动指定r、lora_alpha等超参数。核心实现在get_peft_kwargs(src/diffusers/utils/peft_utils.py#L153-L208),其逻辑为:

  1. 若所有模块 rank 一致,直接令r = lora_alpha = 该 rank;
  2. 若 rank 不一致,取出现次数最多的 rank 作为全局r,其余模块写入rank_pattern;当 checkpoint 中没有 alpha 数据时,按 diffusers/PEFT 约定W_eff = W + lora_B @ lora_A(即每个模块alpha == rank,缩放为 1.0),把rank_pattern镜像到alpha_pattern以保持每模块 scale 为 1.0;
  3. 若提供了network_alphas,则按同样「取众数、其余进alpha_pattern」的方式处理;
  4. 通过"lora_magnitude_vector"键检测 DoRA(use_dora),通过"lora_B"后缀.bias检测lora_bias;
  5. target_modules由 state dict 中所有.lora前缀的模块名去重得到。

这些推断规则在仓库测试 tests/lora/test_peft_utils.py 中有明确的验证用例,例如:

  • test_mixed_ranks_without_alphas_apply_at_scale_one:不同模块 rank 各异且无 alpha 键时,所有模块有效缩放alpha / rank必须等于 1.0;
  • test_uniform_rank_without_alphas_unchanged:rank 一致时r == lora_alpha == rank,且rank_pattern与alpha_pattern为空;
  • test_mixed_ranks_with_uniform_alpha_keep_declared_alpha:显式声明的 alpha 优先于alpha == rank约定(如 alpha=32、rank=64 的模块缩放为 0.5)。

这也解释了为什么load_lora_adapter的metadata参数有意义:当 checkpoint 自带lora_adapter_metadata元数据时,_create_lora_config直接采用元数据,跳过上述推断,避免歧义。

五、多适配器的激活、切换与权重控制

PeftAdapterMixin提供了完整的适配器激活管理 API,这在同时使用多个 LoRA 时尤其重要。

5.1 set_adapters:同时激活多个适配器并设置权重

set_adapters(peft.py#L437-L491)接收适配器名列表与权重列表,权重为None时默认1.0。实现上通过set_weights_and_activate_adapters(peft_utils.py#L254-L283)逐模块调用 PEFT 的set_adapter与set_scale。

值得注意的是源码中的_SET_ADAPTER_SCALE_FN_MAPPING(peft.py#L47-L53):对UNet2DConditionModel和UNetMotionModel,权重会先经过_maybe_expand_lora_scales展开(该函数定义于 src/diffusers/loaders/unet_loader_utils.py),以支持对 UNet 各 down/mid/up 块做细粒度缩放;其他模型类则直接使用原权重。

pipeline.load_lora_weights( "jbilcke-hf/sdxl-cinematic-1", weight_name="pytorch_lora_weights.safetensors", adapter_name="cinematic", ) pipeline.load_lora_weights( "nerijs/pixel-art-xl", weight_name="pixel-art-xl.safetensors", adapter_name="pixel", ) pipeline.unet.set_adapters(["cinematic", "pixel"], weights=[0.5, 0.5])

5.2 LoRA 缩放(Weight scale)

scale参数控制 LoRA 的应用强度:0等价于只用基础模型,1等价于完全使用 LoRA。

  • 简单场景:通过cross_attention_kwargs={"scale": 1.0}传入 Pipeline。其底层由apply_lora_scale装饰器(peft_utils.py#L286-L332)实现——它从attention_kwargs中弹出scale,在 forward 前调用scale_lora_layers缩放、结束后在finally中调用unscale_lora_layers恢复,保证异常时也能正确还原。
  • 细粒度控制:向set_adapters传字典,可分别指定 text encoder 与 UNet 各块的缩放:
scales = { "text_encoder": 0.5, "text_encoder_2": 0.5, "unet": { "down": 0.9, "up": { "block_0": 0.6, "block_1": [0.4, 0.8, 1.0], }, }, } pipeline.set_adapters("cereal", scales)

[!WARNING]set_adapters只缩放注意力权重。若 LoRA 还包含 ResNet、下采样/上采样等组件,这些组件会保持1.0的缩放;未显式指定的块(如"mid")默认使用1.0。

5.3 单一适配器的设置与查询

  • set_adapter(adapter_name):强制模型只使用指定适配器、停用其他适配器。传入不存在的名字会抛出ValueError并列出当前已加载的适配器;旧版 PEFT 不支持多适配器推理时,设置多个适配器会提示升级(peft.py#L531-L578)。
  • active_adapters():返回当前激活的适配器列表,实现是遍历模块找到第一个BaseTunerLayer并读取其active_adapter(peft.py#L625-L644)。
  • enable_adapters()/disable_adapters():分别启用/停用模型上所有适配器。停用后模型回退到仅使用基础权重,但适配器仍然保留在模型上,可随时重新启用(peft.py#L580-L623)。

六、LoRA 融合:fuse_lora / unfuse_lora

fuse_lora(peft.py#L646-L674)将 LoRA 权重直接合并进基础模型权重(UNet 与 text encoder),从而:

  • 只需加载一次基础模型即可服务多个 LoRA,降低内存占用;
  • 合并后推理速度更快(省去 LoRA 分支计算)。

关键参数:

参数默认值说明
lora_scale1.0融合时的输出缩放比例,需要在此刻确定——融合后cross_attention_kwargs={"scale": ...}不再生效
safe_fusingFalse融合前检查权重中是否存在 NaN,存在则跳过该层(safe merge)
adapter_namesNone指定要融合的适配器名;旧版 PEFT 不支持该参数时会要求升级

实现细节:fuse_lora遍历所有BaseTunerLayer,先按lora_scale调用scale_layer,再调用 PEFT 的merge完成权重合并(若为safe_fusing则传safe_merge=True)。

pipeline.set_adapters(["ikea", "feng"], adapter_weights=[0.7, 0.8]) pipeline.fuse_lora(adapter_names=["ikea", "feng"], lora_scale=1.0) # 融合后即可卸载 LoRA 权重 pipeline.unload_lora() pipeline.save_pretrained("path/to/fused-pipeline")

unfuse_lora执行逆向操作unmerge,把基础权重恢复原状。需要特别注意的是:只有在只融合了一个 LoRA 时才能 unfuse;多个 LoRA 融合后无法逐一撤销,只能重新加载整个模型。

七、torch.compile 与 LoRA 热替换(Hotswapping)

7.1 编译前先融合

torch.compile通过生成优化内核加速推理。由于编译后的计算图对结构敏感,使用 PEFT 时必须先把 LoRA 融合进基础模型并卸载权重,再执行编译:

pipeline.load_lora_weights( "ostris/ikea-instructions-lora-sdxl", weight_name="ikea_instructions_xl_v1_5.safetensors", adapter_name="ikea", ) pipeline.set_adapters("ikea", adapter_weights=0.7) pipeline.fuse_lora(adapter_names=["ikea"], lora_scale=1.0) pipeline.unload_lora() pipeline.unet.to(memory_format=torch.channels_last) pipeline.unet = torch.compile(pipeline.unet, mode="reduce-overhead", fullgraph=True)

通常只编译计算量最大的 UNet 即可获得大部分收益。

7.2 Hotswapping:免重编译切换 LoRA

Hotswapping 是「就地替换」已加载适配器权重的机制:新 LoRA 的权重直接写入已有适配器的参数槽位,而不是追加新参数,因此不会触发 torch.compile 重编译,也不会随多次load_lora_weights调用累积内存。这是多 LoRA 场景下的高效工作流,前提是先加载过一个 LoRA。

load_lora_adapter的hotswap参数(peft.py#L279-L310)实现细节:

  1. 校验adapter_name必须是已存在的适配器名(否则报错提示设置hotswap=False);
  2. 通过map_state_dict_for_hotswap在 state dict 键中补入适配器名(lora_A.weight→lora_A.<name>.weight,lora_B.bias同理);
  3. 调用check_hotswap_configs_compatible校验新旧适配器配置兼容性;
  4. 调用 PEFT 的hotswap_adapter_from_state_dict完成就地替换。

[!WARNING] Hotswapping 目前不支持作用于 text encoder 的 LoRA,且新 LoRA 只能作用于第一个 LoRA 覆盖的层或其子集。因此加载顺序很重要——建议把覆盖层数最多的 LoRA 放在第一个加载。

7.3 enable_lora_hotswap:不同 rank / 编译模型的准备

当模型已编译,或后续要热替换的 LoRA 与首个 LoRA 的 rank、alpha 不同时,需要调用enable_lora_hotswap(peft.py#L786-L817):

参数默认值说明
target_rank128所有将被加载适配器的最高 rank;不同 rank 的 LoRA 应设为较大值
check_compiled"error"模型已编译时的处理方式:"error"抛错、"warn"告警、"ignore"忽略

调用顺序非常关键:必须在加载第一个 LoRA之前调用enable_lora_hotswap,并在加载第一个 LoRA之后再执行torch.compile,随后方可对后续 LoRA 使用hotswap=True:

# 1. 先启用 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. 再编译 pipeline.unet = torch.compile(pipeline.unet, mode="reduce-overhead", fullgraph=True) # 3. 热替换 pipeline.load_lora_weights( "lordjia/by-feng-zikai", hotswap=True, adapter_name="ikea", )

其底层原理(由 src/diffusers/loaders/peft.py 中_prepare_lora_hotswap_kwargs与 PEFT 的prepare_model_for_compiled_hotswap配合实现):把 LoRA 缩放因子从 float 转成 torch tensor,并将权重张量 padding 到最大所需形状,使权重替换时无需重新分配整个属性、不改变计算图结构。虽然零填充不改变计算结果,但 padding 越大计算越慢。

调试提示:将代码放入with torch._dynamo.config.patch(error_on_recompile=True):上下文,可检测模型是否发生了重编译;若在推理中预期出现多种分辨率,编译时应设置dynamic=True。

八、适配器的保存、卸载与删除

8.1 save_lora_adapter:序列化指定适配器

save_lora_adapter(peft.py#L369-L435)将指定适配器权重保存为文件:

参数默认值说明
save_directory必填保存目录,不存在则自动创建;若传入的是文件路径会报错
adapter_name"default"要序列化的适配器名,多适配器模型下用于区分
upcast_before_savingFalse序列化前是否将模型转为torch.float32
safe_serializationTrue使用 safetensors 保存(pytorch_lora_weights.safetensors);为False时用 pickle(pytorch_lora_weights.bin)
weight_nameNone自定义权重文件名

实现上通过 PEFT 的get_peft_model_state_dict收集指定适配器的 LoRA 参数,并把peft_config序列化后写入 safetensors 元数据(键为lora_adapter_metadata),确保保存的 checkpoint 可被load_lora_adapter完整还原。

8.2 unload_lora / delete_adapters / disable_lora

三者的区别在于「移除」的程度:

  • disable_lora()/enable_lora():临时停用/启用 LoRA 层,权重保留在模型上,可随时恢复,用于 A/B 对比基础模型与 LoRA 效果(peft.py#L702-L746);
  • unload_lora():通过recurse_remove_peft_layers(peft_utils.py#L35-L103)递归地把BaseTunerLayer替换回基础层,删除peft_config,并处理 group offload 的重挂载;常用于 LoRA 融合后的清理(peft.py#L687-L700);
  • delete_adapters(names):针对性地删除指定适配器的 LoRA 层,并同步从peft_config中弹出对应条目;删除全部适配器后peft_config整体移除、_hf_peft_config_loaded复位(peft.py#L748-L784)。

九、训练侧:add_adapter

除了推理,PeftAdapterMixin也支持训练场景。add_adapter(peft.py#L493-L529)将一个空的适配器注入当前模型用于训练:

  • 接收一个 PEFT 的PeftConfig实例(如LoraConfig)和适配器名(默认"default");
  • 注入成功后立即调用set_adapter激活它;
  • 要求 PEFT 版本不低于MIN_PEFT_VERSION(当前仓库中该常量为"0.17.0",见 src/diffusers/utils/constants.py),否则抛出版本不兼容错误。

十、哪些模型支持 PeftAdapterMixin

PeftAdapterMixin被大量 Diffusers 模型类继承,覆盖 UNet、Transformer、ControlNet 等主流架构。从源码检索可见,其使用者包括但不限于:

  • UNet2DConditionModel(src/diffusers/models/unets/unet_2d_condition.py#L76-L78,定义处同时继承了UNet2DConditionLoadersMixin,因此 Pipeline 层的load_lora_weights最终也落到 PEFT 后端);
  • SD3Transformer2DModel(src/diffusers/models/transformers/transformer_sd3.py)等各代 Transformer(FLUX、Hunyuan Video、CogVideoX、Sana、Mochi、Wan、QwenImage、Lumina2 等);
  • ControlNet 系列(src/diffusers/models/controlnets/controlnet.py、controlnet_flux、controlnet_sd3 等);
  • 自编码器(AutoencoderKL 等)与条件编码器。

从源码结构看,这套 mixin 的设计使其可以「即插即用」地挂载到任何继承它的 Diffusers 模型上,这也是它比 Pipeline 层 API 更具通用性的原因。

十一、安装与环境要求

使用PeftAdapterMixin前需安装 PEFT 库:

pip install -U diffusers peft
  • PEFT 后端相关功能(set_adapters、fuse_lora、unload_lora等)在未启用 PEFT 后端时会抛出ValueError: PEFT backend is required ...;
  • 仓库要求的 PEFT 最低版本为0.17.0(src/diffusers/utils/constants.py),add_adapter、set_adapter等方法会通过check_peft_version做版本校验;
  • 若 PEFT 未安装,add_adapter/active_adapters会提示pip install peft。

十二、总结与进阶指引

PeftAdapterMixin是 Diffusers 模型层适配器能力的统一出口,围绕它形成了完整的 LoRA 生命周期:加载(load_lora_adapter)→ 配置自动推导(rank/alpha)→ 多适配器激活与缩放(set_adapters)→ 融合加速(fuse_lora)→ 编译与热替换(enable_lora_hotswap)→ 保存/卸载/删除。

继续深入可参考:

  • 推理实战完整教程:Inference with PEFT,涵盖文生图、文生视频、LoRA 合并(add_weighted_adapter、TIES/DARE 方法)等场景;
  • 核心实现:src/diffusers/loaders/peft.py 与 src/diffusers/utils/peft_utils.py;
  • 单元测试:tests/lora/test_peft_utils.py,可对照验证 rank/alpha 推断规则。

【免费下载链接】diffusers🤗 Diffusers: State-of-the-art diffusion models for image, video, and audio generation in PyTorch.项目地址: https://gitcode.com/GitHub_Trending/di/diffusers

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询