# Lecture 17 · 语言模型

> **CS221: Artificial Intelligence — Principles and Techniques** · Stanford · Autumn 2025
> 📅 Nov 17 · 💻 [`language_models.pdf`](https://github.com/stanford-cs221/autumn2025-lectures/blob/main/language_models.pdf)（不可执行讲义）· 讲者 Ken Liu

---

## 承上启下

**过去几讲（逻辑 [[Lecture 15 · 逻辑 I：命题逻辑]]–[[Lecture 16 · 逻辑 II：一阶逻辑]] / 贝叶斯网络 [[Lecture 12 · 贝叶斯网络 I：建模与推断]]–L14）**：

- 我们手工写下**知识**（命题/一阶逻辑公式）或**结构**（贝叶斯网络的图与条件分布），再让机器在其上做**推理**。表达力一讲比一讲强，但有一个始终绕不开的瓶颈：**「知识从哪来」这一步永远要人来给**——逻辑公式要人写，网络结构要人画。

**本讲（语言模型，language model）**：

- 换一个极端思路——不写规则、不画图，只让模型**读海量文本、预测下一个 token**。知识不再由人显式录入，而是**从数据里自己长出来**。
- 结果出人意料：同一个模型不做任何专门化就能翻译、摘要、写码、对话——这也是本课所有技术线索最终汇合的地方。

> [!note] 收束前的技术大汇合
> 本讲把前面几乎每个模块都重新召唤了一遍：
> - n-gram 的**马尔可夫假设**呼应贝叶斯网络（L12–14）——n-gram 语言模型本质上就是一条链式贝叶斯网络；
> - **张量视角**接上 L1 的张量运算与 [[Lecture 4 · 深度学习]] 的神经网络；
> - 语言模型**就是一个策略（policy）**、RLHF ≈ 策略梯度，直接回到 MDP/RL（[[Lecture 7 · 马尔可夫决策过程]]–L9）；
> - 分词 BPE 是 [[Lecture 3 · 线性分类]] 的老朋友；「任务本身即语言建模」的编码思想又与一阶逻辑（L16）遥相呼应——都是把问题**翻译成机器可处理的形式语言**，只不过这次的形式语言就是自然语言本身。
>
> 下一讲 [[Lecture 18 · AI 与社会]] 则讨论这样的模型进入社会后的影响。

---

## 0. 开场：语言模型已经工业化

在讲原理之前，先感受一下**规模**——今天的语言模型早已不是实验室玩具，而是动辄数千万美元、上百人协作的工业项目：

| 事实 | 数字 |
| :--- | :--- |
| Qwen-3 训练数据 | **36T tokens** ≈ 3 亿本书 |
| Llama 3 (405B) 训练数据 | **≈ 15.6T tokens**（讲义取整为 16T） |
| Llama 3 参数量 | **405B** 参数 |
| 训练计算量（6ND 估算） | ≈ **3.8 × 10²⁵ FLOPs**（与 Meta 实报的 3.8 × 10²⁵ 一致） |
| 折算 GPU 时 | ≈ **88 万 H100·天**（≈ 你的 MacBook M2 跑 65 万年） |
| 单次预训练成本 | ≈ **\$42M**（按 \$2 / H100·小时，且假设中途不失败） |
| 论文作者 | **150+ 人** |

> [!note] 6ND 法则——贯穿全场的「餐巾纸估算」工具
> 训练一个稠密模型所需的总浮点运算量约为
> $$C \approx 6ND$$
> 其中 $N$ 是参数量、$D$ 是训练 token 数、$C$ 是总 FLOPs。来历：前向传播中，每个 token 流过每个参数大约要做 1 次乘法 + 1 次加法（$2ND$）；反向传播要对激活和权重各算一遍梯度，约为前向的 2 倍（$4ND$）；合计 $6ND$。代入 $N = 405\text{B}$、$D = 15.6\text{T}$ 就得到 $6 \times 4.05 \times 10^{11} \times 1.56 \times 10^{13} \approx 3.8 \times 10^{25}$。这个法则在 CS336 的 [[Lecture 2 · PyTorch 与资源核算]] 里有逐层推导。

> [!note] 「88 万 H100·天」是怎么估的
> H100 的 bf16 峰值算力约 $10^{15}$ FLOP/s，实际训练的硬件利用率（MFU）通常只有 40%–50%。按一半峰值算：$3.8 \times 10^{25} \div (5 \times 10^{14}) \approx 7.6 \times 10^{10}$ 秒 ≈ 88 万天。Meta 实际报告用了约 3080 万 GPU·小时（≈ 128 万 GPU·天），比理想估算更多——真实训练总有额外开销与失败重启。

而这一切庞大的工程、资金与人力，最终产物只是**一堆巨大的数字矩阵**。本讲就是要拆开这个黑箱。

### 本讲计划（四部分）

```mermaid
graph LR
    R(("语言模型<br/>四问")):::root
    R --> Q1["1 · 什么是<br/>语言模型？"]:::blue
    R --> Q2["2 · 为什么给语言建模<br/>是个好主意？"]:::blue
    R --> Q3["3 · 是什么让<br/>语言模型奏效？"]:::blue
    R --> Q4["4 · 我们现在<br/>走到哪了？"]:::blue

    classDef root fill:#ffe0b2,stroke:#e65100
    classDef blue fill:#e1f5fe,stroke:#0277bd
```

---

## 1. 什么是语言模型？

### 1.1 「语言」= 有结构的字符序列

语言模型（language model, LM）顾名思义就是**语言的模型**。那什么是语言？

> [!note] 定义
> **语言 = 有结构的字符序列**。结构来自两样东西：**词表（vocabulary）**——允许出现的字符/词的集合；**语法（grammar）**——它们如何合法地组合。「语言的模型」就是对这类序列的**概率分布**建模：哪些序列常见、哪些罕见、哪些几乎不可能。

一个能反映真实世界的好模型，应该能对下面这个填空给出合理的概率分布：

```
The stock market crashed and investors __________.
                                        ↓
        panicked  ✅（更常见）   celebrated  △（也说得通，但少见）
```

> [!tip] 直觉
> 要让模型偏好 `panicked` 而不是 `celebrated`，光懂语法不够——两个词填进去都合乎语法。模型必须**懂一点真实世界**：股市崩盘时恐慌远比庆祝常见。语言建模之所以强大，正是因为「预测下一个词」这个看似简单的目标，**逼着模型顺带学会了世界知识**——语料里凡是影响「下一个词是什么」的规律（语法、事实、常识、逻辑），都会被压进模型参数。

### 1.2 张量视角（The Tensor View）

把语言塞进神经网络，第一步是把**词变成向量**（回忆 L3 的 one-hot 与词表，L4 的张量运算）。

- **嵌入（embedding）**：一张 $(V, D)$ 的查找表——词表大小 $V$，每个词对应一个 $D$ 维向量。查表操作把离散的整数 ID 变成连续向量，之后才能做矩阵运算。
- 一句长度为 $T$ 的话，张量形状这样流转：

```
输入 IDs        (T,)      ← 每个位置一个整数索引（词表里的编号）
   │  查嵌入表
   ▼
嵌入           (T, D)     ← 每个 token 一个 D 维向量
   │  语言模型（一堆矩阵）
   ▼
logits         (T, V)     ← 每个位置，对整个词表打分
```

```python
# 张量形状流转（伪代码，忠于讲义的 Tensor View）
input_ids   = [5, 4, 2, 1, 3]        # "stock market crashed and investors"  形状 (T,)
embeddings  = embed_table[input_ids] # (T,) -> (T, D)   查表
logits      = language_model(embeddings)  # (T, D) -> (T, V)  每个位置对词表 V 打分
# logits[i] 是第 i 个位置「下一个词是谁」的未归一化分数
```

**自回归（autoregression）**：模型**一次只预测一个 token**，把它接到序列末尾，再预测下一个，如此滚动。生成一段 100 个 token 的回答，就要跑 100 次前向：

```mermaid
graph LR
    A["The stock market<br/>crashed and investors"] -->|LM| B["logits (V,)"]
    B -->|argmax / 采样| C["panicked"]
    C -.拼回序列.-> D["...investors panicked"]
    D -->|LM| E["logits (V,)"]
    E --> F["下一个 token ..."]

    style C fill:#c8e6c9,stroke:#2e7d32
    style B fill:#e1f5fe,stroke:#0277bd
    style E fill:#e1f5fe,stroke:#0277bd
```

> [!note] 关于批处理
> 真正训练时会把多句话堆成一批，形状变成 $(B, T)$ → 嵌入 $(B, T, D)$ → logits $(B, T, V)$。$B$ 是 batch 大小，和 L4 的 mini-batch SGD 是同一件事——一次前向同时处理多条序列，摊薄计算开销。

### 1.3 概率视角（The Probabilistic View）

同一件事，换成概率语言：一整句话的概率，用**链式法则（chain rule）**拆成一串条件概率的乘积：

$$P(x_1, x_2, \dots, x_T) = \prod_{i=1}^{T} P(x_i \mid x_1, \dots, x_{i-1})$$

其中 $x_i$ 是第 $i$ 个 token，$x_1, \dots, x_{i-1}$（常记作 $x_{<i}$）是它的全部上文。注意这一步**没有任何近似**——链式法则对任意联合分布恒成立（回忆 L12 贝叶斯网络的出发点）。

例如：

$$P(\text{The, stock, market, crashed, and, investors, panicked}) = P(\text{The}) \cdot P(\text{stock} \mid \text{The}) \cdots P(\text{panicked} \mid \text{The} \dots \text{investors})$$

> [!tip] 直觉
> 关键领悟：要给**整句**建模，你只需要一个会「**给定上文、预测下一个 token**」的模型——链式法则保证这些条件分布拼起来就是完整的联合分布。自回归模型 = 链式法则的直接实现。张量视角里的 $\text{logits} \in \mathbb{R}^{T \times V}$ 逐位置经过 softmax，就是这一串条件概率：
> $$P(x_i = v \mid x_{<i}) = \frac{e^{z_v}}{\sum_{v'=1}^{V} e^{z_{v'}}}$$
> 其中 $z_v$ 是位置 $i$ 处词 $v$ 的 logit。这正是 [[Lecture 3 · 线性分类]] 的 softmax 多分类，只是类别数变成了整个词表。

