Lecture 6 · 适配:提示、探针、微调与参数高效方法 · Adaptation: Prompting, Probing, Fine-tuning & Parameter-Efficient Methods

Advances in Foundation Models · Lecture 06。个人学习讲义与配套实验,按主题整理并持续修订。

课程源码 ZIP ↓

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

本篇目录

对应 Maps to:Stanford CS324 Winter 2023 Advances in Foundation Models,Syllabus 主题 “Adapting FMs to New Tasks and Data Domains”。这个主题在 W23 日历上没有单独的讲座,也没有公开的 slides;它的阅读清单是:CS324 Winter 2022 课程笔记 Adaptation、Li & Liang Prefix-Tuning(2021)、Lester et al. The Power of Scale for Parameter-Efficient Prompt Tuning(2021)、Hu et al. LoRA(2021)、Mitchell et al. Fast Model Editing at Scale(MEND,2021);清单里同一主题下还有 T0、FLAN、InstructGPT 三篇,它们讲的是”为了听懂指令”而做的适配,放在第 7 讲。领域适配部分对应 Syllabus 另一个主题下列出的 Stanford CRFM 博客 PubMedGPT(后改名 BioMedLM,2022.12),以及 Gururangan et al. Don’t Stop Pretraining(2020)。 依据说明:本讲的主线沿着 W22 笔记 Adaptation 的结构展开(为什么要适配 → 探针 → 微调 → 轻量微调),在每一步补上阅读清单里的原论文;另外读了 Houlsby et al. 2019(adapter)、Zaken et al. 2021(BitFit)、Aghajanyan et al. 2020(内在维度)、Kumar et al. 2022(LP-FT)、Meng et al. 2022(ROME)作为补充。文献里的数字都是我从论文或笔记里读到的;§8”2023 年之后”是我补充的。 配套代码 Code:code/lecture06/(PyTorch;5 个脚本 + 1 个模块。代码 01 约 85 秒、02 约 70 秒、03 约 50 秒、04 第一次约 70 秒(之后读缓存,约 35 秒)、05 约 10 秒) 预计阅读时间:3.5 小时(含运行代码)


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

0.1 学习目标 Learning goals

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

  1. 说出下游任务和”预测下一个词”可能在哪三个方面不同(格式、主题、时间),并据此判断一个任务需要哪一类适配。
  2. 把提示、探针、全量微调、轻量微调写进同一个公式:γadapt=arg⁡min⁡γ∈Γ1n∑iℓtask(γ,θLM,xi,yi)\gamma_{\text{adapt}} = \arg\min_{\gamma \in \Gamma} \frac{1}{n}\sum_i \ell_{\text{task}}(\gamma, \theta_{\text{LM}}, x_i, y_i),区别只在于 Γ\Gamma——允许改动什么。
  3. 解释线性探针能读出什么、读不出什么,“读在哪里”为什么重要,以及 LP-FT(先探针再微调)为什么能同时提高同分布和分布外的表现。
  4. 说清全量微调贵在哪里(每个任务一整份模型、训练内存约 4 倍于模型),以及它在标签很少时的两个毛病:背答案和灾难性遗忘。
  5. 描述四种参数高效微调——adapter、prefix tuning、prompt tuning、BitFit——各改了模型的哪一部分;说出 Lester et al. “规模越大,prompt tuning 越接近全量微调”的结论。
  6. 从零写出 LoRA:W+αrBAW + \frac{\alpha}{r} BA、为什么 BB 初始化为 0、缩放 α/r\alpha/r 起什么作用、怎样合并进 WW;用”内在维度”解释为什么低秩就够。
  7. 解释领域适配的两条路:在领域文本上继续预训练(DAPT)和从零训练领域模型(PubMedGPT),以及继续预训练带来的遗忘和”回放”。
  8. 说出模型编辑的三个要求(可靠性、泛化、局部性),解释为什么”在一句话上微调”不是好的编辑,ROME 的”定位 + 秩一编辑”怎样做,以及”涟漪效应”为什么让编辑很难。
  9. 拿到一个实际问题时,能根据”手里有什么”(只有 API 还是有权重、有多少标签、要服务多少任务、有多少算力)选择适配方法。

0.2 先修与本讲范围 Prerequisites & scope

默认你已经会 Assumed本讲只给直觉,后面几讲再展开 Previewed only
第 1 讲:语言模型、base model、提示与 few-shot、探针(§3.4)、提示格式的脆弱(§4.2)指令微调、RLHF、对齐——“为了听懂指令”的适配(第 7 讲)
第 3 讲:Transformer 的内部(嵌入、注意力的 q / k / v、MLP、残差、LayerNorm);“评测协议必须相同”(§5.2)GPU 显存、混合精度、量化的细节(第 8–9 讲)
第 5 讲:上下文学习、知识的存储与提取(§6.2)领域基础模型的全景:代码、生物医学、法律(第 15 讲)
线性代数:矩阵的秩、奇异值分解(SVD)的含义机制可解释性的电路分析(第 12 讲)

本讲回答一个问题:一个预训练好的基础模型,怎样变成你要的那个模型?——从”什么都不改”(提示)到”只训练一个头”(探针),到”只改很少几个参数”(轻量微调),到”全部都改”(全量微调),再到”在新领域的文本上继续预训练”和”只改一个事实”(模型编辑)。

0.3 本讲地图 Map

§1  为什么要适配:前几讲留下的问题;格式 / 主题 / 时间三种差异;一个统一的写法;先问四个问题
§2  提示与探针:提示受格式和上下文限制;线性探针读在哪里;微调会扭曲特征(LP-FT)     ← 代码 01、02
§3  全量微调:做法;为什么贵;样本少时背答案;灾难性遗忘                              ← 代码 02、04
§4  参数高效微调:adapter;prefix tuning;prompt tuning 与规模;BitFit;               ← 代码 01、03
    LoRA;为什么低秩就够(内在维度);一个基座多个插件;在同一个任务上排一排
§5  领域适配:Don't Stop Pretraining;PubMedGPT;迷你实验:新药名的诊所记录            ← 代码 04
§6  模型编辑:三个要求;微调一句话为什么不行;ROME;MEND 与批量编辑;涟漪效应           ← 代码 05
§7  怎样比较、怎样选:比较表、决策表、哪些结论能推广
§8  2023 年之后
§9–13  小结、常见误区、练习、延伸阅读、下一讲预告

0.4 记号 Notation(只列本讲新增的,其余见第 1 讲 §0.4)

符号含义例子
θLM\theta_{\text{LM}}预训练好的语言模型参数第 1 讲的 base model,109,056 个参数
γ\gamma,Γ\Gamma适配时要学的参数,以及它允许取值的范围(“改动什么”)探针:一个线性头;LoRA:所有 AA、BB
kk标注样本数代码 01:kk = 4、16、64
WW,ΔW\Delta W一个权重矩阵,以及适配对它的改动量W∈Rdout×dinW \in \mathbb{R}^{d_{\text{out}} \times d_{\text{in}}}
AA,BB,rr,α\alphaLoRA:ΔW=αrBA\Delta W = \frac{\alpha}{r} BA,A∈Rr×dinA \in \mathbb{R}^{r \times d_{\text{in}}},B∈Rdout×rB \in \mathbb{R}^{d_{\text{out}} \times r};rr 是秩,α\alpha 是缩放r=1r = 1 时每个矩阵只多 din+doutd_{\text{in}} + d_{\text{out}} 个参数
dd(内在维度)只允许参数在一个 dd 维子空间里移动:θ=θ0+Pz\theta = \theta_0 + Pz,z∈Rdz \in \mathbb{R}^d代码 03 演示 4
ID / OODin-distribution / out-of-distribution:同分布 / 分布外代码 01:测试评论的形容词在标注数据里出现过 / 从没出现过
LP / FT / LP-FT线性探针 / 全量微调 / 先探针再微调Kumar et al. 2022
DAPT / TAPTdomain- / task-adaptive pretraining:在领域文本 / 任务的无标签文本上继续预训练Gururangan et al. 2020
k∗k^*,v∗v^*ROME:一个 MLP 输出矩阵的”键”(它的输入)和”值”(希望它输出的东西)代码 05

0.5 关于数字 · About the numbers

讲义里所有实验数字都来自 code/lecture06/ 的实际输出(固定种子、PyTorch 单线程,同一台机器上重复运行逐字相同,只有计时不同)。所有实验都从第 1 讲的 base model 出发——每次适配前先 copy.deepcopy 一份,第 1 讲的缓存永远不会被改动。模型只有约 11 万个参数,所以它演示的是机制,不是量级:哪些结论能推广到真实的大模型、哪些不能,§7.3 专门讨论;每个结论旁边也给出论文里真实模型的数字(注明出处)。这一讲有好几处结果和我写代码前的预想不一样——最重要的是”没有哪种方法总是最好”(§4.9)和”参数高效 ≠ 不遗忘”(§3.4)——讲义按实际结果来写,完整清单在代码 README 的”关于实验结果”里。

一个贯穿全讲的约定(第 3 讲的教训):同一张表里的所有方法,用同一个打分函数。默认是”受限选择”:把每个候选答案接在提示后面,比较模型给的概率,选最大的;需要时再用”自由生成 + 完全匹配”做对照(§2.1)。


1. 为什么要适配 · Why adapt?

1.1 从前几讲留下的问题说起

前几讲反复遇到同一种情况:知识在模型里,但取不出来,或者取出来的样子不对。

  • 第 1 讲 §4.2:30 个只在叙述句里出现过的人,用 “batul lives in” 问城市答对 0.93;用问答格式问 0.47;用训练时没见过的 “question: … answer:” 问,0.00——先给 2 个示例也还是 0.00。
  • 第 5 讲 §6.2:Allen-Zhu & Li 的实验里,模型能把人物介绍背得一字不差,换成问答格式却问不出来;§6.3 的反转诅咒更极端。
  • 第 1 讲 §3.4 换了一种用法:不让模型”说”,而是在它的内部表示上训练一个线性探针。region 这个词从没出现在语料里,探针用 32 个标签读到了 0.739。

提示是最轻的适配:一个参数都不改。可它有两个硬限制。第一,格式:模型只会它在预训练里见过的格式(第 1 讲的小模型连”从示例里学新格式”都不会)。第二,上下文长度:示例要放进提示里。代码 01 演示 1 量了一下:一条评论连同答案约 70 个字符,放一个示例再接上问题要 130 个字符,已经超过了 base model 96 个字符的上下文——一个示例都放不下。GPT-3 的 2048 个 token 宽裕得多,但同样有上限;W22 笔记把这一点列为”为什么还需要别的适配方式”的理由之一。

所以,当提示不够用时,我们要动模型本身——动多少、动哪里,就是这一讲的内容。

1.2 下游任务和语言建模哪里不一样

W22 笔记 Adaptation 把下游任务和”在 The Pile 上预测下一个词”的差异分成三类:

差异例子迷你世界里的对应
格式(formatting)自然语言推理(NLI)要读两句话、输出一个二值标签,既不是”续写”,也不是”填空”代码 01、02:同样的评论 / 同样的事实,换一个没见过的格式、换几个没见过的标签词
主题偏移(topic shift)任务集中在一个专门的领域,例如病历代码 04:诊所记录,里面全是没见过的药名
时间偏移(temporal shift)任务需要预训练时还不存在的知识(笔记的例子:GPT-3 训练完时拜登还没当选总统),或者不公开的知识代码 05:某个人搬家了,模型记住的城市过时了

这三类差异对应本讲的三大块:格式差异主要靠微调类方法(§2–4),主题偏移靠继续预训练(§5),时间偏移里”改一两个事实”的情形靠模型编辑(§6)。

1.3 一个统一的写法

W22 笔记给了一个把所有适配方法装进去的框架。预训练好的参数是 θLM\theta_{\text{LM}};下游任务有 nn 个样本 (xi,yi)(x_i, y_i),来自任务分布 PtaskP_{\text{task}};适配就是在某个参数族 Γ\Gamma 里找一组参数 γ\gamma,让任务损失最小:

