← 阅读室全部精读正在排版公式与图表…
PAPER ANALYZER · P15 · 细粒度方向

LoRA:用低秩更新理解参数高效适配

从矩阵形状、初始化和合并推理,到情绪任务中的可验证迁移

Hu et al. · ICLR 2022 · arXiv:2106.09685v2

一句话总结

冻结基础矩阵,只训练低秩更新;节约训练参数并不自动解决跨场景遗忘。

先明确 LoRA 到底节省什么

LoRA 研究的是预训练模型的参数高效适配:已经有一个训练好的大模型,现在希望用较小代价学会一个下游任务。作者没有重新发明情感标签,也没有专门设计跨场景表情识别方法,而是限制权重更新的自由度。因此它在你的学习路线中是一件实验工具,可以放进视觉或多模态编码器,让比较不同场景的适配方案更便宜。

先分清三个对象:原权重是模型已学会的映射,更新量是新任务要求它改变的部分,低秩分解则是表示这一更新量的方式。原权重仍然可以是满秩大矩阵;论文没有把整个预训练网络都压缩成低秩。把“低秩更新”误读成“低秩模型”,会错误估计推理内存和模型容量。

论文题为 LoRA: Low-Rank Adaptation of Large Language Models,Hu 等人发表于 ICLR 2022,预印本编号为 2106.09685。本页依据原文第二版及作者微软仓库阅读。原文主要证据来自语言任务,因此把方法移植到动态表情、ALMT 的文本编码器或视觉 Transformer,均需重新验证,不能直接继承论文中的准确率结论。

从矩阵乘法走到低秩更新

假设一个线性层把长度为 k 的输入向量映射为长度为 d 的输出。普通微调允许 d×k 个权重都变化;LoRA 只学习两个窄矩阵,先把输入送到 r 维中间空间,再映射回输出空间。r 是人为指定的秩上限,通常远小于输入输出维度。这里的“维度低”描述更新的方向数,不等于特征中只能表达 r 种情绪。

请把原文 Figure 1 中的两条路径连起来看:一条经过冻结的预训练权重,另一条经过可训练的小矩阵,两路相加才是这一层的输出。相加要求输出维度一致。注意力层里的查询、键、值投影只是可以替换的线性层位置,LoRA 本身不是一个新的注意力公式,也不负责决定视频如何采样。

缩放系数用于调节更新量的幅度。固定学习率时改变秩,若不考虑缩放,会同时改变容量和优化尺度,因此排名变化不一定来自表达能力。原文采用 alpha 除以 r 的缩放;教学实验应同时记录 r、alpha、插入层和 dropout,避免只写一个“用了 LoRA”的模糊标签。

$$h=W_0x+\frac{\alpha}{r}BAx,\quad W_0\in\mathbb R^{d\times k},\ B\in\mathbb R^{d\times r},\ A\in\mathbb R^{r\times k}$$

原文式(3)的缩放展开:先算 Ax,再算 B(Ax),最后加回冻结路径。输入为 k 维,两个分支的输出均为 d 维。

$$N_{\rm full}=dk,\qquad N_{\rm LoRA}=r(d+k)$$

教学参数计数,不是原文新增实验。若 d=k=768、r=8,则原矩阵有589824个权重,小矩阵共12288个可训练权重,约为前者的四十八分之一;未计分类头和偏置。

$$W_{\rm merged}=W_0+\frac{\alpha}{r}BA$$

由原文机制推导的合并等式。评价模式可将两个小矩阵的乘积一次性加到原权重中,再使用普通线性层。只有合并或等效实现成立时,才能据此说明不新增这一层的推理分支。

$$\frac{\partial L}{\partial A}=\frac{\alpha}{r}B^T\frac{\partial L}{\partial h}x^T,\quad \frac{\partial L}{\partial B}=\frac{\alpha}{r}\frac{\partial L}{\partial h}(Ax)^T$$

教学链式求导。B 初始化为零时,第一次反传 A 的梯度可以为零,而 B 一般有梯度;第二步以后 A 才可能开始变化。这是正确初始化的结果,不应立刻判断程序断梯度。

