第 6 讲:上下文学习、面向任务的微调与参数高效微调

Large Language Models: Methods and Applications · Lecture 06。个人学习讲义与配套实验,按主题整理并持续修订。

课程源码 ZIP ↓

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

本篇目录

对应原课:CMU 11-667 Fall 2025 · Lecture 6 · Thu 11-Sep-2025 · In-context learning, task-oriented finetuning, and parameter-efficient tuning methods(Yubin Kim、Jaromir Savelka;多任务一节沿用 Daphne Ippolito、Chenyan Xiong 的往届 slides) 原课 slides:Customizing LLMs via full model finetuning(课表上标为 finetuning) 阅读材料:Lester et al. (2021), The Power of Scale for Parameter-Efficient Prompt Tuning;Ding et al. (2022), Delta Tuning: A Comprehensive Study of Parameter Efficient Methods for Pre-trained Language Models;一篇 PEFT 科普博客 配套代码:code/lecture06/(2 个模块 + 5 个演示脚本,每个不超过 2 分钟) 本讲的写法:骨架来自原课 slides;花园世界上的”布置新任务”实验是补充的。所有数字都来自配套代码的实际输出。


目录


0. 本讲导读

0.1 这一讲要回答的问题

前五讲我们一直在”造”语言模型:积木、结构、数据、改进、评估。从这一讲开始进入第二单元”使用与适配”。手里有了一个预训练好的模型,它只会做一件事——续写。可我们要它做的是分类、抽取、翻译、改写、回答问题……

slides 列出了三条路:

  1. 一个参数都不改:把任务写进输入,让”续写”恰好就是答案——提示(prompting)、上下文学习(in-context learning, ICL);
  2. 改全部参数:在任务数据上继续训练——面向任务的全参数微调(full finetuning);
  3. 只改很少的参数:参数高效微调(parameter-efficient finetuning, PEFT)——prompt tuning、prefix tuning、adapter、BitFit、LoRA、(IA)³……

还有一条和它们正交的路:一次训练很多任务——多任务微调,后来演变成今天所有聊天模型都要经过的指令微调(instruction tuning)。

这一讲要回答:

  • 每条路分别要改多少参数、要多少数据、效果怎样、代价是什么?
  • 为什么有的模型”一提示就会”,有的模型怎么提示都不会?
  • 只训练 1% 的参数,凭什么能接近全参数微调?什么时候不能?
  • 一个模型在训练时从没见过的指令,它为什么有可能做对?

0.2 学完这一讲你应该能

  • 说出”预训练 + 微调”范式取代”一个任务一个模型”的原因,以及全参数微调的五个问题;
  • 区分零样本 / 少样本提示,解释模板和标签词(verbalizer)的作用,以及上下文学习为什么依赖预训练数据;
  • 按”加法 / 指定 / 重参数化”三类说出主要的 PEFT 方法,写出它们的公式、参数量和初始化方式;
  • 解释 LoRA 的低秩假设、合并、秩的选择,以及 QLoRA 为什么能省下大部分显存;
  • 解释多任务 / 指令微调带来零样本泛化的机制,以及它的局限;
  • 根据数据量、算力、部署需求,在 ICL、PEFT、全参数微调之间做选择。

0.3 本讲的实验平台

三个新任务(garden_tasks.py),都用同一种格式:

<bos> [<x1> 指令 <x2>] 输入句子 <x0> 输出 <eos>

<x0> 是第 2 讲扩展词表里的一个哨兵 token,这里当作”答案从这里开始”的分隔符——相当于聊天模型模板里的 “Assistant:“。只有 <x0> 之后的 token 计入损失(第 1 讲 §2.2 的损失掩码)。

任务例子(输入 → 输出)需要什么本事
分类my friend feeds the cat with their friends . → hungry知道名词属于哪一类(标签词:水果 ripe、蔬菜 fresh、花 pretty、动物 hungry)
抽取同上 → cat找到宾语名词,照抄出来
改写同上 → she feeds the cat with her friends .照抄整句,同时改掉主语、动词形式和所有物主代词

句子按真实分布采样、互不重复,切成测试 300 / 开发 200 / 训练 2500 句。

两个基座模型,结构完全相同(第 2 讲的 2 层、64 维 Transformer,106,496 个参数),只是预训练数据不同:

  • 基座 A:第 2 讲训练好的模型,只在花园文档上预训练。花园文档里的句子彼此独立,从来不需要照抄前文。
  • 基座 B:在”带复读”的花园语料上预训练——每句话说完后,以 20% 的概率原样再说一遍。真实网页里到处是重复(引用、转述、页眉页脚、代码里的变量名),在这样的数据上预训练,模型会学会”照抄上文”(Olsson et al. 2022 的归纳头 induction head)。基座 B 在标准测试集上的困惑度是 3.937,比基座 A 的 3.871 略高(它把一点概率留给了”这句话再说一遍”)。

两个基座的对比会贯穿这一讲:同一个适配方法,在”会”和”不会”某种本事的基座上,结果天差地别。


1. 从”一个任务一个模型”到”预训练 + 微调”

1.1 几年前的做法:每个任务单独训练一个模型

slides 的第一页提醒我们,几年前大家是这样理解”语言模型 + NLP”的:每个任务一个模型,只用这个任务的数据从头训练。原版 Transformer(Vaswani et al. 2017)是在机器翻译数据上训练的翻译模型;最早基于语言模型的聊天机器人论文(Vinyals & Le 2015, A Neural Conversational Model)是在电影字幕上训练的。

问题是显然的:每个任务都要大量标注数据;任务之间学到的东西(语法、常识)没法共享。

1.2 2018 年前后的范式转变

一个语言模型先在通用数据上预训练,学会”通用的语言理解”,再针对每个任务微调:

  • 预训练阶段:在大量”通用”数据上训练,模型从随机初始化开始。常见数据:网页爬取、书籍、维基百科、GitHub。
  • 任务微调阶段:在你希望模型擅长的任务数据上继续训练。常见任务:问答、摘要、翻译。

两个代表:

  • BERT(Devlin et al. 2018):预训练一个 encoder-only 模型,目标是掩码填空和”下一句预测”;每个 NLP 任务微调一次。
  • GPT-1(Radford et al. 2018):预训练一个 decoder-only 模型,目标是语言建模;每个 NLP 任务微调一次。

为什么这样好?预训练把”语言本身”的知识(语法、搭配、事实)从海量无标注文本里学出来,微调只需要用少量标注数据教会”这个任务要什么”。§3.2 的实验会告诉我们,这个”只需要少量数据”是有前提的。

1.3 全参数微调的五个问题

slides 列出了全参数微调的缺点:

  1. 一个任务一个模型,对小模型还行,对今天的大模型不行;
  2. 每个任务分别训练,比所有任务一起训练更贵;
  3. 小数据集上容易过拟合 / 灾难性遗忘(catastrophic forgetting);
  4. 每个任务一份检查点,存储很贵;
  5. 推理时要把这么多模型同时放在 GPU 上,也很贵。

1.4 三条出路

slides 给出的解决方向:

  • 完全不微调 → 上下文学习(§2);
  • 只微调一小部分参数 → 参数高效微调(§4、§5);
  • 多任务微调 → 指令微调(§6)。

2. 不改参数:提示与上下文学习

2.1 GPT-2 与 GPT-3:语言模型”自己学会了”做任务

GPT-2(Radford et al. 2019)的论文标题是 Language Models are Unsupervised Multitask Learners。作者在一个几百万网页组成的数据集 WebText 上训练语言模型,发现它”开始在没有任何显式监督的情况下学会问答、翻译、阅读理解和摘要”,而且这种行为随模型变大逐渐”涌现”。例如在文章后面加上 “TL;DR:“,模型就会续写出一段摘要——因为 Reddit 上大量帖子正是这样写的。

