给十个任务各微调一份模型,最直观的办法是保存十份完整权重。问题是,任务并没有创造十个底座:绝大多数预训练能力相同,真正不同的只是模型为了适应数据而发生的那一点变化。模型越大,这种“为了保存差异而复制整体”的做法越昂贵。
LoRA(Low-Rank Adaptation)把问题改写了:不要直接训练新的权重 \(W'\),而是冻结预训练权重 \(W\),只学习它的更新量 \(\Delta W\)。进一步假设这个更新不需要覆盖整个高维空间,可以写成两个窄矩阵的乘积:
\[ W' = W + \Delta W, \qquad \Delta W = \frac{\alpha}{r}BA. \]这里的关键不是“给模型外挂一个小网络”,而是改变要保存和优化的对象。底座仍负责绝大多数计算与知识,适配器只描述这次任务要把底座往哪个方向推。LoRA 论文以 GPT-3 175B 为例,报告相对 Adam 全量微调最多可将可训练参数减少约一万倍、GPU 显存需求降至约三分之一;这些是特定配置下的结果,不是所有模型都能复现的固定倍率。
一次完整更新,被压成两次窄投影
设某个线性层的权重 \(W\in\mathbb{R}^{d_{out}\times d_{in}}\)。全量微调要为 \(d_{out}d_{in}\) 个元素学习变化。LoRA 令
\[ A\in\mathbb{R}^{r\times d_{in}},\qquad B\in\mathbb{R}^{d_{out}\times r},\qquad r\ll\min(d_{in},d_{out}), \]前向计算变为
\[ y=Wx+\frac{\alpha}{r}B(Ax). \]底座路径 \(Wx\) 不动;支路先把输入压到 \(r\) 维,再投影回输出空间。适配器只有 \(r(d_{in}+d_{out})\) 个参数。以一个 \(4096\times4096\) 的方阵为例,秩 \(r=8\) 时,完整矩阵有 16,777,216 个元素,而两块 LoRA 矩阵共 65,536 个,恰好是前者的 \(1/256\)。这是单个线性层的几何账,整模型比例还取决于把 LoRA 放到哪些模块。
图 1:LoRA 不修改冻结的 W,而用先降维再升维的 BA 支路表达低秩更新
原始实现通常随机初始化 \(A\)、把 \(B\) 初始化为零,所以训练开始时 \(BA=0\),模型输出与底座完全一致。系数 \(\alpha/r\) 控制更新强度,使改变 rank 时尺度更可控。训练期间只给 \(A\) 和 \(B\) 求梯度、维护优化器状态;\(W\) 仍参与前向和反向传播中的计算,但不被更新。
这也是一个容易说错的地方:LoRA 减少的是可训练参数、参数梯度和优化器状态,不是把大模型的计算删除了。冻结权重仍要驻留或按需要读取,激活仍要为反向传播保存,训练吞吐还受序列长度、batch、激活检查点和算子效率影响。“只训练 0.x% 参数”不等于“训练只花 0.x% 的显存或算力”。
低秩不是压缩魔法,而是对任务变化的假设
任意矩阵都可以用足够高的 rank 表示;LoRA 真正下注的是,下游任务需要的 \(\Delta W\) 具有很低的“内在秩”。预训练模型已经拥有语言和世界模式,适配往往不必从零重建它们,只需放大、抑制或重新组合一小组方向。
原论文在 GPT-3 的部分任务上发现,对 query 与 value 投影同时适配时,rank 低至 1 仍可有竞争力;不同 rank 的更新也共享少数主要奇异方向。但论文同时明确说,小 rank 不会适用于所有任务和数据。这个实验证据支持“许多适配更新可低秩近似”,却没有证明所有新知识、所有领域迁移或所有模型层都天然低秩。
rank 因而不是越小越先进,也不是越大越稳妥。太小会成为表达瓶颈;太大增加显存、通信和过拟合空间,收益可能饱和。更重要的是目标模块:原始 LoRA 实验主要适配 self-attention 的投影,并发现同一参数预算下,同时覆盖 query 和 value 往往优于只扩大单个投影的 rank。现代工具也允许覆盖 output、key 或 MLP 中的线性层,但这应当由任务和验证集决定,不能把某个社区默认值当成定律。
真正省下来的,是训练与版本管理的账
全量微调时,每个任务都产生一份完整模型;训练还要为所有可训练权重保留梯度以及 Adam 的一阶、二阶状态。LoRA 把这些按任务增长的部分限制在小矩阵中。一份底座可以配许多适配器,每个检查点只保存 LoRA 参数,Microsoft 的参考实现也直接提供了只导出 LoRA state_dict 的接口。
图 2:全量微调、LoRA 与 QLoRA 省下的是不同账目;冻结不等于删除底座计算和激活
这改变了模型交付方式。过去“一个任务一个模型”意味着复制、传输和部署整份权重;LoRA 之后,可以把底座看作共享运行时,把任务差异看作小型补丁。实验、回滚和版本组合更便宜,私有数据训练产生的差异也可以单独管控。
但这并不自动降低所有成本。底座参数的前向乘法仍存在;训练中的激活显存可能占主导;分布式训练还要考虑适配器梯度与底座权重的放置。LoRA 最显著地削减“需要更新和持久化的状态”,至于端到端速度与峰值显存,必须测完整工作负载。
合并之后没有额外层,但多租户服务另有代价
LoRA 的线性形式允许在部署前计算
\[ W_{merged}=W+\frac{\alpha}{r}BA. \]之后推理仍做一次普通的 \(W_{merged}x\),因此单个已合并适配器不会像在 Transformer block 中插入串行 adapter layer 那样增加网络深度。这就是论文所说的“无额外推理延迟”的准确含义:合并后的单适配器路径与同形状的全量微调权重拥有相同矩阵乘法结构。
如果服务需要在一个底座上同时承载许多适配器,情况不同。不能把多个互相冲突的 \(BA\) 永久合进同一份 \(W\);系统要按请求选择适配器,动态执行支路或维护不同的合并副本。小检查点让切换和存储容易许多,但 batching、缓存、内核融合和适配器驻留仍是服务系统的问题。“可以合并”与“任意数量适配器并发都免费”不是一回事。
图 3:共享底座可以挂接多个小适配器;单个适配器可合并,多适配器并发仍需路由和调度
这也解释了 LoRA 为什么改变的不只是训练门槛。模型的发布单位从“完整权重副本”变成“底座版本 + 适配器版本 + 精确配置”。rank、alpha、目标模块、偏置设置和底座哈希都是产物的一部分;底座对不上,即使矩阵尺寸勉强兼容,语义也不再可靠。
QLoRA 把冻结底座再压到 4 bit
LoRA 仍需装下底座。QLoRA 进一步把冻结的预训练权重以 4 bit 保存,计算时按需要反量化到计算精度,梯度继续穿过这条路径流向 LoRA 适配器;量化后的底座本身仍不更新。它不是“用 4 bit 训练所有模型参数”,而是“用 4 bit 承载冻结底座,用较高精度训练低秩更新”。
QLoRA 论文引入了面向近似正态分布权重的 NF4、压缩量化常数的 double quantization,以及处理优化器显存尖峰的 paged optimizers。论文展示了在单张 48 GB GPU 上微调 65B 参数模型,并在所测设置中匹配 16 bit 全量微调表现。这项结果说明底座存储与可训练状态可以同时压缩,但不应被外推为“任意 65B、任意长度和 batch 都能装进 48 GB”:激活、上下文长度、目标模块、优化器与实现都会改变峰值。
量化也没有把乘法变成零成本。推理或训练仍需读取、反量化并计算底座权重;不同硬件对 4 bit kernel 的支持差异很大。QLoRA 的最大贡献,是把“底座精度”和“更新精度”拆成两个可独立选择的轴,而不是承诺所有场景都比普通 LoRA 更快。
什么时候应该换一种方法
LoRA 很适合底座能力已经足够、任务主要需要行为或领域适配,并且要保存许多版本的场景。它不保证把预训练中缺失的大量知识可靠地塞进一个很小的 rank,也不修复低质量数据、错误标注和评测泄漏。适配器很小,只说明参数账很小,不说明它学到的行为容易审计。
若任务与底座差异极大,LoRA 在合理 rank 下持续落后于全量微调,就应把低秩假设当作失败的实验结论,而不是继续堆技巧。若只需要少量示例即可完成任务,检索或提示设计可能比训练更简单;若知识必须可更新、可引用,RAG 往往比把事实写入权重更合适。选择方法要看变化属于“行为”“知识”还是“基础能力”,而不是看到参数高效就默认使用。
LoRA 最值得保留的直觉,是把“模型”和“模型的任务变化”分开计价。一个巨大系统的有效改动,可能只占高维空间里的少数方向。找到并保存这些方向,可以省下梯度、优化器状态、检查点和部署副本;但底座计算、激活成本与数据风险依然存在。它不是免费微调,而是一种更准确的账本。
参考资料
- Hu et al., LoRA: Low-Rank Adaptation of Large Language Models, ICLR 2022.
- Microsoft, LoRA / loralib 官方实现.
- Dettmers et al., QLoRA: Efficient Finetuning of Quantized LLMs, NeurIPS 2023.
- QLoRA authors, QLoRA 官方代码仓库.
- Hugging Face, PEFT LoRA 官方文档.