第 18 讲 · LLM 训练:参数高效微调 · Parameter-Efficient Fine-Tuning

Advanced Topics in Machine Learning Systems (LLM Edition) · Lecture 18。个人学习讲义与配套实验,按主题整理并持续修订。

课程源码 ZIP ↓

个人整理的学习笔记,非官方讲义;课程来源与整理说明见课程介绍。配套示例未逐一运行验证。

本篇目录

对应 Maps to:CMU 15-779(Fall 2025,Zhihao Jia),Lecture 18 · Wed 11/5 · LLMs: Training (Parameter-Efficient Fine-Tuning)(slides 标题页写的是 Lecture 17,课表上是第 18 次课)。 必读论文:E. Hu et al., LoRA: Low-Rank Adaptation of Large Language Models(ICLR 2022);T. Dettmers et al., QLoRA: Efficient Finetuning of Quantized LLMs(NeurIPS 2023)。 依据说明:本讲以原课 slides 17-LLM-finetuning.pdf 为主线:

  • LLM 需要微调(GPT-3 few-shot vs 微调的对比表);微调极其昂贵(80 块 A100-40GB、每个检查点 1 TB、服务又要 10 块 A100);
  • 调提示:上下文学习、提示工程(思维链)及其局限、提示调优 / 前缀调优;
  • 调适配器:适配器(Houlsby et al.)、LoRA、LoHa、LoKr;
  • 量化:absmax、分块量化、双重量化、QLoRA;
  • LoRA 的问题(激活)与旁路微调(LST、QST)。

slides 的大纲里还有”服务适配器”一项,但没有对应的 slides;本讲在 §3.4 根据 LoRA 论文和后续工作(S-LoRA、Punica)简单补上。QST(Quantized Side Tuning, ACL 2024)是本课程所在研究组的工作,slides 用了它的图 1。论文数字只做转述。

peft_lab.py(LoRA 及变体、适配器、提示 / 前缀调优、分块量化与 NF4、QLoRA、旁路网络、激活的实测)和全部实验都是本讲义自己设计的;基础模型是第 5 讲的 LikesWorld 小 Transformer(8.5 万参数),不是真实的 LLM。 配套代码 Code:code/lecture18/(5 个脚本 + 一个共享模块;需要 numpy 和 torch;最长的约 2.5 分钟) 预计阅读时间:3 小时(含运行代码)


0. 本讲导航 · How to read this lecture

0.1 学习目标

学完这一讲,你应该能够:

  1. 把微调的显存拆成权重、梯度 + 优化器状态、激活三块,说出每种参数高效微调(PEFT)方法省掉了哪一块、没省掉哪一块。
  2. 区分提示工程、提示调优、前缀调优、适配器、LoRA:新参数放在哪里、推理时有什么代价。
  3. 写出 LoRA 的前向公式和初始化,解释为什么它没有额外的推理延迟,以及怎样在一批里服务多个 LoRA。
  4. 解释分块量化、双重量化、NF4 各自解决什么问题,算出每个参数的平均比特数。
  5. 说明 QLoRA 怎样把 65B 模型的微调放进一块 48 GB 的卡;说明旁路微调为什么能省掉激活,以及它的代价。
  6. 举例说明什么样的任务适合 PEFT,什么样的任务可能需要全量微调。

0.2 先修与本讲范围

默认你已经会本讲只给直觉,后面再展开
第 13 讲:混合精度 Adam 的 16 字节 / 参数、激活显存后训练(RLHF、RLVR)的系统问题(第 19 讲)
第 5 讲:Transformer、KV 缓存、LikesWorld 小模型训练后量化(GPTQ、AWQ,附加讲 A2)
第 16、17 讲:LLM 服务的基本概念指令微调的数据选择(第 20 讲的 LESS)
线性代数:秩、奇异值分解分页优化器、4 比特矩阵乘法 kernel 的实现

0.3 本讲地图

§1  为什么要微调,为什么这么贵                      ← 代码 01
§2  调提示与适配器                                  ← 代码 02
§3  LoRA:低秩的权重更新                            ← 代码 03、05 演示 4
§4  量化与 QLoRA                                     ← 代码 04、05 演示 1
§5  LoRA 省不掉的激活,与旁路微调                   ← 代码 05 演示 2–3、01 演示 5
§6  把它们放在一起
§7  论文精读:LoRA;QLoRA
§8 小结  §9 常见误区  §10 练习  §11 延伸阅读  §12 下一讲预告

0.4 关于实验

  • 基础模型:第 5 讲训练好的 LikesWorld 模型,只学过一种问法 “q 某人 某物 .”(答案准确率 100%)。
  • 主要的新任务(“代号”任务):用新的句式 “likes 某人 代号 .” 提问,答案换成物品的代号(物品表里往后数 3 个)。它复用原模型已经会的”回头查”,只要改提问的识别和输出的映射。基础模型准确率 0%。
  • 两个对照任务:
    • 反向任务 “q 某物 某人 .”——需要一条新的查找回路,用来展示 PEFT 的边界;
    • 换说法任务 “q 某人 代号 .”——原来的问法,只换答案,用来测试旁路微调。
  • 训练时一半是新任务、一半是原任务;每次 800 步、批 32。所有随机初始化都固定了种子。

1. 为什么要微调,为什么这么贵 · Why fine-tuning, and why it’s expensive

1.1 LLM 需要微调

微调:从预训练好的基础模型出发,在下游任务上继续更新参数。slides 给的 GPT-3 数字(few-shot vs 微调):

任务GPT-3 few-shotGPT-3 微调
问答 SQuAD v2(F1)69.8%88.4%
文本蕴含 RTE(准确率)69%85.4%
自然语言转 SQL:WikiSQL20%73%
自然语言转 SQL:Spider18%62%

结构化输出(SQL)这类任务上差距最大。

