LoRA、QLoRA 与显存

# LoRA、QLoRA 与显存

模型权重就是模型计算时使用的数值参数。全参数微调要更新大量原参数,而 LoRA 保留原参数,只训练一小组附加参数;QLoRA 再把冻结的原参数压缩存储,进一步省显存。

可以把 LoRA 理解成在原模型旁边加一组可学习的修正量。它不是一段 Prompt,也不是人工写的客服规则,修正量由训练样本决定。

# LoRA 到底训练了什么

假设原模型中一个线性层的权重为 W,LoRA 用两个较小矩阵 A 和 B 表示权重增量:

新权重 W' = W + (alpha / r) × B × A

W:原权重,形状 d_out × d_in,训练时冻结
A:形状 r × d_in
B:形状 d_out × r
r:rank,增量表达的秩
1
2
3
4
5
6

原来要训练 d_out × d_in 个参数,现在只训练 r × (d_in + d_out) 个参数。例如 4096 × 4096 的一层,原权重约 1678 万参数;rank 为 16 的 LoRA 只有约 13 万参数。

这里省的是可训练参数及其梯度和优化器状态,不是把原模型从内存里删除。前向计算仍需要基座参与。

前向计算就是用当前参数算出预测;梯度表示为了减少误差,参数应该朝什么方向调整;优化器根据梯度更新参数。冻结基座意味着不更新这部分参数,但它仍要参与计算,因此 Adapter 很小也不能单独回答问题。

alpha / r 是常见 LoRA 缩放方式;某些变体使用其他规则。rank 越大,增量表达能力和资源需求通常越高,但不保证效果更好;数据少时还要注意过拟合。

# QLoRA 如何进一步省显存

冻结的基座权重:低比特存储
          ↓ 前向计算时按需要解量化到计算精度
基座输出 + 可训练 LoRA 分支输出
          ↓ 反向传播
更新 LoRA 参数,不更新冻结基座权重
1
2
3
4
5

QLoRA 不是先普通训练完,再把 Adapter 压缩成 4 bit。它是在训练过程中就使用量化的冻结基座,训练附加参数。原始 QLoRA 方案还讨论了 NF4、双重量化和分页优化器;具体工具配置是否启用这些功能,应分别确认。

举例说,原来的数值可能用 16 bit 存储,量化后用 4 bit 编码及额外缩放信息近似表示,读入计算时再转换到适合运算的精度。4 bit 主要是在说怎样存权重,不是所有加减乘除都只有 4 bit 精度。

# NF4、INT4、BF16、FP16 和 GGUF

名称 是什么 不要混淆什么
INT4 用 4 bit 整数及相关缩放方式表示数值 不代表所有 4 bit 量化都用同一算法
NF4 NormalFloat 4,一种面向近似正态分布权重的 4 bit 表示 不是训练计算全部使用 4 bit
BF16 16 bit 浮点格式,指数范围接近 FP32 精度和硬件支持与 FP16 不同
FP16 另一种 16 bit 浮点格式 数值范围较小,部分训练可能需要损失缩放
GGUF 常见于 llama.cpp 生态的模型文件格式 是容器格式,不是某一个量化算法

量化把数值存储压缩到更少的比特,会引入近似误差;推理量化和量化基座上的训练使用场景不同。模型能以某种格式推理,不代表同一文件可直接交给训练框架训练。

# 显存为什么不能只按参数量算

8B 参数如果每个权重理论上占 4 bit,单看权重约为 4 GB(十进制)。这只是粗略下界,不包括量化元数据、未量化层、计算缓冲、激活、Adapter、梯度、优化器状态等。

训练显存还受序列长度、batch、训练层和梯度检查点影响;推理则还要考虑 KV Cache、并发与输出长度。不能用权重文件大小直接承诺显卡一定够用。

梯度检查点是什么

Gradient checkpointing 不保存所有中间激活,而是在反向传播时重新计算一部分,用更多计算换更少显存。它不是模型保存到磁盘的 checkpoint,也不能用来恢复训练进度。

遇到 OOM(显存不足),按出现的位置处理:模型还没加载完就报错,先查其他 GPU 进程、模型大小和量化设置;第一批数据开始训练才报错,优先降低单卡 batch 和序列长度,并检查梯度检查点。batch 降到 1 仍不够时,继续增加梯度累积并不能解决单个长样本的显存开销。

