封面图

系列:开源 AI 论文复现实验与代码解读
日期:2026-08-26
适合读者:研究生、科研新人、希望把参数高效微调做清楚的工程读者
检索日期:2026-08-26

摘要

LoRA 和 QLoRA 常被概括成“省显存微调大模型”,但复现实验里真正容易出错的并不是口号,而是三笔账:哪些权重被冻结,低秩增量的形状是不是对的,4-bit 量化到底省的是基座权重、梯度还是优化器状态。本文的目标不是在本机复现 Guanaco 或 GPT-3 规模实验,而是把 LoRA 的公式、官方代码、最小 PyTorch 层、QLoRA 显存预算和失败排查串成一条可检查路线。

本次实际验证受限于当前工作区没有安装 PyTorch:配套脚本完成了 Python 语法检查,并运行了不依赖 PyTorch 的参数/显存预算检查;toy LoRA 训练代码需要安装依赖后再人工运行。文中涉及论文大模型指标时只引用论文报告或官方仓库说明,不把本地 toy 检查外推为原论文复现。

目录

  1. 为什么先复现 LoRA 而不是直接跑 PEFT
  2. LoRA 的公式和张量形状
  3. 官方代码阅读路线
  4. QLoRA 多省显存,省在哪里
  5. 最小实验与验收协议
  6. 如何报告复现实验边界
  7. 失败排查清单
  8. 可以继续研究的问题
  9. 总结与参考资料

为什么先复现 LoRA 而不是直接跑 PEFT

参数高效微调(Parameter-Efficient Fine-Tuning, PEFT)指的是不更新模型全部参数,而只训练少量新增参数或少量原参数。它适合科研新人入门,因为实验规模可以小很多;也容易误导人,因为库调用太简单,隐藏了关键约束。若只写 get_peft_model(model, config),很难看出 LoRA 增量究竟加在什么矩阵上,也难以判断训练参数比例是否符合预期。

LoRA 论文的核心假设是:下游任务所需的权重更新 Delta W 可能具有低内在秩,因此不必完整学习一个与原矩阵同形状的更新。对一个线性层,原始权重 W0 冻结,只训练两个小矩阵 AB,前向时使用 W0 + Delta W,其中 Delta W = B A。这样复现时的第一条验收标准就很明确:基座权重必须 requires_grad=False,只有 LoRA 参数和必要的任务头参与优化。

这和“只冻结一部分层”不同。LoRA 仍然让输入经过原模型的同一计算路径,只是在目标线性层旁边并联一个低秩增量。推理时,若不需要动态切换 adapter,可以把 B A 合并回原权重,避免额外矩阵乘法。这也是 LoRA 相比传统 adapter 的一个重要工程卖点。

LoRA 的公式和张量形状

设某个线性层输入 x 形状为 [B, T, d_in],原始权重 W0 形状为 [d_out, d_in]。全量微调会直接更新 W0 中的 d_out * d_in 个参数;LoRA 冻结 W0,学习:

$$ \Delta W = B A,\quad A\in R^{r\times d_{in}},\ B\in R^{d_{out}\times r} $$

于是前向计算可以写成:

$$ y = x W_0^T + \frac{\alpha}{r} x A^T B^T $$

这里 r 是 rank,alpha/r 是缩放因子。若 d_in=d_out=4096r=8,全量更新需要约 1677 万参数,而 LoRA 增量只有 8*(4096+4096)=65536 个参数。这个数量级差异解释了为什么训练优化器状态会明显变小;但它不表示激活显存、KV cache 或数据加载开销自动消失。

初始化也有复现意义。PEFT 文档说明默认初始化会让 LoRA 起步时近似 no-op:A 随机初始化,B 初始化为零。这样第 0 步模型输出与冻结基座一致,训练不会在一开始破坏预训练行为。若为了调试把两者都随机化,应明确标注这不再是常用初始化。

LoRA / QLoRA 复现实验流程

官方代码阅读路线

Microsoft 的 loralib 是阅读 LoRA 实现的最短路径。建议先看 loralib/layers.py 中的 Linear:它创建 lora_Alora_B,设置 self.scaling = lora_alpha / r,并把预训练 weight.requires_grad 设为 False。前向函数先计算普通 F.linear(x, weight),再加上 x @ A^T @ B^T * scaling。训练/评估模式切换中还包含 merge/unmerge 逻辑,用来把低秩更新加回原权重或撤销。

Hugging Face PEFT 则展示了科研复现实验会遇到的配置层。LoraConfig 里最容易影响结果的是 rlora_alphatarget_moduleslora_dropoutbias。如果目标模块名写错,训练参数可能为零;如果只给 q_projv_proj 加 LoRA,和 QLoRA 风格的 target_modules="all-linear" 就不是同一实验。正式报告应打印 trainable parameter ratio,并保存 adapter 配置。

