PEFT documentation

故障排除

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v0.21.0).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

故障排除

如果在使用 PEFT 时遇到任何问题,请查看以下常见问题及其解决方案列表。

示例无法运行

示例通常依赖于最新版本的软件包,因此请确保它们已更新到最新。特别是,请检查以下软件包版本:

  • peft
  • transformers
  • accelerate
  • torch

通常,您可以在 Python 环境中运行以下命令来更新软件包版本:

python -m pip install -U <package_name>

从源代码安装 PEFT 有助于跟进最新进展:

python -m pip install git+https://github.com/huggingface/peft

Dtype 相关问题

ValueError: 尝试对 FP16 梯度进行反缩放

此错误很可能是由于模型以 dtype=torch.float16 加载,随后在自动混合精度(AMP)上下文中使用所致,例如在 🤗 Transformers 的 Trainer 类中设置 fp16=True。原因是使用 AMP 时,可训练权重不应使用 fp16。若要在不将整个模型以 fp32 加载的情况下使其正常工作,请在代码中添加以下内容:

peft_model = get_peft_model(...)

# add this:
for param in model.parameters():
    if param.requires_grad:
        param.data = param.data.float()

# proceed as usual
trainer = Trainer(model=peft_model, fp16=True, ...)
trainer.train()

或者,您可以使用 cast_mixed_precision_params() 函数来正确转换权重:

from peft import cast_mixed_precision_params

peft_model = get_peft_model(...)
cast_mixed_precision_params(peft_model, dtype=torch.float16)

# proceed as usual
trainer = Trainer(model=peft_model, fp16=True, ...)
trainer.train()

从 PEFT 版本 v0.12.0 开始,PEFT 会在适当的情况下自动将适配器权重的数据类型从 torch.float16 和 torch.bfloat16 提升为 torch.float32。若要防止此行为,您可以将 autocast_adapter_dtype=False 传递给 ~get_peft_model()、from_pretrained() 以及 load_adapter()。

选择适配器的 dtype

大多数 PEFT 方法(例如 LoRA)通过添加可训练的适配器权重来工作。默认情况下,这些权重以 float32 数据类型(fp32)存储,即相对较高的精度。因此,即使基础模型以 float16(fp16)或 bfloat16(bf16)加载,适配器权重仍为 float32。在前向传播过程中计算适配器结果时,输入通常为基础模型的数据类型,因此如有必要会向上转换为 float32,然后再转换回原始数据类型。

如果您希望适配器权重采用与基础模型相同的较低精度,即 float16 或 bfloat16,可以在创建模型(~get_peft_model())或加载模型(from_pretrained())时传入 autocast_adapter_dtype=False。这样做有一些优缺点:

半精度适配器的优势:

  • 计算速度略快
  • 内存占用略少
  • 检查点文件更小(大小减半)

半精度适配器的缺点:

  • 损失略高
  • 溢出或下溢的风险更高

请注意,对于大多数使用场景,整体运行时间和内存开销主要由基础模型的大小和数据集决定,而 PEFT 适配器的 dtype 影响较小。

训练可以运行,但模型没有提升

如果训练看起来正常(损失下降,检查点保存无错误),但生成的模型性能并不优于基础模型,请检查可训练参数在训练过程中是否确实发生了变化:

# before training
sample_param = next(p for p in model.parameters() if p.requires_grad)
before = sample_param.detach().clone()

# ... train for at least three optimizer steps ...

after = next(p for p in model.parameters() if p.requires_grad)
print("weights updated:", not torch.allclose(before, after.detach().cpu()))

缺少前向或反向钩子

如果权重没有移动,一个可能的原因是参数引用在参数于目标设备上实例化之前就被捕获了。这种情况可能发生在基础模型在参数尚未移动到目标设备时就被 get_peft_model() 包装,随后第三方库注册了前向/反向钩子(例如来自差分隐私框架的逐样本梯度钩子),或者在第一次前向调用之前创建了优化器。当参数稍后实例化时,这些钩子和优化器引用指向的是过时的张量,导致每次更新都成为静默的空操作。

