Transformers 文档

参数高效微调

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始使用

参数高效微调 (Parameter-efficient fine-tuning)

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

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

安装 PEFT 即可开始。该集成要求 peft >= 0.18.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 指定了与适配器一起进行完全微调的层,因此它们的所有参数都会被更新。这在某些层需要更新时非常有用,例如将因果语言模型 (causal LM) 用于序列分类任务时的语言模型头部 (lm_head)。

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

选择需要适配的层

对于常见的架构(Llama、Gemma、Qwen 等),PEFT 具有预定义的默认目标层(如 q_projv_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 自动封装 (auto-wrap) 策略以正确处理 LoRA 层。对于 QLoRA(量化基础模型 + LoRA),Trainer 还会调整混合精度策略以匹配量化存储数据类型。

加载适配器

要加载适配器,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-bit 或 4-bit 精度加载量化版本来节省内存。添加 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")

热切换 (Hotswapping) 适配器

每次处理请求时加载新适配器都会分配新的内存。如果模型使用 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 适配器中的最大秩 (rank)。如果你有秩为 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 与热切换如何提升运行速度。
在 GitHub 上更新

© . This site is unofficial and not affiliated with Hugging Face, Inc.