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
Dtype 相关问题
ValueError: Attempting to unscale FP16 gradients
这个错误可能是因为模型以 `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()。
选择适配器的数据类型 (dtype)
大多数 PEFT 方法,如 LoRA,通过添加可训练的适配器权重来工作。默认情况下,这些权重以 float32 (fp32) 数据类型存储,即以相对较高的精度存储。因此,即使基础模型以 float16 (fp16) 或 bfloat16 (bf16) 加载,适配器权重也是 float32。在正向传播计算适配器结果时,输入通常会是基础模型的 dtype,因此如有必要,它将被上转换为 float32,然后转换回原始 dtype。
如果您希望适配器权重具有基础模型的较低精度,即 float16 或 bfloat16,您可以在创建模型 (~get_peft_model()) 或加载模型 (from_pretrained()) 时传递 `autocast_adapter_dtype=False`。这样做有利有弊:
半精度适配器的优点:
- 计算速度略快
- 内存占用略少
- 检查点文件更小(大小减半)
半精度适配器的缺点:
- 损失略高
- 溢出或下溢的风险更高
请注意,对于大多数用例,总运行时间和内存成本将由基础模型的大小和数据集决定,而 PEFT 适配器的 dtype 影响较小。
从已加载的 PEFT 模型得到糟糕的结果
从加载的 PEFT 模型获得较差结果的原因可能有多种,下面列出了一些。如果您仍然无法解决问题,请查看 GitHub 上是否有其他人遇到类似的问题,如果找不到,请开一个新的 issue。
在开 issue 时,提供一个可重现问题的最小代码示例会非常有帮助。另外,请报告加载的模型性能是否与微调前相同,是否是随机水平,或者只是比预期略差。这些信息有助于我们更快地确定问题。
随机偏差
如果您的模型输出与之前的运行不完全相同,可能存在随机因素的问题。例如:
- 请确保模型处于 `.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)
随机初始化的层
对于某些任务,正确配置配置中的 `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"]`。
扩展词汇表
对于许多语言微调任务,由于引入了新的词元,扩展模型的词汇表是必要的。这需要扩展嵌入层以适应新的词元,并且根据微调方法的不同,在保存适配器时除了适配器权重外,还需要存储嵌入层。有几种实现方式,按参数效率排序如下:
- 可训练词元,仅训练指定的词元,可选地只存储更新的值。
- 在嵌入矩阵上训练一个适配器,可选地只存储更新的值。
- 对嵌入层进行全量微调。
使用可训练词元
让我们从可训练词元开始,这里是它的 LoRA 集成。如果您只对训练新的嵌入而不训练其他任何东西感兴趣,请参考独立文档。
要启用嵌入层的选择性词元训练,您需要通过 `trainable_token_indices` 参数提供新添加词元的 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`),可训练词元会尝试解析这些并保持它们的更新,因此在这种情况下不需要添加 `modules_to_save=["lm_head"]`。这仅在模型使用 Transformers 的权重绑定约定时有效。
使用 `model.save_pretrained` 保存模型可能会保存整个嵌入矩阵而不是仅仅保存差异,这是因为嵌入矩阵被调整了大小。为了节省空间,您可以在调用 `save_pretrained` 时设置 `save_embedding_layers=False` 来禁用此行为。只要您不同时通过其他方式修改嵌入矩阵,这样做是安全的,因为这些更改不会被可训练词元跟踪。
使用适配器,例如 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 检查点。
有关完整示例,请查看此 notebook。
全量微调
全量微调在 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 权重,它仍然会给出这个警告。
一如既往,最佳实践是通过对模型进行一些验证来确保其在推理中正常工作。
检查层和模型状态
有时,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 是推荐的解决方案。
< > 在 GitHub 上更新