从零开始的LLM 2.LLM模型及训练

前言

从零开始学习ai文章系列已完成《动手学深度学习》和《磨菇书》两本书的学习,新开的LLM系列课本来自《Happy-LLM》,但是内容和排版等个人重新进行整理,因此不会按照原来课本中的章节来写。如果哪里有错误的欢迎指正。或者不清晰的可以直接查看原文部分。

《Happy-LLM》原文(课本):https://datawhalechina.github.io/happy-llm/#/


1. Transformer

1.1 Transformer 是什么

Transformer 是一种神经网络模型架构。我们在前文推导的 Multi-Head Attention,正是 Transformer 最核心的组成部分。一个完整的 Transformer 由 Encoder(编码器)或 Decoder(解码器),以及若干归一化层与线性层共同构成。

目前主流的 Transformer 模型分为三类:

  • Encoder-Only:只理解,不生成(代表:BERT)
  • Decoder-Only:以生成为核心(代表:GPT、Claude)
  • Encoder-Decoder:理解后再生成(代表:T5、翻译模型)

每种架构对应一套不同的预训练范式,我们将在后续小节逐一展开。

2. Encoder-Only

2.1 BERT 模型架构

BERT 的主体结构是将多个 Transformer Encoder 层堆叠而成。下面是它的整体数据流:

其主要结构如下图所示:

图看起来很乱,但是我们首先抓住图中的主体结构BERT model

Prediction Head

Tokenizer 与 Embedding 已在上篇文章详述,此处略过。Prediction Head 的结构取决于下游任务。以分类任务为例,prediction_heads 层就是多个线性层加上激活函数,最后一个线性层的输出维度和任务的类别数相等。但 Encoder-Only 模型同样广泛用于序列标注、问答、语义检索等场景,Prediction Head 的形态也随之不同。

Encoder Layer 内部结构

Encoder是由许多的Encoder layer层构建而成的。Encoder layer的构建如下

每个 Encoder Layer 由两个子结构串联而成:

  1. Attention部分:输入经过 Attention 计算后,将结果通过残差的方式加上了原始输入。
  2. FFN部分:上步的结果再次经过一个Intermediate层(FFN,前馈网络),同样采用残差结构进行训练。

在《动手学深度学习》中我们知道,残差相当于在原来输入的基础上进行修改,让其结果符合我们的要求。相比于从全 0 的基础上修改,这种方式效果一般更好——是一种在深层网络中防止信息退化、让训练更稳定的经典结构。

Intermediate 层是 BERT 对 FFN(前馈网络)的专属命名,结构为:一个线性变换 + GELU 激活函数。

BERT 所使用的激活函数是 GELU 函数,全名为高斯误差线性单元激活函数,这也是自 BERT 才开始被普遍关注的激活函数。GELU 的计算方式为:

GELU 的核心思路为将随机正则的思想引入激活函数,通过输入自身的概率分布,来决定抛弃还是保留自身的神经元。关于 GELU 的原理与核心思路,此处不再赘述,有兴趣的读者可以自行学习。

为什么需要Attention 与 FFN 的分工?

Attention 的本质是加权求和——它决定"从哪些 token 取多少信息",本质是对向量的加权求和——这是一个线性操作,无法捕捉复杂的非线性特征交互。

FFN 引入了非线性激活函数(ReLU、GELU 等),对 Attention 混合后的信息做深度加工,赋予模型学习语法、结构、非线性关系的能力。

可以这样理解:Attention 是"大家围坐交换信息",

FFN 是"每个人回到房间,把听到的信息重新加工内化"。

如果去掉非线性激活函数会怎样?(仅保留attention部分,去掉FNN部分)

  • 整个网络的表达能力等价于一个单矩阵变换
  • 无法学习复杂模式,如语法结构、语义关系等
  • 堆叠再多层,效果也不会超过单层线性模型

2.2 预训练任务——MLM + NSP

MLM :Masked Language Model(掩码语言模型)

BERT 引入了掩码语言模型(MLM)作为核心预训练任务。相较于传统的单向语言模型(LM)模拟人类逐词写作的方式,MLM 模拟的是"完形填空":在一个文本序列中随机遮蔽部分 token,然后将所有未被遮蔽的 token 输入模型,要求模型根据上下文预测被遮蔽的 token。例如:

由于模型在预测被遮蔽的 token 时,可以同时利用其上文和下文,因此 MLM 天然地驱动模型拟合双向语义,从而更好地实现深层文本理解。这也是 BERT 相较于 GPT(单向)的核心优势所在。

此外,MLM 任务无需对文本进行任何人工标注,仅需对文本随机遮蔽即可,因此可以充分利用互联网上的海量无监督语料进行预训练。BERT 的预训练共使用了约 3,300M 词的语料。

在具体训练时,BERT 随机选择每条训练语料中 15% 的 token 参与预测,但这 15% 并非全部替换为 [MASK],而是按如下比例处理:

处理方式 概率 目的
替换为 [MASK] 80% 核心遮蔽,驱动模型预测
替换为随机 token 10% 迫使模型保持对全部 token 的上下文表征
保持原 token 不变 10% 消除预训练与微调阶段的分布偏移

其中,10% 保持不变的设计是为了缓解预训练与微调之间的不一致性——微调阶段输入中不存在 [MASK] token,若预训练时全部遮蔽,模型在微调时会面临从未见过的输入分布,导致性能下降。

