跳至主要内容
返回文章列表
约 10 分钟阅读

LoRA 与 QLoRA:用交互实验理解低秩更新与量化微调

拖动秩、逐步查看训练路径、对比显存账本,理解两个小矩阵如何完成微调,以及 QLoRA 怎样压缩冻结基座。

博客目录 →

微调一个大模型,不一定要更新它的全部参数。LoRA 把要学习的权重变化写成两个小矩阵的乘积;QLoRA 在这个基础上,进一步压缩冻结基座的存储。

LoRA 是不是把原模型变成低秩矩阵?QLoRA 是不是直接训练 4-bit 参数?这篇文章从一个线性层出发,配合可点击的实验,把权重、梯度和显存分别讲清楚。

一、先动手:三个交互实验

在独立页面打开实验室 ↗(手机也可以使用。)

先把「低秩更新」中的 r 从 1 拖到 8,观察参数量;再逐步点击「训练路径」,切换 LoRA 与 QLoRA;最后在「显存账本」里改变模型规模。图中方块、计算路径与显存条均为原创教学示意,不是实际训练测量。

二、LoRA:学习任务需要的变化

考虑一个线性层,采用列向量记法:输入 x∈Rkx\in\mathbb{R}^k,预训练权重 W0∈Rd×kW_0\in\mathbb{R}^{d\times k}。全参数微调直接更新整个权重矩阵,可写为:

y=(W0+ΔW)x.y=(W_0+\Delta W)x.

LoRA(Low-Rank Adaptation)冻结 W0W_0,用两个可训练矩阵表示更新:

A∈Rr×k,B∈Rd×r,y=W0x+αrBAx.A\in\mathbb{R}^{r\times k},\quad B\in\mathbb{R}^{d\times r},\qquad y=W_0x+\frac{\alpha}{r}BAx.

其中 r 是选择的秩上限,α/r\alpha/r 是原始 LoRA 的缩放因子。受低秩约束的是更新 BABA,不是原始权重 W0W_0,因为 rank⁡(BA)≤r\operatorname{rank}(BA)\le r。这种约束减少任务微调时需要学习的自由度。LoRA 原论文

两个矩阵为什么更省?

完整更新需要 dk 个参数,A、B 共需要 r(d+k) 个。例如一个 4096 × 4096 的线性层,取 r = 8:

更新方式可训练参数
完整矩阵16,777,216
A 与 B65,536
占比0.390625%

这是单个目标线性层的比较。整个模型的可训练占比,还取决于层数、目标模块,以及是否训练偏置或其他参数。

实验中使用 8 × 8 的小矩阵,因此 r = 4 时,A、B 的参数总量已经等于完整矩阵。节省来自 r 足够小,而不是“做矩阵分解”本身。 r 增大可以提高更新容量,但效果不一定持续提升,还要考虑数据量、过拟合和资源开销。

初始化:为什么一边随机,一边为零?

原始 LoRA 随机初始化 A、零初始化 B。这样开始时 BA = 0,加入适配器不会立刻改变基座输出。B 可以先获得梯度,随后 A 也能学习。若 A、B 都为零,对于这个双线性乘积,两者的初始梯度也会为零,无法正常起步。

三、冻结参数,不代表跳过基座计算

“冻结 W₀”表示优化器不更新它,通常也不为它保存参数梯度和优化器状态。但前向仍要计算 W₀x,反向也要通过基座运算,把梯度传给前面层的适配器。

所以 LoRA 没有把大模型变成一个小网络。基座权重仍要加载,激活值和中间计算仍然占用显存;长序列、大 batch 依然可能造成显存不足。

如果精度与部署方式允许,推理前可以合并为 W′=W0+αrBAW'=W_0+\frac{\alpha}{r}BA,省去单独的适配器支路。不合并时,支路可能增加计算开销。保存适配器也要记录所依赖的基座版本,A、B 本身不是一个完整模型。

四、FP32、BF16、FP16:数是怎么存下来的?

