LLM的PEFT微调方法¶
学习目标.¶
- 理解Prefix-Tuning、Adapter-Tuning、LoRA三种大模型参数微调方法的原理
PEFT(大模型参数高效微调)¶
目前在工业界应用大模型主流方式:参数高效微调方法(Parameter-Efficient Fine-Tuning,PEFT),PEFT 方法仅微调少量或额外的模型参数,固定大部分预训练参数,大大降低了计算和存储成本,同时最先进的 PEFT 技术也能实现了与全量微调相当的性能。
该方法可以使 PLM 高效适应各种下游应用任务,而无需微调预训练模型的所有参数,且让大模型在消费级硬件上进行全量微调(Full Fine-Tuning)变得可行。
目前应用较多的PEFT方法主要分为三大类:
- Prefix/Prompt-Tuning:在模型的输入或隐层添加 k个额外可训练的前缀 tokens(这些前缀是连续的伪 tokens,不对应真实的 tokens),只训练这些前缀参数;
- Adapter-Tuning:将较小的神经网络层或模块插入预训练模型的每一层,这些新插入的神经模块称为 adapter(适配器),下游任务微调时也只训练这些适配器参数;
- LoRA:通过学习小参数的低秩矩阵来近似模型权重矩阵 W的参数更新,训练时只优化低秩矩阵参数;
此外**Huggface 开源的一个高效微调大模型的库PEFT**,该算法库支持上述三类方法,可以直接调用。
1. Prefix Tuning¶
Prefix-Tuning 在模型输入前添加一个连续的且任务特定的向量序列(continuous task-specific vectors),称之为前缀(prefix)。前缀被视为一系列“虚拟 tokens”,但是它由不对应于真实 tokens 的自由参数组成。与更新所有 PLM 参数的全量微调不同,Prefix-Tuning 固定 PLM 的所有参数,只更新优化特定任务的 prefix。因此,在生产部署时,只需要存储一个大型 PLM 的副本和一个学习到的特定任务的 prefix,每个下游任务只产生非常小的额外的计算和存储开销。

Fine-tuning 更新所有 PLM 参数,并且需要为每个任务存储完整的模型副本。Prefix-tuning 冻结了 PLM 参数并且只优化了 prefix。因此,只需要为每个任务存储特定 prefix,使 Prefix-tuning 模块化且节省存储空间。
如下图所示,以 GPT2 的自回归语言模型为例,将输入 x 和输出 y 拼接为 z=[x;y] ,经过 LM 的某一层计算隐层表示h=[h_1,...,h_i,....,h_n] , h_i=LM_Ø(z_i, h<i) ,其中, X_{idx} 和Y_{idx}分别为输入和输出序列的索引。

Prefix-Tuning 在输入前添加前缀,即z=[Prefix,x,y] ,P_{idx}为前缀序列的索引,|P_{idx}| 为前缀的长度。前缀索引对应着由θ参数化的向量矩阵 P_θ ,维度为|P_{idx}|×dim(h_i)。隐层表示:若索引为前缀索引P_{idx},直接从 P_θ 复制对应的向量作为h_i (在模型每一层都添加前缀向量);否则直接通过 LM 计算得到,同时,经过 LM 计算的h_i也依赖于其左侧的前缀参数P_θ ,即**通过前缀来影响后续的序列隐层激化值**。
但是直接优化 P_θ 会导致训练不稳定,通过一个更小的矩阵 P_w和一个更大的前馈神经网络MLP_θ 对P_θ 进行重参数化: P_θ[i,:]=MLP_θ(P_w[i,:]) 。在训练时,LM 的参数 Ø 被固定,只有前缀参数 θ 为可训练的参数。训练完成后,只有前缀P_θ被保存。
P-Tuning 与 Prefix-Tuning 的方法思路相似,Prefix-Tuning 是将额外的embedding加在开头,看起来更像模仿Instruction指令,而P-Tuning 位置不固定。Prefix-Tuning 通过在每个层都添加可训练参数,通过MLP初始化,而P-Tuning只在输入的时候加入embedding, 并通过LSTM+MLP初始化.
Prompt Tuning 方式可以看做是 Prefix Tuning 的简化,只在输入层加入 prompt tokens,并不需要加入 MLP 进行调整来解决难训练的问题.
2. Adapter Tuning¶
与 Prefix Tuning 和 Prompt Tuning 这类在输入前可训练添加 prompt embedding 参数来以少量参数适配下游任务,Adapter Tuning 则是在预训练模型内部的网络层之间添加新的网络层或模块来适配下游任务。
假设预训练模型函数表示为Ø_w(x),对于 Adapter Tuning ,添加适配器之后模型函数更新为Ø_{w,w_0}(x), w是预训练模型的参数, w_0是新添加的适配器的参数,在训练过程中, w被固定,只有 w_0被更新。|w_0|<<|w| ,这使得不同下游任务只需要添加少量可训练的参数即可,节省计算和存储开销,同时共享大规模预训练模型。

Series Adapter的适配器结构和与 Transformer 的集成如上图所示。适配器模块被添加到每个 Transformer 层两次:多头注意力映射之后和两层前馈神经网络之后。适配器是一个 bottleneck(瓶颈)结构的模块,由一个两层的前馈神经网络(由向下投影矩阵、非线性函数和向上投影矩阵构成)和一个输出输出之间的残差连接组成。
3. LoRA¶
上述Adapter Tuning 方法在 PLM 基础上添加适配器层会引入额外的计算,带来推理延迟问题;而 Prefix Tuning 方法难以优化,其性能随可训练参数规模非单调变化,更根本的是,为前缀保留部分序列长度必然会减少用于处理下游任务的序列长度。因此微软推出了LoRA方法。
低秩适应(Low-Rank Adaptation)是一种参数高效的微调技术,其核心思想是对大型模型的权重矩阵进行隐式的低秩转换,也就是:通过一个较低维度的表示来近似表示一个高维矩阵或数据集。

基本原理:LoRA技术冻结预训练模型的权重,并在每个Transformer块中注入可训练层(称为秩分解矩阵),即在模型的Linear层的旁边增加一个“旁支”A和B。其中,A将数据从d维降到r维,这个r是LoRA的秩,是一个重要的超参数;B将数据从r维升到d维,B部分的参数初始为0。模型训练结束后,需要将A+B部分的参数与原大模型的参数合并在一起使用。
python伪代码
input_dim = 768 # 例如,预训练模型的隐藏大小
output_dim = 768 # 例如,层的输出大小
rank = 8 # 低秩适应的等级'r'
W = ... # 来自预训练网络的权重,形状为 input_dim x output_dim
W_A = nn.Parameter(torch.empty(input_dim, rank)) # LoRA权重A
W_B = nn.Parameter(torch.empty(rank, output_dim)) # LoRA权重B初始化LoRA权重
nn.init.kaiming_uniform_(W_A, a=math.sqrt(5))
nn.init.zeros_(W_B)
def regular_forward_matmul(x, W):
h = x @ W
return h
def lora_forward_matmul(x, W, W_A, W_B):
h = x @ W # 常规矩阵乘法
h += x @ (W_A @ W_B) * alpha # 使用缩放的LoRA权重,alpha缩放因子
return h
LoRA方法是目前最通用、同时也是效果最好的微调方法之一。
小结总结¶
- 本小节主要介绍企业面向超大规模模型的Prompt-Tuning方式进行了原理介绍,以及目前PEFT方式的原理讲解。