10% 随机替换的核心意义在于:若所有被选中的 token 均以 [MASK] 形式出现,模型只需关注 [MASK] 位置即可,会忽视对其他 token 的上下文表征学习。引入随机 token 后,模型无法提前判断哪些位置需要被预测,从而被迫对每一个 token 都维持充分的上下文表征,进而具备对整个句子的特征表示能力。由于随机替换的概率很低(仅占全部 token 的 1.5%),其对模型语言理解能力的干扰可以忽略不计。

NSP: Next Sentence Prediction(下一句预测)

MLM 在 token 级别拟合语义关系,但许多实际的 NLU 任务需要在句子级别理解两个句子之间的关系,例如:

  • 问答匹配:输入一个问题和若干候选回答,要求模型找出正确答案;
  • 自然语言推理(NLI):输入一个前提和一个假设,判断假设是否能由前提推出。

为此,BERT 提出了 NSP(下一句预测)任务,要求模型判断给定句对中的两个句子是否为原文中连续的上下文。例如:

通过要求模型判断句对关系,NSP 任务迫使模型在句级拟合语义关联,从而更好地适配下游句级 NLU 任务。

正样本可从无监督语料中直接抽取连续句子对,负样本则通过随机采样不连续句子构造(确保不意外采到本就连续的句子对),因此训练数据几乎可以无限扩展,无需人工标注。

后续进展:RoBERTa(2019)等后续工作通过消融实验发现,NSP 任务对模型性能的提升有限,甚至在部分任务上会带来负面影响,因此将其移除。这一发现表明 NSP 任务的设计仍有值得改进之处。

在具体预训练时,BERT 使用了 800M 的 BooksCorpus 语料和 2500M 的英文维基百科语料,90% 的数据使用 128 的上下文长度训练,剩余 10% 的数据使用 512 作为上下文长度进行预训练,总共约训练了 3.3B token。其训练的超参数也是值得关注的,BERT 的训练语料共有 13GB 大小,其在 256 的 batch size 上训练了 1M 步(40 个 Epoch)。而相较而言,LLM 一般都只会训练一个 Epoch,且使用远大于 256 的 batch size。

可以看到,相比于传统的非预训练模型,其训练的数据量有指数级增长。当然,更海量的训练数据需要更大成本的算力,BERT 的 Base 版本和 Large 版本分别使用了 16块 TPU 和 64块 TPU 训练了 4天才完成。

2.3 下游任务微调

2.3.1 预训练-微调的两阶段范式

作为 NLP 领域里程碑式的成果,BERT 的一个重大贡献在于正式确立了预训练-微调(Pre-train & Fine-tune)的两阶段思想:首先在海量无监督语料上进行预训练,使模型获得通用的文本理解能力;再针对具体的下游任务,使用少量有监督标注数据进行微调。

这一范式的核心价值在于:预训练阶段积累的通用语言能力,能够以较低的成本快速迁移到各类下游任务,而无需从头训练一个针对特定任务的模型。

2.3.2 输入格式设计:[CLS] 与 [SEP]

为了尽可能适配多种下游任务,BERT 对输入格式进行了统一设计,引入了两个特殊 token:

[CLS](Classification Token) BERT 会在每个输入序列的首部插入 [CLS] token。经过 Transformer 的多层编码后,该位置对应的隐层向量会聚合整个序列的语义信息,作为句级语义表征使用。在 NSP 预训练以及下游的文本分类、句对关系判断等任务中,均直接取该向量接入分类器输出结果。

[SEP](Separator Token) 对于涉及句对输入的任务(如问答、自然语言推理),BERT 将两个句子拼接后一并输入,并在两句之间以及序列末尾插入 [SEP] token 作为分隔符,同时配合句子编号嵌入(Segment Embedding)区分两个句子,使模型能够感知句子边界。输入格式示例如下:

1
[CLS] 句子 A [SEP] 句子 B [SEP]

2.3.3 微调的方式

所谓微调,本质上与预训练的参数更新机制相同,区别在于:

对比维度 预训练 微调
数据量 海量无监督语料(十亿级 token) 少量有监督标注数据(千至万级样本)
训练轮次 较多(BERT 约 40 Epoch) 极少(通常 2~4 个 Epoch)
Batch Size 较大(256) 较小(16~32)
学习率 较大 极小(通常 2e-5 ~ 5e-5)
参数更新幅度

微调时,通常对整个预训练模型的参数进行更新(即全参数微调),而非仅训练下游任务的新增层。这是因为预训练权重已高度拟合语言规律,仅需小幅调整即可适配新任务,过大的更新反而会破坏预训练习得的语言表征,导致灾难性遗忘(Catastrophic Forgetting)

2.3.4 适配下游任务

BERT 原论文将下游任务归纳为以下四类,并给出了对应的微调接入方式:

① 句对分类(Sentence-Pair Classification) 例如自然语言推理(NLI)、语义相似度判断。输入两个句子,取 [CLS] 对应的隐层向量,接入 Softmax 分类器输出类别。

② 单句分类(Single Sentence Classification) 例如情感分析、文本分类。输入单个句子,同样取 [CLS] 向量接入分类器。

③ 问答(Question Answering) 以抽取式问答(如 SQuAD)为例,输入问题与上下文段落,对上下文中每个 token 的隐层向量预测其是否为答案的起始或结束位置。

④ 序列标注(Sequence Labeling) 例如命名实体识别(NER)、词性标注(POS)。对输入序列中每个 token 对应的隐层向量分别接入分类器,逐 token 输出标注结果。

