第 11 讲:高效适配——提示与参数高效微调

Natural Language Processing with Deep Learning · Lecture 11。个人学习讲义与配套实验,按主题整理并持续修订。

课程源码 ZIP ↓

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

本篇目录

对应:Stanford CS224N Winter 2026 Lecture 9(Efficient Adaptation)与 Lecture 18(嘉宾讲座:LoRA and Fine-tuning);Winter 2025 Lecture 11(Efficient Adaptation)。和同目录 CME295 的提示与微调部分有重叠,可以互相参照。 配套代码:code/lecture11/(03 号文件导入第 9 讲的 lm_utils.py,并加载第 9 讲预训练的迷你 GPT;04 号文件的演示 6 加载第 7 讲训练的迷你 GPT) 预计阅读时间:4–5 小时(第 3.3 节和第 6 节的推导值得在纸上推一遍)


0. 本讲导航

0.1 学完本讲,你应该能够

  1. 说出让一个预训练模型适应新任务的三条路(提示、参数高效微调、全量微调),以及它们各自的代价。
  2. 写出零样本和少样本提示的结构,解释提示为什么脆弱(例子的选择、顺序、格式,三种偏差),以及上下文校准怎么修正偏差。
  3. 从三个角度解释上下文学习:示例到底教了模型什么、贝叶斯视角(用例子定位任务)、机制视角(归纳头;注意力可以实现梯度下降)。
  4. 算清全量微调的显存账(每个参数约 16 字节),说出参数高效微调(PEFT)的总体思路和三大类方法。
  5. 解释适配器、前缀微调 / 提示微调、BitFit 的做法和取舍。
  6. 详细解释 LoRA:低秩假设和内在维度、公式、初始化、缩放、合并、放在哪些层,以及低秩假设什么时候不成立。
  7. 解释量化的基本做法(absmax、分块)、NF4 和双重量化,以及 QLoRA 为什么能在一张显卡上微调 650 亿参数的模型。
  8. 面对一个具体场景,在提示、PEFT 和全量微调之间做出选择。

0.2 先修知识

知识点用在哪里最低要求
第 9 讲全讲预训练 → 微调;零样本分类;冻结主体 + 线性头
第 10 讲第 2、8 节指令微调;对话模型
第 7 讲第 3、5、6 节自注意力的 Q、K、V;Transformer 块的结构;参数量
线性代数第 6 节矩阵的秩、奇异值分解(SVD)的直观含义
概率第 3.2 节贝叶斯公式:后验 ∝ 先验 × 似然

0.3 本讲地图

第1节  动机:让一个大模型做新任务,有哪几条路?
第2节  提示:零样本与少样本                         ← 代码 01(演示 2)
第3节  上下文学习是怎么发生的?                     ← 代码 01(演示 1、3)
第4节  全量微调的代价与 PEFT 的思路
第5节  加法式与选择式:适配器、提示微调、BitFit      ← 代码 03
第6节  LoRA(本讲重点)                            ← 代码 02、03
第7节  量化与 QLoRA                                ← 代码 04
第8节  怎么选:提示、PEFT 还是全量微调?
第9–13节 小结、与作业的关系、练习、延伸阅读、下一讲预告

关于本讲的实验数字:01、02、04 号代码运行很快(几秒到十几秒),编写讲义时都完整运行过,讲义引用的是它们的实际输出。03 号代码要训练约 20 个分类器,编写时只做了冒烟测试,讲义中只描述预期的趋势,具体数字请以你自己运行的结果为准。


1. 动机:让一个大模型做新任务,有哪几条路?

第 9、10 讲之后,我们手里有了一个预训练好(也许还做过后训练)的语言模型。现在来了一个新任务:给客服邮件分类、把病历摘要成固定格式、用公司内部的术语回答问题……怎么让模型做这件事?

有三条路,按”改动模型的程度”从小到大排:

不改参数                            改一小部分参数                    改所有参数
   │                                     │                               │
提示(prompting)              参数高效微调(PEFT)                  全量微调
零样本、少样本、                适配器、提示微调、                  (第 9 讲的做法)
提示工程                        BitFit、LoRA、QLoRA
   │                                     │                               │
零训练成本;                    训练便宜;每个任务                  效果上限最高;
每次调用都要带上                只存几 MB 的"补丁"                  但贵,每个任务
长长的提示                                                          存一份完整模型

为什么不总是全量微调?对一个 70 亿参数的模型,全量微调要一百多 GB 的显存(第 4 节会算这笔账),需要多张高端显卡;每个任务还要存一份 14 GB 的模型副本。如果要服务一千个客户、每个客户一个定制版本,就要存 14 TB 的模型。更别提千亿参数的模型,很多人根本拿不到它的权重(只能通过 API 调用),这时候提示就是唯一的选择。

本讲沿着这条轴从左往右走:先讲提示和上下文学习(第 2、3 节),再讲参数高效微调(第 4–7 节),最后讨论怎么选(第 8 节)。


2. 提示:零样本与少样本

2.1 从零样本到少样本

第 9 讲第 8 节已经见过零样本(zero-shot)的想法(GPT-2,Radford 等 2019):不训练,只把任务写成”续写”的形式。例如情感分类:

Review: the plot was gripping and the acting superb.
Sentiment:

然后看模型续写 “positive” 还是 “negative” 的概率高。

GPT-3 的论文(Brown 等 2020,标题就是 Language Models are Few-Shot Learners)往前走了一步:在提示里放几个示例(demonstrations):

Review: a waste of time.                     Sentiment: negative
Review: i loved every minute of it.          Sentiment: positive
Review: the dialogue was painfully dull.     Sentiment: negative
Review: the plot was gripping and the acting superb.
Sentiment:

这叫少样本提示(few-shot prompting),模型从提示里的例子”学会”任务的现象叫上下文学习(in-context learning,ICL)。注意:模型的参数一点都没变,“学习”完全发生在一次前向传播之内;换一个提示,模型就”忘了”。

GPT-3 论文的关键发现是:少样本的能力随模型规模急剧增长。小模型从例子里获益很少,1750 亿参数的模型在不少任务上,只靠几十个例子就接近了当时专门微调过的模型。这让”一个模型、不训练、做所有任务”第一次显得可行。

一个提示通常由三部分组成:

  1. 任务描述(可选):用自然语言说明要做什么(“判断下面评论的情感”);
  2. 示例(零样本时没有):若干个”输入 → 输出”对;
  3. 查询:要模型处理的新输入,以及引出答案的格式(“Sentiment:”)。

用模型给出答案也有两种方式:生成(让模型自由续写,再从文字里解析答案)和打分(像第 9 讲那样比较几个候选答案词的概率,叫 verbalizer)。分类任务常用打分,开放式任务只能生成。

2.2 提示为什么脆弱

上下文学习的结果对提示的细节非常敏感,这是它在实践中最大的麻烦:

  • 例子的选择:用哪几个例子,结果可以差很多。选和查询相似的例子(比如用检索找最近邻)通常更好(Liu 等 2022)。
  • 例子的顺序:Lu 等(2022)发现,同样的几个例子只换顺序,GPT-3 的准确率就能从接近瞎猜变化到接近最好的水平。
  • 格式:Sclar 等(2023)发现,改变分隔符、大小写、空格这些对人来说毫无意义的格式细节,LLaMA-2-13B 的少样本准确率最多能差 76 个百分点。
  • 三种偏差(Zhao 等 2021,Calibrate Before Use):
    • 多数标签偏差:例子里哪个标签多,模型就倾向于预测哪个;
    • 近因偏差:倾向于预测最后几个例子的标签;
    • 常见词偏差:倾向于预测在预训练语料里更常见的答案词。