QLoRA 官方仓库的阅读重点不是把整套训练脚本复制到本机,而是看它如何加载量化模型、调用 prepare_model_for_kbit_training,再添加 LoRA 模块。这样能明确一件事:QLoRA 不是“训练 4-bit 权重”,而是把冻结基座以 4-bit 存储/计算,并把梯度反传到 LoRA adapter。

QLoRA 多省显存,省在哪里

QLoRA 的关键增量有三项:NormalFloat 4-bit(NF4)、double quantization 和 paged optimizers。NF4 是面向近似正态分布权重的 4-bit 数据类型;double quantization 继续量化量化常数,减少平均存储开销;paged optimizers 用来缓解长序列或大 batch 下的瞬时显存峰值。论文报告称 QLoRA 可在单张 48GB GPU 上微调 65B 模型,但这个结论依赖具体模型、序列长度、batch、checkpointing、优化器和硬件,不能从 toy 脚本直接推出。

显存预算要拆开看。全量 fp16 微调通常要存基座权重、梯度、优化器状态和激活;LoRA 冻结基座后,梯度和 Adam 状态主要只为 adapter 保留;QLoRA 进一步把冻结基座权重压到 4-bit。配套脚本位于 code/minimal_lora_qlora_budget.py,默认用一个 7B 参数模型和 2000 万 LoRA 参数做粗略账本:

python3 code/minimal_lora_qlora_budget.py --check-only

本次实际运行的标准库检查输出了单层 base_weightlora_Alora_Bdelta_W 形状,并给出 full fp16、LoRA fp16 base、QLoRA 4-bit base 三种粗略 tracked memory。注意它没有统计激活、临时 kernel、allocator 碎片、CUDA 上下文、checkpoint 保存和 tokenizer batch,因此只能作为复现实验前的预算草稿。

最小实验与验收协议

安装 PyTorch 后,可以运行一个 CPU toy 训练:

python3 -m pip install -r code/requirements.txt
python3 code/minimal_lora_qlora_budget.py --steps 40 --seed 7

toy 模型只有一个冻结线性层和一个 LoRA 增量,用合成二分类任务检查 LoRA 参数是否能被优化。验收不应只看 loss 是否下降,还要检查四个不变量:冻结权重梯度为空,LoRA 参数数等于 r*(d_in+d_out),第 0 步 no-op 初始化不会改变基座输出,训练日志记录 seed、rank、alpha、学习率和 batch size。

若扩展到真实模型,建议把评测协议分成三层。第一层是配置可复查:模型名、revision、tokenizer、数据集版本、target modules、rank、alpha、dropout、量化配置全部写入日志。第二层是资源可复查:峰值显存、每步时间、有效 batch、序列长度、gradient checkpointing 是否开启。第三层才是任务指标:验证集 loss、准确率、MT-Bench 或 Vicuna 这类对话评测。动态榜单和闭源评测器版本必须标注“待人工核验”。

如何报告复现实验边界

LoRA/QLoRA 的复现报告尤其需要把“配置成功”和“方法有效”分开。配置成功只说明 adapter 已经插入、参数量符合预期、训练循环能跑通;方法有效还需要在固定数据切分、固定评测脚本和合理 baseline 下比较指标。很多失败记录之所以后来无法复查,是因为只保存了最终 checkpoint,却没有保存 adapter 配置、量化配置和训练日志。

建议每次实验至少保存五类材料。第一是 config.json 或命令行参数,里面应包含 rank、alpha、dropout、target modules、是否训练 bias、是否合并 adapter。第二是环境信息,包括 PyTorch、Transformers、PEFT、bitsandbytes、CUDA、GPU 型号和驱动版本。第三是资源日志,至少记录峰值显存、每步耗时、序列长度、batch size 和 gradient accumulation。第四是数据记录,包括数据集版本、过滤规则、模板、最大长度和随机种子。第五是评测输出,最好保留逐样本预测或打分明细,而不只是一行平均分。

对论文指标的引用也要谨慎。LoRA 论文报告了在 RoBERTa、DeBERTa、GPT-2、GPT-3 等模型上的参数效率和质量对照;QLoRA 论文报告了 4-bit 微调大模型的显存可行性和对话模型结果。本文没有复现这些规模实验,因此只能说“论文报告”或“官方仓库说明”,不能写成本机结论。本文真正验证的是:脚本中的 LoRA 参数形状、参数比例公式和粗略显存账本可以在无 PyTorch 环境下运行;PyTorch toy 训练与真实 QLoRA 训练仍待安装依赖和硬件后验证。

