PEFT 文档
故障排除
并获得增强的文档体验
开始使用
故障排除
如果您在使用 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.float16
和 torch.bfloat16
提升到 torch.float32
。要阻止此行为,您可以将 autocast_adapter_dtype=False
传递给 ~get_peft_model()、from_pretrained() 和 load_adapter()。
从已加载的 PEFT 模型中获得错误结果
从已加载的 PEFT 模型获得不良结果可能有几个原因,下面列出了一些原因。如果您仍然无法排除故障,请查看是否有人在 GitHub 上遇到过类似的 问题,如果找不到,请打开一个新问题。
在打开问题时,如果您提供一个重现该问题的最小代码示例,将会有很大帮助。此外,请报告加载的模型性能是否与微调前模型的性能水平相同,是否表现出随机水平,或者是否仅比预期稍差。这些信息有助于我们更快地识别问题。
随机偏差
如果您的模型输出与之前的运行不完全相同,则可能是随机元素存在问题。例如
- 请确保它处于
.eval()
模式,这很重要,例如,如果模型使用 dropout - 如果您在语言模型上使用 generate,则可能存在随机抽样,因此获得相同的结果需要设置随机种子
- 如果您使用了量化并合并了权重,由于舍入误差,预计会出现小的偏差
模型加载不正确
请确保您正确加载模型。一个常见的错误是尝试使用 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']
。由此可见,classifier
和 pooler
层应添加到: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_embeddings 和 get_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_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, 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.BatchNorm1d
或 torch.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。