1.2 全量微调和从头训练一样贵(代码 01 演示 2)

slides:微调 175B 的 GPT-3 要 80 块 A100-40GB;每个检查点约 1 TB;服务一个微调后的模型还要 10 块 A100。

用第 13 讲的混合精度 Adam 账(16 字节 / 参数):

组成每参数GPT-3 175B
FP16 权重2349 GB
FP16 梯度2349 GB
FP32 主权重 + Adam 的 m、v122,094 GB
合计(不含激活)162.8 TB → 至少 70 块 40 GB 的卡

权重本身只占 1/8,梯度和优化器状态占 7/8。只要冻结大部分参数,就能省掉其中的大部分——这是所有 PEFT 方法的共同出发点。

注意不同论文的口径不同:QLoRA 说”65B 的 16 位微调要 780 GB 以上”,是按 12 字节 / 参数(没有 FP32 主权重)算的;LoRA 说”GPT-3 训练要 1.2 TB”,看起来是把优化器状态算成两份 FP16(代码 01 演示 3、4 逐一核对)。

1.3 各方法要训练多少参数(代码 01 演示 1)

方法LLaMA-2-7BGPT-3 175B
提示调优(20 个虚拟 token)0.08M(0.001%)0.25M
前缀调优(每层 K、V 各 20 个)5.2M(0.08%)47.2M(0.03%)
适配器(每层 2 个,瓶颈 64)33.8M(0.5%)304M(0.17%)
LoRA(r = 8,Wq、Wv)4.2M(0.06%)37.7M(0.02%)
LoRA(r = 64,所有线性层,QLoRA 的设置)160M(2.4%)1,359M(0.8%)

都不到 3%。但”参数少”不等于”显存少”:冻结的权重本身、反向传播要用的激活,才是 PEFT 之后的大头(§5)。


2. 调提示与适配器 · Tuning prompts and adapters

2.1 上下文学习与提示工程(代码 02 演示 1)

上下文学习(in-context learning):大模型能从提示里的几个输入—输出示例理解任务,不需要训练(GPT-3 论文:模型越大越能利用上下文里的信息)。提示工程:人工设计文本提示,例如思维链(chain-of-thought)——把大任务拆成子任务、让模型一步步推理。

slides 列出的两个局限:

  • 难设计:要花很多精力才能得到一个好提示;
  • 不可微:不能直接在数据集上优化。

代码 02 演示 1:我们的小模型在上下文里已经有 0、1、2 个”代号问答”的示范时,准确率都只有 0%–1%——它没有上下文学习的能力。上下文学习是大模型的”涌现”能力,小模型只能靠训练。

2.2 提示调优 / 前缀调优

把提示变成可训练的:

  • 提示调优(prompt tuning):在输入前面拼几个虚拟 token,每个虚拟 token 的嵌入向量是可训练参数;
  • 前缀调优(prefix tuning,Li & Liang 2021):在每一层的 K、V 前面拼几个可训练的向量;
  • 共同点:冻结 LLM 的参数,只通过反向传播训练这些新参数。
提示工程提示调优
提示的形式一串普通 token一串虚拟 token,每个都有可训练的参数
怎样起作用利用 LLM 的上下文学习冻结 LLM,通过反向传播训练新参数

实现上,前缀调优就是一段可训练的 KV 缓存:PrefixTuned 构造一个 KVCache,把每层的 k、v 设成参数,再调用原模型的 forward(tokens, cache)。

2.3 适配器

适配器(Houlsby et al. 2019):在每个 Transformer 块里(注意力之后、前馈之后)插一个瓶颈小网络 h ← h + up(GELU(down(h))),训练时冻结原网络,只训练适配器。

  • 参数少(瓶颈小)→ 显存、计算开销低;
  • 近似恒等的初始化(up 层初始化为 0)→ 训练从原模型出发,稳定。

2.4 在小模型上比一比(代码 02 演示 2)

代号任务,800 步:

方法训练的参数原任务新任务
不微调0100%0.2%
提示调优(4 / 8 / 16 个)256–1,02493%–99%6%–9%
前缀调优(每层 2 / 4 / 8 个)512–2,04898%–100%15%–17%
适配器(瓶颈 2 / 4 / 8)1,288 / 2,320 / 4,384≈100%33% / 43% / 95%
全量微调85,056100%100%
  • 提示 / 前缀调优在小模型上学不会:几个虚拟 token 只能”引导”原模型已有的计算,而这个任务要模型在新句式下启动查找、再换一种输出——要改模型内部的计算。这和文献一致:提示调优的效果随模型规模增长,在很大的模型上才追得上全量微调(Lester et al. 2021,“规模的力量”)。
  • 提示调优还让原任务掉到 93%–99%:虚拟 token 拼在每一个输入前面,原任务的输入也被改变了。
  • 适配器能学会,但瓶颈要够宽(8 时 95%,4 时只有 43%)。

2.5 推理时的代价(代码 02 演示 3)

  • 提示 / 前缀调优永久占用一部分上下文长度(LoRA 论文:“减少了可用的序列长度”);前缀调优还难优化,效果随参数量非单调变化(LoRA 论文 §3 的观察)。
  • 适配器多出几层串行的计算。我们的小模型上计算量多 5%、时间几乎不变;LoRA 论文表 1 在 GPU 上测 GPT-2 medium:批 32、序列 512 时延迟多 2%–3%,批 1、序列 128 时多 21%–30%——在线推理的小批场景里,GPU 很难用并行把多出的几层藏起来;模型切分到多卡时还要多做同步。

3. LoRA:低秩的权重更新 · Low-Rank Adaptation

3.1 方法(代码 03 演示 1)

假设:微调时权重的变化量有很低的”内在秩”。于是冻结预训练的 W₀ ∈ ℝ^{d×k},把更新写成两个小矩阵的乘积:

