跳到主要内容

数据截至 (上游 commit eb980a5c9eea)

换上工业框架 — 从「自己写」到「业界怎么干」

这一章讲三件事: 手写的那套为什么撑不到实际生产;换成业界通用的工具之后, 每一步分别由谁负责;以及在「显卡不够」这个绕不开的现实面前,业界最后选定了哪一种妥协。 这一章的落点是 LoRA——今天你在任何一个模型微调教程里都会看到的那四个字母。 不需要会写代码,我们只讲每个工具在流水线上占哪个位置、解决哪个具体的麻烦。

1. 先看现象:手写的那套,在真实项目里活不下来

第 07 章从零训出了一个 2.15 亿参数的模型,跑通了,但只能停在这儿。 书里给的三条理由,每一条都是硬墙1:

撞上什么墙具体是什么
跟不上手写模型结构工作量大,新模型一出结构就变,你永远在追
训不动从零实现做不好多卡分布式训练,训练效率低
接不上和现有的预训练模型不兼容,别人训好的权重你用不了

第三条最致命。 第 05 章说过,预训练要上千张卡跑一个月——绝大多数人这辈子都不会自己做预训练。 真实的工作是:拿别人训好的模型,在自己的数据上继续训。 手写的模型结构和别人的权重对不上,这条路就断了。

这一章就是把第 07 章那套手写的,换成业界的标准件。

2. 顶层全景:三个工具,各占一段

第 07 章(手写) 第 08 章(工业)
─────────────────────────────────────────────────────────────
自己写模型类 → Transformers:一行加载任意架构
自己写数据集类 → datasets:加载 + 批量处理
自己写训练循环 → Trainer:封装了循环、日志、存盘
自己想办法多卡 → DeepSpeed:一条命令启动分布式
全量微调,显存不够 → peft:只训一小部分参数

图说:五处替换,每一处都对应第 07 章里你亲手写过的一段。
这一章的价值在于:你已经知道它们在替你做什么。

这就是为什么书把手写放在前面。 先手写一遍再换框架,你看到的是「工具替我做了什么」; 反过来先学框架,你看到的只是一堆记不住的类名。

主走查:同一条样本,换一套工具再走一遍

五个工具各讲各的,读者读完只记得五个名字。所以这一章沿用第 07 章那条样本,从头再走一遍:

用户说「你好」,模型该答「你好呀」。 底座换成书里用的 Qwen-2.5-1.5B——这一次不是从零开始,是在别人训好的权重上接着训。

后面每一节末尾都有一小段这条样本走到这一步,写出它此刻是什么形状、哪一串数:

第 3 节 Transformers → 加载底座和它配套的分词器,这条样本被切成另一串编号
第 4 节 datasets → 和别的样本首尾相连,切成 2048 个标记一块
第 5 节 Trainer → 套模板、把用户说的那部分换成 -100、交给训练器
第 6 节 DeepSpeed → 这一批被分到 8 张卡上,优化器状态各存 1/8
第 7 节 peft → 底座冻住,只在旁边挂一对小矩阵去训

图说:这五步和第 07 章第 2 节那四件事一一对得上,只是每一步换了人来做。

先声明:下面这些小段里的编号和显存数字是为演示编的,不是真实数值。 2048、-100、ZeRO-2、r 的取值范围都来自书里。

3. 工具一:Transformers——一行加载任意模型

3.1 它是什么

Transformers 是 Hugging Face 开发的框架, 通过模块化设计,统一支持 BERT、GPT、LLaMA、T5 等上百种主流架构2

关键收益:开发者不必重复实现基础网络结构,一行代码就能加载任意预训练模型2

它还内置了 Trainer 类,封装了分布式训练的核心逻辑, 支持 PyTorch 原生的多卡、DeepSpeed、Megatron-LM 等多种策略; 配上自动存盘和日志组件,训练过程的管理基本自动化3

书里给了一个规模上的说法:在 8 卡 A100 集群上可以轻松支持百亿参数模型的高效训练3

这个数要对照着读才有量感: 第 05 章那笔账里,同样是百亿参数, 从零预训练要 1024 张 A100 跑一个多月;这里说的 8 张卡,是在别人训好的权重上接着训。 卡数差 128 倍,差的就是「从零开始」和「接着训」。

3.2 比框架更重要的是社区

这一点书里说得很到位4:

Hugging Face 基于这个框架搭起了一个庞大的社区, 开放了数亿个预训练模型参数、25 万以上不同类型的数据集。

而且新发布的开源模型(DeepSeek、Qwen 等)都会第一时间在这个社区放出权重和调用示例5

判断(我们的,不是书里的): 这一段值得单独记住,因为它说明了一件事—— 这个框架赢的不是技术,是网络效应。 模型作者把权重放这儿是因为用户在这儿,用户在这儿是因为模型在这儿。 判断一个基础设施该不该学,常常不该看它设计得好不好,而该看它的生态有多大。 如果错,会错在: 如果某个新框架能提供显著的性能优势(比如推理速度快几倍), 生态劣势是可以被抵消的——推理框架这一层就正在发生这种事。

3.3 两种起手式:从零初始化,还是加载已有权重

这是一个很重要的区分,书里讲得很清楚。

做法加载什么什么时候用
从配置初始化只读架构定义,参数全是随机的你要从零预训练一个自己的模型
从权重初始化架构 + 别人训好的参数你要在别人的模型上继续训

书里用 Qwen-2.5-1.5B 当例子:先下载它的模型文件, 里面的配置文件描述了架构、隐藏层维度、层数等6

  • 只加载配置,再从配置生成模型 → 得到一个「架构是 Qwen-2.5-1.5B、参数全随机」的模型7;
  • 直接加载权重 → 得到一个训练好的模型8

书里给了一句很实在的现状判断:很少有人从零初始化去做预训练, 更常见的做法是加载一个预训练好的模型,在自己的语料上接着训8——这一步叫后训练。

「后训练」(Post Train)就是:在别人预训练好的模型上继续训。 它和预训练用的是同一道题(接龙),只是起点不是随机参数。

3.4 一个容易被跳过的细节:分词器也要一起加载

书里顺带提了一句:还要初始化一个分词器,直接用这个模型对应的那一份9

这句话不能跳过。 第 07 章讲过,分词器决定了文字怎么切成编号; 换一个分词器,同一句话切出来的编号就完全不同,而模型只认它训练时用的那一套编号。 模型和分词器必须配套,这是这一行最常见的一类低级错误。

