详解大模型微调方法Prompt Tuning(内附实现代码)

文章介绍了PromptTuning的不同方法在大模型微调中的应用,包括Prefix-Tuning对输入添加连续任务向量,P-Tuningv1利用自动搜索的prompt增强重训练,Parameter-EfficientPromptTuning简化版的prompt调优,以及P-Tuningv2支持多任务学习的改进。这些方法旨在减少参数量,提高模型在NLU和NLG任务上的性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


Prompt Tuning是现在大模型微调方法中的一种常用方法,本文通过解读5篇论文来了解Prompt Tuning方法演进的过程。分别是Prefix-Tuning、P-Tuning v1、Parameter-Efficient Prompt Tuning、P-Tuning v2。

1. Prefix-Tuning:Optimizing Continuous Prompts for Generation

Finetuning之前是使用大模型进行下游任务重训的方法,但由于大模型参数量过大,Finetuning需要大量的数据,以及更多的算力去更新学习参数,不够实用。在2021年提出的prefix-tuning算法,并在自然语言生成任务(NLG, Nature Language Generation)上做了验证。这里注意区分下另一个NLP的概念,在NLP中还一类任务叫自然语言理解(NLU, Nature Language Understanding)。

在Prompt思想的启发下,在Prefix-Tuning中提出了给每一个input输入增加一个连续的任务相关的embedding向量(continuous task-specific vectors)来进行训练。

在这里插入图片描述

这里的连续(continuous)是相对于人工定义的文本prompt token的离散(discrete)来说的,例如一个人工定义的prompt token数组是['The', 'movie', 'is', '[MASK]'],把其中的token The替换成一个embedding向量做为输入的话,其中embedding就是连续的(continuous)表达方式。在下游任务重训的时候固定原有的大模型所有参数,只用重训跟下游任务相关的前缀向量(prefix embedding)即可。