h = W₀x + ΔWx = W₀x + (α/r)·B·A·x,B ∈ ℝ^{d×r},A ∈ ℝ^{r×k},r ≪ min(d, k)

  • 初始化:A 用随机高斯、B 用 0,所以一开始 ΔW = 0,模型和原模型完全相同(代码里输出差 0)。A 不能也是 0,否则 A、B 的梯度都是 0,永远学不动。
  • 缩放 α/r:论文的说法是”用 Adam 时调 α 大致相当于调学习率”,所以把 α 设成第一次尝试的 r、不再调;这样改变 r 时不用重新调超参数。
  • 计算:先算 x·Aᵀ(r 维)再乘 Bᵀ,代价 r·(d + k),不用显式算出 d×k 的 ΔW。
  • 一般化:把 LoRA 加到所有权重上、把 r 加到满秩,就大致恢复了全量微调的表达力;而适配器会收敛到一个 MLP,前缀调优收敛到一个不能处理长输入的模型。

3.2 秩和”加在哪里”(代码 03 演示 2)

代号任务,所有线性层:

r1248
参数1,9203,8407,68015,360
新任务90.3%99.7%100%100%

只加注意力(wqkv、wo,r = 4)99.3%,只加前馈(r = 4)99.0%。

  • 很小的秩就够了——和论文表 6 一样(GPT-3 上 r = 1 在 WikiSQL、MNLI 上就有竞争力)。
  • 论文表 5:同样 18M 参数的预算,分散到 Wq、Wv 两个矩阵(r = 4)比集中在一个矩阵(r = 8)好。“加在哪里”取决于任务要改模型的哪一部分;我们的任务里差别不大。
  • 和适配器比:LoRA r = 2(3,840 个参数)99.7%,适配器瓶颈 4(2,320 个)只有 43%。

3.3 没有额外的推理延迟(代码 03 演示 3)

部署时直接算出 W = W₀ + BA 存下来,推理和全量微调的模型完全一样快:

  • 合并前后输出差 1.5e-5(float32 舍入),新任务准确率不变;
  • 换任务:减去 BA、加上 B’A’,是精确的加减法(减去后和原权重差 3e-8);
  • 存储:一份原模型 + 每个任务几十 MB 的增量。GPT-3 上 r = 4、只加 Wq、Wv:18.9M 参数 = 37.7 MB(论文”约 35 MB”),检查点缩小约 10,000 倍;100 个任务 353 GB vs 35 TB(代码 01 演示 3)。

3.4 服务适配器:一批里混用多个 LoRA

LoRA 论文 §4.2 指出的局限:合并以后,就不能在一个前向里给不同的请求用不同的 (A, B)。解决办法是不合并:原权重的矩阵乘法对整批做一次,低秩支路按请求选各自的 (A, B):

yᵢ = W₀xᵢ + (α/r)·B_{t(i)}·A_{t(i)}·xᵢ

代码 03 演示 3:同一批 8 个请求,前 4 个用代号任务的 LoRA,后 4 个用另一个任务的 LoRA,和分别用各自的模型计算完全一致(差 0)。代价是每个 token 多一条低秩支路(wqkv 上 +8% 的乘加)。S-LoRA、Punica 用专门的 kernel 把”一批里每个请求一条不同的低秩支路”做快,一块 GPU 能同时服务上千个 LoRA。

3.5 ΔW 长什么样(代码 03 演示 4)

论文 §7 研究了 GPT-3 上学到的 ΔW,得到三个结论:(1) 很小的秩就够;(2) 不同秩、不同种子学到的主方向重合(图 3、图 4);(3) ΔW 放大了 W 里已有但不突出的方向(表 7,放大倍数 21.5)。

我们的小模型上(r = 4 和 r = 8 两次独立训练):

  • 能量集中:第 2 层前馈输出矩阵的 ΔW,前 2 个奇异方向占 87% 的能量——结论 (1) 成立。
  • 方向重合:只在两处明显重合,远高于随机——第 1 层 wqkv 的输入端(A 的 φ = 0.57,“识别新句式”)和第 2 层 w2 的输出端(0.86,“换成代号”);其余矩阵上大多不重合。论文的结论 (2) 在这里只部分成立:小模型、只训练 800 步,很多矩阵上的改动很小,A 主要还是它的随机初始化。
  • 放大:把 W 投影到 ΔW 的 4 维子空间上,范数 0.58,比 W 自己的主方向(5.47)小、比随机方向(0.33)大;放大倍数 8.9——结论 (3) 的方向成立。

3.6 两个变体:LoHa、LoKr(代码 03 演示 5)

  • LoHa:ΔW = (B₁A₁) ⊙ (B₂A₂)(逐元素乘)。参数和秩 2r 的 LoRA 一样,秩上限却是 r²。
  • LoKr:ΔW = C ⊗ (BA)(克罗内克积)。rank(C ⊗ M) = rank(C)·rank(M),用很少的参数得到很高的秩。
  • slides 的说法:同样的参数,更高的秩、更强的表达力。

代码里:

方法参数秩上限新任务
LoRA r = 47,6804100%
LoHa r = 27,680488.3%
LoKr f = 4, r = 21,120827.6%
LoKr f = 4, r = 167,84064(实际 60)99.0%

秩确实可以更高,但这个任务只需要低秩的改动,更高的秩用不上,参数化越直接越好学。变体在需要高秩更新的任务上才可能显出优势(它们最早用在 Stable Diffusion 的微调上,LyCORIS 库)。

3.7 LoRA 做不到的事(代码 05 演示 4)

反向任务”q 某物 → 某人”需要模型换一种查找方式:

配置步数反向任务
不微调00%
全量微调200099.8%
LoRA r = 8,α = 32,种子 0 / 1 / 2300029.3% / 41.4% / 98.0%