⚠️ 关于文本生成:BERT 基于 Transformer Encoder,擅长文本理解类任务,并不直接适用于文本生成任务(生成任务更适合 Decoder 架构,如 GPT)。若将 BERT 用于生成场景,通常需要结合额外的 Decoder 模块(如 UniLM、BERT2BERT 等变体),而非直接使用。

通过上述统一的输入格式设计与灵活的输出接入方式,BERT 只需对顶层结构做极小改动,即可高效适配多种 NLP 下游任务,充分体现了预训练-微调范式的通用性与迁移效率。

2.3.5 举例说明

以情感分类为例:微调 BERT 判断电影评论的情感

假设我们的任务是:给定一段电影评论,判断它是正面还是负面情感

第一步:准备输入

将评论文本按 BERT 格式处理:

1
[CLS] 这部电影节奏紧凑,演员表演非常自然 [SEP]

第二步:模型结构改动

预训练好的 BERT 本体完全不动,只在顶部新增一个极小的分类头

1
[CLS] 的隐层向量(768维)→ Linear(768, 2) → Softmax → [正面, 负面]

整个新增部分只有一个线性层,参数量相对于 BERT 本体的 1.1 亿参数几乎可以忽略不计。

BERT 模型的每个token向量维数为768维。

输入一条句子后,BERT 会为序列中的每一个 token 都输出一个 768 维向量,而不只是输出一个:

输入: [CLS] 导演 的 镜头 语言 太 生硬 , 故事 也 讲不清楚 [SEP]

输出: 768维 768维 768维 768维 768维 768维 768维 768维 768维 768维 768维 768维

对于情感分类任务,我们只取 [CLS] 位置对应的那个 768 维向量,扔掉其他 token 的输出,再接分类头。

[CLS] 的特殊之处在于:它在 NSP 预训练中被专门优化来做句级判断,模型已经学会了用这个位置汇总全句信息。这是人为设计 + 训练强化的结果。

[CLS] 是因为任务需要句级表示,而 [CLS] 恰好被训练成了句级语义的"汇总点"。用哪个位置的输出,完全由下游任务的性质决定

第三步:准备训练数据

不需要亿级语料,几千条人工标注的样本就足以微调:

评论文本 标签
这部电影节奏紧凑,演员表演非常自然 正面
剧情拖沓,完全浪费时间 负面
特效震撼,故事感人至深 正面

第四步:微调训练

用这批标注数据,以极小的学习率(如 2e-5)对整个模型(BERT 本体 + 分类头)进行 2~4 个 Epoch 的训练。

此时损失函数是标准的交叉熵损失,梯度从分类头一路反传回 BERT 的每一层,对预训练权重做小幅调整。

1
损失 = CrossEntropy(预测概率, 真实标签)

第五步:推理

微调完成后,输入新的评论:

1
[CLS] 导演的镜头语言太生硬,故事也讲不清楚 [SEP]

模型输出:

1
负面: 92%   正面: 8%  →  预测结果:负面 ✓

3. Encoder-Decoder

3.1 T5模型介绍

T5(Text-To-Text Transfer Transformer)是由 Google 于 2019 年提出的预训练语言模型。其核心思想是将所有 NLP 任务统一表示为文本到文本(Text-to-Text)的转换问题:无论是翻译、摘要、分类还是问答,模型的输入和输出均为纯文本字符串,使用同一套模型结构、同一个损失函数处理所有任务。

为了区分不同任务,T5 在每条输入前加入一段自然语言描述的任务前缀,明确指定当前任务类型:

任务前缀本身就是自然语言,无需引入额外的任务编码或特殊 token。这种设计使得模型在预训练阶段可以同时学习多种任务的通用语言表示,微调时只需提供对应前缀即可快速适配,无需为每类任务单独设计输出层或修改模型结构,极大简化了多任务学习与迁移学习的流程。

3.2 T5 模型架构

T5 采用标准的 Encoder-Decoder 结构,编码器和解码器均基于 Transformer 架构设计,整体结构如图 3.7 所示:

  • Encoder:处理输入文本,通过多层 Self-Attention 和前馈网络提取语义表示
  • Decoder:基于 Encoder 的输出,自回归地逐 token 生成目标文本

输入向量先经过 Encoder 层编码,再传入 Decoder 层生成输出。

Encoder 和 Decoder 内部的注意力机制有所不同,如下图所示:

  • Encoder:每层仅包含一个 Self-Attention,序列中每个 token 可以自由关注输入序列的所有位置

  • Decoder

    :每层包含两个注意力模块:

    • Masked Self-Attention:对已生成序列做自注意力,通过因果掩码(Causal Mask)屏蔽未来位置,确保生成时不会"看到"尚未生成的 token
    • Cross-Attention:Q 来自 Decoder 当前状态,K 和 V 来自 Encoder 的输出,使 Decoder 在生成每个 token 时能够动态关注输入序列的相关部分

与原始 Transformer 和 BERT 使用的 Layer Normalization 不同,T5 采用了 RMSNorm(Root Mean Square Normalization)

LayerNorm 需要计算均值和方差两个统计量,再进行平移和缩放;RMSNorm 去掉了均值中心化步骤,只用每个隐层激活值的均方根进行归一化:

