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)

随机初始化的层

对于某些任务,正确配置配置中的 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_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 检查点。

有关完整示例,请查看 此笔记本

检查层和模型状态

有时,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()

如果此选项在不同的用例中都能很好地工作,它可能会在未来成为适配器加载的默认选项。

可重复性

使用批量归一化的模型

加载经过训练的 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" 具有不同的名称,因此请确保名称与您的模型架构匹配。

< > 在 GitHub 上更新