全量微调稳定地学会了;LoRA 多训练 50%,三个种子里只有一个学会。另外:

  • 全量训练所有块的线性层(不训练嵌入、归一化、输出层)也能学会,所以问题不在”改哪些参数”,而在低秩参数化学得慢、对初始化敏感;
  • α 很敏感:α = 8(α/r = 1)、或者 α 不变把学习率提到 1e-2,都学不会(README 的”计划 vs 实际”)。

这正是 LoRA 论文自己的提醒:“我们不指望小的 r 对每个任务都管用”——下游任务和预训练差得越远(例如换一种语言),全量微调越可能更好。也提醒我们:PEFT 的超参数可能比全量微调更敏感,比较方法时要多跑几个种子。


4. 量化与 QLoRA · Quantization and QLoRA

4.1 absmax 量化与离群值(代码 04 演示 1)

量化:把数据类型换成更少的比特。slides 的例子:FP32 → INT8,范围 [−127, 127]:

X_int8 = round(127 / absmax(X) · X),反量化 X ≈ X_int8 · absmax(X) / 127

问题:有大的离群值时,量化区间用不好。代码里:4096 个正态值整个张量一个常数,相对误差 0.9%;加一个 1.0 的离群值,absmax 变成约 13 倍,其余 4095 个数只用到 19 / 255 个码,误差涨到 9.0%。

4.2 分块量化与双重量化(代码 04 演示 2)

分块量化:把张量切成大小为 B 的块,每块独立量化,各有一个 FP32 的量化常数。离群值只影响它所在的块(上例误差回到 1.3%)。代价:每个参数平均多 32/B 比特,B = 64 时是 0.5 比特。

双重量化(QLoRA 的贡献之一):把第一级的量化常数再量化:

  • 第一级常数都是正的,先减去均值,再按 256 个一块做 8 比特量化;
  • 每参数的开销从 32/64 = 0.5 比特降到 8/64 + 32/(64·256) = 0.127 比特,省 0.373 比特(65B 模型上约 3 GB);
  • 代码里常数的相对误差平均 0.11%,权重误差几乎不变(9.225% → 9.226%)。
  • QLoRA 论文第二级用 8 比特浮点(FP8),slides 写的是 8 比特整数,两者误差都很小。

4.3 4 比特 NormalFloat(NF4)(代码 04 演示 3)

分位数量化:让每个量化区间里落入的数一样多——信息论意义上最优。一般的分位数估计很贵、对离群值误差大;但预训练的权重近似服从零均值的正态分布,缩放后分布是固定的,分位数可以事先精确算好。

NF4 的构造(论文 §3):

  1. 估计标准正态分布的 2^k + 1 个分位数,得到一个 k 比特的”正态分布专用”数据类型;
  2. 归一化到 [−1, 1];
  3. 输入张量用 absmax 缩放到 [−1, 1] 后量化。

为了让 0 能被精确表示(padding 等零值不能有误差),而且用满 2^k 个值,做成不对称的:负半边取 2^{k−1} 个分位点、正半边取 2^{k−1} + 1 个,合并后去掉重复的 0。

代码 04 按这个描述(和 bitsandbytes 源码里最外侧分位点取 0.9677 的做法)重新造出了 QLoRA 附录 E 的 16 个值,最大差 1.1e-7。

数据类型码使用分布的熵正态权重的相对误差
INT4(等距)3.50 比特10.8%
FP4(E2M1)3.82 比特10.6%
NF43.89 比特9.2%

NF4 的码在 0 附近密、两头稀,正好匹配正态分布:每个码用得更均匀,误差最小。量化小模型时(代码 04 演示 4),权重误差的排序同样是 NF4 < INT4 ≈ FP4;但这个小模型很”皮实”,所有 4 比特方案的准确率都还是 100%、损失只差第四位小数。论文表 2 在大模型上(Pile 困惑度):INT4 34.34、FP4 31.07 / 29.48、NF4 + 双重量化 27.41。

4.4 QLoRA(代码 05 演示 1、代码 01 演示 4)

一个线性层的 QLoRA:

Y^{BF16} = X^{BF16} · doubleDequant(c₁^{FP32}, c₂^{k-bit}, W^{NF4}) + X^{BF16} · L₁^{BF16} · L₂^{BF16}

  • 一种存储类型 + 一种计算类型:权重用 NF4 存,用的时候反量化成 BF16 再做矩阵乘法;
  • 只为 LoRA 参数算权重梯度;对 4 比特权重只需要算”对输入的梯度”(也要用反量化出来的 BF16 权重);
  • 块大小:W 用 64(精度),第二级常数用 256(省内存);
  • 分页优化器:用 NVIDIA 的统一内存,GPU 显存不够时自动把优化器状态换到 CPU 内存、用到时再换回来,防止梯度检查点带来的显存尖峰导致 OOM。

显存账(LLaMA-65B):16 位全量微调 783 GB(12 字节 / 参数)→ 权重 NF4 + 双重量化 33.7 GB + LoRA(r = 64,所有线性层)状态约 9.6 GB = 43 GB,加上梯度检查点后的激活,放进一块 48 GB 的卡。

效果(代码 05 演示 1,代号任务):

方法训练的参数块内权重存储梯度 + Adam新任务
全量微调85,056163.8 KB1,021 KB100%
LoRA(r = 4)7,680163.8 KB92 KB100%
QLoRA(NF4 + 双重量化,r = 4)7,68042.3 KB92 KB99.8%

在 4 比特的冻结权重上训练 LoRA,学得和 16 位的 LoRA 一样好。论文的结论更强:在 GLUE、Super-NaturalInstructions、MMLU 上,4 比特 QLoRA 能复现 16 位全量微调和 16 位 LoRA 的效果(“量化损失的精度可以通过适配器微调完全找回来”)。

论文还发现一个关键的超参数:LoRA 要加在所有线性层上才能追上全量微调;只加在 Wq、Wv 上(LoRA 论文的默认)不够,而 r 本身影响不大。