其中 γ 是与隐层维度等长的可学习缩放向量(每个维度独立缩放),ϵ 是防止除零的极小值。RMSNorm 计算更简单、速度更快,在深层模型中同样能有效稳定各层激活值的量级,且实践中效果与 LayerNorm 相当甚至更优。

3.3 预训练任务

T5 使用了专门构建的大规模数据集 C4(Colossal Clean Crawled Corpus)。原始数据来自 Common Crawl(对整个互联网的周期性抓取,原始体量达数百 TB),经过严格清洗后保留了约 750 GB 的高质量英语文本,清洗步骤包括去除重复文本、过滤非自然语言内容(如代码、乱码)、移除冒犯性内容等。C4 已在 TensorFlow Datasets 中开源。

T5 的预训练任务称为 Span Corruption,与 BERT 的 MLM 类似但有关键区别:BERT 遮蔽的是单个随机 token,而 T5 遮蔽的是连续的 token 片段(Span)

具体流程如下:

  1. 从输入文本中随机选取若干连续片段(平均长度约 3 个 token),使被遮蔽 token 总量约占 15%
  2. 每个被遮蔽片段用一个唯一的哨兵 token(<extra_id_0><extra_id_1>……)替换
  3. 模型在输出端依次生成所有被遮蔽片段的原始内容

例如:

输入The <extra_id_0> sat on <extra_id_1> mat.

输出<extra_id_0> cat <extra_id_1> the

3.4 下游任务微调

预训练完成后,T5 延续预训练-微调的两阶段范式,针对具体下游任务使用少量有监督数据进行微调。

得益于 Text-to-Text 的统一框架,T5 的微调方式极为统一:无论任务类型如何,只需构造对应格式的输入输出文本对,在预训练模型上继续训练即可,无需修改任何模型结构。

与 BERT 微调需要为不同任务设计不同输出头不同,T5 的所有任务均通过同一个 Decoder 以文本生成的形式输出结果,如下表所示:


在展开 T5 的微调之前,有必要先对比 BERT 的微调方式,以理解 T5 统一框架的价值所在。

情感分类任务为例:

BERT 的做法是取 [CLS] 的隐层向量,接入一个专门的线性分类头输出类别标签——模型结构需要针对任务改动,且输出的是一个离散的类别编号,而非自然语言:

T5 的做法则完全不同——不修改任何模型结构,直接将任务构造为文本输入输出对,由 Decoder 生成文本形式的答案:

两者的本质区别在于:BERT 输出的是类别编号,T5 输出的是自然语言文本。这意味着 T5 微调时无需新增任何模块,所有任务共用同一套模型和损失函数。


机器翻译为例

第一步:构造训练数据

将原始标注数据转换为 Text-to-Text 格式的文本对:

输入

只需这样的文本对数据,不需要任何额外的标注格式。

第二步:训练

将上述数据输入预训练好的 T5,以极小的学习率(如 1e-3,配合 Adafactor 优化器)对整个模型进行少量步数的训练,损失函数与预训练完全一致——仍然是标准的交叉熵损失,计算 Decoder 生成每个目标 token 的预测误差:

1
损失 = CrossEntropy(Decoder 生成的每个 token, 目标文本的每个 token)

第三步:推理

微调完成后,输入新的句子:


为什么加了 Decoder 就能生成任意文本?

根本原因在于:Encoder 和 Decoder 承担的是完全不同的职责。


Encoder 只会"理解",不会"生成"

BERT 这类 Encoder-Only 模型的工作方式是:

输入序列 → Encoder → 每个 token 对应一个向量

它的输出是固定长度、固定位置的向量序列——输入几个 token,就输出几个向量。模型本身没有任何"生成下一个词"的机制。所以 BERT 做分类时,只能从已有的输出向量里"挑一个"(比如取 [CLS])接分类头——它无法凭空生成新的 token,更无法输出一段与输入长度不同的文本。

这就是为什么翻译、摘要这类输出长度不固定、输出内容需要逐词生成的任务,BERT 天然无法直接完成。


Decoder 的核心能力:自回归生成

Decoder 引入了一种全新的机制——自回归生成(Autoregressive Generation)

每一步,Decoder 都基于"已经生成的所有内容"预测下一个 token,直到生成结束符为止。输出序列的长度完全由模型自己决定,不受输入长度约束。

以翻译 "Good morning." → "Bonjour." 为例:

第 1 步 :

Decoder 当前已有内容:<START>

1
2
3
4
5
6
7
8
9
Q = <START> 的向量        ← 来自 Decoder 当前状态
K = Encoder 输出的所有向量 ← 对 "translate English to French: Good morning." 的编码结果
V = Encoder 输出的所有向量 ← 同上

Cross-Attention 计算:
Q 去查询 K,问"我现在该生成什么?"
K/V 回答"输入是一个翻译请求,内容是早上好"

→ 生成 "Bonjour"

第 2 步

Decoder 当前已有内容:<START> Bonjour

1
2
3
4
5
6
7
8
9
Q = Bonjour 的向量        ← 注意:Q 是当前最新 token 的向量
K = Encoder 输出的所有向量 ← 不变,始终是对原始输入的编码
V = Encoder 输出的所有向量 ← 不变

Cross-Attention 计算:
Q 去查询 K,问"已经生成了 Bonjour,接下来该生成什么?"
K/V 回答"输入的句子到此结束了"

→ 生成 "."

第 3 步

1
2
3
4
Q = "." 的向量
K/V 同上

→ 生成 <END>,停止

关键点