# Adapter 如何部署

Adapter 是小的增量权重,通常不能单独推理。部署时需要兼容的基座模型、Tokenizer、对话模板和目标模块。最好记录基座 revision 和文件校验值,而不只是模型大类名称。

有两种方式:加载基座后挂载 Adapter,或者在支持的精度和工具链中合并权重后导出。合并不是生产部署的必选步骤;也不能简单将增量直接加到任意 4 bit 文件上而不考虑反量化、精度与重新量化。

# rank、训练模块和学习率怎样一起选

可以先用项目已跑通的 rank 16 作为实验起点,但不是通用最佳值。先固定数据、模板和测试集,再一次改变一个主要因素:

观察结果 先排查什么 下一步实验
训练和验证都没改善 样本目标、模板、是否真的挂载 Adapter、参数是否可训练 确认链路后再调整学习率、训练步数或 rank
训练更好,独立测试更差 模板记忆、样本单一、训练过久 改数据覆盖、提前选 checkpoint,必要时减小训练容量
风格变好了,但工具参数仍乱填 有没有工具格式和缺参追问示范 补对应样本;格式和权限仍由程序校验
增加 rank 后只增加耗时 任务是否真的需要更大增量表达能力 保留更小且效果相当的配置

lora_target 决定哪些线性模块挂载 LoRA:只改注意力中的部分投影,与同时改更多注意力和前馈层,训练容量不同。不要把 all 理解成全参数微调;也不要同时改 rank、学习率、训练数据后,只把效果变化归因于 rank。

# 省显存与加速不是同一件事

QLoRA 省的是权重存储;解量化也需要计算。梯度检查点省的是中间激活,代价是反向时重算。梯度累积让多个小批次共同完成一次更新,不会让一个装不下的长样本突然能装下。

例如单卡每次 1 条、累积 16 次,与一次处理 16 条在有效 batch 上接近,但前者要多次前后向,吞吐和数值细节不保证相同。增加上下文长度也不仅增加输入存储,还会增加注意力计算及激活开销,具体增长受实现影响。

因此实验记录至少同时保存:峰值显存、每秒训练 Token 数、每次更新耗时和独立测试效果。只报 “能跑” 不能说明训练成本合理。

# 换基座、合并和回滚要验证什么

Adapter 与目标模块形状、基座参数及 Tokenizer 配套。给 8B 训练的 Adapter 不能直接套到另一个 14B 上;即使参数形状相同,换成另一份权重也不保证语义兼容。

发布时把基座 revision、Adapter、Tokenizer、模板、量化配置视为一个版本包。用固定输入验证挂载前后,再比较合并导出前后的结果;重新量化可能引入误差,不能只检查文件能加载。回滚也恢复这整个版本组合,而不是只换一个 Adapter 路径。

# 面试问答

1. LoRA 为什么比全参数微调便宜?参考答案

LoRA 冻结原来的大权重,只训练少量表示增量的矩阵,所以梯度和优化器状态会少很多。QLoRA 还把冻结基座低比特存储,进一步省显存。但原模型依然要参与前向计算,显存还包括激活等开销,所以不是模型只有 Adapter 那么大。

2. rank 越高越好吗?参考答案

不是。rank 越高,模型能学习的增量更复杂,但资源消耗和过拟合风险也会上升。我会在固定数据划分和评测集的情况下比较几个 rank,看真实任务是否改善,而不是只看训练 Loss 更低。

3. 为什么 4 bit 的 8B 模型只有约 4 GB,训练却可能爆显存?参考答案

约 4 GB 只算了理想情况下的权重,不包括量化元数据、未量化层、中间激活和训练状态。模型加载就失败,我先检查权重与精度;第一批才失败,我先看序列长度、单卡 batch 和梯度检查点。增加梯度累积只能维持有效 batch,不能减少单个样本的显存需求。

4. QLoRA 比 LoRA 更省显存,为什么不总选它?参考答案

QLoRA 要引入低比特量化及对应工具链,会有数值近似和兼容性约束,也不保证训练更快。显存足够时,较高精度基座上的 LoRA 可能更简单;显存紧张时,QLoRA 能让更大的基座进入可训练范围。我会比较任务效果、峰值显存和吞吐,不能只按文件大小选择。

# 参考资料