type
status
password
date
slug
summary
category
URL
tags
icon
参数高效微调方法仅对模型的一小部分参数(这一小部分可能是模型自身的,也可能是外部引入的)进行训练,便可以为模型带来显著的性能变化,一些场景下甚至不输于全量微调。
![LiaIin V, Deshpande V, Rumshisky A. Scaling down to scale up. A guide to parameter-efficient fine-tuning[J]. arXiv preprint arXiv:2303.15647,2023. 高效微调方法](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F06d43171-d41b-42f5-92c6-383f47fbf41c%2Fcc3d6520-ca7f-4a35-bf7c-f055f6a2111a%2FUntitled.png?table=block&id=3b60e3f4-bd9d-4f4d-bb0f-847aa9bd42e5&t=3b60e3f4-bd9d-4f4d-bb0f-847aa9bd42e5&width=708&cache=v2)
- additive:增量模型。通过增加额外的参数或网络层,实现对预训练模型的微调。例如
- selective:选择性方法。只对模型的部分层进行调整,减少了训练的参数数量。例如BitFit只更新bias
- adapters:适配器。设计Adapter结构并嵌入Transformer中。(实际上就是在原始模型部分模块中增加新的网络结构,做旁路)
- soft prompts: 软体提示。通过对向量的参数进行微调来适应不同的任务。
- reparametrization-based:基于重参数化。通过寻找低维表示的权重矩阵,减少了需要训练的参数数量。
显存计算
测量工具
- 网页
- 安装库:accelerate, transformers
被Transformers开发库支持的模型,计算值比较稳定, 比如Misteral, Gemma,Qwen1.5等,但是,源代码没有集成进Transformers的一些模型,可能会不可靠。
原理
在模型的前向传播(Forward)过程中,仅需依次计算各中间层的输出,并将这些中间结果保存下来,这些保存的中间结果被称为激活值(activations)在随后的反向传播阶段,这些激活值用于梯度的计算,从而实现模型参数的更新。而在推理阶段,由于不涉及梯度计算,仅需获取最终的输出结果,因此无需保存中间层的激活值。
模型框架
例如pytorch框架的cuda context会占用大约几百MB显存,与版本有关;
模型基础参数
例如上图中的、。又如在8B模型中,使用fp16,那么会占用16GB内存。
激活值
例如上图中的。临时占用显存,在反向传播阶段,这些激活值用于梯度的计算。一旦当前迭代的反向传播完成,显存就会被收回。
transformer激活参数:,层block、b为batch_size、s为序列长度、a为注意力头数。
梯度值
例如上图中的。临时占用显存,当前训练迭代的参数更新完成后,下一轮迭代开始前被清空。
使用 LoRA 微调时,激活值的存储量与全量微调完全相同。因为根据链式法则,计算某一层参数的梯度时,会用到前面所有激活层的参数。
优化器
许多优化器需要保存历史信息以调整更新步长。Lora会显著降低优化器的显存占用。
- Momentum:需要保存上一次的梯度动量。
- Adam:需要保存一阶矩(动量)和二阶矩(梯度平方的累积),即每个参数对应 2 个额外状态。
- AdaGrad/RMSprop:需要保存梯度平方的累积(与参数同维度)。
降低显存占用方案
启用激活检查点
背景
模型结构
数据集格式
指令微调
数据集
输出
- 输入input: <bos> Q1 Q2 A1 A2 A3
- 标签labels: <pad> <pad> A1 A2 A3 <eos>
输入input是QA的拼接,标签labels中Q的部分的被<pad>token填充(mask掉),以便后续计算loss时,Q不纳入计算。
对话微调
数据集
Q1A1 Q1A1Q2A2 Q1A1Q2A2Q3A3
Q1A1Q2A2Q3A3
示例
数据集
数据预处理
- input_ids:输入的是真个原始文本,包括填充部分
- attention_mask:需要计算注意力的地方为1,不需要的地方填充0(例如padding部分)。下面的padding_mask部分
- labels:不需要计算loss的部分填充padding(一般padding为-100)。chatglm3在代码中写死了损失函数会忽略
labels
中值为 -100 的位置。
创建模型
GenerationConfig
:在大语言模型(LLM)生成文本时用于控制生成过程和输出特性的配置类
参数 | 描述 |
max_length | 限制生成文本的最大长度,避免生成过长的文本。 |
min_length | 确保生成文本达到一定的最小长度,防止生成过短或不完整的内容。 |
temperature | 用于控制生成文本的随机性。值越高,生成的文本越随机、多样化。 |
top_k | 从概率排名前 k 的词中进行采样,有助于减少生成结果的随机性。 |
top_p | 把模型输出的所有词按照概率从高到低进行排序,直到累加的概率和达到 p |
repetition_penalty | 对已经出现过的词施加惩罚,降低其再次出现的概率。 |
eos_token_id | 指定序列结束的标记,当模型生成该标记时,生成过程结束。 |
pad_token_id | 指定填充标记,用于在生成过程中填充序列到固定长度。 |
num_beams | 在Beam Search中使用,指定束的数量。 |
num_return_sequences | 指定生成的序列数量,用于一次性生成多个不同的结果 |
添加Lora适配
对自注意力+MLP层进行lora
确定哪些参数使用lora微调
添加lora后打印模型,可以看到lora微调层会添加lora_A、lora_B参数
训练
模型保存与读取
技巧
- 一般使用几万条数据。在简单的信息提取任务中使用几千条数据,就达到了不错的效果。
- 13B模型的fp16 LoRA可能果优于4位qLora 30B模型。
Freeze
Freeze是冻结的意思,Freeze方法指的是参数冻结,对原始模型的大部分参数进行冻结,仅训练少部分的参数,这样就可以大大减少显存的占用,从而完成对大模型的微调。
BitFit
基本思想
只更新bias的参数或者部分bias参数。对于Transformer模型而言,涉及到的bias参数有attention模块中计算query,key,value跟合并多个attention结果时涉及到的bias,Layernormalization层的bias参数,MLP层中的bias。
模型中bias参数量的计算
代码实战
- 数据集https://huggingface.co/datasets/shibing624/alpaca-zh 指令微调
- 预训练模型Langboat/bloom-1b4-zh
- 参数高效微调方法BitFit
提示工程
基本思想
Prompt-Tuning 的思想:冻结主模型全部参数,通过提示词提升模型效果; Prompt 存在两种形式,一种是 hard prompt, 一种是soft prompt。
- hard prompt:人工指定输入
- soft prompt:不用人工指定,进行自学习
hard prompt
假设我们将英语句子翻译成德语。我们可以通过各种不同的方式询问模型,如下所示。
- "Translate the English sentence '{english_sentence}' into German: {german_translation}”
- "English:'{english_sentence}'| German: {german_translation}”
- "From English to German::'{english_sentence}'→{german_translation}
本质就是人工不断更换模板,尝试哪种提示词更加好用
Prompt Tuning(soft prompt)
给定一系列个标记,对这些标记进行Embedding,形成一个矩阵,其中是嵌入空间的维度。我们的软提示表示为一个参数,其中是提示的长度。然后,我们的提示与嵌入后的输入连接起来,形成一个单一矩阵,接着像往常一样通过Transformers。在训练过程中只更新提示参数。
本质上,在训练数据前添加一段Embedding,当作提示词;并训练这段Embedding,提升模型效果。
代码实战
transformers 4.33.1 | peft 0.50 | accelerate 0.22.0 |

