5.6 参数高效微调LoRA、Adapter与Prefix Tuning
1.全量微调为什么普通人玩不起
说起来,前面5.1 节里我们聊过预训练,一个大语言模型先在海量语料上把语言的底子打牢,这一步叫预训练。可预训练出来的模型,对什么任务都懂一点,却对什么任务都不算精通。要让它真正擅长某一件具体的事,比如翻译、做问答、写代码,还得再喂一批针对性的数据,把参数再调一调,这一步就叫微调。
微调这件事说起来朴素,做起来却很烧钱。我们回想一下3.10 节讲过的训练流程,每训练一个参数,不光参数本身要占显存,优化器还得为它存一份状态,反向传播还要为它存一份梯度。以一个几十亿参数的大模型为例,光是把模型权重装进显卡,就已经把显存挤得七七八八,再加上优化器状态和梯度,一张消费级显卡根本扛不住。我记得有一次小张在自己的电脑上想微调一个七十亿参数的模型,刚把数据准备齐,OOM(显存不够)三个字母就跳了出来,他盯着屏幕发了半天呆,最后只能去排队租卡。
更让人头疼的是,这么大的开销换回来的,常常只是参数表上很小幅度的变动。我们把训练后每个参数的变化量收集起来看,会发现真正起作用的调整,往往集中在一个相对低维的方向上。这就像装修房子,你以为是推倒重建,其实大部分改动就是刷刷墙、挪几件家具。既然真正有效的改动没那么大,那何必把所有参数都从头到尾翻一遍呢。
2.PEFT:冻住大的,只训小的
这种只动一小部分参数、把大头冻住的做法,统称参数高效微调(PEFT,Parameter-Efficient Fine-Tuning)。它有几条主要的路线,思路都差不多,就是先冻住预训练模型里绝大部分参数,让它们在训练中一动不动,再额外引入很少的新参数来学新东西。
我先简单捋一捋三条最常见的路线。
第一条叫Adapter。它的做法是在Transformer的每一层中间,额外插一个小小的模块。我之前看过一本讲模块化设计的书,里头提到一个比喻,说这就像给一台老式相机加装各种转接环,相机本身一动不动,转接环负责把新镜头的信号转接过去。Transformer主体冻住,Adapter这个小模块是可训练的,每层都加一个,参数量也远远少于整张网络。
第二条叫Prefix Tuning,和它相近的还有Prompt Tuning。这两条路子的思路是,模型权重完全不动,只在输入前面拼一段可学习的向量,叫前缀。5.5 节我们讲RLHF的时候提过,模型的输入和它的最终行为之间,是可以通过引导来调整的。Prefix Tuning就把这种引导具体化成一段真正参与训练的数字向量,让模型在不同任务上学会不同的前缀。Prefix Tuning改的是每一层注意力前面的那几个向量,Prompt Tuning更轻,只改最开头那段输入。
第三条也是今天最流行的,叫LoRA。下面我们专门来聊它。
3.LoRA:把更新量拆成两个小矩阵
LoRA的全称是Low-Rank Adaptation,低秩适配。它最核心的想法,是1.4 节概率信息论里协方差矩阵那种低秩观察的延伸,大模型在微调时,权重的实际更新量其实是个低秩的东西。
我之前翻过一本讲编剧的书,里头说一部几十集的电视剧,看着人物众多、情节千头万绪,可真正推动故事的主线,掰着手指头数得过来,就那么几条。权重矩阵的更新量也一样,表面积很大,真正起作用的维度却有限。
我们先把一个线性层的权重矩阵记作 , 是这一层原本的权重,下标 表示它是预训练好的、被冻住不动的那一份。全量微调的时候,我们其实是在学一个新的权重 ,等价于学一个更新量 ,使得 ,其中 表示训练过程中权重的变化量。LoRA的作者假设这个 是低秩的,也就是说,虽然 形状上是个大矩阵,但它真正包含的独立信息维度并不高,可以用两个小得多的矩阵乘出来近似。
具体怎么做呢,把 拆成两个小矩阵的乘积:
这里 和 是两个新引入的小矩阵,是要训练的对象, 仍然冻住。假设 是一个 的矩阵, 和 分别是输入维度和输出维度,那么我们让 取 , 取 ,其中 就是这两个小矩阵共享的秩,它远远小于 和 ,常见的取值就 或者 。一前一后乘起来,正好又能凑成和 一样形状的 。我们数一下参数量,原本 有 个参数,现在 和 加起来只有 个。拿 、 来算,原本一千六百多万个参数,现在 个,差不多只剩原来的零头。
训练的时候,输入先过 ,再额外加上 这一支的输出,两条路并行加在一起。初始化有个小讲究, 用随机高斯初始化, 初始化成全零,这样训练刚开始的时候 ,新加的这一支什么都不贡献,模型从预训练的状态稳稳起步,不会一上来就乱跑。
推理的时候更省心。训完之后,直接把 加到 上去,合并成一个新的 ,再删掉那两个小矩阵。这样一来,推理时模型结构和原来一模一样,没有多任何分支,延迟几乎没有增加。这是LoRA比Adapter讨喜的一点,Adapter那个额外插进去的小模块,推理时也得留着,多少有点开销。
至于秩 该怎么选,这是个很实际的问题。 越大, 能表达的信息越丰富,效果通常越好,参数也越多,省的就不明显了。 越小,参数越省,可表达能力也跟着降,复杂任务可能学不动。一般来说,任务越简单、和预训练分布越接近, 取小一点就够了,比如做情感分类这种, 或 已经够用。任务越复杂、和预训练差得越远,比如要教会模型一门新编程语言, 可以放到 甚至 。还有一种常见做法,是先设一个 的初值,再根据验证集上的表现来回扫几轮,挑一个性价比最高的。
4.QLoRA:消费级显卡上微调大模型
LoRA把要训练的参数省下来了,可还有一道坎,预训练模型本身的权重还是得装进显卡里做前向和反向。一个七十亿参数的模型,即便用fp16来存,也要十几GB显存,再加上LoRA训练要的那些东西,普通人手头的显卡还是吃不消。
于是就有了QLoRA。它在LoRA前面加了个Q,意思是Quantized,量化。量化的思路我们在3.10 节也提过,就是用更少的比特来表示同一个数,把存储压下来。QLoRA先把预训练模型量化到4比特(每个数只用4个二进制位表示)存进显存,再在这个4比特的冻结模型上挂LoRA来训练。这里用到一个叫NormalFloat的4比特数据格式,专门针对神经网络权重大多集中在零附近这种分布做了优化。还有一个叫双重量化的技巧,连量化时用的缩放参数本身再做一次量化,能再省一点显存。再加上分页优化器,把放不下的优化器状态挪到CPU内存里,需要时再取回来。
这一套组合拳打下来,一个原本要在高端专业卡上才能跑的大模型微调任务,小张在自己那台装着消费级显卡的台式机上也能跑起来了。他曾经用QLoRA在一张24GB显存的卡上,微调一个七十亿参数的开源大模型,让它专门回答某家公司的内部客服问题。训完之后合并权重,部署到服务器上,回复准确率比直接用原模型高出不少。说穿了,QLoRA让LoRA这件事真正落到了普通研究者和爱好者手里,不再是大厂独享的玩具。
5.几条路线怎么挑
讲到这里,我们不妨简单梳理一下,遇到具体任务该怎么选。
如果在意推理延迟,部署的时候不想多带任何额外结构,那就选LoRA,训完合并掉,推理时和原模型一样快。如果项目里已经有现成的Adapter实现,团队也熟悉,Adapter一样能上,只是推理时那个小模块得留着。Prefix Tuning和Prompt Tuning对参数量省得更狠,连权重都不动,可在大模型上整体效果不如LoRA稳定,调起来也娇气一些。
还有一点值得说,LoRA和后面5.5 节讲到的RLHF能很好地配合。RLHF的第二步训练奖励模型、第三步用PPO微调策略模型,都涉及对大模型的调整,这两步都可以把全量微调换成LoRA,照样能学到合理的对齐效果,开销却小得多。现在开源社区里,绝大多数针对大模型的轻量微调方案,背后几乎都有LoRA或QLoRA的影子,已经成了事实上的标配。
参数高效微调这件事,说到底就是把好钢用在刀刃上,既然大模型微调时真正起作用的改动有限,那就把算力集中到那些真正需要变的地方。LoRA用两个小矩阵把权重更新近似出来,QLoRA再叠加4比特量化把显存压下来,二者合在一起,让大模型的微调从大厂专属,变成了一件普通学生和研究者也能动手的事。今天就先到这儿,下一篇见。
练习
Q1. LoRA为什么把权重更新 拆成两个小矩阵 ,而不是直接学整个 ?
因为大量实验发现,微调时权重的有效更新是低秩的——表面积很大(比如 ),但真正起作用的独立维度有限。直接学整个 要 个参数,把它们冻住只学 ( 取 , 取 ),参数量降到 。以 、 为例,原本一千六百多万个参数,现在只要 万,只剩零头,省显存省算力,照样能学到合理的微调效果。
Q2. LoRA初始化时 用随机高斯、 初始化成全零,为什么这样安排?
这样训练刚开始时 (全零矩阵乘任何矩阵都是零),新加的这一支什么都不贡献,模型从预训练的状态 稳稳起步,不会一上来就乱跑、损失突跳。随着训练推进, 逐渐偏离零、 也在调整, 慢慢长出有意义的更新量。如果两个都随机初始化,训练初期模型行为会偏离预训练状态,相当于把好不容易攒的家底抖掉了。
Q3. 易错点:LoRA训完之后,推理时一定要保留那两个小矩阵 、 吗?
不一定。LoRA有个讨喜的特点:训完之后可以把 直接加到 上合并成新的 ,再删掉两个小矩阵。这样推理时模型结构和原来一模一样,没有任何额外分支,延迟几乎不增加。这比Adapter强,Adapter那个额外插进去的小模块推理时也得留着,多少有开销。当然如果想保留多个LoRA任务切换,也可以不合并、推理时动态加。
Q4.(面试题) 请说明 LoRA 的低秩假设依据,并解释 QLoRA 是如何在 LoRA 基础上进一步把显存压下来的。
LoRA的低秩假设来自经验观察:大模型微调时权重的有效更新量虽然形状上是大矩阵,但真正包含的独立信息维度有限(低秩),可以用两个小矩阵 、 的乘积 近似(秩 远小于 和 )。这就像一部几十集电视剧看似人物众多、情节千头万绪,真正推动故事的主线掰着手指头数得过来。所以只训 个新参数就能逼近全量微调的效果。QLoRA在LoRA前面加个Q(Quantized,量化),进一步压显存:LoRA虽然省了要训练的参数,但预训练模型本身那几十亿参数的权重还是得以fp16装进显卡做前向反向,单这一项就把消费级显卡挤爆。QLoRA先把预训练模型量化到4比特(用专门的NormalFloat格式,针对权重大多集中在零附近做了优化)存进显存,再在这个4比特冻结模型上挂LoRA训练;配合双重量化(连量化用的缩放参数本身再量化一次)和分页优化器(放不下的优化器状态挪到CPU内存)。这样原本要高端专业卡才能跑的70亿参数微调,24GB消费级显卡也能跑,让大模型微调真正落到普通人手里。