PEFT 方法 #

PEFT 概述 #

PEFT(Parameter-Efficient Fine-Tuning)是一系列只训练少量参数就能达到接近全量微调效果的方法。

text
┌─────────────────────────────────────────────────────────────┐
│                    PEFT 方法对比                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  方法           参数量      效果      训练速度    推理开销   │
│  ─────────────────────────────────────────────────────────  │
│  Full FT        100%       最好      慢          无         │
│  LoRA           0.1-1%     很好      快          极小       │
│  QLoRA          0.1-1%     好        快          极小       │
│  Prefix Tuning  0.1%       好        快          有         │
│  Adapter        1-5%       好        中等        有         │
│  Prompt Tuning  0.01%      一般      最快        有         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Prefix Tuning #

原理 #

text
┌─────────────────────────────────────────────────────────────┐
│                  Prefix Tuning 原理                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  在输入序列前添加可学习的前缀向量                            │
│                                                             │
│  输入:[prefix_1, prefix_2, ..., prefix_k, x_1, x_2, ...]  │
│                                                             │
│  特点:                                                      │
│  ├── 只训练前缀向量                                         │
│  ├── 原始模型参数冻结                                       │
│  ├── 前缀长度通常为 10-100                                  │
│  └── 参数量极少                                             │
│                                                             │
└─────────────────────────────────────────────────────────────┘

配置 #

python
from peft import PrefixTuningConfig, get_peft_model, TaskType

prefix_config = PrefixTuningConfig(
    task_type=TaskType.CAUSAL_LM,
    num_virtual_tokens=20,
    prefix_projection=True,
    prefix_projection_hidden_size=512,
)

model = get_peft_model(model, prefix_config)
model.print_trainable_parameters()

参数说明 #

text
num_virtual_tokens
├── 前缀长度
├── 常用值:10-100
├── 越长:效果越好,参数越多
└── 推荐:20-30

prefix_projection
├── 是否使用投影层
├── True:效果更好
└── 推荐:True

prefix_projection_hidden_size
├── 投影层隐藏维度
├── 常用值:512-1024
└── 推荐:512

Prompt Tuning #

原理 #

text
┌─────────────────────────────────────────────────────────────┐
│                  Prompt Tuning 原理                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  在输入嵌入层添加可学习的软提示                              │
│                                                             │
│  输入嵌入:[soft_prompt_1, ..., soft_prompt_k, x_1, ...]   │
│                                                             │
│  特点:                                                      │
│  ├── 只训练软提示向量                                       │
│  ├── 最简单的 PEFT 方法                                     │
│  ├── 参数量最少                                             │
│  └── 效果相对较弱                                           │
│                                                             │
└─────────────────────────────────────────────────────────────┘

配置 #

python
from peft import PromptTuningConfig, get_peft_model, TaskType

prompt_config = PromptTuningConfig(
    task_type=TaskType.CAUSAL_LM,
    num_virtual_tokens=20,
    token_dim=4096,
    num_transformer_submodules=1,
    num_attention_heads=32,
    num_layers=32,
)

model = get_peft_model(model, prompt_config)

初始化方式 #

python
from peft import PromptTuningInit

prompt_config = PromptTuningConfig(
    task_type=TaskType.CAUSAL_LM,
    num_virtual_tokens=20,
    prompt_tuning_init=PromptTuningInit.TEXT,
    prompt_tuning_init_text="分类以下文本的情感:",
    tokenizer_name_or_path="Qwen/Qwen2-7B",
)

model = get_peft_model(model, prompt_config)
text
初始化方式:

RANDOM
├── 随机初始化
├── 简单直接
└── 需要更多训练

TEXT
├── 从文本初始化
├── 效果更好
└── 推荐使用

Adapter #

原理 #