GPT-3(Brown et al. 2020, Language Models are Few-Shot Learners)把这一点推到极致:1750 亿参数的模型,在提示里放几个示例,就能做很多任务,有些接近专门微调的模型。

2.2 零样本、少样本、模板与标签词

上下文学习:完成任务所需的全部信息都写在给语言模型的文字提示里。

  • 零样本(zero-shot):提示里只有任务说明,没有示例;
  • 少样本(few-shot):提示里既有任务说明,也有几个”题目 + 答案”的示例。

slides 的三个例子:

零样本情感分类。模板是”Review: <评论> On a 1 to 4 star scale, the reviewer would probably give this restaurant a <标签>“。对每个可能的标签都填一遍,看模型给哪个标签的概率最高:一篇夸匹兹堡汉堡店的评论,P(“1”) = 0.0016、P(“2”) = 0.0032、P(“3”) = 0.012、P(“4”) = 0.78,输出 4 星。

零样本摘要。模板是”<标题> <文章> TL;DR;“,模型的续写就是摘要。

少样本翻译。模板是若干行”<英文> = <中文>“,最后一行只有英文和等号:

The dog chased a squirrel at the park. = 那只狗在公园里追一只松鼠。
I was late for class. = 我上课迟到了。
The hippopotamus ate my homework. =

模型续写出”河马吃了我的家庭作业。”

同一个任务可以有很多种写法:前面加不加一句 “Translate from English to Chinese.”、用 ”=” 还是 “English: … Chinese: …”。这些不同的模板叫作 verbalizer(把任务”说成话”的方式;更窄的用法专指把类别映射到标签词的那一步)。第 5 讲 §5.3 已经看到,模板的小改动能让分数变化好几个百分点。

2.3 实验:直接问基座模型

代码 01 §1 把三个任务的输入直接喂给两个基座:

基座分类抽取改写<x0> 之后最想说的 token
A0.0%0.0%0.0%.(0.84)、from(0.05)、in(0.04)
B0.0%0.0%0.0%.(0.91)、with(0.05)、from(0.01)

两个模型都把 <x0> 当成了句子中间某个不认识的词,最想做的是用句号把句子结束掉。它们不知道”<x0> 后面该说答案”——这个格式在预训练数据里从来没有出现过。GPT-3 能做少样本翻译,是因为网上有大量”英文 = 中文”式的双语对照;我们的模型没见过任何”X → Y”式的成对文本。

2.4 实验:零样本——把任务翻译成模型熟悉的续写

既然模型只会续写花园句子,那就把分类任务写成花园句子。代码 01 §2 用”噪声信道”式打分(Min et al. 2022, Noisy Channel Language Model Prompting):每个类别 cc 对应一段提示 ctxc\text{ctx}_c,看名词 nn 作为它的续写有多大概率,

score(c)=log⁡p(n∣ctxc),c^=arg⁡max⁡cscore(c).\text{score}(c) = \log p(n \mid \text{ctx}_c), \qquad \hat{c} = \arg\max_c \text{score}(c).

模板提示(水果 / 蔬菜 / 花 / 动物)准确率各类别
1:形容词I eat the ripe / I eat the fresh / I water the pretty / I feed the hungry100.0%全部 100%
2:动词I eat the / I eat the / I water the / I feed the75.3%蔬菜 0%
3:另一组动词I pick the / I grow the / I water the / I feed the74.3%水果 83%、蔬菜 39%、花 81%、动物 100%

模板 1 的每个形容词只修饰一类名词,标签之间没有重叠,基座 A 学过这些搭配,于是 100%。模板 2 里水果和蔬菜用了同一段提示,分数永远并列,蔬菜一个都分不对。模板 3 的 grow 后面水果、蔬菜、花都可以,“蔬菜”的提示不够专一。

同一个模型、同一个任务,只换提示的写法,准确率从 74% 到 100%。这就是”提示工程”要做的事,也是它的问题:好的模板要靠人对任务(和对预训练数据)的了解去试出来。注意这里零样本能成功,是因为分类所需的知识(名词的类别)已经在模型里,我们只是找到了一种续写方式把它”读”出来。

2.5 实验:少样本——示例什么时候有用

代码 01 §3(a) 用任务格式写示例:‘句子 <x0> 名词’ × kk 个,再放要问的句子和 <x0>:

k(示例数)0124816
基座 A0.0%0.0%0.0%0.0%0.0%0.0%
基座 B0.0%0.0%0.0%0.0%0.0%0.0%

示例再多也没用。两个基座都没在预训练里见过”成对出现”的格式,不知道示例是在”教”它什么。

代码 01 §3(b) 换一种预训练见过的格式:kk 句宾语相同的话,再给一句只说到 the 的话,看模型能不能接上那个名词。例如 the children feed the goat . he feeds the goat . I feed the ___。

k01248
基座 A:准确率(正确名词的概率)6.3%(0.041)5.0%(0.041)6.0%(0.041)5.0%(0.039)3.7%(0.037)
基座 B5.3%(0.041)16.3%(0.081)15.0%(0.075)13.7%(0.073)13.3%(0.070)

基座 A 完全不利用示例:kk 从 0 到 8,正确名词的概率基本不变(甚至略降)。基座 B 看到 1 个示例后概率翻倍、准确率从 5% 升到 16%——它在复读语料里学会了”前面出现过的词,后面可能再出现”。

2.6 小结:上下文学习不是凭空出现的

这两个实验说明了一件在大模型身上很难看清的事:上下文学习要靠预训练数据先奖励”利用上下文”这件事。真实网页里的列表、问答、对照翻译、重复,让大模型学会了从上下文里推断”现在在做什么”;我们的花园文档没有这些,模型就学不会。这也解释了 slides 后面(第 9 讲)的一句话:“任务的实例本来就存在于预训练数据里……少样本示例只是’教’模型这个任务的模板。”

第 9 讲会造一个专门的世界,研究上下文学习的各种”意外行为”(示例的标签全错了居然还能工作)。在这一讲的小模型上,提示远远不够——这正是要微调的原因。


3. 全参数微调

3.1 做法

全参数微调就是”接着训练”:拿预训练好的模型,用任务数据、标准的交叉熵损失、只对答案部分算损失,更新所有参数。和预训练相比,通常有几处不同:

  • 学习率更小、步数少得多(数据少,步子大了容易把预训练学到的东西冲掉);
  • 数据少时要按开发集挑检查点(提前停止,P2 §6.4);
  • 只对答案算损失(输入部分的 token 是给定的,不需要模型去”预测”)。

我们的设置(garden_tasks.finetune):Adam,学习率 3e-3(开发集网格搜索选出),30 步预热后余弦衰减,固定 600 步(数据量不同时步数相同,比较才公平),每 100 步在开发集上评估一次、留下最好的检查点。

3.2 预训练到底省了多少数据?

常说预训练的好处是”省数据”。代码 02 §1 比较三个起点——随机初始化、基座 A、基座 B——在 8 到 512 条任务数据上的表现:

抽取任务(看答案的第一个 token 是否正确):

起点N=8N=32N=128N=512
随机初始化7.7%11.7%79.0%100.0%
基座 A(无复读)6.3%6.7%45.7%100.0%
基座 B(有复读)14.7%49.3%91.0%100.0%

改写任务(贪心生成整句,完全一致才算对):

起点N=8N=32N=128N=512
随机初始化1.0%3.0%60.7%99.7%
基座 A(无复读)1.0%2.3%20.3%95.0%
基座 B(有复读)1.7%23.7%71.7%98.7%