为避免此问题,请使用以下顺序:

  1. 加载基础模型并将其移动到目标设备(例如 model.to(device))。
  2. 使用 get_peft_model() 将其包装起来。
  3. 然后才能注册钩子或创建优化器。

目标模块与模型不匹配

如果训练过程没有报错,但损失值几乎不下降——或者模型在微调后没有表现出任何改进——那么配置的 target_modules(如果用户未另行指定,则为默认的 target_modules)可能与模型中实际的模块名称不匹配。这种情况在使用 Mamba、Jamba 或 NemotronH 等混合架构时很常见,因为这些架构使用的层名称与标准 Transformer 模型不同。

PEFT 仅在配置的 target_modules 中没有任何一个与模型中的模块匹配时才会抛出错误。如果部分匹配而部分不匹配,不匹配的条目会被静默跳过——不会发出任何警告。模型中很大一部分可能保持冻结状态,而没有任何提示。

第一个诊断步骤是检查可训练参数的数量:

from peft import LoraConfig, get_peft_model

config = LoraConfig(target_modules=["q_proj", "v_proj", "gate_proj"])
model = get_peft_model(base_model, config)
model.print_healthcheck()

如果报告的数值远低于预期,请检查模型中的适配器层:

print(model.get_layer_status())

这将返回一个 TunerLayerStatus 条目列表,显示每个适配器层的名称、模块类型、启用状态以及当前激活的适配器。或者,打印模型的 repr(print(model))可以查看完整的模块层级结构概览,这对于在不熟悉的架构中识别正确的层名称非常有用。

任何与模型中模块不对应的 target_modules 条目都会被静默跳过。请更新 target_modules 以匹配实际的模块名称。

此问题会影响所有使用 target_modules 的 PEFT 方法(如 LoRA、LoHa、IA³ 等),而不仅仅是 LoRA。无论采用哪种方法,诊断步骤均相同。如果不确定特定架构使用的模块名称,请在配置适配器之前查阅模型文档或检查 model.named_modules()。

加载的 PEFT 模型结果不佳

从加载的 PEFT 模型中获得较差结果可能有多种原因,以下列出了这些原因。如果您仍然无法排查问题,请查看 GitHub 上是否有人遇到过类似的问题;如果找不到,请提交一个新问题。

在提交 issue 时,提供一个能复现该问题的最小代码示例会非常有帮助。此外,请报告加载的模型表现是否与微调前处于同一水平、是否处于随机水平,还是仅略低于预期。这些信息有助于我们更快地定位问题。

随机偏差

如果模型的输出与之前的运行结果不完全一致,则可能存在与随机元素相关的问题。例如:

  1. 请确保它处于 .eval() 模式,这一点非常重要,例如当模型使用 dropout 时
  2. 如果将 generate 用于语言模型,可能会涉及随机采样,因此要获得相同的结果,需要设置随机种子
  3. 如果您使用了量化并合并了权重,由于舍入误差,出现小的偏差是预期之中的

模型加载错误

请确保正确加载模型。一个常见的错误是尝试使用 get_peft_model() 来加载 已训练 的模型,这是不正确的。相反,加载代码应如下所示:

from peft import PeftModel, PeftConfig

base_model = ...  # to load the base model, use the same code as when you trained it
config = PeftConfig.from_pretrained(peft_model_id)
peft_model = PeftModel.from_pretrained(base_model, peft_model_id)

绕过适配器的优化执行路径

某些优化后的执行路径会直接读取模块权重,而不是调用其 forward 方法。这可能会在没有任何警告的情况下跳过未合并适配器所做的更改,即使该适配器处于激活状态。此问题可能影响不同的模块和 PEFT 方法。

例如,PyTorch 原生的 torch.nn.TransformerEncoder 在评估期间,若使用 torch.no_grad() 或 torch.inference_mode(),可能会走融合路径。该路径可能会跳过 TransformerEncoderLayer 子模块中的适配器,例如 linear1 和 linear2 上的 LoRA 适配器。这可能导致训练正常,但评估时忽略了适配器的更改。请参阅 issue #3976 以获取复现步骤。

如果您怀疑存在此问题,请先使用 get_layer_status() 检查适配器是否已启用并处于活动状态。在评估模式下,比较启用和未启用适配器时相同输入的输出,然后再比较禁用优化时的输出。请使用一个会改变输出的已训练适配器,因为新初始化的 LoRA 适配器通常没有影响。微小的数值差异是正常的。