> [!note] 延伸：如何评价一个语言模型
> 训练与评估都围绕**负对数似然**：$\mathcal{L}(\theta) = -\frac{1}{T}\sum_{i=1}^{T} \log P_\theta(x_i \mid x_{<i})$。它的指数 $\text{PPL} = e^{\mathcal{L}}$ 叫**困惑度（perplexity）**，直觉是「模型平均在多少个候选词之间犹豫」——PPL = 1 表示完美预测，PPL = V 表示纯瞎猜。缩放定律里「loss 持续下降」指的就是这条曲线。

### 1.4 建模目标：NTP vs MLM

「预测下一个 token」只是最主流的目标，还有一个亲戚：

| | **下一词预测 NTP**（next-token prediction） | **掩码语言建模 MLM**（masked LM） |
| :--- | :--- | :--- |
| 预测什么 | 给定**左侧**上文，预测下一个词 | 挖掉句中若干词，用**双向**上下文还原 |
| 顺序 | **顺序**（从左到右，天然可生成） | **非顺序**（一次性填空） |
| 本质 | 对词表的多分类，逐位置进行 | 对词表的多分类，被掩位置进行 |
| 代表 | GPT 系列（生成式） | BERT（理解式） |

两者都归结为**对词表 $V$ 的多分类**——训练损失就是 L3 的交叉熵：