主走查:同一句「你好呀」,换了分词器就换了一串数

这是「必须配套」最直观的证据——同样三个字,两套分词器给的编号毫不相干:

第 07 章那个自己训的分词器(词表 6144):
你 → 1024 好 → 1531 呀 → 2790 共 3 个编号

Qwen-2.5 那个现成的分词器(词表十几万):
你好 → 108386 呀 → 100158 共 2 个编号
└ 「你好」常见到足以单独占一格,所以两个字被合成了一个片段

图说:同一句话,一个切成 3 段、一个切成 2 段,而且编号一个都对不上。
把 1024 喂给 Qwen-2.5,它查到的是自己表里第 1024 行 —— 完全是另一个字。
模型不会报错,它只会一本正经地答非所问。
(这些编号是为演示编的,不是真实数值;「词表十几万」这个量级来自第 04 章 LLaMA-3 那一行。)

4. 工具二:datasets——把语料变成能喂的形状

这一节对应第 07 章第 4 节那件事,只是换了工具。

4.1 三步

① 加载 一行代码读进语料文件(jsonl:一行一条记录的纯文本格式)
② 批量分词 对每条文本调用分词器,并把原来的文本列删掉
③ 拼成定长块 把多段文本首尾相连,切成统一长度(书里取 2048)

图说:第 ③ 步是这一节唯一的新东西,也是和第 07 章不一样的地方。

4.2 第 ③ 步为什么要这么做

书里给的理由值得完整记住10:

预训练做的是接龙,一次学习多个样本拼在一起的序列不影响性能; 而训练数据量大、训练时间长,对效率要求高。 所以一般把多个文本段拼在一起,处理成统一长度的文本块。

为什么这样更快:

不拼接: 一批里句子长短不一 → 短的要补一堆填充标记 → 算力浪费在填充上
拼接后: 每一块都正好 2048 个标记 → 一个填充标记都不用 → 算力全花在真数据上

图说:代价是一个文本块里可能横跨两篇文章。
对接龙训练来说这个代价可以接受——反正它学的就是「照着前文往下写」。

主走查:那条 13 个标记的样本,被拼进了一个 2048 的块里

这条样本(套完模板)大约 13 个标记 → 一个人待着的话,后面要补 2035 个填充标记
└ 算力的 99.4% 花在填充上,只有 0.6% 花在真数据上

拼接之后:
… 上一条样本 … | <im_s>user\n你好<im_e><im_s>asst\n你好呀<im_e> | 下一条样本 …
└ 一块 2048 个标记里塞了一百多条这样的短样本,一个填充标记都不用

图说:2035 ÷ 2048 = 99.4% 这个浪费比例,就是这一步存在的全部理由。
(13 个标记是照第 07 章那条样本数出来的;2048 来自书里。)

4.3 一个提醒

书里补了一句很实在的:数据集较大时加载可能很慢或者内存不够, 建议前期测试时先拆一小部分出来11

5. 工具三:Trainer——训练循环不用自己写了

5.1 两步

先定一个词。 模型内部那些训练时自动被调的数叫参数(第 01 章讲过); 而学习率多大、一批喂几条、多少步存一次盘这类训练之前由人定死、训练过程中不会自己变的设定, 这一行统称超参数——「超」的意思就是「在参数之上、管着参数怎么被调」。

① 用 TrainingArguments 配好所有超参数(学习率、批大小、存盘间隔、日志间隔……)
② 把模型、分词器、那一整包超参数设置、数据集交给 Trainer,让它开训

图说:第 07 章第 5 节手写的那四样东西——学习率调度、混合精度、
梯度累积、定期存盘——全都变成了配置项。

书里对它的评价:封装了模型的训练逻辑,做了较好的效率优化和可视化12

5.2 有监督微调:只改一处

书里在这里给了一个特别干净的结论,值得原样带走13:

预训练和有监督微调都用接龙训练,核心差异只有一条: 预训练对全部文本算损失,有监督微调只对输出算损失、不算指令部分。

所以相较于预训练的代码,有监督微调只需要改数据处理这一环,其余完全一致。

这正是第 05 章那句话和第 07 章那个损失掩码的第三次出现。 三章讲同一件事,说明它确实是这条流水线上最要紧的一处。

5.3 对话模板:这一步决定成败

书里先解释了什么是对话模板:它表示怎么把对话数据转成一个模型可以拟合的文本序列14

然后给了一条非常实用的建议:

当你要在一个做过有监督微调的模型上继续微调时, 一定要先去看它原来的对话模板并保持一致—— 这是为了不损伤它在原来那次微调里学到的指令遵循能力。14

模型原本学的: <|im_start|>user 你好<|im_end|><|im_start|>assistant …
你用的模板: ### Instruction: 你好 ### Response: …
└ 对不上 ⟹ 它认不出「该我说话了」的信号 ⟹ 原来的能力被打乱

图说:对话模板不是排版风格,是模型识别角色的唯一线索。

5.4 屏蔽指令部分:一个具体的数字

书里的实现里,用户说的那部分在训练目标里被换成一个特殊值屏蔽掉, 只有助手说的那部分保留原文、参与损失计算15

书里给了一个业界惯例的具体数字:主流大模型的屏蔽值一般设成 -10015

为什么是负数: 因为标记编号都是非负整数,用一个不可能是合法编号的值当记号最保险—— 训练框架看到它就跳过。这是一个约定,不是推导出来的。

主走查:同一条样本,这次不用 0/1 掩码,用 -100

做的是同一件事,写法不一样,值得并排看清楚:

第 07 章手写版(两串东西):
目标 Y: [ …, 你, 好, 呀, <im_e> ]
掩码: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 ] ← 另外拿一串 0/1 去乘

这一章框架版(一串东西):
目标: [ -100, -100, -100, -100, -100, -100, -100, -100, -100, 108386, 100158, 5 ]
(九格 -100,再加「你好」「呀」和收尾记号,一共 12 格)
└──────────── 用户说的那九格,编号全被换成 -100 ────────────┘
└ 框架看到 -100 就跳过这一格,不用再另外传一串掩码

图说:两种写法结果完全一样。前者把「算不算分」记在另一串数里,
后者直接写进目标本身。框架选了后者,因为少传一个参数。
(编号是为演示编的;-100 这个惯例值来自书里。)

5.5 一处值得注意的细节:角色名被改了

