PEFT
自然语言处理进入大语言模型(Large Language Model, LLM)时代之后,模型的参数量级越来越庞大,以稍早之前的 GPT-3 为例,它有 175B 即1亿7千5百万参数,而ChatGPT及后续模型则更大。一方面大语言模型解决自然语言处理任务所需的涌现能力确实需要如此量级的参数,另一方面如此巨大的参数会引起训练成本的急剧增加,甚至从消费级硬件角度来讲,用户全量训练大语言模型的参数的成本令人望而却步。大语言模型已经过 pre-training 阶段,于是就有学者提出是否能通过 finetune一部分少量参数实现下游任务的精调,这催生了一系列PEFT(Parameter-Efficient Fine Tuning,PEFT,可译为参数高效精调)方法。
截至目前(2023年7月),PEFT一共有三类方法,不同的方法以不同的形式在固定预训练模型参数的前提下添加少量的新参数来实现对下游任务的适配。三类方法列举如下:
Adapter-Tuning:在预训练模型的每一层新增浅层前馈网络或模块作为adapter以适配下游任务,训练时仅更新adapter的参数,存储时每个下游任务仅存储对应的adapter的参数。
Prefix/Prompt:在预训练模型的输入层或者一层增加一些可训练的虚拟tokens(也称作Prompt),只训练这些token的参数,存储时每个下游任务仅存储对应的token的参数。
LoRA:通过引入低秩矩阵参数参与模型前向传播(与原有部分参数产生的及或者相加)来适配下游任务,训练时仅更新低秩矩阵参数,存储时每个下游任务仅存储对应的低秩矩阵参数。
Adapter Tuning
Bert时代,自然语言处理范式为Pre-Training + Finetuning:即首先使用超大型语料库预训练一个通用语言模型,然后在各下游任务中精调参数。我们可以将预训练模型(Pre-Training Model, PTM)到下游任务模型看作是一个迁移学习的过程。这种模式使得模型在不同的下游任务均能取得良好效果,但存在一个问题是训练每一个下游任务都相当于训练了一个新模型(预训练模型迁移学习时参数低效),这样比较繁琐且训练有比较大的开销。那有没有可能存在更高效的迁移学习方式使得预训练模型能在不损失性能的前提下一次性高效迁移到所有下游任务呢,答案是有的。
Google 研究人员在2019年的ICML上发表了一篇题为Parameter-Efficient Transfer Learning for NLP的论文,提出了Adapter tuning(可译为转换器微调)作为一种高效的迁移学习方法。该方法并不复杂,它在原始 transformer 的基础上增加了一个新的模块 Adapter module,Adapter module 的架构可选,但论文提出一个非常简单的架构却可以实现非常好的效果。
原论文提出的架构如下图,作者在每一个transformer的2个前馈层之后加入一个新的组件Adapter module,每个adapter module包含三个部分:
Feedforward down-project 层将输入的维度放缩到非常小的级别,以减少训练参数量
Nonlinearity 引入非线性特征,矩阵运算 + 非线性等价于一层 FFW
Feedforward up-project 层将输入还原到原始维度,以参与后续计算
记输入原始维度为,放缩后的维度为,则 Feedforward down-project 层参数量为, Feedforward up-project 层的参数量为, 总参数量为,但是可以设置得非常小,即,最终达到的效果是仅使用相当于原始模型参数总量的的新参数,却可以实现在全部下游任务上流式训练后共用一套参数,取得和每个任务都 finetune 一遍全量参数差不多的效果:
更多详细比较和其他信息参见原论文,需要补充说明的是,用来执行分类任务的最后一层,也是可训练参数的一部分。
Prefix Tuning
原论文:Prefix-Tuning: Optimizing Continuous Prompts for Generation
Finetuning在下游任务精调阶段更新预训练模型的所有参数,并为每一个任务保存一份预训练模型参数副本(如下图上半部分)。
红色部分表示更新,灰色部分参数不更新
Prefix-Tuning在模型执行不同的任务时添加不同的前缀(prefix),训练时固定预训练模型的原始参数,仅更新前缀部分的参数(上图下半部分)。前缀其实是连续的向量序列形式的可训练参数,从逻辑上可以认为是人为在输入前加入了一些虚拟token,前缀表示这些虚拟token产生的激活值。这使得不同的任务仅需要存储一份预训练模型参数加上每个任务对应的前缀,节省了可观的存储空间。
如上图,以GPT2(自回归语言模型典型代表)为例:将输入和输出拼接在一起记为,经过预训练模型某一层参数计算之后得到激活值,使用和表示输入序列和输出序列的索引。那么Prefix-Tuning在之前插入prefix部分,得到,使用表示前缀的索引,表示前缀的长度,整个前缀部分对应向量矩阵形式的参数,参数的维度为,模型隐层的激活值可以按照如下公式给定:
即索引 i i i在前缀部分时,激活值由前缀参数给定,否则由预训练模型计算。
方法提出者实际检验发现直接优化参数结果并不稳定且导致模型效果变差,于是又提出了更小的矩阵维度为,原论文文本分类任务是,表格转文字任务是)并经过一个稍大的前馈网络处理(将还原到)来作为替代优化目标,即,此时可训练参数包括 和 MLP 的参数。
有读者可能这里会想到,能否把额外的参数部分添加在输入和输出之间呢,原作者把这种方式称为,也进行了尝试,但实际效果不如。
除此之外还有一个细节,原作者实际检验发现仅在 embedding 层加 prefix 表示能力不足,模型的所有隐层都加上 prefix 才能效果最好。
Prompt Tuning
原论文:The Power of Scale for Parameter-Efficient Prompt Tuning
Prompt Tuning可以看作是Prefix Tuning的简化版本,仅在输入层加入了可训练的 prompt token,无需引入MLP(如上图)。作者主要的目的是想说明只要模型的参数规模持续增大到一定量级时,那么固定预训练模型的参数,仅需在输入文本前加入少量的可更新的 token(称为 prompt)就能够达到 Finetuning 的效果:
作者在论文中列出了一系列消融实验,比较了prompt长度、prompt初始化方法,预训练任务目标调整、LM adaptation(即下游任务 finetuning)步数对模型最终效果的影响,感兴趣的读者可以打开论文一窥究竟,这里不再赘述。
P-Tuning
P-Tuning方法的提出是为了让GPT类模型可以更好的应用于自然语言理解(Natural Language Understanding,NLU)任务,它引入了可训练的的连续的embedding层参数作为prompt代替人工设计的prompt,prompt参见GPT-3原论文: Language Models are Few-Shot Learners。
如上图,以图中的The capital of Britain is \[MASK\]
为例,这是一个由prompt(黄色部分),context(蓝色部分),target(红色部分)组成的template。采用P-Tuning的情况下(上图右半部分),template则可以表示为 其中表示模板中第 个 prompt token,模板 token分布在输入左右,同样可以认为是插入的虚拟token,经过Prompt Encoder之后变为,作者认为相对于离散的词向量这部分是prompt token序列产生的连续参数值,,整个template则可以表示为 ,其中表示embedding,未经prompt encoder处理也不参与训练,则是可训练的参数,除此之外预训练模型本身的参数是固定的。
作者针对实验中发现的2个问题进行了优化,这2个问题是:
(离散性)由于词向量已经高度离散化,直接初始化 使用SGD进行训练,则非常容易陷入局部最优值(笔者注:可以加一两层网络来处理,增加一定程度的灵活性)。
(关联性)作者认为prompt embedding部分即 彼此之间是相互依赖而不是相互独立的,因而需要有一种机制能将它们关联起来(笔者注:很容易联想到序列模型,常用的即为 LSTM)。
综上,作者提出了Prompt Encoder的架构,由Bi-LSTM(solve for 关联性,为了加强效果采用了双向模型)处理之后再接上一个两层前馈网络(solve for 离散性)来对prompt虚拟token进行编码得到 prompt embedding 再喂给预训练模型,即:
作者通过实验证明了通过 P-Tuning 的方法可以使得 GPT 类模型在NLU方面达到BERT同样水平的效果,偶尔甚至能有更好的表现。
从方法上来讲,P-Tuning也是固定了预训练模型的参数,通过引入一部分额外参数加一个Bi-LSTM加DNN的简单Encoder(Encoder本身的参数也属于额外增加的部分)来实现NLU任务的训练,它跟 Prefix 有以下不同点:
Prefix-Tuning将额外的参数加在输入embedding开头,更像是一种 Instruction;而 P-Tuning 则加在输入的一部分构成的 prompt token embedding 的左右两边。
Prefix Tuning在每个transformer前馈层都加入了Prefix Embedding,通过 MLP 来 encoding;而 P-Tuning 则仅在输入层加入额外的 embedding,并通过 Bi-LSTM 和 MLP 来进行初始化。
P-Tuning V2
原论文:P-Tuning v2: Prompt Tuning Can BeComparable to Fine-tuning Universally Across Scales and Tasks
P-Tuning V2 版本则跟 Prefix-Tuning 一样将额外添加的token加入到了网络的每一层,训练时更新每一层的 prompt token embedding,它同样也是仅适配 NLU 任务。这样的改动也使得相较于初版,P-Tuning V2 具备以下2点优势:(1) 拥有更多的可训练参数,表征能力更强,但总体仍维持少量(初版$0.1%,0.1%~3%)。(2) 加入到更深层的网络结构中,对模型最终预测带来更直接的影响,能取得更好的效果。
从论文标题就能够看出,作者的目的是希望在不同参数规模的预训练模型、针对不同的下游任务都可以使用P-Tuning V2达到Fine-Tuning同等水平的效果。
LoRA
LORA: LOW-RANK ADAPTATION OF LARGE LANGUAGE MODELS核心思路是对模型参数做低秩分解,仅训练分解后的参数,模型部署也需额外保存低秩参数,计算时加上低秩参数部分。
LoRA的提出在上述PEFT方法之后,来自微软的研究者认为,现有的Adapter Tuning和Prefix Tuning这两种方法均有缺点:
Adapter Layers Introduce Inference Latency:虽然Adapter后续又有很多变种,但无论如何额外添加的Adapter层都会拖慢推理速度
Directly Optimizing the Prompt is Hard:应用Prefix-Finetuning时,直接优化prompt非常困难,而且其效果也不是随着训练参数的增加而单调递增
Aghajanyan等研究者在论文Intrinsic Dimensionality Explains the Effectiveness of Language Model Fine-Tuning提出了关于大模型的一个核心观察点:预训练模型存在一个低秩的“内在维度”(intrinsic dimension)使得其在被随机映射到一个更小的子空间时仍然可以高效学习。基于这种想法,作者假设预训练模型在转换到下游模型过程中也有一个内在维度,提出了下面的方法。
图中的A,B均为可训练参数,参数,是初始化参数的方法
对于预训练模型权重 ,引入一个低秩部分 来限制其更新,即:,其中 , ,但秩 ,训练时 被冻结不参与梯度更新,仅有和为可训练参数,修改后的前向传播可表示为:这意味着增加的部分 和原始部分可以并行计算,没有引入任何推理时延。
总结下来,LoRA拥有以下优点:
A Generalization of Full Fine-tuning
- LoRA 是一个更通用的 finetuning 方法,可以仅训练预训练模型参数的一小部分,它不需要在模型适配过程中累积梯度来更新全秩参数。这意味着当应用在全部权重矩阵和偏差项上应用 LoRA 更新时,通过设置 LoRA 的秩为预训练权重的秩,基本能够还原全量 finetuning 同等水平的表征能力。换句话讲,随着我们增大增加可训练参数的数量,使用 LoRA 方式训练基本可以收敛到训练原始模型。与之形成对比的是,采用 adapter 的一系列方法仅能收敛到 MLP,而基于 prefix 的方法不能不处理长输入序列。
No Additional Inference Latency
- 可以按照来存储和执行推理,迁移到其他任务时,可以减去,再加上新任务的,仅需一个占用少量存储的快捷操作即可迁移到新任务。这保证了和采用构建的finetuning方法相比,单任务及多任务都没有引入额外的推理时延。
通过实践发现,LoRA 实际产生的最大的益处是节省内存和存储消耗,通过设置 model 能够将一个使用 Adam 训练的大型 transformer 的 VRAM(即显存)占用最大减少。具体而言,在GPT-3 175B上,VRAM消耗从1.2TB降低到350GB,在仅采用 query 和 value 矩阵映射矩阵的条件下,检查点的大小被降低了10000倍(从350GB到35MB)。假设我们需要 100 个转化模型,使用 LoRA 仅需保存 大小的空间,而全量 Finetuning 则需要 的存储空间。这使得训练需要的GPU数量变少且减少了瓶颈的次数,并且在任务间切换时,仅需在 VRAM 实时切换 LoRA 权重而不需要花费大量时间切换全量参数。除此之外,由于不需要计算大多数参数的梯度,训练速度也提升了25%。