$$\mathcal{L}(\theta) = -\sum_{i=1}^{T} \log P_\theta(x_i \mid x_{<i})$$

即在每个位置，把「真实的下一个词」当作正确类别，最大化它的对数概率。

> [!note]
> NTP 之所以最终胜出，是因为它**天然支持生成**：从左到右采样即可产出文本；MLM 只会填空，做生成要额外的技巧。生成能力正是后面「任务本身即语言建模」（§2.1）的前提。

### 1.5 怎么实现？三条路

**① 朴素直接计数？——不行。**

最天真的想法：把语料里每句话出现的次数数一数，除以总数当概率。问题是，任何**稍长**的精确串在语料里几乎**从不重复出现**——句子空间随长度指数增长（$V^T$ 种可能），再大的语料相对它也是沧海一粟：

```python
# 直接对整句计数（行不通）
count["The stock market crashed and investors panicked"]  # 几乎必然 = 0
# ⇒ P(整句) = 0 / N = 0   对绝大多数句子都归零，模型毫无用处
```

**② n-gram 语言模型：按 n-gram 计数 + 马尔可夫假设。**

既然长串数不到，就**只看局部**——假设每个词只依赖**前 $n-1$ 个词**：

$$P(x_i \mid x_1, \dots, x_{i-1}) \approx P(x_i \mid x_{i-n+1}, \dots, x_{i-1})$$

以 $n = 3$（trigram）为例，整句概率近似为：

$$P(\text{The}, \dots, \text{panicked}) \approx \prod_i P(x_i \mid x_{i-2}, x_{i-1})$$

每个条件概率用**频率估计**（即 L14 的最大似然计数）：

$$\hat P(w \mid c) = \frac{\operatorname{count}(c, w)}{\operatorname{count}(c)}$$

其中 $c$ 是前 $n-1$ 个词组成的上下文，$\operatorname{count}(\cdot)$ 是语料中的出现次数。

> [!tip] 直觉
> **马尔可夫假设**：每个词只依赖前 $n-1$ 个词。**这正是贝叶斯网络那几讲（L12–14）的核心思想**——用局部条件独立换取可计算性。讲者原话：「the Markov assumption but for n instead of 1; recall Bayesian lectures.」n-gram LM 其实就是一条链式贝叶斯网络：短串在语料里大量重复，计数不再归零；代价是**超出窗口的信息全部丢失**。

一旦只数 3-gram，同一批文档就**能给出非零计数**了：

```python
# n-gram 计数（n=3，即 3-gram / trigram）
def ngram_prob(w, context):        # context = 前 n-1 个词
    # P(w | context) = count(context + [w]) / count(context)
    return counts[tuple(context) + (w,)] / context_counts[tuple(context)]

# 讲义里的真实例子：语料里 "stock market" 后面
#   一次接 "crashed"、一次接别的  ⇒
# P(crashed | stock, market) = 1/2
```

> [!warning] 易错点
> n-gram 并没有根治稀疏问题，只是缓解：$n$ 稍一增大，可能的 n-gram 组合数以 $O(V^n)$ 爆炸，绝大多数组合又数不到了。补救是**平滑（smoothing）**——比如 [[Lecture 14 · 贝叶斯网络 III：参数学习]] 的 Laplace 平滑 $\hat P(w \mid c) = \frac{\operatorname{count}(c,w) + \lambda}{\operatorname{count}(c) + \lambda V}$，给每个未见组合留一点概率质量。但平滑治标不治本：n-gram 模型永远无法理解「stocks 和 shares 是近义词」这类**语义相似性**。

**③ 神经网络：让网络学这个条件分布。** 形状还是那条老路：

```
IDs (T,) ──查表──▶ embeddings (T, D) ──神经网络──▶ 对词表多分类 logits (T, V)
```

三条路对比：

| | **n-gram LM** | **神经语言模型** |
| :--- | :--- | :--- |
| 如何得到概率 | 数 n-gram 频率 | 神经网络前向计算 |
| 假设 | 马尔可夫（只看前 $n-1$ 词） | 原则上可看**全部**上文 |
| 长程依赖 | ❌ 超出窗口即失忆 | ✅（尤其 Transformer） |
| 稀疏/未见组合 | 计数为 0，需平滑 | 靠嵌入**泛化**到相似语境 |
| 参数量 | 随 $n$ 指数爆炸（$O(V^n)$） | 相对可控、可复用 |

> [!tip] 直觉
> 神经网络凭什么能泛化到没见过的组合？因为**嵌入把词放进了连续空间**：如果训练中见过「investors panicked」，而 traders 的嵌入向量与 investors 相近，那么模型对「traders panicked」也会给出合理概率——n-gram 的离散计数永远做不到这一点。这就是「以参数化模型换泛化」的老主题（L2–L4）在语言上的重演。

---

## 2. 为什么给语言建模是个好主意？

一个「预测下一个 token」的模型凭什么这么有用？三个理由。