5. LoRA 省不掉的激活,与旁路微调 · Activations and side tuning

5.1 适配器类方法的共同问题(代码 05 演示 2、代码 01 演示 5)

slides 用 QST 论文图 1(LLaMA-2-70B,批 16,序列 384)说明:

权重优化器状态激活
全量微调140 GB420 GB197 GB
QLoRA36 GB13 GB197 GB
LST(旁路)140 GB24 GB78 GB
QST(量化 + 旁路)36 GB4 GB69 GB

代码 01 演示 5 用公式复现了其中几个数:激活 = 每层 s·b·h·(34 + 5·a·s/h) 字节(Korthikanti et al.)= 197.3 GB;QLoRA 的 LoRA 参数 828M × 16 字节 = 13.3 GB;NF4 + 双重量化的 70B 权重 36 GB。QLoRA 省掉了权重和优化器状态,激活却一点没少,占了它总显存的 80%。

为什么?适配器网络减少了可训练参数和优化器状态,但反向传播还是要穿过整个模型,中间激活都要保存。代码 05 演示 2 用 PyTorch 的 saved_tensors_hooks 实测前向时为反向保存的张量:

方法保存的激活(相对全量微调)
全量微调100%
LoRA / QLoRA86% / 87%
提示调优80%
适配器、前缀调优61%
旁路网络(LST / QST 式)39% / 32%
  • LoRA 加在所有线性层上,每条 LoRA 支路算 A 的梯度要用到这一层的输入,和全量微调为算权重梯度保存的东西几乎一样;
  • 适配器、前缀调优冻结了所有原线性层,算”对输入的梯度”只需要权重、不需要输入,所以少一些,但仍要保存注意力、归一化、激活函数的中间结果;
  • QST 论文的说法:PEFT 方法仍要保存超过 70% 的激活。

5.2 旁路微调(代码 05 演示 3)

旁路网络(side network,LST:Sung et al. 2022):用一个更小的网络(原模型的”缩小版”)当适配器,它读原模型每一层的隐藏状态(先降维),自己的输出最后加回原模型。关键:

  • 信息从原模型流向旁路,反过来不行;
  • 所以不需要对原模型做反向传播,原模型的中间激活一个都不用存(代码里在 no_grad 下前向)。

QST(量化旁路微调)的两步:

  1. 原模型做 4 比特分块、双重量化(同 QLoRA)——省权重;
  2. 加一个和原模型分开的旁路网络——省激活;再用低秩适配器和无梯度的降采样模块(如池化)减少旁路的可训练参数——省优化器状态。三块都省。
  • 论文:总显存最多减少 2.3 倍、微调最多快 3 倍,效果和 QLoRA 相当(MMLU 等);对比全量微调最多省 7 倍;聊天能力(GPT-4 评判)和其他方法相当。
  • 推理时原模型和旁路可以并行计算,不增加延迟;换任务只要换旁路网络(slides 图里的”Task Switch”)。

代价:旁路只能利用原模型已经算出来的特征。代码 05 演示 3:

方法代号任务(新句式)换说法任务(原问法,只换答案)
LoRA(r = 4)100%100%
旁路(LST,线性降维)5.9%100%
旁路(QST 式:NF4 原模型 + 池化降维)6.6%100%

换说法任务里,原模型在 “q 某人” 之后已经把答案查出来了,旁路只要读到这个特征、换成代号;代号任务里,原模型在新句式下根本不查找,旁路(16 维、2 层)自己从头学查找太难。LoRA 能改原模型内部的计算,所以两个都会。大模型的特征足够丰富时,这个限制不太明显;任务需要原模型”换一种算法”时就会暴露。


6. 把它们放在一起 · Putting it together

方法新参数放在哪里省掉的显存没省掉的推理代价这里的结果(代号任务)
提示工程不训练全部(不训练)—占上下文小模型学不会
提示 / 前缀调优输入 / 每层 KV 前面梯度 + 优化器状态权重、大部分激活占上下文6%–17%
适配器每层插瓶颈网络梯度 + 优化器状态权重、大部分激活多几层串行计算33%–95%
LoRA权重的低秩增量梯度 + 优化器状态权重、激活合并后为 090%–100%
QLoRA4 比特权重 + LoRA加上 3/4 的权重激活反量化(或合并)99.8%
旁路 / QST旁边的小网络加上原模型的激活—旁路可并行6%(新句式) / 100%(换说法)
全量微调所有参数——0100%

几条结论:

  1. 显存有三块,每种方法省的不一样:LoRA 省优化器状态,QLoRA 再省权重,旁路微调再省激活。
  2. 越省,越不能改原模型:旁路微调省得最多,但只能利用原模型已有的特征;LoRA 能改原模型的计算,但改动受低秩的限制。
  3. 部署是 LoRA 的杀手锏:合并后零延迟,不合并时一批里能混用多个 LoRA,一份原模型服务很多任务。
  4. PEFT 不是免费的午餐:需要模型”换一种算法”的任务上,低秩更新可能学得很慢、对超参数和种子敏感(§3.7)。

7. 论文精读 · Paper reading

7.1 LoRA: Low-Rank Adaptation of Large Language Models(Hu et al., ICLR 2022)

1. 问题:一个预训练大模型要适配很多下游任务。全量微调为每个任务产出一份和原模型一样大的模型——GPT-3 175B 上,每个任务一份 350 GB,部署不起。

2. 为什么重要又困难:

  • 模型每隔几个月变大一次,“每个任务一份完整模型”从不方便变成不可能;
  • 训练时的显存(优化器状态)、存储、任务切换都随模型大小线性增长;
  • 已有的高效方法要么增加推理延迟,要么减少可用的序列长度,而且常常追不上全量微调的效果。