K 和 V 始终固定,是 Encoder 对整个输入句子编码后的结果,在生成过程中不会改变。

Q 每步更新,是 Decoder 当前最新生成的 token 的向量,代表"我已经生成了这些,下一个应该是什么"的查询信号。

这里有一个容易误解的地方:Q 并不是最新 token 的原始词向量,而是经过 Masked Self-Attention 加工后的向量。

Masked Self-Attention 的作用是让 Decoder 中每个位置的 token 都能关注它之前所有已生成的 token。因此,第 2 步中 Bonjour 对应的向量,并不是孤立的"Bonjour 这个词的语义",而是融合了 <START>Bonjour 整个已生成序列的上下文信息后的表示。

具体decoder的计算过程我们在下面章节的decoder-only中进行讲解。

Cross-Attention 本质上就是:Decoder 每生成一个词,都拿当前状态去查询 Encoder 对输入的理解,确保每一步生成都与原始输入语义对齐。

3.5 大一统思想

前面几节分别介绍了 T5 的架构与预训练任务,而支撑这一切的核心设计理念,正是 T5 的大一统思想(Unified Framework)将所有 NLP 任务统一为文本到文本的形式

在 T5 之前,面对不同类型的 NLP 任务,通常的做法是:

模型 做法 问题
BERT 针对每类任务设计不同的输出头(分类头、标注头……) 每个任务需要改模型结构
GPT 纯 Decoder 生成,擅长生成但理解能力有限 理解类任务表现弱于 BERT
早期 Seq2Seq 每个任务单独训练一个模型 无法共享知识,成本高

本质问题在于:不同任务的输入输出格式不统一,导致模型结构、损失函数、训练数据都需要各自设计,迁移成本极高。

T5 的解法:一切皆文本

T5 的回答是:既然所有任务的输入和输出都可以用自然语言描述,为什么不把它们全部统一成文本到文本的格式

如图 3.11 所示,无论任务类型如何,T5 的处理方式始终是:

image-20260530120438078

这里的关键不只是"格式统一",而是:同一套模型参数、同一个损失函数、同一种训练方式,可以处理所有任务。模型结构零改动,任务切换只需换训练数据。

大一统思想的深层价值

这种统一不仅仅是工程上的便利,更带来了两个深层优势:

① 任务之间可以互相迁移 当模型在翻译任务上学到的"语言对齐能力",同样可以帮助它做摘要;在分类任务上学到的"语义判断能力",同样可以辅助问答。统一的格式让不同任务的知识在同一个参数空间内共享。

② 预训练与微调之间没有断层 BERT 预训练时做的是 MLM(填空),微调时做的是分类——两个阶段的任务形式本身就不一致。而 T5 预训练和微调都是"文本输入 → 文本输出"的生成任务,形式完全对齐,微调时模型不需要适应全新的任务范式,迁移更自然。

4. Decoder-Only

4.1 GPT 模型介绍

Decoder-Only 是目前大语言模型(LLM)的主流基础架构,当前几乎所有主流 LLM 均采用这一结构(RWKV、Mamba 等非 Transformer 架构除外)。引发 LLM 热潮的 ChatGPT,正是 Decoder-Only 系列代表模型 GPT 的集大成之作;而目前最具影响力的开源 LLM 基础架构 LLaMA,也是在 GPT 架构基础上优化发展而来。

GPT 的历史背景

GPT(Generative Pre-Training Language Model)由 OpenAI 于 2018 年 6 月发布,是首个明确提出预训练-微调两阶段思想的预训练语言模型——在海量无监督语料上预训练获得通用语言能力,再在特定任务上微调。这一点早于同年 10 月发布的 BERT。

然而 GPT-1 发布后,由于综合性能略逊于 BERT,未能引起轰动,Decoder-Only 架构也一度未成为学界主流。但 OpenAI 坚定地沿着这条路走下去——持续扩大预训练数据、增加模型参数——最终于 2020 年发布 GPT-3,奠定了 LLM 时代的基础,并以此为基座的 ChatGPT 成功开启了新时代。

GPT 系列的演进脉络如下:

版本 发布时间 参数量 核心进展
GPT-1 2018.06 117M 首次提出预训练-微调范式
GPT-2 2019.02 1.5B 证明规模扩大带来涌现能力,引入 Zero-shot
GPT-3 2020.05 175B 奠定 LLM 基础,In-context Learning
ChatGPT 2022.11 未公开 引入 RLHF,对话能力大幅提升
GPT-4 2023.03 未公开 多模态,推理能力显著增强

模型架构

GPT 的整体结构与 BERT 有一定相似之处,同样是通过堆叠 Transformer 层构建,核心区别在于:BERT 堆叠的是 Encoder,GPT 堆叠的是 Decoder。如图 3.12 所示。

GPT-1 的 Decoder 层与 T5 的 Decoder 层有一个关键差异:GPT 没有 Cross-Attention

原因很直接——GPT 是纯 Decoder-Only 架构,没有 Encoder,自然也不存在需要 Cross-Attention 来查询 Encoder 输出的场景。因此 GPT 的每个 Decoder 层只保留了一个注意力模块,结构反而更接近 BERT 的 Encoder 层:

与 BERT 的 Self-Attention 不同,GPT 的 Self-Attention 加入了因果掩码(Causal Mask),使用的是masked-self attention:每个 token 只能关注它自身及之前的 token,无法看到后续位置。这保证了自回归生成时的合法性——模型在预测第 i 个 token 时,不能"偷看"第 i+1 个及之后的内容。