$$\phi(U,V)=\frac{\|U^TV\|_F^2}{\min(i,j)}$$

原文式(4)的简化写法:U、V 是两组正交基,分别有 i、j 列,比较的是子空间重合程度。接近一表示方向重合,接近零表示近乎正交;它不是分类准确率,也不能由一次较高重合度推断所有任务都低秩。

初始化、训练与部署的细节

原文描述随机初始化 A、零初始化 B,使更新支路在开始时为零。这保证刚插入模块后模型函数与预训练模型一致,便于把性能变化归因于训练。微软实现实际使用 Kaiming 均匀初始化 A;某处代码注释与变量名相反,阅读时应以执行语句为准,不能照抄注释成为方法解释。

冻结原权重指它不接收参数更新,并不意味着前向激活和反向传播都消失。梯度仍需穿过相应运算传向可训练模块,因此显存下降不会简单等于可训练参数下降比例。基础模型本身仍须加载;LoRA 也不自动包含量化,不能仅凭小矩阵规模保证任意大模型能在你的笔记本显卡运行。

保存检查点时,可以仅保存低秩矩阵与被训练的分类头,但恢复必须配合同一份基础权重、相同模块位置和缩放配置。若分类头已经训练却没有保存,加载后的准确率可能完全失真。合并后的权重则是另一种交付形式,应记录是否已合并,避免重复相加。

原文讨论了多个任务同时服务的限制:若把某个适配器合并进基础模型,切换任务就需要替换或重新合并权重;一批样本分别使用不同适配器也需要额外工程处理。因此“推理零额外延迟”依赖服务方式,不是所有动态多任务系统都自动成立。

flowchart TD
X[Input features] --> W[Frozen weight]
X --> A[Trainable low rank A]
A --> B[Trainable low rank B]
W --> S[Sum]
B --> S
S --> L[Task loss]
L -. Update adapters .-> A

如何读实验而不被参数比例误导

原文 Table 3 比较 GPT-2 在 E2E 数据到文本生成任务上的表现。BLEU、NIST、MET、ROUGE-L 和 CIDEr 衡量生成文本与参考文本的匹配,不能当作表情识别准确率。下面选取同一模型规模的完整微调与 LoRA,既看质量,也看可训练参数;星号基线来自既有工作,不能视为作者重新运行了所有对照。

第二张表来自 Table 6,是 GPT-3 查询和值投影同时适配时的秩分析。性能没有随秩单调上升,说明这个任务里多增加方向未必有用。但这不是秩一普遍最优的理论证明;原文明确讨论了下游语言或任务大幅变化时,小秩可能不足。研究时应该让验证集决定合适容量,而不是只选择论文里最惊人的比例。

原文 Table 3:GPT-2 Medium / E2E
方法可训练参数BLEUNISTROUGE-L
全量微调354.92M68.28.6271.0
LoRA0.35M70.4 ± 0.18.85 ± 0.0271.8 ± 0.1
原文 Table 6:GPT-3 对 Wq、Wv 适配,验证准确率 %
秩WikiSQLMNLI
173.491.3
273.391.4
473.791.3
873.891.6
6473.591.4

图表定位与源码对应

原文 Figure 2 把参数量与性能放在同一坐标系,用于比较效率;Figure 3 比较不同秩学到的子空间,回答增加的方向是否重复。两图分别提供效果证据与机制证据,不应混为同一种实验。Table 5 还固定参数预算比较插入查询、键、值等不同位置,提醒你公平消融必须控制预算。

下面第一段代码创建 A、B 并冻结原矩阵,第二段实现两路相加。阅读时依次核对形状、缩放和 merged 状态,而不是只寻找类名。作者仓库提供 NLU/NLG 示例;GPT-2 原文 Table 11 的设置包括秩四、alpha 三十二、批量八、五个 epoch、学习率零点零零零二和五百步预热。这些是语言模型实验配置,不是表情任务的默认最优配置。