三个发现:

  1. 基座 A 不但没帮上忙,在 N=128 时还明显不如随机初始化(抽取 46% vs 79%,改写 20% vs 61%)——这叫负迁移(negative transfer)。这两个任务真正需要的是”把输入里的词照抄出来”,基座 A 从没学过;它学到的东西(比如”句号后面开始一句新话”)反而要先被改掉。
  2. 基座 B 在数据少时遥遥领先:N=32 时抽取 49% 对 12%,改写 24% 对 3%。它在预训练里已经学会了复制,微调只需要教它”复制哪一个”。
  3. 数据足够多(N=512)时三个起点都接近 100%,预训练的优势消失。

**预训练能省下多少任务数据,取决于预训练教会的本事和任务需要的本事有多少重合。**大模型的预训练数据五花八门,“重合”几乎总是很大,于是我们习惯了”预训练总是有用”;但这个前提不是自动成立的。

3.3 灾难性遗忘

微调完的模型,还是一个好的语言模型吗?代码 02 §2 在基座 B 上做改写任务(512 条),每 100 步测一次”把模型当普通语言模型用”时在花园测试文档上的困惑度(微调前 3.937,真实分布的下限约 3.66):

做法第 100 / 200 / 300 / 400 / 500 / 600 步的困惑度改写准确率最终困惑度
全参数,学习率 3e-326.60 / 35.62 / 40.35 / 43.48 / 45.20 / 46.1098.7%26.60
全参数,学习率 3e-46.04 / 7.75 / 8.82 / 9.48 / 9.85 / 10.0494.3%10.04
全参数 3e-3 + 混入预训练数据4.30 / 5.42 / 4.16 / 4.48 / 4.11 / 4.28100.0%4.48
LoRA(r=4,训练 1.9% 的参数)6.70 / 8.33 / 9.21 / 9.39 / 9.58 / 9.7076.0%9.39
└ 把 LoRA 拆掉之后0.0%3.937

(“最终困惑度”对应按开发集挑出的那个检查点。)

  • 全参数微调 100 步之内,困惑度就从 3.94 涨到 26.6:模型把所有参数都挪去适应新任务了。这就是灾难性遗忘(McCloskey & Cohen 1989)。
  • 降低学习率只是让遗忘慢一些——600 步后还是涨到 10。
  • 混入预训练数据”复习”(rehearsal)最有效:每一步除了任务数据,再算一批预训练文档的语言模型损失,困惑度只到 4.5,任务准确率也没有损失。代价是每步多算一批数据。InstructGPT(Ouyang et al. 2022)的 PPO-ptx 用的正是这个办法:在 RLHF 时混入预训练梯度,减少所谓的”对齐税”(alignment tax)。
  • LoRA 一个原参数都不改:拆掉就是原模型,困惑度和微调前完全一样(任务当然也不会做了);装着 LoRA 时困惑度 9.4,遗忘比全参数轻,但任务准确率也低一截(76%)。

3.4 存储账

任务数每个任务一份完整模型(fp32)一个基座 + 每个任务一份 LoRA(r=4,q/v,2,048 个参数)
1416 KB424 KB
104,160 KB496 KB
10041,600 KB1,216 KB

放大到 Llama-2-7B(fp16):一份完整模型约 13.5 GB;q、v 上 r=8 的 LoRA 约 420 万个参数、8 MB。100 个任务:完整模型 1.35 TB,LoRA 方案 13.5 GB + 0.8 GB。推理时也一样:一块 GPU 放一个基座,按请求换 LoRA。这就是 slides 列出的第 4、5 个问题,也是 PEFT 的出发点。


4. 参数高效微调(PEFT)

4.1 三大类

PEFT:不微调整个模型,只微调很少量的权重。slides 按 Delta Tuning 综述(Ding et al. 2022)把它们分成三类:

类别问题方法
加法(Addition)加入新的可训练参数,只训练它们?prompt tuning、prefix tuning、adapter、compacter
指定(Specification)挑出原模型参数的一小部分,只训练它们?层冻结、BitFit、diff pruning
重参数化(Reparameterization)把模型改写成更容易训练的形式?LoRA、QLoRA、(IA)³

下面逐个讲清楚”改了哪里、加了多少参数、为什么这样初始化”。代码在 peft.py,每种方法都是几十行。

4.2 加法类

Prompt tuning(Lester et al. 2021)。直觉:提示工程要人反复试词语,很麻烦、很玄学。既然手里有任务数据,为什么不用梯度下降去找提示?离散的 token 没法求导,于是退一步:在输入 token 的嵌入前面接 PP 个可学习的向量(“软提示” soft prompt),其余一切冻结:

[p1,…,pP⏟可训练,  e(x1),…,e(xT)]→冻结的 Transformer.[\underbrace{\mathbf{p}_1, \dots, \mathbf{p}_P}_{\text{可训练}}, \; \mathbf{e}(x_1), \dots, \mathbf{e}(x_T)] \to \text{冻结的 Transformer}.

参数量只有 P×dP \times d。Lester et al. 用的是”LM 改装过的 T5”(在 T5 的基础上再用语言建模目标训练了 10 万步,因为原版 T5 只会做 span corruption,见第 2 讲)。

Prefix tuning(Li & Liang 2021)。同样的想法,但可学习的向量不只加在输入层,而是加在每一层注意力的 K、V 前面:每个 query 除了看真实 token,还能看 PP 个”虚拟 token”。参数量 L×2×P×dL \times 2 \times P \times d。prompt tuning 的软 token 要靠后面的层把影响”传上去”;prefix tuning 直接在每一层提供可学习的 K、V,影响更直接。初始化:slides 指出,用真实词的激活来初始化前缀,比随机初始化效果好。

slides 总结的优点:学到的嵌入很小(几 MB 甚至更少),每个任务存一份很便宜;预训练模型加载在服务器上,推理时按任务换上对应的嵌入(例如每个用户一套,做个性化)。缺点:收敛比全参数微调慢得多;前缀长度不好选——前缀占掉的每个位置,都是从真实任务那里借来的上下文;学到的嵌入不可解释(它们不对应任何真实的词)。

Adapter(Houlsby et al. 2019)。在预训练网络的层与层之间插入新的小模块,原权重固定,只训练这些模块。每个 adapter 是一个瓶颈 MLP,加在子层输出后面、残差相加之前:

h←h+Wup σ(Wdown h),Wdown∈Rm×d,  Wup∈Rd×m,  m≪d.\mathbf{h} \leftarrow \mathbf{h} + W_\text{up}\, \sigma(W_\text{down}\, \mathbf{h}), \qquad W_\text{down} \in \mathbb{R}^{m \times d},\; W_\text{up} \in \mathbb{R}^{d \times m},\; m \ll d.

初始化:WupW_\text{up} 全 0,刚装上时整个 adapter 是恒等映射——“让插入 adapter 后的模块输出和原模型一样”(slides 原话)。从一个已经很好的起点出发,而不是一开始就把模型搞乱。

slides 总结的优点:比 prompt / prefix tuning 收敛快;在多任务场景里效果好(AdapterFusion 可以组合多个任务的 adapter);比全参数微调训练快;可能对训练数据里的对抗扰动更鲁棒。缺点:加了新层,推理变慢;模型变大(新参数随层数增长)。

4.3 指定类

层冻结。研究发现 Transformer 靠前的层更多地捕捉语言现象和基本的语言理解,靠后的层是任务相关的学习发生的地方。所以可以冻结前面的层,只训练后面几层。

BitFit(Ben Zaken et al. 2022):只训练偏置项(线性层的 bias、LayerNorm 的 bias)。偏置只是给每个神经元的输入”挪一挪零点”,在 BERT 上不到 0.1% 的参数,却能做到接近全参数微调——这说明在那些任务上,微调很大程度上是”调整已有特征的开关和阈值”,而不是学新特征。