text
┌─────────────────────────────────────────────────────────────┐
│                    Adapter 原理                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  在 Transformer 层中插入小型神经网络                         │
│                                                             │
│  结构:                                                      │
│  ├── 下投影:d → r                                          │
│  ├── 激活函数                                               │
│  ├── 上投影:r → d                                          │
│  └── 残差连接                                               │
│                                                             │
│  特点:                                                      │
│  ├── 只训练 Adapter 参数                                    │
│  ├── 原始模型参数冻结                                       │
│  ├── 结构简单                                               │
│  └── 效果稳定                                               │
│                                                             │
└─────────────────────────────────────────────────────────────┘

配置 #

python
from peft import AdapterConfig, get_peft_model, TaskType

adapter_config = AdapterConfig(
    task_type=TaskType.CAUSAL_LM,
    r=8,
    lora_alpha=16,
    adapter_lora_alpha=16,
    adapter_lora_dropout=0.1,
)

model = get_peft_model(model, adapter_config)

Adapter 类型 #

text
Bottleneck Adapter
├── 标准结构
├── 下投影-激活-上投影
└── 最常用

LoRA Adapter
├── 使用 LoRA 结构
├── 参数更少
└── 效果相近

Parallel Adapter
├── 并行插入
├── 推理更快
└── 效果相当

P-Tuning #

原理 #

text
┌─────────────────────────────────────────────────────────────┐
│                    P-Tuning 原理                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  使用 LSTM/MLP 编码可学习的提示嵌入                          │
│                                                             │
│  特点:                                                      │
│  ├── 比 Prompt Tuning 更强大                                │
│  ├── 使用编码器生成提示                                     │
│  ├── 可以建模提示之间的依赖                                 │
│  └── 效果比 Prompt Tuning 好                                │
│                                                             │
└─────────────────────────────────────────────────────────────┘

配置 #

python
from peft import PromptEncoderConfig, get_peft_model, TaskType

p_tuning_config = PromptEncoderConfig(
    task_type=TaskType.CAUSAL_LM,
    num_virtual_tokens=20,
    encoder_hidden_size=128,
)

model = get_peft_model(model, p_tuning_config)

IA³ #

原理 #

text
┌─────────────────────────────────────────────────────────────┐
│                     IA³ 原理                                 │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  通过可学习的缩放向量调整激活值                              │
│                                                             │
│  公式:                                                      │
│  ├── 注意力:softmax(QK^T / √d) × V × l_v                  │
│  ├── FFN:FFN(x) × l_ffn                                    │
│  └── l_v, l_ffn 是可学习的缩放向量                          │
│                                                             │
│  特点:                                                      │
│  ├── 参数量极少                                             │
│  ├── 只需训练缩放向量                                       │
│  └── 效果出奇地好                                           │
│                                                             │
└─────────────────────────────────────────────────────────────┘

配置 #

python
from peft import IA3Config, get_peft_model, TaskType

ia3_config = IA3Config(
    task_type=TaskType.CAUSAL_LM,
    target_modules=["k_proj", "v_proj", "down_proj"],
    feedforward_modules=["down_proj"],
)

model = get_peft_model(model, ia3_config)

方法选择 #

按任务选择 #

text
文本生成:
├── 推荐:LoRA、Prefix Tuning
├── 效果好、训练快
└── 示例:对话、写作

文本分类:
├── 推荐:LoRA、Adapter
├── 效果稳定
└── 示例:情感分析、主题分类

序列标注:
├── 推荐:LoRA、Adapter
├── 适合结构化任务
└── 示例:NER、词性标注

问答系统:
├── 推荐:LoRA、Prefix Tuning
├── 理解和生成能力
└── 示例:知识问答、客服

按资源选择 #

text
极低资源(<8GB 显存):
├── QLoRA
├── Prompt Tuning
└── IA³

低资源(8-16GB 显存):
├── LoRA
├── QLoRA
└── Prefix Tuning

中等资源(16-32GB 显存):
├── LoRA(更大 rank)
├── Adapter
└── Prefix Tuning