书里因为用的数据集里对话角色叫 human 而不是 user, 就把模板里的 user 改成了 human16

判断(我们的,不是书里的): 这个改动看起来无关紧要,实际上有代价—— 它让训出来的模型和 Qwen 系列的对话模板不再兼容。 以后想拿这个模型接进任何按 Qwen 模板组装输入的系统,都会对不上。 正确的做法通常是改数据去适配模板,而不是改模板去迁就数据。 书里没有提醒这一点。 如果错,会错在: 如果这个模型从头到尾只在自己的体系里用、不打算和别人对接, 那改哪一边都无所谓,书里的做法就是更省事的那个。

6. 工具四:DeepSpeed——一条命令上多卡

这一节把第 05 章的 ZeRO 落到实处。

6.1 为什么必须换个跑法

书里给了两条很实在的理由17:

  • 预训练规模大、时间长,不推荐用交互式笔记本跑——容易中断;
  • 规模大就必须多卡,否则时间长到不可接受。

所以标准做法变成:写一个 Python 脚本 + 一个 shell 脚本,用命令行启动。

6.2 脚本里多出来的四样东西

这些在第 07 章的手写版里都没有,但在真实训练里一样都不能少18:

多出来什么干什么为什么必须有
参数解析(把命令行里写的那串文字读出来、认成一个个设定)从 shell 脚本里读超参数换一组超参不用改代码
日志系统用标准日志库而不是打印书里明说:不推荐用打印,容易发生关键训练信息的丢失19
断点续训启动时先检查有没有旧的存档,有就接着训大规模训练中断难以避免20
训练监控接一个可视化平台,启动后给一个网址看进度和损失曲线监测损失下降趋势尤为重要21

日志那一条值得多说一句: 日志分五个级别,从最啰嗦到最要命依次是 调试 → 信息 → 警告 → 错误 → 严重; 设成哪一级,就只输出这一级以及比它更要命的那些19。这是控制输出量的标准手段。

6.3 启动:一条命令

装好 DeepSpeed 之后,直接用它的命令启动多卡训练, shell 脚本里定义各种超参数的值,再指定一个配置文件22

书里选的是 ZeRO-223——也就是第 05 章讲的三级里的中间那一级: 切优化器状态 + 切梯度,参数每张卡各存一份。

这个选择本身就是一次取舍的示范: ZeRO-1 显卡利用率最高但最费显存,ZeRO-3 最省显存但通信最贵。 选中间那一级,是「显存够用的前提下尽量快」这个判断的结果。 书里没有解释为什么选它,但第 05 章给了判断的依据。

主走查:那一批数据被切到 8 张卡上

同一条样本所在的那一批,现在被拆开了:

假设这一批 64 条,8 张卡 → 每张卡分 8 条,「你好 → 你好呀」那一块落在第 3 号卡上
└ 8 张卡各自算各自那 8 条,算完把调整量汇总求平均,再发回每张卡

显存这一侧,按 15 亿参数的底座、第 05 章那条「模型状态 = 参数量的 16 倍」估:
不切: 每张卡都存一整份 → 15 亿 × 16 ≈ 24 GB
ZeRO-2 切: 优化器状态和调整量各存 1/8,只有参数每张卡各存一份
→ 大约降到 8 GB 上下

图说:降下来的这十几个 GB,就是「一张 24GB 的卡跑不动」和「跑得动」的分界。
代价是每一步结束时,8 张卡之间要把切开的那些数互相传一遍。
(64 条、8 GB 这两个数是为演示编的;16 倍来自第 05 章,ZeRO-2 的切法来自书里。)

7. 落点:高效微调,以及为什么最后是 LoRA

从这里开始是这一章最有价值的部分。

7.1 先看问题

书里的开场很直接:大模型参数量大、训练数据多, 按前面的方式训练需要调整模型全部参数,资源压力非常大。 对资源有限的企业或课题组来说,怎么低成本地微调,是非常重要的24

「全量微调」就是:模型里所有参数都参与更新。 第 05 章算过账——光是优化器状态就要参数量的 12 倍显存。

7.2 两条前辈路线,各死在一处

书里给了两种在 LoRA 之前的方案25:

路线一:插适配层(Adapt Tuning)。

原来的每一层: … → 子层 → …
加了适配层: … → 子层 → [适配层] → …
└ 先把维度压窄,再还原回去。只训这一小块,原参数冻结

图说:压窄那一步的宽度决定了新增参数有多少,一般设得很小。

它死在哪:推理延迟(就是从提问到出结果多等的那段时间)。 书里说得很明白——因为增加了额外的参数和额外的计算量, 微调之后的模型计算速度比原模型更慢26

为什么这条是硬伤: 微调是一次性的,推理是天天在跑的。 用永久的推理变慢,换一次性的训练省钱,账算不过来。

路线二:加可训练前缀(Prefix Tuning)。

在每一个输入标记前面构造一段和任务相关的「虚拟标记」, 微调时只更新这段前缀,其余全部冻结(就是让那些参数在整个训练过程中原样不动)27。 好处是不同任务可以各存一份前缀,成本很小。

它死在哪:占地方。 书里说得很直白—— 加入虚拟标记会占用可用的序列长度,微调质量越高,模型能用的序列长度就越短28

书里还点了一句:今天常用的 P-tuning 就是它的一种改进28

7.3 LoRA 的依据:微调真正需要动的数,少得出奇

这一节是这一章唯一一段真正的原理,慢一点看。

书里的论证链29:

① 大模型把数据映射到一个非常高维的空间去处理
② 但处理一个细分的小任务,未必需要那么高的维度
└ 可能在某个「子空间」里就够了
③ 定义:当只优化某个子空间的参数,就能达到全量微调的一定水平(比如 90% 精度)时,
这个子空间参数矩阵的「秩」,就叫这个任务的「本征秩」
④ 于是:与其调整全部参数,不如只训练那个低秩的部分

图说:整条论证的落点是——微调真正需要动的数,远少于模型的总参数量。

「秩」是什么: 一个矩阵里真正互相独立的行(或列)有多少。 秩低,意味着这个矩阵虽然很大,但它的信息其实可以用少得多的数描述。

书里还引了一个更强的结论30:

预训练模型本身就隐式地降低了本征秩(就是上一段定义的那个数:够用的最小的秩); 针对特定任务微调后,权重矩阵的本征秩更低。 而且越简单的下游任务,对应的本征秩越低。