γadapt=arg⁡min⁡γ∈Γ1n∑i=1nℓtask(γ,θLM,xi,yi).\gamma_{\text{adapt}} = \arg\min_{\gamma \in \Gamma} \frac{1}{n} \sum_{i=1}^{n} \ell_{\text{task}}(\gamma, \theta_{\text{LM}}, x_i, y_i).

各种方法的区别只在 Γ\Gamma——允许改动什么:

不改任何参数        只训练一个头        只训练很少的参数             全部参数都训练
(提示 / ICL)  →  (探针 probing)  →  (adapter、prefix、prompt、  →  (全量微调)
                                         BitFit、LoRA……)
 0 个参数           几百个参数          模型的 0.01%–几%              100%
 只需要 API          需要读表示          需要权重和梯度                 需要权重、梯度、优化器状态

越往右,表达能力越强,代价也越大:要存的东西更多、要的标签更多、训练更贵、越容易背答案和遗忘。这一讲的大部分内容,就是在这条轴上找合适的位置。

1.4 选哪一种:先问四个问题

动手之前,先问自己四个问题(§7.2 会把它们做成一张决策表):

  1. 我手里有什么? 只有 API(例如当年的 GPT-3)——只能提示(以及服务商提供的微调接口);有权重——什么都能做。
  2. 有多少标签? 几个到几十个,提示或者参数很少的方法;几千个以上,全量微调的优势才显出来。
  3. 要服务多少个任务(或用户)? 一个任务,存一份完整模型无所谓;一千个用户各要一个定制版,每份都存完整模型就不现实。
  4. 有多少算力和显存? 全量微调要放下模型、梯度和优化器状态(§3.2);只训练小插件,显存几乎只要”模型本身”。

2. 提示与探针 · Prompting and probing, revisited

2.1 提示:不改参数,但受格式和上下文限制

本讲的主要任务(代码 01、03、04 共用)是一个刻意设计的”格式差异”:同一条评论,换一个 base model 从没见过的格式,答案也换成语料里从没出现过的三个词:

语料里的样子:  review: the soup was tasty but the host was rude. rating: ok.
新任务:        opinion: the soup was tasty but the host was rude. verdict: mixed.
                (good → loved,ok → mixed,bad → hated)

评分规则(两个形容词决定好 / 一般 / 差)base model 早就会了:用原格式问,受限选择 1.000。新任务不需要任何新知识,只需要”新格式 + 新标签词”。代码 01 演示 1 先看不训练时的起点:

同分布测试(300 条)
瞎猜(3 选 1)0.333
多数类(永远答 mixed)0.513
base model,受限选择0.253
base model,自由生成 + 完全匹配0.000
参照:原格式 + 原标签词1.000

两种协议给出不同的数字,结论却一样:不训练不行。自由生成时,base model 最常写出的是 ' brena.'(一个城市名)、' ook.'、' goo.'(像是原来的评分词 ok、good 的变形)——和第 1 讲里 “question: … answer:” 格式被接上评论碎片是同一回事:模型认不出这是什么任务,就按它最熟悉的统计往下写。

这里值得停一下想想 GPT-3 为什么能靠提示做那么多任务:它在预训练里见过几乎所有常见的格式(问答、列表、翻译对照……),任务”长得像”它见过的东西;而且它大到足以从几个示例里推断格式(第 5 讲)。小模型两样都没有。提示能做多少,取决于模型见过多少、模型有多大——这也是 §4.4 里 prompt tuning 随规模变好的同一个道理。

2.2 线性探针:冻结模型,只训练一个头

探针(probing)是 Γ\Gamma 最小的训练类方法:冻结整个模型,在某个位置的表示上训练一个很小的分类器(通常是线性的)。W22 笔记强调它主要是一个分析工具——“如果一个线性探针能从表示里读出词性,就说明表示里’存着’词性”——同时也是一种便宜的适配方式。

从哪里读? 编码器模型(BERT)把 LL 个 token 映射成 LL 个向量,要得到一个向量来分类,笔记介绍了两种做法:在开头放一个特殊的 [CLS] token、读它的向量;或者把 LL 个向量取平均。笔记顺带指出,取平均并不让模型对词序不敏感——每个向量本身已经是带位置的上下文表示。对 decoder-only 的模型,自然的选择是读最后一个位置,那里看到了整个输入。前几讲已经反复看到”读在哪里”有多重要:

  • 第 1 讲 §3.4:region 探针读在 “kari lives in ” 的末尾 0.739,读在名字的最后一个字符只有 0.513——那个位置的模型还不需要”想起”城市;食物好坏的探针,读在 “rating: ” 处 0.941,各位置取平均只有 0.672。
  • 第 3 讲 §5.4:因果模型的表示取平均会被前面”还没看到形容词”的位置冲淡(0.838),读最后一个位置 0.936。

代码 01 的主表里也有一行探针:读在 “verdict:” 处,kk = 4 / 16 / 64 时 0.390 / 0.617 / 0.852——只训练 195 个参数,标签多了以后也能做到八成多,但比后面的微调类方法差。原因在 代码 02 演示 2 里看得更清楚:在人物问答的新格式上,探针(读在 “answer:” 处)只有 0.156——模型在这个它不认识的格式末尾,根本没有去”回忆”城市,表示里就没有可读的东西;而且 15 个标签连 8 个城市都覆盖不全,探针从没见过的类别它几乎不可能预测出来。探针只能读出已经在那里的信息;格式不对时,信息不会出现在你读的位置上。

2.3 微调会扭曲好的特征:LP-FT

如果探针读得出来,再把整个模型一起微调,是不是一定更好?Kumar et al. 2022(Fine-Tuning can Distort Pretrained Features and Underperform Out-of-Distribution)的回答是:同分布上通常更好,分布外常常更差。他们在 10 个分布偏移数据集上(BREEDS、DomainNet、CIFAR-10 → STL、ImageNet 的几个变体等)发现,全量微调(FT)平均比线性探针(LP)同分布高 2%、分布外低 7%(平均准确率:同分布 FT 85.1 / LP 82.9;分布外 FT 59.3 / LP 66.2)。

他们的解释叫特征扭曲(feature distortion):分类头是随机初始化的,训练初期梯度很大、方向很”乱”,底层特征会被改来迁就这个乱头——但只在训练数据覆盖到的方向上被改;分布外的输入落在那些没被一起调整的方向上,于是和新的分类头”对不上”。解决办法简单得出奇:先做线性探针,用它初始化分类头,再全量微调(LP-FT)。一开始的头就已经是好的,特征只需要小改。论文报告 LP-FT 比 FT 同分布高 1%、分布外高 10%(平均:同分布 85.7、分布外 68.9)。论文也说明了适用条件:预训练特征要好、分布偏移要够大;偏移很小时(CIFAR-10 → CIFAR-10.1),FT 的分布外表现反而比 LP 好(92.3 vs 82.7)。

代码 02 演示 1 在迷你世界里重做了这个实验。任务是第 1 讲的”评论里的食物好不好”;分布外测试用的食物形容词,在标注数据里一个都没出现过。每格 3 个种子,微调 150 步:

kk微调学习率LP 同分布 / 分布外FT 同分布 / 分布外LP-FT 同分布 / 分布外特征变化 FT特征变化 LP-FT
161e-40.882 / 0.8770.839 / 0.7780.913 / 0.8971.260.51
641e-40.974 / 0.9630.979 / 0.8740.999 / 0.9731.290.42
2561e-40.991 / 0.9421.000 / 0.9171.000 / 0.9851.310.44
2561e-30.991 / 0.9421.000 / 0.7061.000 / 0.8501.360.83

(“特征变化”:微调前后,同一条评论在 “rating: ” 处的表示变了多少,∥f−f0∥/∥f0∥\lVert f - f_0 \rVert / \lVert f_0 \rVert,这里只列同分布输入上的值。)

论文的现象完整地出现了:kk = 64、256 时,FT 在同分布上和 LP 一样好或更好,在分布外上明显更差(0.874 vs 0.963,0.917 vs 0.942);LP-FT 两边都最好。特征变化一栏是原因的直接证据:随机的头让 FT 把表示改得面目全非(相对变化超过 1),LP-FT 只改了 FT 的三分之一到四成。学习率调大 10 倍,两种微调都改得更多,FT 的分布外成绩掉到 0.706。

这个小实验的教训比”LP-FT 更好”更一般:微调改动的不只是任务需要的那部分,还有很多任务数据覆盖不到、却决定分布外表现的东西。改得越少、越有方向,分布外越安全。§3–4 的很多现象都可以从这个角度理解。


3. 全量微调 · Full fine-tuning

3.1 做法

全量微调(full fine-tuning)在 Γ\Gamma 的另一端:从 θLM\theta_{\text{LM}} 出发,所有参数都参加训练,再加上任务需要的输出头(如果任务的输出不是语言模型自己的词表)。W22 笔记列出了几条惯例:预训练时的优化器状态丢掉不用;学习率比预训练至少小一个数量级;训练的步数也短得多。BERT 时代几乎所有任务都这样做,GPT 系列的第一代(2018)也是”预训练 + 在每个任务上微调整个模型”(第 1 讲 §2.6)。

代码里的全量微调和别的方法共用一个训练循环(adapt_lab.py 的 train_on_pairs):只在答案字符上算交叉熵——这就是**监督微调(supervised fine-tuning, SFT)**的最小形式。代码 01 演示 2 在开发集上为它选出的学习率是 3e-4,确实比预训练的 3e-3 小一个数量级;学习率取 3e-3 时开发集只有 0.505(模型被训坏了)。

3.2 为什么贵

全量微调的表达能力最强(W22 笔记:“通常比探针好”),代价也最大:

  • 存储:每个任务一整份模型。GPT-3 有 1750 亿个参数,以半精度存一份约 350 GB(LoRA 论文的数字);100 个任务就是 100 份。
  • 训练内存:用 Adam 训练时,每个参数要同时存它本身、它的梯度、Adam 的两个动量。以单精度算是 4 + 4 + 8 = 16 字节,约是模型本身的 4 倍(还没算激活值)。代码 01 演示 5 把这笔账在玩具上算了一遍:base model 本身 426 KB,全量微调训练时 1704 KB;BitFit、LoRA r = 1 这类方法只要 443–458 KB,几乎就是”模型本身”。真实规模上,LoRA 论文报告 GPT-3 175B 用 Adam 全量微调需要 1.2 TB 显存,换成 LoRA 是 350 GB。
  • 部署:服务 NN 个任务要么放 NN 份模型,要么来回换——都不便宜。

这三点合起来,就是 §4 所有”参数高效”方法的动机。

3.3 样本少时:背答案,而不是暴露知识

全量微调还有一个不那么显眼的问题:标签很少时,它有足够的参数去把标签背下来。代码 02 演示 2 用第 1 讲里得 0.00 的人物问答新格式来看这件事:

question: where does kari live? answer: varno.

从”有问答文本的 30 人”里随机拿 15 人当训练例子,测另外的人——答案只能来自预训练时学到的知识。三组测试分别是:同类新人(有问答文本、但不在训练例子里的 15 人)、只在叙述句里出现过的 30 人、以及同类新人的职业(训练只教了问城市)。每格是 3 种划分的平均,8 选 1,瞎猜 0.125:

训练集同类新人叙述句里的人同类·问职业
参照:base model 用熟悉的格式问—0.9780.9331.000
不训练:base model,新格式—0.2890.1330.111
线性探针(读 “answer:” 处)—0.1560.167不适用
全量微调,lr 3e-41.0000.2000.2000.067
全量微调,lr 1e-31.0000.2670.2780.289
BitFit,lr 3e-31.0000.6440.4440.222
BitFit,lr 1e-21.0000.5330.4560.222
LoRA r = 1,lr 3e-31.0000.4220.3330.156
LoRA r = 1,lr 1e-21.0000.3330.3220.222

