首页
/ Transformers × PEFT 集成实战:加载、训练、切换与量化加载轻量适配器

Transformers × PEFT 集成实战:加载、训练、切换与量化加载轻量适配器

2026-09-04 13:07:22作者:毕习沙Eudora

本篇基于 docs/source/ar/peft.md 的 PEFT(Parameter-Efficient Fine-Tuning,参数高效微调)集成文档展开,系统讲解如何在 Transformers 中原生加载、添加、启用/禁用、训练 PEFT 适配器(如 LoRA),并结合 src/transformers/integrations/peft.pyPeftAdapterMixin 实现与 tests/peft_integration/test_peft_integration.py 的测试用例,剖析适配器自动检测、多适配器管理、8-bit/4-bit 量化加载等底层机制。读完后可独立完成「预训练基座 + 轻量适配器」的部署、微调与推理全流程。

1. PEFT 是什么,Transformers 如何集成它

PEFT 的核心思想是:冻结预训练模型的全部参数,仅在模型之上叠加少量可训练的「适配器」参数(adapter),只训练适配器来学习任务特定信息。这一方式在显存占用上极为节省,却能取得接近全参数微调的效果。由于适配器本身很小,分享、存储和加载都远比完整模型方便——例如文档中给出的例子,一个 OPTForCausalLM 模型的 LoRA 适配器权重仅约 6 MB,而完整模型权重大约 700 MB。

Transformers 对 PEFT 的集成体现在:所有 PreTrainedModel 类都混入了 ~integrations.PeftAdapterMixin,无需再用 PEFT 库的 PeftModel 单独包装模型,直接在基座模型对象上即可完成适配器的加载、注入、切换、启停与保存。

该 Mixin 的能力边界(见 src/transformers/integrations/peft.py 的类注释):

  • 从 Hub 仓库或本地目录加载适配器权重并注入模型;
  • 向模型附加新适配器并用 Trainer 或自定义循环训练;
  • 同时挂载多个适配器,按需激活/停用;
  • 一次性启用/禁用全部适配器;
  • 获取当前激活适配器的 state_dict

方法支持范围:Transformers 原生支持所有「非提示学习」类 PEFT 方法,即可以「注入式」插入 torch 模块的方法——LoRA、IA³、AdaLoRA。而 prompt tuning、prefix tuning 等提示学习方法由于无法注入 torch 模块,不在本集成的支持范围内,需要直接使用 PEFT 库来完成。

版本前提:集成要求安装 peft,且 MIN_PEFT_VERSION 定义为 "0.19.1",低于该版本调用相关方法时会被 check_peft_version 拦截。

2. 安装

pip install peft

如需体验最新特性,可从源码安装 PEFT 库(外部仓库地址此处不展开)。安装后,Transformers 侧无需任何额外配置即可使用 PeftAdapterMixin 的全部方法。

3. 加载 PEFT 适配器

3.1 前置条件:adapter_config.json + 适配器权重

要加载的 Hub 仓库或本地目录必须同时包含:

  • adapter_config.json(适配器配置,含 base_model_name_or_path 等字段);
  • 适配器权重文件:adapter_model.safetensorsadapter_model.bin(在 load_adapter 的源码 中,加载时优先找 adapter_model.safetensorsuse_safetensors=False 时反过来)。

3.2 方式一:from_pretrained 直接加载适配器仓库

只需两步:指定 PEFT 适配器 ID,传给 AutoModelFor 类:

from transformers import AutoModelForCausalLM, AutoTokenizer

peft_model_id = "ybelkada/opt-350m-lora"
model = AutoModelForCausalLM.from_pretrained(peft_model_id)

可以用 AutoModelForCausalLM 等 Auto 类,也可以直接用具名模型类如 OPTForCausalLMLlamaForCausalLM 加载。

底层原理(从源码结构看):from_pretrained 内部会调用 maybe_load_adapters,其定义在 src/transformers/integrations/peft.py。该函数通过 find_adapter_config_file 探测给定路径是否存在 adapter_config.json;若存在,则读取其中的 base_model_name_or_path 字段解析出真正的基座模型,加载基座后再把适配器注入其上。这里有一个细节值得注意:如果传入的是一个本地完整模型目录(目录内已包含 config.json,即「内嵌适配器的本地模型」),代码会保留原路径、直接用本地基座,而不是去 Hub 拉取 adapter_config.json 中声明的基座模型,避免重复下载。