代码 01 的演示 2 用一个按假设写出来的模型模拟这三种偏差(对数几率 = 评论本身的证据 + 三项偏差,见代码里的 simulated_log_odds),看它们的破坏力有多大。没有偏差时这个模型的准确率是 93.4%;4 个例子的 16 种标签序列下:

设置最低平均最高
16 种例子标签序列,不校准51.8%74.8%91.5%
其中 2 正 2 负的 6 种顺序76.4%83.9%91.4%
16 种序列,上下文校准之后93.4%93.4%93.4%
偏差还和输入有关时,校准之后84.6%91.3%93.4%

四个例子全是正面时,模型几乎什么都预测成正面(51.8%);即使正负各半,只是顺序不同(“负负正正”和”正正负负”),准确率也差了 15 个百分点(76.4% 对 91.4%)。

2.3 上下文校准

Zhao 等提出的修正办法很简单:先问一个”没有内容”的问题。把查询换成 “N/A”(或空字符串)喂给模型,这时模型对各个标签的偏好只可能来自偏差——输入里没有任何证据。然后把这个偏好从每个真实查询的打分里”除掉”:

p^(y∣x,C)∝p(y∣x,C)p(y∣N/A,C)\hat{p}(y \mid x, C) \propto \frac{p(y \mid x, C)}{p(y \mid \text{N/A}, C)}

在对数几率上,就是减去 “N/A” 的对数几率。上表第三行:只要偏差是一个”和输入无关的常数”,校准就能把它完全修掉,16 种序列都回到 93.4%。

但校准不是万能的。如果偏差本身和输入有关(表的最后一行:我们让长评论上的多数标签偏差更强、短评论上相反),一个常数修不掉,校准后平均 91.3%,最差 84.6%。

【边界】 演示 2 的模型是我们按 Zhao 等观察到的现象假设出来的,它说明的是”如果模型有这样的偏差,校准能修多少”,而不是证明真实模型的偏差就是这个形式。真实模型上校准的效果因任务、因模型而异。

2.4 提示工程的要点

后训练过的对话模型(第 10 讲)能直接听懂指令,“提示工程”更多是在把话说清楚。几条经过大量实践检验的原则:

  1. 把任务说具体:要什么、不要什么、给谁看、多长、什么格式。“总结一下”不如”用三条要点总结,每条不超过 20 个字,面向没有技术背景的读者”。
  2. 给足背景:模型不知道你知道的事。说明数据从哪里来、结果用在哪里、有哪些约束。
  3. 给例子:格式要求复杂时,一两个例子胜过一大段描述(但例子会被模仿,包括它们的缺点)。
  4. 用分隔符标出结构:把指令、资料、例子用标签或标题分开(比如 <document>…</document>),减少混淆。
  5. 让模型先想再答:复杂问题要求模型先写出推理过程(这是第 14 讲”思维链”的主题)。
  6. 拆成多步:一个复杂任务拆成几个简单的提示,前一步的输出作为后一步的输入。
  7. 用评估集迭代:准备几十个有标准答案的例子,每改一次提示就测一次。凭感觉改提示,很容易”按下葫芦浮起瓢”。

提示也可以自动优化:让模型自己生成、评估、改写候选提示(APE,Zhou 等 2022),或者把一个多步骤的提示流程写成程序、用数据自动调整其中的提示和例子(DSPy,Khattab 等 2023)。

2.5 提示的局限

  • 每次调用都要付费:几十个例子的提示,每次查询都要重新处理一遍(提示缓存能缓解一部分)。
  • 能放的例子有限:上下文窗口是有限的。长上下文模型出现后,放几百上千个例子(“多样本上下文学习”,Agarwal 等 2024)确实还能继续提升,但成本也随之上升。
  • 脆弱:第 2.2 节的各种敏感性。
  • 天花板:模型在预训练和后训练里没学会的能力,提示很难”变”出来。任务和预训练分布差得越远,提示的效果越差,这时就需要训练(第 4–7 节)。

3. 上下文学习是怎么发生的?

参数没有变,模型怎么就”学会”了新任务?这个问题至今没有完整的答案,但有三个互相补充的视角。

3.1 示例到底教了模型什么?

Min 等(2022)做了一个出人意料的实验:把少样本提示里示例的标签换成随机的(输入还是原来的输入,标签随便给),很多模型在很多分类任务上的准确率只掉了一点点。他们进一步发现,示例真正重要的是:

  • 输入的分布:例子长什么样(让模型知道”现在处理的是哪类文本”);
  • 标签的空间:可能的答案有哪些(“positive / negative”);
  • 格式:输入和标签怎么排列。

而”每个输入配对的标签是否正确”反而没那么重要。

但 Wei 等(2023)给这个结论加了重要的限定:他们把标签全部反转(正面标成负面、负面标成正面),发现小模型基本不理会,仍然按自己预训练学到的语义做题;足够大的模型却会跟着例子学会反转后的规则,准确率掉到远低于 50%。也就是说,模型越大,越能”从例子里学到新东西”,而不只是”被例子提醒去做一个已经会的任务”。

这两个发现看起来矛盾,下一节的贝叶斯视角可以把它们统一起来。

3.2 贝叶斯视角:用例子定位任务

Xie 等(2021)提出:预训练语料是由很多种”潜在的任务 / 概念”生成的(一篇影评、一段代码、一份问答……)。为了把下一个词预测好,模型必须学会从上下文推断”现在是哪种任务”。少样本提示里的例子,就是在帮模型做这个推断:

P(任务∣例子)  ∝  P(任务)⏟预训练里有多常见×∏iP(yi∣xi,任务)⏟例子和这个任务有多吻合P(\text{任务} \mid \text{例子}) \;\propto\; \underbrace{P(\text{任务})}_{\text{预训练里有多常见}} \times \underbrace{\prod_i P(y_i \mid x_i, \text{任务})}_{\text{例子和这个任务有多吻合}}

然后按后验对各个任务的预测加权平均,得到对查询的回答。这就是隐式的贝叶斯推断。

代码 01 的演示 1 把这个想法变成一个能精确计算的小模型:输入是 48 个形容词,“预训练时见过的任务”有 5 种,每种有一个先验(“情感”最常见,0.70;“情感反转”几乎不存在,0.01;“按长度分类”0.10……)。模型还有一个参数 ε\varepsilon:它认为”例子的标签写错了”的概率。ε\varepsilon 小表示模型很信任例子的标签,ε\varepsilon 接近 0.5 表示几乎不看标签。

(a) 例子能让模型做一个不常见的任务。 例子的标签按”长度”任务给出(先验只有 0.10),在其余词上的准确率:

例子数 kk0124816
ε=0.05\varepsilon = 0.05(信任标签)58.3%69.0%79.1%93.5%99.5%100%
ε=0.35\varepsilon = 0.35(不太信任)58.3%58.3%59.0%62.1%78.3%98.9%

没有例子时,模型只能按先验去做最常见的情感任务,它在长度任务上的准确率(58.3%)恰好等于两个任务标签一致的比例。例子越多,后验越集中到长度任务上。不太信任标签的模型需要多得多的例子才能被说服。

(b) 随机标签和反转标签。 情感任务,但例子的标签分别是正确的、随机的、全部反转的(准确率按真实情感计算):

正确标签随机标签反转标签
ε=0.35\varepsilon = 0.35,k=4k = 4100%98.4%86.7%
ε=0.35\varepsilon = 0.35,k=16k = 16100%88.1%0.1%
ε=0.05\varepsilon = 0.05,k=4k = 4100%67.8%7.2%
ε=0.05\varepsilon = 0.05,k=16k = 16100%59.0%0.0%
  • 不太信任标签的模型,例子不多时随机标签几乎不影响它(98.4%)——它主要靠先验做题,这就是 Min 等看到的现象。但反转的例子足够多时,它也会被说服(贝叶斯模型里,只要 ε<0.5\varepsilon < 0.5,证据终究会压过先验)。
  • 很信任标签的模型,只要 4 个反转的例子就改用反转的规则(7.2%)——这就是 Wei 等在大模型上看到的现象。