对于处于 eager 模式下的原生编码器上的未合并适配器,请比较相同张量输入 x 的输出:

import torch

model.eval()
previous = torch.backends.mha.get_fastpath_enabled()
try:
    with torch.inference_mode():
        torch.backends.mha.set_fastpath_enabled(True)
        fast_output = model(x)

        torch.backends.mha.set_fastpath_enabled(False)
        reference_output = model(x)
finally:
    torch.backends.mha.set_fastpath_enabled(previous)

print((fast_output - reference_output).abs().max().item())

仅启用后端标志并不意味着融合路径会执行;其使用还取决于模型、输入和运行时条件。请检查外围模块的 forward 实现,查看是否存在直接的权重访问或融合算子调用。在使用前向钩子进行调试时要小心:注册钩子本身可能会禁用某项优化,从而改变你正在调查的行为。

对于处于 eager 模式的原生编码器,torch.backends.mha.set_fastpath_enabled(False) 是一种可保持适配器未合并状态的变通方案。此设置作用于整个进程,可能会影响其他模型及性能,且不应由并发请求独立切换。对于其他优化路径,请禁用受影响的优化,或使用会调用适配后模块的实现。如果 PEFT 方法和量化配置允许,你也可以使用 merge_and_unload() 合并适配器,并使用返回的模型进行推理。此时,适配器的更改将包含在基础权重中。由于合并操作会移除适配器包装器,因此如果你需要切换适配器或恢复训练,请保留原始检查点。

随机初始化的层

对于某些任务,在配置中正确设置 modules_to_save 以处理随机初始化的层非常重要。

例如,如果你使用 LoRA 对语言模型进行序列分类的微调,这一步骤就是必需的,因为 🤗 Transformers 会在模型顶部添加一个随机初始化的分类头。如果你没有将该层添加到 modules_to_save 中,分类头将不会被保存。下次加载模型时,你会得到一个不同的随机初始化的分类头,从而导致完全不同的结果。

如果你在配置中提供了 task_type 参数,PEFT 会尝试正确推断 modules_to_save。对于遵循标准命名规范的 transformers 模型,这通常可以正常工作。不过,由于我们无法保证所有模型都遵循该命名规范,因此始终建议进行二次检查。

当您加载一个包含随机初始化层的 transformers 模型时,您应该会看到类似以下的警告:

Some weights of <MODEL> were not initialized from the model checkpoint at <ID> and are newly initialized: [<LAYER_NAMES>].
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.

应将上述层添加到配置中的 modules_to_save,以避免出现上述问题。

例如,在加载一个使用 DeBERTa 架构进行序列分类的模型时,您会看到一条警告,提示以下权重是新初始化的:['classifier.bias', 'classifier.weight', 'pooler.dense.bias', 'pooler.dense.weight']。由此可知,应将 classifier 和 pooler 层添加到:modules_to_save=["classifier", "pooler"]。

扩展词汇表

在许多语言微调任务中,由于会引入新的 token,因此需要扩展模型的词汇表。这要求扩展嵌入层以容纳新 token,并且根据微调方法的不同,在保存适配器时除了适配器权重外,还需要保存嵌入层。有几种方法可以实现这一点,按参数效率排序如下:

  • 可训练 token,仅训练指定的 token,可选择仅存储更新后的值
  • 在嵌入矩阵上训练适配器,可选择仅存储更新后的值
  • 嵌入层的全量微调

使用可训练标记

让我们从可训练 token 开始,在本例中即 LoRA 集成。如果你只想训练新的嵌入而不训练其他内容,请参阅独立文档。

要启用嵌入层的选择性 token 训练,您需要通过 trainable_token_indices 参数提供新添加 token 的 token id。如果存在多个嵌入层,您可以选择性地指定要针对的层。对于 Mistral 模型,示例如下:

new_tokens = ['<think>', '</think>']
tokenizer.add_tokens(new_tokens)
base_model.resize_token_embeddings(len(tokenizer))