- num_virtual_tokens:指定虚拟Token数。在原理篇中,提到过提示虚拟 Token 的长度在20左右时的表现已经不错(超过20之后,提升Prompt token长度,对模型的性能提升不明显了);同样的,这个gap也会随着模型参数规模的提升而减小(即对于超大规模模型而言,即使提示虚拟 Token 长度很短,对性能也不会有太大的影响)
- task_type:指定任务类型。如:条件生成任务(SEQ_2_SEQ_LM),因果语言建模(CAUSAL_LM)等。
- prompt_tuning_init_text:用于初始化提示嵌入的文本,在使用文本(TEXT)初始化方法时使用。从词汇表中选择部分token作为prompt的初始值。
- prompt_tuning_init:提示嵌入的初始化方法。PEFT支持文本(TEXT)和随机(RANDOM)初始化。
整体代码
P-Tuning(soft)
P-Tuning 的思想:在 Prompt-Tuning 的基础上,对 Prompt 部分进行进一步的编码计算,加速收敛。具体来说, PEFT 中支持两种编码方式,一种是 LSTM, 一种是 MLP。
对Prompt-Tuning的Embedding输入到LSTM/MLP在与input一起输入到大模型。
代码实战
Prefix-Tuning(soft)
生成类模型的解码逻辑是根据历史输入,每次预测一个新的token,然后将新的token加入输入,再预测下一个token。这个过程中,会存在大量的重复计算,因此可以将key和value的计算结果缓存,作为past_key_values输入到下一次的计算中,这一技术又被称之为kv_cache。Prefix-Tuning就是通过past_key_values的形式将可学习的部分放到了模型中的每一层,这部分内容又被称之为前缀。
代码实战
p-tuning v2(soft)
p-tuning v2没有强制嵌入矩阵重参数化。所以,peft没有专门实现p-tuning v2的代码。初始化PrefixEncoder时把self.prefix_projection置为False,就相当于p-tuning v2了。