补充(不在书里,但书里给了参考文献):这个结论的原始出处是 2020 年 12 月的论文。 它最有冲击力的结果是:只优化 200 个随机投影回全空间的参数, 就能让一个模型在某个任务上达到全参数微调 90% 的水平; 并且更大的预训练模型,本征维度反而更低31

7.4 从「秩很低」到「并联一对小矩阵」:中间隔着四级台阶

上一节论证到「只需要动低秩(就是上一节说的那个「秩」很低)的部分」, 下一节就要并联一对小矩阵——这中间不是一步,是四步。 一级一级走:

① 全量微调实际上在算一个「更新量」。 微调前那个权重矩阵叫 W,微调后变成另一个矩阵。 两者一减,得到的就是这次微调改动了多少,记作 ΔW。 所以「微调完的模型」= 用 W + ΔW 去干活,而训练要算的其实只有 ΔW 这一个东西。

② 上一节那条结论,翻译过来就是一句话:这个 ΔW 的秩很低。 「只需要在一个低秩的子空间里动」说的不是 W 低秩(W 不低秩), 说的是「这一次改动了什么」这件事本身很低秩。

③ 而任何一个低秩矩阵,都能拆成两个瘦长矩阵相乘。 这是矩阵这门数学里的一条基本事实: 一个 4096 × 4096 的矩阵,如果秩只有 8,那它一定等于某个 4096 × 8 乘上某个 8 × 4096。 反过来也成立——这样乘出来的东西,秩最多就是 8。

④ 所以不必去算那个 4096 × 4096 的 ΔW,只训那两个瘦长的就够了。 两个瘦长矩阵一共 6.5 万个数,而 ΔW 有 1677 万个。这就是 LoRA 的全部。

这四级里最容易被跳过的是第 ①、② 级。 不把 ΔW 这个东西单独拎出来,下一节那张图里 WA × B 就是并排的两项, 读者根本看不出 A × B 就是 ΔW。

7.5 LoRA 具体怎么做

做法一句话:原来的权重矩阵冻结不动,在它旁边并联一对小矩阵,只训这一对32

原来: 输出 = 输入 × W (W 是一个大矩阵,这里取 4096 × 4096)
LoRA: 输出 = 输入 × W + 输入 × A × B (A 是 4096 × r,B 是 r × 4096,r 很小)
└ 冻结 └ 只训这两个,它俩乘起来就是上一节说的 ΔW

r = 8 时:A 和 B 加起来只有 4096×8×2 ≈ 6.5 万个参数,
而 W 有 1677 万个 —— 训练量降到 0.4%

图说:并联而不是串联,这是它和适配层最关键的区别。
这里的 4096 借的是第 05 章那张表里 LLaMA-7B 的隐藏层维度,不是编的;
r = 8 取自书里给的经验值(见第 7.7 节:一般取 4、8、16)。

两个实现细节33:

  • A 里的数随机取,B 里的数全部设成 0。 A 的取法叫高斯分布——就是那种「中间高、两边低」的钟形取值: 离 0 越近的数越容易被取到,越极端的数越少见,大多数人听过的「正态分布」就是它; 这样一开始 A×B = 0,模型的输出和原来完全一样——不会因为加了旁路就先变差;
  • 用 Adam 优化器训练。

7.6 LoRA 的四条优势,以及书里译错的那一条

书里列了四条34:

书里的说法该怎么理解
① 可以为不同下游任务各建一个小模块,共享同一个底座来回切换正确,而且是它最实用的一点——一个底座配一柜子小模块
② 「LoRA 使用自适应优化器,不需要计算梯度或维护大多数参数的优化器状态」这句话译反了,见下
③ 线性设计,部署时可以把小矩阵合并回冻结权重,不存在推理延迟正确,而且这是它打败适配层的关键
④ 和其他方法正交,可以组合正确

判断(我们的,不是书里的):第 ② 条是一处翻译错误。 补充(不在书里):原论文的原话是—— 「当使用自适应优化器时,LoRA 让训练更高效、把硬件门槛降低最多三倍, 因为我们不需要为大多数参数计算梯度或维护优化器状态。」35 也就是说:「使用自适应优化器」是前提条件,不是 LoRA 的动作。 逻辑是:Adam 这类优化器要为每个可训练参数额外存两个数, 冻结了绝大多数参数,这笔开销就自然没了。 按书里那句话读,会以为 LoRA 自带一种叫「自适应优化器」的东西,那是不存在的。 如果错,会错在: 如果原论文另有一处确实写了「LoRA 采用某种自适应优化器」, 那书里可能引的是那一处——但我们核对了论文正文的优势列表,只有上面这一句。

7.7 用在哪些层、秩取多少

书里给的做法36:

  • 只作用在注意力的四个权重矩阵上(Q、K、V 和输出投影),前馈层的权重冻结;
  • 消融实验(就是一次去掉一个部件,看效果掉多少,以此判断哪个部件重要)发现同时调整 Q 和 V 效果最好;
  • 秩一般取 4、8、16。

实际用的时候,在 peft 库里指定要作用的层名就行, 比如 ["q_proj", "v_proj"] 分别对应查询和值那两个矩阵37

书里还提了一个坑:不同模型的层名不一样。 ChatGLM 不用指定,库能自己找到;百川的模型就必须手动指定38

7.8 peft:三步用起来

peft 是 Hugging Face 的库,封装了 LoRA、适配层、P-tuning 等多种高效微调方法39。用起来三步40:

① 正常加载底座模型和分词器
② 写一个 LoRA 配置(秩、作用在哪些层、任务类型)
③ 用一个函数把模型包一层,得到 LoRA 版模型

之后照常交给 Trainer 训练 —— 显存占用会有大幅度的降低

图说:整个改动只有第 ②③ 两步,训练部分一个字都不用改。

内部它做的三件事41:

  1. 找出要作用的层(按层的名字去筛,可以写一小段模式一次匹配一批);
  2. 把这些层替换成 LoRA 层——在原层的基础上加一条旁路;
  3. 冻结原参数,只更新旁路的那对小矩阵。

主走查最后一步:同一条样本,这次只训千分之几的参数

样本、模板、掩码一个字都没变,变的只有「谁在被训」:

底座 Qwen-2.5-1.5B:15 亿个参数,全部冻结 —— 一个都不更新