Diff pruning(Guo et al. 2021):前面的方法里,“冻结哪些、训练哪些”是人定的。为什么不让模型自己学?给每个参数学一个可加的改变量 δ\delta,用 L0 范数惩罚鼓励 δ\delta 稀疏——模型自己决定哪些参数值得改。

4.4 重参数化类

直觉:微调的内在维度(intrinsic dimension)很低。Aghajanyan et al. (2021) 发现,只在一个 200 维的随机子空间里优化(再投影回全部参数),就能让 RoBERTa 在 MRPC 上达到全参数微调 90% 的效果。既然需要改的”自由度”不多,就可以用低维的代理参数来表示改变量,只优化代理。

LoRA(Hu et al. 2021)。直觉:不只是模型权重低秩,权重的改变量也是低秩的。冻结预训练权重 WW,在每层注入可训练的秩分解矩阵:

y=Wx+αr BA x,A∈Rr×din,  B∈Rdout×r,  r≪d.\mathbf{y} = W\mathbf{x} + \frac{\alpha}{r}\, B A\, \mathbf{x}, \qquad A \in \mathbb{R}^{r \times d_\text{in}},\; B \in \mathbb{R}^{d_\text{out} \times r},\; r \ll d.

和 diff pruning 一样,LoRA 学的是加到每个权重上的改变量 ΔW=BA\Delta W = BA,只是这个改变量被重参数化成了低维的形式,参数量从 doutdind_\text{out} d_\text{in} 降到 r(din+dout)r(d_\text{in} + d_\text{out})。初始化:AA 随机,BB 全 0——训练开始时 ΔW=0\Delta W = 0。(如果 AA、BB 都是 0,梯度也全是 0,永远学不动;都随机,起点就被破坏了。练习 3。)原论文只改注意力的权重(通常是 WqW_q、WvW_v),其余不动。

(IA)³(Liu et al. 2022, Few-Shot Parameter-Efficient Fine-Tuning is Better and Cheaper than In-Context Learning),意在改进 LoRA,三个目标:要加 / 改的参数尽量少;只用少量样本就能达到高准确率;允许一个批次里混着不同任务。做法:不学”加多少”,只学”放大 / 抑制多少”——用三个可学习的向量逐元素缩放 K、V 和 FFN 的中间激活:

K←lk⊙K,V←lv⊙V,FFN(x)=W2(lff⊙σ(W1x)).K \leftarrow \mathbf{l}_k \odot K, \quad V \leftarrow \mathbf{l}_v \odot V, \quad \text{FFN}(\mathbf{x}) = W_2\big(\mathbf{l}_\text{ff} \odot \sigma(W_1 \mathbf{x})\big).

向量初始化为全 1(不改变模型)。和 LoRA 的区别:LoRA 学注意力权重的低秩改变量,(IA)³ 学可注入的向量。因为只是逐元素乘法,同一批里每个样本可以乘不同任务的向量。

slides 总结的优点:比全参数微调训练快;换任务只要换上训练过的那一小部分权重;LoRA 在各种任务上都被证明很有效;QLoRA 更省(§5.4)。

4.5 实验:参数量

代码 03 §1(模型共 106,496 个参数):

方法类别可训练参数占全模型推理时的额外开销
全参数微调—106,496100.00%无
只训最后一层指定50,11247.06%无
BitFit(偏置)指定1,4721.38%无
LoRA r=4(q、v)重参数化2,0481.92%合并后无
(IA)³重参数化7680.72%合并后无
Adapter m=8加法4,3844.12%多两层 / 子层
Prefix P=8加法2,0481.92%每层多 8 个 K/V
Prompt P=8加法5120.48%序列长 8

怎么算的:LoRA 每个矩阵 r(64+64)=512r(64+64) = 512,q、v 两个矩阵 × 2 层 = 2,048;(IA)³ 每层 64+64+256=38464 + 64 + 256 = 384;Adapter 每个 2×64×8+8+64=1,0962 \times 64 \times 8 + 8 + 64 = 1{,}096,注意力和 FFN 后各一个 × 2 层;Prefix 每层 8 个 K + 8 个 V,各 64 维。我们的小模型里偏置占的比例(1.38%)比大模型高得多(BERT 里不到 0.1%)。

学习率要分别选。在开发集上网格搜索(完整结果见代码 README)的结论:可训练参数越少,要的学习率越大——全参数微调最好的是 1e-3~3e-3,LoRA、Adapter 3e-2,BitFit、Prompt 1e-1,(IA)³ 和 Prefix 3e-1。少数参数要”挪”得更远,才能产生同样大的效果。用同一个学习率比较各种方法是不公平的。

4.6 实验:三个任务上的成绩

代码 03 §2:基座 B,512 条数据,600 步:

方法分类抽取改写改写:开发集准确率(第 100…600 步)装着它时的困惑度
全参数微调100.0%100.0%98.7%100 100 100 100 100 10026.60
只训最后一层100.0%99.3%98.3%88 100 100 100 100 10028.41
BitFit100.0%81.7%25.0%22 34 33 32 32 347.96
LoRA r=4(q、v)98.7%96.3%76.0%50 63 75 84 84 849.39
(IA)³99.7%92.7%48.3%48 50 54 57 56 5815.28
Adapter m=8100.0%94.0%65.0%48 57 64 72 80 7821.53
Prefix P=8100.0%84.7%34.0%26 32 38 41 44 449.48
Prompt P=846.0%36.0%6.0%8 6 8 6 6 63.94(不加提示时)

三个任务难度递增:分类只要读出”名词属于哪一类”(基座已经知道类别),几乎所有方法都接近 100%;抽取要把名词照抄出来;改写要照抄整句并改掉三处。任务越需要”新的计算”,少参数的方法掉得越多:LoRA 和 Adapter 在抽取上离全参数只差几个百分点,在改写上差两三成;BitFit 只调偏置,改写只有四分之一;Prompt tuning 参数最少,也最弱。

改写任务的排序——全参数 ≈ 最后一层 > LoRA > Adapter > (IA)³ > Prefix > BitFit > Prompt——和 Delta Tuning 综述在大模型上的结论基本一致(slides:“全参数 > LoRA > Adapter > Prefix > Prompt”,“如果有资源,全参数微调往往最好”)。收敛曲线也印证了 slides 的说法:prompt / prefix tuning 收敛最慢,600 步后 Prefix 还在缓慢上升。

最后一列值得注意:装着 PEFT 把模型当普通语言模型用,困惑度照样上升(PEFT 方法 8~22),只是比全参数微调(26.6)轻;“只训最后一层”改动的参数最多,遗忘甚至比全参数还重。Prompt 一行不变,是因为测困惑度时没有加软提示;Prefix 装在每一层的注意力里,测的是装着它时的值。所以 PEFT 的真正好处不是”不遗忘”,而是”可拆卸”:原参数一个没动,拆掉就回到原模型。

4.7 实验:PEFT 能”教会”新本事吗?

代码 03 §3 把同样的方法用到没学过复制的基座 A 上:

方法基座 A 抽取基座 B 抽取基座 A 分类基座 B 分类
全参数微调100.0%100.0%100.0%100.0%
只训最后一层99.7%99.3%100.0%100.0%
BitFit17.7%81.7%92.7%100.0%
LoRA r=4(q、v)71.7%96.3%100.0%98.7%
(IA)³61.3%92.7%98.3%99.7%
Adapter m=827.0%94.0%97.7%100.0%
Prefix P=823.0%84.7%100.0%100.0%
Prompt P=810.7%36.0%46.7%46.0%