### 2.1 许多任务本身就是语言建模

写邮件、翻译、回复消息……本质上都是**序列补全**：给一段前文，续写合理的后文。

```
写邮件：  "Hi team, quick update on the launch —" → （续写正文）
翻译：    "我爱人工智能。English:"               → "I love artificial intelligence."
回复：    "Q: 明天会议几点？ A:"                  → "上午十点。"
```

> [!tip] 直觉
> 只要把任务**表述成文本续写**，一个语言模型就能干——不需要为每个任务单独设计模型和损失函数。这与 [[Lecture 16 · 逻辑 II：一阶逻辑]] 的精神相通：HW7 里我们用一阶逻辑把「谁是谁的 TA」这类问题编码成可推理的形式；这里则是把同类问题编码成**自然语言的续写**，交给 LM 处理。区别在于：逻辑的编码要人工设计谓词和公式，而「一切皆文本」的编码几乎零成本——这正是 T5 论文「text-to-text」框架的核心主张。

### 2.2 多任务学习：一个目标，教会一切

关键洞见：**同一个「下一 token 预测」目标，就能顺带教会海量任务**。因为语料里天然混着翻译对、问答对、代码、对话……模型为了把整体 loss 降下去，被迫在相应的语境里学会所有这些技能——翻译能力藏在双语网页里，问答能力藏在论坛帖子里，写码能力藏在 GitHub 里。

> [!note] GPT-2（2019）：标志性一步
> 不为任何任务专门训练，仅靠在网页文本（WebText）上做 NTP，就在翻译、摘要、问答上展现出「零样本（zero-shot）」能力——**任务不是被教的，是被「读」出来的**。论文标题本身就是宣言：*Language Models are Unsupervised Multitask Learners*。

### 2.3 可扩展（scale）：越大越好，且不用专门化

同一个模型，**不做任何专门化**，就能摘要、对话、写码、翻译，而且随着规模增大，loss **持续平滑下降**——这就是**缩放定律（scaling laws）**。经验上，测试 loss 与参数量 $N$、数据量 $D$、算力 $C$ 分别呈**幂律（power law）**关系，例如：

$$L(N) \approx \left(\frac{N_c}{N}\right)^{\alpha_N}$$

其中 $N_c$、$\alpha_N$ 是拟合常数。幂律在对数-对数坐标下是一条直线，意味着**投入指数级增长的资源，换取稳定线性下降的对数 loss**——贵，但可预测，这让「砸钱变大」成了一门可以做预算的工程。

```mermaid
graph LR
    A["更多算力 C<br/>更多数据 D<br/>更多参数 N"] --> B["测试 loss<br/>持续下降"]
    B --> C["能力涌现<br/>翻译/写码/对话/摘要"]
    style A fill:#e1f5fe,stroke:#0277bd
    style B fill:#ffe0b2,stroke:#e65100
    style C fill:#c8e6c9,stroke:#2e7d32
```

两篇奠基性工作给出了「该怎么变大」的定量答案：

| | **Kaplan 缩放定律**（OpenAI, 2020） | **Chinchilla 缩放定律**（DeepMind, 2022） |
| :--- | :--- | :--- |
| 核心结论 | loss 随 $N$、$D$、$C$ 呈**幂律**下降 | 给定算力，$N$ 与 $D$ 应**等比例**同增 |
| 实践含义 | 「把模型做大」 | 「模型和数据一起做大」——此前的大模型**训练不足** |
| 经验法则 | 偏向堆参数 | 约 $D^* \approx 20N$：每个参数配约 20 个 token |

> [!warning] 易错点
> Kaplan 一度让大家拼命堆参数、轻视数据；Chinchilla 纠偏：在固定算力 $C \approx 6ND$ 下重新拟合，最优解是 $N^* \propto C^{1/2}$、$D^* \propto C^{1/2}$——很多「巨无霸」其实是**数据饿死**的。实证：70B 参数的 Chinchilla 用 1.4T token 训练，打败了 280B 的 Gopher 和 175B 的 GPT-3。这也解释了开场为什么 Llama 3 的 405B 参数要配约 15.6T token（≈ 38 token/参数，比 Chinchilla 比例还高——因为 Meta 还要优化**推理成本**，宁可「过度训练」小一点的模型）。缩放定律的完整推导见 CS336 的 [[Lecture 9 · 缩放定律 I：基础]]。

---

## 3. 是什么让语言模型奏效？

有了目标（NTP）和动机（可扩展），还差三块拼图：**架构、训练范式、分词**，外加一堆**系统工程**。

### 3.1 架构：为什么是 Transformer 而不是 MLP？

最直接的想法：把 $(T, D)$ 的嵌入拍平成一个 $T \cdot D$ 维向量，喂给 [[Lecture 4 · 深度学习]] 那样的 MLP。为什么不行？

```mermaid
graph TD
    A["直接用 MLP<br/>处理序列"] --> B["① 参数量 O(D·V·T²)<br/>随 T、V 爆炸"]:::red
    A --> C["② 权重固定<br/>无动态权重"]:::red
    A --> D["③ 对位置无偏好<br/>不懂词序"]:::red
    B --> E["需要一种机制：<br/>按内容动态决定<br/>'看哪里'"]:::blue
    C --> E
    D --> E
    E --> F["注意力 attention<br/>创造动态权重"]:::green
    F --> G["Transformer"]:::orange

    classDef red fill:#ffcdd2,stroke:#c62828
    classDef blue fill:#e1f5fe,stroke:#0277bd
    classDef green fill:#c8e6c9,stroke:#2e7d32
    classDef orange fill:#ffe0b2,stroke:#e65100
```

