自然语言处理进入大语言模型 (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(可译为转换器微调)作为一种高效的迁移学习方法。该方法并不复杂,它在原始tranformer的基础上增加了一个新的模块Adapter module,Adapter module的架构可选,但论文提出一个非常简单的架构却可以实现非常好的效果。
原论文提出的架构如下图,作者在每一个transformer的2个前馈层之后加入一个新的组件Adapter module,每个adapter module包含三个部分:
- Feedforward down-project层将输入的维度放缩到非常小的级别,以减少训练参数量
- Nonlinearity引入非线性特征,矩阵运算 + 非线性等价于一层FFW
- Feedforward up-project层将输入还原到原始维度,以参与后续计算
记输入原始维度为 d d d,放缩后的维度为 m m m,则Feedforward down-project层参数量为 d ∗ m + m d*m+m d∗m+m, Feedforward up-project层的参数量为 m ∗ d + d m*d+d m∗d+d, 总参数量为 2 m d + d + m 2md+d+m 2md+d+m,但是 m m m可以设置得非常小,即 m ≪ d m\ll d m≪d,最终达到的效果是仅使用相当于原始模型参数总量的 % 0.5 − % 8 \%0.5\ - \%8 %0.5 −%8的新参数,却可以实现在全部下游任务上流式训练后公用一套参数,取得和每个任务都finetune一遍全量参数差不多的效果:
更多详细比较和其他信息参见原论文,需要补充说明的是,用来执行分类任务的最后一层,也是可训练参数的一部分。
Prefix Tuning
原论文:Prefix-Tuning: Optimizing Continuous Prompts for Generation
Finetuning在下游任务精调阶段更新预训练模型的所有参数,并为每一个任务保存一份预训练模型参数副本(如下图上半部分)。
红色部分表示更新,灰色部分参数不更新
Prefix-Tuning在模型执行不同的任务时添加不同的前缀 (prefix),训练时固定预训练模型的原始参数,仅更新前缀部分的参数 (上图下半部分)。前缀其实是连续的向量序列形式的可训练参数,从逻辑上可以认为是人为在输入前加入了一些虚拟token,前缀表示这些虚拟token产生的激活值。这使得不同的任务仅需要存储一份预训练模型参数加上每个任务对应的前缀,节省了可观的存储空间。
如上图,以GPT2(自回归语言模型典型代表)为例:
将输入 x x x和输出 y y y拼接在一起记为 z = [ x : y ] z=[x:y] z=[x:y],经过预训练模型某一层参数计算之后得到激活值 h = [ h 1 , h 2 , . . . , h n ] ( h i = L M ϕ ( z i , h < i ) h=[h_1,h_2,...,h_n] (h_i=LM_{\phi}(z_i,h_{<i}) h=[h1,h2,...,hn](hi=LMϕ(zi,h<i),使用 X i d x X_idx Xidx和 Y i d x Y_idx Yidx表示输入序列和输出序列的索引。那么Prefix-Tuning在 x x x之前插入prefix部分,得到 z = [ p r e f i x , x , y ] z=[prefix,x,y] z=[prefix,x,y],使用 P i d x P_{idx} Pidx表示前缀的索引, ∣ P i d x ∣ |P_{idx}| ∣Pidx∣表示前缀的长度,整个前缀部分对应向量矩阵形式的参数 P θ P_{\theta} Pθ,参数的维度为 ∣ P i d x ∣ ∗ d i m ( h i ) |P_{idx}|*dim(h_i) ∣Pidx∣∗dim(hi),模型隐层的激活值可以按照如下公式给定:
h i = { P θ [ i , : ] , if i ∈ P i d x h i = L M ϕ ( z i , h < i ) , o t h e r w i s e h_i = \begin{cases} P_{\theta}[i,:], \ \text{if } i\in P_{idx} \\ h_i=LM_{\phi}(z_i,h_{<i}), \ otherwise \end{cases} hi={Pθ[i,:], if i∈Pidxhi=LMϕ(zi,h<i), otherwise
即索引 i i i在前缀部分时,激活值由前缀参数给定,否则由预训练模型计算。
方法提出者实际检验发现直接优化参数 P θ P_{\theta} Pθ结果并不稳定且导致模型效果变差一点,于是又提出了通过更小的矩阵 P θ ′ [ i , : ] P'{\theta}[i,:] Pθ′[i,:](维度为 ∣ P i d x ∣ ∗ k |P{idx}|*k ∣Pidx∣∗k,原论文文本分类任务时 k = 512 k=512 k=512,表格转文字任务是 k = 800 k=800 k=800)并经过一个稍大的前馈网络 M L P θ MLP_{\theta} MLPθ处理(将 k k k还原到 d i m ( h i ) dim(h_i) dim(hi))来作为替代优化目标,即 P θ = M L P θ ( P θ ′ [ i , : ] ) P_{\theta}=MLP_{\theta}(P'{\theta}[i,:]) Pθ=MLPθ(Pθ′[i,:]),此时可训练参数包括 P θ ′ [ i , : ] P'{\theta}[i,:] Pθ′[i,:]和MLP的参数 θ \theta θ。
有读者可能这里会想到,能否把额外的参数部分添加在输入 x x x和输出 y y y之间呢,原作者这种方式称为 I n f i x − T u n i n g Infix-Tuning Infix−Tuning,也进行了尝试,但实际效果不如 P r e f i x − T u n i n g Prefix-Tuning Prefix−Tuning。
除此之外还有一个细节,原作者实际检验发现仅在embedding层加prefix表示能力不足,模型的所有隐层都加上prefix才能效果最好。
Prompt Tuning
原论文:The Power of Scale for Parameter-Efficient Prompt Tuning
Prompt Tuning可以看作是Prefix Tuning的简化版本,仅在输入层加入了可训练的prompt token,无需引入MLP(如上图)。作者主要的目的是想说明只要模型的参数规模持续增大到一定量级( 1 0 1 0 , 100 亿 10^10,100亿 1010,100亿)时,那么固定预训练模型的参数,仅需在输入文本前加入少量的可更新的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则可以表示为 T = [ P 0 : i , x , [ P i + 1 : m , y ] ] T={[P_{0:i},x,[P_{i+1:m},y]]} T=[P0:i,x,[Pi+1:m,y]],其中 P i P_i Pi表示模板 T T T中第 i i i个prompt token,模板token分布在输入 x x x左右,同样可以认为是插入的虚拟token,经过Prompt Encoder之后变为 h 0 , . . . , h i , h i + 1 , . . . , h m {h_0,...,h_i,h_{i+1},...,h_m} h0,...,hi,hi+1,...,hm,作者认为相对于离散的词向量这部分是prompt token序列产生的连续参数值,,整个template则可以表示为 T = { h 0 , . . . , h i , e ( x ) , h i + 1 , . . . , h m , e ( y ) } T=\{h_0,...,h_i,e(x),h_{i+1},...,h_m,e(y)\} T={h0,...,hi,e(x),hi+1,...,hm,e(y)},其中 e e e表示embedding,未经prompt encoder处理也不参与训练, h i h_i hi则是可训练的参数,除此之外预训练模型本身的参数是固定的。
作者针对实验中发现的2个问题进行了优化,这2个问题是:
- (离散性 )由于词向量已经高度离散化,直接初始化 h 0 , . . . , h i , h i + 1 , . . . , h m {h_0,...,h_i,h_{i+1},...,h_m} h0,...,hi,hi+1,...,hm使用SGD进行训练,则非常容易陷入局部最优值(笔者注:可以加一两层网络来处理,增加一定程度的灵活性)。
- (关联性 )作者认为prompt embedding部分即 h 0 , . . . , h i , h i + 1 , . . . , h m {h_0,...,h_i,h_{i+1},...,h_m} h0,...,hi,hi+1,...,hm彼此之间是相互依赖而不是相互独立的,因而需要有一种机制能将它们关联起来(笔者注:很容易联想到序列模型 ,常用的即为LSTM)。
综上,作者提出了Prompt Encoder的架构,由Bi-LSTM(solve for 关联性 ,为了加强效果采用了双向模型)处理之后再接上一个两层前馈网络(solve for 离散性)来对prompt虚拟token进行编码得到prompt embedding在喂给预训练模型,即:
h i = M L P ( h → i : h ← i ) = M L P ( [ L S T M ( h 0 : i : L S T M ( h i : m ) ) ] ) \begin{equation} \begin{split} h_i&=MLP(\overrightarrow h_i: \overleftarrow h_i)\\ &=MLP([LSTM({h_{0:i}}:LSTM(h_{_{i:m}}))]) \end{split} \end{equation} hi=MLP(h i:h i)=MLP([LSTM(h0:i:LSTM(hi:m))])
作者通过实验证明了通过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 Be
Comparable 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均为可训练参数,参数A=正态分布,B=0是初始化参数的方法
对于预训练模型权重 W 0 ∈ R d × k W_0 \in \R^{d \times k} W0∈Rd×k,引入一个低秩部分 Δ W \Delta W ΔW来限制其更新,即: W 0 + Δ W = W 0 + B A W_0 + \Delta W = W_0 + BA W0+ΔW=W0+BA,其中 B ∈ R d × r B\in \R^{d \times r} B∈Rd×r, A ∈ R r × k A \in \R^{r \times k} A∈Rr×k,但秩 r ≪ m i n ( d , k ) r \ll min(d,k) r≪min(d,k),训练时 W 0 W_0 W0被冻结不参与梯度更新,仅有 A A A和 B B B为可训练参数,修改后的前向传播可表示为:
h = W 0 x + Δ W x = W 0 x + B A x h=W_0x+\Delta Wx=W_0x + BAx h=W0x+ΔWx=W0x+BAx
这意味着增加的部分 Δ W x \Delta Wx ΔWx和原始部分可以并行计算,没有引入任何推理时延。
总结下来,LoRA拥有以下优点:
- A Generalization of Full Fine-tuning
LoRA是一个更通用的finetuning方法,可以仅训练预训练模型参数的一小部分,它不需要在模型适配过程中累积梯度来更新全秩参数。这意味着当应用在全部权重矩阵和偏差项上应用LoRA更新时,通过设置LoRA的秩 r r r为预训练权重的秩,基本能够还原全量finetuning同等水平的表征能力。换句话讲,随着我们增大 r r r增加可训练参数的数量,使用LoRA方式训练基本可以收敛到训练原始模型。与之形成对比的是,采用adapter的一系列方法仅能收敛到MLP,而基于prefix的方法不能不处理长输入序列。 - No Additional Inference Latency
可以按照 W = W 0 + B A W=W_0 + BA W=W0+BA来存储和执行推理,迁移到其他任务时,可以减去 B A BA BA,再加上新任务的 B ′ A ′ B'A' B′A′,仅需一个占用少量存储的快捷操作即可迁移到新任务。这保证了和采用构建的finetuning方法相比,单任务及多任务都没有引入额外的推理时延。
作者通过实践发现,LoRA实际产生的最大的益处是节省内存和存储消耗,通过设置 r ≪ d m o d e l r \ll d_{model} r≪dmodel能够将一个使用Adam训练的大型transformer的VRAM(即显存)占用最大减少 2 / 3 2/3 2/3。具体而言,在GPT-3 175B上,VRAM消耗从1.2TB降低到350GB,在仅采用query和value矩阵映射矩阵的条件下,检查点的大小被降低了10000倍(从350GB到35MB)。假设我们需要100个转化模型,使用LoRA仅需保存 350 G B + 35 M B × 100 ≈ 354 G B 350GB+35MB\times 100 \approx 354GB 350GB+35MB×100≈354GB大小的空间,而全量Finetuning则需要 100 × 350 G B = 350 T B 100 \times 350GB=350TB 100×350GB=350TB的存储空间。这使得训练需要的GPU数量变少且减少了I/O瓶颈的次数,并且在任务间切换时,仅需在VRAM实时切换LoRA权重而不需要花费大量时间切换全量参数。除此之外,由于不需要计算大多数参数的梯度,训练速度也提升了25%。
开源第三方库
- huggingface开源了peft库,支持LoRA、Prefix Tuning、P-Tuning、Prompt Tuning、AdaLoRA等方法,并可以结合 Accelerate的DeepSpeed一起使用,实现高效训练。
git地址:github.com/huggingface...
bash
pip install peft
- LLM-Adapters在peft库基础上增加了adapter变种AdapterP、Parallel的支持。
git地址:github.com/AGI-Edgerun...
参考文献
- 大模型参数高效微调(PEFT)
- 让天下没有难Tuning的大模型-PEFT技术简介
- Parameter-Efficient Transfer Learning for NL
- Prefix-Tuning: Optimizing Continuous Prompts for Generation
- The Power of Scale for Parameter-Efficient Prompt Tuning
- GPT Understands, Too
- P-Tuning v2: Prompt Tuning Can Be
Comparable to Fine-tuning Universally Across Scales and Tasks - LORA: LOW-RANK ADAPTATION OF LARGE LANGUAGE MODELS
- Intrinsic Dimensionality Explains the
Effectiveness of Language Model Fine-Tuning