分类任务:两个基座都知道名词的类别,PEFT 在两个基座上都还行。抽取任务:在基座 A 上,除了全参数和”整个最后一层”这种参数多的做法,其余方法都大幅下降——LoRA、(IA)³ 从九成多掉到六七成,Adapter、BitFit、Prefix、Prompt 掉到一到三成。而且结果不稳定:只换 adapter 参数的随机初始化种子,基座 A 上的 Adapter 从 27% 变成了 63%。

**PEFT 擅长的是调用和组合模型已有的本事,而不是从头教会一种新本事。**基座 A 没有”复制”的电路,少量参数调不出来;基座 B 有,少量参数就能把它”接”到新任务上。这也解释了为什么 PEFT 在大模型上效果好得多:大模型”已经会”的东西多得多。(同样的道理在 §6 还会出现一次。)

4.8 Prompt tuning 与规模

代码 03 §4 调 prompt tuning 的两个旋钮(基座 B,抽取任务):

设置参数测试准确率
随机初始化,P=851236.0%
用真实词初始化,P=851233.7%
随机初始化,P=21282.7%
随机初始化,P=322,04844.7%

加长到 32 个软 token 有帮助,用真实词初始化帮助不大,但在这个 10 万参数的模型上最好也不到五成。**Lester et al. (2021) 的标题就是答案:The Power of Scale。**他们的主要发现:T5 从 6000 万参数增大到 110 亿参数,prompt tuning 和全参数微调的差距从几十分缩小到零;在小模型上,用词表里的词(尤其是类别标签词)初始化比随机初始化好,长度增加到 20 左右有帮助,但这些”技巧”的作用在大模型上都消失了。模型越大,“只改输入”就越能撬动它已有的计算——和 §4.7 是同一个道理。

4.9 怎么选

slides 引用 Delta Tuning 综述的两个结论:

  • 效果:全参数微调 > LoRA > Adapter > Prefix tuning > Prompt tuning(如果有资源,全参数微调往往最好);
  • 显存:全参数微调最多,Adapter、LoRA、BitFit 都少得多。slides 特别注明 prompt tuning 和 prefix tuning 没有画进图里,因为它们的训练显存和全参数微调差不多——梯度仍要穿过整个网络一直反传到输入层,激活值一点没少(省下的只是梯度和优化器状态里那一份)。

可以用的工具:OpenAI、Gemini、Nova / Claude 的微调 API(很可能用的就是这些方法之一,但外界只能猜);HuggingFace PEFT 库(LoRA / QLoRA、prefix tuning、prompt tuning、(IA)³ 都有实现,支持很多模型)。

情况建议
没有训练数据 / 不能训练(只有 API)提示、少样本(§2);第 9 讲的技巧
有几百到几千条数据,要服务很多任务 / 用户LoRA(铺到所有线性层)、QLoRA
有大量数据和算力,只关心一个任务的效果全参数微调,混入一些通用数据防遗忘
要模型泛化到没见过的指令多任务 / 指令微调(§6)

5. LoRA 细看

LoRA 是今天最常用的 PEFT 方法。这一节用代码 04 检查它的几个关键说法。

5.1 ΔW 真的是低秩的吗?

在基座 B 上做全参数微调(改写,512 条,测试准确率 98.7%),然后对每个权重矩阵的改变量 ΔW=W后−W前\Delta W = W_\text{后} - W_\text{前} 做奇异值分解。把奇异值的平方 σi2\sigma_i^2(“能量”)归一化成分布 pip_i,有效秩 =exp⁡(−∑ipilog⁡pi)= \exp(-\sum_i p_i \log p_i):能量全在一个方向上时是 1,均匀分布在 nn 个方向上时是 nn。

矩阵形状ΔW 前 1 / 前 4 / 前 8 的能量ΔW 有效秩W 本身的有效秩同形状随机矩阵
第 0 层 q64×6420% / 62% / 82%13.226.238.3
第 0 层 v64×6420% / 52% / 72%17.131.438.5
第 1 层 q64×6422% / 60% / 83%12.525.437.2
第 1 层 v64×6413% / 41% / 65%20.931.237.9
第 1 层 o64×6414% / 39% / 63%21.033.338.8
第 1 层 FFN up256×6416% / 44% / 65%21.744.956.8
第 1 层 FFN down64×25615% / 36% / 56%27.728.756.4

ΔW\Delta W 的能量明显比随机矩阵集中:前 8 个方向占了 56%~83%,有效秩只有 12~28(同形状的随机矩阵约 38~57),甚至比原权重 WW 本身的有效秩还低。但它也不是严格低秩的:全参数微调走出来的改变量散布在很多方向上。LoRA 赌的是——只在低秩子空间里找改变量,也能找到一个足够好的解(不必和全参数微调找到的那个一样)。这要用下一个实验回答。

5.2 秩取多大?加在哪里?

改写任务,512 条,学习率 3e-2(按 r=4 选定):

加在哪里r可训练参数测试准确率
q、v151225.0%
q、v21,02441.7%
q、v42,04876.0%
q、v168,192100.0%
q、v64(满秩)32,76874.0%
全部 6 种线性层12,30471.7%
全部 6 种线性层49,21699.3%
  • 秩太小是瓶颈:q、v 上 r=1 只有 25%,r=16 达到 100%。
  • 但也不是越大越好:满秩 r=64 反而降到 74%。学习率是按 r=4 选的,没有为每个 r 重调——参数多的配置不一定在同一个学习率下最好训。(原论文的 α/r\alpha / r 缩放就是为了让”换秩时少调学习率”,但并不能完全做到。)
  • 同样的参数预算,“铺得广”和”秩更高”在这里差不多:全部层 r=1(2,304 个)≈ q、v 上 r=4(2,048 个);全部层 r=4(9,216 个)≈ q、v 上 r=16(8,192 个)。

QLoRA 论文(Dettmers et al. 2023)在大模型上的结论更明确:影响最大的超参数是 LoRA 一共加了多少,要铺到所有线性层上才追得上全参数微调,而 r 本身的影响不大。今天 LoRA 的常见默认配置也是”所有线性层,r = 8~64”。

5.3 合并:推理零开销

LoRA 训练完,可以把 ΔW=αrBA\Delta W = \frac{\alpha}{r} BA 直接加回 WW:W′=W+ΔWW' = W + \Delta W。之后的模型和普通模型一模一样,推理不多算任何东西。代码 04 §3 验证:合并前后输出 logits 的最大差异 1.9×10−51.9 \times 10^{-5}(浮点舍入误差),测试准确率不变(99.3%)。

一次前向(128 条,补齐到 24 个 token)的耗时(毫秒,每次运行会有波动):原模型 5.68、LoRA 合并后 5.66(1.00 倍)、LoRA 未合并 6.87(1.21 倍)、Adapter 5.98(1.05 倍)。未合并时每个线性层要多算两次小矩阵乘法;adapter 多出来的层没法合并掉。大模型推理通常受显存带宽限制,adapter 多出来的层还会增加串行的”层数”,生成每个 token 都要付这份钱(slides:“加了新层,推理变慢”)。

合并有一个代价:合并之后就只能服务这一个任务了。要在一个基座上同时服务成百上千个 LoRA(每个用户 / 每个任务一个),就不能合并,而是在一个批次里对不同请求分别加上各自的 BAxBA\mathbf{x}——S-LoRA、Punica 这类系统专门优化这件事(练习 4)。

5.4 QLoRA:4 位的基座 + 高精度的 LoRA