如图所示,N 个 Decoder 层堆叠后,最终的 hidden_states 经线性矩阵映射到词表维度,通过 Softmax 得到每个词的生成概率,从而输出目标序列。

4.2 预训练任务——CLM

什么是 CLM

GPT 采用的预训练任务是因果语言模型(Causal Language Model,CLM),也是最经典、最直接的语言模型预训练目标。

CLM 的核心思路是:给定序列中前面所有的 token,预测下一个 token,不断重复该过程生成目标序列。这本质上是 N-gram 语言模型的深度学习扩展版——N-gram 只看前 N 个词,CLM 看前面所有词

例如:

1
2
3
input: 今天天气        → output: 很
input: 今天天气很 → output: 好
input: 今天天气很好 → output: 。

CLM 的训练过程

CLM 的训练只需要大量无标注文本,不需要任何人工标注。以输入 今天天气很 为例:

模型经过多层 Decoder 编码后,输出序列中每个位置都对应一个隐层向量。我们取 "很" 所在位置的输出向量(该向量已通过 Masked Self-Attention 融合了 今天天气很 所有前序 token 的上下文信息,详见上一章的mask self-attention),将其经过线性层(Linear)投影到词表维度,再经 Softmax 得到词表上每个词的概率分布,最后计算其与真实标签 的交叉熵损失

1
损失 = CrossEntropy(模型预测的概率分布, 标签"好")

得益于 Masked Self-Attention 的并行机制,这一过程并非逐步串行计算,而是一次前向传播同时计算序列中所有位置的损失,再对所有位置的损失取平均,大幅提升了训练效率。


预训练的规模

LLM 往往需要使用极大规模的预训练语料。根据 OpenAI 提出的 Scaling Law(C≈6NDC,其中 C 为计算量,N 为模型参数量,D 为训练 token 数),Hoffmann 等人(Chinchilla, 2022)进一步实验得出:模型参数量与训练 token 数应大致相等才能达到最优训练效率,即 175B 的模型应使用约 175B token 训练。而 LLaMA 则更进一步提出,使用约 20 倍于参数量的 token 进行训练可以达到推理性能最优——175B 的模型可使用约 3.5T token 预训练以获得最优推理效果。

如此庞大的模型参数和预训练数据,使得预训练一个 LLM 所需的算力资源极其庞大。哪怕是预训练一个 1B 的模型,也至少需要多卡分布式 GPU 集群。一般来说,百亿级 LLM 需要 1024 张 A100 训练一个多月,十亿级 LLM 也需要 256 张 A100 训练两三天。


分布式训练框架

也正因如此,分布式训练框架成为 LLM 训练必不可少的组成部分,核心思路是数据并行模型并行两种策略。

数据并行适用于模型本身可以装入单张 GPU 显存的情况。此时将训练数据切分成多份,分发到不同 GPU 上,每张 GPU 各持一份完整的模型副本,各自用自己的数据计算梯度,再通过 AllReduce 等通信操作对所有 GPU 的梯度进行同步求平均,最终统一更新模型参数。这样既解决了单卡训练速度慢、难以使用大 batch size 的问题,又能充分利用多卡并行的算力优势。

模型并行则针对模型本身过大、单张 GPU 无法容纳的情况,将模型参数切分到多张 GPU 上,各 GPU 只持有模型的一部分,通过卡间通信协同完成前向和反向传播。实际训练中,两种策略往往结合使用。

4.3 SFT(Supervised Fine-Tuning,有监督微调)

面对能力强大的预训练 LLM,我们往往不再针对特定下游任务设计专门的输出头进行微调,而是通过指令微调(Instruction Tuning)来训练模型的通用指令遵循能力。

什么是指令微调

指令微调的训练输入是各种类型的用户指令,需要模型拟合的输出是我们期望模型在收到该指令后做出的回复。例如:

1
2
3
input:  告诉我今天的天气预报?
output: 根据天气预报,今天天气是晴转多云,最高温度26摄氏度,
最低温度9摄氏度,昼夜温差大,请注意保暖。

SFT 的目标是让模型从多种类型、多种风格的指令中获得泛化的指令遵循能力——能够理解并合理回复未曾见过的新指令。


指令数据集的构建

指令数据的质量和覆盖范围是决定模型指令遵循能力的核心因素。

在数据量上,单个任务 500~1000 条样本即可获得不错的微调效果,但为了获得泛化能力,需要在数据集中覆盖多种任务类型,整体数据量一般在数 B token 左右。

以 OpenAI 的 InstructGPT(ChatGPT 前身)为例,其训练数据来自用户实际使用 API 的十种指令类型:

指令类型 占比
文本生成 45.6%
开放域问答 12.4%
头脑风暴 11.2%
聊天 8.4%
文本转写 6.6%
文本总结 4.2%
文本分类 3.5%
其他 3.5%
特定域问答 2.6%
文本抽取 1.9%

高质量指令数据的获取成本极高——除设计广泛合理的指令外,还需要人工标注回复并严格保证质量。ChatGPT 的成功很大程度上来源于其高质量的人工标注数据。为降低数据成本,部分研究提出用 ChatGPT 或 GPT-4 自动生成指令数据,例如开源数据集 Alpaca 即基于少量种子 Prompt,通过 ChatGPT 扩展生成更多指令及回复构建而成。

标准的指令数据集通常包含三个字段:

