三大抽象:PeftConfig / PeftModel / Tuners
PEFT 的代码量看似不小,但骨架只有三层:PeftConfig(说我要什么算法)、
PeftModel(包装后的模型)、Tuners(每个算法的具体实现)。
本章把这条调用链从 get_peft_model() 拆到 LoraLayer.forward()。
4.1俯瞰:一次 get_peft_model() 发生了什么
根据 cfg.peft_type 找到对应 PeftModel 子类
+ 对应 Tuner Model 类"] M1 --> Build["PeftModel(base, cfg, ...)"] Build --> Tuner["Tuner.__init__()
(如 LoraModel)"] Tuner --> Scan["scan base.named_modules()"] Scan --> Match["target_modules 匹配?"] Match -->|匹配| Wrap["把 nn.Linear 替换成 LoraLinear"] Match -->|不匹配| Skip[skip] Wrap --> Done["返回包装好的 PeftModel"]
4.2抽象 1:PeftConfig
看 src/peft/config.py,所有算法 config 的基类:
@dataclass
class PeftConfig(PeftConfigMixin):
base_model_name_or_path: Optional[str] = None
revision: Optional[str] = None
peft_type: Union[str, PeftType] = None # ★ 算法类型
task_type: Optional[Union[str, TaskType]] = None
inference_mode: bool = False
每个算法继承之,加自己的字段。LoRA 的:
@dataclass
class LoraConfig(PeftConfig):
r: int = 8
target_modules: Optional[Union[list[str], str]] = None
lora_alpha: int = 8
lora_dropout: float = 0.0
use_dora: bool = False
use_rslora: bool = False
init_lora_weights: bool | str = True
...
PeftType 枚举(src/peft/utils/peft_types.py)覆盖:
| 枚举值 | 对应算法 |
|---|---|
| LORA | LoRA / QLoRA / DoRA / rsLoRA(同一 tuner) |
| ADALORA | AdaLoRA |
| IA3 | IA³ |
| LOHA / LOKR / OFT | 正交家族 |
| BOFT / HRA | 正交家族 v2 |
| PROMPT_TUNING | Prompt Tuning |
| PREFIX_TUNING | Prefix Tuning |
| P_TUNING | P-Tuning |
| MULTITASK_PROMPT_TUNING | Multitask Prompt |
| POLY / VERA / FOURIERFT | 各种小众实验 |
4.3抽象 2:PeftModel
看 src/peft/peft_model.py,简化结构:
class PeftModel(PushToHubMixin, torch.nn.Module):
"""所有 PEFT 包装后模型的基类"""
def __init__(self, model, peft_config, adapter_name="default"):
super().__init__()
self.base_model_torch_dtype = ...
self.peft_config = {adapter_name: peft_config}
# 1) 选 tuner
tuner_cls = PEFT_TYPE_TO_TUNER_MAPPING[peft_config.peft_type]
# 2) 让 tuner 包装 base
self.base_model = tuner_cls(model, {adapter_name: peft_config}, adapter_name)
...
def forward(self, *args, **kwargs):
return self.base_model(*args, **kwargs)
def save_pretrained(self, save_dir, ...):
# 只保存 adapter 部分
...
@classmethod
def from_pretrained(cls, model, model_id, ...):
# 1) 读 adapter_config.json
# 2) 找对应 tuner,包装 model
# 3) 把 adapter_model.safetensors load 进去
...
按 task_type 还派生了子类:
| 子类 | task_type |
|---|---|
| PeftModelForCausalLM | CAUSAL_LM |
| PeftModelForSeq2SeqLM | SEQ_2_SEQ_LM |
| PeftModelForSequenceClassification | SEQ_CLS |
| PeftModelForTokenClassification | TOKEN_CLS |
| PeftModelForFeatureExtraction | FEATURE_EXTRACTION |
| PeftModelForQuestionAnswering | QUESTION_ANS |
子类的作用:包装额外的 head、注入 prompt embedding(prompt-tuning 系列)、定制 generate 行为。
4.4抽象 3:Tuners
src/peft/tuners/ 下每个目录是一个算法,结构相似:
src/peft/tuners/lora/
├── __init__.py
├── config.py # LoraConfig
├── model.py # LoraModel(继承 BaseTuner)
├── layer.py # LoraLinear / LoraLayer
├── bnb.py # bitsandbytes 量化兼容层
├── awq.py # AWQ 兼容
├── gptq.py / hqq.py # 其他量化兼容
├── tp_layer.py # tensor parallel 兼容
├── variants.py # DoRA / rsLoRA 等变体
└── ...
BaseTuner:所有 tuner 的基类
看 src/peft/tuners/tuners_utils.py::BaseTuner:
class BaseTuner(nn.Module):
def __init__(self, model, peft_config, adapter_name, ...):
self.model = model
self.peft_config = peft_config
# 关键 4 步
self._check_new_adapter_config(peft_config[adapter_name])
self.inject_adapter(self.model, adapter_name)
self._mark_only_adapters_as_trainable(self.model)
self._post_inject(adapter_name)
def inject_adapter(self, model, adapter_name):
"""遍历模型 + 替换匹配 target_modules 的层"""
for name, target in model.named_modules():
if self._check_target_module_exists(cfg, name):
self._create_and_replace(cfg, adapter_name, target, name, ...)
def _create_and_replace(self, cfg, adapter_name, target, name, ...):
"""每个 tuner 自家实现:把 target (如 nn.Linear) 换成 Lora/IA3/...Layer"""
raise NotImplementedError
所有 tuner 子类必须实现 _create_and_replace:
| Tuner | 替换的 layer |
|---|---|
| LoraModel | nn.Linear → LoraLinear / lora.Conv2d / lora.Embedding |
| IA3Model | nn.Linear → IA3Linear |
| LoHaModel | nn.Linear → LoHaLayer |
| BOFTModel | nn.Linear → BOFTLayer |
| VeRAModel | nn.Linear → VeRALinear(共享 A/B 矩阵) |
4.5BaseTunerLayer:所有"替换 layer"的接口
看 BaseTunerLayer:
class BaseTunerLayer(ABC):
"""所有替换后的 layer 共用的接口"""
active_adapter: Union[str, list[str]] = None
merged_adapters: list[str] = []
def merge(self, safe_merge=False, adapter_names=None):
"""合并 adapter 进 base weight"""
def unmerge(self):
"""合回去(merge 的逆)"""
def set_adapter(self, adapter_names):
"""切换激活的 adapter(多 adapter 场景)"""
def enable_adapters(self, enable: bool):
"""整体开关"""
def update_layer(self, adapter_name, r, ...):
"""加新 adapter 到这个 layer"""
核心 invariant:"any adapter layer ⊆ BaseTunerLayer + nn.Module",这让 PeftModel 不用知道具体算法就能 merge / set_adapter / enable_adapters。
4.6LoraLinear 的 forward 长这样
看 src/peft/tuners/lora/layer.py::Linear.forward 的简化:
def forward(self, x):
if self.disable_adapters:
return self.base_layer(x)
if self.merged:
return self.base_layer(x) # 已 merge,直接走 base
result = self.base_layer(x) # 主路径:原版 Linear
for active_adapter in self.active_adapters:
lora_A = self.lora_A[active_adapter]
lora_B = self.lora_B[active_adapter]
dropout = self.lora_dropout[active_adapter]
scaling = self.scaling[active_adapter]
result = result + lora_B(lora_A(dropout(x))) * scaling
return result
看到 lora_A / lora_B / dropout / scaling 都是按 adapter_name 索引的 ModuleDict——这是 PEFT 支持多 adapter 同时挂载的基础(详见第 10 章)。
4.7把三大抽象串成调用栈
用户 → get_peft_model(model, LoraConfig(...))
│
├── mapping_func._prepare_lora_config 校验
├── PEFT_TYPE_TO_TUNER_MAPPING[LORA] → LoraModel
├── PEFT_TYPE_TO_MODEL_MAPPING[CAUSAL_LM][LORA] → PeftModelForCausalLM
└── 实例化 PeftModelForCausalLM(model, cfg)
│
└── self.base_model = LoraModel(model, {default: cfg}, default)
│
├── BaseTuner.inject_adapter
│ └── for each module: _check_target_module_exists?
│ → _create_and_replace
│ → Linear → LoraLinear
├── _mark_only_adapters_as_trainable
└── return PeftModel(已 wrap)
forward 时:
PeftModel.forward → self.base_model → self.model (原 HF 模型,但 q_proj/k_proj 已是 LoraLinear)
→ LoraLinear.forward → base_layer + lora_B(lora_A(x))*s
4.8第四层抽象:LoraVariant(变体注入点)
本章前面的三层模型在 2024 中之后多了一个内部层:LoraVariant(lora/layer.py:53–101)。它是"同一个 LoraLinear 内部跑不同变体(DoRA / ALoRA / VeLoRA)" 的解耦点:
# lora/layer.py:53 简化版
class LoraVariant(ABC):
@abstractmethod
def init(self, layer, adapter_name): ...
@abstractmethod
def merge_safe(self, layer, adapter_name): ...
@abstractmethod
def merge_unsafe(self, layer, adapter_name): ...
@abstractmethod
def unmerge(self, layer, adapter_name): ...
@abstractmethod
def forward(self, layer, x, scaling): ...
实际子类:
| Variant | 位置 | 触发 |
|---|---|---|
| DoraVariant | lora/dora.py | use_dora=True |
| ALoraVariant | lora/variants.py | "Activated" LoRA (实验) |
| ArrowVariant | lora/variants.py | 多 LoRA 路由 |
| VeLoRAVariant | lora/variants.py | 梯度 checkpointing + 压缩 |
"为什么需要这一层?"——因为 DoRA / ALoRA 这些变体只是修改 LoRA 的 forward / merge 行为,没必要单独建一个 tuner。一个 LoraLinear 对象通过 resolve_lora_variant() 选择 variant,各 adapter 可以用不同 variant。"在一个 base 上挂 DoRA + 普通 LoRA + ALoRA 三个 adapter"靠的就是这个抽象。
4.9量化兼容的 dispatch_xxx 函数
四层抽象在量化场景下还要再多一道。src/peft/tuners/lora/model.py:51–61 进口的一长串 dispatch 函数:
# lora/model.py:51
from .aqlm import dispatch_aqlm
from .awq import dispatch_awq
from .gptq import dispatch_gptq
from .hqq import dispatch_hqq
from .eetq import dispatch_eetq
from .torchao import dispatch_torchao
from .te import dispatch_transformer_engine
from .tp_layer import dispatch_megatron
from .inc import dispatch_inc
from .bnb import dispatch_bnb_8bit, dispatch_bnb_4bit
每个 dispatch_xxx(target_layer, adapter_name, lora_config, **kwargs) 检测 target_layer 类型——如果是自己负责的量化类型就返回包装层;否则返回 None。LoraModel 在创建 wrapper 时依次试每个 dispatch,第一个非 None 的返回就用它。
含义:
- "加新量化后端" → 写一个新的
dispatch_xxx+ 一个 LoRA layer 类,不用动 LoraModel; - "为什么 GPTQ 不能 merge" → 看
lora/gptq.py的 wrapper:GPTQLoraLinear 没实现 merge_and_unload,本质是 packed int4,回不去 fp16; - "AWQ / HQQ 也是同理"——只是支持/不支持的检查写在各自 dispatch 里。
4.10PeftMixedModel:同一 base 上挂不同算法的 adapter
普通 PeftModel 一个 base 只能挂同一种 tuner 的多个 adapter(如三份 LoRA)。src/peft/mixed_model.py:67 的 PeftMixedModel 打破这个限制:
from peft import PeftMixedModel, LoraConfig, IA3Config
mixed = PeftMixedModel(base, LoraConfig(...), adapter_name="lora_v1")
mixed.add_adapter("ia3_v1", IA3Config(...)) # ★ 不同算法
mixed.set_adapter("ia3_v1")
out = mixed(...)
限制:
- 只支持 inference / forward;不能 save_pretrained;
- 白名单兼容(如 LoRA + IA3 + DoRA 互相兼容,但 LoRA + Prefix Tuning 不能 mix)。
这是研究多种 PEFT 组合时的实验工具,生产代码不要碰。
4.11这章你需要带走的
- PEFT 三层抽象:Config(说明书)→ PeftModel(用户面)→ Tuner(实现);
- 40+ 算法靠"继承 BaseTuner + 继承 BaseTunerLayer"两条规则统一;
- 核心入口在
src/peft/mapping_func.py和src/peft/peft_model.py; - 所有 adapter 用
ModuleDict[adapter_name]存,是多 adapter 的基础; - 读懂
BaseTuner.inject_adapter+LoraLinear.forward这两段就能看懂任何 tuner。