挂上 LoRA(r = 8,只作用在每层的 Q 和 V 两个矩阵上):
每个矩阵配一对 A、B,新增参数 = 隐藏层维度 × 8 × 2
28 层 × 每层 2 个矩阵 × 每个矩阵一对 → 一共几百万个可训练参数
└ 占 15 亿的千分之几

这条样本走的路和第 5 节一模一样:
套模板 → 换 -100 → 算损失 → 反着推
唯一的差别是「反着推」到底之后,只有那几百万个数被挪了一下,
另外 15 亿个原样不动 —— 优化器不必为它们各记两个数,显存就是这么省下来的

训完之后:把每一对 A×B 加回它旁边那个 W,得到一个和原来一模一样形状的模型
└ 推理时没有任何额外计算 —— 这就是它打败适配层的那一条

图说:第 05 章算过,优化器状态要占参数量的 12 倍显存。
冻掉 15 亿里的绝大部分,省掉的正是那 12 倍里的绝大部分。
(28 层和「几百万」是为演示估的,不是真实数值;r 取 8、只作用在 Q 和 V 上来自书里。)

7.9 一条明确的禁区

书里最后给了一个很重要的限制,而且说得毫不含糊。

它省显存,也确实能把模型调到你要的那类任务上——但这只管「怎么说」。

一旦这个任务要求模型学会它原本不知道的东西,LoRA 就不灵了: 你能动的只有那一对很窄的小矩阵(上一节说的「秩很小」),装不下新知识

书里给这件事下的判词是「难以实现知识的注入」, 并且明确说了不推荐拿 LoRA 去做预训练或后训练42

适合 LoRA: 改语气、改格式、学一种回复风格、适配一类任务
不适合: 往模型里灌新知识(比如让它学会你们公司的产品手册)

图说:一条很实用的判据——你想改的是「怎么说」还是「知道什么」。
前者用 LoRA,后者别用。

这条禁区值得单独记住。 它是很多「用 LoRA 微调之后模型还是不知道我们的业务」 这类困惑的直接答案。知识要进模型,要么全量后训练,要么走第 09 章的检索增强(先去文档库里捞出相关段落,再让模型照着答)那条路。

8. 作者的判断与证据

说法书里给了什么该怎么看
手写训练的三条局限给了三条具体理由完全成立,而且第三条(接不上现成权重)是决定性的
有监督微调只需改数据处理给了机制论证(两者都是接龙,差别只在算不算指令的损失)论证扎实,和第 05、07 章一致
适配层有推理延迟给了机制解释(额外参数和计算)可以采信
前缀微调占用序列长度给了机制解释可以采信
微调存在低本征秩给了推理链,并引了原始论文这是全书少见的、给了理论依据的地方,值得肯定
同时调 Q 和 V 最好说是消融实验的结论,没给数字是原论文的结论,书里转述正确
秩取 4、8、16只给了经验值当经验用
LoRA 不适合注入知识只给了结论和一句机制解释(只调那对秩很低的矩阵)结论是业界共识,但书没给实验依据
「LoRA 使用自适应优化器」这是一处翻译错误见第 7.6 节

判断(我们的,不是书里的): 这一章有一个结构上的观察值得带走—— 三个前辈方案(适配层、前缀微调、LoRA)各自的死因,恰好构成了一个完整的判据表: 适配层死在「推理变慢」,前缀微调死在「占用输入长度」, 而 LoRA 之所以赢,是因为它两样都不占——训完可以合并回去,什么痕迹都不留。 评价一个「省资源」的方法,要同时看它在训练期省了什么、在推理期又赔了什么。 如果错,会错在: 如果某个场景需要在运行时快速切换几十个任务 (那时不合并、保持旁路反而更方便),LoRA 的「可合并」优势就用不上, 判据要换成「切换成本」。

9. 边界与局限

① 说好的三阶段,这一章仍然只做了两段

第 6.3 节开头写着「详细介绍了 Pretrain、SFT 以及 RLHF 的原理和实践细节」24, 但这本书从头到尾没有 RLHF 的实践。 第 05 章讲了原理,这一章讲的是预训练和有监督微调。

这是全书最明显的一处内容缺口。

② DPO、KTO 一笔带过

书里在最后提了一句「如果是应用在 DPO、KTO 上,思路一致,加上 LoRA 参数即可」43, 没有任何展开。 而 DPO 恰恰是第 05 章推荐的那条低门槛路线。

③ 完全没提的四件事

第一件要先交代一个词:量化就是用更少的位数存每个参数,拿一点精度换显存。 同一个底座,存每个参数的位数减一半,它占的显存就少一半。

缺什么为什么重要
QLoRA(先把底座压缩、再挂 LoRA 的那种做法)把底座模型量化到 4 位再挂 LoRA,是今天单卡微调大模型的实际标准做法44
秩和缩放系数怎么调书给了「取 4、8、16」,但没说和数据量、任务难度的关系
数据质量整章讲的是「怎么训」,没有一句讲「什么样的微调数据是好数据」
训练完怎么评估和第 07 章一样,没有验证集、没有指标

④ 一处提醒:框架版本会变

这一章讲的是工具怎么用,而工具的接口是会变的。 书里的代码写于 2024 年前后,参数名、类名、默认值都可能已经不同。

正确的读法:记住每一步「在解决什么问题」,不要记参数名。 问题不会过时,接口一定会。

10. 可带走的

  1. 手写训练撞三堵墙:跟不上新架构、做不了多卡、用不了别人的预训练权重;第三条最致命;
  2. 业界的标准组合是:Transformers(模型 + 训练器)+ DeepSpeed(分布式)+ peft(高效微调);
  3. Transformers 赢的是生态不是技术——模型作者和用户互相吸引;
  4. 从配置初始化 = 只要架构、参数随机;从权重初始化 = 架构 + 别人训好的参数;
  5. 模型和分词器必须配套,换一个分词器,同一句话的编号就全变了;
  6. 预训练数据会被拼成定长块(书里取 2048),这样一个填充标记都不浪费;
  7. 有监督微调和预训练只差数据处理这一环,其余代码完全一致;
  8. 对话模板不是排版风格,是模型识别角色的唯一线索;在别人微调过的模型上继续训,必须沿用它的模板;
  9. 屏蔽指令部分的惯例值是 -100,因为合法编号不可能是负数;
  10. 大规模训练必须有:参数解析、日志、断点续训、训练监控——一样都不能少;
  11. 适配层死在推理变慢——用永久的推理开销换一次性的训练省钱,账算不过来;
  12. 前缀微调死在占用序列长度——微调质量越高,能用的输入越短;
  13. LoRA 的依据是:微调真正需要动的数远少于模型的总参数量(本征秩很低);
  14. LoRA = 冻结原矩阵,在旁边并联一对小矩阵;B 初始化为 0,所以一开始输出和原模型完全一样;
  15. 它赢在「可合并」:部署时把小矩阵加回原权重,推理一点不慢;
  16. 它有明确禁区:注入新知识不行——判据是「你想改的是怎么说,还是知道什么」;
  17. 记问题,不要记参数名——问题不会过时,接口一定会。