3. 以前的工作为什么不行:

  • 适配器:多出的层要串行计算,小批在线推理时延迟明显增加(批 1 时多 20%–30%),模型切分时还要多做同步;
  • 前缀调优 / 提示调优:难优化,效果随参数量非单调变化;占用一部分序列长度;
  • 只训练部分层或偏置(BitFit):效果常常追不上。

4. 新意:

  • 受”预训练模型的内在维度很低”(Aghajanyan et al. 2020)启发,假设权重更新的内在秩也很低:ΔW = BA;
  • 初始化 B = 0(训练从原模型出发),缩放 α/r(换 r 时不用重调超参数);
  • 推理时把 BA 合并回 W:没有额外的推理延迟;
  • 只加在注意力的权重上(主要是 Wq、Wv),冻结 MLP;
  • 对 ΔW 的实证分析:秩很小就够;不同秩学到的主方向重合;ΔW 放大 W 里已有但不突出的特征。

5. 结果:

  • GPT-3 175B:可训练参数减少 10,000 倍、训练显存减少 3 倍(1.2 TB → 350 GB),检查点 350 GB → 35 MB,训练快 25%;
  • GPT-3 上 LoRA(4.7M 参数)WikiSQL 73.4、MNLI 91.7,全量微调 73.8、89.5,适配器(40.1M)73.2、91.5——效果相当或更好;
  • RoBERTa、DeBERTa(GLUE)、GPT-2(E2E NLG)上同样和全量微调相当;
  • r = 1 在 GPT-3 的 Wq、Wv 上就有竞争力。

本讲的验证:复现了检查点大小和多任务存储的数字;小模型上 r = 2 就学会了新任务;B = 0 时输出和原模型完全相同;合并后零延迟、多 LoRA 批处理精确等价;ΔW 的能量集中在少数方向上、放大了 W 里不突出的方向。和论文不同的发现:不同秩学到的主方向只在少数矩阵上重合;在需要”新回路”的任务上,LoRA 学得慢、对种子敏感。

局限(含论文自己的讨论):

  • 合并后不能在一批里混不同任务的请求(不合并就有一点开销);
  • 加在哪些矩阵上、取多大的秩,靠经验;论文只研究了注意力权重;
  • 小的 r 不一定对所有任务都管用(下游任务和预训练差得很远时);
  • 训练显存里的激活没有减少(§5)。

7.2 QLoRA: Efficient Finetuning of Quantized LLMs(Dettmers et al., NeurIPS 2023)

1. 问题:LoRA 省掉了优化器状态,但冻结的权重本身仍然很大:65B 模型的 16 位权重就要 130 GB,微调 LLaMA-65B 要 780 GB 以上的显存。能不能在一块 48 GB 的卡上微调 65B 的模型,而且不损失效果?

2. 为什么重要又困难:

  • 大模型的微调只有少数机构做得起;
  • 已有的量化方法只适用于推理,用在训练上会崩(论文引用的观察);
  • 4 比特的精度很低,离群值会浪费量化区间;量化常数本身也占内存;
  • 梯度检查点会带来显存尖峰,偶尔一个长序列就 OOM。

3. 以前的工作为什么不行:

  • LLM.int8()、GPTQ 等只做推理时的量化;
  • 普通的 INT4 / FP4 对正态分布的权重不是最优的;
  • 分块量化的常数开销(块 64 时 0.5 比特 / 参数)在 4 比特下占比很大;
  • 默认的 LoRA(只加在 Wq、Wv)追不上全量微调。

4. 新意:

  • 4 比特 NormalFloat(NF4):按正态分布的分位数设计的数据类型,对正态分布的权重信息论意义上最优;不对称构造以精确表示 0;
  • 双重量化:把量化常数再量化,每参数省 0.373 比特(65B 上约 3 GB);
  • 分页优化器:用统一内存自动在 CPU、GPU 之间换页,应对显存尖峰;
  • 一种存储类型 + 一种计算类型:NF4 存、BF16 算,只为 LoRA 参数算权重梯度;
  • 实证发现:LoRA 要加在所有线性层上才能追上 16 位全量微调。

5. 结果:

  • 65B 模型的微调从超过 780 GB 降到小于 48 GB 的显存;
  • GLUE、Super-NaturalInstructions(RoBERTa、T5 到 3B)上 4 比特 QLoRA 复现了 16 位全量微调;LLaMA 7B–65B 上 NF4 + 双重量化复现了 16 位 LoRA 的 MMLU;FP4 比 BF16 LoRA 低约 1 个百分点;
  • 量化数据类型(Pile 困惑度):INT4 34.34、FP4 31.07 / 29.48、NF4 + 双重量化 27.41;
  • Guanaco:在 Vicuna 基准上达到 ChatGPT 的 99.3%,单卡微调 24 小时;训练了 1000 多个模型分析指令数据(数据质量比数量重要),并指出聊天机器人基准不完全可信。

本讲的验证:复现了 NF4 的 16 个值(最大差 1.1e-7)、双重量化的 0.127 比特、65B 的显存账;NF4 的码使用最均匀、误差最小;小模型上 QLoRA 与 16 位 LoRA 学得一样好,块内权重存储降到约 1/4。和论文不同的地方:我们的小模型对 4 比特量化很不敏感,看不出各数据类型在模型质量上的差别。

局限:

  • 只减少了权重和优化器状态,激活没有减少,长序列、大批时仍要靠梯度检查点(§5,QST 要解决的问题);
  • 每次前向、反向都要反量化,比 16 位 LoRA 慢;
  • 论文没有给出分页优化器的系统测量(只说长序列时才会换页,批 16 时速度相同);
  • 结论主要基于正态分布的权重假设。

