LoRA 和 QLoRA 到底省在哪里?从矩阵分解到显存占用讲清楚
刚开始接触大模型微调时,很多人都会听到一句话:
显存不够就用 LoRA,再不够就用 QLoRA。
这句话没错,但它说得太粗了。
LoRA 到底省了什么?
QLoRA 又是在 LoRA 的基础上继续省了哪一块?
为什么同样是微调,有的人 24GB 显存能跑起来,有的人换了 LoRA 还是 OOM?
如果只停留在“LoRA 省显存”“QLoRA 更省显存”这个层面,其实很容易理解偏。
我刚开始看 LoRA 的时候,也以为它是把模型变小了。后来真正看参数、梯度和优化器状态之后,才发现不是这样。
LoRA 省的不是原始模型本身,而是减少了需要训练和保存的参数。
QLoRA 则进一步通过 4-bit 量化基础模型权重,降低大模型加载和训练时的显存占用。
理解 LoRA 和 QLoRA,不能只看参数量,还要看训练时显存到底被哪些东西占掉了。
这篇文章就从全量微调讲起,把 LoRA、QLoRA 的核心原理、参数量计算、显存占用和工程选型一次讲清楚。
1. 为什么全量微调大模型这么贵?
先从最直接的问题开始:为什么大模型全量微调这么吃显存?
很多人会简单理解为:
因为模型参数太多。
这只说对了一部分。
比如一个 7B 模型,如果用 FP16 加载,单看模型权重大概是:
7B × 2 bytes ≈ 14GB
看起来 24GB 显卡好像还能放得下。
但问题是,训练不是只加载模型权重。
全量微调时,模型参数不仅要参与前向计算,还要参与反向传播和优化器更新。也就是说,训练时显存里通常不只是模型权重,还包括:
模型权重
梯度
优化器状态
激活值
临时 buffer
如果使用 AdamW 这类优化器,每个参数还需要额外维护一阶矩和二阶矩。
这部分显存非常容易被忽略,但实际训练时它往往是大头之一。
所以全量微调 7B 模型,并不是简单的“14GB 显存就够了”。
如果考虑梯度、优化器状态、激活值,显存需求会远远超过模型权重本身。
这也是为什么全量微调大模型对普通开发者并不友好。
不是代码难写,而是硬件成本太高。
2. 训练显存到底由哪些部分组成?
想真正理解 LoRA 和 QLoRA 省在哪里,先要把训练显存拆开看。
一个比较粗略但很有用的公式是:
训练显存 ≈ 模型权重 + 梯度 + 优化器状态 + 激活值 + 临时 buffer
这几个部分分别看一下。
2.1 模型权重
模型权重就是 Transformer 里的各种参数,比如:
-
Attention 里的 q_proj、k_proj、v_proj、o_proj;
-
MLP 里的 gate_proj、up_proj、down_proj;
-
LayerNorm 参数;
-
Embedding 参数;
-
LM Head 参数。
如果是 FP16 或 BF16,每个参数通常占 2 bytes。
如果是 FP32,每个参数通常占 4 bytes。
模型越大,这部分显存越高。
2.2 梯度
训练时,可训练参数需要保存梯度。
全量微调时,模型的大部分参数都需要更新,所以这些参数都要保存梯度。
但 LoRA 不一样。
LoRA 会冻结基础模型参数,只训练额外注入的小矩阵。
所以基础模型虽然还在参与前向计算,但它的参数通常不需要保存梯度。
这就是 LoRA 省显存的一个关键点。
2.3 优化器状态
以 AdamW 为例,除了参数和梯度,还要为每个可训练参数维护:
一阶矩 m
二阶矩 v
这两个状态是优化器用来稳定更新方向和步长的。
全量微调时,几乎所有参数都要进入 optimizer。
这意味着优化器状态会非常大。
LoRA 因为只训练少量 adapter 参数,所以 optimizer 只需要维护 LoRA 参数对应的状态。
这也是 LoRA 显存下降明显的原因之一。
2.4 激活值
激活值是前向传播过程中保存下来的中间结果,用于反向传播。
它和下面这些因素关系很大:
-
batch size;
-
sequence length;
-
hidden size;
-
模型层数;
-
attention 实现方式;
-
是否开启 gradient checkpointing。
很多时候,即使用了 LoRA 或 QLoRA,训练长上下文时还是会 OOM,原因就是激活值太大。
所以如果你的显存瓶颈主要来自长序列训练,只靠 LoRA / QLoRA 不一定够,还要配合:
-
减小 sequence length;
-
减小 batch size;
-
使用 gradient accumulation;
-
开启 gradient checkpointing;
-
使用 flash attention。
2.5 临时 buffer
这部分包括 CUDA kernel、attention 计算、中间张量、通信缓存等。
它不是最容易估算的一部分,但实际训练时经常存在。
所以有时候你按公式算显存觉得够,实际一跑还是爆了。
这并不奇怪,因为框架和算子本身也会有额外开销。
3. LoRA 的核心思想:不要更新整个 W,只学习一个低秩增量
现在进入 LoRA。
普通线性层可以写成:
y = Wx
其中:
-
x是输入; -
W是原始权重矩阵; -
y是输出。
全量微调时,我们直接更新 W。
LoRA 的思路不一样。
它不直接更新原始权重 W,而是冻结 W,额外学习一个低秩增量。
LoRA 修改后的形式可以写成:
y = Wx + BAx
也可以理解为:
W' = W + ΔW
ΔW = BA
其中:
-
W:原始预训练权重,训练时冻结; -
A:低秩矩阵,通常负责降维; -
B:低秩矩阵,通常负责升维; -
r:rank,也就是低秩维度; -
BA:对原始权重更新量的近似。
LoRA 的核心思想就是:
不训练完整的巨大矩阵 W,而是训练两个很小的矩阵 A 和 B,用它们组合出一个低秩更新 ΔW。
这有点像在原模型旁边加了一个“小旁路”。
原来的模型能力还在,LoRA 只学习当前任务需要调整的那部分变化。
这里一定要注意一个点:
LoRA 不是把原始模型变小了。
基础模型仍然要加载到显存里,也仍然参与前向计算。
LoRA 省的是:
-
可训练参数;
-
这些参数对应的梯度;
-
这些参数对应的优化器状态;
-
checkpoint 保存体积。
这和“模型本身变小”不是一回事。
4. 从 4096 × 4096 的例子看 LoRA 省了多少参数
只讲低秩分解可能还是有点抽象,我们用一个具体例子看。
假设原始线性层权重矩阵 W 的维度是:
4096 × 4096
全量微调时,这一层需要训练的参数量是:
4096 × 4096 = 16,777,216
也就是大约 1677 万个参数。
LoRA 会把更新量写成两个小矩阵:
A: r × d_in
B: d_out × r
如果:
d_in = 4096
d_out = 4096
r = 8
那么 LoRA 需要训练的参数量是:
r × d_in + d_out × r
= 8 × 4096 + 4096 × 8
= 32,768 + 32,768
= 65,536
对比一下:
全量微调参数量:16,777,216
LoRA 参数量:65,536
比例是:
65,536 / 16,777,216 ≈ 0.39%
也就是说,对于这个线性层来说,LoRA 只训练不到 0.4% 的参数。
这就是 LoRA 参数效率高的原因。
当然,这只是单个线性层的例子。
真实 Transformer 里通常会对多个模块注入 LoRA,比如:
-
q_proj;
-
k_proj;
-
v_proj;
-
o_proj;
-
gate_proj;
-
up_proj;
-
down_proj。
实际训练参数量取决于:
-
模型层数;
-
hidden size;
-
rank;
-
target_modules;
-
是否训练 bias;
-
是否对 MLP 也注入 LoRA。
所以不要只看 rank 一个参数。
同样是 r=8,只注入 q_proj / v_proj,和注入所有 Linear,参数量和显存都不是一个级别。
5. LoRA 为什么常加在 q_proj、v_proj 上?
很多 Hugging Face / PEFT 的 LoRA 配置里,会看到类似这样的设置:
target_modules=["q_proj", "v_proj"]
很多人直接复制,但不一定知道为什么。
Transformer 的 Attention 里通常有几个关键线性层:
q_proj:生成 Query
k_proj:生成 Key
v_proj:生成 Value
o_proj:输出投影
简单理解:
-
q_proj影响模型“关注什么”; -
k_proj影响内容如何被匹配; -
v_proj影响被聚合的信息内容; -
o_proj影响 attention 输出如何回到 hidden state。
很多任务中,只对 q_proj 和 v_proj 注入 LoRA,就能让模型获得不错的任务适配能力。
如果任务更复杂,比如希望模型在推理风格、指令跟随、领域表达上变化更明显,也可以考虑扩展到:
q_proj
k_proj
v_proj
o_proj
gate_proj
up_proj
down_proj
但 target_modules 不是越多越好。
模块越多,意味着:
-
可训练参数更多;
-
显存占用更高;
-
训练时间更长;
-
小数据集上更容易过拟合。
我的经验是,如果数据量不大,或者只是做格式、风格、简单任务适配,可以先从 q_proj、v_proj 或 q_proj、k_proj、v_proj、o_proj 开始。
不要一上来就所有 Linear 都加 LoRA。
那样看起来很强,但实际可能只是更容易把训练集记住。
6. LoRA 训练时冻结什么、更新什么?
LoRA 训练时最关键的一点是:
基础模型冻结,只训练 LoRA adapter。
伪代码大概是这样:
# 方法:只让 LoRA 参数参与训练
# 思想:冻结基础模型,减少梯度和优化器状态,只更新 adapter 参数
# 循环终止条件:遍历完模型所有参数后停止
for name, param in model.named_parameters():
if "lora_" in name:
param.requires_grad = True
else:
param.requires_grad = False
这段逻辑背后的含义很重要。
全量微调时:
基础模型参数:更新
基础模型梯度:需要保存
优化器状态:需要维护
checkpoint:保存完整模型或大量参数
LoRA 微调时:
基础模型参数:冻结
基础模型梯度:通常不保存
优化器状态:只维护 LoRA 参数
checkpoint:只保存 adapter
这就是 LoRA 为什么适合参数高效微调,也就是 PEFT。
但这里还有一个容易误解的点:
冻结参数不代表这些参数不参与计算。
基础模型仍然要参与前向传播。
输入仍然要经过 Transformer 的每一层。
只是反向传播时,不对基础权重做更新。
所以 LoRA 不是让训练完全变轻,而是让“需要更新的部分”大幅变小。
7. LoRA 推理时如何合并权重?
LoRA 训练完成后,我们学到的是一个增量:
ΔW = BA
原始权重是:
W
所以可以合并成:
W_merged = W + BA
也就是说,推理时可以有两种方式。
方式一:base model + adapter 动态加载
这种方式不合并权重,而是在推理时加载基础模型和 LoRA adapter。
优点是灵活:
-
一个基础模型可以挂多个 adapter;
-
不同业务可以切换不同 adapter;
-
adapter 文件很小,保存和分发方便。
比如一个通用基础模型,分别挂:
客服 adapter
法律 adapter
代码 adapter
医疗问答 adapter
这样比每个任务保存一份完整模型更省空间。
方式二:合并权重后部署
另一种方式是把 LoRA 权重合并进基础模型:
W_merged = W + BA
合并后推理路径更简单,不需要额外处理 LoRA 分支。
适合:
-
固定任务;
-
不需要频繁切换 adapter;
-
希望部署链路更简单;
-
推理服务不想额外依赖 PEFT adapter 加载逻辑。
但要注意,如果基础模型本身是量化模型,合并权重时要看框架是否支持,以及合并后的精度是否符合预期。
尤其是 QLoRA 训练出来的 adapter,实际部署时经常需要根据推理框架决定是单独加载 adapter,还是在合适精度下合并。
8. LoRA 不是免费午餐:rank、alpha、dropout 怎么看?
LoRA 确实省参数、省显存,但它不是免费午餐。
最常见的几个参数是:
rank r
lora_alpha
lora_dropout
target_modules
8.1 rank 不是越大越好
rank 决定 LoRA 分支的表达能力。
rank 太小,可能学不动。
表现为:
-
loss 降得慢;
-
输出仍然像 base model;
-
任务格式学不好;
-
复杂任务适配不够。
rank 太大,也不一定好。
尤其在小数据集上,rank 太大可能更容易过拟合。
很多人微调效果不好,第一反应是把 rank 从 8 调到 16、32、64。
但我的经验是,数据质量、Prompt 模板、学习率、训练轮数往往比盲目调大 rank 更关键。
8.2 alpha 影响 LoRA 分支缩放
LoRA 里通常会有一个缩放系数,常见形式类似:
scaling = alpha / r
它会影响 LoRA 分支对最终输出的贡献。
alpha 太小,LoRA 分支影响不明显。
alpha 太大,训练可能不稳定,输出风格也可能被过度拉偏。
常见配置里,alpha 会设置为 r 的 2 倍或 4 倍,但这不是绝对规则。
8.3 dropout 可以缓解过拟合
lora_dropout 是 LoRA 分支上的 dropout。
如果数据量比较小,或者样本重复度高,可以适当加一点 dropout。
比如:
0.05
0.1
但 dropout 也不是越大越好。
太大可能导致模型学不到稳定模式。
9. QLoRA 和 LoRA 的关系:不是替代,而是进一步压缩基础模型
讲完 LoRA,再看 QLoRA。
QLoRA 不是推翻 LoRA。
它是在 LoRA 的基础上,进一步把冻结的基础模型用 4-bit 量化加载。
普通 LoRA 通常是:
基础模型用 FP16 / BF16 加载
基础模型冻结
训练 LoRA adapter
QLoRA 通常是:
基础模型用 4-bit 量化加载
基础模型冻结
训练 LoRA adapter
计算时按需要反量化
也就是说:
LoRA 主要省的是可训练参数、梯度和优化器状态。
QLoRA 在此基础上,进一步节省基础模型权重的显存。
这两者省的重点不是完全一样。
很多人说 QLoRA 更省显存,这没错。
但更准确的说法是:
QLoRA 让基础模型权重以更低 bit 加载,从而降低大模型本体占用的显存;同时仍然通过 LoRA adapter 做参数高效微调。
10. QLoRA 的 4-bit 量化到底省在哪里?
如果基础模型用 FP16 加载,每个参数通常占 2 bytes。
如果用 4-bit 加载,理论上每个参数占:
4 bit = 0.5 byte
以 7B 模型为例:
FP16 权重:7B × 2 bytes ≈ 14GB
4-bit 权重:7B × 0.5 bytes ≈ 3.5GB
当然,真实情况不会这么理想。
因为还会有:
-
量化 scale;
-
metadata;
-
计算缓存;
-
LoRA 参数;
-
激活值;
-
框架额外开销。
但即使如此,4-bit 量化对模型权重显存的降低仍然非常明显。
这也是为什么 QLoRA 能让很多人用单卡微调更大的模型。
不过要注意:
QLoRA 不是把所有训练都变成 4-bit。
通常是基础模型权重量化存储,计算时会涉及反量化。
LoRA adapter 仍然以较高精度训练,比如 FP16、BF16。
所以 QLoRA 不是无损魔法。
它是在显存、速度、数值稳定性之间做折中。
11. NF4、Double Quantization、Paged Optimizer 分别解决什么问题?
QLoRA 里经常会提到三个关键词:
NF4
Double Quantization
Paged Optimizer
这几个名字看起来有点论文味,但用工程语言解释并不复杂。
11.1 NF4:更适合神经网络权重的 4-bit 表示
NF4 全称是 NormalFloat 4-bit。
神经网络权重通常不是均匀分布的,而是更接近某种正态分布。
如果随便用普通 4-bit 量化,可能会损失比较多信息。
NF4 的想法是:
既然权重分布有特点,那就设计一种更适合这种分布的 4-bit 表示方式。
所以 NF4 解决的是:
如何让 4-bit 量化更适合神经网络权重分布
它不是简单地把数字粗暴压成 16 个等级,而是尽量让低 bit 表示更贴合权重分布。
11.2 Double Quantization:量化参数也要省
量化不是只保存 4-bit 权重就完了。
通常还要保存一些量化相关参数,比如 scale。
这些参数本身也会占空间。
Double Quantization 的思路是:
对量化常数再做一次量化,进一步减少额外存储开销。
它解决的是:
量化权重省了,但量化参数本身也占显存
虽然这部分不像模型权重那么大,但在大模型规模下,能省一点是一点。
11.3 Paged Optimizer:缓解训练时显存峰值
训练过程中,显存不是一直平稳的。
有些阶段显存会突然升高,比如:
-
长序列 batch;
-
反向传播某些阶段;
-
optimizer step;
-
临时张量分配。
Paged Optimizer 借助类似分页的机制,在 GPU 和 CPU 之间管理优化器状态,从而缓解显存峰值问题。
它解决的是:
训练过程中的显存峰值和不稳定 OOM
这对显存卡得很紧的训练特别有用。
12. 实际训练时如何估算显存?
现在把 LoRA 和 QLoRA 放回训练显存公式里看。
训练显存可以粗略拆成:
模型权重 + 梯度 + 优化器状态 + 激活值 + 临时 buffer
12.1 全量微调
全量微调需要保存:
基础模型权重
基础模型梯度
Adam 一阶矩
Adam 二阶矩
激活值
临时 buffer
如果模型很大,这几部分叠加起来非常夸张。
尤其是 Adam 优化器状态。
它不是一个小开销,而是和可训练参数规模强相关。
12.2 LoRA
LoRA 里,基础模型仍然要加载。
但是:
基础模型被冻结
基础模型参数不进入 optimizer
基础模型不保存对应优化器状态
只训练 LoRA 参数
只保存 LoRA 参数的梯度和优化器状态
所以 LoRA 主要减少的是:
可训练参数
梯度
优化器状态
checkpoint 大小
但基础模型权重仍然在。
激活值也仍然在。
所以如果你用 LoRA 训练时仍然 OOM,不一定是 LoRA 没生效,可能是:
-
sequence length 太长;
-
batch size 太大;
-
没开 gradient checkpointing;
-
target_modules 太多;
-
不小心把全量参数也设成可训练;
-
optimizer 接收了不该训练的参数。
12.3 QLoRA
QLoRA 在 LoRA 的基础上进一步做了:
基础模型权重 4-bit 加载
基础模型冻结
训练 LoRA adapter
所以 QLoRA 进一步减少的是:
基础模型权重显存
但它仍然需要:
LoRA 参数
LoRA 梯度
LoRA 优化器状态
激活值
attention 计算开销
临时 buffer
所以判断显存问题时,可以这样看:
如果模型加载阶段就 OOM,QLoRA 帮助很大。
如果反向传播阶段 OOM,还要继续看 batch size、sequence length、gradient checkpointing 和 attention 实现。
这也是为什么有些人用了 QLoRA,模型能加载了,但一训练还是 OOM。
13. 一个最小 PEFT / bitsandbytes 配置示例
CSDN 上很多文章会直接贴一大段训练脚本,但我觉得更重要的是看懂配置。
一个最小的 LoRA 配置大概是这样:
from peft import LoraConfig
lora_config = LoraConfig(
r=8,
lora_alpha=16,
target_modules=["q_proj", "v_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
几个参数解释一下:
r:低秩维度,控制 LoRA 分支表达能力
lora_alpha:缩放系数,影响 LoRA 分支强度
target_modules:注入 LoRA 的模块
lora_dropout:LoRA 分支 dropout,用于缓解过拟合
bias:是否训练 bias
task_type:任务类型,例如因果语言模型
如果是 QLoRA,还会涉及 bitsandbytes 的 4-bit 加载配置:
from transformers import BitsAndBytesConfig
import torch
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True,
bnb_4bit_compute_dtype=torch.bfloat16
)
这里几个参数也很关键:
load_in_4bit=True:使用 4-bit 加载基础模型
bnb_4bit_quant_type="nf4":使用 NF4 量化类型
bnb_4bit_use_double_quant=True:启用 Double Quantization
bnb_4bit_compute_dtype=torch.bfloat16:计算时使用 bfloat16
配置不是固定模板。r、target_modules、learning rate、batch size、sequence length 都要结合任务和数据来调。
不要只复制别人的配置。
同样是 r=8,数据质量不同、任务难度不同、模型规模不同,效果会差很多。
14. LoRA 和 QLoRA 优缺点对比
下面用一个表格总结一下。
| 对比项 | LoRA | QLoRA |
|---|---|---|
| 基础模型加载 | 通常 FP16 / BF16 | 4-bit 量化 |
| 训练参数 | 只训练 adapter | 只训练 adapter |
| 主要节省部分 | 训练参数、梯度、优化器状态 | 在 LoRA 基础上进一步节省基础模型权重 |
| 显存占用 | 比全量微调低很多 | 通常比 LoRA 更低 |
| 训练速度 | 通常更简单稳定 | 可能受量化和反量化影响 |
| 数值稳定性 | 相对更稳 | 需要关注量化配置 |
| 硬件门槛 | 中等 | 更低 |
| 配置复杂度 | 较低 | 稍高 |
| 适合场景 | 显存相对够、追求稳定 | 单卡显存有限、模型较大 |
| 部署复杂度 | 较低 | 稍高 |
这里不要简单理解成:
QLoRA 一定比 LoRA 好。
更准确的说法是:
-
显存够,LoRA 更简单、更稳;
-
显存不够,QLoRA 能把门槛降下来;
-
QLoRA 更省显存,但可能带来速度、数值稳定性、框架兼容性方面的问题。
工程上没有绝对最优,只有适合当前资源和任务的方案。
15. 实际项目里怎么选 LoRA 还是 QLoRA?
我一般会按下面这个思路判断。
15.1 显存够,优先 LoRA
如果你的显存足够,比如模型能用 FP16 / BF16 正常加载和训练 LoRA,那优先用 LoRA。
原因很简单:
-
配置更简单;
-
训练更稳定;
-
排查问题更容易;
-
部署链路更清晰。
在工程项目里,稳定性很重要。
不是越极限越好。
15.2 显存不够,再考虑 QLoRA
如果模型刚加载就爆显存,或者 LoRA 训练显存压力很大,可以考虑 QLoRA。
典型场景:
-
单卡 16GB / 24GB;
-
想微调 7B、13B 模型;
-
预算有限;
-
可以接受训练速度稍慢;
-
能处理 bitsandbytes、量化配置、环境兼容问题。
QLoRA 的价值在于降低硬件门槛。
但你要接受它带来的额外复杂度。
15.3 数据少,不要盲目加大 rank
如果数据集只有几百条、几千条,rank 拉太大不一定有帮助。
小数据集上更常见的问题是过拟合,而不是模型容量不够。
这时更应该关注:
-
数据是否干净;
-
样本格式是否统一;
-
是否有重复样本;
-
Prompt 模板是否一致;
-
验证集是否能真实反映任务;
-
epoch 是否太多。
很多时候,清洗数据比调 rank 更有用。
15.4 不是所有问题都该微调
这一点很重要。
如果问题本质是知识更新,比如:
公司制度变化
产品文档更新
业务资料查询
FAQ 经常变化
那优先考虑 RAG,而不是微调。
如果问题只是输出格式不稳定,可能 Prompt 或 few-shot 就能解决。
可以用一个简单判断:
知识缺失?优先 RAG
格式不稳定?先调 Prompt / few-shot
风格迁移?可以 LoRA
任务模式固定?可以 LoRA / QLoRA
显存不够?考虑 QLoRA
数据很少?先别急着调大 rank
微调不是万能的。
有些问题用微调解决,反而会增加维护成本。
16. 常见踩坑:rank、数据、学习率和过拟合
这一节讲几个真实训练里很常见的坑。
16.1 rank 太小,模型学不动
表现可能是:
-
loss 降得很慢;
-
输出格式没变化;
-
指令跟随能力没明显提升;
-
任务特征学不到。
这种情况可以适当提高 rank,比如从 8 调到 16。
但不要只看训练 loss。
还要看验证集和真实样例效果。
16.2 rank 太大,小数据集容易过拟合
rank 大,表达能力更强,但也更容易记住训练集。
表现可能是:
-
训练集回答很好;
-
验证集效果下降;
-
输出开始套训练数据里的固定表达;
-
泛化能力变差。
这时可以考虑:
-
降低 rank;
-
减少 epoch;
-
加大 dropout;
-
清洗重复数据;
-
增加验证集;
-
降低 learning rate。
16.3 学习率不合适
LoRA 训练参数少,但不代表学习率可以随便设。
学习率太大,可能导致:
-
输出格式崩;
-
模型变得啰嗦;
-
学到错误风格;
-
loss 波动明显。
学习率太小,可能导致:
-
学不动;
-
收敛很慢;
-
微调前后差异不明显。
所以学习率要结合任务、数据量、batch size 和训练轮数一起看。
16.4 数据质量差,比参数设置更致命
微调里最容易被低估的是数据。
如果训练数据里有:
-
错误答案;
-
格式混乱;
-
重复样本;
-
低质量合成数据;
-
用户问题和回答不匹配;
-
Prompt 模板不统一;
那 LoRA / QLoRA 都救不了。
大模型微调不是把数据丢进去就行。
数据质量通常比盲目调 rank 更重要。
16.5 sequence length 设置太大
很多人用了 QLoRA 之后,以为显存问题就解决了,于是把 sequence length 拉得很长。
结果一训练还是 OOM。
原因前面说过:
长序列会显著增加激活值和 attention 计算开销。
所以如果显存紧张,优先检查:
max_seq_length
per_device_train_batch_size
gradient_accumulation_steps
gradient_checkpointing
flash attention
不是所有 OOM 都是模型权重导致的。
17. LoRA / QLoRA 选型建议和排查清单
最后给一份比较实用的清单。
17.1 选型建议
显存够,优先 LoRA,简单稳定。
显存不够,再考虑 QLoRA。
数据少,不要盲目加大 rank。
知识更新类问题,优先 RAG。
格式控制类问题,先试 Prompt / few-shot。
需要多任务复用,可以保留多个 adapter。
固定任务部署,可以考虑 merge 权重。
任务复杂度高,可以适当扩大 target_modules。
小数据集训练,重点关注过拟合。
17.2 显存排查清单
如果模型加载阶段就 OOM:
检查基础模型加载精度
考虑 8-bit / 4-bit 加载
考虑 QLoRA
检查 device_map 配置
检查是否加载了不必要的模型副本
如果训练反向传播阶段 OOM:
降低 batch size
降低 sequence length
开启 gradient checkpointing
使用 flash attention
检查是否错误训练了全量参数
检查 target_modules 是否过多
检查是否保存了过多中间结果
如果用了 LoRA 显存没有明显下降:
检查 requires_grad 是否正确
检查 optimizer 是否只接收 LoRA 参数
检查是否误把 base model 参数加入 optimizer
检查 target_modules 是否设置过多
检查是否保存了全量 checkpoint
17.3 效果排查清单
如果模型学不会:
检查训练数据格式
检查 Prompt 模板是否一致
检查 learning rate 是否太低
检查 rank 是否太小
检查 target_modules 是否太少
检查训练轮数是否不够
如果模型过拟合:
减少 epoch
降低 rank
增加 lora_dropout
降低 learning rate
清洗重复样本
增加验证集
检查训练集和测试集是否泄漏
如果输出格式混乱:
检查训练样本格式是否统一
检查 system prompt / user prompt 是否一致
检查推理时模板是否和训练时一致
检查是否混入不同风格数据
检查 stop words 和生成参数
18. 总结:LoRA 和 QLoRA 省的不是同一块
最后回到标题:
LoRA 和 QLoRA 到底省在哪里?
可以简单总结成几句话。
全量微调贵,是因为训练时不只要保存模型权重,还要保存梯度、优化器状态、激活值和临时 buffer。
LoRA 的核心是冻结基础模型,只训练两个低秩矩阵 A 和 B。
它主要省的是:
可训练参数
梯度
优化器状态
checkpoint 体积
但 LoRA 并没有让基础模型消失。
基础模型仍然要加载,仍然参与前向计算。
QLoRA 则是在 LoRA 的基础上,把冻结的基础模型用 4-bit 量化加载。
它进一步省的是:
基础模型权重显存
所以更准确地说:
LoRA:减少要训练的东西
QLoRA:进一步压缩要加载的基础模型
LoRA 不是免费午餐,rank 太小可能表达能力不够,rank 太大又可能过拟合。
QLoRA 能让单卡微调大模型更现实,但它也会带来量化、速度、数值稳定性和环境兼容问题。
真正做大模型微调时,不要只问:
LoRA 和 QLoRA 哪个更好?
更应该先问:
我的瓶颈到底是显存、数据、任务定义,还是评测体系?
如果瓶颈是显存,可以考虑 LoRA / QLoRA。
如果瓶颈是知识更新,可能 RAG 更合适。
如果只是输出格式不稳定,Prompt 或 few-shot 可能就够了。
如果数据质量很差,换什么微调方法都不会有太好结果。
大模型微调的难点,不只是把脚本跑起来,而是知道每个技术选择背后到底在省什么、牺牲什么,以及它是否真的适合当前业务场景。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)