所以两个发现不矛盾:它们对应”模型在多大程度上相信例子的标签”这个参数的两端。

【边界】 这个贝叶斯模型是一个解释框架,不是真实模型的内部机制。它解释不了为什么模型越大越信任例子,也假设了”任务”是有限的几种。真实的预训练里,“任务”是连续而模糊的。

3.3 机制视角:注意力里藏着一个学习算法

贝叶斯视角回答”做了什么”,没有回答”用什么电路做的”。这方面有两条重要的线索。

线索一:归纳头(induction head)。 Olsson 等(2022)在 Transformer 里找到一种由两个注意力头组成的电路:第一个头把”前一个词是什么”的信息写进每个位置,第二个头用当前词去查找”上下文里前面哪里出现过这个词”,然后把那个位置后面的词复制过来。效果是完成”……[A][B]……[A] → [B]“这样的模式补全。这是最朴素的上下文学习:从上下文里找到模式并照着做。他们发现归纳头在训练中突然形成的时刻,恰好是模型上下文学习能力突然提升的时刻。(第 17 讲”可解释性”会在小 Transformer 里找归纳头。)

线索二:注意力可以实现梯度下降。 考虑一个干净的任务:上下文里给出 NN 个线性回归的例子 (xi,yi)(x_i, y_i),yi=w⊤xiy_i = w^\top x_i(每个任务的 ww 都不同),要求预测查询 xqx_q 的 yy。这是一个真正的”学习”问题:模型必须从上下文里的例子估计出 ww。

von Oswald 等(2023)证明:一层线性自注意力(去掉 softmax),配上特定的权重,恰好等于在上下文的例子上做一步梯度下降。 推导并不难:

把每个例子写成一个词元 ei=(xi,ri)e_i = (x_i, r_i),最后一维 rir_i 是”当前的残差”,一开始 ri=yir_i = y_i(相对于 w=0w = 0 的预测);查询词元是 eq=(xq,0)e_q = (x_q, 0)。取

WQ=WK=(I000),WV=(000−η)W_Q = W_K = \begin{pmatrix} I & 0 \\ 0 & 0 \end{pmatrix},\qquad W_V = \begin{pmatrix} 0 & 0 \\ 0 & -\eta \end{pmatrix}

那么 (WKei)⊤(WQej)=xi⊤xj(W_K e_i)^\top (W_Q e_j) = x_i^\top x_j,WVei=(0,…,0,−η ri)W_V e_i = (0, \dots, 0, -\eta\, r_i)。线性注意力加残差后,每个词元最后一维的更新是

rj  ←  rj−ηN∑i=1Nri (xi⊤xj).r_j \;\leftarrow\; r_j - \frac{\eta}{N}\sum_{i=1}^N r_i\,(x_i^\top x_j).

再看梯度下降:损失 L(w)=12N∑i(w⊤xi−yi)2L(w) = \frac{1}{2N}\sum_i (w^\top x_i - y_i)^2,从 ww 走一步得 w′=w+ηN∑i(yi−w⊤xi) xiw' = w + \frac{\eta}{N}\sum_i (y_i - w^\top x_i)\,x_i。新的残差

yj−w′⊤xj  =  (yj−w⊤xj)⏟rj−ηN∑iri (xi⊤xj),y_j - w'^\top x_j \;=\; \underbrace{(y_j - w^\top x_j)}_{r_j} - \frac{\eta}{N}\sum_i r_i\,(x_i^\top x_j),

和注意力的更新一模一样。查询词元的最后一维按同样的公式变成 −w′⊤xq-w'^\top x_q,也就是负的预测。叠 LL 层就是走 LL 步。

代码 01 的演示 3 数值验证了这一点(1、2、5 层注意力和 1、2、5 步梯度下降的预测之差都在 10−1610^{-16} 量级,只是浮点误差),并比较了 8 维线性回归上,上下文里有 NN 个例子时的平均平方误差:

NN不看例子1 层3 层10 层最小二乘
47.455.364.564.163.89
88.214.453.322.000.00
167.812.931.330.300.00
327.971.790.370.010.00
648.091.100.130.000.00

例子越多、层数越多,预测越准,逐渐接近最小二乘(这个任务上最好的算法)。von Oswald 等还发现,在这类任务上从头训练的线性 Transformer,学到的权重和这个构造几乎一样;Garg 等(2022)、Akyürek 等(2022)也发现 Transformer 能在上下文中学会线性回归,表现接近最优算法。

【边界】 这是一个构造性的存在证明:注意力能够实现梯度下降。没人证明真实的大语言模型在处理语言任务时”就在做梯度下降”。它的价值在于说明”前向传播里藏着一个学习算法”在原理上是完全可能的,上下文学习并不神秘。

3.4 三种视角放在一起

视角回答的问题结论
示例的作用(3.1)示例里什么信息重要?格式、输入分布、标签空间;标签对不对的重要性因模型而异
贝叶斯(3.2)模型在做什么推断?用例子在预训练学到的任务里定位当前任务
机制(3.3)用什么电路做的?归纳头复制模式;注意力可以实现梯度下降这样的学习算法

4. 全量微调的代价与 PEFT 的思路

提示有天花板。任务足够重要、数据足够多时,我们还是想训练模型。先算一算全量微调有多贵。

4.1 显存账本

用混合精度和 Adam 训练时,每个参数要存:

内容格式字节
权重(前向、反向用)bf162
梯度bf162
主权重(更新时用,保证精度)fp324
Adam 的一阶矩 mmfp324
Adam 的二阶矩 vvfp324
合计16

一个 70 亿参数的模型:7×109×167 \times 10^9 \times 16 字节 ≈ 112 GB,还不算激活值。而推理只需要 bf16 的权重,14 GB。训练的大头是梯度和优化器状态(详见 CS336 第 2、5 讲)。

4.2 存储与服务

每个任务一份完整的模型副本:10 个任务就是 140 GB,1000 个客户的定制模型就是 14 TB。服务时,每切换一个任务就要把 14 GB 的权重换进显存。

4.3 灾难性遗忘

全量微调会修改所有参数,模型在新任务上变好的同时,可能忘掉预训练学到的一般能力(McCloskey & Cohen 1989 称之为灾难性遗忘)。数据少的时候,全量微调还容易过拟合。

4.4 参数高效微调的思路

参数高效微调(parameter-efficient fine-tuning,PEFT)的核心想法:

冻结预训练模型的全部参数,只训练很少量的新参数(或一小部分旧参数)。

好处直接对应上面三个问题:

  • 显存:冻结的参数不需要梯度和优化器状态,只剩 bf16 权重的 2 字节/参数;
  • 存储:每个任务只存新参数,几 MB 到几十 MB;
  • 遗忘:原模型不变,换回去就是原样;参数少也不容易过拟合。

按”新参数加在哪里”,PEFT 方法大致分三类:

类别做法代表
加法式往模型里加新的模块或向量适配器、前缀微调、提示微调
选择式只训练原有参数的一个子集BitFit(只训练偏置)
重参数化式把权重的变化量写成参数更少的形式LoRA

配套代码 peft.py 在第 7 讲的 TransformerLM 上从零实现了这几种方法,每种只有几十行。


5. 加法式与选择式:适配器、提示微调、BitFit

5.1 适配器(Adapter)

Houlsby 等(2019)在每个 Transformer 子层(注意力、前馈网络)的输出后面插入一个小的瓶颈网络:

Adapter(h)=h+Wup GELU(Wdown h),Wdown∈Rm×d,  Wup∈Rd×m,  m≪d\text{Adapter}(h) = h + W_{\text{up}}\, \text{GELU}(W_{\text{down}}\, h),\qquad W_{\text{down}} \in \mathbb{R}^{m \times d},\; W_{\text{up}} \in \mathbb{R}^{d \times m},\; m \ll d