“32 位”和“16 位”说的是一个数使用多少个二进制位存储,不是小数点后有多少位。浮点数可以理解为二进制科学计数法:符号决定正负,指数决定数量级,小数部分决定这个数量级里能分得多细。

格式符号位指数位存储的小数位每个数占用
FP3218234 字节
BF161872 字节
FP1615102 字节

对于正常数,可以写成 (−1)s×(1+f)×2E−bias(-1)^s\times(1+f)\times2^{E-\mathrm{bias}},其中 s 是符号位,E 是存储的指数,f 是小数位对应的二进制小数。最高位 1 是隐含的;零、次正规数和无穷大有特殊编码。

BF16 保留了与 FP32 相同的指数位数,因此正常数的范围接近,但有效数字更少;FP16 把更多位分给小数,同一数量级的精度比 BF16 高,却覆盖不了那么大的范围。PyTorch:BF16 格式说明

独立打开数值与量化实验 ↗

在实验的「FP32 / BF16 / FP16」中输入 1.003,再切换格式:它在 BF16 中会舍入为 1,在 FP16 中约为 1.0029296875。因为在 [1, 2) 内,BF16 的相邻值间距是 0.0078125,FP16 则是 0.0009765625。小变化可能落在同一个格点上。

再点击 70000:FP16 会溢出为无穷大,而 BF16 仍能表示一个附近值。点击 10⁻⁸,则可看到它在 FP16 中舍入为零。这些例子说明,表示范围和表示精度是两个不同问题。演示采用最近舍入、中点取偶数;真实训练还会涉及运算精度、累加精度和硬件实现。

五、从均匀量化到 NF4:4-bit 不等于等分十六段

4 个比特有 24=162^4=16 种编码。可以让每种编码对应一个可表示值,再把原始权重映射到最近的值,这会产生舍入误差。

先理解 min/max 均匀网格

用一个教学例子:一块权重的最小值是 −1,最大值是 1,在两端之间放 16 个等距点,包括两端。因此相邻间距是 (1−(−1))/15=2/15(1-(-1))/15=2/15,不是除以 16。令编码 q 为 0 到 15:

s=wmax⁡−wmin⁡15,q=round⁡(x−wmin⁡s),x^=wmin⁡+qs.s=\frac{w_{\max}-w_{\min}}{15},\qquad q=\operatorname{round}\left(\frac{x-w_{\min}}{s}\right),\qquad \hat{x}=w_{\min}+qs.

例如 x = 0.2 恰好落在一个网格点上,编码为 9(二进制 1001),还原后仍是 0.2;换成 x = 0.25,就只能映射到附近值。反量化用编码与范围参数恢复近似值,无法找回已经丢弃的信息。

在交互页选「均匀 4-bit」,拖动权重,再逐步点击四个步骤,观察原值、选中刻度、编码与误差。这个端点网格例子展示 min/max 思路,不是所有 INT4 的统一定义。有符号 INT4 通常用 −8 到 7,实际仿射量化还会涉及整数零点、截断和具体校准方案。这里使用 0–15 作为编码,便于直观理解。

QLoRA 的 NF4 不使用等距网格

QLoRA 使用的 NF4 采用非均匀的 16 值码本,零附近的码点更密,并包含精确的零。按块处理时,先计算绝对最大值 a=max⁡i∣wi∣a=\max_i|w_i|,用 u=x/au=x/a 归一化,再查找最接近的码本值 c,存储对应的 4-bit 索引;反量化得到 x^=a×c\hat{x}=a\times c。全零块需要单独处理。

这与“找最小、最大值后等分”不同。在同一个交互里切到 NF4,就能看到刻度在数轴上聚集到零附近。取 x = 0.2,NF4 最近值约为 0.160930,而均匀例子可精确表示 0.2:NF4 并非对每一个数都更准确,它的码本设计考虑的是权重分布。QLoRA 论文 · bitsandbytes 的 NF4 码本

实验固定块内范围和尺度,便于单独观察映射。真实模型会有许多块,每块的尺度不同;块内若有很大的离群值,其他小权重归一化后可能挤到零附近。Double Quantization 进一步压缩这些尺度等常数,这也解释了为什么“每个权重 4-bit”不等于完全没有额外元数据。