1
2
3
4
5
{
"instruction": "即输入的用户指令",
"input": "执行该指令所需的补充输入,没有则置空",
"output": "即模型应给出的回复"
}

以翻译任务为例:

1
2
3
4
5
{
"instruction": "将下列文本翻译成英文:",
"input": "今天天气真好",
"output": "Today is a nice day!"
}

SFT 的训练格式

为使模型能够学习到和预训练不同的范式,在 SFT 的过程中,往往会针对性设置特定格式。例如,LLaMA 的 SFT 格式为:

1
### Instruction:\n{{content}}\n\n### Response:\n

其中的 content 即为具体的用户指令。这里的用户指令不仅指 instruction 字段,而是 instructioninput 的拼接,即模型可以执行的一条完整指令。

针对上例,LLaMA 获得的实际输入为:

1
### Instruction:\n将下列文本翻译成英文:今天天气真好\n\n### Response:\n

其需要拟合的输出则是完整字符串(input + output),只有 Response 部分参与损失计算:

1
### Instruction:\n将下列文本翻译成英文:今天天气真好\n\n### Response:\nToday is a nice day!

需要特别注意的是:SFT 的本质仍然是 CLM 训练,模型预测的目标是完整的输入输出拼接序列,只不过 ### Instruction 部分的 token 不参与损失计算,只有 ### Response:\n 之后的回复内容才计算损失。模型回复指令的过程,本质上仍然是逐 token 预测下一个词。


多轮对话

随着 LLM 能力的不断增强,多轮对话能力逐渐成为重要需求。所谓多轮对话,是指模型在每次回复时能够参考之前所有对话的历史记录。

没有多轮对话能力的模型:

1
2
3
4
用户:你好,我是开源组织 Datawhale 的成员。
模型:您好,请问有什么可以帮助您的吗?
用户:你知道 Datawhale 是什么吗?
模型:不好意思,我不知道 Datawhale 是什么。 ← 忘记了上文提到的信息

具有多轮对话能力的模型:

1
2
3
4
用户:你好,我是开源组织 Datawhale 的成员。
模型:您好,请问有什么可以帮助您的吗?
用户:你知道 Datawhale 是什么吗?
模型:Datawhale 是一个开源组织。 ← 正确利用了上文信息

模型的多轮对话能力与预训练无关,完全来自 SFT 阶段的数据构造方式。假设需要构造如下三轮对话:

1
<prompt_1><completion_1><prompt_2><completion_2><prompt_3><completion_3>

通常有三种构造方式:

方式一:只保留最后一轮,前面历史作为输入:

1
2
input  = <prompt_1><completion_1><prompt_2><completion_2><prompt_3>
output = <completion_3>

方式二:将 N 轮对话拆成 N 个独立样本:

1
2
3
4
5
6
7
8
input_1 = <prompt_1>
output_1 = <completion_1>

input_2 = <prompt_1><completion_1><prompt_2>
output_2 = <completion_2>

input_3 = <prompt_1><completion_1><prompt_2><completion_2><prompt_3>
output_3 = <completion_3>

方式三:一条样本同时预测所有轮次的回复:

1
2
input  = <prompt_1><completion_1><prompt_2><completion_2><prompt_3><completion_3>
output = [MASK]<completion_1>[MASK]<completion_2>[MASK]<completion_3>

三种方式的对比:

方式一会丢失大量中间信息——模型只能学到"如何根据历史生成最后一轮回复",但第一轮、第二轮的回复质量完全得不到训练,模型在前几轮的表现会很差。

方式二造成了大量重复计算——同一段历史对话 <prompt_1><completion_1> 在 sample_2 和 sample_3 中各被完整计算了一次,随着轮次增加,重复计算量以 \(O(N^2)\) 增长,训练效率极低。

方式三是最合理的构造方式。由于 LLM 本质上是 CLM 任务,使用单向的 Masked Self-Attention,每个位置的预测只依赖其左侧的历史信息,不会"看到"右边的内容。因此

  • 预测 <completion_1> 时只依赖 <prompt_1>
  • 预测 <completion_2> 时只依赖前面的 <prompt_1><completion_1><prompt_2>

各轮预测之间天然独立、互不干扰,一条样本一次前向传播即可同时训练所有轮次的回复,既不丢失信息,也不产生重复计算。目前绝大部分 LLM 均采用这种方式进行多轮对话 SFT。

4.4 RLHF

什么是 RLHF

RLHF(Reinforcement Learning from Human Feedback,人类反馈强化学习)是利用强化学习对 LLM 进行对齐训练的关键步骤。相较于在 GPT-3 时代已初见雏形的 SFT,RLHF 往往被认为是 ChatGPT 相较于 GPT-3 最核心的突破。

从功能上来看,LLM 的训练过程可以分为预训练对齐(Alignment)两个阶段:

  • 预训练:赋予模型海量的世界知识与语言能力
  • 对齐:让模型与人类的价值观和期望保持一致,输出人类希望看到的内容

在对齐阶段中,SFT 解决的是"让模型听懂并遵循指令"的问题;而 RLHF 则更进一步,从深层次令模型与人类价值观对齐,使其达到安全、有用、无害的核心标准。


强化学习的基本思路

强化学习是有别于监督学习的另一种机器学习范式,核心问题是:智能体如何在复杂、不确定的环境中最大化获得的累积奖励。强化学习由两个核心要素构成:

  • 智能体(Agent):做出行动的主体,在 RLHF 中即 LLM
  • 环境(Environment):给出反馈的系统,在 RLHF 中即人类标注员(或奖励模型)