7.3 两篇论文之间的联系

  • 一条递进的线:全量微调(权重 + 梯度 + 优化器状态 + 激活全都要)→ LoRA(冻结权重,省掉梯度和优化器状态)→ QLoRA(权重也压到 4 比特)→ QST(连激活也省掉)。每一步都盯着显存账里剩下的最大一块。
  • 共同的假设:两篇都依赖”预训练模型已经很好,微调只需要小的改动”——LoRA 假设改动是低秩的,QLoRA 进一步假设冻结的权重可以容忍 4 比特的误差(误差可以由 LoRA 补偿)。§3.7 的反向任务正好是这个假设不成立的例子。
  • 互相成就:QLoRA 的”LoRA 要加在所有线性层上”修正了 LoRA 论文”只加在注意力上”的默认设置;QLoRA 又让 LoRA 的”一份原模型服务多个任务”更便宜(原模型只要 1/4 的显存)。
  • 系统思想:两篇都是”把原模型当成只读的共享资源”——LoRA 在存储和服务上共享,QLoRA 在显存里用更紧凑的格式共享。这和第 16 讲”KV 缓存的共享”、第 13 讲”消除冗余”是同一类思路。

8. 小结 · Summary

  1. 微调的显存有三块:权重、梯度 + 优化器状态、激活。全量微调里梯度 + 优化器状态占 7/8,冻结参数就能省掉它们。
  2. 调提示:提示工程不可微;提示 / 前缀调优可训练,但只能引导原模型已有的计算,在小模型上效果差,并占用上下文。
  3. 适配器插在模型内部,能改变计算,但多出串行的层,小批推理时延迟明显。
  4. LoRA:ΔW = BA,B 初始化为 0;很小的秩就够;合并后零延迟,不合并时一批里能混用多个 LoRA;LoHa / LoKr 用同样的参数得到更高的秩。
  5. QLoRA:NF4(按正态分位数设计的 4 比特类型)+ 分块 + 双重量化(4.127 比特 / 参数)+ 分页优化器 + LoRA 加在所有线性层,65B 的微调放进一块 48 GB 的卡,效果和 16 位微调相当。
  6. 激活:LoRA / QLoRA 省不掉(实测 86%–87%);旁路微调让反向不经过原模型,激活省得最多,代价是只能利用原模型已有的特征。
  7. 边界:需要原模型”换一种算法”的任务上,PEFT 可能学得慢、对超参数和种子敏感,全量微调更可靠。

9. 常见误区 · Common misconceptions

  • “PEFT 训练的参数少 1000 倍,显存也少 1000 倍。” 省掉的是梯度和优化器状态;冻结的权重还在,激活几乎不减(LoRA 实测保存了全量微调 86% 的激活;QST 图里 QLoRA 的激活占它总显存的 80%)。
  • “LoRA 会让推理变慢。” 合并 W = W₀ + BA 以后和原模型一模一样;只有不合并(为了一批里混用多个 LoRA)时才多一条低秩支路。会让推理变慢的是适配器(串行的额外层)。
  • “LoRA 的 A、B 都初始化为 0 最安全。” 两个都是 0 时,A、B 的梯度都是 0,永远学不动。要一个随机、一个为 0。
  • “秩越大越好。” 很多任务 r = 1–4 就够(论文表 6,代码 03),更大的秩多花参数却没有收益;但也有任务低秩学不会(§3.7)。
  • “4 比特量化就是把数值四舍五入到 16 个等距的值。” 等距(INT4)对正态分布的权重不好;NF4 按分位数设计,0 附近密、两头稀;还要分块(处理离群值)、双重量化(省常数的开销)。
  • “QLoRA 的计算也在 4 比特下进行。” 4 比特只是存储格式;用的时候反量化成 BF16 再算,LoRA 参数也是 BF16。
  • “旁路微调又省显存又不掉精度,可以替代 LoRA。” 它不能改变原模型的计算,原模型没算出来的特征它用不上(代码 05:新句式的任务只有 6%)。
  • “提示调优是参数最少的方法,所以最划算。” 在小模型上它几乎学不会新任务,还会影响原任务(代码 02);它的效果随模型规模增长,在很大的模型上才有竞争力。

10. 练习 · Exercises(含参考答案)

练习 1 LLaMA-2-7B(32 层,d = 4096,多头注意力)在 Wq、Wk、Wv、Wo 上加 r = 16 的 LoRA,一共多少可训练参数?占原模型的多少?FP16 存下来多大?

答案:

  • 每个矩阵 16 × (4096 + 4096) = 131,072 个,每层 4 个,32 层:16,777,216 ≈ 16.8M;
  • 占 6.74B 的 0.25%;FP16 约 33.6 MB。

练习 2 NF4 权重,块大小 128,第二级块大小 256,做双重量化。每个参数平均多少比特?70B 参数的模型权重多大?不做双重量化呢?

答案:

  • 双重量化:4 + 8/128 + 32/(128 × 256) = 4 + 0.0625 + 0.00098 = 4.063 比特 → 70e9 × 4.063 / 8 = 35.6 GB;
  • 不做:4 + 32/128 = 4.25 比特 → 37.2 GB。

练习 3 解释为什么 LoRA 用”A 随机、B = 0”初始化,而不是”A = 0、B 随机”或”都随机”。

答案:

  • 只要一个是 0,ΔW = BA 就是 0,训练从原模型出发(不破坏预训练的能力);“都随机”会在第一步就给模型加一个随机扰动。
  • “A 随机、B = 0”时,B 的梯度 ∝ (梯度)·(Ax)ᵀ 不为 0,第一步 B 就开始变化;A 的梯度 ∝ Bᵀ(梯度) 一开始是 0,等 B 变大后才开始学。反过来”A = 0、B 随机”也能工作,两者大致对称,论文选了前者。
  • 都是 0 不行:两者的梯度都是 0。

练习 4 证明合并后的模型和 LoRA 模型输出相同;再说明在什么场景下不应该合并,以及不合并的代价。