所有方法都把 15 个训练例子全部答对了。区别全在没见过的人身上:全量微调在两种学习率下都停在 0.2–0.3,不比不训练好;训练过程的记录更直白——学习率 3e-4 时,第 50 步训练集已经 0.96,同类新人却从训练前的 0.29 降到 0.18。它有 10 万个参数,可以把”这 15 个名字 → 这几个城市”直接背下来;BitFit 只有 1,472 个偏置,背不下,只能学一个对所有人都适用的变换——也就是把新格式接到模型原有的知识上。

Zaken et al. 2021 提出 BitFit 时的假设正是:“微调主要是在暴露语言建模训练里学到的知识,而不是学新的任务知识。“这个小实验给了它一个具体的含义:限制可训练的参数,逼模型去暴露,而不是去背。(§4.9 会看到,换一个任务,结论就会反过来——那个任务的难点在别处。)

表里还有两个值得记住的数字:

  1. 没有一种方法接近参照线。同类新人用熟悉的格式问是 0.978,最好的适配也只有 0.644;只在叙述句里出现过的人,适配后最多 0.456(参照 0.933)。适配不能取出模型本身取不出的东西——第 5 讲 §6.2 的”存储 ≠ 提取”在这里又出现了一次:这 30 人的知识只能从叙述句的路径取出来,新格式接不到那条路上。
  2. 问职业,所有方法都远低于参照线(0.067–0.289,参照 1.000)。自由生成一栏说明了原因:问 “what does tevan do?”,全量微调答 varno.,BitFit 答 osku.——它们学会的是”answer: 后面写一个城市”,而不是”answer: 后面写问题问的那个属性”。训练数据里只有一种问题,模型就只学这一种问题。想让模型”跟着问题走”,训练数据里就得有很多种问题——这正是第 7 讲指令微调的出发点。

3.4 灾难性遗忘

微调的梯度只关心新任务。模型原来会的东西,只要新任务的数据里没有,就没有任何力量保护它。这叫灾难性遗忘(catastrophic forgetting),是神经网络在顺序学习多个任务时的老问题。

代码 04 演示 1 在评论新任务上(kk = 64)训练 400 步,一边训练一边量”原来的本事”:第 1 讲验证集的 bit/字符、人物事实(“kari lives in” → 城市)、原格式的问答(问职业)和原格式的评论打分。下表取”任务第一次达到 0.95 时”那一刻(所有方法都达标了):

方法达标于验证 bpc人物事实原格式问答原格式打分
base model(不训练)—0.4120.9331.0001.000
全量微调第 50 步1.1190.7670.5670.767
全量微调 + 回放第 50 步0.4490.7500.9331.000
只调最后一个块第 50 步1.1870.5670.5670.780
BitFit第 250 步3.2150.2500.1330.733
adapter(瓶颈 4)第 250 步3.6510.2330.3330.527
LoRA r = 1第 200 步0.8900.7670.6330.947
LoRA r = 8第 100 步0.6440.8670.6670.987

三件事:

  1. 遗忘在达标时就已经发生了。全量微调只训练 50 步,验证 bpc 就从 0.412 涨到 1.119,原格式问答从 1.000 掉到 0.567。再训练到第 400 步,变化反而不大。
  2. 参数高效 ≠ 不遗忘。这是我写代码前没想到的:插件开着的时候,BitFit 和 adapter 的遗忘比全量微调重得多(验证 bpc 3 以上,人物事实掉到两成多)。原因是它们改动的是每个位置都要经过的东西——一个偏置加在所有位置上,一个 adapter 改写每一层的输出;为了让模型在 “verdict:” 后面说出新词,它们把整个模型的计算都推偏了。还要注意一个混杂因素:每种方法的学习率是在开发集上按”任务准确率”选的(代码 01),BitFit 和 adapter 选出的是 1e-2,比全量微调的 3e-4 大 30 多倍,而且它们要训练 250 步才达标(全量微调 50 步)。“大学习率 + 更多步数”本身就会把模型推得更远,所以这张表说明的是”在各自最好的任务设置下”谁遗忘更多,而不是”少改参数这件事本身”会加重遗忘。LoRA 在这里遗忘最轻(r = 8 时人物事实 0.867),但也不是零。Biderman et al. 2024 在 7B 模型上做了系统的比较,结论的标题就是 LoRA Learns Less and Forgets Less:LoRA 在目标领域学得不如全量微调,但更好地保住了领域外的能力。
  3. 回放(replay)几乎消除了遗忘:每一步同时在一小批原来的预训练文本上做语言建模,任务照样第 50 步达标,验证 bpc 只到 0.449(第 400 步 0.416,几乎回到 base),原格式打分保持 1.000。代价是训练时要一直带着原来的数据,计算也多了一倍。

还有一种最朴素的保护:插件可以拔掉。prompt / prefix tuning、不合并的 LoRA、adapter 都不改动原来的权重,拔掉插件,base model 原封不动;同一个基座可以同时服务原来的用途和新任务。全量微调要做到这一点,只能另存一份原模型——又回到了 §3.2 的存储问题。


4. 参数高效微调 · Parameter-efficient fine-tuning

4.1 思路:冻结大部分,只训练”高杠杆”的一小部分

W22 笔记对这一类方法(笔记叫 lightweight fine-tuning,后来通称 PEFT, parameter-efficient fine-tuning)的概括是:冻结语言模型的大部分参数,只优化很少的新增参数(“不到 1%”),并且把它们放在模型里杠杆最大的位置。目标是两头兼顾:接近全量微调的表达能力,又不必为每个任务存一整份模型。

笔记还提醒了一个容易忽略的事实:这些方法的表达能力取决于预训练模型本身。如果预训练模型的权重全是 0,在输入前面加多少软提示都没用——轻量微调能做的,是在一个已经很强的模型上”拨动开关”,而不是从头造能力。下面四种方法(加上 §4.6 的 LoRA)的区别,就在于”开关装在哪里”:

                    ┌──────────── 一个 Transformer 块 ────────────┐
输入 ─→ [prompt tuning:         LN → 注意力(q,k,v)→ 输出投影 ─→ + ─→ LN → MLP ─→ + ─→ …
        在输入前接 m 个            ↑           ↑            ↑                 ↑
        可训练的"软 token"]    [prefix:每层   [LoRA:      [adapter:         [adapter:
                                多 m 个键值]   W + BA]      瓶颈小网络]        瓶颈小网络]
        [BitFit:所有的偏置 b]

4.2 Adapter:在层之间插一个瓶颈

Houlsby et al. 2019 是这条路的起点。在每个 Transformer 块里插入两个小网络——一个在注意力的输出之后,一个在 MLP 的输出之后——每个都是带残差的瓶颈:

Adapter(h)=h+Wup σ(Wdown h),Wdown∈Rm×d,  Wup∈Rd×m,  m≪d.\text{Adapter}(h) = h + W_{\text{up}}\, \sigma(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.

两个设计细节值得注意。残差:adapter 输出的是”在原来的基础上加一点修正”。接近恒等的初始化:权重初始化得很小(代码里 Wup=0W_{\text{up}} = 0),一开始 adapter 什么也不做,模型和原来完全一样;论文发现初始化离恒等函数太远时模型会训不动。结果:在 GLUE 上用 BERT-Large,平均 80.0 vs 全量微调 80.4,差 0.4,每个任务只多 3.6% 的参数;9 个任务合计只需要 1.3 倍于原模型的参数,全量微调要 9 倍。另外 17 个文本分类任务上,adapter 用 1.14% 的参数,平均 73.3 vs 全量微调 73.7。

代价:adapter 串在计算路径上,推理时每层多两次小矩阵乘法,去不掉。

4.3 Prefix tuning:每一层多几个键值

Li & Liang 2021 从提示得到灵感:既然一段合适的文字前缀能”引导”模型,那就直接学一个前缀——但不是学离散的词,而是学连续的向量,并且放在每一层:在每层注意力的 key 和 value 前面接上 mm 个可训练的向量,后面所有位置都能”看到”它们(adapt_lab.py 的 forward_with 实现了这个插口)。

论文在 GPT-2 上做表格到文本(E2E、WebNLG、DART),在 BART 上做摘要(XSUM),只训练 0.1% 的参数:

  • 数据充足时和全量微调相当:E2E 的 BLEU,prefix 69.7、全量微调 68.2、adapter(3% 参数)68.9、adapter(0.1%)66.3。摘要上略差(XSUM ROUGE-L:全量微调 37.25,prefix 0.1% 35.05、2% 36.05)。
  • 数据少时更好:训练集 50–500 条时,平均比全量微调高 2.9 BLEU。
  • 外推到没见过的主题更好:在新闻上训练、在体育新闻上测试,ROUGE-L 31.51 vs 全量微调 30.26。
  • 一个消融很能说明问题:只在嵌入层放可训练向量(相当于 §4.4 的 prompt tuning),E2E 只有 48.1–62.2,论文说它”表达能力不够”——每层都能直接调是 prefix 比”只在输入端调”强的原因。
  • 两个实用技巧:直接优化前缀不稳定,论文用一个小 MLP 对前缀做重参数化(训练完丢掉 MLP,只存前缀);数据少时用真实词的激活值初始化前缀比随机初始化好得多。

在代码 01 的玩具上,prefix tuning(每层 8 个,2,048 个参数)在统一的 150 步预算内只学到一半多(kk = 64 时 0.670);多训练到 600 步就到了 0.950(练习 4)。它不是不行,而是在这个小模型上收敛慢:要通过每一层的键值间接地影响输出,150 步不够。比较方法时,“同样的训练步数”本身就是一个会影响结论的设定。

4.4 Prompt tuning:只在输入端,而且规模越大越好

Lester et al. 2021 把 prefix tuning 再简化一步:只在输入前面接 mm 个可训练的嵌入向量(“软提示”),其余全部冻结。W22 笔记把它和 prefix tuning 并列介绍,并指出它是为 T5 上的文本分类设计的。论文最重要的发现写在标题里——The Power of Scale:

  • 规模越大,prompt tuning 越接近全量微调。在 SuperGLUE 上,小模型上 prompt tuning 远不如全量微调(论文图 1),但到 T5-XXL(110 亿参数),prompt tuning 追平了全量微调,甚至追平了更强的多任务微调基线——而每个任务只需要两万个参数左右(5 个软 token 时 20,480 个,模型本身 110 亿)。作为参照,全量微调的 T5-XXL 是 89.3,GPT-3 的 few-shot 只有 71.8。
  • 软提示的长度:超过 20 个 token 收益就很小,默认用 100;XXL 模型只用 1 个 token 也表现不错。
  • 初始化:用类别标签词的嵌入初始化最好,随机初始化最差(W22 笔记:“随机初始化效果不好”);但这些差别在 XXL 上消失了。
  • 一个工程细节:T5 原来的 span corruption 目标训练出的模型”只会写哨兵”,很难被软提示引导;作者先把 T5 在语言建模目标上再训练 10 万步(“LM adaptation”)才稳定。第 3 讲 §5.3 里,我们的迷你 T5 正是卡在这件事上。
  • 分布外更好:在 SQuAD 上训练、在 TextbookQA 上测,prompt tuning 66.8 vs 全量微调 54.3(+12.5 F1);QQP → MRPC 76.3 vs 73.1。冻结模型、只动很少的参数,不容易”过拟合到训练分布”——这和 §2.3 的 LP-FT 是同一类现象。
  • 部署上的好处:同一个冻结的模型,一个 batch 里不同的样本可以接不同任务的软提示(“混合任务推理”);用 5 个软提示做集成,SuperGLUE 从单个提示的平均 90.5 提高到 91.3。

我们的模型落在 Lester 曲线的最左端:比他们最小的 T5 还小几百倍。代码 01 里 prompt tuning(8 个软 token,512 个参数)在 kk = 64 时只有 0.393,训练 600 步、换更大的学习率也还是 0.485(练习 4)。代码 01 演示 4 看了它的原始输出:自由生成写的是 ' thely.'、' the toste' 这样的碎片——连格式都没学会;受限选择给出的 0.5 左右只是”在三个词里总得挑一个”的产物。一个 11 万参数、2 层的模型,在输入端加 8 个向量,不足以把它的计算”拨”到一个新任务上。这和论文的结论方向一致,只是我们的点在曲线上太靠左了。

4.5 BitFit:只调偏置

Zaken et al. 2021 提出了也许是最简单的 PEFT:只训练所有的偏置项(bias),权重矩阵全部冻结。偏置只占 BERT 参数的 0.08–0.09%。在 GLUE 上,BERT-base 用 BitFit 平均 82.4、全量微调 82.3;BERT-large 84.2 vs 84.1——不相上下;完全冻结(只训练分类头)只有 62.1。只调注意力 query 的偏置和 MLP 中间层的偏置(0.04% 的参数)就有 81.1。论文的结论是:在中小数据上,BitFit 和全量微调相当,有时更好;数据很多时,它只能和其他稀疏微调方法相当。

我们的玩具里,BitFit 是 1,472 个参数(含 LayerNorm 的偏置,占 1.35%)。它在两个任务上的表现正好相反:人物问答(§3.3)里它是最好的方法——参数太少、背不下答案,只好去暴露知识;评论新任务(§4.9)里它学得慢、停在 0.8 左右——要让模型说出三个它从没说过的词,只动偏置不够。而且它的遗忘也很重(§3.4)。“只调偏置”的好处和坏处来自同一个事实:偏置作用在每一个位置上。

4.6 LoRA:低秩的权重更新

Hu et al. 2021 的 LoRA(Low-Rank Adaptation)是今天最常用的 PEFT 方法。想法只有一行:冻结一个权重矩阵 W0∈Rdout×dinW_0 \in \mathbb{R}^{d_{\text{out}} \times d_{\text{in}}},学一个低秩的修正:

h=W0x+αrBAx,A∈Rr×din,  B∈Rdout×r,  r≪min⁡(din,dout).h = 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},\; r \ll \min(d_{\text{in}}, d_{\text{out}}).

