
系列:开源 AI 论文复现实验与代码解读
日期:2026-08-26
适合读者:研究生、科研新人、希望把参数高效微调做清楚的工程读者
检索日期:2026-08-26
摘要
LoRA 和 QLoRA 常被概括成“省显存微调大模型”,但复现实验里真正容易出错的并不是口号,而是三笔账:哪些权重被冻结,低秩增量的形状是不是对的,4-bit 量化到底省的是基座权重、梯度还是优化器状态。本文的目标不是在本机复现 Guanaco 或 GPT-3 规模实验,而是把 LoRA 的公式、官方代码、最小 PyTorch 层、QLoRA 显存预算和失败排查串成一条可检查路线。
本次实际验证受限于当前工作区没有安装 PyTorch:配套脚本完成了 Python 语法检查,并运行了不依赖 PyTorch 的参数/显存预算检查;toy LoRA 训练代码需要安装依赖后再人工运行。文中涉及论文大模型指标时只引用论文报告或官方仓库说明,不把本地 toy 检查外推为原论文复现。
目录
- 为什么先复现 LoRA 而不是直接跑 PEFT
- LoRA 的公式和张量形状
- 官方代码阅读路线
- QLoRA 多省显存,省在哪里
- 最小实验与验收协议
- 如何报告复现实验边界
- 失败排查清单
- 可以继续研究的问题
- 总结与参考资料
为什么先复现 LoRA 而不是直接跑 PEFT
参数高效微调(Parameter-Efficient Fine-Tuning, PEFT)指的是不更新模型全部参数,而只训练少量新增参数或少量原参数。它适合科研新人入门,因为实验规模可以小很多;也容易误导人,因为库调用太简单,隐藏了关键约束。若只写 get_peft_model(model, config),很难看出 LoRA 增量究竟加在什么矩阵上,也难以判断训练参数比例是否符合预期。
LoRA 论文的核心假设是:下游任务所需的权重更新 Delta W 可能具有低内在秩,因此不必完整学习一个与原矩阵同形状的更新。对一个线性层,原始权重 W0 冻结,只训练两个小矩阵 A 和 B,前向时使用 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=4096 且 r=8,全量更新需要约 1677 万参数,而 LoRA 增量只有 8*(4096+4096)=65536 个参数。这个数量级差异解释了为什么训练优化器状态会明显变小;但它不表示激活显存、KV cache 或数据加载开销自动消失。
初始化也有复现意义。PEFT 文档说明默认初始化会让 LoRA 起步时近似 no-op:A 随机初始化,B 初始化为零。这样第 0 步模型输出与冻结基座一致,训练不会在一开始破坏预训练行为。若为了调试把两者都随机化,应明确标注这不再是常用初始化。

官方代码阅读路线
Microsoft 的 loralib 是阅读 LoRA 实现的最短路径。建议先看 loralib/layers.py 中的 Linear:它创建 lora_A、lora_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 里最容易影响结果的是 r、lora_alpha、target_modules、lora_dropout 和 bias。如果目标模块名写错,训练参数可能为零;如果只给 q_proj、v_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_weight、lora_A、lora_B、delta_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=True、bnb_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、缩放因子、是否重复合并以及权重转置方向。这个检查很便宜,却能提前发现导出阶段的严重问题。
复现日志越细,后续排错成本越低。
可以继续研究的问题
- 在相同训练预算下,LoRA rank 从 4、8、16 到 64 时,验证集收益和显存增量是否线性?
q_proj/v_proj、attention 全投影、MLP 全线性层和all-linear的差异,是否来自容量还是目标模块位置?- NF4、FP4、INT8 在同一模型、同一数据上的困惑度退化和训练稳定性如何比较?
- LoRA adapter merge 前后,logit 最大误差在不同 dtype 下是否可接受?
- 小数据指令微调中,LoRA 的过拟合表现是否比全量微调更轻,还是只是训练参数少导致收敛慢?
这些问题都比“能不能省显存”更适合作为研究入口,因为它们把方法、预算、指标和失败模式放在同一个可复现框架内。
总结
LoRA 的最小复现可以浓缩成一句话:冻结 W0,训练 A 和 B,用 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。
- Edward J. Hu et al., LoRA: Low-Rank Adaptation of Large Language Models, arXiv 2021 / ICLR 2022。
- Microsoft, LoRA official repository,
loralibPyTorch 实现。 - Microsoft,
loralib/layers.py,LoRA Linear、merge/unmerge 与冻结权重逻辑。 - Tim Dettmers et al., QLoRA: Efficient Finetuning of Quantized LLMs, arXiv 2023 / NeurIPS 2023。
- Artidoro Pagnoni et al., QLoRA official repository,训练脚本、配置和 Guanaco 说明。
- Hugging Face PEFT, LoRA documentation,
LoraConfig、初始化和 target modules。 - Hugging Face PEFT, Quantization guide,LoRA 与 4-bit/8-bit 量化结合流程。
- Hugging Face Transformers, bitsandbytes quantization documentation,
BitsAndBytesConfig、NF4 与 nested quantization。 - Hugging Face bitsandbytes, Linear4bit / LinearNF4 reference,4-bit 线性层和 NF4 API 说明。