对于自回归的LM模型(例如GPT-2)来说,会在原有prompt之前增加prefix(z = [PREFIX; x; y]);对于encoder+decoder的LM模型(例如BART)来说,会分别在encoder和decoder的输入前加上prefix(z = [PREFIX; x; PREFIX'; y],)。如下图所示,P_idx表示加的前缀序列, h对应的是可学习的参数, 用Pθ=[h1, h2, h3, ...]表示可学习参数矩阵。

在这里插入图片描述

直接学习参数效果不好,所以使用MLP网络对进行了reparameter修正,即Pθ[i,:] = MLP(Pθ'[i,:]),重训完只用保存prefix的相关参数。

huggingface peft关于prefix-tuning的核心代码实现在PrefixEncoder

class PrefixEncoder(torch.nn.Module):
        ......
        if self.prefix_projection and not config.inference_mode:
            # Use a two-layer MLP to encode the prefix
            self.embedding = torch.nn.Embedding(num_virtual_tokens, token_dim)
            self.transform = torch.nn.Sequential(
                torch.nn.Linear(token_dim, encoder_hidden_size),
                torch.nn.Tanh(),
                torch.nn.Linear(encoder_hidden_size, num_layers * 2 * token_dim),
            )
        else:
            self.embedding = torch.nn.Embedding(num_virtual_tokens, num_layers * 2 * token_dim)

    def forward(self, prefix: torch.Tensor):
        if self.prefix_projection:
            prefix_tokens = self.embedding(prefix)
            past_key_values = self.transform(prefix_tokens)
        else:
            past_key_values = self.embedding(prefix)
        return past_key_values

2. P-Tuning v1:GPT Understands, Too

P-Tuning这个词在这篇文中被第一次提出,为了跟后续P-Tuning v2区分,这里加了个v1。本论文主要是为了解决GPT大模型在自然语言理解任务(NLU, Nature Language Understanding 重训效果不好的问题。在P-Tuning方法中会在连续向量空间中自动搜索合适的prompt,来增强重训练的效果。

对于之前存在的离散prompt搜索方法(discrete prompt search)来说, 比如AUTOPROMPTLPAQA, 其中的Prompt Generator通过接受离散的反馈来选择合适的prompt。

对于Prompt Generator来说,给定一个词库V和语言模型M, P_i表示在prompt模版T中第i个token,会用词库V中的词来填充模版并并生成embedding向量,

例如:[e_template(The), e_template(capital), e_template(of), e_input(Britain), e_template(is), e_output([Mask])], 其中template表示模版的,input表示输入,ouput表示输出。

而在P-Tuning中通过Prompt Encoder来实现prompt的生成,跟之前的区别在于这里使用了伪prompt和反向传播来对encoder进行更新。在embedding的输入上有所不同,模版中的prompt token embedding向量都是从Prompt Encoder生成出来的,没有对应词库中具体的词。

例如:{h0, ..., hi, e_input(Britain), hi+1, ..., hm, e([MASK])}

在这里插入图片描述

在网络结构上使用embedding层加上基于双层LSTM和relu激活的MLP来实现。训练过程中使用LSTM,但在推理过程中可以把LSTM给去掉。

在这里插入图片描述

最终在效果上实现了bert等大模型重训的提升。

在这里插入图片描述

huggingface peft关于p-tuning的核心代码实现在PromptEncoder

class PromptEncoder(torch.nn.Module):
    ......
    def forward(self, indices):
        input_embeds = self.embedding(indices)
        if self.encoder_type == PromptEncoderReparameterizationType.LSTM:
            output_embeds = self.mlp_head(self.lstm_head(input_embeds)[0])
        elif self.encoder_type == PromptEncoderReparameterizationType.MLP:
            output_embeds = self.mlp_head(input_embeds)
        else:
            raise ValueError("Prompt encoder type not recognized. Please use one of MLP (recommended) or LSTM.")

        return output_embeds

3. Parameter-Efficient Prompt Tuning

本篇论文可以看成是prefix-tuning的简化版,一方面文中实验证明了使用自动生成的soft prompt方法进行tuning的效果跟model tuning差不多,同时超过了人工设计的prompt。

在这里插入图片描述

另一方面,文中对model tuning和prompt tuning做了如下图的对比,prompt tuning可以大幅节省参数量。对于T5的XXL的model来说,全量的model tuning每个下游任务需要11B的参数量,用prompt tuning只需要20480个参数。需要注意跟prefix-tuning不同点:这里的prompt-tuning没有包含中间层的prefix,也没有对下游任务的输出网络进行修改。在prefix-tuning中使用了MLP进行prefix的reparameter

在这里插入图片描述

huggingface peft关于prompt-tuning的核心代码实现在PromptEmbedding

class PromptEmbedding(torch.nn.Module):
    ......
    def forward(self, indices):
        # Just get embeddings
        prompt_embeddings = self.embedding(indices)
        return prompt_embeddings

4. P-Tuning v2

在模型参数量小于10B的训练中,prompt tuning效果还是不及FT(fine-tuning), P-Tuning v2支持330M~10B规模的多任务tuning。P-Tuning v2可以看成是Deep Prompt Tuning在NLU领域的实现,而Deep Prompt Tuning用于问答任务的训练。

跟之前区别主要有以下几点:

  1. 对于NLU任务没有使用像MLP的Reparameterization。
  2. 在模型的每一层上都加上了layer prompt,不同任务可以共享相同的网络参数,支持多任务学习
  3. 在分类头的verbalizer中使用了一个随机初始化的linear head
  4. Prompt长度对于简单分类任务小于20,对于像序列标注这样的复杂任务需要100左右

在这里插入图片描述

以bert分类任务为例,P-Tuning v2相关的核心代码实现在BertPrefixForTokenClassification

在实现的时候有一个get_prompt方法,通过这个函数提前生成各层的prompt的prefix向量

class BertPrefixForTokenClassification(BertPreTrainedModel):
    def get_prompt(self, batch_size):
        prefix_tokens = self.prefix_tokens.unsqueeze(0).expand(batch_size, -1).to(self.bert.device)
        past_key_values = self.prefix_encoder(prefix_tokens)
        # bsz, seqlen, _ = past_key_values.shape
        past_key_values = past_key_values.view(
            batch_size,
            self.pre_seq_len,
            self.n_layer * 2, 
            self.n_head,
            self.n_embd
        )
        past_key_values = self.dropout(past_key_values)
        past_key_values = past_key_values.permute([2, 0, 3, 1, 4]).split(2)
        return past_key_values

然后在forward中通过self.bert方法中的past_key_values方法把prefix向量传入,在前向计算时会把传入的prefix向量进行拼接。

class BertPrefixForTokenClassification(BertPreTrainedModel):    
        ...
        def forward(
        self,
        input_ids=None,
        attention_mask=None,
        token_type_ids=None,
        position_ids=None,
        head_mask=None,
        inputs_embeds=None,
        labels=None,
        output_attentions=None,
        output_hidden_states=None,
        return_dict=None,
    ):
        return_dict = return_dict if return_dict is not None else self.config.use_return_dict

        batch_size = input_ids.shape[0]
        past_key_values = self.get_prompt(batch_size=batch_size)
        prefix_attention_mask = torch.ones(batch_size, self.pre_seq_len).to(self.bert.device)
        attention_mask = torch.cat((prefix_attention_mask, attention_mask), dim=1)

        outputs = self.bert(
            input_ids,
            attention_mask=attention_mask,
            token_type_ids=token_type_ids,
            position_ids=position_ids,
            head_mask=head_mask,
            inputs_embeds=inputs_embeds,
            output_attentions=output_attentions,
            output_hidden_states=output_hidden_states,
            return_dict=return_dict,
            past_key_values=past_key_values,
        )
    ...

对于past_key_values可参考huggingface transformer中的实现【BertSelfAttention】。在forward中如果设置了past_key_value会通过torch.cat和layer的参数进行拼接。

class BertSelfAttention(nn.Module):
    ...
    def forward(
        self,
        hidden_states: torch.Tensor,
        attention_mask: Optional[torch.FloatTensor] = None,
        head_mask: Optional[torch.FloatTensor] = None,
        encoder_hidden_states: Optional[torch.FloatTensor] = None,
        encoder_attention_mask: Optional[torch.FloatTensor] = None,
        past_key_value: Optional[Tuple[Tuple[torch.FloatTensor]]] = None,
        output_attentions: Optional[bool] = False,
    ) -> Tuple[torch.Tensor]:
        ...
        elif past_key_value is not None:
            key_layer = self.transpose_for_scores(self.key(hidden_states))
            value_layer = self.transpose_for_scores(self.value(hidden_states))
            key_layer = torch.cat([past_key_value[0], key_layer], dim=2)
            value_layer = torch.cat([past_key_value[1], value_layer], dim=2)
        ...
    ...

5. 参考

<think>好的,我现在需要回答用户关于大模型微调方法和过程的提问。首先,我得理清楚用户的需求。用户提到了大模型微调,可能想知道常见的微调技术、它们的优缺点以及具体步骤。用户还提供了几个引用,涉及LoRA、Prompt Tuning等,这些都需要整合到回答中。 首先,我应该从基础的全参数微调开始解释,这样用户能有一个全面的了解。全参数微调虽然有效,但计算和存储成本高,尤其是对于大模型来说。接下来,需要介绍参数高效微调方法(PEFT),比如LoRA和Prompt Tuning系列,这些是用户提到的引用中的重点。 引用[1]提到过度参数化模型,如CNN和Transformer,这类模型泛化能力强但微调时需要处理大量参数。这里需要说明为什么需要高效的微调方法。引用[2]讲的是LoRA,可以强调其减少可训练参数的优势,但存储问题依然存在。引用[3]详细介绍了Prompt Tuning的几个变体,需要逐个解释它们的特点和演进过程。 然后,我需要组织回答的结构。首先概述微调的基本概念和步骤,然后分点介绍不同的方法,包括全参数微调和参数高效方法。在参数高效方法中,详细说明LoRA、Prefix-Tuning、P-Tuning系列等,结合引用内容。最后总结选择微调方法的考虑因素,并生成相关问题。 需要注意的是,用户要求使用中文回答,行内公式用$...$,独立公式用$$...$$。例如,在解释LoRA时,可能需要提到低秩分解的数学表示,使用行内公式。同时,确保引用的正确标注,比如在提到LoRA时添加[^2],提到Prompt Tuning时引用[^3]。 还要检查是否有遗漏的微调方法,比如Adapter Tuning,虽然用户没有提到,但作为常见技术应该包含进去。此外,回答需要结构清晰,分步骤或分点,帮助用户逐步理解。 最后,生成的相关问题需要围绕大模型微调展开,比如比较不同方法、应用场景、挑战等,确保问题有针对性,能够引导用户进一步探索。</think>大模型微调是指通过调整预训练模型的参数,使其适应特定任务的过程。以下是主要方法和技术详解: ### 一、微调基础流程 1. **预训练模型加载**:从Hugging Face等平台加载基础模型(如BERT、GPT-3) 2. **任务适配改造**:修改输出层结构,例如分类任务添加softmax层 3. **参数选择策略**: - 全参数微调:更新所有参数(计算成本高) - 部分冻结:固定底层参数,仅微调顶层(常用在相似领域任务) 4. **损失函数设计**:根据任务类型选择交叉熵、均方误差等 ### 二、主流微调方法 #### 1. 全参数微调 $$ \theta_{new} = \theta_{pretrained} - \eta \nabla_\theta L(\theta) $$ 适用于数据量充足的场景,但需要$O(N)$存储(N为参数量),例如175B参数的GPT-3需要700GB显存[^1] #### 2. 参数高效微调(PEFT) **(1) LoRA(Low-Rank Adaptation)** 将权重更新量分解为低秩矩阵:$W&#39; = W + BA$,其中$B \in \mathbb{R}^{d×r}, A \in \mathbb{R}^{r×k}$,可减少97%参数[^2] ```python # 伪代码实现 class LoRALayer(nn.Module): def __init__(self, rank): self.A = nn.Parameter(torch.randn(input_dim, rank)) self.B = nn.Parameter(torch.zeros(rank, output_dim)) def forward(x): return x @ (W + self.A @ self.B) ``` **(2) Prompt Tuning系列** - **Prefix-Tuning**:在输入前添加可学习的连续向量$P \in \mathbb{R}^{l×d}$,通过LSTM生成前缀参数 - **P-Tuning v2**:在不同网络层插入prompt tokens,解决深层次任务适配问题 **(3) Adapter Tuning** 在Transformer层间插入适配模块: $$ h&#39; = h + f(W_{down} \cdot \sigma(W_{up} \cdot h)) $$ 通常仅添加3-5%新参数 ### 三、方法对比 | 方法 | 参数量占比 | 训练速度 | 任务迁移能力 | |---------------|------------|----------|--------------| | 全参数微调 | 100% | 慢 | 优 | | LoRA | 0.1-1% | 快 | 良 | | Prefix-Tuning | 0.1-0.5% | 中 | 中 | ### 四、实践建议 1. 小样本场景优先使用LoRA或Prompt Tuning 2. 多任务学习建议使用Adapter结构 3. 部署时可通过参数合并(如LoRA的$W+BA$合并为单个矩阵)减少推理延迟
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MLTalks

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值