代码 03 演示 1 把它的零件拆开看(adapt_lab.py 里的 LoRALinear 去掉注释只有二十几行):

  • 参数怎么数:一个 64 → 192 的矩阵(每个块的 qkv)有 12,288 个数;LoRA 只训练 r(din+dout)r(d_{\text{in}} + d_{\text{out}}) 个:r=1r = 1 时 256 个(2.1%),r=4r = 4 时 1,024 个,r=8r = 8 时 2,048 个。注意 r=64r = 64 时是 16,384 个,比原矩阵还多——LoRA 只在 rr 远小于维度时才省参数。
  • 为什么 BB 初始化为 0:AA 用小的随机数,B=0B = 0,于是一开始 ΔW=BA=0\Delta W = BA = 0,模型和 base model 逐位相同(代码里的最大差是 0.0e+00)。训练从”原模型”出发,而不是从一个被随机扰动过的模型出发。为什么不能两个都是 0?那样 AA 和 BB 的梯度都是 0,永远学不动(练习 6)。
  • 缩放 α/r\alpha/r:论文把 α\alpha 设成第一次尝试的 rr,之后不再调;这样换 rr 时不用重新调学习率。用 Adam 时,调 α\alpha 大致等价于调学习率。代码 03 演示 2 比较了两种做法(kk = 16,学习率 1e-3,同分布准确率):
rr可训练参数α=r\alpha = r(缩放恒为 1)α=8\alpha = 8 固定(缩放 8/r8/r)
12,0480.7380.873
48,1920.8820.895
1632,7680.9630.957
64131,0720.9520.975

α=r\alpha = r 时,秩越小学得越慢;固定 α\alpha 后秩 1 明显变好。把 α=r\alpha = r 的学习率放大到 8e-3,秩 1 也升到 0.842(练习 5)——缩放和学习率在做差不多的事。

  • 合并:训练完把 αrBA\frac{\alpha}{r}BA 直接加进 W0W_0,得到一个普通的矩阵。代码里合并前后的输出最大差 1.4e-05(浮点舍入),准确率完全相同(0.860)。推理时没有任何额外计算——这是 LoRA 相对 adapter 的关键优势。存一个 LoRA 插件(r=4r = 4,4 个矩阵 × 2 层)是 36.9 KB,一整份模型 434.1 KB。

论文的实验在 GPT-3 175B 上,只在注意力的 WqW_q、WvW_v 上加 LoRA:

方法可训练参数WikiSQLMNLI-mSAMSum(ROUGE-1/2/L)
全量微调175,255.8M73.889.552.0 / 28.0 / 44.5
LoRA4.7M73.491.753.8 / 29.8 / 45.9
LoRA37.7M74.091.653.4 / 29.2 / 45.1

可训练参数少了约一万倍,成绩持平或更好;检查点从 350 GB 变成 35 MB,训练显存从 1.2 TB 降到 350 GB,训练还快了 25%。秩的实验更惊人:在 WqW_q、WvW_v 上用 r=1r = 1 就有 WikiSQL 73.4、MultiNLI 91.3。在 1800 万参数的固定预算下比较”放在哪个矩阵上”,WqW_q 和 WvW_v 一起调最好;只调 WqW_q 或 WkW_k 差一些。我们的玩具里,同样约 2,048 个参数,“4 个线性层各放 r=1r = 1“(0.738)明显好于”全部放在 qkv 上、r=4r = 4“(0.538)——后来 QLoRA(2023)也发现,要追上全量微调,LoRA 得放在所有线性层上。

4.7 为什么低秩就够:内在维度

LoRA 论文的假设是:“适配时权重的变化量有一个低的’内在秩’“。这个假设来自两条线索。

线索一:内在维度(intrinsic dimension)。Aghajanyan et al. 2020 问了一个问题:微调真的需要动全部参数吗?做法是只允许参数在一个随机的 dd 维子空间里移动:θ=θ0+Pz\theta = \theta_0 + P z,PP 是一个固定的随机投影,只训练 z∈Rdz \in \mathbb{R}^d。能达到全量微调 90% 表现的最小 dd,叫 d90d_{90}。结果:RoBERTa-Large 在 MRPC 上 d90d_{90} 约 200——3.5 亿个参数里,随便挑 200 个随机方向就够了;而且模型越大、预训练越久,内在维度越低。他们的解释是:预训练把模型带到了一个”只要在很少几个方向上调整,就能适配各种任务”的位置。他们还给出了一个泛化界,只依赖 dd 和样本数、而不依赖总参数量;作者用它来解释为什么一个有上亿参数的模型,用几千个样本微调也不会严重过拟合。

代码 03 演示 4 在玩具上做了同样的实验(kk = 64,同分布 / 分布外):

dd占全部参数的比例训练损失同分布分布外
160.01%2.2680.4600.397
640.06%0.4380.4830.477
2560.23%0.0250.8070.758
10240.94%0.0030.9220.793
40963.76%0.0010.9050.848

一千维左右(不到全部参数的 1%)同分布就到了全量微调(1.000)的九成以上。两个细节:随机子空间比 LoRA 笨得多(LoRA 用 2,048 个学出来的方向就有 0.970);而且在随机子空间里硬凑出来的解,分布外一直落后于同分布。

线索二:全量微调的更新量本身是低秩的。代码 03 演示 3 做了一个更直接的检验:先做全量微调(同分布 1.000),然后对每个权重矩阵的 ΔW=W微调后−W0\Delta W = W_{\text{微调后}} - W_0 做奇异值分解,看能量(奇异值的平方)集中在前几个方向上的比例:

  • 前 4 个方向占了 46%–71% 的能量;同样形状的随机矩阵只有 13%–22%。前 16 个方向占了 88%–96%。
  • 把每个 ΔW\Delta W 截断到秩 rr、其他改动保留:rr = 1、2 时只剩 0.667、0.637;r=4r = 4 时 0.963,r=8r = 8 时 0.997——和全量微调几乎一样。

全量微调从来没有被要求”低秩”,可它的更新量自己就集中在少数几个方向上。在这个任务上,“有效的秩”是个位数——比矩阵的宽度 64 小得多,但也不是 1。LoRA 论文对 GPT-3 做了类似的分析:r=8r = 8 和 r=64r = 64 学到的 ΔW\Delta W 最重要的那个方向高度重合,这解释了为什么 r=1r = 1 就表现不错;ΔW\Delta W 放大的是 WW 里本来就有、但没被强调的方向(r=4r = 4 时放大约 21.5 倍)。

要小心的是,低秩假设不是对所有适配都成立。Biderman et al. 2024 发现,在编程和数学这类”要学很多新东西”的任务上(持续预训练 200 亿 token,或十万条指令数据),全量微调的更新秩比常用 LoRA 设定高 10–100 倍,LoRA 明显学得不如全量微调。低秩假设最适合”格式类""暴露类”的适配——我们的两个任务恰好都是。

4.8 一个基座,很多插件

PEFT 在工程上最大的好处,也许不是省训练内存,而是一个基座可以挂很多插件。代码 03 演示 5 为两个任务各训练一个 LoRA(r=4r = 4):评论 verdict(3 选 1)和一个新的”服务好不好”(staff check,标签词 nice / mean,同样从没出现在语料里)。基座只有一份,换任务就是换掉 A、B:

装上的插件评论 verdict服务 staff
不装(就是 base)0.2530.677
verdict 插件0.9130.487
staff 插件0.2700.980

每个插件只在自己的任务上有用,装错了另一个任务就回到不装插件的水平甚至更差。存储账:100 个任务时,全量微调要 10,905,600 个数,LoRA 只要 928,256 个(约 11.7 倍的差别;模型越大、秩越小,差别越悬殊——GPT-3 上是万倍)。W22 笔记讨论 prefix tuning 时也提到了同样的好处:NN 个用户各存一个前缀,不同用户的请求还能放在同一个 batch 里。插件不合并时,同一个 batch 里不同的请求可以挂不同的 LoRA(基座的计算共享);S-LoRA(2023)把这个想法做成了在一张 GPU 上同时服务上千个插件的系统。合并进 WW 之后就失去了这种灵活性——LoRA 论文自己也指出了这个限制。

4.9 在同一个任务上排一排

最后把所有方法放在评论新任务上,用同一个打分函数、同样 150 步、学习率在开发集上选(代码 01 演示 2、3;每格 2 个种子):

方法可训练参数同分布 kk=4kk=16kk=64分布外 kk=4kk=16kk=64验证 bpc
不训练00.2530.2530.2530.2430.2430.2430.412
线性探针1950.3900.6170.8520.3870.6180.842—
全量微调109,0560.6250.9421.0000.6570.8901.0001.119
只调最后一个块50,1120.9220.9881.0000.8880.9770.9981.183
BitFit1,4720.4820.7930.8070.5180.7900.7923.467
adapter(4)2,0480.7020.9470.7750.7000.9130.7982.667
LoRA r = 12,0480.4070.8530.9700.3870.8620.9550.962
LoRA r = 816,3840.8120.9581.0000.7920.9571.0000.678
prefix tuning(8)2,0480.3430.6200.6700.3570.6120.6934.938
prompt tuning(8)5120.2530.4100.3930.2520.3880.4024.877

(“验证 bpc”:kk = 64 适配后、插件开着,在第 1 讲验证集上的 bit/字符。噪声:同一格两个种子的同分布成绩平均相差 0.095;adapter 最不稳定,kk = 64 时两个种子分别是 0.550 和 1.000。)

这张表最重要的一句话是:没有一种方法在所有列上都赢。

  • 这个任务的难点在输出端:要让模型说出三个它从没说过的词(loved / mixed / hated)。能直接改输出附近权重的方法学得最快——“只调最后一个块” kk = 4 就有 0.922。和 §3.3 的人物问答对照:那里的难点是”把格式接到已有的知识上”,参数少的 BitFit 最好;这里 BitFit 学得慢、停在八成。哪种方法好,取决于任务要求模型改变的是什么。
  • 分布外(形容词在标注数据里从没出现)和同分布几乎一样:这些方法都在复用预训练学到的”形容词好坏”,而不是从 kk 个例子里重新学。
  • LoRA r = 8 是这张表里最”均衡”的一行:kk = 4 时 0.812,kk = 16 时 0.958,kk = 64 满分,遗忘也最轻(0.678)。这大概也是 LoRA 在实践中最流行的原因——不是在每一列都最好,而是没有明显的短板。
  • 每格只有 2 个种子,差 0.1 以内的比较不要当真。