作者官方代码:参数与初始化:loralib/layers.py:108–125;固定提交 c4593f060e6a。

        # Actual trainable parameters
        if r > 0:
            self.lora_A = nn.Parameter(self.weight.new_zeros((r, in_features)))
            self.lora_B = nn.Parameter(self.weight.new_zeros((out_features, r)))
            self.scaling = self.lora_alpha / self.r
            # Freezing the pre-trained weight matrix
            self.weight.requires_grad = False
        self.reset_parameters()
        if fan_in_fan_out:
            self.weight.data = self.weight.data.transpose(0, 1)

    def reset_parameters(self):
        nn.Linear.reset_parameters(self)
        if hasattr(self, 'lora_A'):
            # initialize B the same way as the default for nn.Linear and A to zero
            # this is different than what is described in the paper but should not affect performance
            nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
            nn.init.zeros_(self.lora_B)

作者官方代码:前向分支:loralib/layers.py:144–153;固定提交 c4593f060e6a。

    def forward(self, x: torch.Tensor):
        def T(w):
            return w.transpose(0, 1) if self.fan_in_fan_out else w
        if self.r > 0 and not self.merged:
            result = F.linear(x, T(self.weight), bias=self.bias)            
            result += (self.lora_dropout(x) @ self.lora_A.transpose(0, 1) @ self.lora_B.transpose(0, 1)) * self.scaling
            return result
        else:
            return F.linear(x, T(self.weight), bias=self.bias)

从一个线性层开始,做出自己的验证

第一步只写一个小线性层,输入维度八、输出维度四、秩二,复制基础权重,检查初始输出与原模型的最大绝对误差接近浮点精度。完成一次反向传播后打印所有参数名、requires_grad 和梯度范数,确认基础矩阵没有更新,B 有变化。这个检查不依赖数据集,一张截图和一个断言就能证明你理解了更新路径。

第二步在同一批随机输入上分别计算未合并与合并输出,验收阈值可先设为 FP32 下最大绝对差小于十的负五次方。再执行训练、评价、训练模式切换,确认没有重复加更新量。测试保存和重新加载时,比较输出而非只检查文件能否打开;初始化与合并错误经常产生能运行但不正确的模型。

第三步把模块插入已能跑通的编码器,只训练任务头作为基线,再加入查询和值投影的 LoRA。先比较秩二、四、八,并固定训练步数;额外记录峰值显存、每步时间、训练参数量和验证指标。若只看到显存节约而准确率下降,也是一项有意义的效率折中,不能省略失败结果。

第四步再进行两个场景的顺序学习:只对后一场景训练同一个适配器,评价前后场景。LoRA 自身没有旧任务约束,所以即使基础权重冻结,旧场景表现仍可能下降,因为新增更新量和分类头会改变输出。接下来才有理由比较不同场景独立适配器、LwF 蒸馏或 GPM 投影,不要把参数高效直接等同于抗遗忘。

你的验收产物应包含一个矩阵形状图、一组初始化与合并一致性断言、一张容量—指标—显存表,以及两场景结果矩阵。能够解释“为什么冻结基础权重仍会遗忘”,比背诵低秩公式更接近真正掌握。作者证据支持若干语言任务上的高效适配;对于表情动态、细粒度类别和强域偏移,低秩是否足够仍是你的待检验假设。

进一步理解秩、模块位置与独立局限

秩可以通过一个二维例子理解。若更新矩阵的两列互相成比例,所有输入经过它以后,新增输出都落在同一条直线上,此时秩为一。允许秩二后,新增输出才能覆盖整个二维平面。高维模型同理:秩限制的是更新能组合的独立方向,而不是每个参数只能变化一点,也不是模型只能预测有限个类别。

可辨识性也是一个有用概念。同一个更新矩阵可以由不同 A、B 表示:把 A 放大两倍、把 B 缩小一半,乘积不变。因此单独比较 A 的数值大小不能判断某个任务改变了多少,更可靠的对象是缩放后的 BA、网络输出变化和任务指标。两个随机种子的 A 不相等,也不代表它们学到的有效更新完全不同。

模块位置与秩应分开研究。对查询矩阵适配,主要改变当前 token 如何提出检索要求;对值矩阵适配,改变被汇聚的信息内容。它们都通过后续注意力运算影响结果,但作用路径不同。先固定总参数预算,再比较只改查询、只改值、同时修改二者,才能避免把“覆盖更多位置”的优势误解释成“只是参数更多”。

