LoRA: Low-Rank Adaptation of Large Language Models
前言
LoRA作为大模型的微调框架十分实用,在LoRA出现以前本人都是通过手动修改参数、优化器或者层数来“炼丹”的,具有极大的盲目性,但是LoRA技术能够快速微调参数,如果LoRA微调后的效果不行,就可以转而修改优化器或模块了。而调整参数一步是工作量最大的,而且大模型的参数都是以亿为计数单位的,所以人工硬调参数变得不太可能,而LoRA就为这个提供了解决方法!
LoRA通过仅训练低秩矩阵,然后将这些参数注入到原始模型中,从而实现对模型的微调。这种方法不仅减少了计算需求,而且使得训练资源比直接训练原始模型要小得多,因此非常适合在资源有限的环境中使用。
主流的微调方式
目前主流的低成本微调大模型方法包括2019年 Houlsby N 等人提出的 Adapter Tuning,2021年微软提出的 LORA,斯坦福提出的 Prefix-Tuning,谷歌提出的 Prompt Tuning,2022年清华提出的 P-tuning v2。
LORA 的效果会好于其它几种方法。其它方法都有各自的一些问题:
- Adapter Tuning 增加了模型层数,引入了额外的推理延迟
- Prefix-Tuning 难于训练,且预留给 Prompt 的序列挤占了下游任务的输入序列空间,影响模型性能
- P-tuning v2 很容易导致旧知识遗忘,微调之后的模型,在之前的问题上表现明显变差
LoRA的出发点
微调大规模语言模型到特殊领域和任务是自然语言处理的重要课题之一。但随着模型规模的不断扩大,微调模型的所有参数(full fine-tuning
)的可行性变得越来越低。以GPT-3的175B参数为例,每增加一个新领域就需要完整微调一个新模型,代价和成本很高。
LoRA的特点
- 优点:训练和计算成本低、可并行、不引入推理延迟
- 在 每层 transfomer block 旁边引入一个并行低秩的支路,支路的输入是transfomer block 的输入。然后将输出和 transfomer block 的输出相加,在固定主pretrian model参数的情况下,用支路去学习特定任务知识,来完成特定任务。
动机
-
Lora在不改变模型推理结构的条件下,直接调整少量参数
-
参数的变化量△W△W 通常是参数冗余的,使用低秩近似可以很好地表达
-
普适性强,可以微调任意模型中的线性层
算法流程
-
将模型参数分解为W0+△WW0+△W,W0W0 表示预训练参数,△W表示微调后的变化量
-
训练阶段:并联低秩矩阵分支,冻结预训练参数W0W0 ,学习变化量△W
-
推理阶段:去除并联分支,将学习到的△W与W0W0 相加,不改变模型结构,不影响推理效率。公式为:W=W0W0 +△W
实验结论
LORA论文实验表明
相比其它微调方法
- LORA 相比其它微调方法,增加参数量不会导致性能的下降。
- 性能上与全参数微调持平甚至超过。
秩的选择
实验结果显示,对于一般的任务, r=1,2,4,8r=1,2,4,8 就足够了。而一些领域差距比较大的任务可能需要更大的 rr
同时,增加 rr 值变大并不能提升微调的效果,这可能是因为参数量增加需要更多的语料。
动手实践
目前 LORA 已经被 HuggingFace 集成在了 PEFT(Parameter-Efficient Fine-Tuning) 代码库里。lora 微调需要设置两个参数一个是r,即矩阵秩。 alpha是一个scale参数。
pip install peft
通过使用 get_peft_model 包装基础模型和 PEFT 配置,准备使用 PEFT 方法(例如 LoRA)进行训练的模型。对于 bigscience-mt0-large 模型,仅需要训练 0.19% 的参数!
from transformers import AutoModelForSeq2SeqLM
from peft import get_peft_config, get_peft_model, LoraConfig, TaskType
model_name_or_path = "bigscience/mt0-large"
tokenizer_name_or_path = "bigscience/mt0-large"
peft_config = LoraConfig(
task_type=TaskType.SEQ_2_SEQ_LM, inference_mode=False, r=8, lora_alpha=32, lora_dropout=0.1
)
model = AutoModelForSeq2SeqLM.from_pretrained(model_name_or_path)
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()
结果如下:
"trainable params: 2359296 || all params: 1231940608 || trainable%: 0.19151053100118282"
可以看到仅仅需要训练0.19%的参数
加载一个PEFT模型用于推理:
from peft import AutoPeftModelForCausalLM
from transformers import AutoTokenizer
import torch
model = AutoPeftModelForCausalLM.from_pretrained("ybelkada/opt-350m-lora").to("cuda")
tokenizer = AutoTokenizer.from_pretrained("facebook/opt-350m")
model.eval()
inputs = tokenizer("Preheat the oven to 350 degrees and place the cookie dough", return_tensors="pt")
outputs = model.generate(input_ids=inputs["input_ids"].to("cuda"), max_new_tokens=50)
print(tokenizer.batch_decode(outputs, skip_special_tokens=True)[0])
结果:
"Preheat the oven to 350 degrees and place the cookie dough in the center of the oven. In a large bowl, combine the flour, baking powder, baking soda, salt, and cinnamon. In a separate bowl, combine the egg yolks, sugar, and vanilla."
拓展:QLoRA和LoRA的区别
QLoRA和LoRA的性能对比通常取决于多个因素,包括任务类型、数据集、模型架构、硬件环境等。以下是一些可能的性能对比方面:
参数数量和训练效率
- LoRA:由于使用了低秩矩阵,LoRA显著减少了需要微调的参数数量,这通常意味着更快的训练速度和更低的内存需求。
- QLoRA:进一步通过量化减少了参数的大小,因此在相同的硬件上,QLoRA可能会提供更高的训练效率,尤其是在资源受限的环境中。
"参数量化"是指在机器学习和深度学习中,将模型的参数(通常是权重和偏置)从浮点数(如32位浮点数)转换为低精度的表示,比如16位、8位甚至更少的位。这样做的目的主要是为了减少模型的存储需求和计算复杂度,从而提高模型在硬件上的运行效率,尤其是在那些不支持或不擅长处理浮点运算的硬件上。
参数量化可以带来以下好处:
- 减少模型大小:量化后的模型占用的存储空间更小,便于在资源受限的设备上部署。
- 降低计算成本:量化后的模型在计算时需要的算力更少,可以减少能耗和提高处理速度。
- 提高硬件兼容性:在不支持浮点运算的硬件上,量化模型可以更有效地运行。
然而,参数量化也可能带来一些挑战,比如模型精度的下降,因为量化过程可能会导致信息的损失。因此,量化技术需要在模型效率和精度之间找到一个平衡点。
精度和模型性能
- LoRA:保持浮点精度,通常能够提供与全模型微调相当或接近的性能,尤其是在参数较少且任务与原始预训练任务相似的情况下。
- QLoRA:量化可能会引入一些精度损失,这可能导致模型性能略有下降。然而,这种损失通常很小,特别是在使用4位或更高位量化时。
推理速度和资源消耗
- LoRA:在推理时,由于参数数量的减少,LoRA可以提高推理速度并减少资源消耗。
- QLoRA:由于参数量化,QLoRA在推理时可能会更加高效,特别是在不支持浮点运算或浮点运算效率较低的硬件上。
具体性能对比
- 在标准硬件上:LoRA和QLoRA的性能可能非常接近,但QLoRA可能在使用低精度运算的硬件上具有优势。
- 在移动或嵌入式设备上:QLoRA的性能通常优于LoRA,因为它更适合资源受限的环境。
- 在特定任务上:对于某些任务,量化可能对模型性能的影响较小,因此QLoRA可能接近或达到LoRA的性能。
实际应用中的考量
点击一文读懂「LoRA」:大型语言模型的低秩适应查看全文。