一个方法论上的提醒:如果学习率不在开发集上选,而是所有方法共用一个,这张表会完全变样——全量微调用 3e-3 时开发集只有 0.505,BitFit 用 1e-3 时只有 0.490。“某方法不行”最常见的原因之一,是它的超参数没调;而调超参数时绝不能看测试集。


5. 领域适配:继续预训练 · Domain adaptation by continued pretraining

5.1 Don’t Stop Pretraining

§2–4 的方法都在用标注数据适配。可是 W22 笔记的第二种差异——主题偏移——往往伴随着大量无标注的领域文本:两百多万篇生物医学论文、两千多万条商品评论(下面这篇论文用的正是这样的语料)。Gururangan et al. 2020(Don’t Stop Pretraining)的建议就在标题里:别停,接着在领域文本上做预训练(继续用原来的自监督目标),然后再在任务上微调。

他们用 RoBERTa 在 4 个领域、8 个分类任务上做了系统的实验:

  • DAPT(domain-adaptive pretraining):在领域文本上继续预训练 12.5K 步(大约把每个领域的语料过一遍)。领域语料:生物医学论文 75.5 亿 token、计算机论文 81.0 亿、新闻 66.6 亿、亚马逊评论 21.1 亿(RoBERTa 自己的预训练语料是 160 GB)。
  • TAPT(task-adaptive pretraining):只在这个任务本身的无标注文本上继续预训练 100 轮。数据小得多,在 RCT-500 上比 DAPT 快近 60 倍。
  • 结果(测试 F1,节选):
任务(领域)RoBERTa+ DAPT+ TAPT+ DAPT + TAPT
ChemProt(生物医学)81.984.282.684.4
ACL-ARC(计算机)63.075.467.475.6
SciERC(计算机)77.380.879.381.3
HyperPartisan(新闻)86.688.290.490.0
Helpfulness(评论)65.166.568.568.7
IMDB(评论)95.095.495.595.6

领域离 RoBERTa 的预训练语料越远(生物医学、计算机),DAPT 的收益越大;新闻、评论和原语料重叠多,收益小。

  • 对照实验:在一个不相关的领域上继续预训练(例如用计算机论文去帮新闻任务),结果一般比不做还差。所以收益不是”多训练了几步”带来的,而是来自对的数据。

这里有一个和第 2 讲呼应的观点:数据决定能力。适配到一个领域,最直接的办法是让模型多读这个领域的文本。

5.2 PubMedGPT / BioMedLM:从零训练一个领域模型

另一条路是不从通用模型出发,直接用领域数据从零训练。Stanford CRFM 和 MosaicML 2022 年 12 月发布的 PubMedGPT 2.7B(后来改名 BioMedLM)就是这样做的:

  • 一个 27 亿参数的 GPT-2 式模型,只在 The Pile 里的 PubMed 摘要和全文上训练,共 3000 亿 token(语料比这个小得多,所以要过很多遍;作者发现困惑度和下游表现一直在提高,认为值得);128 张 A100、约 6.25 天。
  • 自己训练了一个生物医学分词器:例如 “chromatography” 在 GPT-2 的分词器里被切成两段,在它的分词器里是一个 token;“immunohistochemistry” 在 GPT-2 里要切成 6 段。领域里的常见概念能用一个 token 表示,模型不必把一个概念的意思分散到几个通用的词片段上。
  • 在 MedQA(美国执业医师考试风格的题目)上微调后达到 50.3%,当时的最好成绩;对照:通用的 GPT-Neo 2.7B(同样大小,在 The Pile 全部内容上训练)33.3%,Galactica 120B(大 44 倍,零样本)44.4%,专门设计的生物医学模型 BioLinkBERT 45.1%、DRAGON 47.5%。
  • 作者明确说明:它的生成能力只供研究,不能用于临床——可靠性、真实性、可解释性都还是问题。

两条路的取舍:从零训练能为领域定制一切(分词器、数据配比),但需要海量领域数据和完整的预训练算力;继续预训练继承了通用模型的一切(语言能力、常识),只需要相对少的领域数据。第 15 讲(领域基础模型)会更详细地讨论代码、生物医学、法律这些领域各自的选择。

5.3 迷你实验:一个新药名的诊所

代码 04 演示 2 造了一个 base model 从没见过的领域——诊所记录:

note: deze took melatrin for the rash. the rash got worse.
note: vuna took olvadin for the itch. the itch got better.

病人是迷你世界里的 60 个人;8 个药名、4 个症状名都是新造的词。每种药治两种症状(这是这个领域的”知识”),10% 的记录是噪声。领域语料 3000 条记录;领域任务是”the rash got”后面是 better 还是 worse(200 题,瞎猜 0.5)。三种做法各训练 600 步:

模型领域 bpc领域任务通用 bpc人物事实原格式问答原格式打分
base model(不适配)7.7950.5300.4090.9331.0001.000
从零训练(只用领域数据)0.2440.6008.7680.1170.0670.260
继续预训练(DAPT)0.2271.0004.5030.1330.0330.200
DAPT + 回放 1/3 原来的数据0.2470.9650.4840.8670.5001.000
  1. base model 对新领域是”自信地错”:领域文本上 7.795 bit/字符,比均匀分布(5.46)还差。逐词看,“zorvex” 每个字符 9.18 bit,“fever” 12.30 bit;连它认识的 “the “,在这个陌生的上下文里也要 3.80 bit/字符。这就是”主题偏移”——不只是生词,整个句式都是陌生的。
  2. 继续预训练学得快得多。两者的领域 bpc 相差不大(0.227 vs 0.244),但领域任务的学习曲线完全不同:DAPT 在第 300 步到 0.830、第 600 步到 1.000;从零训练的模型 600 步里一直在 0.48–0.61 之间徘徊。从零训练的模型要先学会拼写、句子结构、“把前面提到的症状抄过来”这类基本的计算;DAPT 直接继承了这些——Gururangan 的结果在玩具里的样子。
  3. 代价是遗忘:只用领域数据继续预训练,通用 bpc 从 0.409 涨到 4.503,人物事实从 0.933 掉到 0.133——比 §3.4 的任务微调严重得多,因为这次训练了 600 步、全是领域文本。
  4. 回放几乎两全:混入 1/3 原来的数据,领域任务 0.965,通用 bpc 0.484,人物事实 0.867,原格式打分 1.000;只有原格式问答掉到 0.500(一个可能的原因:回放的数据是从语料里随机抽的一小部分,问答行在里面不多;没有验证)。把回放比例降到 1/10,领域任务 1.000、通用 bpc 0.595、人物事实 0.700(练习 9)——回放越少,领域学得越专,原来的忘得越多。

5.4 新词与分词器

我们的模型是字符级的,新药名不会变成”未知 token”,只是字符组合陌生。真实模型用子词分词器(第 3 讲 §1),领域新词会被切成很多碎片——PubMedGPT 专门训练领域分词器正是为了这个。可是继续预训练时换分词器很麻烦:嵌入矩阵和输出层都是按旧词表建的。常见的做法是保留旧分词器(新词只是切得碎一点),或者往词表里加新 token、为它们初始化新的嵌入(例如用切成碎片时各片嵌入的平均)再继续训练。没有哪种做法是免费的:这是从零训练领域模型的一个真实理由。


6. 模型编辑 · Model editing

6.1 为什么需要编辑

W22 笔记的第三种差异是时间偏移:世界在变,模型的知识停在训练那一刻。更常见的情形是错误:模型记错了某个事实。重新预训练太贵;在新数据上继续训练会遗忘、也不精确。能不能像改数据库一样,只改一个事实?这就是模型编辑(model editing)。

6.2 三个要求

Mitchell et al. 2021(MEND)把一次好的编辑要满足的条件说得很清楚(ROME 用的是同一套):

要求含义怎样量
可靠性 reliability用编辑时那句话问,答出新答案编辑成功率(efficacy)
泛化 generality换一种说法问(“等价邻域”,例如改写的句子)也答新答案改写成功率(paraphrase)
局部性 locality和这次编辑无关的输入,输出不变其他事实的准确率下降多少(drawdown),或编辑前后输出分布的 KL 散度

三者之间有天然的张力:改得太”窄”,换个说法就不认;改得太”宽”,无关的事实也跟着变了。

6.3 在一句话上微调为什么不行

最朴素的编辑:在”kari lives in osku.”这一句话上微调。代码 05 演示 2 随机选 10 个人,每人改一个城市(每次从原模型出发,只改一个),用受限选择评分:

方法可靠性(编辑句)泛化:moved to / is a JOB from / the JOB X lives in / 问答别人的城市(局部性)别人也搬去新城自己的职业
不编辑0.000.00 / 0.10 / 0.00 / 0.200.930.010.90
微调:lr 1e-3,30 步1.001.00 / 1.00 / 1.00 / 1.000.130.870.30
微调:lr 1e-4,10 步1.000.70 / 0.90 / 1.00 / 0.800.450.480.30
微调:lr 1e-4,5 步0.900.60 / 0.60 / 0.70 / 0.600.690.210.70
只微调第 2 层 MLP,30 步1.001.00 / 1.00 / 1.00 / 1.000.300.680.30
微调 + 回放 40 个别人1.001.00 / 0.80 / 0.70 / 0.600.780.030.30
ROME 式秩一编辑(正则 0.1)1.000.60 / 0.70 / 0.60 / 0.600.880.070.80
ROME 式秩一编辑(正则 0.01)1.000.60 / 0.90 / 0.70 / 0.700.850.090.80

微调这一句(lr 1e-3、30 步)在可靠性和泛化上都是满分,可是其余 59 个人里 87% 被答成了新城市。模型学到的不是”kari 住在 osku”,而是”lives in 后面写 osku”——一句话的训练数据,没有任何东西告诉它”只对 kari 成立”。把学习率调小、步数减少,局部性回来一些,新事实也写得不那么牢:这是一个此消彼长的旋钮,不是解决办法。把 40 个别人的事实一起训练(“回放”,MEND 论文里 FT+KL 基线的简化版)能保住大部分局部性,但需要额外的数据,而且被编辑的人自己的职业照样被带歪了(0.30)。

MEND 论文的数字是同一个模式:在 GPT-J(60 亿参数)的 Wikitext 编辑上,微调的编辑成功率 0.80、困惑度上升 0.125;加上 KL 约束后局部性好了(0.109),成功率却掉到 0.36。

6.4 ROME:先定位,再做秩一编辑

Meng et al. 2022(ROME)把编辑分成两步。

第一步:定位——事实存在哪里? 他们发明了因果追踪(causal tracing):把主语的嵌入加上噪声(模型就想不起这个事实了),再把某一层、某一个位置的”干净”状态放回去,看正确答案的概率能恢复多少(indirect effect)。在 GPT-2 XL 上,他们发现决定性的位置是主语最后一个 token 处的中间层 MLP(单个位置的效应在第 15 层最高,8.7%;MLP 的贡献最高 6.6%,同一位置的注意力只有 1.6%)。

代码 05 演示 1 在 base model 上做了同样的追踪(60 个人,提示 “<名字> lives in “;正确城市的概率干净时 0.860,名字加噪声后 0.121)。放回去之后概率回升了多少:

层 · 状态名字第一个字符名字最后一个字符“lives” 里的 v最后一个位置
第 1 层 · 残差流+0.003+0.006-0.000+0.039
第 2 层 · 残差流+0.000+0.000+0.000+0.739
第 2 层 · 注意力输出+0.000+0.000+0.000+0.143
第 2 层 · MLP 输出+0.000+0.000+0.000+0.565

