疑难解答
如果您在使用 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)
随机初始化的层
对于某些任务,正确配置配置中的 modules_to_save
以考虑随机初始化的层非常重要。
例如,如果您使用 LoRA 微调语言模型以进行序列分类,则需要这样做,因为 🤗 Transformers 会在模型之上添加一个随机初始化的分类头。如果您没有将此层添加到 modules_to_save
中,则分类头将不会保存。下次加载模型时,您将获得一个不同的随机初始化的分类头,从而导致完全不同的结果。
PEFT 会尝试在您在配置中提供 task_type
参数的情况下正确地猜测 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
中,以避免出现上述问题。
扩展词汇表
对于许多语言微调任务,扩展模型的词汇表是必要的,因为正在引入新的词元。这需要扩展嵌入层以考虑新的词元,并且在保存适配器时,还需要保存嵌入层以及适配器权重。
通过将嵌入层添加到配置的 target_modules
中来保存嵌入层。嵌入层名称必须遵循 Transformers 的标准命名方案。例如,Mistral 配置可能如下所示。
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 检查点。
有关完整示例,请查看 此笔记本。
检查层和模型状态
有时,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()。
如果此选项在不同的用例中都能很好地工作,它可能会在未来成为适配器加载的默认选项。
可重复性
使用批量归一化的模型
加载经过训练的 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"
具有不同的名称,因此请确保名称与您的模型架构匹配。