Transformers documentation
参数高效微调
参数高效微调
参数高效微调(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() 会自动检测适配器。当它找到 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")要将适配器加载到现有模型上,请使用 load_adapter()。
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained("google/gemma-7b")
model.load_adapter("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 2torch.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如何提升运行时性能进行了基准测试。