测试侧对这一行为有明确验证:tests/peft_integration/test_peft_integration.py 中的 test_peft_from_pretrained 断言加载后模型中存在 BaseTunerLayer 注入层、_hf_peft_config_loaded 标记为 True,且加载过程不产生 missing/unexpected keys 警告。

3.3 方式二:先加载基座,再 load_adapter

如果基座模型已经在内存中(或你想显式控制基座来源),可以先加载基座,再调用 ~integrations.PeftAdapterMixin.load_adapter 挂上适配器:

from transformers import AutoModelForCausalLM, AutoTokenizer

model_id = "facebook/opt-350m"
peft_model_id = "ybelkada/opt-350m-lora"

model = AutoModelForCausalLM.from_pretrained(model_id)
model.load_adapter(peft_model_id)

load_adapter 的关键参数(完整签名见 源码):

参数 默认值 说明
peft_model_id None Hub 适配器 ID 或本地适配器目录;也支持直接传 peft_config + adapter_state_dict
adapter_name "default" 适配器名称;同名适配器已存在时会报错(hotswap 场景除外)
is_trainable False False 时适配器冻结、仅用于推理(inference_mode);True 时可继续训练
hotswap "auto" 是否原地替换同名已有适配器(见第 6 节)
low_cpu_mem_usage False 降低加载时的 CPU 内存占用
local_files_only False 仅使用本地文件,不访问 Hub

实现 可见,load_adapter 先调用 PEFT 库的 inject_adapter_in_model 把适配器结构注入模型,再复用 from_pretrained 的权重加载管线(_load_pretrained_model)把权重对位加载到 hf_device_map 指定的设备上——这与第 4 节的 device_map 分发机制是同一套底层。

4. 以 8-bit 或 4-bit 精度加载

大模型场景下,可借助 bitsandbytes 集成以 8-bit / 4-bit 量化精度加载(原理详见 bitsandbytes 量化文档)。做法是在 from_pretrained 中传入 quantization_config,并建议设置 device_map="auto" 让 Accelerate 自动把模型切分部署到各设备:

from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig

peft_model_id = "ybelkada/opt-350m-lora"
model = AutoModelForCausalLM.from_pretrained(
    peft_model_id,
    quantization_config=BitsAndBytesConfig(load_in_8bit=True),
    device_map="auto",
)

说明:

  • BitsAndBytesConfig(load_in_8bit=True) 启用 8-bit(LLM.int8());4-bit 则用 BitsAndBytesConfig(load_in_4bit=True)(常配合 bnb_4bit_quant_type="nf4",参考 测试用例 _get_bnb_4bit_config)。
  • 对「基座 + 适配器」仓库,量化作用于基座权重,适配器本身参数极少,保持原精度加载即可。
  • device_map="auto" 依赖 Accelerate 的 infer_auto_device_map 计算最优切分;PeftAdapterMixin 内部还保留了 _dispatch_accelerate_model 方法,用于在模型已按 device_map 分发后重新 dispatch 并挂接新的 hooks。

5. 添加新适配器与多适配器管理

5.1 add_adapter:向已有适配器的模型再挂一个

只要新适配器与现有适配器类型一致,就可以继续添加。例如基座上已挂了一个 LoRA 适配器,再添加第二个:

from transformers import AutoModelForCausalLM, OPTForCausalLM, AutoTokenizer
from peft import LoraConfig

model_id = "facebook/opt-350m"
model = AutoModelForCausalLM.from_pretrained(model_id)

lora_config = LoraConfig(
    target_modules=["q_proj", "k_proj"],
    init_lora_weights=False
)

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

# 添加第二个同名同类型的适配器
model.add_adapter(lora_config, adapter_name="adapter_2")

add_adapter 的行为要点:

  • 未指定 adapter_name 时默认命名为 "default"(沿用 PEFT 库约定);
  • 会校验 adapter_config 必须是 PeftConfig 实例,并把当前模型的 name_or_path 回填为 base_model_name_or_path
  • 内部调用 inject_adapter_in_model 注入,最后自动 set_adapter(adapter_name) 激活刚添加的适配器;
  • 重名适配器直接抛 ValueError,必须换一个名字。