直觉:LoRA 训练时,基座只被”读”、不被”改”。那基座能不能存得粗糙一点?QLoRA(Dettmers et al. 2023)把冻结的基座量化成 4 位,LoRA 的 AA、BB 仍然用 16 位训练;反向传播时梯度穿过反量化后的基座权重,只更新 LoRA。

代码里用最简单的分块 absmax 量化模拟:每 32 个权重一组,按组内最大绝对值 mm 缩放到 [−qmax,qmax][-q_\text{max}, q_\text{max}] 的整数(4 位时 qmax=7q_\text{max} = 7),再乘回去。QLoRA 用的是更讲究的 4 位 NormalFloat(NF4,按正态分布的分位数取 16 个值)和”双重量化”(把每组的缩放因子也量化),思路相同。

基座的权重精度花园测试困惑度
fp323.937
8 位3.937
4 位3.982
3 位4.365
2 位14.811

8 位完全无损,4 位只有一点损失,2 位就坏了。在 4 位基座上训练 LoRA(全部线性层,r=4),改写测试准确率 100.0%,全精度基座上是 99.3%——基座少一点精度影响不大。

省多少显存?按 70 亿参数估算(参数 + 梯度 + Adam 状态,不含激活值):

做法显存
全参数微调(混合精度 + Adam,每参数 16 字节,P3 §4)104.3 GiB
LoRA(fp16 基座 + 0.1% 参数训练)13.1 GiB
QLoRA(4 位基座 + 0.1% 参数训练)3.4 GiB

QLoRA 论文正是靠这一点,在一块 48 GB 的 GPU 上微调了 650 亿参数的模型。


6. 多任务微调与指令微调

6.1 从 T5 到 T0、FLAN

T5 的多任务学习(Raffel et al. 2019):同时在 7 种 NLP 任务的混合数据上训练,每个输入前面加一个很简单的任务前缀:

  • 摘要:summarize: Star Daniel Radcliffe gains access to a reported £20 million as he …
  • 英译法:translate English to French: The legislative assembly decided not to vote on the issue until …

T0(Sanh et al. 2021, Multitask Prompted Training Enables Zero-Shot Task Generalization)把 T5 的想法扩展到自然语言指令:拿 LM 改装过的 T5,在一大批 NLP 任务的混合上微调,每个任务的输入前面加一段描述任务的话,而且同一个任务有很多种说法:

  • Can you write an outline of the following article in a few points? … / Summarise the article: …
  • Does the word “{{word}}” have the same meaning in these two sentences? Yes, No? …
  • What label best describes this news article? … / Is this a piece of news regarding world politics, sports, business, or science and technology? …

这套”任务 + 指令模板”的数据集叫 P3。T0 把任务按类型分组,一部分类型(例如自然语言推理、指代消解、句子补全、词义消歧)整组留作测试、训练时一个都不见,检验模型能不能零样本完成没见过的任务类型。

FLAN(Wei et al. 2022, Finetuned Language Models are Zero-Shot Learners)用 1370 亿参数的模型、62 个数据集(12 个任务类别)做了同样的事,零样本在 25 个数据集中的 20 个上超过了 GPT-3 的零样本。后来的 FLAN Collection(Longpre et al. 2023)扩展到 1800 多个任务,并混合零样本、少样本和思维链格式的模板。

slides 列出的多任务学习的挑战:

  • 每个任务放多少数据(例如多语言翻译里,要不要对低资源语言过采样?T5 比较了按样本数比例混合、按”温度”平滑后的比例混合、等量混合);
  • 能不能泛化到训练时没见过的指令模板;
  • 能不能泛化到训练时没见过的任务;
  • 用户怎样向模型”解释”要它做什么。

多任务训练演变成了指令微调:不应该只在 NLP 基准任务上微调,而应该在真实用户想让模型做的、各种各样的指令上微调。今天,指令微调过的 LLM 是默认选项——模型名字里带 “-Instruct” 或 “-Chat”;没有指令微调过的叫”基座模型”(base model)。现在有大量公开的指令微调数据集(slides 引用了 awesome-instruction-learning 的列表)。

6.2 实验:一个可以精确检验的”没见过的指令”

在花园世界里造一个指令任务(代码 05):

  • 换名词:指令 = [<x5>, 名词 X],输出 = 把输入句子的宾语换成 X(X 和原名词同类,保证结果合法)。
  • 训练时只用 35 个名词当指令;留出 8 个(每类 2 个:cherry、lemon、tomato、cabbage、lily、violet、duck、pony),它们在输入句子里照常出现,只是从来没被当成”换成它”的指令。
  • 另外两个可以混入的任务:照抄(指令 <x3>,输出 = 输入)和抽取(指令 <x4>,输出 = 宾语名词)。
换名词:<x1> <x5> dog <x2> my friend feeds the cat with their friends . <x0>  →  my friend feeds the dog with their friends .
照抄:  <x1> <x3> <x2> my friend feeds the cat with their friends . <x0>      →  my friend feeds the cat with their friends .
抽取:  <x1> <x4> <x2> my friend feeds the cat with their friends . <x0>      →  cat

全部在基座 B 上做全参数微调,1500 步:

训练的任务组合训练样本见过的指令没见过的指令
只有换名词2,500100.0%0.0%
换名词 + 抽取5,000100.0%99.7%
换名词 + 照抄5,000100.0%100.0%
换名词 + 抽取 + 照抄7,500100.0%100.0%

6.3 查表 vs 规则

只训练”换名词”的模型,见过的指令全对,没见过的全错。它输出了什么?

指令 duck   输入 my friend feeds the cat with their friends .  输出 my friend feeds the dog with their friends .
指令 duck   输入 he feeds his hungry hen every day .           输出 he feeds his hungry dog every day .
指令 lemon  输入 I eat the ripe fig in the morning .           输出 I eat the ripe lime in the morning .
指令 pony   输入 they feed the rabbit in the morning .         输出 they feed the goose in the morning .
指令 lily   输入 I water the rose in the morning .             输出 I water the lilac in the morning .

它把没见过的名词换成了某个见过的同类名词。它学到的是”35 个指令各对应输出哪个词”的查表,而不是”把指令里的那个词搬到宾语的位置”的规则。原因很具体:训练时宾语位置上只出现过这 35 个名词,留出的 8 个名词在那个位置上的概率一直被往下压。

混入”照抄”之后,模型在另一个任务里练习了”输入里有什么就输出什么”——对所有名词都一样。这个通用的复制能力被”换名词”任务借用,没见过的指令也能做对了。混入”抽取”(同样要把名词照抄出来,对所有名词都练过)效果几乎一样好。这就是多任务 / 指令微调带来零样本泛化的一个具体机制:不同任务共享底层的本事,一个任务里练出来的本事,可以在另一个任务没见过的情况下派上用场。(练习 6 会看到:在这个设置里,连随机初始化的模型都能从”照抄”任务里学到这种复制能力。)

6.4 指令的种类越多越好

代码 05 §3:换名词 + 抽取,训练用的指令名词从 35 个减到 11 个、4 个(留出的仍是同样那 8 个):

训练用的指令名词数见过的指令没见过的指令
4100.0%2.0%
11100.0%35.3%
35100.0%99.7%

见过的指令越多,模型越难靠”查表”应付,越倾向于学到通用的规则。这和 FLAN 的消融实验一致:指令微调时任务类别越多,在留出的任务上零样本越好。

FLAN 还有一个和规模有关的发现值得记住:指令微调只在足够大的模型上改善没见过的任务——对 80 亿参数及以下的模型,它反而让留出任务上的表现变差(小模型的容量被训练任务”占满”了);到 680 亿、1370 亿参数时才明显变好。我们的实验里泛化之所以能成功,是因为任务被设计得只需要”复制”这一种简单的本事。这和 §4.7、§4.8 是同一个主题:适配方法能做到什么,很大程度上取决于基座已经会什么。