LoRa
通过矩阵分解的方式,将原本要更新的大的矩阵变为两个小的矩阵。权重更新: 。训练时,输入分别与原始权重和两个低秩矩阵进行计算,共同得到最终结果,优化则仅优化 A 和 B 。训练完成后,可以将两个低秩矩阵与原始模型中的权重进行合并,合并后的模型与原始模型无异,避免了推理期间 Prompt 系列方法带来的额外计算量。

代码实战
lora config常用参数
- r:秩
- target_modules:哪些层做lora,支持正则化。例如
target_modules=[.*1.*query_key_value]
、target_modules=["query_key_value","dense_4h_to_h"]
添加lora后打印模型,可以看到lora微调层会添加lora_A、lora_B参数
- lora_alpha:对lora权重进行缩放。,其中,和 是 LoRA 模块中引入的两个低秩矩阵,是低秩矩阵的秩,就是缩放参数。
- lora_dropout:lora_dropout的默认值为0
- bias:偏差可以是“无”、“全部”或“lora_only”。 当偏差类型设为 “无” 时,在 LoRA 微调过程中,模型里所有层的偏差项都不会被更新;偏差类型为 “全部” 时,在微调过程中,模型里所有层的偏差项都会被更新;若偏差类型是 “lora_only”,那么只有与 LoRA 模块相关的偏差项会被更新,而原始模型中其他层的偏差项保持不变。
- modules_to_save:除了lora以外还要训练哪部分参数。这部分训练参数不用进行矩阵分解。
整体代码
保存模型
QLora
量化参数
IA3
简介
IA3的思想:抑制和放大内部激活,通过可学习的向量对激活值进行抑制或放大。具体来说,会对K、V、FFN三部分的值进行调整,训练过程中同样冻结原始模型的权重,只更新可学习的部分向量部分。训练完成后,与Lora类似,也可以将学习部分的参数与原始权重合并,没有额外推理开销。
原理