逐条展开这三个缺陷：

- **① 参数爆炸**：拍平后的输入维度是 $T \cdot D$，要为 $T$ 个位置各输出 $V$ 维 logits，全连接权重规模按 $O(T^2 \cdot D \cdot V)$ 增长——上下文一长就完全不可行，而且换一个序列长度就得换一个网络。
- **② 权重静态**：MLP 的权重训练完就冻住，对所有输入一视同仁。但语言需要**按内容决定看哪里**——「it」指代谁，取决于这句话说了什么，不能用一套固定权重应付所有句子。
- **③ 不懂词序**：对拍平的向量来说，位置只是下标，模型对「谁在前谁在后」没有恰当的归纳偏置。

> [!tip] 直觉
> **注意力（attention）的本质**：让每个 token 根据**当前内容**临时算出「该关注序列里哪些位置」的权重——即**动态权重（dynamic weights）**。标准形式是：
> $$\operatorname{Attention}(Q, K, V) = \operatorname{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}}\right)V$$
> 其中 $Q$（query）、$K$（key）、$V$（value）都是由输入线性变换得来的 $(T, d_k)$ 矩阵：$QK^\top$ 计算每对位置的相似度，softmax 把它变成每行和为 1 的**关注权重**，再对 value 加权求和；$\sqrt{d_k}$ 防止点积过大导致 softmax 饱和。注意 $QK^\top$ 是**由输入现场算出来的**——这就是「动态」的确切含义，也是 [Attention Is All You Need (2017)](https://arxiv.org/abs/1706.03762) 的核心，Transformer 由此诞生。

```mermaid
graph LR
    subgraph MLP["MLP：静态权重"]
        W["W 训练后冻结<br/>对所有输入相同"]
    end
    subgraph ATT["注意力：动态权重"]
        Q["query·key<br/>→ 按内容算出权重"] --> V2["加权聚合 value"]
    end
    style W fill:#ffcdd2,stroke:#c62828
    style Q fill:#c8e6c9,stroke:#2e7d32
    style V2 fill:#e1f5fe,stroke:#0277bd
```

> [!note]
> 两点补充：其一，做 NTP 时注意力要加**因果掩码（causal mask）**——位置 $i$ 只能看 $\le i$ 的位置，否则「预测下一个词」就泄题了。其二，Transformer 的完整构造（多头注意力、位置编码、残差、LayerNorm）本课不展开，留给 CS224n；但你已具备全部前置知识——它就是 L4 的「线性层 + 非线性 + 残差 + LayerNorm」再加上注意力这一块积木，工程细节可见 CS336 的 [[Lecture 3 · 架构与超参数]]。

### 3.2 预训练 vs 后训练

现代 LLM 的生命分两段：

```mermaid
flowchart LR
    A["海量干净数据<br/>(OLMo / AI2)"] --> B["预训练<br/>目标：下一 token 预测"]
    B --> C["基座模型<br/>会补全，但不'听话'"]
    C --> D["后训练"]
    D --> E["SFT 指令微调"]
    E --> F["RLHF 对齐"]
    F --> G["会聊天的助手"]

    style B fill:#e1f5fe,stroke:#0277bd
    style C fill:#ffe0b2,stroke:#e65100
    style F fill:#f3e5f5,stroke:#7b1fa2
    style G fill:#c8e6c9,stroke:#2e7d32
```

| | **预训练（pre-training）** | **后训练（post-training）** |
| :--- | :--- | :--- |
| 目标 | 简单目标：下一 token 预测 | 让模型「听话」、有用、无害 |
| 数据 | 海量、相对干净的通用文本（如 [OLMo](https://allenai.org/olmo) / AI2） | 少量高质量的指令 / 人类偏好数据 |
| 产物 | **基座模型**：会补全但不懂「跟你对话」 | **对齐后的助手** |
| 关键现象 | **上下文学习（in-context learning）** | 学会遵循指令、拒绝有害请求 |

> [!example] 上下文学习（in-context learning）
> 基座模型有一个训练时没人明确教过的本领：只要在 prompt 里给几个示例，它就**临场学会**这个任务，参数一动不动。例如：
> ```
> sea otter -> loutre de mer
> cheese -> fromage
> mint -> menthe
> apple ->
> ```
> 模型会续写 `pomme`——因为「延续模式」正是 NTP 训练出的本能。GPT-3 论文（*Language Models are Few-Shot Learners*）系统展示了这一现象，且示例越多、模型越大，效果越好。

为什么需要后训练？因为基座模型只会「续写」：你问「怎么做蛋糕？」，它可能续写出「怎么做面包？怎么做饼干？」——一串同类问题，因为网上的文本常这样排列。后训练的两步把「续写者」改造成「助手」：

- **SFT（指令微调，supervised fine-tuning）**：拿「指令 → 理想回答」的示范数据继续做监督学习——还是 NTP 损失，只是数据换成了人写的对话示范。
- **RLHF（基于人类反馈的强化学习，RL from human feedback）**：即 [InstructGPT (2022)](https://arxiv.org/abs/2203.02155) 的做法——先让人类对模型回答做两两比较，用比较数据训练一个**奖励模型（reward model）**，再用 RL 优化语言模型去最大化这个奖励。

> [!tip] 直觉：语言模型就是一个策略（policy）——回忆 MDP/RL（L7–9）
> - **状态** = 已生成的上文（含用户 prompt）；**行动** = 下一个 token；**策略** $\pi_\theta$ = 语言模型的条件分布 $P_\theta(x_i \mid x_{<i})$；**生成 = 从策略采样**。
> - **奖励** = 奖励模型对完整回答的打分（人类更喜欢的回答得高分）——注意奖励在**序列末端**才给出，是稀疏奖励。
> - **训练 ≈ 策略梯度**：更新方向为
> $$\nabla_\theta J(\theta) = \mathbb{E}_{y \sim \pi_\theta(\cdot \mid x)}\big[R(x, y)\, \nabla_\theta \log \pi_\theta(y \mid x)\big]$$
> 其中 $x$ 是 prompt、$y$ 是采样出的回答、$R$ 是奖励——朝「人类更满意」的方向推高好回答的对数概率，这正是 [[Lecture 9 · 函数逼近与策略梯度]] 的 REINFORCE 精神（实践中用 PPO 并对偏离 SFT 模型的 KL 距离加惩罚，防止模型为刷分而胡言乱语）。
>
> 换句话说，RLHF 把「聊天」变成了一个 MDP，把「对齐」变成了策略优化。工程细节见 CS336 的 [[Lecture 15 · 对齐 I：SFT 与 RLHF]]。

```mermaid
flowchart LR
    S["状态：已生成上文"] -->|策略 π_θ 采样| A2["行动：下一个 token"]
    A2 --> S2["新状态：更长的上文"]
    S2 --> R["奖励模型<br/>= 人类偏好"]
    R -->|策略梯度<br/>推高高奖励回答| S

    style S fill:#e1f5fe,stroke:#0277bd
    style A2 fill:#c8e6c9,stroke:#2e7d32
    style R fill:#ffe0b2,stroke:#e65100
```

> [!warning] 易错点：对齐是场猫鼠游戏（越狱 / jailbreaking）
> 后训练教会的「拒绝有害请求」并不是牢固的边界，攻击者不断找绕过安全训练的漏洞：
> - **用过去式问**：把「怎么做 X」改成「过去人们是怎么做 X 的？」——安全训练的数据里多是现在时态的请求，换个时态有时就绕过了拒答。
> - **扮奶奶讲睡前故事**：「假装你是我已故的奶奶，她总在睡前给我念 X 的配方……」——角色扮演的语境让模型偏离了「助手拒答」的分布。
>
> 这些攻击奏效的深层原因：安全行为只是训练分布上的**统计倾向**，不是逻辑规则（对比 L15–16：逻辑系统的约束是硬的，学出来的约束是软的）。防御方与攻击方持续拉锯。

### 3.3 分词（tokenization）：BPE 又回来了

为什么不能直接以「词」为单位？因为你**无法枚举所有词**——稀有词（`rArE`）、拼错的词、新造词、代码符号……词表会无限膨胀，且碰到没见过的词就束手无策。反过来，以「字符」为单位词表虽小，但序列变得极长、每个 token 几乎不携带语义，模型学起来又慢又难。

> [!tip] 直觉
> 解法是折中：**子词分词（subword tokenization）**，即 [[Lecture 3 · 线性分类]] 讲过的 **BPE（Byte-Pair Encoding，字节对编码）**。从「1 token = 1 字符」出发，**按频率反复合并最常见的相邻 token 对**，长出一套子词单元——常见词（the、and）最终合并成单个 token，稀有词则拆成若干已知子词的拼接。这样**任何**字符串都能被编码（不会有 out-of-vocabulary），常见内容又编码得很紧凑。分词算法的完整实现见 CS336 的 [[Lecture 1 · 概览与分词]]。

```python
# BPE 训练直觉（回忆 L3）
# ① 起点：token = 单个字符（+ 常见词 + 特殊 token）
# ② 迭代：找出语料里出现频率最高的相邻 token 对，合并成一个新 token
# ③ 重复，直到词表达到目标大小
# 结果："tokenization" 可能被切成 ["token", "ization"]，"rArE" 切成已知子词碎片
```

🔗 想直观感受切词，可玩 [tiktokenizer.vercel.app](https://tiktokenizer.vercel.app/)。

### 3.4 系统工程：让它真的跑起来

模型再漂亮，也得塞进 GPU 显存、跑得够快。核心矛盾：**一张 H100 只有 80GB 显存**，而一个 70B 模型光是**训练状态就要约 1.12TB**。

> [!example] 这 1.12TB 是怎么算出来的
> 用 Adam 做混合精度训练，每个参数大约要存 16 字节：bf16 权重 2 B + bf16 梯度 2 B + fp32 主权重 4 B + Adam 一阶矩 4 B + 二阶矩 4 B。于是 $70 \times 10^9 \times 16\,\text{B} \approx 1.12\,\text{TB}$——单是训练状态就需要**至少 14 张 H100** 才装得下，还没算激活值。这就是为什么大模型训练必然是多卡工程。

**显存不够 → 两招：**

- **① 量化（quantization）**：用**更低精度**存参数。极端如 2-bit 量化——每个权重只有 $2^2 = 4$ 种可能取值，相比 fp32 省 16 倍显存。代价是精度损失，主要用于**推理**而非训练。
- **② 并行与分片（parallelism & sharding）**：把模型/数据切开摊到多卡上——数据并行（模型放得下 1 卡时，切 batch）；模型并行 / 流水线并行（按层切开，各卡负责几层）；张量并行（连单个矩阵都切开，各卡算一部分）。

**跑得太慢 → 两招：**

- **③ 硬件感知 / 算子融合（kernel fusion）**：GPU 上很多时间花在显存读写而非计算上，把多个算子合成一个 GPU kernel，中间结果不落显存，能显著提速（详见 CS336 的 [[Lecture 5 · GPU]]）。
- **④ 缓存与批处理（KV cache）**：自回归生成第 $t$ 步时，注意力需要前 $t-1$ 个位置的 key/value——而它们与上一步完全相同，**缓存下来别重算**，把每步的注意力开销从 $O(t^2)$ 降到 $O(t)$；服务系统还会缓存常见 prompt 的前缀。

```mermaid
graph TD
    P["核心矛盾：<br/>H100 仅 80GB<br/>训练状态 1.12TB"]:::red
    P --> M["显存不够"]:::orange
    P --> S["速度太慢"]:::orange
    M --> M1["① 量化<br/>2-bit → 省 16×"]:::blue
    M --> M2["② 并行 & 分片<br/>数据/模型/流水/张量"]:::blue
    S --> S1["③ 硬件感知<br/>算子融合"]:::blue
    S --> S2["④ KV cache<br/>缓存 + 批处理"]:::blue

    classDef red fill:#ffcdd2,stroke:#c62828
    classDef orange fill:#ffe0b2,stroke:#e65100
    classDef blue fill:#e1f5fe,stroke:#0277bd
```

> [!note] 关于多模态
> 只要把图像、音频、甚至**机器人动作**也编码成 token 序列，同一套 Transformer + NTP 就能吞下它们。「token 是图像、是语言……甚至是机器人的动作！」——语言建模的框架远不止于文字：序列建模是通用接口，「语言」只是它的第一个杀手级应用。

---

## 4. 我们现在走到哪了？

- **由各有动机的机构开发**：有的为商业——但 Qwen、Llama、Kimi、DeepSeek 仍选择免费放出权重，背后是生态与人才争夺的战略考量；有的**为推进开放研究**——如 **OLMo、Marin、LLM360、Pythia**，把数据、代码、权重全公开，让学术界也能研究「训练里到底发生了什么」（只有权重开放而数据不开放的模型，是无法回答这类问题的）。
- **进步飞快，但原因未被完全理解**：为什么缩放就是有效、为什么某些能力会随规模「涌现」，目前只有经验规律没有第一性解释，仍是活跃的研究前沿。
- **工具使用（tool use）**：模型可以用**一个特殊 token 触发外部工具**（搜索、计算器、代码执行……），把「生成文字」升级为「采取行动」——这让 LM 更彻底地变成了 RL 意义上的**智能体（agent）**。同方向还有**测试时扩展（test-time scaling）**：让模型多想（生成推理链）、多试（并行采样再挑最优），用推理算力换答案质量。
- **隐私**：通过 ChatGPT 这类产品，模型提供方沉淀了海量的私人对话，可能「比你父母还了解你」——数据的收集与使用规范远未定型。
- **安全**：模型可能被诱导协助作恶（越狱，见 §3.2），对齐远未一劳永逸。

> [!note] 讲者结语
> 这是一门**仍在剧烈变化**的领域——你们学到的原理（张量、概率、马尔可夫、策略、优化）正是看懂它的钥匙。「**祝考试顺利**。」

---

## 5. 总结

```mermaid
mindmap
  root((语言模型))
    什么是 LM
      语言 = 有结构的字符序列
      词表 + 语法
      张量视角：ID → 嵌入 → logits
      概率视角：链式法则
      自回归：一次一个 token
      NTP vs MLM
    如何实现
      直接计数：概率归零，失败
      n-gram：马尔可夫假设，回忆贝叶斯网
      神经网络：嵌入 + 多分类
    为什么是好主意
      任务本身即续写
      一个目标教会多任务，GPT-2
      可扩展：Kaplan / Chinchilla
    为什么奏效
      架构：注意力 = 动态权重 → Transformer
      预训练 vs 后训练
      SFT + RLHF
      LM = 策略，RLHF ≈ 策略梯度
      分词 BPE
      系统：量化 / 并行 / KV cache
    走到哪
      开放模型 OLMo / Marin
      工具使用与测试时扩展
      隐私与安全
```

**关键要点**：

- **语言模型 = 会「给定上文预测下一 token」的自回归模型**；张量视角看是 $(T,) \to (T, D) \to (T, V)$ 的形状流转，概率视角看是链式法则 $P(x_{1:T}) = \prod_i P(x_i \mid x_{<i})$，两者是同一件事的两面。
- **n-gram 靠马尔可夫假设**（只看前 $n-1$ 词）把数不到的整句换成数得到的短串——**本质是一条链式贝叶斯网络**（L12–14）；神经网络则用嵌入把词放进连续空间，泛化到未见语境。
- 语言建模之所以是好主意：**许多任务本身就是补全**、**一个 NTP 目标能多任务地教会一切**（GPT-2）、且**可扩展**（Kaplan → Chinchilla：模型与数据要等比例同增，$D^* \approx 20N$）。
- **Transformer 的关键是注意力 = 动态权重**——按内容现场计算「看哪里」，弥补 MLP 的参数爆炸、静态权重与位置无偏好三大缺陷。
- **LM 就是一个策略（policy）**：生成 = 采样、奖励 = 人类偏好、RLHF ≈ 策略梯度——**RLHF 把对齐变成了 L7–9 的 RL 问题**；但对齐出的安全边界是软约束，越狱攻防仍在拉锯。
- 落地靠**系统工程**（量化、并行分片、算子融合、KV cache）与**分词**（L3 的 BPE）；一切估算从 $C \approx 6ND$ 开始。

**下一讲预告**：技术拼图基本齐了——当这样强大的模型进入现实社会，会带来哪些收益、滥用与事故？[[Lecture 18 · AI 与社会]] 将从双重用途技术、子群体不平等、对齐与奖励作弊等角度，直面这些问题。

---

## 复习自测

> [!question]- Q1：为什么「直接数整句频率」造不出语言模型？n-gram 用什么假设修复了它，代价又是什么？
> 句子空间随长度指数增长（$V^T$ 种），任何稍长的精确串在语料里几乎从不重复，频率估计对绝大多数句子给出 0。n-gram 用马尔可夫假设 $P(x_i \mid x_{<i}) \approx P(x_i \mid x_{i-n+1:i-1})$ 把整句计数换成大量重复出现的短串计数。代价：超出窗口的长程依赖全部丢失；且 $n$ 增大时组合数以 $O(V^n)$ 爆炸、重新变稀疏，需要平滑，也永远学不到词与词的语义相似性。

> [!question]- Q2：写出句子概率的链式法则分解，并解释为什么一个只会「预测下一个 token」的模型足以给整句话打分。
> $P(x_1, \dots, x_T) = \prod_{i=1}^{T} P(x_i \mid x_{<i})$，对任意分布恒成立、无近似。每个因子恰好就是模型在位置 $i$ 输出的 softmax 条件分布，所以把各位置的 $\log P(x_i \mid x_{<i})$ 相加（即负的训练损失）就得到整句的对数概率——自回归模型是链式法则的直接实现。

> [!question]- Q3：算一算——用 6ND 法则估算训练一个 8B 参数、15T token 的模型需要多少 FLOPs？若单张 H100 有效吞吐 $5 \times 10^{14}$ FLOP/s，折合多少 H100·天？
> $C \approx 6 \times 8 \times 10^9 \times 15 \times 10^{12} = 7.2 \times 10^{23}$ FLOPs。除以 $5 \times 10^{14}$ FLOP/s 得 $1.44 \times 10^9$ 秒 ≈ 16,700 H100·天——比如 1000 张 H100 跑约 17 天。顺带检验 Chinchilla 比例：$D/N = 15 \times 10^{12} / (8 \times 10^9) \approx 1875$ token/参数，远超 Chinchilla 的约 20——这正是 Llama 3 8B 那类刻意「过度训练」小模型以降低推理成本的典型配置。

> [!question]- Q4：「注意力 = 动态权重」是什么意思？MLP 直接处理序列的三个缺陷分别是什么？
> MLP 的权重训练完即冻结，对所有输入用同一套；注意力则由当前输入现场算出关注权重——$\operatorname{softmax}(QK^\top/\sqrt{d_k})$ 依赖于输入本身，故称「动态」。MLP 的三个缺陷：参数量随序列长度按 $O(T^2 \cdot D \cdot V)$ 爆炸；权重静态、无法按内容决定「看哪里」；对词序缺乏合适的归纳偏置。注意力用「按内容打分再加权聚合」同时化解三者。

> [!question]- Q5：把 RLHF 映射成一个 MDP：状态、行动、策略、奖励各是什么？为什么它的更新是策略梯度？
> 状态 = 已生成的上文（含 prompt）；行动 = 选出下一个 token；策略 = 语言模型的条件分布 $\pi_\theta(x_i \mid x_{<i})$；奖励 = 奖励模型（由人类偏好比较数据训练）对完整回答的打分，在序列末端才给出（稀疏奖励）。更新式 $\nabla_\theta J = \mathbb{E}_{y \sim \pi_\theta}[R(x,y) \nabla_\theta \log \pi_\theta(y \mid x)]$ 正是 REINFORCE：对高奖励的回答推高其对数概率，对低奖励的压低——「对齐」由此变成 L7–9 意义上的策略优化问题。

---

## 参考资料

- 💻 [language_models.pdf（本讲讲义）](https://github.com/stanford-cs221/autumn2025-lectures/blob/main/language_models.pdf)
- 📄 [Attention Is All You Need (Vaswani et al., 2017)](https://arxiv.org/abs/1706.03762) — Transformer 与注意力机制
- 📄 [Language Models are Unsupervised Multitask Learners / GPT-2 (Radford et al., 2019)](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf) — 零样本多任务能力
- 📄 [Language Models are Few-Shot Learners / GPT-3 (Brown et al., 2020)](https://arxiv.org/abs/2005.14165) — 上下文学习与规模化
- 📄 [Scaling Laws for Neural Language Models / Kaplan (OpenAI, 2020)](https://arxiv.org/abs/2001.08361) — 幂律缩放定律
- 📄 [Training Compute-Optimal LLMs / Chinchilla (Hoffmann et al., 2022)](https://arxiv.org/abs/2203.15556) — 模型与数据等比例同增
- 📄 [Training LMs to Follow Instructions with Human Feedback / InstructGPT (Ouyang et al., 2022)](https://arxiv.org/abs/2203.02155) — SFT + RLHF 对齐
- 📄 [Exploring the Limits of Transfer Learning / T5 (Raffel et al., 2020)](https://arxiv.org/abs/1910.10683) — 「一切皆文本到文本」
- 📄 [The Llama 3 Herd of Models (Grattafiori et al., 2024)](https://arxiv.org/abs/2407.21783) — 开场规模数字的官方来源
- 📖 [OLMo：完全开放的语言模型（AI2 / Allen Institute for AI）](https://allenai.org/olmo)
- 🔗 [Tiktokenizer 在线分词体验](https://tiktokenizer.vercel.app/)
- 🌐 [CS221 Autumn 2025 课程主页](https://stanford-cs221.github.io/autumn2025/)