11. 原文地图

主题原书章原文位置
手写训练的三条局限6.1 模型预训练text/19-ch06-01-6-1.txt:6(搜「⼿写实现 LLM 结构⼯作量」) · text/19-ch06-01-6-1.txt:8(搜「⽆法较好地实现多卡」) · text/19-ch06-01-6-1.txt:10(搜「⽆法使⽤预训练好的模型参数」)
Transformers 与社区6.1 模型预训练text/19-ch06-01-6-1.txt:17(搜「Hugging Face 开发的 NLP 框架」) · text/19-ch06-01-6-1.txt:23(搜「Trainer 类封装」) · text/19-ch06-01-6-1.txt:30(搜「25万+」) · text/19-ch06-01-6-1.txt:39(搜「DeepSeek、Qwen」)
两种起手式6.1 模型预训练text/19-ch06-01-6-1.txt:46(搜「Qwen-2.5-1.5B」) · text/19-ch06-01-6-1.txt:85(搜「from_config」) · text/19-ch06-01-6-1.txt:98(搜「from_pretrained」) · text/19-ch06-01-6-1.txt:103(搜「初始化⼀个 tokenizer」)
数据处理与定长块6.1 模型预训练text/19-ch06-01-6-1.txt:115(搜「load_dataset」) · text/19-ch06-01-6-1.txt:155(搜「remove_columns」) · text/19-ch06-01-6-1.txt:157(搜「拼接在⼀起」) · text/19-ch06-01-6-1.txt:165(搜「block_size = 2048」)
Trainer6.1 模型预训练text/19-ch06-01-6-1.txt:199(搜「Trainer 封装了模型的训练逻辑」) · text/19-ch06-01-6-1.txt:202(搜「TrainingArguments」) · text/19-ch06-01-6-1.txt:235(搜「trainer.train」)
分布式脚本的四样必需品6.1 模型预训练text/19-ch06-01-6-1.txt:240(搜「不推荐使⽤ Jupyter Notebook」) · text/19-ch06-01-6-1.txt:336(搜「HfArgumentParser」) · text/19-ch06-01-6-1.txt:343(搜「不推荐使⽤ print」) · text/19-ch06-01-6-1.txt:376(搜「发⽣中断是往往难以避免」) · text/19-ch06-01-6-1.txt:442(搜「swanlab 作为训练检测」)
DeepSpeed 启动与 ZeRO-26.1 模型预训练text/19-ch06-01-6-1.txt:456(搜「deepspeed pretrain.py」) · text/19-ch06-01-6-1.txt:486(搜「ZeRO-2」)
预训练与有监督微调的唯一差别6.2 模型有监督微调text/20-ch06-02-6-2.txt:54(搜「仅对输」) · text/20-ch06-02-6-2.txt:57(搜「仅需要修改数据处理环节」)
对话模板与角色6.2 模型有监督微调text/20-ch06-02-6-2.txt:63(搜「Chat Template」) · text/20-ch06-02-6-2.txt:75(搜「im_start」) · text/20-ch06-02-6-2.txt:89(搜「human」)
屏蔽指令与 -1006.2 模型有监督微调text/20-ch06-02-6-2.txt:79(搜「IGNORE_TOKEN_ID」) · text/20-ch06-02-6-2.txt:142(搜「-100」)
适配层与前缀微调6.3 高效微调text/21-ch06-03-6-3.txt:36(搜「Adapt Tuning」) · text/21-ch06-03-6-3.txt:46(搜「推理延迟」) · text/21-ch06-03-6-3.txt:49(搜「Prefix Tuning」) · text/21-ch06-03-6-3.txt:54(搜「virtual tokens」)
本征秩与 LoRA 原理6.3 高效微调text/21-ch06-03-6-3.txt:61(搜「本征秩」) · text/21-ch06-03-6-3.txt:63(搜「隐式地降低了本征秩」) · text/21-ch06-03-6-3.txt:73(搜「秩分解矩阵」) · text/21-ch06-03-6-3.txt:99(搜「随机⾼斯初始化」)
LoRA 的四条优势6.3 高效微调text/21-ch06-03-6-3.txt:77(搜「⼩型 LoRA 模块」) · text/21-ch06-03-6-3.txt:79(搜「⾃适应优化器」) · text/21-ch06-03-6-3.txt:82(搜「不存在推理延迟」)
作用在哪些层、秩取多少6.3 高效微调text/21-ch06-03-6-3.txt:106(搜「冻结 MLP」) · text/21-ch06-03-6-3.txt:109(搜「消融实验」) · text/21-ch06-03-6-3.txt:115(搜「r 取到」) · text/21-ch06-03-6-3.txt:139(搜「target_modules」)
peft 与 LoRA 的禁区6.3 高效微调text/21-ch06-03-6-3.txt:118(搜「peft 库」) · text/21-ch06-03-6-3.txt:280(搜「get_peft_model」) · text/21-ch06-03-6-3.txt:296(搜「知识的注⼊」)