为什么这样设计?

  • 瓶颈(mm 远小于 dd):参数只有约 2dm2dm 个。
  • 自带残差、WupW_{\text{up}} 初始化为 0:插入的瞬间 Adapter(h)=h\text{Adapter}(h) = h,模型行为完全不变,训练从预训练模型出发,而不是从一个被随机扰动过的模型出发。(LoRA 的 B=0B = 0 是同一个道理。)

在 GLUE 上,适配器每个任务只训练约 3.6% 的参数,就和全量微调差不到 0.4 个百分点。

缺点:适配器是串联在主路径上的,推理时每一层都要多算两个小矩阵乘法,而且没法省掉。对大模型的小批量推理(比如在线对话),这点额外延迟会很明显。

5.2 前缀微调与提示微调

第 2 节的”提示工程”是人去找一串好用的词。词是离散的,没法求梯度。能不能把提示放宽成连续的向量,用梯度下降去找”最好的提示”?

  • 提示微调(prompt tuning,Lester 等 2021):在输入词向量序列的前面拼接 PP 个可训练的 DD 维向量(“软提示”),模型完全冻结。参数只有 P×DP \times D 个(我们的迷你 GPT 上,P=8P = 8 时只有 512 个)。
  • 前缀微调(prefix tuning,Li & Liang 2021):在每一层注意力的键和值前面都拼接可训练的向量,参数多一些(约 2×P×D×2 \times P \times D \times 层数),在中小模型上更稳定。

软提示不必对应任何真实的词,它可以是词向量空间里任意一个点。

Lester 等最重要的发现是”规模的力量”:在 T5 上,模型小的时候提示微调远不如全量微调,但随着模型增大差距不断缩小,到约 110 亿参数时两者几乎相当。直观理解:模型越大,“只改输入”能调动的能力越多。

缺点:小模型上效果差、训练不稳定、对初始化敏感(用真实词的词向量初始化比随机初始化好);软提示占用了上下文长度。

5.3 BitFit

Ben Zaken 等(2021)的 BitFit 只训练模型里所有的偏置项(bias),在 BERT 上只占约 0.1% 的参数。偏置只是给每个神经元加一个常数,但它能整体地”挪动”每一层的激活值。在不少分类任务上,BitFit 接近全量微调;在更难的任务上就不够了。它说明了一件事:适配一个预训练模型需要的改动,可能比想象的少得多。 第 6 节的 LoRA 把这个观察变成了一个有理论依据、又非常实用的方法。


6. LoRA(本讲重点)

6.1 低秩假设从何而来:内在维度

一个模型有一亿个参数,适配一个新任务真的需要调整一亿个”自由度”吗?

Li 等(2018)提出了一个测量方法:不直接训练参数 θ∈RD\theta \in \mathbb{R}^D,而是只在一个随机的 dd 维子空间里训练:

θ=θ0+Pz,P∈RD×d 是固定的随机矩阵,z∈Rd 是唯一可训练的\theta = \theta_0 + P z,\qquad P \in \mathbb{R}^{D \times d}\ \text{是固定的随机矩阵},\quad z \in \mathbb{R}^d\ \text{是唯一可训练的}

逐渐增大 dd,看需要多大的 dd 才能达到全量训练效果的 90%,这个 dd 叫任务的内在维度(intrinsic dimension)。

Aghajanyan 等(2020)把它用在预训练语言模型的微调上,发现:

  • 内在维度非常低:RoBERTa-large 在 MRPC(句子复述判断)上,只训练 200 个参数(投影回全部参数空间)就能达到全量微调效果的 90%;
  • 预训练越充分、模型越大,内在维度越低。预训练把模型带到了一个”离很多任务都很近”的位置。

代码 02 的演示 4 在一个小例子上复现了这个规律。模型是”词袋 → 32 维 → 2 类”的 MLP(5,922 个参数),任务是用 200 条餐厅点评做情感分类。两种出发点:所有参数随机初始化;或者隐藏层用在 2000 条影评上训练过的参数,分类头重新随机初始化(和微调 BERT 一样:主体是预训练的,任务头是新的)。两者出发时都接近瞎猜(52.6% 和 51.8%)。

子空间维度 dd1248163264256全部参数
随机初始化52.3%53.9%57.3%61.3%64.6%76.1%78.2%80.4%80.9%
影评上预训练68.1%70.0%75.4%83.6%83.7%83.9%83.3%84.0%83.0%

达到全量训练准确率的 90%:随机初始化需要 d=32d = 32,预训练过的只需要 d=4d = 4。只用 4 个随机方向,就能把一个预训练过的模型适配到新领域。

(实现细节:随机方向在每组参数里各自归一化,否则随机方向几乎全落在参数最多的第一层上,分类头几乎动不了。Aghajanyan 等的 SAID 方法也按层分配子空间。)

Hu 等(2021)由此提出 LoRA 的假设:既然适配需要的自由度很少,那么微调带来的权重变化量 ΔW\Delta W 也应该是低秩的。 与其在随机子空间里训练,不如直接学一个低秩的 ΔW\Delta W。

6.2 做法

对一个冻结的权重矩阵 W0∈Rdout×dinW_0 \in \mathbb{R}^{d_{\text{out}} \times d_{\text{in}}},LoRA 把它换成

h=W0x+ΔWx=W0x+αr BAx,A∈Rr×din,  B∈Rdout×rh = W_0 x + \Delta W x = W_0 x + \frac{\alpha}{r}\, B A x,\qquad A \in \mathbb{R}^{r \times d_{\text{in}}},\; B \in \mathbb{R}^{d_{\text{out}} \times r}

只训练 AA 和 BB。ΔW=αrBA\Delta W = \frac{\alpha}{r}BA 的秩最多是 rr(通常取 1~64)。

        x ─────────────► W0(冻结)─────────────┐
        │                                       ▼
        └──► A(r × d_in)──► B(d_out × r)──► ×α/r ──► (+) ──► h
             降到 r 维        升回 d_out 维

参数量:全量微调这个矩阵要 doutdind_{\text{out}} d_{\text{in}} 个参数,LoRA 只要 r(din+dout)r(d_{\text{in}} + d_{\text{out}}) 个。代码 02 的演示 1:一个 4096 × 4096 的矩阵,r=8r = 8 时只要 0.39% 的参数。

计算顺序:先算 AxAx(降到 rr 维),再算 B(Ax)B(Ax),计算量是 r(din+dout)r(d_{\text{in}} + d_{\text{out}}),而不是先算出 dout×dind_{\text{out}} \times d_{\text{in}} 的 ΔW\Delta W。

6.3 三个设计细节

① BB 初始化为 0,AA 随机。 B=0B = 0 让训练开始时 ΔW=0\Delta W = 0,模型和预训练模型完全一样。

为什么 AA 不能也是 0? 对 h=W0x+BAxh = W_0x + BAx(省略缩放),设 g=∂L/∂hg = \partial L / \partial h,则

∂L∂B=g (Ax)⊤,∂L∂A=B⊤g x⊤.\frac{\partial L}{\partial B} = g\,(Ax)^\top,\qquad \frac{\partial L}{\partial A} = B^\top g\, x^\top.

如果 A=B=0A = B = 0,两个梯度都是 0,参数永远停在原地。代码 02 的演示 2 验证了这一点:标准初始化训练 300 步后损失从 0.9617 降到 0.0000;A=B=0A = B = 0 时损失纹丝不动,仍是 0.9617。所以必须有一个是随机的。(反过来,A=0A = 0、BB 随机也能训练,只是惯例是 B=0B = 0。)