7. 小结、常见误区、练习

7.1 小结

  1. 范式:2018 年前”一个任务一个模型”;之后”预训练 + 每个任务微调”(BERT、GPT-1);全参数微调有五个问题——一个任务一个模型、分别训练更贵、过拟合 / 遗忘、存储贵、部署贵。三条出路:上下文学习、PEFT、多任务 / 指令微调。
  2. 提示与上下文学习:零样本靠模板和标签词把任务写成模型熟悉的续写(同一任务换模板,74%~100%);少样本示例只有在预训练让模型学会”利用上下文”之后才有用(基座 A 完全不用示例,基座 B 5% → 16%)。
  3. 全参数微调:预训练省多少数据取决于预训练和任务需要的本事有多少重合(基座 A 甚至负迁移);会迅速灾难性遗忘(困惑度 3.94 → 26.6),混入预训练数据”复习”最有效(4.5)。
  4. PEFT:加法(prompt、prefix、adapter)、指定(层冻结、BitFit、diff pruning)、重参数化(LoRA、(IA)³)。参数相差 200 倍,效果大致是 全参数 > LoRA > Adapter > (IA)³ > Prefix > BitFit > Prompt;任务越需要新的计算,差距越大;PEFT 调用已有的本事,教不会新本事;它的真正好处是可拆卸,而不是不遗忘;参数越少要的学习率越大。
  5. LoRA:全参数微调的 ΔW 能量集中但不严格低秩;秩太小是瓶颈,同样预算下铺得广和秩更高效果相当;可以合并,推理零开销;QLoRA 把基座压到 4 位几乎不掉点,7B 模型的训练显存从约 104 GiB 降到约 3.4 GiB。
  6. 多任务 / 指令微调:单任务训练学的是查表,没见过的指令 0%;混入共享底层本事的任务后 99.7%~100%;指令种类越多越容易泛化(4 → 11 → 35:2% → 35% → 99.7%);FLAN 发现这种泛化需要足够大的模型。

7.2 常见误区

  • “预训练总能让微调更省数据”:前提是预训练教过任务需要的本事;否则可能负迁移(§3.2)。
  • “少样本示例越多越好”:模型得先在预训练里学会利用上下文,而且示例格式要是它能”读懂”的(§2.5);第 9 讲还会看到更多反直觉的现象。
  • “PEFT 不会遗忘”:装着 PEFT 当普通语言模型用,困惑度照样上升;它的好处是拆掉就回到原模型(§4.6)。
  • “PEFT 和全参数微调效果差不多”:在基座已经会的任务上接近;任务需要新本事时差距很大,而且对初始化种子敏感(§4.6、§4.7)。
  • “所有方法用同一个学习率比较才公平”:恰恰相反,可训练参数越少要的学习率越大,统一学习率会系统性地低估 PEFT(§4.5)。
  • “LoRA 的秩越大越好”:秩太小是瓶颈,但大了也不一定更好;铺到哪些层同样重要(§5.2)。
  • “prompt tuning 没用”:它在小模型上确实弱,但在百亿参数的模型上能追平全参数微调(§4.8)。
  • “指令微调让模型学会了理解指令”:至少在我们的实验里,它学的可能只是查表;真正的泛化依赖任务之间共享的本事和足够多样的指令(§6.3、§6.4)。

7.3 练习

练习 1(LoRA 的参数量):Llama-2-7B 有 32 层,隐藏维度 4096,注意力是标准多头(q、k、v、o 都是 4096×4096)。在 q、k、v、o 上都加 r=8 的 LoRA,一共有多少可训练参数?占全模型(67.4 亿)的多少?

参考答案

每个矩阵 r(din+dout)=8×(4096+4096)=65,536r(d_\text{in} + d_\text{out}) = 8 \times (4096 + 4096) = 65{,}536;每层 4 个矩阵、共 32 层:65,536×4×32=8,388,60865{,}536 \times 4 \times 32 = 8{,}388{,}608,约 840 万,占 8.39×106/6.74×109≈0.12%8.39 \times 10^6 / 6.74 \times 10^9 \approx 0.12\%。用 fp16 存储约 16 MB。(§3.4 用的是只加在 q、v 上的 r=8:约 420 万。)

练习 2((IA)³ 的参数量):同一个 Llama-2-7B,FFN 中间维度 11008。(IA)³ 每层有 lk\mathbf{l}_k、lv\mathbf{l}_v、lff\mathbf{l}_\text{ff} 三个向量,一共多少参数?和练习 1 比呢?

参考答案

每层 4096+4096+11008=19,2004096 + 4096 + 11008 = 19{,}200,32 层共 614,400 个,约占 0.009%——比练习 1 的 LoRA 少约 14 倍。(严格地说,Llama 的 FFN 是 SwiGLU,有两个并列的”上投影”;(IA)³ 缩放的是门控相乘之后、下投影之前的那个 11008 维激活。)这也是 (IA)³ 适合”每个用户一套参数”的原因:60 万个参数,fp16 下约 1.2 MB。

练习 3(LoRA 的初始化):设 y=Wx+BAx\mathbf{y} = W\mathbf{x} + BA\mathbf{x},损失对 y\mathbf{y} 的梯度是 g\mathbf{g}。写出 ∂L/∂A\partial L / \partial A 和 ∂L/∂B\partial L / \partial B。说明:(a) 为什么 B=0B = 0、AA 随机时训练能开始;(b) 为什么 A=B=0A = B = 0 时永远学不动;(c) 为什么不让 AA、BB 都随机。

参考答案

∂L/∂B=g (Ax)⊤\partial L / \partial B = \mathbf{g}\,(A\mathbf{x})^\top,∂L/∂A=B⊤g x⊤\partial L / \partial A = B^\top \mathbf{g}\, \mathbf{x}^\top。(a) B=0B = 0 时 ∂L/∂A=0\partial L / \partial A = 0,但 Ax≠0A\mathbf{x} \ne 0,所以 ∂L/∂B≠0\partial L / \partial B \ne 0:第一步先更新 BB,之后 B≠0B \ne 0,AA 也开始更新。而且此时 ΔW=0\Delta W = 0,模型和原来一模一样。(b) A=B=0A = B = 0 时两个梯度都是 0,参数永远停在原地——这是一个”鞍点”。(c) 都随机时 ΔW=BA≠0\Delta W = BA \ne 0,训练一开始就在原模型上加了一个随机扰动,把”已经很好的起点”破坏掉了。adapter 把 WupW_\text{up} 初始化成 0、(IA)³ 把向量初始化成 1,都是同一个原则:刚装上时不改变模型。

练习 4(思考:合并与多任务服务):一个服务要同时为 1000 个客户提供各自微调过的模型(每人一个 LoRA)。(a) 能不能把 LoRA 合并进基座?(b) 一个批次里有来自 10 个不同客户的请求,怎样一次前向算完?代价是什么?(c) 换成 (IA)³ 呢?

参考答案

(a) 不能:合并后基座就只属于一个客户了。为每个客户存一份合并后的完整模型,就退回到”一个任务一个模型”。(b) 不合并:所有请求共享 WxW\mathbf{x} 这一次大矩阵乘法,再对每个请求加上它自己的 BiAixB_i A_i \mathbf{x}——这是许多个小矩阵乘法(按客户分组,或者用专门的”分段 / 收集”内核,S-LoRA、Punica 就是做这件事的)。代价是每个线性层多一些小计算和显存访问(代码 04 里未合并的 LoRA 慢了约 21%),以及要把很多 LoRA 放在 GPU 显存里(或按需换入)。(c) (IA)³ 更简单:每个请求乘自己的缩放向量,就是一次逐元素乘法,可以直接用”每个样本一个向量”的批量运算——这正是 (IA)³ 论文强调的”允许混合任务的批次”。

