Chapter 04

三大抽象:PeftConfig / PeftModel / Tuners

📌 commit a106ff4c7061 40+ 算法共用同一套接口的秘密

PEFT 的代码量看似不小,但骨架只有三层:PeftConfig(说我要什么算法)、 PeftModel(包装后的模型)、Tuners(每个算法的具体实现)。 本章把这条调用链从 get_peft_model() 拆到 LoraLayer.forward()

4.1俯瞰:一次 get_peft_model() 发生了什么

flowchart TB Call["get_peft_model(base, cfg)"] Call --> M1["mapping_func.py:
根据 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_TUNINGMultitask 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
PeftModelForSequenceClassificationSEQ_CLS
PeftModelForTokenClassificationTOKEN_CLS
PeftModelForFeatureExtractionFEATURE_EXTRACTION
PeftModelForQuestionAnsweringQUESTION_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 中之后多了一个内部层:LoraVariantlora/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 的返回就用它。

含义:

4.10PeftMixedModel:同一 base 上挂不同算法的 adapter

普通 PeftModel 一个 base 只能挂同一种 tuner 的多个 adapter(如三份 LoRA)。src/peft/mixed_model.py:67PeftMixedModel 打破这个限制:

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(...)

限制:

这是研究多种 PEFT 组合时的实验工具,生产代码不要碰

4.11这章你需要带走的