② 缩放 α/r\alpha / r。 原论文把 α\alpha 固定为第一次尝试的 rr,之后不再调。这样换一个 rr 时,ΔW\Delta W 的大致量级不变,不用重新调学习率。(后来有工作认为 α/r\alpha/\sqrt{r} 在 rr 很大时更合适,即 rsLoRA。)

③ 加在哪些层? 原论文在 GPT-3 上只给注意力的 WqW_q、WvW_v 加 LoRA,发现在固定的参数预算下,“多加几个矩阵、每个的 rr 小一点”比”只加一个矩阵、rr 大一点”更好,rr 甚至可以小到 1 或 2。后来的实践(例如 QLoRA)发现,给所有线性层(包括前馈网络)都加上,效果更稳定地接近全量微调。

6.4 合并:零推理开销

训练完之后,可以把 ΔW\Delta W 直接加回原来的权重:

W←W0+αrBAW \leftarrow W_0 + \frac{\alpha}{r} BA

之后模型就是一个普通的模型,结构、速度和原来一模一样,推理没有任何额外开销。这是 LoRA 相对适配器(串联、无法合并)的一大优势。代码 02 演示 2 中合并前后的输出最大差别是 1.9×10−61.9 \times 10^{-6}(浮点误差)。

需要切换任务时,减掉 ΔW\Delta W 再加上另一个任务的 ΔW′\Delta W' 即可。更进一步,服务系统可以不合并:在显存里只放一份基座,同一批请求里不同的请求用不同的 LoRA 补丁(S-LoRA,Sheng 等 2023,能在一张显卡上同时服务上千个补丁)。代码 03 的演示 3 演示了”一个基座 + 两个任务的补丁”的保存、切换和合并。

6.5 低秩假设什么时候成立?

LoRA 不是魔法,它依赖一个经验事实:微调需要的变化接近低秩。代码 02 的演示 3 在一个线性层上做了对照实验:“预训练”的权重 W0W_0(64 × 64),新任务的真实权重 W∗=W0+ΔWtrueW^* = W_0 + \Delta W_{\text{true}},比较全量微调和不同秩的 LoRA 在新任务上的测试误差(已减去噪声,0 = 完美):

ΔWtrue\Delta W_{\text{true}} 的秩样本噪声全量微调r=1r=1r=2r=2r=4r=4r=8r=8r=16r=16
210000.10.00070.31880.00010.00010.00010.0003
64(满秩)10000.10.00070.95110.89950.80310.63720.3931
22001.00.46420.33390.02530.07470.15830.2751

三行各说明一件事:

  1. 变化是低秩时,很小的 rr 就够:r≥2r \geq 2 和全量微调一样好;r=1r = 1 少了一个方向,不够。
  2. 变化是满秩时,LoRA 不够:r=16r = 16 的误差仍是全量微调的几百倍。所以,如果新任务和预训练差得很远(比如教模型一门全新的语言、大量新知识),LoRA 可能明显不如全量微调。
  3. 数据少、噪声大时,低秩反而更好:全量微调有 4096 个自由参数,把噪声也拟合了进去(误差 0.46);秩合适的 LoRA 只有几百个参数,被迫只学”主要的变化”(误差 0.025,小了近 20 倍)。低秩也是一种正则化。 但 rr 取大了,这个好处就逐渐消失。

这和实践中的观察一致:Biderman 等(2024)的论文标题就是 LoRA Learns Less and Forgets Less——在代码、数学这类需要大量新知识的持续预训练上,LoRA 明显不如全量微调;但它对原有能力的破坏也小得多。

6.6 LoRA 有多省

代码 02 演示 1 的数字(只算加 LoRA 的线性层;括号里是 16 位存储的大小):

模型总参数LoRA 只加在 WqW_q、WvW_vLoRA 加在所有线性层
GPT-2 small(r=8r = 8)0.12B0.29M(0.238%,1 MB)1.33M(1.070%,3 MB)
7B,LLaMA 结构(r=8r = 8)6.74B4.19M(0.062%,8 MB)19.99M(0.297%,40 MB)
GPT-3 175B(r=4r = 4)175B18.87M(0.011%,38 MB)84.93M(0.049%,170 MB)

GPT-3 的完整检查点(16 位)约 350 GB,LoRA 补丁只有几十 MB(论文报告约 35 MB)。LoRA 论文报告:在 GPT-3 上,可训练参数减少到约万分之一,训练时的显存减少到约三分之一,效果和全量微调相当甚至更好。

一个常见误解:LoRA 不会让每一步训练快很多。前向传播仍要经过完整的模型,反向传播时梯度仍要穿过所有冻结的层传回来(只是不用为冻结的参数存梯度)。它省的主要是显存(梯度和优化器状态)和存储。

6.7 在迷你 GPT 上比较

代码 03 在第 9 讲预训练的迷你 GPT(167,872 个参数)上,学一个预训练时没见过的新任务:褒贬混合检测(3 类:只有贬 / 只有褒 / 有褒有贬;测试集里三类约 25% / 25% / 50%,多数类基线约 50%)。每种做法都在最后一个词的隐藏状态上接一个新的分类头,比较 9 种做法在 100 条和 1000 条标注数据下的测试准确率(编写讲义时只做了冒烟测试,表格请你运行后填写):

做法可训练参数(含分类头)100 条1000 条
只训练分类头195(0.12%)
全量微调168,067(100%)
BitFit2,371(1.41%)
提示微调(P=8P = 8)707(0.42%)
适配器(m=8m = 8)6,771(4.03%)
LoRA r=1r = 1(注意力)1,347(0.80%)
LoRA r=4r = 4(注意力)4,803(2.86%)
LoRA r=16r = 16(注意力)18,627(11.10%)
LoRA r=4r = 4(所有线性层)12,483(7.44%)

预期会看到:

  • 只训练分类头最差:预训练的目标是预测下一个词,最后一个位置的隐藏状态里不一定线性地编码了”前面出现过褒和贬”。这是一个新任务,主体需要调整(和第 9 讲”冻结 + 线性头”对情感分类还不错形成对比:情感是预训练时就必须理解的东西,“是否混合”不是)。
  • LoRA、适配器只训练百分之几的参数,应该接近全量微调;数据少时可能更好(6.5 节第 3 点)。
  • rr 从 1 增加到 16,提升通常不大。
  • 提示微调在这么小的模型上通常明显落后(5.2 节”规模的力量”)。
  • 训练用时差别不大(6.6 节的”常见误解”)。

代码 03 的演示 4 还检查了全量微调改变的权重 ΔW\Delta W 是不是低秩的:对每个线性层算 ΔW\Delta W 的奇异值,看前 4 个方向占了多少”能量”(奇异值平方和的比例),并和同样形状的随机矩阵比较。预期 ΔW\Delta W 比随机矩阵集中得多。

【边界】 解读演示 4 要小心:训练步数很少时,ΔW\Delta W 是少数几次梯度更新的累加,而一个线性层的单步梯度 g x⊤g\,x^\top 本身就是低秩的(秩不超过这一批的词元数)。所以”ΔW\Delta W 低秩”有一部分来自优化过程本身,不全是任务的性质。Hu 等在 GPT-3 上做的分析更有说服力:r=8r = 8 和 r=64r = 64 的 LoRA 学到的最主要的几个方向高度重合。

6.8 LoRA 的变体

LoRA 之后出现了大量变体,几个有代表性的:

  • QLoRA(Dettmers 等 2023):基座量化成 4 位,见第 7 节;
  • DoRA(Liu 等 2024):把权重分解成”大小”和”方向”两部分,方向用 LoRA 更新,更接近全量微调的更新模式;
  • LoRA+(Hayou 等 2024):AA 和 BB 用不同的学习率(BB 的大一些);
  • rsLoRA:缩放改为 α/r\alpha/\sqrt{r},让大 rr 也能稳定训练。

