微调技术:Prefix-tuning vs Prompt-tuning vs P-tuning

这里写目录标题

    • 一、理论说明
      • [1. Prompt-tuning(离散或软提示)](#1. Prompt-tuning(离散或软提示))
      • [2. Prefix-tuning](#2. Prefix-tuning)
      • [3. P-tuning(P-tuning v1 / v2)](#3. P-tuning(P-tuning v1 / v2))
      • 总结对比:
    • 二、代码实例
      • [1. Prompt-tuning(Soft Prompt)](#1. Prompt-tuning(Soft Prompt))
      • [2. Prefix-tuning](#2. Prefix-tuning)
      • [3. P-tuning v2](#3. P-tuning v2)
      • [4. 调用PEFT训练](#4. 调用PEFT训练)

这三种技术都是参数高效微调Parameter-Efficient Fine-Tuning, PEFT 方法,主要用于在不微调大模型全部参数的前提下,通过较少的可训练参数实现良好的下游任务性能。它们的核心思想是:冻结预训练模型参数,仅调整少量额外模块或输入,以适配新任务。


一、理论说明

1. Prompt-tuning(离散或软提示)

基本思想:

将任务信息嵌入为一个可学习的"提示(prompt)",并将其作为输入的一部分传入模型。

特点:

  • 引入一组可学习的嵌入向量(soft prompts),拼接在原始输入前。

  • 模型主体完全冻结,只有这些嵌入向量是可训练的。

  • 形式上可以表示为:

    复制代码
    输入: [P1, P2, ..., Pn, x1, x2, ..., xm]
          ⬑ prompt ⬑ 原始输入

优点:

  • 参数量极小(只需优化几百个向量)。
  • 可适配多个任务。

2. Prefix-tuning

基本思想:

在模型的每一层 Transformer 的 key 和 value 前加上一段可学习的 prefix 向量,用于控制注意力计算。

实现方式:

  • 在每一层添加长度为 l 的 prefix,作为 attention 的一部分。
  • 不改动 token embedding,只作用在注意力机制中。

优点:

  • 比 prompt-tuning 表现更强,尤其在复杂任务中。
  • 对注意力路径提供更细粒度控制。
  • 通常比 full fine-tuning 参数少两个数量级。

3. P-tuning(P-tuning v1 / v2)

P-tuning v1:

  • 将软提示视作可学习的嵌入向量,插入在输入 embedding 前,类似 prompt-tuning。
  • 和 prompt-tuning 差别小,更多是实验设定不同。

P-tuning v2:

  • 结合了 prefix-tuning 的思想,将 prompt 应用于所有 transformer 层,并配合 LSTM 或 MLP 学习提示生成。
  • 更适用于深层模型(如 GPT-2、T5)。

优点:

  • 适用于大规模模型和更复杂任务。
  • 兼顾了 prefix-tuning 的性能与 prompt-tuning 的轻量性。

总结对比:

技术 插入位置 可学习参数位置 参数量 表现 适用场景
Prompt-tuning 输入 token 前部 输入 embedding 前的 soft prompt 最少 较弱 简单任务
Prefix-tuning 每层注意力前缀 每层 Transformer 的 key/value 中等 较强 多层深模型
P-tuning v2 输入 + 各层前缀 LSTM/MLP生成的 prompt embedding 中等 更强 高性能需求

如用于 NLP 下游任务(如情感分析、问答等),Prefix-tuning 和 P-tuning v2 在保持模型冻结的前提下能达到甚至接近 full fine-tuning 的效果。

二、代码实例

以下是每种技术的核心代码片段 (以 PyTorch + HuggingFace Transformers 框架为例),聚焦其主要实现逻辑 。假设基础模型为 GPT2Model


1. Prompt-tuning(Soft Prompt)

python 复制代码
class PromptTuningModel(nn.Module):
    def __init__(self, base_model, prompt_length=10):
        super().__init__()
        self.base_model = base_model
        self.prompt_length = prompt_length
        self.prompt_embeddings = nn.Parameter(
            torch.randn(prompt_length, base_model.config.hidden_size)
        )

    def forward(self, input_ids, attention_mask=None):
        inputs_embeds = self.base_model.transformer.wte(input_ids)  # word embeddings
        batch_size = input_ids.size(0)

        # Expand prompt to batch
        prompt = self.prompt_embeddings.unsqueeze(0).expand(batch_size, -1, -1)
        inputs_embeds = torch.cat([prompt, inputs_embeds], dim=1)

        if attention_mask is not None:
            prompt_mask = torch.ones(batch_size, self.prompt_length).to(attention_mask.device)
            attention_mask = torch.cat([prompt_mask, attention_mask], dim=1)

        outputs = self.base_model(inputs_embeds=inputs_embeds, attention_mask=attention_mask)
        return outputs

2. Prefix-tuning

python 复制代码
class PrefixTuningModel(nn.Module):
    def __init__(self, base_model, prefix_length=5):
        super().__init__()
        self.base_model = base_model
        self.prefix_length = prefix_length
        self.num_layers = base_model.config.n_layer
        self.n_heads = base_model.config.n_head
        self.head_dim = base_model.config.hidden_size // self.n_heads

        # 每层有独立的 prefix key/value
        self.prefix_key = nn.Parameter(torch.randn(
            self.num_layers, prefix_length, self.n_heads, self.head_dim))
        self.prefix_value = nn.Parameter(torch.randn(
            self.num_layers, prefix_length, self.n_heads, self.head_dim))

    def forward(self, input_ids, attention_mask=None):
        # 将 prefix 插入每一层的 attention 中,需修改 transformer 层的 forward
        # 这里只示意插入机制,具体需修改 GPT2Attention 中的 attn 计算
        raise NotImplementedError("需在模型内部改造 attention 层逻辑")

实际使用推荐:使用 PEFT库(HuggingFace)PrefixTuningConfig


3. P-tuning v2

python 复制代码
class PTuningV2Model(nn.Module):
    def __init__(self, base_model, prompt_length=10):
        super().__init__()
        self.base_model = base_model
        self.prompt_length = prompt_length
        self.num_layers = base_model.config.n_layer
        self.hidden_size = base_model.config.hidden_size

        # 可用 LSTM/MLP 编码 prompt
        self.prompt_encoder = nn.LSTM(
            input_size=self.hidden_size,
            hidden_size=self.hidden_size,
            num_layers=2,
            batch_first=True,
            bidirectional=False
        )

        self.raw_prompt = nn.Parameter(torch.randn(prompt_length, self.hidden_size))

    def forward(self, input_ids, attention_mask=None):
        batch_size = input_ids.size(0)
        prompt_input = self.raw_prompt.unsqueeze(0).expand(batch_size, -1, -1)
        encoded_prompt, _ = self.prompt_encoder(prompt_input)

        inputs_embeds = self.base_model.transformer.wte(input_ids)
        inputs_embeds = torch.cat([encoded_prompt, inputs_embeds], dim=1)

        if attention_mask is not None:
            prompt_mask = torch.ones(batch_size, self.prompt_length).to(attention_mask.device)
            attention_mask = torch.cat([prompt_mask, attention_mask], dim=1)

        outputs = self.base_model(inputs_embeds=inputs_embeds, attention_mask=attention_mask)
        return outputs

4. 调用PEFT训练

注意版本:transformers==4.44.2

python 复制代码
from transformers import AutoTokenizer, AutoModelForCausalLM, TrainingArguments, Trainer, DataCollatorForLanguageModeling
from peft import get_peft_model, PromptTuningConfig, PrefixTuningConfig, PromptEncoderConfig, TaskType
# from dataset import load_toy_dataset
from datasets import Dataset
def load_toy_dataset():
    texts = [
        "Translate English to French: Hello",
        "Translate English to French: How are you?",
        "Translate English to French: What is your name?",
    ]
    labels = [
        "Bonjour",
        "Comment ça va?",
        "Comment tu t'appelles?",
    ]
    data = {"input": texts, "label": labels}
    return Dataset.from_dict(data)
# 可选:Prompt-Tuning / Prefix-Tuning / P-Tuning v2
PEFT_METHOD = "p-tuning"  # or "prompt-tuning" / "prefix-tuning"

def get_peft_config(method, model_name):
    if method == "prompt-tuning":
        return PromptTuningConfig(task_type=TaskType.CAUSAL_LM, prompt_length=10, inference_mode=False, base_model_name_or_path=model_name)
    elif method == "prefix-tuning":
        return PrefixTuningConfig(task_type=TaskType.CAUSAL_LM, num_virtual_tokens=10, inference_mode=False, base_model_name_or_path=model_name)
    elif method == "p-tuning":
        return PromptEncoderConfig(task_type=TaskType.CAUSAL_LM, num_virtual_tokens=10, encoder_hidden_size=128, base_model_name_or_path=model_name)
    else:
        raise ValueError("Unknown PEFT method")

def preprocess(example, tokenizer):
    input_ids = tokenizer(example["input"], truncation=True, padding="max_length", max_length=32, return_tensors="pt").input_ids
    labels = tokenizer(example["label"], truncation=True, padding="max_length", max_length=32, return_tensors="pt").input_ids
    example["input_ids"] = input_ids[0]
    example["labels"] = labels[0]
    return example

def main():
    model_name = "/Users/yanjp/.cache/modelscope/hub/models/Qwen/Qwen3-0_6B"

    # 加载 tokenizer
    tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
    if tokenizer.pad_token is None:
        tokenizer.pad_token = tokenizer.eos_token

    # 加载模型
    base_model = AutoModelForCausalLM.from_pretrained(model_name, trust_remote_code=True)

    peft_config = get_peft_config(PEFT_METHOD, model_name)
    model = get_peft_model(base_model, peft_config)

    dataset = load_toy_dataset()
    dataset = dataset.map(lambda e: preprocess(e, tokenizer))
    dataset.set_format(type="torch", columns=["input_ids", "labels"])

    trainer = Trainer(
        model=model,
        args=TrainingArguments(
            output_dir="./outputs",
            per_device_train_batch_size=2,
            num_train_epochs=5,
            logging_steps=1,
            save_strategy="no"
        ),
        train_dataset=dataset,
        tokenizer=tokenizer,
        data_collator=DataCollatorForLanguageModeling(tokenizer, mlm=False)
    )

    model.print_trainable_parameters()
    trainer.train()

if __name__ == "__main__":
    main()
相关推荐
学习日记52510 分钟前
AI 工程实战:一套可复用的提示词库与质量门禁,如何让 AI 辅助研发「可验证、可沉淀」
人工智能·prompt
SHIPKING3932 小时前
【Harness Engineering】02_Prompt 不是人格,Prompt 是控制平面
prompt·harness
奈斯先生Vector1 天前
AI 视频不是会动的图片:用 Shot Contract、时间码与音画质检构建可交付流水线
人工智能·重构·架构·prompt·aigc·音视频
抱抱宝2 天前
Agent-study项目教程(03):手写 Mini-ReAct Agent(不依赖框架)
javascript·人工智能·gpt·react.js·prompt·agent
抱抱宝2 天前
大模型应用开发教程08 | 构建完整 RAG 应用(Chroma/FAISS 实战)
人工智能·gpt·prompt·agent
AI分享猿2 天前
UI设计Prompt系列(十二):设计系统入门——把视觉语言沉淀为可复用规范
ui·prompt
tachibana22 天前
文件上传分布式限流如何做?
人工智能·ai·大模型·llm·prompt
AI分享猿2 天前
UI设计Prompt系列(十四):前端Agent协作——把UI设计意图转化为清晰执行上下文
前端·prompt
小白说大模型2 天前
OpenClaw 测试策略实战:AI Agent 自动化测试体系搭建与落地
人工智能·重构·开源·prompt·embedding
AI分享猿3 天前
UI设计Prompt系列(六):原型验证提速——用设计Prompt跳过反复改稿
ui·prompt