答案:

  • h = W₀x + s·BAx = (W₀ + s·BA)x,矩阵乘法的分配律,所以合并后完全等价(浮点舍入除外,代码里差 1.5e-5)。
  • 不合并的场景:同一份原模型同时服务很多任务(多租户),一批里的请求属于不同的 LoRA——合并就只能服务一个任务。
  • 代价:每个 token、每个矩阵多 r·(d + k) 次乘加(d = k = 4096、r = 16 时多 0.8%),以及”每个请求一个不同的低秩支路”带来的 kernel 效率问题(S-LoRA、Punica 用分段的矩阵乘法 kernel 解决)。

练习 5 用 Korthikanti 的公式估算 LLaMA-2-7B(32 层,h = 4096,a = 32)在批 8、序列 512 时一次前向保存的激活。LoRA 能把它减少多少?QST 呢?

答案:

  • 每层 s·b·h·(34 + 5·a·s/h) = 512 × 8 × 4096 × (34 + 5 × 32 × 512 / 4096) = 16.8M × 54 字节 ≈ 0.906 GB;32 层约 29 GB。
  • LoRA:反向要穿过整个模型,激活几乎不变(代码实测 86%,QST 论文说 PEFT 仍要保存 70% 以上)。
  • QST:原模型不反向,激活只剩旁路网络的部分(旁路窄得多,例如宽度 1/16)加上原模型前向时的临时张量,可以降到原来的很小一部分;QST 图 1 上 70B 是 197 GB → 69 GB。

练习 6 NF4 为什么要做成不对称的(负半边 8 个、正半边 9 个分位点再合并)?如果直接取 16 个对称的分位点会怎样?

答案:

  • 直接取 2^k + 1 = 17 个分位点、相邻两两取中点得到 16 个对称的值时,0 不在其中(对称的偶数个值,最靠近 0 的是 ±ε)。
  • 0 值(padding、被剪掉的权重)就会被量化成 ±ε,产生误差。
  • 不对称构造:负半边 8 个、正半边 9 个,都含 0,合并后去掉一个重复的 0,正好 16 个值,并且 0 能被精确表示。

练习 7 x = [0.1, −0.2, 0.05, 2.0]。(1)整个向量一个常数做 absmax INT8 量化,反量化后的值是多少?(2)每 2 个一块呢?

答案:

  • (1) 常数 127/2.0 = 63.5:码 round(6.35) = 6、round(−12.7) = −13、round(3.175) = 3、127;反量化 0.0945、−0.2047、0.0472、2.0。前三个的相对误差约 5%、2%、6%。
  • (2) 第一块 absmax 0.2,常数 635:码 round(63.5) = 64、−127 → 0.1008、−0.2;第二块 absmax 2.0,常数 63.5:码 3、127 → 0.0472、2.0。第一块的误差降到 1% 以下,第二块不变——离群值 2.0 只影响它所在的那一块。

练习 8 (开放)三个场景,你会选哪种微调方式?(a)一个 SaaS 平台,同一个 70B 基础模型给 500 个客户各微调一个版本,在线服务;(b)一个研究者只有一块 24 GB 的卡,想微调 33B 的模型做指令跟随;(c)一个任务和预训练差得很远(例如一种新的形式语言),数据很多、算力也够。

参考思路:

  • (a) LoRA(或 QLoRA 训练):每个客户只存几十 MB 的增量,服务时不合并、一批里混用多个 LoRA(S-LoRA 式),一份原模型服务所有客户;
  • (b) QLoRA:NF4 + 双重量化的 33B 权重约 17 GB,加上 LoRA 状态和分页优化器勉强放下(QLoRA 附录 G:33B 在 24 GB 上需要分页优化器);序列很长时考虑 QST 式的旁路微调省激活;
  • (c) 全量微调更可靠:低秩更新可能学得慢、对超参数敏感(§3.7);也可以先试较大秩的 LoRA 加在所有线性层上,并多跑几个种子比较。

11. 延伸阅读 · Further reading

  • N. Houlsby et al., Parameter-Efficient Transfer Learning for NLP(ICML 2019)——适配器。
  • X. L. Li, P. Liang, Prefix-Tuning: Optimizing Continuous Prompts for Generation(ACL 2021);B. Lester et al., The Power of Scale for Parameter-Efficient Prompt Tuning(EMNLP 2021)。
  • Y.-L. Sung et al., LST: Ladder Side-Tuning for Parameter and Memory Efficient Transfer Learning(NeurIPS 2022);Z. Zhang et al., Quantized Side Tuning: Fast and Memory-Efficient Tuning of Quantized Large Language Models(ACL 2024)。
  • S.-Y. Yeh et al., Navigating Text-To-Image Customization: From LyCORIS Fine-Tuning to Model Evaluation(ICLR 2024)——LoHa、LoKr。
  • Y. Sheng et al., S-LoRA: Serving Thousands of Concurrent LoRA Adapters(MLSys 2024);L. Chen et al., Punica: Multi-Tenant LoRA Serving(MLSys 2024)。
  • T. Dettmers et al., LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale(NeurIPS 2022)——离群值与混合精度分解,QLoRA 的前身之一。

12. 下一讲预告 · Next lecture

第 18 讲的微调是”监督式”的:给定输入和标准答案,最小化交叉熵。今天的大模型在这之后还有一个更重要的阶段——后训练(post-training),尤其是强化学习:让模型自己生成回答,用奖励模型或可验证的规则打分,再根据分数更新模型(RLHF、RLVR)。

第 19 讲是嘉宾讲座《LLM 后训练系统的新问题》(Banghua Zhu)。slides 没有公开,讲义依据公开的系统论文整理:

  • RL 训练循环里,生成(rollout)为什么占了大部分时间;
  • 生成长度的长尾怎样让同步的训练空等;
  • 异步 RL 与”样本陈旧”(staleness)的权衡;
  • 同一个模型要在”训练引擎”和”推理引擎”之间来回切换,权重怎样重新切分、同步。
← 返回课程目录