lora_config = LoraConfig(
    ...,
    trainable_token_indices={'embed_tokens': tokenizer.convert_tokens_to_ids(new_tokens)},
)

如果您的模型使用了共享权重(例如 lm_head),可训练 token 会尝试解析这些权重并同步更新它们,因此在这种情况下无需添加 modules_to_save=["lm_head"]。这仅在模型遵循 Transformers 的权重共享约定时有效。

使用 model.save_pretrained 保存模型时,由于嵌入矩阵已被调整大小,出于谨慎考虑,可能会保存完整的嵌入矩阵,而不仅仅是差异部分。为了节省空间,您可以在调用 save_pretrained 时设置 save_embedding_layers=False 来禁用此行为。只要您不通过其他方式修改嵌入矩阵,这样做就是安全的,因为此类更改不会被可训练 token 跟踪。

使用适配器,例如 LoRA

通过将其添加到适配器配置的 target_modules 中来准备嵌入层。例如,Mistral 的配置可能如下所示:

config = LoraConfig(..., target_modules=["embed_tokens", "lm_head", "q_proj", "v_proj"])

将其添加到 target_modules 后,如果模型具有 get_input_embeddings 和 get_output_embeddings,PEFT 在保存适配器时会自动存储嵌入层。Transformers 模型通常满足此条件。

如果模型的嵌入层未遵循 Transformer 的命名规范,但仍实现了 get_input_embeddings,那么在保存适配器时手动传入 save_embedding_layers=True 即可将其保存:

model = get_peft_model(...)
# train the model
model.save_pretrained("my_adapter", save_embedding_layers=True)

在推理时,首先加载基础模型,并以训练模型之前相同的方式调整其大小。调整基础模型后,即可加载 PEFT 检查点。

如需完整示例,请查看此笔记本。

全量微调

全量微调在 VRAM 或存储空间方面成本更高,但如果其他方法都不可行,你可以退而求其次采用这种方式,看看是否适合你。实现方法是向 modules_to_save 中添加嵌入层的名称。请注意,你还需要添加绑定层,例如 lm_head。以下是使用 LoRA 的 Mistral 模型示例:

config = LoraConfig(..., modules_to_save=["embed_tokens", "lm_head"], target_modules=["q_proj", "v_proj"])

收到关于“权重未从模型检查点初始化”的警告

加载在某个任务(例如分类)上训练过的 PEFT 模型时,您可能会看到如下警告:

LlamaForSequenceClassification 的部分权重未从 meta-llama/Llama-3.2-1B 的模型检查点初始化,而是进行了全新初始化:[‘score.weight’]。您可能需要针对下游任务对该模型进行训练,才能将其用于预测和推理。

虽然这看起来有些吓人,但通常无需担心。此警告来自 Transformers,并非 PEFT 特有的警告。它提示你,基础模型上附加了一个随机初始化的分类头(score),该分类头需要经过训练才能产生合理的预测结果。

当你在训练模型之前收到此警告时,如果你已正确地将 task_type 参数传递给 PEFT 配置,PEFT 会自动确保分类头可训练。

from peft import LoraConfig, TaskType

lora_config = LoraConfig(..., task_type=TaskType.SEQ_CLS)

如果你的分类头未遵循 Transformers 的常规命名约定(这种情况很少见),则必须在 modules_to_save 中明确告知 PEFT 该头的名称。

lora_config = LoraConfig(..., modules_to_save=["name-of-classification-head"])

要检查分类头的名称,请打印模型,它应该是最后一个模块。

如果推理代码中出现此警告,即训练模型之后加载 PEFT 模型时,您必须先加载 Transformers 模型。由于 Transformers 不知道您稍后会加载 PEFT 权重,因此仍会发出该警告。

一如既往,最佳实践是通过运行一些验证来确保模型在推理时能正常工作。

运行训练健康检查

在训练之前,使用 print_healthcheck() 获取 PEFT 模型状态的紧凑且易于阅读的摘要。它基于模型和层状态 API(见下一节),并标记可疑状态,例如不一致的适配器、已合并的适配器或可疑的可训练参数数量。

peft_model.print_healthcheck()

请特别注意报告末尾是否包含“Findings”部分,该部分仅在发现异常时才会出现。