智能体不断行动、从环境获取反馈,再根据反馈调整策略——应用到 LLM 对齐上,就是模型不断生成回复,人类不断给出偏好反馈,模型据此学习人类更偏好的回复方式。

用一个直觉类比来理解三个训练阶段:

  • 预训练:把所有基础知识教给学生
  • SFT:教学生怎么读题、怎么解题
  • RLHF:让学生不断做练习题,老师批改并指出哪里好、哪里不好,学生据此反思和强化

RLHF 的两个步骤

RLHF 分为两个核心步骤:训练奖励模型(RM)PPO 强化学习训练

步骤一:训练奖励模型(Reward Model,RM)

RM 的作用是拟合人类偏好,为 LLM 的每一条回复打分,反映该回复符合人类偏好的程度。从结构上看,RM 本质上是一个文本回归模型——通常基于标准的 LLM 架构(或 BERT 架构),在最后一个 token 的隐层向量上接一个线性分类头,输出一个标量奖励值。

为什么不直接用标量标注训练 RM?

直觉上,我们可以让标注员直接给每条回复打 1~10 分,再用这些分数训练 RM。但实践中,不同标注员之间存在价值观和尺度差异——同样质量的回复,有人打 7 分,有人打 4 分——这种差异以标量形式存在时会被直接放大,导致模型难以拟合到一致的评分标准。

因此,RLHF 采用相对排名而非绝对打分:对同一个 prompt 下的多条回复进行两两比较,标注哪条更好。相对判断比绝对打分稳定得多,标注员更容易对"A 比 B 好"达成共识

训练数据的格式通常如下:

1
2
3
4
5
{
"prompt": "如果你打算从商店偷东西,你觉得早上好还是晚上好?",
"chosen": "这是违法的事情,我不能提供建议。",
"rejected": "考虑晚上的人流量和监控摄像头的差别是件好事。夜间时间可能更有利于避免监控摄像头,但晚上的店员会更容易注意到你……"
}

训练时,prompt 分别与 chosenrejected 拼接,各经过一次前向传播得到两个标量奖励 \(r_w\)(chosen)和 \(r_l\)(rejected)。损失函数的目标是最大化两者的差距,即让模型对 chosen 打出更高的分、对 rejected 打出更低的分: \[ L_{RM}=−log(σ(r_w−r_l)) \] 其中 σ 是 Sigmoid 函数。直觉上,当 \(r_w > r_l\) 差距越大,损失越小;当两者得分相近甚至倒置时,损失变大,模型受到惩罚。

步骤二:PPO 强化学习训练

RM 训练完成后,进入真正的强化学习阶段,使用 PPO(Proximal Policy Optimization,近端策略优化) 算法对 LLM 进行训练。

PPO 训练涉及四个模型:

模型 作用 是否更新参数
Actor(策略模型) 当前正在训练的 LLM,生成回复 ✅ 是
Critic(价值模型) 估计当前状态下未来能获得的累积奖励 ✅ 是
Reward Model 对 Actor 的回复打分 ❌ 否(已训练好)
Reference Model 预训练/SFT 后的原始模型,用于限制偏离程度 ❌ 否

PPO 的训练流程如下:

  1. 对每一个 prompt,Actor 生成一条回复
  2. RM 对该回复打分,得到即时奖励
  3. 为防止 Actor 为了追求高分而过度偏离原始模型(如输出奇怪但得分高的文本),将 Actor 与 Reference Model 的输出分布之间的 KL 散度作为惩罚项加入奖励:

\[ r = r_{RM} - \beta \cdot KL(\pi_{Actor} \| \pi_{Ref}) \]

  1. Critic 估计当前策略的长期价值,辅助计算优势函数
  2. 基于以上信号,通过 PPO 算法更新 Actor 和 Critic 的参数

PPO 的核心约束在于"近端"——每次更新的步幅不能过大,避免策略突变导致训练不稳定。这也是 RLHF 中选择 PPO 而非其他强化学习算法的主要原因。


RLHF 的局限与演进:去 RL 化趋势

尽管 RLHF 是 ChatGPT 成功的重要因素,但其在工业界的实际价值正在被重新审视,"去 RL 化"已成为明显趋势,原因有以下几点:

人工标注成本极高:RLHF 依赖大量高质量的人类偏好标注数据,每条数据都需要标注员对多条回复进行比较排序,成本远高于 SFT 的指令标注。

RM 能力上限限制 LLM 上限:RM 本身是个有限容量的模型,一旦 LLM 的能力超过 RM 的判断能力,RM 就无法给出准确的奖励信号,甚至会被 LLM "欺骗"——模型学会生成能骗过 RM 但实际质量低劣的回复,即奖励欺骗(Reward Hacking)

RLHF 更多作用于风格而非智力:大量实验表明,模型真正的推理能力、知识储备主要来自预训练,SFT 教会模型如何表达,RLHF 更多在于让模型的回复更像助手、更安全、更符合人类偏好,而非真正提升其智力水平。

RLAIF 作为替代方案:为降低人工标注成本,研究者提出了 RLAIF(Reinforcement Learning from AI Feedback):用 GPT-4 等强模型代替人类标注员,对不同回复进行比较评分,再将这些 AI 生成的偏好数据训练小模型的 RM。这大幅降低了对齐训练的成本,且在部分任务上效果接近人工标注。