对应 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 学习目标
学完这一讲,你应该能够:
- 把微调的显存拆成权重、梯度 + 优化器状态、激活三块,说出每种参数高效微调(PEFT)方法省掉了哪一块、没省掉哪一块。
- 区分提示工程、提示调优、前缀调优、适配器、LoRA:新参数放在哪里、推理时有什么代价。
- 写出 LoRA 的前向公式和初始化,解释为什么它没有额外的推理延迟,以及怎样在一批里服务多个 LoRA。
- 解释分块量化、双重量化、NF4 各自解决什么问题,算出每个参数的平均比特数。
- 说明 QLoRA 怎样把 65B 模型的微调放进一块 48 GB 的卡;说明旁路微调为什么能省掉激活,以及它的代价。
- 举例说明什么样的任务适合 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-shot | GPT-3 微调 |
|---|---|---|
| 问答 SQuAD v2(F1) | 69.8% | 88.4% |
| 文本蕴含 RTE(准确率) | 69% | 85.4% |
| 自然语言转 SQL:WikiSQL | 20% | 73% |
| 自然语言转 SQL:Spider | 18% | 62% |
结构化输出(SQL)这类任务上差距最大。
1.2 全量微调和从头训练一样贵(代码 01 演示 2)
slides:微调 175B 的 GPT-3 要 80 块 A100-40GB;每个检查点约 1 TB;服务一个微调后的模型还要 10 块 A100。
用第 13 讲的混合精度 Adam 账(16 字节 / 参数):
| 组成 | 每参数 | GPT-3 175B |
|---|---|---|
| FP16 权重 | 2 | 349 GB |
| FP16 梯度 | 2 | 349 GB |
| FP32 主权重 + Adam 的 m、v | 12 | 2,094 GB |
| 合计(不含激活) | 16 | 2.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-7B | GPT-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 步:
| 方法 | 训练的参数 | 原任务 | 新任务 |
|---|---|---|---|
| 不微调 | 0 | 100% | 0.2% |
| 提示调优(4 / 8 / 16 个) | 256–1,024 | 93%–99% | 6%–9% |
| 前缀调优(每层 2 / 4 / 8 个) | 512–2,048 | 98%–100% | 15%–17% |
| 适配器(瓶颈 2 / 4 / 8) | 1,288 / 2,320 / 4,384 | ≈100% | 33% / 43% / 95% |
| 全量微调 | 85,056 | 100% | 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)
代号任务,所有线性层:
| r | 1 | 2 | 4 | 8 |
|---|---|---|---|---|
| 参数 | 1,920 | 3,840 | 7,680 | 15,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 = 4 | 7,680 | 4 | 100% |
| LoHa r = 2 | 7,680 | 4 | 88.3% |
| LoKr f = 4, r = 2 | 1,120 | 8 | 27.6% |
| LoKr f = 4, r = 16 | 7,840 | 64(实际 60) | 99.0% |
秩确实可以更高,但这个任务只需要低秩的改动,更高的秩用不上,参数化越直接越好学。变体在需要高秩更新的任务上才可能显出优势(它们最早用在 Stable Diffusion 的微调上,LyCORIS 库)。
3.7 LoRA 做不到的事(代码 05 演示 4)
反向任务”q 某物 → 某人”需要模型换一种查找方式:
| 配置 | 步数 | 反向任务 |
|---|---|---|
| 不微调 | 0 | 0% |
| 全量微调 | 2000 | 99.8% |
| LoRA r = 8,α = 32,种子 0 / 1 / 2 | 3000 | 29.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):
- 估计标准正态分布的 2^k + 1 个分位数,得到一个 k 比特的”正态分布专用”数据类型;
- 归一化到 [−1, 1];
- 输入张量用 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% |
| NF4 | 3.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,056 | 163.8 KB | 1,021 KB | 100% |
| LoRA(r = 4) | 7,680 | 163.8 KB | 92 KB | 100% |
| QLoRA(NF4 + 双重量化,r = 4) | 7,680 | 42.3 KB | 92 KB | 99.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 GB | 420 GB | 197 GB |
| QLoRA | 36 GB | 13 GB | 197 GB |
| LST(旁路) | 140 GB | 24 GB | 78 GB |
| QST(量化 + 旁路) | 36 GB | 4 GB | 69 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 / QLoRA | 86% / 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(量化旁路微调)的两步:
- 原模型做 4 比特分块、双重量化(同 QLoRA)——省权重;
- 加一个和原模型分开的旁路网络——省激活;再用低秩适配器和无梯度的降采样模块(如池化)减少旁路的可训练参数——省优化器状态。三块都省。
- 论文:总显存最多减少 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 | 权重的低秩增量 | 梯度 + 优化器状态 | 权重、激活 | 合并后为 0 | 90%–100% |
| QLoRA | 4 比特权重 + LoRA | 加上 3/4 的权重 | 激活 | 反量化(或合并) | 99.8% |
| 旁路 / QST | 旁边的小网络 | 加上原模型的激活 | — | 旁路可并行 | 6%(新句式) / 100%(换说法) |
| 全量微调 | 所有参数 | — | — | 0 | 100% |
几条结论:
- 显存有三块,每种方法省的不一样:LoRA 省优化器状态,QLoRA 再省权重,旁路微调再省激活。
- 越省,越不能改原模型:旁路微调省得最多,但只能利用原模型已有的特征;LoRA 能改原模型的计算,但改动受低秩的限制。
- 部署是 LoRA 的杀手锏:合并后零延迟,不合并时一批里能混用多个 LoRA,一份原模型服务很多任务。
- 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
- 微调的显存有三块:权重、梯度 + 优化器状态、激活。全量微调里梯度 + 优化器状态占 7/8,冻结参数就能省掉它们。
- 调提示:提示工程不可微;提示 / 前缀调优可训练,但只能引导原模型已有的计算,在小模型上效果差,并占用上下文。
- 适配器插在模型内部,能改变计算,但多出串行的层,小批推理时延迟明显。
- LoRA:ΔW = BA,B 初始化为 0;很小的秩就够;合并后零延迟,不合并时一批里能混用多个 LoRA;LoHa / LoKr 用同样的参数得到更高的秩。
- QLoRA:NF4(按正态分位数设计的 4 比特类型)+ 分块 + 双重量化(4.127 比特 / 参数)+ 分页优化器 + LoRA 加在所有线性层,65B 的微调放进一块 48 GB 的卡,效果和 16 位微调相当。
- 激活:LoRA / QLoRA 省不掉(实测 86%–87%);旁路微调让反向不经过原模型,激活省得最多,代价是只能利用原模型已有的特征。
- 边界:需要原模型”换一种算法”的任务上,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)的权衡;
- 同一个模型要在”训练引擎”和”推理引擎”之间来回切换,权重怎样重新切分、同步。