Transformers × PEFT 集成实战:加载、训练、切换与量化加载轻量适配器
本篇基于 docs/source/ar/peft.md 的 PEFT(Parameter-Efficient Fine-Tuning,参数高效微调)集成文档展开,系统讲解如何在 Transformers 中原生加载、添加、启用/禁用、训练 PEFT 适配器(如 LoRA),并结合 src/transformers/integrations/peft.py 的 PeftAdapterMixin 实现与 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.safetensors或adapter_model.bin(在load_adapter的源码 中,加载时优先找adapter_model.safetensors,use_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 类,也可以直接用具名模型类如 OPTForCausalLM、LlamaForCausalLM 加载。
底层原理(从源码结构看):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 支持传入单个名字或名字列表(多适配器叠加推理)。它会遍历模型所有模块,对 BaseTunerLayer 和 ModulesToSaveWrapper 调用各自的 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:同时作用于BaseTunerLayer与ModulesToSaveWrapper,全部关闭后模型等价于未加适配器的基座;active_adapters:返回当前激活适配器名列表,多适配器叠加推理时可据此判断状态;- 此外,
get_adapter_state_dict可提取指定(默认激活)适配器的纯适配器权重,测试 断言其返回的键全部含lora; - 若不再需要某个适配器,可调用
delete_adapter将其从模型中彻底移除以释放显存;当所有适配器都被删除后,peft_config字典会被移除、_hf_peft_config_loaded复位为False。
6. 进阶:LoRA 热切换(Hotswap)
在高并发服务中,每来一个请求就 load_adapter 一次会带来额外内存分配;若模型经过 torch.compile,加载新适配器还会触发重新编译。load_adapter 的 hotswap 参数解决了这个问题:
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 体积(L3291、L3843); - 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.py 的 PeftAdapterMixin 中,随所有 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();显存紧张时叠加BitsAndBytesConfig8/4-bit 量化与device_map="auto"。 - 验证与排错:集成行为以 tests/peft_integration/test_peft_integration.py 为准绳(加载、保存、启停、state_dict 等均有对应断言);
Trainer的 PEFT 支持逻辑集中在 src/transformers/trainer.py。 - 更完整的 PEFT 方法矩阵、
LoraConfig全参数与热切换边界情况,请以 PEFT 库官方文档为准;微调的通用流程可另见 训练指南,量化细节可参考 bitsandbytes 量化文档。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00