5.2 set_adapter:切换生效的适配器

inputs = tokenizer("Hello", return_tensors="pt")

# 使用 adapter_1
model.set_adapter("adapter_1")
output = model.generate(**inputs)
print(tokenizer.decode(output[0], skip_special_tokens=True))

# 切换到 adapter_2
model.set_adapter("adapter_2")
output = model.generate(**inputs)
print(tokenizer.decode(output[0], skip_special_tokens=True))

set_adapter 支持传入单个名字或名字列表(多适配器叠加推理)。它会遍历模型所有模块,对 BaseTunerLayerModulesToSaveWrapper 调用各自的 set_adapter,把指定适配器设为激活态、其余适配器保持挂载但不参与前向。

5.3 启用 / 禁用全部适配器

from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftConfig

model_id = "facebook/opt-350m"
adapter_model_id = "ybelkada/opt-350m-lora"
tokenizer = AutoTokenizer.from_pretrained(model_id)
text = "Hello"
inputs = tokenizer(text, return_tensors="pt")

model = AutoModelForCausalLM.from_pretrained(model_id)
peft_config = PeftConfig.from_pretrained(adapter_model_id)

# 以随机权重初始化适配器(不加载已有权重)
peft_config.init_lora_weights = False

model.add_adapter(peft_config)
model.enable_adapters()
output = model.generate(**inputs)

# 关闭全部适配器,退化为纯基座模型推理
model.disable_adapters()
output = model.generate(**inputs)

对应实现:

  • enable_adapters:遍历所有 BaseTunerLayer,置 enable_adapters(enabled=True)
  • disable_adapters:同时作用于 BaseTunerLayerModulesToSaveWrapper,全部关闭后模型等价于未加适配器的基座;
  • active_adapters:返回当前激活适配器名列表,多适配器叠加推理时可据此判断状态;
  • 此外,get_adapter_state_dict 可提取指定(默认激活)适配器的纯适配器权重,测试 断言其返回的键全部含 lora
  • 若不再需要某个适配器,可调用 delete_adapter 将其从模型中彻底移除以释放显存;当所有适配器都被删除后,peft_config 字典会被移除、_hf_peft_config_loaded 复位为 False

6. 进阶:LoRA 热切换(Hotswap)

在高并发服务中,每来一个请求就 load_adapter 一次会带来额外内存分配;若模型经过 torch.compile,加载新适配器还会触发重新编译。load_adapterhotswap 参数解决了这个问题:

  • hotswap=True 时,新适配器原地覆盖同名已有适配器的权重,不新建内存块,也不触发 torch.compile 重编译;
  • 当前仅支持 LoRA(源码检查:非 LoRA 类型会要求你显式 hotswap=False);
  • 默认 hotswap="auto":若你事先调用过 enable_peft_hotswap,则后续对已存在名字的 load_adapter 自动进入热切换模式。

不同 rank 的 LoRA 之间热切换需要先预声明最大 rank(需在加载第一个适配器、以及编译之前调用):

model = AutoModelForCausalLM.from_pretrained(base_model_id)
max_rank = ...  # 所有待加载 LoRA 中的最大 rank
model.enable_peft_hotswap(target_rank=max_rank)   # 默认 target_rank=128
model.load_adapter(adapter_path_1, adapter_name="default")
model = torch.compile(model)
out1 = model(**inputs)

# 原地热切换第二个适配器,无需重编译
model.load_adapter(adapter_path_2, adapter_name="default")
out2 = model(**inputs)

限制说明:若热切换的适配器比首个适配器覆盖更多层,仍可能触发重编译——实践建议先加载目标层最多的那个适配器。

7. 训练 PEFT 适配器

挂载适配器的模型可以直接交给 Trainer,因为基座参数已被冻结,Trainer 只会更新 requires_grad=True 的适配器参数。三步走:

第 1 步:定义适配器配置LoraConfig 各参数含义可查 PEFT 库文档):

from peft import LoraConfig

peft_config = LoraConfig(
    lora_alpha=16,        # LoRA 缩放系数,实际缩放 = lora_alpha / r
    lora_dropout=0.1,     # LoRA 分支上的 dropout
    r=64,                 # 低秩矩阵的秩
    bias="none",          # 是否训练 bias
    task_type="CAUSAL_LM",  # 任务类型
)