7. 量化与 QLoRA

LoRA 解决了”可训练参数”的问题,但冻结的基座仍然要整个放进显存:650 亿参数的模型,bf16 也要 130 GB。QLoRA 的想法是:把冻结的基座量化成 4 位,在它上面训练 16 位的 LoRA。 基座不训练,所以低精度带来的误差不会在训练中累积;LoRA 的参数是高精度的,可以”补偿”一部分量化误差。

7.1 浮点数格式

格式符号指数尾数最大值1 附近的间隔
fp321823约 3.4×10383.4 \times 10^{38}2−23≈1.2×10−72^{-23} \approx 1.2 \times 10^{-7}
fp161510655042−10≈9.8×10−42^{-10} \approx 9.8 \times 10^{-4}
bf16187约 3.4×10383.4 \times 10^{38}2−7≈7.8×10−32^{-7} \approx 7.8 \times 10^{-3}

指数位决定范围,尾数位决定精度。代码 04 的演示 1:70000 在 fp16 里变成 inf(溢出),10−910^{-9} 变成 0(下溢);在 bf16 里都没问题,但 1.001 变成了 1。深度学习对精度不太敏感、对溢出非常敏感,所以今天的大模型训练和推理大多用 bf16(它就是 fp32 的高 16 位)。

7.2 量化的基本做法

absmax 量化(对称量化):用一组数里绝对值最大的那个决定刻度,

q=round ⁣(xmax⁡∣x∣⋅Q),x^=q⋅max⁡∣x∣Q,Q=2b−1−1q = \text{round}\!\left(\frac{x}{\max|x|} \cdot Q\right),\qquad \hat{x} = q \cdot \frac{\max|x|}{Q},\qquad Q = 2^{b-1} - 1

8 位时 Q=127Q = 127。存的是整数 qq 和一个缩放因子 max⁡∣x∣\max|x|。

离群值的破坏力:代码 04 演示 2,4096 个服从 N(0,0.022)N(0, 0.02^2) 的权重量化到 int8,相对误差 0.89%,用到了 191 个不同的整数。加入一个等于 1.0 的离群值(是普通权重的 50 倍)后,刻度被放大 50 倍,普通权重全挤在 0 附近,只用到 19 个不同的整数,误差暴涨到 8.96%。

这不是假想的问题:Dettmers 等(2022,LLM.int8())发现,参数超过约 60 亿的模型里会出现数值大几十倍的”离群特征”,朴素的 int8 量化会让模型性能崩溃。

分块量化:每 64 个数一块,各自一个缩放因子。离群值只影响它所在的那一块,误差回到 1.28%。代价是每块要多存一个缩放因子。

7.3 NF4:按分布设计码本

4 位只有 16 个可能的值。放在哪里最好?均匀的 int4 把它们等间隔地放在 [−1,1][-1, 1] 上。但权重大致服从正态分布:靠近 0 的地方权重最密集,却只分到几个值;两端几乎没有权重,却也占了好几个值。

NF4(4-bit NormalFloat)让每个量化值”负责”的概率相同:取标准正态分布的等间隔分位数,再加上一个精确的 0,归一化到 [−1,1][-1, 1]。代码 04 演示 3 从零算出的 16 个值和 QLoRA 公布的数值一致:

-1.000 -0.696 -0.525 -0.395 -0.284 -0.185 -0.091  0.000  0.080  0.161  0.246  0.338  0.441  0.563  0.723  1.000

可以看到它们在 0 附近密、两端稀。在 128,000 个正态分布的权重上(每 64 个一块),NF4 的均方误差是均匀 int4 的 72%。

7.4 双重量化与分页优化器

每 64 个权重存一个 fp32 缩放因子,平均每个参数多 32/64=0.532/64 = 0.5 比特,对一个 4 比特的格式来说是 12.5% 的额外开销。双重量化把这些缩放因子再量化成 8 位(每 256 个一块,再存一个 fp32 的二级缩放因子):

864+3264×256≈0.127 比特/参数\frac{8}{64} + \frac{32}{64 \times 256} \approx 0.127 \text{ 比特/参数}

每个参数省下约 0.373 比特,对 650 亿参数的模型约 3 GB。代码 04 演示 4 中,量化误差几乎不变(均方误差从 3.4059×10−63.4059\times10^{-6} 变成 3.4075×10−63.4075\times10^{-6})。

分页优化器:利用 GPU 和 CPU 内存之间的统一内存,显存不够时把优化器状态临时换出到内存(类似操作系统的虚拟内存分页),避免训练中遇到一条特别长的序列时显存溢出。

7.5 显存账本

代码 04 演示 5(只算模型状态,不含激活值;LoRA r=64r = 64,加在所有线性层上,即 QLoRA 论文的设置):

模型LoRA 参数推理(bf16)全量微调LoRAQLoRA
7B160M(2.4%)13 GB107 GB16 GB6 GB
13B250M(1.9%)26 GB208 GB30 GB11 GB
33B488M(1.5%)65 GB520 GB73 GB25 GB
65B800M(1.2%)130 GB1043 GB143 GB46 GB

65B 的 QLoRA 拆开看:4 位基座(含缩放因子)33.6 GB,加上 LoRA 参数的训练状态 12.8 GB。再用梯度检查点和分页优化器处理激活值,一张 48 GB 的显卡就能微调 650 亿参数的模型,这正是 QLoRA 论文的核心结论。他们用这种方法训练的 Guanaco 模型,在当时的 Vicuna 基准上达到了 ChatGPT 水平的 99.3%(由 GPT-4 评判,第 13 讲会讨论这种评估的偏差)。

注意:QLoRA 省显存、不省计算。前向计算前要把 4 位权重反量化回 bf16 再做矩阵乘法,所以它甚至比 16 位的 LoRA 慢一些。

7.6 量化对一个真实模型的影响

代码 04 演示 6 把第 7 讲训练好的迷你 GPT 的所有二维权重矩阵(112,512 个数)量化再反量化:

格式测试集困惑度主谓一致(2 个修饰语)
不量化(fp32)4.272100.0%
bf164.272100.0%
int8,整个矩阵一个缩放因子4.273100.0%
int4,整个矩阵一个缩放因子4.304100.0%
int4,每 64 个一块4.289100.0%
NF4,每 64 个一块4.295100.0%
int2(3 个值),每 64 个一块14.18083.2%

bf16 和 int8 几乎无损;4 位有一点损失,但仍然很小;2 位就明显坏掉了。两点要诚实地说明:

  • 在这个小模型上,NF4 并不比分块 int4 好。NF4 假设权重服从正态分布,这里的权重(尤其是词嵌入)不完全是,而且两者的差别本身就很小。
  • 小模型没有大模型那样的极端离群值,所以”整个矩阵一个缩放因子”也不算太糟。在几十亿参数以上的模型上,它会差得多。

量化本身是一个很大的话题(GPTQ、AWQ 等训练后量化方法、量化感知训练、KV 缓存量化……),系统层面的细节见 CS336 的相关讲次。本讲只讲 QLoRA 用到的部分。


8. 怎么选:提示、PEFT 还是全量微调?

情况推荐理由
只能通过 API 使用模型提示(+ 检索,第 12 讲)拿不到权重
快速做原型、任务经常变提示零训练成本,改提示即可
有几百到几万条标注数据,任务固定LoRA / QLoRA便宜、稳定、接近全量微调
要服务很多任务或很多用户的定制版本LoRA一个基座 + 很多小补丁
显存很有限(单张消费级显卡)QLoRA4 位基座
要教模型大量新知识或新语言,数据和算力都充足全量微调(或继续预训练)低秩假设可能不成立(6.5 节)
特别在意保留原有能力LoRA原模型不变,遗忘更少

实践中这些方法经常组合使用:用 LoRA 做指令微调或领域适配,再在使用时配合精心设计的提示和检索。