Footnotes

  1. 出处:「6.1 模型预训练」第 6 段(text/19-ch06-01-6-1.txt:6,搜「⼿写实现 LLM 结构⼯作量」)、第 8 段(text/19-ch06-01-6-1.txt:8,搜「⽆法较好地实现多卡」)、第 10 段(text/19-ch06-01-6-1.txt:10,搜「⽆法使⽤预训练好的模型参数」)。

  2. 出处:「6.1 模型预训练」第 17 段(text/19-ch06-01-6-1.txt:17,搜「Hugging Face 开发的 NLP 框架」)。 2

  3. 出处:「6.1 模型预训练」第 23 段(text/19-ch06-01-6-1.txt:23,搜「Trainer 类封装」)与第 25 段(text/19-ch06-01-6-1.txt:25,搜「8 卡 A100 集群」)。 2

  4. 出处:「6.1 模型预训练」第 30 段(text/19-ch06-01-6-1.txt:30,搜「25万+」)。

  5. 出处:「6.1 模型预训练」第 39 段(text/19-ch06-01-6-1.txt:39,搜「DeepSeek、Qwen」)。

  6. 出处:「6.1 模型预训练」第 46 段(text/19-ch06-01-6-1.txt:46,搜「Qwen-2.5-1.5B」)与第 49 段(text/19-ch06-01-6-1.txt:49,搜「config.json」)。Qwen2.5 的一手来源见第 07 章脚注。

  7. 出处:「6.1 模型预训练」第 85 段(text/19-ch06-01-6-1.txt:85,搜「from_config」)与第 92 段(text/19-ch06-01-6-1.txt:92,搜「从零初始化的 Qwen-2.5-1.5B」)。

  8. 出处:「6.1 模型预训练」第 98 段(text/19-ch06-01-6-1.txt:98,搜「from_pretrained」)与第 92 段(text/19-ch06-01-6-1.txt:92,搜「很少从零初始化」)。原文:「一般情况下,我们很少从零初始化 LLM 进行预训练,较多的做法是加载一个预训练好的 LLM 权重,在自己的语料上进行后训练」。 2

  9. 出处:「6.1 模型预训练」第 103 段(text/19-ch06-01-6-1.txt:103,搜「初始化⼀个 tokenizer」)。「模型和分词器必须配套」这层提醒是我们补的(补充,不在书里,来自通用知识)。

  10. 出处:「6.1 模型预训练」第 157 段(text/19-ch06-01-6-1.txt:157,搜「拼接在⼀起」)与第 165 段(text/19-ch06-01-6-1.txt:165,搜「block_size = 2048」)。原文:「由于预训练一般为 CLM 任务,一次性学习多个样本的序列语义不影响模型性能,且训练数据量大、训练时间长,对训练效率要求比较高」。

  11. 出处:「6.1 模型预训练」第 121 段(text/19-ch06-01-6-1.txt:121,搜「内存不够」)。

  12. 出处:「6.1 模型预训练」第 199 段(text/19-ch06-01-6-1.txt:199,搜「Trainer 封装了模型的训练逻辑」)。

  13. 出处:「6.2 模型有监督微调」第 54 段(text/20-ch06-02-6-2.txt:54,搜「仅对输」)与第 57 段(text/20-ch06-02-6-2.txt:57,搜「仅需要修改数据处理环节」)。原文:「Pretrain 会对全部 text 进行 loss 计算,要求模型对整个文本实现建模预测;而 SFT 仅对输出进行 loss 计算,不计算指令部分的 loss」。

  14. 出处:「6.2 模型有监督微调」第 63 段(text/20-ch06-02-6-2.txt:63,搜「Chat Template」)。原文:「当我们使用做过 SFT 的模型进行下游任务微调时,一般需要查看该模型的 Chat Template 并进行适配,即是为了不损伤其在 SFT 中学到的指令遵循能力」。 2

  15. 出处:「6.2 模型有监督微调」第 79 段(text/20-ch06-02-6-2.txt:79,搜「IGNORE_TOKEN_ID」)与第 142 段(text/20-ch06-02-6-2.txt:142,搜「-100」)。原文:「User 的文本不需要拟合,因此 targets 中 User 对应的文本内容是使用的 IGNORE_TOKEN_ID 进行遮蔽,而 Assistant 对应的文本内容则是文本原文,是需要计算 loss 的。目前主流 LLM IGNORE_TOKEN_ID 一般设置为 -100」。 2

  16. 出处:「6.2 模型有监督微调」第 89 段(text/20-ch06-02-6-2.txt:89,搜「human」)。原文:「此处由于数据集中的对话角色为 human,我们将 user 修改为了 human」。三个角色的说明见第 87 段(text/20-ch06-02-6-2.txt:87,搜「System 是系统提示词」)。

  17. 出处:「6.1 模型预训练」第 240 段(text/19-ch06-01-6-1.txt:240,搜「不推荐使⽤ Jupyter Notebook」)。

  18. 出处:「6.1 模型预训练」第 244 段(text/19-ch06-01-6-1.txt:244,搜「bash 脚本设定超参」)与第 336 段(text/19-ch06-01-6-1.txt:336,搜「HfArgumentParser」)。

  19. 出处:「6.1 模型预训练」第 343 段(text/19-ch06-01-6-1.txt:343,搜「不推荐使⽤ print」)与第 361 段(text/19-ch06-01-6-1.txt:361,搜「CRITICAL」)。 2

  20. 出处:「6.1 模型预训练」第 376 段(text/19-ch06-01-6-1.txt:376,搜「发⽣中断是往往难以避免」)与第 439 段(text/19-ch06-01-6-1.txt:439,搜「resume_from_checkpoint」)。

  21. 出处:「6.1 模型预训练」第 442 段(text/19-ch06-01-6-1.txt:442,搜「swanlab 作为训练检测」)。

  22. 出处:「6.1 模型预训练」第 456 段(text/19-ch06-01-6-1.txt:456,搜「deepspeed pretrain.py」)与第 484 段(text/19-ch06-01-6-1.txt:484,搜「Deepspeed 命令来启动多卡训练」)。

  23. 出处:「6.1 模型预训练」第 486 段(text/19-ch06-01-6-1.txt:486,搜「ZeRO-2」)。

  24. 出处:「6.3 高效微调」第 28 段(text/21-ch06-03-6-3.txt:28,搜「RLHF 的原理和实践细节」)与第 30 段(text/21-ch06-03-6-3.txt:30,搜「资源压⼒⾮常⼤」)。 2

  25. 出处:「6.3 高效微调」第 34 段(text/21-ch06-03-6-3.txt:34,搜「两种解决⽅案」)。

  26. 出处:「6.3 高效微调」第 36 段(text/21-ch06-03-6-3.txt:36,搜「Adapt Tuning」)与第 46 段(text/21-ch06-03-6-3.txt:46,搜「推理延迟」)。补充(不在书里):适配层的原始出处是《Parameter-Efficient Transfer Learning for NLP》,第一作者 Neil Houlsby,首次公开于 2019 年 2 月 2 日;摘要报告在 GLUE 上「每个任务只新增 3.6% 的参数,效果落后完整微调 0.4% 以内」。来源:https://arxiv.org/abs/1902.00751(查阅于 2026-08-25)。

  27. 出处:「6.3 高效微调」第 49 段(text/21-ch06-03-6-3.txt:49,搜「Prefix Tuning」)。补充(不在书里):前缀微调的原始出处是《Prefix-Tuning: Optimizing Continuous Prompts for Generation》,作者 Xiang Lisa Li 与 Percy Liang,首次公开于 2021 年 1 月 1 日;摘要说它「冻结语言模型参数,只优化一小段连续的、任务专属的向量(称为前缀)」,只训练 0.1% 的参数。来源:https://arxiv.org/abs/2101.00190(查阅于 2026-08-25)。

  28. 出处:「6.3 高效微调」第 53 段(text/21-ch06-03-6-3.txt:53,搜「Ptuning」)与第 54 段(text/21-ch06-03-6-3.txt:54,搜「virtual tokens」)。 2

  29. 出处:「6.3 高效微调」第 58 段(text/21-ch06-03-6-3.txt:58,搜「⾼维空间进⾏处理」)与第 61 段(text/21-ch06-03-6-3.txt:61,搜「本征秩」)。「秩是什么」这层解释是我们补的(补充,不在书里,来自通用知识)。

  30. 出处:「6.3 高效微调」第 63 段(text/21-ch06-03-6-3.txt:63,搜「隐式地降低了本征秩」)。

  31. 补充(不在书里,但书里在参考文献里列了这篇):《Intrinsic Dimensionality Explains the Effectiveness of Language Model Fine-Tuning》,作者 Armen Aghajanyan、Luke Zettlemoyer、Sonal Gupta,首次公开于 2020 年 12 月 22 日。摘要里的两个关键结果是:「只优化 200 个随机投影回全空间的可训练参数,就能让 RoBERTa 在 MRPC 上达到全参数微调 90% 的水平」;以及「在固定的预训练更新步数下,更大的模型倾向于有更低的本征维度」。来源:https://arxiv.org/abs/2012.13255(查阅于 2026-08-25)。书里引用它的地方见「6.3 高效微调」第 64 段(text/21-ch06-03-6-3.txt:64,搜「Intrinsic Dimensionality」)。

  32. 出处:「6.3 高效微调」第 73 段(text/21-ch06-03-6-3.txt:73,搜「秩分解矩阵」)与第 93 段(text/21-ch06-03-6-3.txt:93,搜「低秩分解来表示其更新」)。补充(不在书里):LoRA 的原始出处是《LoRA: Low-Rank Adaptation of Large Language Models》,第一作者 Edward J. Hu,首次公开于 2021 年 6 月 17 日;摘要说它「冻结预训练模型权重,把可训练的秩分解矩阵注入 Transformer 的每一层」,相较用 Adam 全量微调 GPT-3 175B「可训练参数减少一万倍、显存需求减少三倍」,且「不增加推理延迟」。来源:https://arxiv.org/abs/2106.09685(查阅于 2026-08-25)。

  33. 出处:「6.3 高效微调」第 95 段(text/21-ch06-03-6-3.txt:95,搜「冻结不更新」)与第 99 段(text/21-ch06-03-6-3.txt:99,搜「随机⾼斯初始化」)。「一开始 A×B = 0 所以输出不变」这层解释是我们补的(补充,不在书里,来自通用知识)。

  34. 出处:「6.3 高效微调」第 77 段(text/21-ch06-03-6-3.txt:77,搜「⼩型 LoRA 模块」)、第 79 段(text/21-ch06-03-6-3.txt:79,搜「⾃适应优化器」)、第 82 段(text/21-ch06-03-6-3.txt:82,搜「不存在推理延迟」)、第 84 段(text/21-ch06-03-6-3.txt:84,搜「正交」)。

  35. 补充(不在书里):LoRA 论文正文的优势列表里的原句是:「LoRA makes training more efficient and lowers the hardware barrier to entry by up to 3 times when using adaptive optimizers since we do not need to calculate the gradients or maintain the optimizer states for most parameters.」——「当使用自适应优化器时」是条件从句。来源:https://arxiv.org/html/2106.09685v2(查阅于 2026-08-25)。

  36. 出处:「6.3 高效微调」第 106 段(text/21-ch06-03-6-3.txt:106,搜「冻结 MLP」)、第 109 段(text/21-ch06-03-6-3.txt:109,搜「消融实验」)、第 115 段(text/21-ch06-03-6-3.txt:115,搜「r 取到」)。

  37. 出处:「6.3 高效微调」第 139 段(text/21-ch06-03-6-3.txt:139,搜「target_modules」)与第 141 段(text/21-ch06-03-6-3.txt:141,搜「q_proj」)。

  38. 出处:「6.3 高效微调」第 275 段(text/21-ch06-03-6-3.txt:275,搜「BaiChuan」)。

  39. 出处:「6.3 高效微调」第 118 段(text/21-ch06-03-6-3.txt:118,搜「peft 库」)。

  40. 出处:「6.3 高效微调」第 265 段(text/21-ch06-03-6-3.txt:265,搜「设定 peft 参数」)与第 280 段(text/21-ch06-03-6-3.txt:280,搜「get_peft_model」)、第 284 段(text/21-ch06-03-6-3.txt:284,搜「显存就会有⼤幅度的降低」)。

  41. 出处:「6.3 高效微调」第 126 段(text/21-ch06-03-6-3.txt:126,搜「确定要使⽤ LoRA 的层」)至第 132 段(text/21-ch06-03-6-3.txt:132,搜「冻结原参数」)。

  42. 出处:「6.3 高效微调」第 296 段(text/21-ch06-03-6-3.txt:296,搜「知识的注⼊」)。

  43. 出处:「6.3 高效微调」第 294 段(text/21-ch06-03-6-3.txt:294,搜「DPO、KTO」)。

  44. 补充(不在书里):《QLoRA: Efficient Finetuning of Quantized LLMs》,作者 Tim Dettmers、Artidoro Pagnoni、Ari Holtzman、Luke Zettlemoyer,首次公开于 2023 年 5 月 23 日。摘要说它「把显存占用降到能在单张 48GB 显卡上微调 650 亿参数的模型」,同时保持 16 位全量微调的效果;三项技术是 4 位 NormalFloat 数据类型、双重量化、以及分页优化器。来源:https://arxiv.org/abs/2305.14314(查阅于 2026-08-25)。「量化」指的是用更少的位数来存每个参数,以牺牲一点精度换显存。