Transformers documentation

参数高效微调

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v5.22.2).
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