还有一点:提示和微调并不是”二选一”的关系,而是同一件事的两种形式。少样本提示可以看作”在前向传播里做的、只持续一次的微调”(第 3.3 节);而微调可以看作”把很多提示的效果永久地写进参数里”。


9. 本讲小结

  1. 三条路:提示(不改参数)、参数高效微调(改很少的参数)、全量微调(改所有参数),代价和效果上限依次增加。
  2. 提示:零样本、少样本;结构是任务描述 + 示例 + 查询。很脆弱:例子的选择、顺序、格式,多数标签 / 近因 / 常见词三种偏差。上下文校准用”没有内容的输入”估计偏差再减掉,对常数偏差有效。提示工程的核心是把话说清楚,并用评估集迭代。
  3. 上下文学习的三种视角:示例主要传递格式、输入分布和标签空间;贝叶斯视角是”用例子定位任务”,统一了”随机标签影响不大”和”大模型会跟着反转标签走”;机制视角有归纳头,以及”一层线性注意力恰好等于一步梯度下降”的构造。
  4. 全量微调的代价:每个参数约 16 字节的显存;每个任务一份完整模型;灾难性遗忘。
  5. PEFT:冻结原模型,只训练很少的参数。适配器(串联瓶颈,零初始化,有推理延迟)、提示微调 / 前缀微调(连续的提示,大模型上才好用)、BitFit(只训练偏置)。
  6. LoRA:h=W0x+αrBAxh = W_0x + \frac{\alpha}{r}BAx;来源于”内在维度很低”的发现;B=0B = 0、AA 随机;缩放 α/r\alpha/r;训练完可合并,推理零开销;低秩假设成立时效果接近全量微调,数据少时还有正则化作用,需要大量新知识时不够。
  7. 量化与 QLoRA:absmax 量化怕离群值,分块解决;NF4 按正态分位数放置量化值;双重量化把缩放因子的开销从 0.5 降到约 0.127 比特/参数;4 位基座 + 16 位 LoRA,让 65B 模型在一张 48 GB 显卡上微调。

10. 与作业的关系

  • 期末项目:默认项目要在下游任务上微调 GPT-2(第 20 讲),LoRA 是一个很自然的扩展方向:比较全量微调和 LoRA 的效果、显存和速度,正是一个好的项目实验。很多自选项目受限于算力,也会用 LoRA / QLoRA 微调开源模型。
  • Winter 2026 的作业 4(大语言模型的基准测试与评估):评估时用到的少样本提示,本讲第 2 节讲的敏感性问题(例子顺序、格式)会直接影响你测出的分数,第 13 讲会继续讨论。
  • 本讲代码只用到 PyTorch;实际项目中常用 Hugging Face 的 peft 库(LoRA、提示微调、适配器等)和 bitsandbytes(4 位、8 位量化),它们的接口和我们从零实现的版本概念上一一对应。

作业的具体题目以当年官方发布的版本为准。


11. 练习

练习 1(LoRA 参数量)★:一个 4096×110084096 \times 11008 的前馈网络矩阵,r=16r = 16。(a) LoRA 有多少参数?占原矩阵的百分之几?(b) 合并之后,推理时这一层的计算量和原来相比如何?

参考答案

(a) 16×(4096+11008)=241,66416 \times (4096 + 11008) = 241{,}664 个参数;原矩阵 4096×11008=45,088,7684096 \times 11008 = 45{,}088{,}768 个,占约 0.54%。 (b) 完全一样:合并后就是一个普通的 4096×110084096 \times 11008 矩阵。

练习 2(初始化)★:(a) 写出 ∂L/∂A\partial L/\partial A 和 ∂L/∂B\partial L/\partial B(忽略缩放)。(b) 如果反过来,A=0A = 0、BB 随机,能训练吗?训练开始时 ΔW\Delta W 是多少?(c) 如果 AA、BB 都随机(都不是 0),会有什么问题?

参考答案

(a) 设 g=∂L/∂hg = \partial L/\partial h,∂L/∂B=g (Ax)⊤\partial L/\partial B = g\,(Ax)^\top,∂L/∂A=B⊤g x⊤\partial L/\partial A = B^\top g\,x^\top。 (b) 能。开始时 ΔW=BA=0\Delta W = BA = 0;∂L/∂A=B⊤gx⊤≠0\partial L/\partial A = B^\top g x^\top \neq 0,所以 AA 先动起来,之后 BB 的梯度也不再是 0。只是惯例是 B=0B = 0。 (c) 训练开始时 ΔW=BA≠0\Delta W = BA \neq 0,模型被随机扰动了,不再等于预训练模型。微调就不是从预训练模型出发,而是从一个”被弄坏了一点”的模型出发,需要先花力气把扰动修回来。

练习 3(显存)★★:一个 130 亿参数的模型。(a) 按每参数 16 字节,全量微调要多少显存(不含激活值)?(b) LoRA(bf16 基座)呢?假设 LoRA 有 2.5 亿个参数,每个按 16 字节算。(c) QLoRA 呢(按每参数 0.516 字节)?(d) 一张 24 GB 的消费级显卡能做哪些?

参考答案

(a) 13×109×16=20813 \times 10^9 \times 16 = 208 GB。 (b) 26+0.25×16=3026 + 0.25 \times 16 = 30 GB。 (c) 13×0.516+4≈10.713 \times 0.516 + 4 \approx 10.7 GB。 (d) 只有 QLoRA 放得下(还要给激活值留空间)。这就是 QLoRA 让个人也能微调大模型的原因。

练习 4(贝叶斯视角)★★:只有两个任务,先验 P(T1)=0.9P(T_1) = 0.9、P(T2)=0.1P(T_2) = 0.1,ε=0.1\varepsilon = 0.1。给了 2 个例子,它们都和 T2T_2 一致、都和 T1T_1 矛盾。(a) 计算后验 P(T2∣例子)P(T_2 \mid \text{例子})。(b) 如果 ε=0.4\varepsilon = 0.4 呢?(c) 解释这和”大模型更会跟着反转标签走”的关系。

参考答案

(a) 似然:T1T_1 为 0.12=0.010.1^2 = 0.01,T2T_2 为 0.92=0.810.9^2 = 0.81。后验 ∝(0.9×0.01,0.1×0.81)=(0.009,0.081)\propto (0.9 \times 0.01, 0.1 \times 0.81) = (0.009, 0.081),P(T2∣⋅)=0.081/0.090=0.9P(T_2 \mid \cdot) = 0.081/0.090 = 0.9。两个例子就把先验 0.1 翻成了 0.9。 (b) 似然:0.42=0.160.4^2 = 0.16 和 0.62=0.360.6^2 = 0.36。后验 ∝(0.144,0.036)\propto (0.144, 0.036),P(T2∣⋅)=0.2P(T_2 \mid \cdot) = 0.2,模型仍然倾向于 T1T_1。 (c) 越”信任”例子(ε\varepsilon 越小),越少的例子就能压过先验。大模型跟着反转标签走,相当于它的有效 ε\varepsilon 更小。这个模型只描述了现象,没有解释为什么大模型更信任例子。

练习 5(提示微调 vs 前缀微调)★★:一个 24 层、D=1024D = 1024 的模型。(a) 提示微调用 P=20P = 20 个软提示,有多少参数?(b) 前缀微调在每一层的键和值前都加 20 个向量,有多少参数?(c) 为什么前缀微调在中等大小的模型上通常比提示微调效果好?

参考答案

(a) 20×1024=20,48020 \times 1024 = 20{,}480。 (b) 2×20×1024×24=983,0402 \times 20 \times 1024 \times 24 = 983{,}040(论文里还会用一个 MLP 对前缀重参数化来稳定训练,训练完丢掉)。 (c) 提示微调只能在输入层施加影响,后面每一层都要靠模型自己”传递”这个影响;前缀微调在每一层都直接提供可调的键和值,能更直接地改变每一层的注意力,表达能力更强。模型足够大时,输入层的影响已经足够,两者差距缩小。