练习 5(证明:prompt tuning 是一种受约束的 prefix tuning):在因果注意力的 decoder-only 模型里,软提示放在最前面。证明:软提示位置在每一层产生的 K、V 与后面的真实输入无关。由此说明 prompt tuning 相当于一种”每层的前缀 K、V 被约束住”的 prefix tuning,它的表达能力不超过 prefix tuning(假设长度相同)。

参考答案

因果掩码下,位置 ii 只看位置 0..i0..i。软提示占位置 0..P−10..P-1,所以它们在第 1 层的隐藏状态只依赖软提示本身(和它们的位置编码),与位置 ≥P\ge P 的真实输入无关;归纳下去,每一层软提示位置的隐藏状态都与输入无关,于是它们投影出来的 K、V 也是与输入无关的常量。真实 token 在每一层看到的,就是”PP 个固定的 K、V + 真实 token 自己的 K、V”——这正是 prefix tuning 的形式。区别在于:prefix tuning 的每层 K、V(L×2×P×dL \times 2 \times P \times d 个数)可以自由取值;prompt tuning 的每层 K、V 必须是”P×dP \times d 个输入向量经过冻结网络”算出来的结果,是前者的一个子集。所以同样长度下 prompt tuning 的表达能力不超过 prefix tuning,这和 §4.6 里 Prefix(34.0%)好于 Prompt(6.0%)一致。(如果是双向注意力的 encoder,软提示会看到输入,这个论证就不成立了。)

练习 6(动手:换一个基座做多任务):把 05_multitask_instruction.py 第 2 节的基座 B 换成基座 A(G.base_model())和随机初始化(G.fresh_model()),只跑”只有换名词”和”换名词 + 照抄”两种组合。先预测,再运行验证。

参考答案(作者机器上的实际结果)
起点只有换名词:见过 / 没见过换名词 + 照抄:见过 / 没见过
基座 A100% / 0.0%100% / 99.7%
随机初始化100% / 0.0%100% / 99.3%
基座 B(§6.2)100% / 0.0%100% / 100%

三个起点的结果几乎一样。原因:这里是全参数微调、有 2500 条照抄数据,“照抄”任务本身就足以把复制能力教会,基座有没有这个本事不再重要。这和 §4.7 的结论并不矛盾:那里是 PEFT(参数很少)+ 单任务,基座缺的本事补不上;这里是全参数 + 一个专门练这个本事的任务。真实的指令微调数据里也有大量”照抄 / 改写 / 抽取”类的任务,它们承担了类似的角色。

练习 7(量化误差):分块 absmax 量化,每块 32 个权重,4 位(qmax=7q_\text{max} = 7)。一个块里绝对值最大的权重是 mm。(a) 一个权重的量化误差最大是多少?(b) 块里只有一个”离群”的大权重、其余都很小时,会发生什么?这对选块大小有什么启示?

参考答案

(a) 量化步长是 m/7m / 7,四舍五入的误差最多是半个步长,即 m/14≈0.071mm / 14 \approx 0.071 m。(b) 其余权重如果都小于 m/14m / 14,会全部被量化成 0——一个离群值”吃掉”了整块的精度。块越小,一个离群值影响的权重越少,但每块要多存一个缩放因子(这是 QLoRA 要对缩放因子再做一次”双重量化”的原因)。LLM 的权重和激活里确实存在离群维度(Dettmers et al. 2022, LLM.int8()),这是第 19 讲(高效推理)的话题。

练习 8(显存账):按 §5.4 的口径,估算 130 亿参数的模型:全参数微调、LoRA、QLoRA 的显存各是多少?一块 24 GB 的消费级显卡能用哪种?

参考答案

全参数:13×109×1613 \times 10^9 \times 16 字节 ≈ 193.7 GiB;LoRA:13×109×2+13×106×1613 \times 10^9 \times 2 + 13 \times 10^6 \times 16 ≈ 24.4 GiB;QLoRA:13×109×0.5+13×106×1613 \times 10^9 \times 0.5 + 13 \times 10^6 \times 16 ≈ 6.25 GiB。24 GB 的显卡上 LoRA 刚好放不下(还没算激活值),QLoRA 绰绰有余。这就是 QLoRA 让”在自己的显卡上微调大模型”成为可能的原因。

练习 9(思考:怎么选):你要给公司内部的客服系统做一个”把用户问题分到 20 个类别”的功能。手里有一个 80 亿参数的指令微调模型(只能本地部署),以及 300 条标注数据,未来每个月会新增几个类别。你会怎么做?说出理由,以及你会怎样验证。

参考答案(一种合理的思路)

先试零样本 / 少样本提示:指令微调过的 8B 模型很可能已经会做分类,把类别名和说明写进提示,用 300 条数据当测试集,几乎零成本地得到一个基线(注意第 5 讲 §5 的提示敏感性:多试几种模板,在留出的数据上选)。如果不够好,用 LoRA(或 QLoRA,看显存)在 240 条上训练、60 条上验证:参数少、不易过拟合,可以和基座一起部署;新增类别时重新训练一个小 LoRA 即可,不用动基座。全参数微调在 300 条数据上过拟合和遗忘的风险大,部署成本高,一般不划算。验证时除了总准确率,还要看每个类别的准确率(类别不平衡)、和提示基线做配对比较(第 5 讲 §7.3),并检查微调后模型在别的通用能力上有没有明显退化(§3.3)。

7.4 延伸阅读

  • 原课阅读:Lester et al. (2021), The Power of Scale for Parameter-Efficient Prompt Tuning;Ding et al. (2022), Delta Tuning(PEFT 综述)
  • 范式:Devlin et al. (2018)(BERT);Radford et al. (2018)(GPT-1)、(2019)(GPT-2);Brown et al. (2020)(GPT-3)
  • PEFT:Houlsby et al. (2019)(Adapter);Li & Liang (2021)(Prefix tuning);Ben Zaken et al. (2022)(BitFit);Guo et al. (2021)(Diff pruning);Aghajanyan et al. (2021)(内在维度);Hu et al. (2021)(LoRA);Liu et al. (2022)((IA)³ / T-Few);Dettmers et al. (2023)(QLoRA);Mahabadi et al. (2021)(Compacter);Pfeiffer et al. (2021)(AdapterFusion)
  • 多任务与指令微调:Raffel et al. (2019)(T5);Sanh et al. (2021)(T0);Wei et al. (2022)(FLAN);Longpre et al. (2023)(FLAN Collection);Zhou et al. (2023), LIMA: Less Is More for Alignment(“表面对齐假说”:对齐主要是教格式,能力来自预训练)
  • 上下文学习的来源:Olsson et al. (2022), In-context Learning and Induction Heads;Min et al. (2022), Noisy Channel Language Model Prompting
  • 本仓库的相关内容:CMU17-630(提示工程整门课);CS324 第 6 讲(适配:提示、探针、微调与参数高效方法)

7.5 下一讲预告

这一讲我们看到,模型能做什么,很大程度上取决于它在预训练里”已经会”什么;适配方法只是把已有的本事接到新任务上。那么,当一个大模型看起来”会推理”时,它是在推理,还是在匹配它见过的模式?第 7 讲是 Ondřej Bojar 的客座讲座,从机器翻译的角度提出一个尖锐的问题:**decoder-only 的 LLM 在结构上就是”半个翻译模型”,我们凭什么期待它会推理?**我们会在花园世界里做”换数字、换名字、加一句无关的话”的扰动评估,看看一个”考得很好”的小模型到底学会了什么。

← 返回课程目录