在我们的模型里,“回忆城市”发生在要说出城市的那个位置:第 2 层的注意力把名字的各个字符”搬”过来,第 2 层的 MLP 把”这个名字”变成”这个城市”;名字所在的位置上什么都没有。这和 ROME 在 GPT-2 XL 上找到的位置不同——我们的模型只有 2 层、字符级,名字不是一个 token,信息要到最后才被汇总。方法相同,答案取决于模型。这也再次说明了 §2.2 的教训:在名字的位置上做探针读不出城市(第 1 讲 §3.4 的 0.513),原因就在这张表里。

第二步:秩一编辑。 ROME 把 MLP 的输出矩阵 WW 看成一个线性联想记忆:输入(键 kk)是”主语”的表示,输出(值 vv)是”关于它的事实”。要写入一条新的事实:

  1. 键 k∗k^*:被编辑的主语在那个位置上,WW 的输入;
  2. 值 v∗v^*:用梯度下降找一个向量,让 WW 在那里输出 v∗v^* 时模型说出新答案(ROME 还加了一项 KL,防止主语的其他属性被带偏;代码里用的是 ∥v−v0∥2\lVert v - v_0 \rVert^2 正则);
  3. 闭式解:在"W′k∗=v∗W' k^* = v^*"的约束下,让 W′W' 对其他所有的键改动最小:
W′=W+Λ (C−1k∗)⊤,Λ=v∗−Wk∗(C−1k∗)⊤k∗,C=E[kk⊤].W' = W + \Lambda\, (C^{-1} k^*)^\top, \qquad \Lambda = \frac{v^* - W k^*}{(C^{-1} k^*)^\top k^*}, \qquad C = \mathbb{E}[k k^\top].

CC 是在大量普通文本(ROME 用维基百科)上估计的”平时的键长什么样”;C−1k∗C^{-1}k^* 挑出 k∗k^* 里平时少见的方向,于是这个秩一改动主要作用在像 k∗k^* 的输入上。

在 ROME 的 CounterFact 数据集(21,919 条反事实编辑)上,GPT-2 XL 的结果:

方法效能 ES改写 PS邻居 NS综合 S
微调(FT)100.087.940.465.1
微调 + 约束(FT+L)99.148.770.366.9
MEND99.165.437.957.9
ROME100.096.475.489.2

论文的总结是:除了 ROME,其他方法要么在泛化上失败,要么在局部性上失败,要么两样都失败。

回到代码 05 的表:ROME 式编辑(只改第 2 层 MLP 输出矩阵的一个秩一分量)可靠性满分,局部性最好(0.85–0.88,不编辑时 0.93),被编辑者的职业基本保住(0.80,不编辑时 0.90);泛化到其他说法只有六到九成,不如论文里那么好。一个可能的原因:我们的编辑位置是句子的最后一个位置,键 k∗k^* 里混着 “lives in” 这个句式的信息,换一种说法键就变了;ROME 编辑的是主语所在的位置,键基本只取决于主语。把编辑放到第 1 层(因果追踪说那里几乎没有作用),局部性降到 0.70、泛化降到 0.10–0.60(练习 8)——先定位再编辑不是一句空话。

ROME 自己承认的局限:一次只改一个事实;编辑是有方向的——论文的例子是 “The iconic landmark in Seattle is the Space Needle” 和反方向的知识分开存储,要分别编辑(第 5 讲 §6.3 的反转诅咒)。

6.5 MEND:学一个”编辑器”,以及批量编辑

MEND(Mitchell et al. 2021)走的是另一条路:不手工推导更新公式,而是训练一个小网络,把”在这条编辑上的梯度”变成”一个好的参数更新”。关键的观察是:对单个样本,一个线性层权重的梯度是秩一的外积 δu⊤\delta u^\top;MEND 的编辑器网络只看 uu 和 δ\delta 这两个向量(约 2d2d 维),而不是 d2d^2 维的完整梯度,所以对上百亿参数的模型也可行——在一张 GPU 上不到一天就能训练好。在 GPT-J 的 Wikitext 编辑上,MEND 的成功率 0.88、困惑度只上升 0.031(微调是 0.80 和 0.125)。

一个现实的问题是改很多个事实。MEND 论文的批量实验(BART-base,zsRE 问答):同时编辑 1 个时成功率 0.98,25 个时 0.89,125 个时 0.67。MEMIT(Meng et al. 2022b)重新设计了更新公式,把改动分摊到多层 MLP 上,能一次写入成千上万个事实(GPT-J、GPT-NeoX 20B)。

代码 05 演示 3 做了”一个接一个地改”:

方法已编辑人数已编辑的事实仍然生效没编辑的 40 人仍然答对已编辑者的职业
ROME 式(正则 0.1)11.000.881.00
50.800.620.80
100.800.600.70
200.550.450.60
微调这一句(lr 1e-4,5 步)11.000.681.00
50.400.330.80
200.250.330.35

(没编辑的 40 人在 base model 上是 0.93。)每次编辑单独看都”成功”,累积起来却在慢慢把模型改坏:改到第 20 个时,ROME 式编辑写进去的事实只剩一半多,没编辑的人也只剩不到一半答对;微调退化得更快。在一个只有 64 维、60 个人挤在同一个 MLP 里的模型上,每一个秩一改动都会碰到别人。

6.6 涟漪效应:改一个事实,牵动一片

可靠性、泛化、局部性都满足了,编辑就成功了吗?Cohen et al. 2023(Evaluating the Ripple Effects of Knowledge Editing)指出还缺一块:事实之间是相连的。把”A 的父亲是 B”改掉之后,“A 的兄弟姐妹是谁""A 的祖父是谁”也应该跟着变;而按局部性的要求,它们恰恰应该”不变”。他们的 RippleEdits 基准(约 5000 条编辑)检查六类涟漪:逻辑推论(例如对称关系)、两种组合推理、主语的别名、一对多关系里原有的对象不应消失、同一主语的其他关系不应变化。结果是:ROME、MEMIT、MEND 这些方法大多没能让模型的知识一致地改变;一个简单的基线——把新事实写进提示里(in-context editing)——反而得分最高,在 LLaMA 上比 ROME 平均高约 29 个点。

放到我们的迷你世界里:改了”kari lives in osku”之后,“the baker kari lives in”(职业和城市组合在一起的句子)和问答格式也应该变(代码 05 测了,ROME 式编辑只做到六七成);而 kari 的职业、别人的城市不应该变。更难的问题这个世界里没有设计:如果语料里有”osku 的居民有……”这样的句子,编辑之后它该不该包含 kari?这正是”编辑一个事实”比看起来难的原因:知识不是一张表,而是一张网。


7. 怎样比较、怎样选 · Comparing and choosing

7.1 一张比较表

把前面的内容按几个维度排在一起(“玩具”一栏指本讲代码里的观察,“文献”一栏指论文里的真实模型):

方法每个任务要存训练内存少标签时分布外遗忘(插件开着)推理开销
提示 / ICL一段文字无训练大模型很好;小模型几乎不会(第 1、5 讲)取决于模型无占用上下文
线性探针一个头(几百个数)约等于推理表示里有信息时很好;格式不对时读不出(代码 02)好(LP-FT:探针分布外常胜过 FT)无无
全量微调一整份模型约 4 倍于模型容易背答案(代码 02:0.2–0.3)可能被特征扭曲拖累(Kumar:平均低 7%)中到重无
adapter约 1–4%约等于模型本身中等;在玩具里不稳定好(Houlsby:GLUE 差 0.4)玩具里很重每层多两次小乘法
prefix tuning约 0.1%约等于模型本身文献:比全量微调好(+2.9 BLEU)好(外推到新主题更好)拔掉即无注意力多看 mm 个键值
prompt tuning约 0.01%约等于模型本身小模型很差,大模型追平好(TextbookQA +12.5 F1)拔掉即无序列变长
BitFit约 0.1%约等于模型本身文献:中小数据和全量微调相当—玩具里最重无
LoRA约 0.01–1%约等于模型本身(GPT-3:1.2 TB → 350 GB)好;r 很小就够(GPT-3:r = 1)好较轻(“learns less, forgets less”)合并后为零
继续预训练一整份模型和预训练一样不用标签,要大量领域文本领域内好重,回放可缓解无
模型编辑一个秩一更新很小一条事实泛化到改写不完全局部性好,但会累积无

7.2 一张决策表

你的处境先试理由
只有 API、没有权重提示(零样本 → 少样本 → 改写成模型熟悉的格式,第 5 讲 AMA);服务商若提供微调接口再考虑它唯一能做的;大模型的 ICL 往往已经够用
有权重、标签很少(几十个)、任务是”格式类”提示;不行就 LoRA(小秩)或 BitFit参数少的方法背不下答案,被迫复用已有知识(代码 02)
有权重、标签很少、只要一个分类器线性探针,先找对读表示的位置最便宜;分布外常常最稳(代码 02 演示 1)
标签几百到几千,要分类器且在意分布外LP-FT同分布、分布外两头兼顾
要服务很多任务或很多用户LoRA(或 prefix / prompt tuning,大模型时)一个基座 + 很多几 MB 的插件;不合并还能混合 batch
显存不够做全量微调LoRA / QLoRA(§8)训练内存约等于模型本身
任务要大幅改变模型的行为或学很多新知识(新语言、新领域的推理)全量微调或继续预训练(+ 回放)低秩假设在这里不成立(Biderman et al. 2024)
有大量无标注的领域文本继续预训练(DAPT / TAPT),混入原数据Gururangan 的结果;回放防遗忘(代码 04)
要改正少数几个过时或错误的事实先考虑检索 / 把新事实写进提示;需要改权重时用 ROME / MEMIT 这类方法,并做涟漪和累积测试在一句话上微调会改坏别的事实(代码 05)
想让模型”听得懂各种指令”指令微调(第 7 讲)单一任务的微调只学会那一种问题(代码 02 的问职业)

7.3 哪些结论能推广

本讲的模型只有 11 万参数、2 层,任务是模板造出来的。按”机制 vs 量级”分一下:

能推广的机制(文献里有对应的证据):

  • 全量微调用随机头会扭曲特征、损害分布外(Kumar et al. 在 10 个真实数据集上);LP-FT 缓解它。
  • 参数少的方法在标签少时更不容易背答案(prefix tuning 的低数据结果、BitFit 的中小数据结果)。
  • 全量微调的更新量集中在少数方向上,低秩更新足以完成”格式类”的适配(LoRA 的秩实验、内在维度)。
  • 只在新数据上训练会遗忘,回放能缓解(持续学习的老结论;Ibrahim et al. 2024 在 100 亿参数上)。
  • 在一句话上微调的”编辑”会破坏局部性;定位后的秩一编辑更局部(ROME 在 GPT-2 XL 上)。

不能推广的量级或细节:

  • prompt tuning 在玩具里完全失败;在 110 亿参数的模型上它追平了全量微调。规模改变了结论,这正是 Lester 论文的主题。
  • 玩具里 BitFit 和 adapter 的遗忘比全量微调还重;真实模型上这取决于学习率、步数、插件大小,不能一概而论。
  • 玩具里”只调最后一个块”在评论任务上最好,是因为难点恰好在输出端、而模型只有 2 层(最后一个块就是一半的模型)。
  • ROME 在玩具里定位到”最后一个位置”,在 GPT-2 XL 里是”主语最后一个 token 处的中间层”。定位的结论属于具体的模型。

8. 2023 年之后 · Since 2023