练习 6(线性注意力 = 梯度下降)★★:在第 3.3 节的构造中,我们让查询词元不作为键被关注(只有 NN 个例子作为键)。(a) 如果查询词元也作为键,第一层的结果会变吗?(b) 第二层呢?

参考答案

(a) 不会。第一层时查询词元的最后一维 rq=0r_q = 0,它作为键时贡献的值向量是 (0,…,0,−η⋅0)=0(0, \dots, 0, -\eta \cdot 0) = 0。 (b) 会。第一层之后 rq=−y^q≠0r_q = -\hat{y}_q \neq 0,第二层时它作为键会贡献 −η rq (xq⊤xj)-\eta\, r_q\,(x_q^\top x_j),相当于把查询点也当成了一个”标签为 0 的训练样本”,偏离了梯度下降。所以构造里要用掩码把它排除(代码里的 K[:n_ctx])。

练习 7(量化)★:对向量 x=(0.1,−0.2,0.05,2.0)x = (0.1, -0.2, 0.05, 2.0) 做 int8 absmax 量化(Q=127Q = 127)。(a) 写出 qq 和 x^\hat{x}。(b) 前三个数的相对误差大约多大?(c) 如果分成两块 (0.1,−0.2)(0.1, -0.2) 和 (0.05,2.0)(0.05, 2.0) 分别量化呢?

参考答案

(a) 缩放因子 2.0。q=round(x/2×127)=(6,−13,3,127)q = \text{round}(x / 2 \times 127) = (6, -13, 3, 127);x^=q×2/127=(0.0945,−0.2047,0.0472,2.0)\hat{x} = q \times 2/127 = (0.0945, -0.2047, 0.0472, 2.0)。 (b) 分别约 5.5%、2.4%、5.5%。 (c) 第一块缩放因子 0.2:q=(64,−127)q = (64, -127),x^=(0.1008,−0.2)\hat{x} = (0.1008, -0.2),误差不到 1%;第二块里的 0.05 仍然和 2.0 在一起,误差不变。分块只能隔离”不在同一块里”的离群值。

练习 8(上下文校准)★:模型在 “N/A” 上给出 p(pos)=0.7p(\text{pos}) = 0.7,在某条评论上给出 p(pos)=0.6p(\text{pos}) = 0.6。校准之后预测是什么?

参考答案

按 p^(y)∝p(y∣x)/p(y∣N/A)\hat{p}(y) \propto p(y \mid x)/p(y \mid \text{N/A}):正面 0.6/0.7≈0.8570.6/0.7 \approx 0.857,负面 0.4/0.3≈1.3330.4/0.3 \approx 1.333,归一化后 p^(pos)≈0.39\hat{p}(\text{pos}) \approx 0.39,预测负面。模型本来就有 0.7 的偏向,这条评论只给了 0.6,说明它实际上把预测往负面拉了。

练习 9(怎么选):为下面三个场景各选一种做法并说明理由:(a) 一个创业公司要给 500 个企业客户各做一个定制的客服模型,每个客户有几千条历史对话;(b) 一个研究者想让一个 7B 的英文模型学会斯瓦希里语,有 100 亿词的斯瓦希里语语料和一个 GPU 集群;(c) 一个学生想在自己的笔记本电脑(16 GB 内存,没有独立显卡)上尝试不同的提示做情感分析。

参考答案

(a) LoRA(或 QLoRA):每个客户一个几十 MB 的补丁,服务时一个基座 + 按请求切换补丁。 (b) 全量的继续预训练:要学一门新语言,需要的变化不是低秩的(6.5 节),数据和算力也充足;词表可能也要扩展(第 16 讲)。 (c) 提示(零样本 / 少样本),用一个能在 CPU 上跑的小模型或 API;没有训练的条件,也没必要训练。

练习 10(动手):

(a) 运行代码 03,把结果填进第 6.7 节的表格。和预期一致吗?哪种 PEFT 方法在 100 条数据时最好? (b) 在代码 02 的演示 3 中,把第三行的噪声从 1.0 改成 0.1,LoRA 的正则化优势还在吗?为什么? (c) 在 peft.py 里实现前缀微调:在每一层的注意力里给键和值前面拼接可训练的向量(提示:需要修改 MultiHeadSelfAttention.forward,或者写一个包装类),在代码 03 里和提示微调比较。 (d) 在代码 03 里给 LoRA 的 AA、BB 用不同的学习率(BB 的学习率是 AA 的 4 倍或 16 倍,即 LoRA+),结果有变化吗? (e) 在代码 01 的演示 1 里加一个”语义无关标签”的实验:把标签 1/0 换成两个和情感无关的符号(模型不知道哪个代表褒义)。在贝叶斯模型里应该怎么表示这种情况?


12. 延伸阅读

  • Brown et al.,Language Models are Few-Shot Learners(2020):GPT-3 与少样本提示。
  • Zhao et al.,Calibrate Before Use: Improving Few-Shot Performance of Language Models(2021);Lu et al.,Fantastically Ordered Prompts and Where to Find Them(2022);Sclar et al.,Quantifying Language Models’ Sensitivity to Spurious Features in Prompt Design(2023)。
  • Min et al.,Rethinking the Role of Demonstrations: What Makes In-Context Learning Work?(2022);Wei et al.,Larger Language Models Do In-Context Learning Differently(2023)。
  • Xie et al.,An Explanation of In-context Learning as Implicit Bayesian Inference(2021)。
  • Olsson et al.,In-context Learning and Induction Heads(2022)。
  • von Oswald et al.,Transformers Learn In-Context by Gradient Descent(2023);Garg et al.,What Can Transformers Learn In-Context?(2022);Akyürek et al.,What Learning Algorithm Is In-Context Learning?(2022)。
  • Houlsby et al.,Parameter-Efficient Transfer Learning for NLP(2019):适配器。
  • Li & Liang,Prefix-Tuning(2021);Lester et al.,The Power of Scale for Parameter-Efficient Prompt Tuning(2021)。
  • Ben Zaken et al.,BitFit(2021)。
  • Aghajanyan et al.,Intrinsic Dimensionality Explains the Effectiveness of Language Model Fine-Tuning(2020);Li et al.,Measuring the Intrinsic Dimension of Objective Landscapes(2018)。
  • Hu et al.,LoRA: Low-Rank Adaptation of Large Language Models(2021)。
  • Dettmers et al.,QLoRA: Efficient Finetuning of Quantized LLMs(2023);LLM.int8()(2022)。
  • Biderman et al.,LoRA Learns Less and Forgets Less(2024)。
  • Sheng et al.,S-LoRA: Serving Thousands of Concurrent LoRA Adapters(2023)。
  • Anthropic、OpenAI 等的官方提示工程指南:实践经验的系统总结。

13. 下一讲预告

到目前为止,模型的知识都”长”在参数里:预训练时学到什么就知道什么,学错了就一直错,训练结束之后的事一概不知。问它一个预训练语料里没有的问题,它可能一本正经地编造一个答案。

下一讲(智能体、工具使用与检索增强生成)换一个思路:不把所有知识塞进参数,而是让模型去查。

  • 问答:从抽取式问答(SQuAD)到开放域问答;
  • 检索:稀疏检索(TF-IDF、BM25)与稠密检索(双塔编码器),各自擅长什么;
  • 检索增强生成(RAG):先检索、再阅读、再回答,并给出出处;
  • 工具使用与智能体:让模型调用搜索、计算器、代码解释器,在”思考—行动—观察”的循环中一步步完成任务,以及这种循环会怎样失败。
← 返回课程目录