大模型高效微调-LoRA原理详解和训练过程深入分析
最终版如下:
上个月,有个读者私信我,屏幕都在颤抖。
他说他在京东蹲了仨月,终于抢到一块4090,那一刻感觉自己就是算力之王!结果呢?他兴冲冲地想跑一下微调,代码刚敲下去,屏幕一灰——OOM,直接炸了。
我问他:“你……该不会直接全量微调吧?”
他:“不然呢?”
哎,你想想,他那块4090才24GB显存。8B参数全量微调,光是把模型用fp16存下来就要16GB,梯度再来16GB,优化器状态又是齐刷刷的fp32参数备份、一阶矩、二阶矩……好家伙,满打满算128GB显存打底!
你看,这不叫微调,这叫给显卡上坟。
说到这儿,你是不是也经历过这种感觉?明明想干点大事,结果被硬件生生按在地上摩擦。好多开发者做梦都想自己折腾大模型,结果刚迈出第一步,就被显存给劝退了。
我自己当初也一样,痛到怀疑人生。后来我是怎么翻身的?老老实实投靠了LoRA。
LoRA的核心思路,说白了就一句话:你本来要移动一座大山,现在只需要在山脚下搭两架小梯子。
原来的权重矩阵W₀像个千斤顶,又大又重。我们不去动它,就在它旁边搞两个小矩阵B和A,用这俩小玩意儿去“模拟”微调需要的那点变化量。
你猜怎么着?假设原始矩阵是4096×4096,我们要算一个d²,也就是1677万个参数。但用LoRA拆开,d×r和r×d,r=8的时候,参数量直接掉到6.5万。
少了两个数量级!不是魔法,是线性代数里的低秩分解。
你想想,一个4096×4096的矩阵,能被两个小玩意儿给近似了,说明什么?说明原始矩阵的信息冗余很大!换句话说,微调需要调整的方向并不多,全挤在一个低维的小空间里。
论文里拿GPT-3 175B做实验,全量微调要1.2TB显存,LoRA一上,直接降到350GB。最后模型从350GB压缩到35MB——就存那几组低秩矩阵。
我第一次看这个压缩比,也觉得是不是数据写错了。后来自己跑了几次,发现——真的就是这么扯!
不过这儿有个你绝对想不到的细节。
你以为LoRA节省了计算量?错!LoRA其实并没有降低前向和反向的计算量,反而还多了一丢丢。
你架了两个小梯子,自然多算了乘法。那它到底省在哪了?
省在——你不用再计算预训练权重的梯度了!更不用维护它的优化器状态了!
原始权重W₀还在,但它被冻住了,不产生梯度,也不更新。这部分省下来的梯度显存有多大?以7B模型为例,光是梯度就省了7B×2字节,也就是14GB。优化器状态省得更狠——fp32的动量、方差、参数备份,一共12字节每参数,直接又省了84GB!
而你那两个小梯子呢?总共才几兆到十几兆,梯度和优化器状态几乎可以忽略不计。
所以你看,显存大头就这么悄无声息地省下来了。
那代价是什么?训练时间不一定减少,甚至可能变慢。因为你要多算那两个矩阵的梯度。但在现实世界里,谁在乎呢?多等几个小时,总比跑不起来强吧?
说到这儿,我刚开始用LoRA的时候,也踩过几个坑。
比如对秩r的选择太随意。有人觉得r越大越好,直接拉到64甚至128。但实验告诉你,对于很多任务,r=4或者8就足够了。 再大,不仅容易过拟合,显存和计算量也跟着嗖嗖往上涨。HuggingFace的PEFT库里默认r=8,我建议你从8开始试,不行再调。
学习率也是个容易栽的地方。LoRA论文里用了和全量微调一样的学习率,但我自己试下来发现——LoRA收敛更慢,需要调高一些。 最近有篇论文叫LoRA+,专门分析了这件事:B矩阵初始化为0,导致早期B的梯度接近0,所以B几乎不更新。他们建议给B单独配一个大几倍的学习率。我试过,确实有效,能提升1-2个点。但也不是所有数据集都这么神。如果你懒得折腾,直接统一用lr=2e-4,大部分场景下已经够好了。
还有那个初始化,很多人没想明白。
LoRA论文里,B初始化为0,A用随机高斯。为啥一个要搞成0?直接两个都随机不行吗?
不行!
你想想,两个都随机,一开始就会给模型灌进去一堆噪声,loss直接原地起飞,收敛根本稳不住。保持B=0,A随机,训练第一步A更新了,但B还是0,ΔW还是0。直到第二步B才从A那里得到非零梯度。模型在初始阶段保持了稳定。
这个设计,很精细。
后来呢?LoRA在图像生成领域比LLM火爆得多。几乎市面上所有定制模型,底层都是LoRA。但扩散模型更特殊——它有时序,不同时刻可能需要不同程度的调整。标准的做法是对UNet里所有的Cross-Attention层都加LoRA,有时也加在MLP上。
但记住,训练epoch不能多,三到五轮就到头了。再多,你生成的图就跟训练集一模一样——复制粘贴,毫无灵魂。
说到底,LoRA就像那句话说的:我们不是要造一艘新船,只要船舵能转向,就够了。
你学会了吗?
读者评论 5