模型结构
我们采用可学习的向量对模型的激活值进行逐元素乘法(即缩放)。具体来说,我们考虑形式为 的自适应方式,其中 是学习到的特定任务向量, 表示逐元素乘法, 是长度为的激活序列。 的第 个元素是 。在初步实验中,我们发现在自注意力和编码器 - 解码器注意力机制中的键和值,以及位置式前馈网络的中间激活上引入缩放向量就足够了。具体来说,我们引入三个学习向量 、 和 ,它们在注意力机制中的应用方式为:
在位置式前馈网络中的应用方式为 ,其中 是前馈网络的非线性函数。我们在每个Transformer层块中引入一组独立的 、 和 向量。对于一个有L层块的Transformer编码器,这总共会添加 个新参数;对于一个有L层块的解码器,则会添加 个新参数(其中的系数2是考虑到自注意力和编码器 - 解码器注意力的存在)。、\(l_{v}\) 和 \(l_{ff}\) 都初始化为1,这样在添加它们时,模型计算的整体函数不会改变。此外,我们还注意到,如果一个模型仅用于单个任务, 引入的修改也可以永久应用于权重矩阵,这样就无需进行逐元素乘法,模型的架构也保持不变。这是因为在 中进行的逐元素乘法总是与矩阵乘法同时出现,且 。在这种情况下,与原始模型相比,我们的方法不会产生额外的计算成本。
损失函数
- 正确的损失
在该公式中,模型被训练以提高在给定输入序列的情况下,正确目标序列 的概率。
- 错误损失
其中 是个错误目标序列中的第个。模型会被训练为给错误选择分配更低的概率。
- 归一化损失
对于给定的训练示例,可能的目标序列长度可能有很大差异,在多选任务中尤其如此。因此,基于概率对每个选择进行排名可能会 “偏向” 较短的选择,因为模型分配给每个标记的概率 。为纠正这一问题,我们在进行排序分类时考虑使用长度归一化,即将模型在每个可能答案选择上的得分除以该选择中的标记数量。
然后,通过最小化softmax交叉熵损失来最大化正确答案选择的长度归一化对数概率:
在使用 、 和 训练模型时,我们简单地将它们相加。这样做避免了引入任何在少样本设置中难以调整的超参数(在少样本设置中,实际大小的验证集必然很小)。
我们在附录C中报告了对T0-3B的所有参数进行微调时,我们发现,添加 可将准确率从60.7% 提高到62.71%,同时包含 和 可进一步将准确率提高到63.3%。
代码实战
配置参数
task_type
:指定任务类型。如:条件生成任务(SEQ_2_SEQ_LM),因果语言建模(CAUSAL_LM)等。
inference_mode
:是否在推理模式下使用Peft模型。当 inference_mode 设为 True 时,模型会进入推理模式。在这个模式下,模型不会进行参数更新或者反向传播,仅依据输入来计算输出。这样做能显著减少计算资源的消耗,同时加快推理速度。
target_modules
:要替换为 IA3 的模块名称列表或模块名称的正则表达式,例如,注意力块。在 PEFT 中支持的模型中默认的模块名如下所示:
feedforward_modules
:target_modules 中被视为前馈(feedforward)层的模块名称列表或模块名称的正则表达式。
module_to_save
:除了 IA3 层之外要设置为可训练并保存在最终检查点中的模块列表。
Adapters
简介
在训练中只有绿色的部分是可训练的,别的部分的参数被锁定。实现中和设计中有几个很重要的细节:
- 插入的 adapter 块在 feed-forward 后面,在残差链接前面,因此不影响 transformer block 残差连接在深度上的效果。
- Adapter块首先将原始的 维特征投影到较小的维度 ,然后应用非线性再投影回 维。每层添加的参数总数(包括bias)为 。大大减少了训练参数
- adapter 层本身是含有 skip-connection 的,因此初始化接近0的adpter块对 transformer block 来说相当于不变。这一点很重要,因为训练的初期模型相当于和原模型保持一致,对训练的稳定性非常重要。
- 作者在训练中让 transformer bolck 的 layerNorm 层是不锁参的。
Adapter首先将原始的d维特征投影到较小的维度 m,然后应用非线性再投影回d维。每层添加的参数总数(包括bias)为 。在实际训练过程中只用更新绿色部分。
通过设置,限制每个任务添加的参数数量,使用的参数大约是原始模型参数的0.5−8%。