第 2 步:把适配器加到模型上

model.add_adapter(peft_config)

第 3 步:传入 Trainer 训练

from transformers import Trainer, TrainingArguments

trainer = Trainer(
    model=model,
    args=TrainingArguments(output_dir="./output", ...),
    train_dataset=dataset,
)
trainer.train()

保存与重新加载:训练后只保存适配器(不含基座),产物就是第 3 节所说的 adapter_config.json + adapter_model.safetensors测试 明确断言目录内不含 config.json / pytorch_model.bin / model.safetensors 等基座文件):

model.save_pretrained(save_dir)
model = AutoModelForCausalLM.from_pretrained(save_dir)

Trainer 对 PEFT 场景还有专门的适配(见 src/transformers/trainer.py):

  • 断点续训train(resume_from_checkpoint=...) 时,Trainer 会扫描 checkpoint 子目录中的适配器权重,用 model.load_adapter(peft_id, subdir_name, is_trainable=...) 按正确的可训练状态逐个恢复(L3442-L3454);
  • DeepSpeed ZeRO-3:保存 checkpoint 时传入 exclude_frozen_parameters=True,跳过冻结的基座权重,只保存可训练的适配器参数,显著减小 checkpoint 体积(L3291L3843);
  • FSDP:检测到 PEFT 模型时会更新 FSDP 自动包装策略以正确包裹 LoRA 层(L1627-L1628)。

8. 在适配器之外额外全量微调某些层

通过 modules_to_save 参数,可以让某些层与 LoRA 适配器一起被全量训练(这些层的所有参数都会被更新)。典型场景:把因果 LM 适配到序列分类任务时,需要同时微调 lm_head

from transformers import AutoModelForCausalLM
from peft import LoraConfig

model_id = "facebook/opt-350m"
model = AutoModelForCausalLM.from_pretrained(model_id)

lora_config = LoraConfig(
    target_modules=["q_proj", "k_proj"],
    modules_to_save=["lm_head"],
)

model.add_adapter(lora_config)

modules_to_save 中的模块在 PEFT 内部由 ModulesToSaveWrapper 包装——这也解释了为什么 disable_adapters 同时作用于 ModulesToSaveWrapper:禁用适配器后这些层也会切回原始基座版本。

9. API 速查:PeftAdapterMixin

以上能力全部收敛在 src/transformers/integrations/peft.pyPeftAdapterMixin 中,随所有 PreTrainedModel 类继承:

方法 作用 源码位置
load_adapter 从 Hub/本地加载适配器权重并注入模型,支持 hotswap L80
enable_peft_hotswap 预声明热切换(不同 rank 或已编译模型场景) L353
add_adapter 注入新适配器并激活 L390
set_adapter 切换激活的适配器(可传列表实现叠加) L430
enable_adapters / disable_adapters 启用 / 禁用全部适配器 L490 / L471
active_adapters 查询当前激活适配器 L508
get_adapter_state_dict 提取指定适配器的纯权重 dict L537
delete_adapter 从模型中删除适配器、释放显存 L622

10. 小结与延伸阅读

  • 适用范围:Transformers 原生集成覆盖 LoRA / IA³ / AdaLoRA 等注入式 PEFT 方法;prompt tuning、prefix tuning 请直接使用 PEFT 库(本文档中的外部官方链接此处不再列出)。
  • 典型工作流from_pretrained(基座)add_adapter(LoraConfig)Trainer.train()save_pretrained() → 部署时 from_pretrained(适配器ID)load_adapter();显存紧张时叠加 BitsAndBytesConfig 8/4-bit 量化与 device_map="auto"
  • 验证与排错:集成行为以 tests/peft_integration/test_peft_integration.py 为准绳(加载、保存、启停、state_dict 等均有对应断言);Trainer 的 PEFT 支持逻辑集中在 src/transformers/trainer.py
  • 更完整的 PEFT 方法矩阵、LoraConfig 全参数与热切换边界情况,请以 PEFT 库官方文档为准;微调的通用流程可另见 训练指南,量化细节可参考 bitsandbytes 量化文档
登录后查看全文
热门项目推荐
相关项目推荐