还有一个常被忽略的边界是随机性。LoRA 参数很少,不代表实验方差一定小。小数据集、短训练步数、不同 prompt 模板、不同 tokenizer 截断策略,都可能让最终指标明显波动。可靠报告应至少跑多个 seed,或把单 seed 结果明确标成探索性结果。若预算不足以多 seed,也应优先保留 loss 曲线、验证集逐项输出和失败样本,而不是只保留最好一次的截图。

失败排查清单

第一,训练参数为零。通常是 target_modules 没匹配到真实层名,或 adapter 注入后没有打印 trainable 参数。不同架构的投影层命名不同,不能把 LLaMA 的 q_proj 原样套到所有模型。

第二,显存没有明显下降。常见原因是仍在全量更新 embedding、LM head 或 bias;序列太长导致激活成为主要开销;或者没有启用 gradient checkpointing。LoRA 主要减少可训练权重、梯度和优化器状态,不会免费消除前向激活。

第三,QLoRA 量化配置不一致。Hugging Face 文档中典型配置包括 load_in_4bit=Truebnb_4bit_quant_type="nf4"bnb_4bit_use_double_quant=True 和合适的 compute dtype。若库版本、硬件后端或 dtype 不同,显存和速度都可能变化。

第四,把 adapter merge 后继续训练。merge 适合推理或导出;若训练阶段反复 merge/unmerge,要确认没有把低秩更新重复加到权重上。官方实现中维护 merged 状态,就是为了避免这类错误。

第五,报告方式混淆。论文报告的 65B 单卡结论、官方仓库的 Guanaco 结果、本机 toy script 的参数检查属于三种证据。科研博客必须分开写:论文声称什么,本次实际验证什么,作者据此推断什么。

第六,忽略 merge 校验。adapter 合并前后应在同一输入上比较 logits 差异;若误差异常,优先检查 dtype、缩放因子、是否重复合并以及权重转置方向。这个检查很便宜,却能提前发现导出阶段的严重问题。

复现日志越细,后续排错成本越低。

可以继续研究的问题

  1. 在相同训练预算下,LoRA rank 从 4、8、16 到 64 时,验证集收益和显存增量是否线性?
  2. q_proj/v_proj、attention 全投影、MLP 全线性层和 all-linear 的差异,是否来自容量还是目标模块位置?
  3. NF4、FP4、INT8 在同一模型、同一数据上的困惑度退化和训练稳定性如何比较?
  4. LoRA adapter merge 前后,logit 最大误差在不同 dtype 下是否可接受?
  5. 小数据指令微调中,LoRA 的过拟合表现是否比全量微调更轻,还是只是训练参数少导致收敛慢?

这些问题都比“能不能省显存”更适合作为研究入口,因为它们把方法、预算、指标和失败模式放在同一个可复现框架内。

总结

LoRA 的最小复现可以浓缩成一句话:冻结 W0,训练 AB,用 B A 近似下游任务所需的权重更新。QLoRA 则在这个基础上把冻结基座量化到 4-bit,并继续只训练 LoRA adapter。理解这两点后,显存预算就不再是玄学:基座权重、可训练参数、优化器状态、激活峰值必须分别估算。

本文完成了源码阅读、公式对齐、预算脚本和正式配图。本机尚未安装 PyTorch,因此 toy 训练与真实模型 QLoRA 仍标注为待人工核验。下一步最值得做的是在固定模型 revision 和数据集切分后,比较 full fine-tuning、LoRA 和 QLoRA 的显存、速度与验证指标,而不是只报告一个漂亮的 adapter 参数比例。

参考资料

检索日期:2026-08-26。以下链接优先采用论文页、官方仓库和官方文档;PEFT、Transformers 与 bitsandbytes 文档会随版本变化,正式复现实验应记录本地包版本、GPU 型号和模型 revision。

  1. Edward J. Hu et al., LoRA: Low-Rank Adaptation of Large Language Models, arXiv 2021 / ICLR 2022。
  2. Microsoft, LoRA official repositoryloralib PyTorch 实现。
  3. Microsoft, loralib/layers.py,LoRA Linear、merge/unmerge 与冻结权重逻辑。
  4. Tim Dettmers et al., QLoRA: Efficient Finetuning of Quantized LLMs, arXiv 2023 / NeurIPS 2023。
  5. Artidoro Pagnoni et al., QLoRA official repository,训练脚本、配置和 Guanaco 说明。
  6. Hugging Face PEFT, LoRA documentationLoraConfig、初始化和 target modules。
  7. Hugging Face PEFT, Quantization guide,LoRA 与 4-bit/8-bit 量化结合流程。
  8. Hugging Face Transformers, bitsandbytes quantization documentationBitsAndBytesConfig、NF4 与 nested quantization。
  9. Hugging Face bitsandbytes, Linear4bit / LinearNF4 reference,4-bit 线性层和 NF4 API 说明。