1. 预训练阶段(Pretraining Stage)

当前,不少工作选择在一个较强的基座模型上进行微调,且通常效果不错(如:[https://github.com/tatsu-lab/stanford_alpaca]、[https://lmsys.org/blog/2023-03-30-vicuna/] 等)。

这种成功的前提在于:预训练模型和下游任务的差距不大,预训练模型中通常已经包含微调任务中所需要的知识。

但在实际情况中,我们通常会遇到一些问题,使得我们无法直接使用一些开源 backbone:

  1. 语言不匹配:大多数开源基座对中文的支持都不太友好,例如:[https://huggingface.co/decapoda-research/llama-7b-hf]、[https://huggingface.co/mosaicml/mpt-7b]、[https://huggingface.co/tiiuae/falcon-7b] 等,这些模型在英文上效果都很优秀,但在中文上却差强人意。
续写任务测试 LLaMA MPT
杭州西湖是 杭州西湖是杭州的一个静静的一个游泳池,游泳池是杭州西湖的一个游泳池,游泳池是杭州西湖的一个游泳池,游泳池是杭州西湖的一个游泳池,� 杭州西湖是中国最大的湖泊,是中国最大的湖泊,是中国最大的湖泊,是中国最大的湖泊,是中国最大的湖泊,是中国最大的湖泊,是中国最大的湖泊,
琅琊榜的导演是 琅琊榜的导演是很多的人都不知道,因为他的父亲是一位杰作家,他的父亲的杰作家是一位杰作家, 琅琊榜的导演是谁?Who are the directors of the Rolling Stone?琅琊榜的导演是谁?Who are the
  1. 专业知识不足:当我们需要一个专业领域的 LLM 时,预训练模型中的知识就尤为重要。由于大多数预训练模型都是在通用训练语料上进行学习,对于一些特殊领域(金融、法律等)中的概念和名词无法具备很好的理解。我们通常需要在训练语料中加入一些领域数据(如:[https://arxiv.org/pdf/2305.12002.pdf]),以帮助模型在指定领域内获得更好的效果。

基于上述原因,我们在进行 SFT 步骤之前,先来看看预训练任务是如何做的。

1.1 Tokenizer Training

在进行预训练之前,我们需要先选择一个预训练的模型基座。

一个较为普遍的问题是:大部分优秀的语言模型都没有进行充分的中文预训练,

因此,许多工作都尝试将在英语上表现比较优秀的模型用中文语料进行二次预训练,期望其能够将英语上的优秀能力迁移到中文任务中来。

已经有许多优秀的仓库做过这件事情,比如:[https://github.com/ymcui/Chinese-LLaMA-Alpaca]。

但在进行正式的训练之前,我们还有一步很重要的事情去做:词表扩充。

通俗来讲,tokenizer 的目的就是将一句话进行切词,并将切好词的列表喂给模型进行训练。

例如:

1
2
输入句子 >>> 你好世界
切词结果 >>> ['你', '好', '世', '界']

通常,tokenizer 有 2 种常用形式:WordPiece 和 BPE。

  • WordPiece

WordPiece 很好理解,就是将所有的「常用字」和「常用词」都存到词表中,

当需要切词的时候就从词表里面查找即可。

bert-base-chinese tokenizer 可视化

上述图片来自可视化工具 [https://github.com/HarderThenHarder/transformers_tasks/blob/main/tools/tokenizer_viewer/readme.md]。

如上图所示,大名鼎鼎的 BERT 就使用的这种切词法。

当我们输入句子:你好世界,

BERT 就会依次查找词表中对应的字,并将句子切成词的组合。

BERT 切词测试图

当遇到词表中不存在的字词时,tokenizer 会将其标记为特殊的字符 [UNK]:

Out of Vocabulary(OOV)情况

  • Byte-level BPE(BBPE)

有关 BBPE 的原理可以参考 [https://zhuanlan.zhihu.com/p/146114164]。

WordPiece 的方式很有效,但当字词数目过于庞大时这个方式就有点难以实现了。

对于一些多语言模型来讲,要想穷举所有语言中的常用词(穷举不全会造成 OOV),既费人力又费词表大小,为此,人们引入另一种方法:BBPE。

BPE 不是按照中文字词为最小单位,而是按照 unicode 编码 作为最小粒度。对于中文来讲,一个汉字是由 3 个 unicode 编码组成的,

我们来看看 LLaMA 的 tokenizer 对中文是如何进行 encode 的:

LLaMa tokenizer 中文测试

可以看到,「编码」两个字能够被正常切成 2 个字,

但「待」却被切成了 3 个 token,这里的每个 token 就是 1 个 unicode 编码。

通过 token 查找功能,我们可以发现「编」「码」在词表中,但「待」不在词表中。

但任何 1 个汉字都是可以由 unicode 表示(只是组合顺序不同),因此「待」就被切成了 3 个 token。

BBPE 的优势 不会出现 OOV 的情况。不管是怎样的汉字,只要可以用 unicode 表示,就都会存在于词表中。
BBPE 的劣势 模型训练起来将会更吃力一些。毕竟像「待」这样的汉字特定 unicode 组合其实是不需要模型学习的,但模型却需要通过学习来知道合法的 unicode 序列。

通常在模型训练不够充足的时候,模型会输出一些乱码(不合法的 unicode 序列):

1
游泳池是杭州西湖的一个游泳池,���
  • 词表扩充

为了降低模型的训练难度,人们通常会考虑在原来的词表上进行「词表扩充」,也就是将一些常见的汉字 token 手动添加到原来的 tokenizer 中,从而降低模型的训练难度。

我们对比 [https://github.com/ymcui/Chinese-LLaMA-Alpaca] 和 [https://huggingface.co/decapoda-research/llama-7b-hf] 之间的 tokenizer 的区别:

我们可以发现:Chinese LLaMA 在原始 tokenizer 上新增了17953 个 tokens,且加入 token 的大部分为汉字。而在 [https://arxiv.org/pdf/2304.07854.pdf] 中也有同样的做法:在 120w 行中文文本上训练出一个 5w 规模的 token 集合,并将这部分 token 集合与原来的 LLaMA 词表做合并,最后再在 3.2B 的中文语料上对这部分新扩展的 token embedding 做二次预训练。

1.2 Language Model PreTraining

在扩充完 tokenizer 后,我们就可以开始正式进行模型的预训练步骤了。

Pretraining 的思路很简单,就是输入一堆文本,让模型做 Next Token Prediction 的任务,这个很好理解。

我们主要来讨论几种预训练过程中所用到的方法:数据源采样、数据预处理、模型结构。

  • 数据源采样

在 [https://arxiv.org/pdf/2005.14165.pdf] 的训练过程中,存在多个训练数据源,论文中提到:对不同的数据源会选择不同采样比例:

通过「数据源」采样的方式,能够缓解模型在训练的时候受到「数据集规模大小」的影响。从上图中可以看到,相对较大的数据集(Common Crawl)会使用相对较大的采样比例(60%),这个比例远远小于该数据集在整体数据集中所占的规模(410 / 499 = 82.1%),因此,CC 数据集最终实际上只被训练了 0.44(0.6 / 0.82 * (300 / 499))个 epoch。而对于规模比较小的数据集(Wikipedia),则将多被训练几次(3.4 个 epoch)。这样一来就能使得模型不会太偏向于规模较大的数据集,从而失去对规模小但作用大的数据集上的学习信息。

  • 数据预处理

数据预处理主要指如何将「文档」进行向量化。

通常来讲,在 Finetune 任务中,我们通常会直接使用 truncation 将超过阈值(2048)的文本给截断,但在 Pretrain 任务中,这种方式显得有些浪费。以书籍数据为例,一本书的内容肯定远远多余 2048 个 token,但如果采用头部截断的方式,则每本书永远只能够学习到开头的 2048 tokens 的内容(连序章都不一定能看完)。因此,最好的方式是将长文章按照 seq_len(2048)作分割,将切割后的向量喂给模型做训练。

  • 模型结构

为了加快模型的训练速度,通常会在 decoder 模型中加入一些 tricks 来缩短模型训练周期。目前大部分加速 tricks 都集中在 Attention 计算上(如:MQA 和 Flash Attention [https://huggingface.co/tiiuae/falcon-40b] 等);此外,为了让模型能够在不同长度的样本上都具备较好的推理能力,通常也会在 Position Embedding 上进行些处理,选用 ALiBi([https://huggingface.co/bigscience/bloom-7b1])或 RoPE([https://huggingface.co/spaces/THUDM/GLM-130B])等。

具体内容可以参考下面这篇文章:

何枝:【LLM 加速技巧】Muti Query Attention 和 Attention with Linear Bias(附源码)

  • Warmup & Learning Ratio 设置

在继续预训练中,我们通常会使用 warmup 策略,此时我们按照 2 种不同情况划分:

  1. 当训练资源充足时,应尽可能选择较大的学习率以更好的适配下游任务;
  2. 当资源不充足时,更小的学习率和更长的预热步数或许是个更好的选择。

具体内容可以参考下面这篇文章:

何枝:如何更好地继续预训练(Continue PreTraining)

1.3 数据集清理

中文预训练数据集可以使用 [https://data.baai.ac.cn/details/WuDaoCorporaText],数据集分布如下(主要以百科、博客为主):

但开源数据集可以用于实验,如果想突破性能,则需要我们自己进行数据集构建。在 [https://arxiv.org/pdf/2306.01116.pdf] 中提到,仅使用「清洗后的互联网数据」就能够让模型比在「精心构建的数据集」上有更好的效果,一些已有的数据集和它们的处理方法如下:

有关 Falcon 更多的细节可以看这里:

何枝:【Falcon Paper】我们是靠洗数据洗败 LLaMA 的!

1.4 模型效果评测

关于 Language Modeling 的量化指标,较为普遍的有 PPL BPC等,可以简单理解为在生成结果和目标文本之间的 Cross Entropy Loss 上做了一些处理。这种方式可以用来评估模型对「语言模板」的拟合程度,即给定一段话,预测后面可能出现哪些合法的、通顺的字词。但仅仅是「生成通顺句子」的能力现在已经很难满足现在人们的需求,大部分 LLM 都具备生成流畅和通顺语句能力,很难比较哪个好,哪个更好。

为此,我们需要能够评估另外一个大模型的重要能力 —— 知识蕴含能力

  • C-Eval

一个很好的中文知识能力测试数据集是 [https://github.com/SJTU-LIT/ceval],涵盖1.4w 道选择题,共 52 个学科。

覆盖学科如下:

由于是选择题的形式,我们可以通过将题目写进 prompt 中,

并让模型续写 1 个 token,判断这个续写 token 的答案是不是正确答案即可。

但大部分没有精调过的预训练模型可能无法续写出「A B C D」这样的选项答案,

因此,官方推荐使用 5-shot 的方式来让模型知道如何输出答案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
以下是中国关于会计考试的单项选择题,请选出其中的正确答案。

下列关于税法基本原则的表述中,不正确的是____
A. 税收法定原则包括税收要件法定原则和税务合法性原则
B. 税收公平原则源于法律上的平等性原则
C. 税收效率原则包含经济效率和行政效率两个方面
D. 税务机关按法定程序依法征税,可以自由做出减征、停征或免征税款的决定
答案:D

甲公司是国内一家领先的新媒体、通信及移动增值服务公司,由于遭受世界金融危机,甲公司经济利润严重下滑,经营面临困境,但为了稳定职工队伍,公司并未进行裁员,而是实行高层管理人员减薪措施。甲公司此举采用的收缩战略方式是____
A. 转向战略
B. 放弃战略
C. 紧缩与集中战略
D. 稳定战略
答案:C

... #3, 4, 5 道样例题

下列各项中,不能增加企业核心竞争力的是____
A. 产品差异化
B. 购买生产专利权
C. 创新生产技术
D. 聘用生产外包商
答案:

通过前面的样例后,模型能够知道在「答案:」后面应该输出选项字母。于是,我们获得模型续写后的第一个 token 的概率分布(logits),并取出「A B C D」这 4 个字母的概率,通过 softmax 进行归一化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
probs = (
torch.nn.functional.softmax(
torch.tensor(
[
logits[self.tokenizer.encode(
"A", bos=False, eos=False)[0]],
logits[self.tokenizer.encode(
"B", bos=False, eos=False)[0]],
logits[self.tokenizer.encode(
"C", bos=False, eos=False)[0]],
logits[self.tokenizer.encode(
"D", bos=False, eos=False)[0]],
]
),
dim=0,
).detach().cpu().numpy()
)
pred = {0: "A", 1: "B", 2: "C", 3: "D"}[np.argmax(probs)] # 将概率最大的选项作为模型输出的答案

C-Eval 通过这种方式测出了许多模型在中文知识上的效果,

由于是 4 选项问题,所以基线(随机选择)的正确率是 25%。

C-Eval 也再一次证明了 GPT-4 是个多么强大的知识模型:

2. 指令微调阶段(Instruction Tuning Stage)

在完成第一阶段的预训练后,就可以开始进到指令微调阶段了。

由于预训练任务的本质在于「续写」,而「续写」的方式并一定能够很好的回答用户的问题。

例如:

用户问题 用户预期回答 模型续写结果
《无间道》的主演有哪些? 刘德华、梁朝伟 《无间道》的主演有哪些?不少观众期待看到阵容公告,今天小编…

因为训练大多来自互联网中的数据,我们无法保证数据中只存在规范的「一问一答」格式,这就会造成预训练模型通常无法直接给出人们想要的答案。但是,这并不代表预训练模型「无知」,只是需要我们用一些巧妙的「技巧」来引导出答案:

用户问题 用户预期回答 模型续写结果
《无间道》的主演有 刘德华、梁朝伟 《无间道》的主演有刘德华、梁朝伟和黄秋生,而这部电影也是香港警匪片的代表作之一。

不过,这种需要用户精心设计从而去「套」答案的方式,显然没有那么优雅。

既然模型知道这些知识,只是不符合我们人类的对话习惯,那么我们只要再去教会模型「如何对话」就好了。

这就是 Instruction Tuning 要做的事情,即指令对齐

OpenAI 在 [https://openai.com/research/instruction-following] 中展示了 GPT-3 和经过指令微调前后模型的区别:

2.1Self Instruction

既然我们需要去「教会模型说人话」,那么我们就需要去精心编写各式各样人们在对话中可能询问的问题,以及问题的答案。

在 [https://arxiv.org/pdf/2203.02155.pdf] 中,使用了 1.3w 的数据来对 GPT-3.5 进行监督学习(下图中左 SFT Data):

可以观察到,数据集中人工标注(labeler)占大头,这还仅仅只是 InstructGPT,和 ChatGPT 远远不是一个量级。可见,使用人工标注是一件成本巨大的事情,除了找到足够的人数,还需要保持团队中每个人的「专业」且「认知一致」。如果这件事从头开始做自然很难(OpenAI 确实厉害),但今天我们已经有了 ChatGPT 了。

我们让 ChatGPT 来教我们自己的模型不就好了吗?

这就是 Self Instruction 的思路,即通过 ChatGPT 的输入输出来蒸馏自己的模型。一个非常出名的项目是 stanford_alpaca

如果从 ChatGPT 「套」数据,那么我们至少需要「套」哪些数据?Instruction Tuning 中的「输入」(问题)和「输出」(答案)是训练模型的关键,答案很好得到,喂给 ChatGPT 问题根据返回结果就能获得,但「问题」从哪里获得呢?(靠人想太累了,屏幕前的你不妨试试,看看短时间内能想出多少有价值的问题)

Alpaca 则是使用「种子指令(seed)」,使得 ChatGPT 既生成「问题」又生成「答案」。由于 Alpaca 是英文项目,为了便于理解,我们使用相同思路的中文项目 BELLE 作为例子。通俗来讲,就是人为的先给一些「训练数据样例」让 ChatGPT 看,紧接着利用 ChatGPT 的续写功能,让其不断地举一反三出新的训练数据集:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
你被要求提供10个多样化的任务指令。这些任务指令将被提供给GPT模型,我们将评估GPT模型完成指令的能力。
以下是你提供指令需要满足的要求:
1.尽量不要在每个指令中重复动词,要最大化指令的多样性。
2.使用指令的语气也应该多样化。例如,将问题与祈使句结合起来。
3.指令类型应该是多样化的,包括各种类型的任务,类别种类例如:brainstorming,open QA,closed QA,rewrite,extract,generation,classification,chat,summarization。
4.GPT语言模型应该能够完成这些指令。例如,不要要求助手创建任何视觉或音频输出。例如,不要要求助手在下午5点叫醒你或设置提醒,因为它无法执行任何操作。例如,指令不应该和音频、视频、图片、链接相关,因为GPT模型无法执行这个操作。
5.指令用中文书写,指令应该是1到2个句子,允许使用祈使句或问句。
6.你应该给指令生成适当的输入,输入字段应包含为指令提供的具体示例,它应该涉及现实数据,不应包含简单的占位符。输入应提供充实的内容,使指令具有挑战性。
7.并非所有指令都需要输入。例如,当指令询问一些常识信息,比如“世界上最高的山峰是什么”,不需要提供具体的上下文。在这种情况下,我们只需在输入字段中放置“<无输入>”。当输入需要提供一些文本素材(例如文章,文章链接)时,就在输入部分直接提供一些样例。当输入需要提供音频、图片、视频或者链接时,则不是满足要求的指令。
8.输出应该是针对指令和输入的恰当回答。
下面是10个任务指令的列表:
###
1. 指令: 在面试中如何回答这个问题?
1. 输入:当你在车里独处时,你会想些什么?
1. 输出:如果是在晚上,我通常会考虑我今天所取得的进步,如果是在早上,我会思考如何做到最好。我也会尝试练习感恩和活在当下的状态,以避免分心驾驶。
###
2. 指令: 按人口对这些国家进行排名。
2. 输入:巴西,中国,美国,日本,加拿大,澳大利亚
2. 输出:中国,美国,巴西,日本,加拿大,澳大利亚
###
3. 指令:

如上述例子所示,我们先给出 2 个样例,并让 ChatGPT 进行续写:

关于 BELLE 的更多细节可以参考这篇文章:

何枝:让ChatGPT生成训练ChatGPT的训练数据

2.2 开源数据集整理

在这一章中,我们将梳理一些开源的 Instruction Tuning 的数据集,

除了直接拿来用以外,我们期望通过分析这些已有数据集,从而学习如何构建一个指令数据集。

  • Alpaca

[https://github.com/tatsu-lab/stanford_alpaca/blob/main/alpaca_data.json] 采用上述的 self instruction 的方式采集了 5200 条指令训练数据集。

数据样例如下:

1
2
3
4
5
{
"instruction": "Arrange the words in the given sentence to form a grammatically correct sentence.",
"input": "quickly the brown fox jumped",
"output": "The quick brown fox jumped quickly."
}

其中,instruction 代表要求模型做的任务,input 代表用户输入, output 代表喂给模型的 label。

Alpaca 覆盖了多种类型的指令,其数据分布如下:包含写作、描述、分类、摘要等多种类型的任务集合。

  • BELLE

BELLE 开放了好几种规模的数据集,100万200万300万 等。

训练数据集样例如下:

1
2
3
4
5
{
"instruction": "判断给定的文章是否符合语法规则。如果不符合,请提供修改建议。 下面是一篇文章的开头: ‘为了探讨这个主题,本文将提供一系列数据和实例,以证明这一观点。’",
"input": "",
"output": "这个开头符合语法规则。"
}

1000条评测集 数据分布可以推测出,训练数据集中同样包含:摘要、问答、分类等任务。

我们按照类别对评测数据进行采样,结果如下:

任务名称 例子
文本生成 为一种智能手表编写用户手册,包括详细的使用说明和操作步骤。
头脑风暴 针对给定的主题,进行头脑风暴并记录所有想法。如何提高公司的销售额?
开放域问答 用一两句话描述著名的尼罗河是如何形成的。
封闭域问答 从以下选项中选择正确的词汇填空以完整下面的句子。 他喜欢去_______看电影。A) 邮局 B)超市 C)电影院 D)音乐会
分类 请将以下这篇文章分类为新闻报道、科学文章或社论。据媒体新闻援引美国福克斯新闻网报道,美国伯克希尔哈撒韦公司首席执行官、著名投资人巴菲特近日就美国银行业危机与总统拜登的团队进行对话。
抽取 基于以下表格,请问张三的考勤情况员工姓名,日期,上班时间,下班时间,是否迟到,是否早退,是否请假张三,1月1日,8:30,17:30,否,否,否李四,1月1日,9:00,18:00,是,否,否王五,1月1日,8:00,16:30,否,是,否赵六,1月1日,8:30,17:00,否,否,是张三,1月2日,8:00,17:00,否,否,否李四,1月2日,8:30,17:30,否,否,否王五,1月2日,9:00,18:00,是,否,否赵六,1月2日,8:30,17:00,否,否,是
重写 根据提供的文本重写其中的一段,使之更加简明扼要,同时不丢失原文本的主要信息。纽约市,简称“纽约”,通常被称为“大苹果”,是美国最大的城市,也是全世界最大的城市之一。位于美国东海岸,东北部边界是大西洋,在新泽西州的东南部。
摘要 基于下面的这个故事,总结其中最重要的三个事件。小明是一个好学生,每天早上都要起得很早去上学。有一天,他迟到了,因为他的家里来了一个客人。晚上,他参加了一次班级会议,会议主题是如何提高学习效率。回到家后,他又花了一些时间复习功课。
Code & Math 按照以下要求,写一个SQL查询语句:从表中查找所有性别为女性的学生的姓名和学号。SELECT name, id FROM students WHERE gender = ‘女性’
  • Vicuna
  • BAIZE

2.3 模型的评测方法

比起预训练(Pretrain)环节里相对明确的评价指标(如PPL、NLL等),Instruction 环节中的评价指标比较令人头疼。鉴于语言生成模型的发展速度,BLEU 和 ROUGH 这样的指标已经不再客观。一种比较流行的方式是像 FastChat 中一样,利用 GPT-4 为模型的生成结果打分,我们也尝试使用同样的 Prompt 对 3 种开源模型:OpenLlama、ChatGLM、BELLE 进行测试。

注意:下面的测试结果仅源自我们自己的实验,不具备任何权威性

对于每一个问题,我们先获得 ChatGPT 的回复,以及另外 3 种模型的回复,接着我们将 「ChatGPT 答案 - 候选模型答案」这样的 pair 喂给 GPT-4 打分(满分为 10 分)。得到的结果如下:

我们对每个任务单独进行了统计,并在最后一列求得平均值。GPT-4 会对每一条测试样本的 2 个答案分别进行打分,并给出打分理由:

但是,我们发现,GPT-4 打出的分数和给出理由并不一定正确。如上图所示,GPT-4 为右边模型的答案打出了更高的分数,给出的理由是:将「最长时期」改为了「最长时期之一」会更准确。但事实上,Instruction 中明确设定就是「最长时期」,这种「给高分」的理由其实是不正确的。此外,我们还发现,仅仅调换句子顺序也会对最后打分结果产生影响,针对这个问题,我们考虑「调换句子顺序并求和平均」来缓解。但不管怎么样,GPT-4 给出的分数或许并没有我们想象中的那么靠谱,为此,我们通过人工的 Review 的方式对每个答案进行了一次回扫,得到的结果和标准如下:

再次重申:我们只是期望指出 GPT-4 打分可能会和实际产生偏差的问题,这里排名不具备任何权威性

我们可以看到,在 GPT-4 打分的结果中,已经有模型的效果甚至超过了 ChatGPT(分数为 1.02),但再经过人工 Review 后,ChatGPT 的答案是我们认为更合理一些的。当然,最近陆陆续续的推出了许多新的评测方法,如:PandaLM,以及许多比较有影响力的评测集,如:C-Evalopen_llm_leaderboard 等,我们或许会在后续的整理中更新。

3. 奖励模型(Reward Model)

我们在很早以前写过一篇 reward model 相关的文章,解释了 reward model 的基本原理和一些实验代码(包括 rank_list 的标注平台):

但开源代码中是使用 encoder 作为训练基座的,因此如果只是想了解 reward model 基本概念的话可以参考这篇文章:

何枝:【RLHF】想训练ChatGPT?得先弄明白Reward Model怎么训(附源码)

3.1 奖励模型(Reward Model)的必要性

其实,当我们在做完 SFT 后,我们大概率已经能得到一个还不错的模型。但我们回想一下 SFT 的整个过程**:我们一直都在告诉模型什么是「好」的数据,却没有给出「不好」的数据**。我们更倾向于 SFT 的目的只是将 Pretrained Model 中的知识给引导出来的一种手段,而在SFT 数据有限的情况下,我们对模型的「引导能力」就是有限的。这将导致预训练模型中原先「错误」或「有害」的知识没能在 SFT 数据中被纠正,从而出现「有害性」或「幻觉」的问题。

为此,一些让模型脱离昂贵标注数据,自我进行迭代的方法被提出,比如:RLHFDPO,但无论是 RL 还是 DPO,我们都需要让告知模型什么是「好的数据」,什么是「不好的数据」。

RL 是直接告诉模型当前样本的(好坏)得分,DPO 是同时给模型一条好的样本和一条坏的样本。

而判断样本数据的「好坏」除了昂贵的人工标注之外,那就是 Reward Model 大显身手的时候了。

3.2 利用偏序对训练奖励模型

在 OpenAI 的 SummarizationInstructGPT 的论文中,都使用了「偏序对」来训练模型。

偏序对是指:不直接为每一个样本直接打分,而是标注这些样本的好坏顺序。

直接打分:A句子(5分),B句子(3分)
偏序对标注:A > B

模型通过尝试最大化「好句子得分和坏句子得分之间的分差」,从而学会自动给每一个句子判分。

为什么要使用偏序对而不是直接打分可以看上面给出的文章链接。

我们可以来做一个简单实验,我们构造一批如下数据:

1
2
3
4
5
{
"prompt": "下面是一条正面的评论:",
"selected": "屯了一大堆,今年过年的话蛮富足的!到货很快的!",
"rejected": "对商品谈不上满意,但是你们店的信誉极度不满意,买了件衣服取消了订单,要求退款,结果退款过程进行一半就不进行了,真是因小失大啊"
}

其中,prompt 是要求模型续写一条好评,selected 是一条好的回答(A),rejected 是一条不好的回答(B)。

我们使用 Llama-2-7b 作为基座模型训练,期望模型对于 A 回答能够给尽可能高的分,B 回答则尽可能低。

我们将训练过程中的「分差变化」绘制出来,伴随着 loss 的降低,我们发现分差的均值和方差都呈上升的趋势:

Note:这里的分差是指 r(好答案) - r(坏答案) 的分差。

我们进一步的绘制出在 100 个评测样本的分差分布:

在 step 0(未训练)时,模型打出来的分差是一个近似均值为 0,方差为 0.1 的正态分布,这说明初始模型无法区分数据的好坏(好数据 - 坏数据的得分有正有负)。随着模型训练,分布均值从 0 开始逐渐增长,这证明模型开始逐渐让「好数据」 - 「坏数据」的分差越来越大。到第 60 个 step 之后,分布的均值和方差开始趋于稳定。

至此,我们已经让 RM 学会了给「正向评论」打分更高,给「负向评论」打分更低。但是由于偏序对本身「过于粗糙」,会导致 RM 的打分并不足够精准

后续一些工作在标注偏序的时候不仅标注了 A 好于 B,还同时标注了 A 比 B 好多少,具体细节可以看:

何枝:Llama 2 中使用 RLHF 的一些细节:margin r、reject sampling 和 PPO

还有一些工作会在 Reward Gap Loss 上添加 Prefered Samples 的 LM Loss:

何枝:【RLHF】怎样让 PPO 训练更稳定?早期人类征服 RLHF 的驯化经验

3.3 使用多少数据能够训练好一个RM?

在 OpenAI Summarize 的任务中,使用了 6.4w 条 偏序对进行训练。

在 InstructGPT 任务中,使用了 3.2w 条 [4~9] 偏序对进行训练。

StackLlama 任务中,使用了 10w 条 Stack Exchange偏序对进行训练。

从上述工作中,我们仍无法总结出一个稳定模型需要的最小量级,这取决于具体任务。但至少看起来,5w 以上的偏序对可能是一个相对保险的量级。关于 Reward Model 的 Scaling Law 讨论可以看看 OpenAI 的论文:

何枝:RLHF 训练中,如何挑选最好的 checkpoint?

3.4RM 模型的大小限制?

Reward Model 的作用本质是给生成模型的生成内容进行打分,所以 Reward Model 只要能理解生成内容即可。

关于 RM 的规模选择上,目前没有一个明确的限制:

  • Summarize 使用了 6B 的 RM,6B 的 LM。

  • InstructGPT 使用了 6B 的 RM,175B 的 LM。

  • DeepMind 使用了 70B 的 RM,70B LM。

不过,一种直觉的理解是:判分任务要比生成认为简单一些,因此可以用稍小一点的模型来作为 RM。

4. 强化学习(Reinforcement Learning,PPO)

在获得了一个 Reward Model 后,我们便可以利用这个 RM 来进化我们的模型。

目前比较主流的优化方式有 3 种:BON,DPO 和 PPO。

4.1 Best-of-N(BON)

BON 也叫 reject sampling,是指我们通过设置 temperature 值让同一个模型生成若干回复,接着,使用 Reward Model 挑出这些回复中得分较高的回复并再次训练原本的模型。

Llama2 Paper中使用了这种方法,由于这是一个循环迭代的过程(Sample -> SFT -> Sample -> …),论文中指出:在进行 SFT 时,应当使用之前所有策略下的 Good Samples(而非仅是最近一次策略模型 Sample 出的样本),以提高模型的泛化性。

“我们将之前每一个 stage 中优秀样本都汇聚到一起进行训练,这使得最后的效果显著提升”

BON 和 RL 的区别主要有以下 2 点:

  • 探索广度:对同一个 prompt,BON 一次会进行 N 次采样,但 PPO 每次只会采样 1 个答案。
  • 进化深度:BON 的方法只会进行一次模型的「采样-迭代」,而 PPO 会重复进行「采样-进化-采样-进化」。但我们同样可以持续做多个轮回的 BON,这种情况下这 2 种方法的差别就不那么大。

在这一篇 [https://arxiv.org/pdf/2210.10760.pdf] 中对比了 BON & RL 在训练效果上的区别,

相比于 RL,BON 的训练曲线要更稳定(不那么容易崩),但从最终效果来看 RL 的上限会更高一些:

图左为 BON 训练 Reward 曲线,图右为 RL 训练 Reward 曲线

4.2 Direct Preference Optimisatio(DPO)

[https://arxiv.org/pdf/2305.18290.pdf] 是一种不需要 Reward Model 的训练方法,它可以用训练 RM 的偏序对来直接训练模型本身。

具体来讲,DPO 借鉴了对比学习的思路,

其目标是**:对于同一个 prompt,尽可能大的拉开 selected 答案和 rejected 答案之间的生成概率。**

DPO Loss 的实现公式

上述公式源自 [https://arxiv.org/pdf/2310.12036.pdf],代码中实现也普遍采样该公式。

以下是 [https://github.com/huggingface/trl/blob/main/trl/trainer/dpo_trainer.py] 中对 DPO 中的 loss 实现方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def dpo_loss(
self,
policy_chosen_logps: torch.FloatTensor,
policy_rejected_logps: torch.FloatTensor,
reference_chosen_logps: torch.FloatTensor,
reference_rejected_logps: torch.FloatTensor,
reference_free: bool = False,
) -> Tuple[torch.FloatTensor, torch.FloatTensor, torch.FloatTensor]:
pi_logratios = policy_chosen_logps - policy_rejected_logps # 当前模型的 good/bad sample 的概率差
ref_logratios = reference_chosen_logps - reference_rejected_logps # Ref模型的 good/bad sample 的概率差

if reference_free:
ref_logratios = 0

logits = pi_logratios - ref_logratios # 如果 ref model 对这两个样本差异也很大,则不要拉的太猛(防止训飞了)

if self.loss_type == "sigmoid":
losses = -F.logsigmoid(self.beta * logits) # 最大化 good/bad sample 的概率差
elif self.loss_type == "hinge":
losses = torch.relu(1 - self.beta * logits)
else:
raise ValueError(f"Unknown loss type: {self.loss_type}. Should be one of ['sigmoid', 'hinge']")

chosen_rewards = self.beta * (policy_chosen_logps - reference_chosen_logps).detach()
rejected_rewards = self.beta * (policy_rejected_logps - reference_rejected_logps).detach()

return losses, chosen_rewards, rejected_rewards

4.3 Proximal Policy Optimization(PPO)

[https://arxiv.org/pdf/1707.06347.pdf] 是强化学习中一种基于AC架构(Actor-Critic)的优化方法,其前身是TRPO,

PPO通过引入重要性采样(Importance Sampling)来缓解 on policy 模型一次采样数据只能更新一次模型的问题,提升了数据利用率和模型训练速度。

在 LLM 的训练中,使用 PPO 需要同时载入 4 个模型:

  • Actor Model:用于进化训练的生成模型
  • Critic Model:用于进化训练的评判模型
  • Ref Model:参照模型,通过 KL 来限制 Actor 模型的训练方向
  • Reward Model:奖励模型,用于指导 Actor 进化

为了节省显存,通常会将 actor / critic 共享一个 backbone,这样只用同时载入 3 个模型。

注:这也是 RL 非常耗卡的一个重要原因。

PPO 以其「训练过程不稳定」和「效果不稳定」著称,这里我们通过列出一些具体的 case 来说明。

4.3.1 训练过程不稳定

由于 PPO 对超参非常敏感,不合理的超参搭配很有可能使得模型训练过程中 Reward 剧烈抖动:

图中蓝色折线为 actor 的 reward 曲线,粉红色折线为 ref_model(不参与训练)的 reward 曲线

从上图中可以看出,模型在训练过程中奖励曲线抖动的非常剧烈,

经过实验,我们发现存在几个因素与此有关:

  • KL Penalty:适当调大 KL可以帮助稳定训练(可使用动态调整 KL 系数策略)。
  • Reward Model:使用一个更稳定的 RM 能够有效缓解这种问题。
  • Reward Scaling:reward 的归一化对训练稳定有着很重要的作用。
  • Batch Size:适当增大 batch_size 有助于训练稳定。

4.3.2 训练结果不稳定

当你看到一条比较平稳且漂亮的 Reward 折线时 —— 也不要高兴的太早,

因为 reward 的提升并不代表模型真的表现的更好。

如下图所示:

一条看起来很完美的训练曲线,图中右下角为 reward 分布,橙色为当前模型的 reward 分布(集中在高分区域)

这次看起来非常完美的训练,当模型最终生成的结果却如下所示:

以「情绪识别模型」作为 RM,目标为生成更优情绪的答案

我们发现,模型的输出都是一些乱码,

之所以生成这种结果,是因为 Reward Model 对于这类「乱码」打分很高(最后一列为 RM 打出的分数)。

这种通过找到 shortcut 形成 reward 提升的现象又称为 reward hacking

对于这种情况,除了提升 RM 本身的能力以外,我们还可以通过 Combine 多个 RM 以防止这种情况出现。

如 Llama 2 中同时使用了 2 个 RM(Safety + Helpful)来进行打分,不过论文中给出的理由是 Safety 和 Helpful 这两个任务目标之间可能存在冲突,但使用多个 RM 来综合打分同时也能较好的防止模型训到天上去。

最终的训练结果如下:

使用多个混合RM策略进行训练的结果

通过使用多种策略进行混合,最终模型能够获得较为稳定的分数增长,输出模型也不再崩溃。

关于如何「稳定训练 PPO 的技巧」,在 [https://arxiv.org/pdf/2307.04964.pdf] 文章中有更为详尽的介绍:

何枝:【RLHF】怎样让 PPO 训练更稳定?早期人类征服 RLHF 的驯化经验

References

上一章中,我们讨论了大语言模型(例如,Transformer)的模型结构。
在本章中,我们将讨论如何训练大语言模型。
本章分成目标函数和优化算法两部分。

6.1 目标函数

我们研究三类语言模型的目标函数:

  1. 只包含解码器(Decoder-only)的模型(例如,GPT-3):计算单向上下文嵌入(contextual embeddings),一次生成一个token
  2. 只包含编码器(Encoder-only)的模型(例如,BERT):计算双向上下文嵌入
  3. 编码器解码器(Encoder-decoder)模型(例如,T5):编码输入,解码输出

我们可以使用任何模型将token序列映射到上下文嵌入中(例如,LSTM、Transformers):

$$
\phi : V^L \to \mathbb{R}^{d \times L}.
$$

$$
\left[\text{the}, \text{mouse}, \text{ate}, \text{the}, \text{cheese}\right] \stackrel{\phi}{\Rightarrow} \left[\binom{1}{0.1}, \binom{0}{1}, \binom{1}{1}, \binom{1}{-0.1}, \binom{0}{-1} \right].
$$

6.1.1 Decoder-only 模型

回想一下,自回归语言模型定义了一个条件分布:

$$
p(x_i \mid x_{1:i-1}).
$$

我们将其定义如下:

  • 将 $x_{1:i-1}$ 映射到上下文嵌入$\phi(x_{1:i-1})$。
  • 应用嵌入矩阵 $E \in \R^{V \times d}$ 来获得每个token的得分 $E \phi(x_{1:i-1})_{i-1}$ 。
  • 对其进行指数化和归一化,得到预测 $x_i$ 分布。

简洁地:

$$
p(x_{i+1} \mid x_{1:i}) = softmax(E \phi(x_{1:i})_i).
$$

6.1.1.1 最大似然

设 $\theta$ 是大语言模型的所有参数。设 $D$ 是由一组序列组成的训练数据。
然后,我们可以遵循最大似然原理,定义以下负对数似然目标函数:

$$
O(\theta) = \sum_{x \in D} - \log p_\theta(x) = \sum_{x \in D} \sum_{i=1}^L -\log p_\theta(x_i \mid x_{1:i-1}).
$$

并且,有很多的方法可以有效地优化这一目标函数。

6.1.2 Encoder-only 模型

6.1.2.1 单向到双向

使用上述最大似然可以训练得到Decoder-only模型,它会产生(单向)上下文嵌入。但如果我们不需要生成,我们可以提供更强的双向上下文嵌入。

6.1.2.2 BERT

我们首先介绍BERT的目标函数,它包含以下两个部分:

  • 掩码语言模型(Masked language modeling)
  • 下一句预测(Next sentence prediction)

以自然语言推理(预测隐含、矛盾或中性)任务中的序列为例:

$$
x_{1:L} = [\text{[CLS]}, \text{all}, \text{animals}, \text{breathe}, \text{[SEP]}, \text{cats}, \text{breathe}].
$$

其中有两个特殊的token:

  • $\text{[CLS]}$ :包含用于驱动分类任务的嵌入
  • $\text{[SEP]}$ :用于告诉模型第一个序列(例如,前提)与第二个序列(例如,假设)的位置。

根据上一章的公式,BERT模型定义为:

$$
\text{BERT}(x_{1:L}) = \text{TransformerBlock}^{24}(\text{EmbedTokenWithPosition}(x_{1:L}) + \text{SentenceEmbedding}(x_{1:L})) \in \mathbb{R}^{d \times L},
$$

其中, $\text{SentenceEmbedding}(x_{1:L})$ 根据序列返回以下两个矢量之一

  • 对于 $\text{[SEP]}$ 左边的,返回 $e_A \in \mathbb{R}^d$
  • 对于 $\text{[SEP]}$ 右边的,返回 $e_B \in \mathbb{R}^d$

bert

BERT-large有 $n_\text{heads} = 16$ 个注意头,并且 $d_\text{model} = 1024$ ,总共355M个参数。

6.1.2.2.1 掩码语言模型

掩码语言模型的基本思想是通过加噪然后预测来进行训练:

$$
[\text{the}, \text{[MASK]}, \text{ate}, \text{[MASK]}, \text{cheese}] \Rightarrow [\text{the}, \text{mouse}, \text{ate}, \text{the}, \text{cheese}].
$$

更普遍地说,我们可以将其视为类似于去噪自动编码器,其中我们映射有噪声/不完整版本 $\tilde x_{1:L}$ ,并尝试重建原始 $x_{1:L}$ 。

$$
\tilde x_{1:L} \Rightarrow x_{1:L}.
$$

建模:我们首先定义模型分布。给定输入 $\tilde x_{1:L}$ 及其上下文嵌入,模型独立地预测每个token:

$$
p(x_i \mid \tilde x_{1:L}) = \text{softmax}(E \phi(\tilde x_{1:L})_i).
$$

掩码: 我们定义了一个(随机)噪声函数 $A(\tilde x_{1:L} \mid x_{1:L})$ :

以下是 $A$ 的定义:

  • 假设 $I \subset {1, \dots, L}$ 代表所有位置中随机的15%。
  • 对于每个 $i \in I$ :
    • 以0.8的概率, $\tilde x_i \leftarrow \text{[MASK]}$
    • 以0.1的概率, $\tilde x_i \leftarrow x_i$
    • 以0.1的概率, $\tilde x_i \leftarrow \text{random word from } \mathcal{V}$

减少分布偏移: 如果我们总是使用 $\text{[MASK]}$ 来替换 $I$ 中选定的token,则:

  • 在训练期间,输入到BERT的都是带 $\text{[MASK]}$ 的序列。
  • 而在测试时,我们会输入没有 $\text{[MASK]}$ 的句子,这将导致分布发生变化。一种启发式的解决方法是在20%的时间内(此处指训练的时间)用真实单词替换。
6.1.2.2.2 下一句预测

回想一下,BERT是在拼接好的成对句子上训练的。下一句预测的目标是预测第二句是否跟随第一句。

$$
[\text{[CLS]}, \text{the}, \text{mouse}, \text{ate}, \text{the}, \text{cheese}, \text{[SEP]}, \text{it}, \text{was}, \text{full}] \Rightarrow 1.
$$

$$
[\text{[CLS]}, \text{the}, \text{mouse}, \text{ate}, \text{the}, \text{cheese}, \text{[SEP]}, \text{hello}, \text{world}] \Rightarrow 0.
$$

然后使用 $\text{[CLS]}$ 的嵌入来做二分类。

6.1.2.2.3 数据集

$\mathcal{D}$ 是按如下方式构造的一组样本 $(x_{1:L}, c)$ :

  • 令 $A$ 是语料库中的一个句子。
  • 以0.5的概率, $B$ 是下一句话。
  • 以0.5的概率, $B$ 是语料库中的一个随机句子。
  • 令 $x_{1:L} = [\text{[CLS]}, A, \text{[SEP]}, B]$
  • 令 $c$ 表示 $B$ 是否是下一句。
6.1.2.2.4 训练目标

BERT的训练目标是:

稍后我们将讨论训练,这里简要总结一下BERT:

  • BERT(以及ELMo和ULMFiT)表明,一个统一的体系结构(Transformer)可以用于多个分类任务。
  • BERT真正将NLP社区转变为预培训+微调的范式。
  • BERT显示了深度双向上下文嵌入的重要性,尽管通过模型大小和微调策略可能会弥补这一点(p-tuning)。

6.1.2.3 RoBERTa

RoBERTa对BERT进行了以下改进:

  • 删除了下一句预测这一目标函数(发现它没有帮助)。
  • 使用更多数据训练(16GB文本 $\Rightarrow$ 160GB文本 )。
  • 训练时间更长。
  • RoBERTa在各种基准上显著提高了BERT的准确性(例如,在SQuAD上由81.8到89.4)。

6.1.3 Encoder-decoder 模型

任务示例(表格生成文本):

$$
[\text{name}, \text{:}, \text{Clowns}, \text{|}, \text{eatType}, \text{:}, \text{coffee}, \text{shop}] \mathbb{R}ightarrow [\text{Clowns}, \text{is}, \text{a}, \text{coffee}, \text{shop}].
$$

回想一下编码器-解码器模型(例如,BART、T5):

  • 首先像BERT一样对输入进行双向编码。
  • 然后像GPT-2一样对输出进行自回归解码。

6.1.3.1 BART (Bidirectional Auto-Regressive Transformers)

BART (Lewis et al. 2019)是基于Transformer的编码器-解码器模型。

  • 使用与RoBERTa相同的编码器架构(12层,隐藏维度1024)。
  • 使用与RoBERTa相同的数据进行训练(160GB文本)。

BART使用了以下变换 $A(\tilde x_{1:L} \mid x_{1:L})$ :

bart-transformations

基于BERT的实验,最终模型进行以下了变换:

  • 掩码文档中30%的token
  • 将所有子句打乱

最后,通过微调,BART在分类和生成任务上都展示了强大的效果。

6.1.3.2 T5 (Text-to-Text Transfer Transformer)

T5 (Raffel et al., 2020)是另一种基于Transformer的编码器-解码器模型。

预训练任务:
给定一段文本,在随机位置将其分割为输入和输出:

$$
[\text{the}, \text{mouse}] \Rightarrow [\text{ate}, \text{the}, \text{cheese}].
$$

论文尝试了许多不同的无监督目标:

t5-unsupervised-table

并发现“i.i.d. noise, replace spans”效果最好(尽管许多目标相似)。

论文还将所有经典的NLP任务放在一个统一的框架中,称为“Text-to-Text”任务:
t5-supervised

以分类任务任务为例,不同模型的差异如下:

  • BERT使用 $\text{[CLS]}$ 的嵌入来预测。
  • T5、GPT-2、GPT-3等(生成模型)将分类任务转换成自然语言生成。

注意:

  • 论文对整个pipline的许多方面(数据集、模型大小、训练目标等)进行了深入研究。
  • 基于这些见解,他们训练了一个11B的模型。

6.2 优化算法

现在,我们将注意力转向如何优化目标函数。

为了简单起见,让我们以自回归语言模型为例:

$$
O(\theta) = \sum_{x \in D} -\log p_\theta(x).
$$

6.2.1 随机梯度下降(SGD)

最简单的优化算法是用小批量进行随机梯度下降,该算法的步骤如下:

  • 初始化参数 $\theta_0$
  • 重复以下步骤:
    • 采样小批量 $B_t \subset D$
    • 根据梯度更新参数:

$$
\theta_t \leftarrow \theta_{t-1} - \eta \frac{1}{|B_t|} \sum_{x \in B_t} \nabla_\theta (-\log p_\theta(x)).
$$

优化的关键点包括:

  1. 我们希望参数 $\theta$ 可以快速收敛
  2. 我们希望优化在数值上是稳定的
  3. 我们希望内存高效(尤其是对于大模型)

这些点往往相互矛盾(例如,通过低精度训练,可以实现快速收敛、减少内存占用,但是会导致训练不稳定)

因此,我们可以从几个层次来进行优化:

  1. 针对经典优化:二阶方法、约束优化等。
  2. 针对机器学习:随机方法、隐式正则化+早停法
  3. 针对深度学习:初始化、归一化(更改模型架构)
  4. 针对大语言模型:由于稳定性问题,学习率和一些直觉(例如,二阶方法)仍然有用,但要使大语言模型有效训练,还需要克服许多其他独特的挑战。不幸的是,其中大部分内容都是特别的,人们对此了解甚少。

6.2.2 Adam (adaptive moment estimation)

Adam算法拥有以下两个创新:

  1. 引入动量(继续朝同一方向移动)。
  2. 参数 $\theta_0$ 的每个维度都有一个自适应(不同)的步长(受二阶方法启发)。

它的步骤如下:

  • 初始化参数 $\theta_0$
  • 初始化动量 $m_0, v_0 \leftarrow 0$
  • 重复以下步骤:
    • 采样小批量 $B_t \subset D$
    • 按照如下步骤更新参数:
      • 计算梯度

$g_t \leftarrow \frac{1}{|B_t|} \sum_{x \in B_t} \nabla_\theta (-\log p_\theta(x)).$

    - 更新一阶、二阶动量

$m_t \leftarrow \beta_1 m_{t-1} + (1 - \beta_1) g_t$

$v_t \leftarrow \beta_2 v_{t-1} + (1 - \beta_2) g_t^2$

    - 对偏差进行修正

$\hat m_t \leftarrow m_t / (1 - \beta_1^t)$

$\hat v_t \leftarrow v_t / (1 - \beta_2^t)$

    - 更新参数

$\theta_t \leftarrow \theta_{t-1} - \eta , \hat m_t / (\sqrt{\hat v_t} + \epsilon).$

存储占用分析:

Adam将存储从2倍的模型参数( $\theta_t,g_t$ )增加到了4倍( $\theta_t,g_t,m_t,v_t$ )。

6.2.3 AdaFactor

AdaFactor是一种为减少存储占用的优化算法。它有如下特点:

  • 它不储存 $m_t,v_t$ 这样的 $O(m \times n)$ 矩阵,而是存储行和列的和 $O(m + n)$ 并重构矩阵
  • 去除动量
  • 它被用来训练T5
  • AdaFactor可能使训练变得困难(见Twitter threadblog post

6.2.4 混合精度训练

混合精度训练是另一种减少存储的方法

  • 通常来说,默认的精度是:FP32(32位浮点)
  • 其他可选精度:FP16(16位浮点),但问题是任何小于 $2^{-24}$ 的值都会变为0。
  • 解决方案:将主权重存储在FP32中,并在FP16中执行其他所有操作。
  • 损失缩放:按比例放大损失,以避免梯度数值太小。
  • 结果:存储减少了一半。

混合精度训练

6.2.5 学习率

  • 通常情况下,学习率会随着时间的推移而衰减。
  • 对于Transformer模型,我们实际上需要通过预热(warmup)提高学习率。
  • Huang et al., 2020表明,一个潜在的原因是防止层归一化的梯度消失,导致使用Adam优化器训练时不稳定。

6.2.6 初始化

  • 给定矩阵 $W \in \mathbb{R}^{m \times n}$ ,标准初始化(即,xavier初始化)为 $W_{ij} \sim N(0, 1/n)$ 。
  • GPT-2和GPT-3通过额外的 $1/\sqrt{N}$ 缩放权重,其中 $N$ 是残差层的数量。
  • T5将注意力矩阵增加一个 $1/\sqrt{d}$ (代码)。

以GPT-3为例,使用的参数如下:

  • Adam参数: $\beta_1 = 0.9, \beta_2 = 0.95, \epsilon = 10^{-8}$
  • 批量小:320万个token(约1500个序列)
  • 使用梯度剪裁( $g_t \leftarrow g_t / \min(1, |g|_2)$ )
  • 线性学习率预热(前3.75亿个token)
  • 余弦学习率衰减到10%
  • 逐渐增加批大小
  • 权重衰减设为0.1

延伸阅读