如果您更喜欢字典输出(可序列化为 JSON),请改用 healthcheck():

healthcheck = peft_model.healthcheck()
print(healthcheck.get("findings", "all good")

此检查仅检查模型及其适配器。它不会验证您的训练循环、优化器或数据集。

检查层和模型状态

有时 PEFT 模型可能会进入异常状态,尤其是在处理多个适配器时。关于哪些适配器存在、哪个处于激活状态、哪个已合并等问题,可能会产生混淆。为了帮助排查此问题,请调用 get_layer_status() 和 get_model_status() 方法。

get_layer_status() 方法会提供每个目标层当前处于激活、已合并和可用状态的适配器的详细概览。

>>> from transformers import AutoModel
>>> from peft import get_peft_model, LoraConfig

>>> model_id = "google/flan-t5-small"
>>> model = AutoModel.from_pretrained(model_id)
>>> model = get_peft_model(model, LoraConfig())

>>> model.get_layer_status()
[TunerLayerStatus(name='model.encoder.block.0.layer.0.SelfAttention.q',
                  module_type='lora.Linear',
                  enabled=True,
                  active_adapters=['default'],
                  merged_adapters=[],
                  requires_grad={'default': True},
                  available_adapters=['default']),
 TunerLayerStatus(name='model.encoder.block.0.layer.0.SelfAttention.v',
                  module_type='lora.Linear',
                  enabled=True,
                  active_adapters=['default'],
                  merged_adapters=[],
                  requires_grad={'default': True},
                  available_adapters=['default']),
...]

>>> model.get_model_status()
TunerModelStatus(
    base_model_type='T5Model',
    adapter_model_type='LoraModel',
    peft_types={'default': 'LORA'},
    trainable_params=344064,
    total_params=60855680,
    num_adapter_layers=48,
    enabled=True,
    active_adapters=['default'],
    merged_adapters=[],
    requires_grad={'default': True},
    available_adapters=['default'],
)

在模型状态输出中,您应留意标记为 "irregular" 的条目。这表示 PEFT 检测到模型状态不一致。例如,如果 merged_adapters="irregular",则意味着至少有一个适配器在某些目标模块上已合并,而在其他模块上未合并。因此,推理结果很可能不正确。

解决此问题的最佳方法是重新加载整个模型和适配器检查点。请确保不要对模型执行任何不正确的操作,例如仅在部分模块上手动合并适配器,而在其他模块上不合并。

将层状态转换为 pandas DataFrame,以便更直观地查看。

from dataclasses import asdict
import pandas as pd

df = pd.DataFrame(asdict(layer) for layer in model.get_layer_status())

如果非 PEFT 模型在底层使用了 PEFT 层,也可以获取这些信息,但在这种情况下,某些信息(如 base_model_type 或 peft_types)无法确定。例如,你可以在 diffusers 模型上这样调用:

>>> import torch
>>> from diffusers import StableDiffusionPipeline
>>> from peft import get_model_status, get_layer_status

>>> path = "runwayml/stable-diffusion-v1-5"
>>> lora_id = "takuma104/lora-test-text-encoder-lora-target"
>>> pipe = StableDiffusionPipeline.from_pretrained(path, dtype=torch.float16)
>>> pipe.load_lora_weights(lora_id, adapter_name="adapter-1")
>>> pipe.load_lora_weights(lora_id, adapter_name="adapter-2")
>>> pipe.set_lora_device(["adapter-2"], "cuda")
>>> get_layer_status(pipe.text_encoder)
[TunerLayerStatus(name='text_model.encoder.layers.0.self_attn.k_proj',
                  module_type='lora.Linear',
                  enabled=True,
                  active_adapters=['adapter-2'],
                  merged_adapters=[],
                  requires_grad={'adapter-1': False, 'adapter-2': True},
                  available_adapters=['adapter-1', 'adapter-2'],
                  devices={'adapter-1': ['cpu'], 'adapter-2': ['cuda']}),
 TunerLayerStatus(name='text_model.encoder.layers.0.self_attn.v_proj',
                  module_type='lora.Linear',
                  enabled=True,
                  active_adapters=['adapter-2'],
                  merged_adapters=[],
                  requires_grad={'adapter-1': False, 'adapter-2': True},
                  devices={'adapter-1': ['cpu'], 'adapter-2': ['cuda']}),
...]

>>> get_model_status(pipe.unet)
TunerModelStatus(
    base_model_type='other',
    adapter_model_type='None',
    peft_types={},
    trainable_params=797184,
    total_params=861115332,
    num_adapter_layers=128,
    enabled=True,
    active_adapters=['adapter-2'],
    merged_adapters=[],
    requires_grad={'adapter-1': False, 'adapter-2': True},
    available_adapters=['adapter-1', 'adapter-2'],
    devices={'adapter-1': ['cpu'], 'adapter-2': ['cuda']},
)

速度

加载适配器权重速度较慢

加载 LoRA 权重等适配器通常比加载基础模型要快得多。然而,在某些使用场景中,适配器权重可能相当大,或者用户需要加载大量适配器——在这种情况下,加载时间会累积增加。造成这一现象的原因是,适配器权重会先被初始化,然后才被加载的权重覆盖,这一过程存在资源浪费。为了加快加载速度,您可以在调用 from_pretrained() 和 load_adapter() 时传入 low_cpu_mem_usage=True 参数。

如果该选项在不同使用场景中表现良好,未来它可能会成为加载适配器的默认方式。

可复现性

使用批归一化的模型

在加载训练好的 PEFT 模型时,如果基础模型使用了 batch norm(例如 torch.nn.BatchNorm1d 或 torch.nn.BatchNorm2d),您可能会发现无法复现完全相同的输出。这是因为 batch norm 层在训练过程中会跟踪 running stats,但这些统计量并不包含在 PEFT 检查点中。因此,当您加载 PEFT 模型时,将使用基础模型的 running stats(即使用 PEFT 训练之前的统计量)。

根据您的具体使用场景,这可能并不重要。但如果您需要输出结果 100% 可复现,可以通过将 batch norm 层添加到 modules_to_save 来实现。下面是一个使用 resnet 和 LoRA 的示例。请注意,我们设置了 modules_to_save=["classifier", "normalization"]。我们需要 "classifier" 参数,因为我们的任务是图像分类;我们添加 "normalization" 参数,以确保 batch norm 层被保存在 PEFT 检查点中。

from transformers import AutoModelForImageClassification
from peft import LoraConfig, get_peft_model

model_id = "microsoft/resnet-18"
base_model = AutoModelForImageClassification.from_pretrained(self.model_id)
config = LoraConfig(
    target_modules=["convolution"],
    modules_to_save=["classifier", "normalization"],
),

根据您使用的模型类型,batch norm 层的名称可能与"normalization"不同,因此请确保该名称与您的模型架构相匹配。

版本不匹配

由于意外的关键字参数,加载配置时出错

当您遇到如下所示的错误时,这意味着您尝试加载的适配器是使用比您系统上安装的 PEFT 版本更新的版本进行训练的。

TypeError: LoraConfig.__init__() got an unexpected keyword argument <argument-name>

解决此问题的最佳方法是安装最新版本的 PEFT:

python -m pip install -U PEFT

如果适配器是从 PEFT 的源代码安装(即 PEFT 的未发布版本)中训练得到的,那么您也需要从源代码安装 PEFT。

python -m pip install -U git+https://github.com/huggingface/peft.git

如果无法升级 PEFT,您可以尝试以下变通方法。

假设错误信息指出未知的关键字参数名为 foobar。在此 PEFT 适配器的 adapter_config.json 中查找 foobar 条目,并将其从文件中删除。然后保存文件并再次尝试加载模型。

该方案在大多数情况下有效。只要它是 foobar 的默认值,就可以忽略。但是,当它被设置为其他值时,你将得到错误的结果。建议升级 PEFT。

适配器处理

同时使用多个适配器

PEFT 允许你在同一个模型上创建多个适配器。这在许多情况下都非常有用。例如,在推理阶段,你可能希望从同一个基础模型中提供两个微调模型的推理服务,而不是为每个微调模型分别加载一次基础模型,后者会消耗更多内存。此外,可以同时激活多个适配器。这样,模型就可以同时利用所有这些适配器所学到的知识。举个例子,如果你有一个扩散模型,你可能希望使用一个 LoRA 适配器来改变风格,并使用另一个不同的适配器来改变主题。

在除提示学习(prompt learning)方法(如 p-tuning、prefix tuning 等)之外的所有 PEFT 方法(LoRA、LoHa、IA³ 等)中,通常可以同时激活多个适配器。以下示例展示了如何实现这一点:

from transformers import AutoModelForCausalLM
from peft import PeftModel

model_id = ...
base_model = AutoModelForCausalLM.from_pretrained(model_id)
model = PeftModel.from_pretrained(base_model, lora_path_0)  # default adapter_name is 'default'
model.load_adapter(lora_path_1, adapter_name="other")
# the 'other' adapter was loaded but it's not active yet, so to activate both adapters:
model.base_model.set_adapter(["default", "other"])

在上面的示例中,可以看到我们需要调用 model.base_model.set_adapter(["default", "other"])。为什么不能调用 model.set_adapter(["default", "other"])?遗憾的是,正如前文所述,某些 PEFT 方法不支持同时激活多个适配器,因此无法这样做。

也可以同时训练两个适配器,但请务必确保优化器已知晓两个适配器的权重。否则,只有一个适配器会收到更新。

from transformers import AutoModelForCausalLM
from peft import LoraConfig, get_peft_model

model_id = ...
base_model = AutoModelForCausalLM.from_pretrained(model_id)
lora_config_0 = LoraConfig(...)
lora_config_1 = LoraConfig(...)
model = get_peft_model(base_model, lora_config_0)
model.add_adapter(adapter_name="other", peft_config=lora_config_1)

如果我们现在调用:

from transformers import Trainer

trainer = Trainer(model=model,  ...)
trainer.train()

或

optimizer = torch.optim.AdamW([param for param in model.parameters() if param.requires_grad], ...)

那么第二个 LoRA 适配器("other")将不会被训练。这是因为它当前处于非激活状态,这意味着其参数的 requires_grad 属性被设置为 False,优化器会忽略它。因此,请确保在初始化优化器之前激活所有需要训练的适配器:

# activate all adapters
model.base_model.set_adapter(["default", "other"])
trainer = Trainer(model=model,  ...)
trainer.train()

本节介绍如何在同一模型上使用多个同类型的适配器,例如同时使用多个 LoRA 适配器。它不适用于在同一模型上使用不同类型的适配器,例如一个 LoRA 适配器和一个 LoHa 适配器。如需了解此用法,请参阅 PeftMixedModel。

仅合并部分活动适配器

当适配器权重合并到基础权重中时,受影响的层在前向传播过程中不再应用任何适配器,仅使用合并后的基础权重。因此,如果多个适配器处于激活状态但只有部分被合并,那些仍然激活但未合并的适配器将被静默地忽略,即不会被应用:

model = get_peft_model(base_model, lora_config_0, adapter_name="default")
model.add_adapter("other", lora_config_1)
model.base_model.set_adapter(["default", "other"])
output_both = model(**inputs)

model.merge_adapter(adapter_names=["default"])
output_merged = model(**inputs)  # this is *not* the same as output_both

在此,output_merged 是仅由 "default" 适配器产生的输出,尽管 "other" 仍被列为处于激活状态:

>>> model.get_layer_status()[0].active_adapters
['default', 'other']
>>> model.get_layer_status()[0].merged_adapters
['default']

当合并一个未激活的适配器时,情况也是如此:此后,模型将返回合并后的适配器的输出,而当前激活的适配器将被忽略。在这两种情况下,都不会引发任何错误或警告。适配器权重本身不受影响,因此调用 unmerge_adapter() 可以恢复组合后的输出。

为避免此问题,请合并所有活动适配器,即在不指定 adapter_names 的情况下调用 model.merge_adapter(),或者在更改活动适配器之前先执行取消合并操作。

对于支持合并的 PEFT 方法(LoRA、LoHa、LoKr、IA³、OFT 等),此行为是一致的,因为它们在前向传播中共享相同的逻辑。

此行为令人困惑,计划在 PEFT v1.0 版本中更改。

Update on GitHub