Transformers documentation

参数高效微调

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

参数高效微调

参数高效微调(PEFT)方法仅在预训练模型之上微调少量额外的模型参数(适配器)。由于只更新适配器参数,优化器需要跟踪的梯度和状态大幅减少,从而显著降低内存占用。适配器轻量级,便于共享、存储和加载。

Transformers 通过 PeftAdapterMixin 与 PEFT 库直接集成,该 Mixin 已添加到所有 PreTrainedModel 类中。您可以加载、添加、训练、切换和删除适配器,而无需将模型包装在单独的 PeftModel 中。所有非提示学习类的 PEFT 方法均受支持(LoRA、IA3、AdaLoRA)。基于提示的方法(如 prompt tuning 和 prefix tuning)需要直接使用 PEFT 库。

安装 PEFT 以开始使用。该集成需要 peft >= 0.20.0。

pip install -U peft

添加适配器

创建一个 PEFT 配置,例如 LoraConfig,并使用 add_adapter() 将其附加到模型上。

from peft import LoraConfig, TaskType
from transformers import AutoModelForCausalLM

model = AutoModelForCausalLM.from_pretrained("google/gemma-2-2b")

lora_config = LoraConfig(
    task_type=TaskType.CAUSAL_LM,
    inference_mode=False,
    r=8,
    lora_alpha=32,
    lora_dropout=0.1,
)

model.add_adapter(lora_config, adapter_name="my_adapter")

完全微调特定层

若要同时训练适配器之外的其他模块(例如语言模型头),请在 modules_to_save 中指定它们。modules_to_save 指定了与适配器一起进行完全微调的层,因此这些层的所有参数都会被更新。当某些层需要更新时,这非常有用,例如在将因果语言模型适配用于序列分类任务时,语言模型头(lm_head)就需要更新。

lora_config = LoraConfig(
    modules_to_save=["lm_head"],
    ...
)
model.add_adapter(lora_config)

选择要适配的层

对于常见的架构(Llama、Gemma、Qwen 等),PEFT 已预定义了默认目标(例如 q_proj 和 v_proj),因此你无需指定 target_modules。如果你想针对不同的层,或者模型没有预定义的目标,请显式地将 target_modules 作为模块名称列表或正则表达式模式传入。

lora_config = LoraConfig(
    target_modules=["q_proj", "k_proj"],
    ...
)
model.add_adapter(lora_config)

训练

将附带适配器的模型传入 Trainer,并调用 train()。Trainer 仅更新适配器参数(即 requires_grad=True 的参数),因为基础模型已被冻结。

from transformers import Trainer, TrainingArguments

training_args = TrainingArguments(
    output_dir="./output",
    num_train_epochs=3,
    per_device_train_batch_size=4,
)

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=dataset,
)

trainer.train()

在训练过程中,Trainer 的检查点仅包含适配器权重(adapter_model.safetensors)和配置(adapter_config.json),从而保持检查点体积较小。其中不包含基础模型。

训练完成后,使用 save_pretrained() 保存最终的适配器。

model.save_pretrained("./my_adapter")

从检查点恢复

Trainer 在恢复训练时会自动检测适配器检查点。Trainer 会扫描检查点目录,查找包含适配器权重的子目录,并以正确的可训练状态重新加载每个适配器。

trainer.train(resume_from_checkpoint="./output/checkpoint-1000")

分布式训练

PEFT 适配器开箱即用,支持分布式训练。

对于 ZeRO-3,Trainer 在保存 PEFT 模型的检查点时会传入 exclude_frozen_parameters=True。冻结的基础模型权重将被跳过,仅保存可训练的适配器参数,从而减小检查点体积并缩短保存时间。

对于 FSDP,Trainer 会更新 FSDP 自动包装策略,以正确处理 LoRA 层。对于 QLoRA(量化基础模型 + LoRA),Trainer 还会调整混合精度策略,使其与量化的存储 dtype 相匹配。

您可以将 PEFT 适配器加载到张量并行基础模型上,并让 PEFT 处理适配器权重的分片。加载适配器需要 peft >= 0.21.1。有关使用 TP 进行训练,请参阅 张量并行。

加载适配器

要加载适配器,Hub 仓库或本地目录中必须包含 adapter_config.json 文件和适配器权重。