高资源(>32GB 显存):
├── LoRA(大 rank)
├── 全量微调
└── 任何 PEFT 方法

按效果选择 #

text
追求最佳效果:
├── LoRA(大 rank)
├── 全量微调
└── 多方法组合

平衡效果和效率:
├── LoRA(中等 rank)
├── Adapter
└── Prefix Tuning

追求最快训练:
├── Prompt Tuning
├── IA³
└── 小 rank LoRA

PEFT 组合 #

多方法组合 #

python
from peft import get_peft_model, LoraConfig, PrefixTuningConfig

lora_config = LoraConfig(
    r=16,
    lora_alpha=32,
    target_modules=["q_proj", "v_proj"],
)

model = get_peft_model(model, lora_config)

prefix_config = PrefixTuningConfig(
    num_virtual_tokens=10,
    prefix_projection=True,
)

model = get_peft_model(model, prefix_config)

多任务 PEFT #

python
from peft import PeftModel

base_model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2-7B")

task1_model = PeftModel.from_pretrained(base_model, "./task1-lora")
task2_model = PeftModel.from_pretrained(base_model, "./task2-lora")

def switch_task(model, task_name):
    if task_name == "task1":
        return task1_model
    else:
        return task2_model

PEFT 最佳实践 #

配置建议 #

text
LoRA:
├── rank: 16-64
├── alpha: 2 × rank
├── target: 所有注意力层
└── dropout: 0.05

Prefix Tuning:
├── num_virtual_tokens: 20-30
├── prefix_projection: True
└── hidden_size: 512

Adapter:
├── r: 8-16
├── dropout: 0.1
└── 位置: 注意力后

Prompt Tuning:
├── num_virtual_tokens: 20-50
├── init: TEXT
└── 适合简单任务

IA³:
├── target: k_proj, v_proj, down_proj
└── 极少参数

训练建议 #

text
学习率:
├── LoRA: 2e-4
├── Prefix Tuning: 1e-4
├── Adapter: 1e-4
├── Prompt Tuning: 3e-4
└── IA³: 2e-4

训练轮数:
├── 数据量大: 1-2 轮
├── 数据量中: 2-3 轮
└── 数据量小: 3-5 轮

批次大小:
├── 根据显存调整
├── 使用梯度累积
└── 等效批次 16-32

PEFT 调试 #

检查配置 #

python
def inspect_peft_config(model):
    if hasattr(model, 'peft_config'):
        for key, config in model.peft_config.items():
            print(f"Config {key}:")
            print(config)

inspect_peft_config(model)

检查可训练参数 #

python
def count_parameters_by_type(model):
    params_by_type = {}
    
    for name, param in model.named_parameters():
        if param.requires_grad:
            param_type = name.split('.')[0]
            if param_type not in params_by_type:
                params_by_type[param_type] = 0
            params_by_type[param_type] += param.numel()
    
    for param_type, count in params_by_type.items():
        print(f"{param_type}: {count:,} parameters")

count_parameters_by_type(model)

常见问题 #

效果不如全量微调 #

text
解决方案:
1. 增加可训练参数
   - LoRA: 增大 rank
   - Prefix Tuning: 增加前缀长度
   - Adapter: 增加瓶颈维度

2. 增加目标模块
   - LoRA: 添加更多层
   - Adapter: 插入更多位置

3. 调整学习率
   - 适当提高学习率
   - 使用不同的调度器

4. 增加训练数据
   - 数据质量是关键
   - 数据增强

推理速度慢 #

text
解决方案:
1. 合并权重
   merged_model = model.merge_and_unload()

2. 选择无推理开销的方法
   - LoRA: 合并后无开销
   - 其他方法: 有额外开销

3. 量化推理
   - 使用 4-bit/8-bit 量化
   - 加速推理

下一步 #

现在你已经掌握了各种 PEFT 方法,接下来学习 性能优化,了解如何进一步优化训练性能!

最后更新:2026-04-05