PEFT 文档

故障排除

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始使用

故障排除

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

示例无法运行

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

  • peft
  • transformers
  • accelerate
  • torch

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

python -m pip install -U <package_name>

从源代码安装 PEFT 对于跟上最新进展非常有用

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

ValueError: 尝试取消缩放 FP16 梯度

此错误可能是因为模型加载时使用了 torch_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 会在适当的情况下自动将适配器权重的 dtype 从 torch.float16torch.bfloat16 提升到 torch.float32。要阻止此行为,您可以将 autocast_adapter_dtype=False 传递给 ~get_peft_model()from_pretrained()load_adapter()

从已加载的 PEFT 模型中获得错误结果

从已加载的 PEFT 模型获得不良结果可能有几个原因,下面列出了一些原因。如果您仍然无法排除故障,请查看是否有人在 GitHub 上遇到过类似的 问题,如果找不到,请打开一个新问题。

在打开问题时,如果您提供一个重现该问题的最小代码示例,将会有很大帮助。此外,请报告加载的模型性能是否与微调前模型的性能水平相同,是否表现出随机水平,或者是否仅比预期稍差。这些信息有助于我们更快地识别问题。

随机偏差

如果您的模型输出与之前的运行不完全相同,则可能是随机元素存在问题。例如

  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)

随机初始化的层

对于某些任务,正确配置 config 中的 modules_to_save 以考虑随机初始化的层非常重要。

例如,如果您使用 LoRA 微调用于序列分类的语言模型,则这是必要的,因为 🤗 Transformers 在模型顶部添加了一个随机初始化的分类头。如果您不将此层添加到 modules_to_save,则不会保存分类头。下次加载模型时,您将获得一个不同的随机初始化的分类头,从而导致完全不同的结果。

如果您在 config 中提供 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.

提到的层应添加到 config 中的 modules_to_save 中,以避免描述的问题。

例如,当加载使用 DeBERTa 架构进行序列分类的模型时,您会看到警告,指出以下权重是新初始化的:['classifier.bias', 'classifier.weight', 'pooler.dense.bias', 'pooler.dense.weight']。由此可见,classifierpooler 层应添加到:modules_to_save=["classifier", "pooler"]

扩展词汇表

对于许多语言微调任务,扩展模型的词汇表是必要的,因为引入了新的 tokens。这需要扩展嵌入层以适应新的 tokens,并在保存适配器时同时存储嵌入层和适配器权重。

通过将其添加到 config 的 target_modules 来保存嵌入层。嵌入层名称必须遵循 Transformers 的标准命名方案。例如,Mistral config 可能如下所示

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

一旦添加到 target_modules,如果模型具有 get_input_embeddingsget_output_embeddings,PEFT 会在保存适配器时自动存储嵌入层。这通常是 Transformers 模型的情况。

如果模型的嵌入层不遵循 Transformer 的命名方案,您仍然可以通过在保存适配器时手动传递 save_embedding_layers=True 来保存它

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

对于推理,首先加载基础模型,并按照训练模型之前的方式调整其大小。在调整基础模型大小后,您可以加载 PEFT 检查点。

有关完整示例,请查看 此 notebook

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

当您加载已在任务(例如,分类)上训练的 PEFT 模型时,您可能会收到类似以下的警告

LlamaForSequenceClassification 的某些权重未从模型检查点 meta-llama/Llama-3.2-1B 初始化,而是新初始化的:[‘score.weight’]。您可能应该在下游任务上训练此模型,以便能够将其用于预测和推理。

尽管这看起来很可怕,但很可能没什么可担心的。此警告来自 Transformers,而不是 PEFT 特有的警告。它让您知道,随机初始化的分类头 (score) 已附加到基础模型,并且必须训练该头才能产生合理的预测。

当您在训练模型之前收到此警告时,如果您正确地将 task_type 参数传递给 PEFT config,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 权重,因此它仍然会发出警告。

与往常一样,最佳实践是进行一些验证,以确保模型在推理中正常工作。

检查层和模型状态

有时,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_typepeft_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, torch_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 权重这样的适配器通常应该很快。但是,在某些用例中,适配器权重可能非常大,或者用户需要加载大量适配器,在这种情况下,加载时间可能会累积。造成这种情况的原因是适配器权重首先被初始化,然后被加载的权重覆盖,这是一种浪费。为了加快加载速度,您可以将 low_cpu_mem_usage=True 参数传递给 from_pretrained()load_adapter()

如果此选项在不同的用例中运行良好,则将来可能会成为适配器加载的默认选项。

可复现性

使用批归一化 (batch norm) 的模型

当加载一个训练好的 PEFT 模型时,如果基础模型使用了批归一化(例如 torch.nn.BatchNorm1dtorch.nn.BatchNorm2d),您可能会发现无法复现完全相同的输出。这是因为批归一化层在训练期间会跟踪运行统计信息,但这些统计信息不属于 PEFT 检查点的一部分。因此,当您加载 PEFT 模型时,将使用基础模型的运行统计信息(即来自使用 PEFT 训练之前的信息)。

根据您的用例,这可能不是什么大问题。但是,如果您需要 100% 可复现的输出,您可以通过将批归一化层添加到 modules_to_save 来实现。以下是使用 resnet 和 LoRA 的示例。请注意,我们设置了 modules_to_save=["classifier", "normalization"]。我们需要 "classifier" 参数是因为我们的任务是图像分类,并且我们添加 "normalization" 参数以确保批归一化层保存在 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"],
),

根据您使用的模型类型,批归一化层可能具有与 "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。

< > 在 GitHub 上更新