from_pretrained
load_adapter

from_pretrained() 会自动检测适配器。当它找到 adapter_config.json 时,会读取 base_model_name_or_path 字段以加载正确的基座模型,然后在其上加载适配器。

from transformers import AutoModelForCausalLM

# Automatically loads the base model and attaches the adapter
model = AutoModelForCausalLM.from_pretrained("klcsp/gemma7b-lora-alpaca-11-v1")

对于大型模型,使用 bitsandbytes 加载 8 位或 4 位精度的量化版本以节省内存。添加 device_map="auto" 可将模型分布到可用的硬件上。

from transformers import AutoModelForCausalLM, BitsAndBytesConfig

model = AutoModelForCausalLM.from_pretrained(
    "klcsp/gemma7b-lora-alpaca-11-v1",
    quantization_config=BitsAndBytesConfig(load_in_8bit=True),
    device_map="auto",
)

管理多个适配器

一个模型可以同时加载多个适配器。使用唯一的名称添加适配器,并根据需要在它们之间切换。

from peft import LoraConfig

model.add_adapter(LoraConfig(r=8, lora_alpha=32), adapter_name="adapter_1")
model.add_adapter(LoraConfig(r=16, lora_alpha=64), adapter_name="adapter_2")

使用 set_adapter() 来激活特定的适配器。其他适配器会被禁用,但仍保留在内存中。

model.set_adapter("adapter_2")

enable_adapters() 会启用所有已挂载的适配器,而 disable_adapters() 会禁用所有适配器。

# Disable all adapters for base model inference
model.disable_adapters()

# Re-enable all adapters
model.enable_adapters()

使用 active_adapters() 查看当前激活的适配器。

model.active_adapters()
# ["adapter_1"]

使用 delete_adapter() 删除不再需要的适配器,以释放内存。

model.delete_adapter("adapter_1")

热切换适配器

每次处理请求时加载新的适配器都会分配新的内存。如果模型使用 torch.compile 编译,每个新适配器都会触发重新编译。热切换(Hotswapping)会就地替换适配器权重,从而避免这两个问题。目前仅支持 LoRA 适配器。

加载 LoRA 适配器时,传入 hotswap=True 可将其权重交换到现有的适配器插槽中。将 adapter_name 设置为要替换的适配器名称("default" 是默认适配器名称)。

model = AutoModel.from_pretrained(...)
# Load the first adapter normally
model.load_adapter(adapter_path_1)
# Generate outputs with adapter 1
...
# Hotswap the second adapter in-place
model.load_adapter(adapter_path_2, hotswap=True, adapter_name="default")
# Generate outputs with adapter 2

torch.compile

对于已编译的模型,请在加载第一个适配器之前以及编译之前调用 enable_peft_hotswap() 。

model = AutoModel.from_pretrained(...)
max_rank = ...  # highest rank among all LoRAs you'll load
model.enable_peft_hotswap(target_rank=max_rank)
model.load_adapter(adapter_path_1, adapter_name="default")
model = torch.compile(model, ...)
output_1 = model(...)

# Hotswap without recompilation
model.load_adapter(adapter_path_2, adapter_name="default")
output_2 = model(...)

target_rank 参数用于设置所有待加载 LoRA 适配器中的最大秩。如果您拥有秩为 8 和秩为 16 的适配器,请传入 target_rank=16。默认值为 128。

调用 enable_peft_hotswap 后,后续所有 load_adapter 调用默认执行热切换。如需禁用热切换,请显式传入 hotswap=False。

如果热切换的适配器所针对的层数多于初始适配器,仍可能发生重新编译。为避免重新编译,请优先加载针对层数最多的适配器。

将代码包裹在 with torch._dynamo.config.patch(error_on_recompile=True) 中,以检测意外的重新编译。如果按照上述步骤操作后仍检测到重新编译,请附带可复现的示例,向 PEFT 提交 issue。

后续步骤

  • PEFT 文档涵盖了 PEFT 方法和选项的完整范围。
  • PEFT 的 热切换参考 详细说明了其限制和边界情况。
  • 一篇博客文章对使用热替换的 torch.compile 如何提升运行时性能进行了基准测试。
Update on GitHub