LoRA 成了默认的轻量微调方法,QLoRA 让它进了普通人的显卡。 Dettmers et al. 2023(QLoRA)把冻结的基座量化成 4 比特(一种为正态分布权重设计的数据类型 NF4,再加上”对量化常数再量化”,平均每个参数省约 0.37 比特,65B 模型约省 3 GB),梯度穿过量化的基座流到 16 位的 LoRA 插件上。结果:在一张 48 GB 的 GPU 上微调 650 亿参数的模型(普通 16 位微调要 780 GB 以上),在学术基准上和 16 位的全量微调、LoRA 微调相当。论文还有两条至今常被引用的经验:LoRA 必须加在所有线性层上才能追上全量微调(我们在 §4.6 的玩具里也看到了);数据质量比数量重要——9 千条高质量的对话数据(OASST1)训练出的聊天模型,胜过从 FLAN v2 里抽的 45 万条。他们用 24 小时训练的 Guanaco 在 Vicuna 基准上达到 ChatGPT 的 99.3%(这类”让模型当裁判”的评测本身有争议,第 7 讲)。

PEFT 成了一个生态。 Hugging Face 的 PEFT 库把 LoRA、prefix tuning、prompt tuning 等做成了几行代码就能用的组件;LoRA 的变体层出不穷,例如 DoRA(2024)把权重分解成”大小”和”方向”,只用 LoRA 学方向,报告在多种任务上稳定地优于 LoRA。服务端,S-LoRA(2023)在一张 GPU 上同时服务上千个 LoRA 插件,吞吐量比 HF PEFT、vLLM 的基础实现高到 4 倍。开放权重模型的社区里,“一个基座 + 很多个 LoRA”已经很常见。另一个相关方向——把多个微调过的模型直接合并(权重平均、任务向量相加)——是第 13 讲 Colin Raffel 的主题。

“LoRA 学得少、忘得也少”。 Biderman et al. 2024 在编程和数学上系统比较了 LoRA 和全量微调(指令微调约 10 万条,继续预训练 200 亿 token):常用设定的 LoRA 明显学得不如全量微调,但更好地保住了领域外的能力,比 dropout、权重衰减这些正则更能防遗忘;全量微调的更新秩比常用 LoRA 高 10–100 倍。这把 §4.7 的低秩假设放回了它适用的范围:格式和风格的适配是低秩的,学大量新知识不是。

“适配”的默认形式变了。 2023 年以后,大多数人用的已经不是 base model,而是经过指令微调和 RLHF 的模型(第 7 讲)——“让模型听懂指令”本身成了最大的一次适配,之后用户主要靠提示来”适配”。同时,上下文窗口从几千 token 变成几十万乃至上百万,many-shot ICL(第 5 讲 §7)在很多任务上接近微调;知识类的任务越来越多地交给检索增强(RAG,第 3 讲 §8.2)。微调的角色更集中在:特定格式和风格、领域术语、小模型的蒸馏、以及服务商不提供的能力。

微调教的是”怎样用知识”,不太是”新知识”。 Gekhman et al. 2024 在受控的闭卷问答实验里发现:微调数据里那些模型预训练时不知道的事实,学得明显更慢;一旦学会,反而会线性地增加模型的幻觉倾向。他们的结论和 BitFit 的假设、§3.3 的玩具结果是同一个方向:大部分事实知识在预训练时获得,微调主要教模型更有效地使用它。

继续预训练有了”配方”。 Ibrahim et al. 2024 研究了怎样在新数据上继续预训练大模型而不遗忘:学习率重新预热、再衰减,加上回放一部分旧数据,在 4.05 亿参数(英语 → 英语、英语 → 德语)和 100 亿参数(英语 → 英语)上都追平了”用全部数据从头重训”,而计算只用了一小部分。

模型编辑:评测越来越严,热情有所冷却。 RippleEdits(§6.6)之后,越来越多的工作发现参数编辑在一致性、累积编辑、多跳推理上都很脆弱;需要更新知识的生产系统大多选择检索或提示,而不是改权重。编辑方法仍然是理解”知识存在哪里”的重要工具(第 12 讲的机制可解释性),但”像改数据库一样改模型”还没有实现。

哪些结论仍然成立?Γ\Gamma 选得越小,越便宜、越不容易背答案和遗忘,但表达能力越受限、越依赖预训练模型本身——这条主线在 2026 年看依然成立。被修正最多的是 prompt tuning(在实践中基本让位给了 LoRA)和”模型编辑能解决知识更新”的期待。


9. 小结 · Summary

  1. 适配就是在某个参数族 Γ\Gamma 里找参数,让任务损失最小;提示、探针、轻量微调、全量微调的区别只在于”允许改动什么”。下游任务和语言建模的差异有三类:格式、主题、时间,分别对应微调类方法、继续预训练、模型编辑。
  2. 提示不改参数,但受格式(模型只认得它见过的格式)和上下文长度限制:代码 01 里 base model 在新格式上受限选择 0.253、自由生成 0.000,96 个字符连一个示例都放不下。
  3. 线性探针只能读出表示里已有的信息,读在哪里很重要;LP-FT(先探针再微调)避免了随机分类头带来的特征扭曲:代码 02 里 kk = 256 时 FT 分布外 0.917,LP-FT 0.985(Kumar et al.:FT 比 LP 分布外平均低 7%)。
  4. 全量微调最强也最贵:每个任务一整份模型、训练内存约 4 倍;标签少时会背答案(代码 02:同类新人 0.2–0.3,BitFit 0.644);只训练新任务会灾难性遗忘,回放能缓解。
  5. 参数高效微调把可训练参数放在杠杆最大的位置:adapter(层间瓶颈)、prefix tuning(每层键值)、prompt tuning(输入端,规模越大越接近全量微调)、BitFit(偏置)、LoRA(W+αrBAW + \frac{\alpha}{r}BA,BB 从 0 开始,可合并进 WW,推理零开销)。没有一种方法在所有任务上都最好:代码 01 的评论任务难在输出端,“只调最后一个块”和 LoRA r = 8 最好;代码 02 的人物问答难在”接上已有知识”,BitFit 最好。
  6. 低秩就够有两条证据:内在维度(RoBERTa-Large 在 MRPC 上 d90d_{90} 约 200;玩具里约一千维到九成);全量微调的 ΔW\Delta W 本身就集中在少数方向上(玩具里截断到秩 8 和全量微调几乎一样)。但它对”学大量新知识”不成立。
  7. 领域适配:在领域文本上继续预训练(DAPT)比从零训练快得多(玩具:领域任务 1.000 vs 0.600),但会遗忘(通用 bpc 4.503),回放 1/3 原数据几乎两全;PubMedGPT 选择了从零训练一个领域模型,用了 3000 亿 token 和定制的分词器。
  8. 模型编辑要同时满足可靠性、泛化、局部性。在一句话上微调会把”lives in 后面写 osku”教给所有人(玩具:87% 的其他人被改成新城市);ROME 先用因果追踪定位,再做秩一编辑,局部性好得多(0.88),但泛化不完全、连续编辑会累积损伤;涟漪效应说明知识是一张网,不是一张表。

10. 常见误区 · Common pitfalls

  1. “微调总比探针好。” 同分布上通常如此,分布外不一定:随机初始化的分类头会扭曲特征(代码 02 演示 1:kk = 64 时 FT 分布外 0.874,LP 0.963)。标签很少时,探针本身就是一种保护。
  2. “参数越多,适配效果越好。” 标签少时恰恰相反:参数多的方法能把标签背下来,参数少的方法被迫复用已有知识(代码 02:全量微调 0.2–0.3,BitFit 0.644)。
  3. “参数高效 = 不会遗忘。” 插件开着时,BitFit、adapter 在玩具里的遗忘比全量微调还重(验证 bpc 3 以上)。PEFT 防遗忘的真正优势是插件可以拔掉,以及 LoRA 这类方法改动相对克制。
  4. “LoRA 的秩越大越好。” 秩到某个点之后就不再有用(GPT-3 上 r = 1 就够),rr 接近维度时参数甚至比原矩阵还多;而缩放 α/r\alpha/r 和学习率纠缠在一起,比较不同的 rr 时要注意它(代码 03 演示 2)。
  5. “prompt tuning 不行。” 在小模型上确实不行(玩具里连格式都没学会),但 Lester et al. 的核心结论是它在 110 亿参数时追平了全量微调。一个方法行不行,可能取决于规模。
  6. “某方法在我的实验里分数很低,所以它不行。” 先检查学习率是否在开发集上调过(代码 01:全量微调用错学习率只有 0.505),再看原始输出(代码 01 演示 4:prompt tuning 的 0.5 只是”总得挑一个”)。
  7. “在新领域的文本上继续训练没有副作用。” 只用领域数据会忘掉原来的本事(玩具:人物事实 0.933 → 0.133)。真实的继续预训练几乎都要回放原来的数据。
  8. “要改一个事实,在那句话上微调一下就行。” 微调学到的是”这个句式后面写这个词”,会波及所有人(玩具:别人的城市只剩 0.13 答对);即使局部性保住了,涟漪效应(相关事实该不该变)也没解决。
  9. “适配能教会模型新知识。” 大多数时候适配只是在暴露预训练学到的知识(BitFit 的假设、Gekhman et al. 2024);模型本身取不出的知识,适配也取不出(代码 02:只在叙述句里出现过的人最多 0.456)。新知识要么靠继续预训练,要么靠检索。
  10. “低秩假设对所有微调都成立。” 它对格式、风格类的适配很好;学编程、数学这类大量新内容时,全量微调的更新秩高得多(Biderman et al. 2024)。

11. 练习 · Exercises

练习 1(三种差异) 下面四个场景各属于 W22 笔记的哪一种差异(格式 / 主题 / 时间)?各自首先会试什么适配方法?(a) 让模型从邮件里抽取会议时间,输出固定格式的 JSON;(b) 用一个通用模型给法律合同的条款分类,手里有 50 万份无标注合同和 300 条标注;(c) 用户问一个 2025 年才发布的产品;(d) 模型一直说某公司的 CEO 是前任。

参考答案

(a) 格式。先试提示(给几个”邮件 → JSON”的示例);大模型通常就够了。要求很严格或模型较小时,用几百条样本做 LoRA 微调,并用受限解码保证格式。

(b) 主题偏移(法律术语和句式)+ 一点格式。先在 50 万份合同上继续预训练(DAPT,混入通用数据防遗忘),再用 300 条标注做分类:探针或 LP-FT(在意分布外时),或者 LoRA。Gururangan 的结果说明 DAPT 在离通用语料远的领域收益最大。

(c) 时间偏移,而且是”模型根本不知道”的知识。最实际的是检索(把产品资料放进提示);改权重的成本和风险都高(Gekhman et al.:微调新知识会增加幻觉)。

(d) 时间偏移里的”改一个事实”。可以把正确信息放进提示 / 检索;要改权重的话用 ROME / MEMIT 这类方法,并检查局部性(别的公司的 CEO 有没有被改)和涟漪(“该公司的前任 CEO 是谁”该怎么答)。在那一句话上直接微调是最差的选择(代码 05)。

练习 2(LoRA 的参数账) GPT-3 175B 有 96 层,宽度 dmodel=12288d_{\text{model}} = 12288。只在每层的 WqW_q、WvW_v(都是 12288×1228812288 \times 12288)上加秩 rr 的 LoRA,一共有多少个可训练参数?分别取 r=1r = 1 和 r=8r = 8,和 LoRA 论文表里的 4.7M、37.7M 对照。

参考答案

每个矩阵 r(din+dout)=r×24576r(d_{\text{in}} + d_{\text{out}}) = r \times 24576 个参数;每层 2 个矩阵,96 层:2×96×24576×r=4,718,592 r2 \times 96 \times 24576 \times r = 4{,}718{,}592\, r。

r=1r = 1:约 4.72M;r=8r = 8:约 37.7M。正好对应论文表 4 里的两行(4.7M 和 37.7M)——也就是说,表里那个”WikiSQL 73.4、MNLI 91.7”的 LoRA 只用了秩 1。和全量微调的 1750 亿相比,r=1r = 1 时少了约 37,000 倍;论文摘要说的”一万倍”对应的是 r=4r = 4 左右的设定(175B/18.9M≈9,300175\text{B} / 18.9\text{M} \approx 9{,}300)。