六、QLoRA:进一步压缩冻结基座

LoRA 减少了可训练参数,但基座仍然很大。若 7B 参数以 BF16 保存,仅权重载荷就约为 14 GB(十进制),还不含适配器与激活等开销。

QLoRA 将冻结基座以低比特形式存储,再训练高精度 LoRA 适配器。用到权重时按需反量化到计算精度参与运算。可以用下面的近似公式理解:

y=dequant⁡(Q(W0))x+αrBAx.y=\operatorname{dequant}(Q(W_0))x+\frac{\alpha}{r}BAx.

Q 表示量化。反量化结果是原权重的近似值,不是无损恢复。4-bit 主要描述基座存储,不表示梯度、适配器、优化器和所有计算都是 4-bit。 QLoRA 原论文

冻结基座:4-bit 编码 → 按需反量化 → 参与计算
适配器:  输入 x → A → B → 缩放
两路相加 → 后续网络 → 损失 → 反向传播,只更新适配器

三项配套设计分别做什么?

设计作用容易混淆的地方
NF4(4-bit NormalFloat)用适合近似正态分布权重的非均匀量化级别压缩权重不是普通的均匀整数取整,也不是对任何分布都最优
Double Quantization对量化使用的尺度等常数再次量化,减少元数据开销不是把同一份权重反复压缩两次
Paged Optimizers利用统一内存分页,在必要时迁移优化器状态以应对内存峰值不保证更快,CPU 与 GPU 间的数据迁移可能有代价

这三项处理的是不同问题:权重载荷、量化元数据,以及偶发的内存峰值。作者论文与实现

七、显存账本:不能直接说整个训练省四倍

BF16 每参数 2 字节,4-bit 每参数 0.5 字节,理想权重载荷相差四倍。但训练显存不只有基座权重。

交互页采用一个明确的简化假设:每个可训练参数用 16 字节,包含低精度权重 2 字节、梯度 2 字节、FP32 主权重 4 字节、Adam 一阶与二阶动量共 8 字节。P 为基座参数量,T 为适配器参数量:

方法示意模型状态开销
全参数微调16P 字节
LoRA2P + 16T 字节
QLoRA 理想载荷估算0.5P + 16T 字节

例如 P = 7B、T = 0.1%P,得到 112 GB、14.112 GB、3.612 GB。它们只是统一假设下的算术对比,不能据此判断“4 GB 显卡就能训练 7B”。

实际还需要计入量化元数据、未量化模块、激活、反量化临时缓冲、运行时与显存碎片。具体优化器和精度策略,也会改变每参数状态开销。序列长度、batch size、梯度检查点等配置,需要结合实际训练测量。

八、常见误解与选择思路

  • LoRA 只用于注意力的 Q、V? 这是原论文中的实验设置之一,目标模块可以不同,并不是方法定义。
  • QLoRA 一定达到全参数微调的效果? 论文结果依赖特定模型、数据与评测,需要在自己的验证集上比较。
  • 适配器很小,所以训练一定很快? 基座依然参与前向与反向;减少模型状态显存和提高吞吐是两件事。
  • 量化基座可以直接合并适配器? 要看部署实现,可能涉及反量化、合并、重新量化,必须重新评估误差与显存峰值。

如果高精度基座已经能放入显存,LoRA 是直接的参数高效微调路线;若主要瓶颈是冻结基座的权重存储,QLoRA 则进一步降低这部分负担。两者最终都要用任务质量、速度和实测显存来评估。

再回到交互页,把两条路径分清:LoRA 让任务更新更小;QLoRA 在此基础上,让冻结基座存得更省。大模型的计算并没有因此消失。

参考资料

  1. Hu et al.:LoRA: Low-Rank Adaptation of Large Language Models
  2. Dettmers et al.:QLoRA: Efficient Finetuning of Quantized LLMs
  3. QLoRA 作者仓库
打开原图