若模型把查询、键、值打包成一个大矩阵,直接替换整个投影层可能连键也一起训练。需要先检查模块命名、权重切片与前向拼接方式。验收时打印实际新增参数的完整名称和形状,数出哪些子块有梯度;在论文中写查询和值适配时,程序也应确实只包含对应部分。

一次最小算例可以取输入为两个数,先用一行 A 计算一个加权和,再用两行一列的 B 把这个标量送到两个输出。改变输入后,新增输出的两个分量比例保持由 B 决定,直观显示秩一的限制。你可以据此设计断言:若多组新增输出构成的矩阵数值秩明显超过一,应检查是否把偏置或其他训练分支也算进来了。

作者限制集中在小秩的适用范围、模块选择与多任务部署方式。独立判断是,论文语言实验中的低秩性不能自动证明动态表情的时间变化同样低秩;背景和身份偏差可能占据最容易学习的方向。因此你应同时检查跨人物、跨场景和少数类别,而不只观察平均准确率。

对于你的学习项目,可以先把 LoRA 当作控制实验成本的工具,而非论文创新点。只有当你提出明确问题,例如同样参数预算下,时序层适配是否比静态视觉层更能处理场景变化,并用匹配对照验证,参数高效方法才成为研究设计的一部分。单纯把原模型换成更流行的适配器并不能解释技术贡献。

还要把理论上的秩与训练后实际数值秩区分开。设定秩八只提供最多八个方向,优化可能让某些奇异值接近零。可以对最终更新矩阵做一次奇异值分解,画归一化谱,观察是否集中在前几个方向。这是诊断工具,不能仅凭谱集中就断言其余方向永远无用;重新训练更小秩模型仍需要验证。

可执行研究记录与评价纪律

把阅读成果变成可检查的实验记录。先建立一张样本清单,每行至少包括样本标识、原始视频或说话人标识、类别、场景、训练验证测试角色。先检查这些标识的交集,再开始训练。视频切成不同短片,并不意味着它们在语义、人物或拍摄来源上相互独立;随机划分得到的高分也不能自动证明跨场景泛化。

所有比较都应固定输入帧数、预训练权重、数据增强、优化步数和评价脚本。先用一个种子验证数据流、梯度和保存恢复,再用预先约定的多个种子做正式比较。记录每个种子的结果,报告均值和波动,而不是只保留最高一次。若为了显存缩小模型或批量,把它写入兼容性实验说明,不把改动后的结果称为原论文的严格复现。

检查分类效果时,同时保存逐样本预测和混淆矩阵。总体准确率上升可能只是多数类别变好;逐类召回率能揭示少数表情被牺牲的问题。跨场景实验还要报告每个场景的指标,防止样本量大的场景掩盖小场景退化。一个漂亮的特征可视化只能辅助观察,不能替代独立测试集、明确指标和误差分析。

模型选择必须与最终测试分开。用验证集选择学习率、训练轮数和新增模块的强度,先写清选择规则,再运行最终测试;不要根据测试集反复挑选超参数。若研究设定禁止访问旧数据,就不能为了选择防遗忘系数反复查看旧任务测试结果。可以预先固定系数,或另行声明验证数据的可访问范围,让比较遵循同一规则。

每次改动只回答一个问题,保存配置文件、代码提交、随机种子、数据清单哈希和检查点路径。最后交付一页实验说明,按“问题、对照、改动、结果、解释、失败案例”组织。若结果没有提高,先确认机制是否确实生效,再判断假设是否错误;未运行的实验只能写成计划,不能填上看似合理的数字。本文表格均为原文报告或明确注明的教学计算,并非本机复现实测。

原文短引与证据入口

原论文与完整上下文。以下三个短引仅定位作者用语,解释与判断见上文:“low-rank adaptation”;“no additional inference latency”;“intrinsic rank”。

自己的解释与验收记录

写出一条输入到输出的链路、一个关键公式的含义,以及你完成的最小检查。笔记仅保存在当前浏览器,可从主站「我的学习笔记」导出。