练习 3(训练内存) 一个 70 亿参数的模型,用 Adam、单精度(fp32)全量微调,参数相关的内存(权重 + 梯度 + 两个动量)要多少 GB?换成 LoRA,可训练参数是 0.5%,又要多少?如果像 QLoRA 那样把冻结的权重存成 4 比特呢?(都不算激活值。)

参考答案

全量微调:每个参数 4 + 4 + 8 = 16 字节,7×109×16=1127 \times 10^9 \times 16 = 112 GB。

LoRA:冻结的权重 7×109×4=287 \times 10^9 \times 4 = 28 GB;可训练的 3.5×1073.5 \times 10^7 个参数 × 16 字节 ≈ 0.56 GB。合计约 28.6 GB——几乎就是”模型本身”,和代码 01 演示 5 的比例一样。

QLoRA:冻结的权重 4 比特 = 0.5 字节/个,约 3.5 GB(加上量化常数,略多一点);LoRA 部分不变。合计约 4 GB——这就是 QLoRA 能在一张 48 GB 的卡上微调 65B 模型的原因(实际还要算激活值、优化器分页等)。

练习 4(改一行:多训练几步) 代码 01 里所有方法都只训练 150 步。把 prefix tuning 和 prompt tuning 的训练改成 600 步、学习率 0.1(kk = 64),预测它们的同分布准确率。

参考答案

我在一份代码副本里跑了(没有改动课程目录里的文件):prefix tuning 从 0.670 升到 0.950(两个种子 0.943、0.957),分布外 0.948;prompt tuning 只有 0.485(分布外 0.478),仍然没学会。

prefix tuning 不是不行,而是收敛慢:它要通过每一层的键值去间接影响输出,150 步不够。prompt tuning 在这个规模上是真的不行——只在输入端加 8 个向量,不足以把一个 2 层的小模型”拨”到新任务上(Lester et al. 的结论:小模型上 prompt tuning 远不如全量微调)。这个练习也说明:比较方法时,训练预算本身是一个变量,“同样 150 步”对收敛慢的方法不公平,对收敛快的方法也不代表上限。

练习 5(改一行:缩放与学习率) 代码 03 演示 2 里,α=r\alpha = r(缩放 1)时秩 1 只有 0.738,α=8\alpha = 8(缩放 8)时 0.873。如果保持 α=r\alpha = r、把学习率从 1e-3 放大 8 倍,秩 1 能追上吗?秩 4 时呢(缩放 2,学习率放大 2 倍)?

参考答案

我在一份代码副本里跑的结果:秩 1、学习率 8e-3:0.842,接近但没有完全追上 0.873;秩 4、学习率 2e-3:0.895,和 α=8\alpha = 8 的 0.895 一样。

为什么”差不多”而不是”完全一样”?用 Adam 时,每一步的更新量大约是”学习率 × 一个归一化过的方向”,和梯度的大小无关。把输出乘以 cc(缩放),相当于让 ΔW=cBA\Delta W = cBA 每一步多走 cc 倍——这和把 BB、AA 的学习率乘以 cc 很像,但不完全相同:ΔW\Delta W 是 BB 和 AA 的乘积,两个因子各自的步长变化合起来不等于 cc 倍;Adam 的归一化也会在开始几步产生差别。LoRA 论文的说法是”调 α 大致等价于调学习率”。

练习 6(为什么 B = 0) LoRA 里 AA 随机、B=0B = 0。(a) 如果 AA、BB 都初始化为 0,训练会怎样?(b) 如果都随机初始化呢?(c) 反过来,A=0A = 0、BB 随机,行不行?

参考答案

记 h=W0x+BAxh = W_0 x + BAx,损失对 hh 的梯度为 gg。则 ∂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。

(a) A=B=0A = B = 0:两个梯度都是 0(Ax=0Ax = 0,B⊤g=0B^\top g = 0),参数永远不动——训练不起来。

(b) 都随机:一开始 BA≠0BA \neq 0,相当于给预训练模型加了一个随机扰动,先把它弄坏一点再修;而且这个扰动的大小随 rr 变化,换 rr 时又要重新调。能训练,但浪费了”从原模型出发”这个好处。

(c) A=0A = 0、BB 随机:一开始 BA=0BA = 0 ✓;∂L/∂A=B⊤gx⊤≠0\partial L / \partial A = B^\top g x^\top \neq 0,AA 能动起来,之后 BB 也会动。理论上可以(对称的选择),论文选了 AA 随机、B=0B = 0。关键是两个因子里恰好一个为 0:既保证起点等于原模型,又保证梯度不全为 0。

练习 7(为什么会背答案) 代码 02 演示 2 里,全量微调(lr 3e-4)第 50 步时训练集已经 0.96,同类新人却从 0.29 降到 0.18。(a) 为什么”训练集上做对”反而让没见过的人变差了?(b) 除了”换成参数更少的方法”,再给出两种不增加标签、可能缓解这个问题的做法,并说出你的预期。

参考答案

(a) 15 个训练例子里,答案完全由名字决定。最省事的解法是”记住这 15 个名字分别对应哪个城市”——全量微调有足够的参数做到这一点,而且它会改动名字的嵌入、注意力等所有部件来实现这种记忆。这些改动把原来”新格式之外”的那条知识通路也搅乱了,所以连训练前碰巧能答对的人(0.29)也变差了。

(b) 几种做法(这里只给预期,未经验证):

  1. 把权重拉回原点:在损失里加 λ∥θ−θLM∥2\lambda \lVert \theta - \theta_{\text{LM}} \rVert^2(L2-SP 一类的正则),让模型只能做小的改动。预期:背答案变难,同类新人有所提高,但可能也学得更慢。
  2. 增加同一事实的说法:用同一批人造多种格式的训练句(例如 “question: …”、“query: …”、“ask: …”),让”写一个城市”不能靠记住某个固定的句式完成。预期:更接近 Allen-Zhu & Li 的”知识增强”(第 5 讲 §6.2),泛化更好。
  3. 回放:同时在预训练文本上做语言建模(代码 04),保住原有的知识通路。
  4. 早停:用一小部分训练例子做开发集——但这里只有 15 个,开发集会非常吵。

练习 8(改一行:编辑哪一层) 代码 05 默认编辑第 2 层(EDIT_LAYER = 1)的 MLP。把它改成 0(第 1 层),根据演示 1 的因果追踪,预测 ROME 式编辑的可靠性、泛化和局部性会怎样。

参考答案

因果追踪说第 1 层在最后一个位置的作用很小(放回去只恢复 0.039),城市主要是第 2 层 MLP 回忆出来的。在第 1 层写入新事实,仍然可以通过优化 v∗v^* 让编辑句答对(可靠性仍是 1.00),但第 1 层的输出还要经过第 2 层再处理,换一种说法时很难保持。

我在一份代码副本里跑的结果:可靠性 1.00,泛化 0.10–0.60(第 2 层是 0.60–0.70),别人的城市 0.70(第 2 层 0.88),别人被改成新城市 0.20(第 2 层 0.07)。先定位、再编辑是 ROME 的核心:编辑放在事实”真正被回忆”的地方,才能既泛化又局部。

练习 9(改一行:回放多少) 代码 04 演示 2 的回放比例是 1/3(3000 条领域记录 + 1500 行原语料)。改成 1/10(333 行原语料),预测领域任务、通用 bpc 和人物事实。

参考答案

我在一份代码副本里跑的结果(领域模型缓存在副本自己的目录里):

回放比例领域 bpc领域任务通用 bpc人物事实原格式问答
0(只用领域数据)0.2271.0004.5030.1330.033
1/100.2331.0000.5950.7000.433
1/30.2470.9650.4840.8670.500

只回放 1/10 就把通用 bpc 从 4.503 拉回 0.595——一点点回放就有巨大的作用;回放越多,原来的能力保得越好,领域上的专注度略降。实际中回放比例是一个要调的超参数,常见的是几个百分点到几十个百分点。

练习 10(特征扭曲的直觉) 用一个两层线性模型 f(x)=v⊤Wxf(x) = v^\top W x 说明 Kumar et al. 的特征扭曲:假设训练数据只在某个子空间 SS 里,分布外的输入有一部分落在 SS 的正交补里。(a) 全量微调时 WW 的哪些部分会变、哪些不会?(b) 为什么随机初始化的 vv 会让问题更严重?(c) LP-FT 为什么能缓解?

参考答案

(a) ∂L/∂W=(∂L/∂f) v x⊤\partial L / \partial W = (\partial L / \partial f)\, v\, x^\top。所有训练样本 x∈Sx \in S,所以 WW 的更新只发生在”输入在 SS 里”的方向上:WW 对 SS 里的输入的作用被改了,对正交补里的输入的作用完全没变。

(b) 同时 vv 也在变。vv 一开始是随机的,训练要把它从随机推到”好的方向”;在这个过程中,WW 对 SS 的部分会被改得去迁就一路变化的 vv。训练结束后,vv 和”新的 WW 在 SS 上的特征”配合得很好,却和”没变的 WW 在正交补上的特征”对不上——分布外的输入里属于正交补的那部分被错误地读出。

(c) LP-FT 先把 vv 训练到一个对原来的 WW 好的位置,此时 ∂L/∂f\partial L / \partial f 已经很小,后面全量微调时 WW 只需要小改;WW 在 SS 上和正交补上的特征基本保持”同一套”,vv 对两者都适用。代码 02 演示 1 的特征变化一栏:FT 1.26–1.31,LP-FT 0.42–0.51。

练习 11(设计一个涟漪测试) 在迷你世界里设计一组”涟漪”问题,检验”kari lives in varno → osku”这次编辑是否被一致地吸收。哪些问题的答案应该变?哪些不应该变?哪些在当前的世界里无法测、需要怎样改 fm_world.py?

参考答案

应该变:所有提到 kari 住处的说法——“kari moved to”、“kari is a baker from”、“the baker kari lives in”、问答格式(代码 05 测了这四个,ROME 式编辑做到六七成)。

不应该变:kari 的职业(“kari works as a”);其他人的城市,尤其是住在 varno 和 osku 的人(“邻居”——ROME 的 Neighborhood Score 就是专门测这些最容易被波及的输入)。

当前世界里测不了的涟漪:城市有一个隐藏属性 region(第 1 讲 §3.4),它从不出现在文本里,所以”kari 住在北方还是南方”无法用文本问——但可以用探针问:编辑后,在 “kari lives in ” 处训练好的 region 探针应该给出 osku 的 region。要做组合推理的测试,可以给 fm_world.py 加一种体裁,例如”<城市> is near the <河流>.”,再问”kari lives near the ___“——编辑后它应该变成 osku 附近的河流。这正是 RippleEdits 里”组合推理”一类的问题,也是现有编辑方法最容易失败的地方。


12. 延伸阅读 · Further reading

原课阅读(W23 Syllabus “Adapting FMs to New Tasks and Data Domains”)

本讲用到的其他论文

2023 年之后

如果你学过本目录下的 CS336,它的后训练部分对 SFT 的工程细节讲得更深;本讲的重点是各种适配方法的取舍和”为什么”。


13. 下一讲预告 · Next lecture

这一讲的适配都是”为一个任务”做的。代码 02 留下了一个刺眼的结果:只在”问城市”上微调过的模型,被问到职业时还是答城市——它学会的是那一种问题,而不是”回答问题”这件事。

第 7 讲:指令微调与对齐——从 FLAN、T0 到 RLHF 与 Constitutional AI。 如果微调数据里有成百上千种任务、每种都用自然语言写成指令,模型能不能学会”跟着指令走”,在从没见过的任务上也能做?FLAN 和 T0 的回答是能——但只对足够大的模型(第 5 讲 §1.2:80 亿参数以下反而有害)。再往前一步,“有用、诚实、无害”这样的目标写不成标注数据,InstructGPT 用人类的偏好训练一个奖励模型、再用强化学习优化它;Anthropic(Jared Kaplan 的客座报告)则用一组写成文字的原则,让模型自己给回答提意见、打分(Constitutional AI)。这些都是”适配”,只是适配的目标从”一个任务”变成了”听懂指令、符合人的偏好”。

← 返回课程目录