▤书架
▶目录

大语言模型理论与实践

对应:培养方案 专业选修课 · 学期 6 · 2 学分 · 32 学时(16 课堂 + 16 实践)
标记 ↗:与信息科学学院人工智能专业同堂共享课,同一间教室、同一份作业、同一套考试。
标记 *:含创新创业思维。这门课是专业选修里离产业最近的一门,* 主要落在
第 6、7 两节(提示工程、高效微调)讲的是今天真实公司在做的事。

前置:这页假设你已经读过同目录的《深度学习原理与实践》和《自然语言处理》。
Transformer、注意力、softmax 的推导在《深度学习》§8;Word2Vec、BERT、困惑度、预训练+微调
范式的推导在《自然语言处理》§2、§3、§6。本页一个都不重复推导,只引用。
如果那两页没读,先回去读,本页是它们的下游。

这份笔记怎么用:所有代码都用 numpy / scipy 从零手写并实际跑过,
复制到终端就能跑。搜索 >>> 实跑输出 能看到我跑出来的真实结果。
运行环境:~/miniconda3/bin/python(numpy 2.5.3 / scipy 1.18.1)。
不需要 GPU,不需要联网,不需要 API key。

⚠️ 时效性声明(这门课的特殊风险):这是所有专业课里变化最快的一门。
下面涉及具体模型版本、产品能力、"最新进展"的地方,凡是我没有把握的,
都写成了"截至本书写作时"或直接标注不确定。不要把本页任何一句关于
"现在最强的模型能做什么"的话当成定论。但原理部分(规模定律的形式、
RLHF 的三步、LoRA 的分解、解码的数学)是稳定的,那些可以放心当知识。

相关笔记:认知侧请对照 ../专业课/教材/笔记-心理语言学核心.html(§6 大模型 vs 人类语言加工)
与 ../专业课/教材/笔记-语言认知神经科学.html。


1 导论:这门课在研究什么(先读这个)

1.1 这门课不是什么

误解一:"大模型就是'更大的 BERT'。"

这是本专业学生最容易有的误会,而且它是唯一一个本页要花一整节去反驳的(§1)。
BERT 和 GPT 都是 Transformer,都靠预训练,都在 2018–2020 年出现,所以看起来像同一条
路上的两个刻度。但它们不是同一条路。 BERT 用的还是"预训练 + 每个任务微调一个头"
的老范式;GPT-3 之后,微调这一步在很多任务上直接消失了:任务写在提示里,
模型的权重一动不动。这不是"更大",这是换了工作方式。

误解二:"大模型的能力是设计出来的。"

不是。GPT-3 的架构和 GPT-2 几乎一样,训练目标也一样(预测下一个词)。
从 GPT-2 到 GPT-3 的主要改动就是变大,参数从 15 亿到 1750 亿,数据从 40GB 到 3000 亿 token。
而 in-context learning、思维链这些能力,没有一条是专门设计进去的。
它们是在规模到达某个量级后自己冒出来的。这个"冒出来"(emergence)本身
(以及它是否真的是"冒出来",见 §5)是这门课最有意思的问题。

误解三:"大模型的困难在于训练,训练完就没事了。"

训练只是其中一段。今天的成本大头在推理(每次你打字,背后都在跑一次完整的前向)。
所以 §7 的高效微调(LoRA、量化)和 §8 的解码策略,在工业界的权重不比训练低。
而且"模型说了什么"不只由权重决定,同一份权重,换解码参数,输出可以完全不同(§8 会实跑给你看)。

误解四:"这是一门讲'AI 会不会取代人'的课。"

不是。这门课讲机器:它的损失函数长什么样、为什么这么训、在什么条件下会崩。
"会不会取代"是另一门课的事(《心理语言学》《认知神经科学》和哲学课都在谈)。
本页只在 §9、§10 各留一节,把能验证的部分说清楚,剩下的一律不下结论。
这门课的判据是"这个说法有没有可复现的实验支撑",不是"这个说法听起来深不深刻"。

1.2 它在专业里的位置

                        认知科学与技术 · 专业选修
                                    │
        ┌───────────────────────────┼───────────────────────────┐
        │                           │                           │
     它依赖谁                    【本课】                     它被谁依赖
   ────────────        大语言模型理论与实践 (学期 6)          ──────────────
   · 深度学习原理与实践     2 学分 / 32 学时                 · 智能体原理与设计
     (学期 5,注意力/       16 课堂 + 16 实践                  (把大模型当"大脑")
      Transformer 推导)  ↗ 与信科院人工智能专业共享           · 认知神经科学
   · 自然语言处理           * 含创新创业思维                    (语言脑机制建模)
     (学期 5,BERT/预训练         │                          · 心理语言学
      +微调/困惑度)              │                              (模型-人脑对齐)
   · 概率统计(KL/信息论)        │
   · 人工智能原理(学期 3)        ▼
                    ┌────────────────────────────────┐
                    │ 一条主线:规模改变了什么         │
                    └────────────────────────────────┘
                       │
     ┌─────────────────┼──────────────────┬───────────────────┐
     ▼                 ▼                  ▼                   ▼
  为什么变大有用     变大之后           怎么用/怎么改        怎么把话吐出来
  规模定律 §2       冒出新能力 §1,§5    提示 §6 / 微调 §7    解码 §8
  (Kaplan/Chinchilla) (ICL/CoT)        (RLHF §4)           (T/k/top-p)

和隔壁几门课的分工:

课程 它负责 和本课的分界
深度学习原理与实践(学期 5) 组件级原理:反向传播、注意力、Transformer 那里拆开每个零件;这里把整个模型当黑箱,问"规模给它带来了什么"
自然语言处理(学期 5) 任务、表示、评测(Word2Vec/BERT/BLEU) 那里到 BERT 为止;本课从 BERT 之后讲起,重点是质变而非量变
本课(学期 6) 十亿参数级模型的行为与工程 讲规模定律、RLHF、涌现争议、LoRA、解码,这些在 NLP 那页都没出现
人工智能原理(学期 3) 搜索、逻辑、agent 框架 那门讲"怎么决策",不涉及"怎么从数据里学参数"
心理语言学 / 认知神经科学 人怎么做语言加工 那两门研究人;本课研究机器,但 §10 是两者的接口

本课是认知科学专业里唯一一门直接讨论"当前最强 AI 系统"的课。
它的价值不在教你训一个大模型(32 学时、没有 GPU,做不到),
而在教你看懂关于大模型的每一句主张是怎么被验证或没被验证的,
这个能力在你读研、看新闻、写论文时天天要用。

1.3 真正要教你的能力

具体、可检验,一共四条:

  1. 能读懂一条规模定律并算它的推论。给你 $C=6ND$ 和 $D=20N$,
    你能推出 $N^*=\sqrt{C/120}$,并算出某个算力预算下"该训多大模型、喂多少数据"。
    检验方式:§2 的推导 + §11 第 1、2 题。
  2. 能手写奖励模型的目标函数并对它求导。 Bradley-Terry 形式
    $\mathcal{L}=-\log\sigma(r_w-r_l)$ 的梯度你要能不查资料写出来,
    并且知道它和逻辑回归是同一个东西。检验方式:§4.3 + §11 第 3 题。
  3. 能分辨"能力涌现"和"指标涌现"。看到一个"某能力在 X 亿参数突然出现"的图,
    你能追问:用的是哪个指标?这个指标是连续的还是跳变的?测试集多大?
    检验方式:§5 的实跑 + §11 第 5 题。
  4. 能算清一次微调/推理的参数与显存账。给你一个矩阵的形状和 LoRA 的秩,
    你能说出可训练参数降了几倍、显存大概省多少;给你一个概率分布和 $T$/$k$/$p$,
    你能手算解码后的分布。检验方式:§7、§8 + §11 第 6、7 题。

1.4 这份笔记的组织逻辑 / 怎么读

组织的原则是:先讲清"规模改变了什么"(§1–§2),再讲"人怎么把它变听话"(§3–§4),
然后处理"这中间有多少是幻觉"(§5),最后是全部工程手段(§6–§8),
末尾才是局限与和你专业的关系。

   §1 大模型不是更大的 BERT ★★  ▸ 先破掉最大的误会,建立坐标系
        │                          (含 in-context learning 实跑)
        ▼
   §2 规模定律 ★★★              ▸ Kaplan 2020 vs Chinchilla 2022
        │                          "参数、数据、算力按幂律下降" + 实跑
        ▼
   §3 GPT 系列的技术路线          ▸ GPT-1 → 2 → 3 → InstructGPT → 4
        │                          每一步解决了什么问题
        ▼
   §4 RLHF ★★★                  ▸ 全篇最硬的一节。三步逐项讲,
        │                          奖励模型目标函数 + PPO 的 KL 罚项 + 实跑
        ▼
   §5 涌现能力与它的争议 ★★★     ▸ 用实跑说明"指标决定结论"
        │                          (和《实验方法论》同构)
        ▼
   §6 提示工程与思维链            ▸ few-shot / CoT / self-consistency
        │                          (含真实 API 调用的写法,本页不跑)
        ▼
   §7 高效微调:LoRA 与量化 ★★    ▸ 低秩分解推导 + numpy 实跑 + INT4 实跑
        │
        ▼
   §8 推理与解码 ★★              ▸ temperature / top-k / top-p 的数学 + 实跑
        │
        ▼
   §9 局限与风险 -> §10 和你专业的关系 -> §11 习题 -> §12 出处索引

阅读顺序建议:

一页速查:

符号 读法 含义 典型值
$N$ 参数量 模型有多少个非嵌入参数 $10^8$ ~ $10^{12}$
$D$ 数据量 训练用了多少个 token $10^{10}$ ~ $10^{13}$
$C$ 算力 训练总浮点运算数,$C\approx 6ND$ $10^{21}$ ~ $10^{25}$ FLOPs
$D/N$ 数据参数比 每个参数分到多少 token Chinchilla 最优 ≈ 20
$r$ LoRA 的秩 低秩分解保留几维 4 ~ 64
$\beta$ KL 罚系数 RLHF 里"允许偏离参考模型多少" $0.01$ ~ $0.1$
$\alpha$ LoRA 缩放常数 $\Delta W$ 前的系数 $=\alpha/r$ 常取 $r$ 或 $2r$
$T$ 温度 解码时 logit 的除数 0 ~ 2
$p$ 核采样阈值 top-p 保留的累计概率 0.9 ~ 0.95

2 大模型不是"更大的 BERT"

这一节立坐标系:BERT 和 GPT 是 Transformer 的两条分岔。
(注意力、Transformer 的机制推导不在这里,见《深度学习》§8。这里只讲范式的差别。)

2.1 两条分岔:编码器 vs 解码器

BERT 和 GPT 都出自 Transformer,但取了 Transformer 的两个不同部分:

BERT(2018) GPT 系列(2018–)
用了 Transformer 的 编码器(双向) 解码器(因果、单向)
注意力能看到 左右都看 只能看左边(不能偷看答案)
预训练目标 掩码语言模型:挖掉 15% 的词让模型填 自回归:预测下一个词
擅长 理解(分类、抽取、匹配) 生成(续写、对话、推理)
用它的方式 预训练 + 每个任务微调一个头 预训练(+ 对齐),然后写提示

关键区别不在"大小",在"目标函数"。掩码语言模型的训练目标是
"给定双向上下文,还原被挖掉的词",这个目标天生不含生成:
模型学的是"填空",它没有"往下写"的接口。而自回归目标
$p(w_t \mid w_1,\dots,w_{t-1})$ 本身就是生成:
模型每一步都在输出一个关于下一个词的概率分布,采样它就得到文本。

规模对比(这些数字来自各自论文):

模型 年份 参数量 训练数据 上下文长度
BERT-base 2018 1.1 亿 33 亿词(BooksCorpus + 英文维基) 512
BERT-large 2018 3.4 亿 同上 512
GPT-1 2018 1.17 亿 BooksCorpus(约 7000 本书) 512
GPT-2 2019 15 亿 WebText,40GB 1024
GPT-3 2020 1750 亿 3000 亿 token 2048
GPT-4 2023 未公开 未公开 未公开

(GPT-4 Technical Report, arXiv:2303.08774,明确拒绝披露架构、参数量、算力和数据,
只报了评测结果。所以上表后两格的"未公开"是报告里真的没写,不是我查不到。
这是 2023 年之后的一个行业转向:前沿模型不再公开技术细节。)

从 BERT-large 到 GPT-3,参数差 约 500 倍。但真正改变游戏规则的不是这 500 倍,
是这个规模上冒出来的新用法。

2.2 质变在哪:从"微调"到"不动权重"

把两代工作流的差别并排画出来:

【BERT 工作流】每个任务都要改权重
   预训练 BERT ──► 拿一批标注数据 ──► 反向传播改全部 1.1 亿参数 ──► 冻结 ──► 上线
                    (每个新任务都要重来一遍,都要标注数据)

【GPT-3 工作流】权重全程不动
   预训练 GPT-3 ──► 把 3 个例子写进提示 ──► 一次前向 ──► 输出
                    (没有反向传播,没有新参数,没有标注数据)

                    ↑
              这 3 个例子就是"学习"的全部发生地

第二种叫 in-context learning(上下文学习,ICL)。
GPT-3 论文(Brown et al., 2020, NeurIPS, arXiv:2005.14165)把它按提示里的
例子数量分成三档:

叫法 提示里给几个例子 权重更新
zero-shot 0 个,只有任务描述 无
one-shot 1 个 无
few-shot 10 ~ 100 个 无

"无"这个字是重点:这三档里没有任何梯度更新。
模型的参数在推理阶段是常数。"学习"这件事被搬进了前向计算里。

2.3 上下文学习到底学到了什么:一次实跑

ICL 听起来像魔法。但至少在一类干净的情形下,它不是什么魔法,就是前向过程里解了一个最小二乘。

设定:真实任务是一个线性映射 $y = \mathbf{w}^\top \mathbf{x}$($\mathbf{x}\in\mathbb{R}^5$)。
上下文里给 $k$ 个 $(\mathbf{x}_i, y_i)$ 例子,然后问新的 $\mathbf{x}_q$ 该输出什么。
模型参数一个都没动,我们只是在前向里对上下文做岭回归:

$$\hat{\mathbf{w}} = (X^\top X + \lambda I)^{-1} X^\top \mathbf{y}$$

import numpy as np
np.set_printoptions(precision=4, suppress=True)
rng = np.random.default_rng(7)
DIM = 5
w_true = rng.normal(size=DIM)                      # 真实任务
Xte = rng.normal(size=(4000, DIM)); yte = Xte @ w_true
LAM = 0.3

def mk(k):
    X = rng.normal(size=(k, DIM))
    return X, X @ w_true + rng.normal(scale=0.3, size=k)

def ls(X, y):                                       # 前向里"读"出任务
    return np.linalg.solve(X.T@X + LAM*np.eye(DIM), X.T@y)

base = (yte**2).mean()                              # 没有上下文时只能输出 0
print("   k    平均测试 MSE    比基线降了")
print("   0    %9.5f      --" % base)
for k in [1, 2, 3, 5, 8, 16, 32, 64, 128]:
    m = np.mean([((Xte@ls(*mk(k)) - yte)**2).mean() for _ in range(300)])
    print("  %3d    %9.5f    %5.1f%%" % (k, m, (1-m/base)*100))

def gd(X, y, T):                                    # 换成梯度下降
    w = np.zeros(DIM); eta = 1.0/np.linalg.norm(X, 2)**2
    for _ in range(T):
        w = w - eta*(X.T@(X@w - y))
    return w

k = 20
X, y = mk(k)
w_ls = np.linalg.lstsq(X, y, rcond=None)[0]
print("  步数 T    测试 MSE     ||w_T - w_最小二乘||")
for T in [1, 2, 5, 10, 30, 100, 2000]:
    w = gd(X, y, T)
    print("  %4d    %9.5f      %9.5f" % (T, ((Xte@w-yte)**2).mean(), np.linalg.norm(w-w_ls)))

eta = 1.0/np.linalg.norm(X, 2)**2
w1 = gd(X, y, 1)
print("  GD 一步的预测  前 5 个 =", (Xte@w1)[:5])
print("  注意力形式的预测 前 5 个 =", (eta*(Xte@X.T)@y)[:5])
print("  最大偏差 = %.2e" % np.abs(Xte@w1 - eta*(Xte@X.T)@y).max())

实跑输出

   k    平均测试 MSE    比基线降了
   0      1.08998      --
    1      0.89599     17.8%
    2      0.71932     34.0%
    3      0.57373     47.4%
    5      0.32023     70.6%
    8      0.12382     88.6%
   16      0.04252     96.1%
   32      0.01736     98.4%
   64      0.00802     99.3%
  128      0.00370     99.7%
  步数 T    测试 MSE     ||w_T - w_最小二乘||
     1      0.22448        0.46989
     2      0.06836        0.24073
     5      0.01293        0.07564
    10      0.00482        0.02599
    30      0.00251        0.00044
   100      0.00248        0.00000
  2000      0.00248        0.00000
  GD 一步的预测  前 5 个 = [ 0.2715  0.5954  1.2868 -0.1913 -0.223 ]
  注意力形式的预测 前 5 个 = [ 0.2715  0.5954  1.2868 -0.1913 -0.223 ]
  最大偏差 = 4.44e-16

例子从 1 个加到 128 个,误差从 0.90 掉到 0.0037。整个过程中权重的梯度一次都没算过。

后半段把"闭式解"换成了"梯度下降"。梯度下降的迭代是

$$\mathbf{w}_0 = \mathbf{0},\qquad \mathbf{w}_{t+1} = \mathbf{w}_t - \eta \nabla \tfrac12\|X\mathbf{w}_t - \mathbf{y}\|^2$$

一步之后 $\mathbf{w}_1 = \eta X^\top \mathbf{y} = \eta \sum_i \mathbf{x}_i y_i$,
于是对新 query 的输出是

$$\mathbf{w}_1^\top \mathbf{x}_q = \eta \sum_i \underbrace{(\mathbf{x}_q^\top \mathbf{x}_i)}_{\text{注意力权重}} \underbrace{y_i}_{\text{value}}$$

右边正是《深度学习》§8.2 里那个缩放点积注意力的形式(只不过这里没除以 $\sqrt{d_k}$、
value 直接取上下文里的 $y_i$,不是学出来的)。也就是说,
一层线性注意力可以恰好实现"对上下文做一步梯度下降",
输出里那两行"GD 一步的预测"和"注意力形式的预测"逐位相同,
偏差 $4.44\times10^{-16}$ 就是浮点误差本身。
上下文学习至少在一类任务上,就是"在前向里做优化"。

边界声明(重要):上面只证明了"一层线性注意力可以实现一步梯度下降"。
这不等于"真实的 Transformer 一定在跑梯度下降"。
论文(von Oswald et al., 2022, arXiv:2212.07677)的立场是:
这是一个很好的假设,在简单回归任务上训练出来的 Transformer 学到的权重
确实和这个构造吻合;但真实大模型里还有 MLP、LayerNorm、softmax 注意力、几十层堆叠,
它到底在算什么,至今没有公认答案。别把上面这段当成"ICL 的原理已被破解"。

2.4 边界与批评


3 规模定律(Scaling Laws)

3.1 要解决什么问题

在 2020 年之前,训练大模型是一件赌博:花几百万美元、跑几个月,
结果好不好要跑完才知道。规模定律要回答的是一个纯工程问题:

我有 $C$ 块钱的算力,该买多大的模型、喂多少数据,才能让损失最低?

如果损失和规模之间有可外推的规律,这个问题就能在纸上回答,
而不用真的把每个配置都训一遍。这就是这一节的全部动机。

3.2 Kaplan 2020:三件事都按幂律下降

OpenAI 的 Kaplan 等人(Scaling Laws for Neural Language Models, arXiv:2001.08361, 2020)
在 7 个数量级的范围内做了几百次训练,发现交叉熵损失 $L$(单位:nat,即自然对数下的
每 token 损失,见《自然语言处理》§3.3 关于困惑度的讨论,$L = \log\mathrm{PPL}$)
单独随三个量按幂律下降:

$$L(N) = \left(\frac{N_c}{N}\right)^{\alpha_N},\qquad L(D) = \left(\frac{D_c}{D}\right)^{\alpha_D},\qquad L(C) = \left(\frac{C_{\min}}{C}\right)^{\alpha_C}$$

逐项解释:

为什么这个形式重要:幂律在对数坐标下是一条直线。
这意味着你可以在小规模上做实验、画一条直线、外推到大模型。
这套结果真正的价值在于,它把"要不要花几千万训一个模型"变成了可计算的风险评估。

论文还给了联合式:

$$L(N, D) = \left[\left(\frac{N_c}{N}\right)^{\alpha_N/\alpha_D} + \frac{D_c}{D}\right]^{\alpha_D}$$

这个式子的结构值得看:括号里是两项相加,所以当 $N$ 很小或 $D$ 很小时,
损失由对应的那一项主导,这解释了为什么"数据和参数必须同步涨",
只涨一个会被另一个卡住。

Kaplan 的核心结论是:$N_{\text{opt}} \propto C^{0.73}$,$D_{\text{opt}} \propto C^{0.27}$。
翻译成人话:算力增加时,主要应该拿来把模型变大,数据只要慢慢加。
GPT-3(1750 亿参数,3000 亿 token,即 1.71 token/参数)就是按这个配方做的。

3.3 Chinchilla 2022:早期模型全都"训欠了"

DeepMind 的 Hoffmann 等人(Training Compute-Optimal Large Language Models,
arXiv:2203.15556, 2022,模型名 Chinchilla)重做了这个实验,结论是Kaplan 错了。

错在哪:Kaplan 的实验里,不同大小的模型用了同一套学习率调度,
而小模型在同样的 token 数下其实已经跑完、大模型还没跑完(或者说调度不匹配)。
这导致小模型的损失被高估,拟合出来的曲线就把最优解推向了大模型那一侧。

Chinchilla 的修正结论是:参数和数据应该按比例一起涨:

$$N_{\text{opt}} \propto C^{0.5},\qquad D_{\text{opt}} \propto C^{0.5}$$

论文的 Approach 1 和 Approach 2 给出 $(0.50, 0.50)$ 和 $(0.49, 0.51)$。
连起来就得到那条著名的经验规则:

$$\boxed{D_{\text{opt}} \approx 20\, N_{\text{opt}}}\qquad\text{(每参数约 20 个 token)}$$

证据:Chinchilla 用 700 亿参数 / 1.4 万亿 token(正好 20 token/参数),
在相同算力下超过了 2800 亿参数 / 3000 亿 token 的 Gopher。
换句话说,同样的钱,买"小模型 + 多数据"比买"大模型 + 少数据"划算。

3.4 实跑:把两个定律放在一起算

import numpy as np
from scipy.optimize import minimize_scalar

NC, DC = 8.8e13, 5.4e13          # Kaplan 的尺度常数
aN, aD = 0.076, 0.095            # Kaplan 的两个指数
E_, A_, B_ = 1.69, 406.4, 410.7  # Chinchilla Approach 3 拟合系数
al, be = 0.34, 0.28

L_kaplan = lambda N, D: ((NC/N)**(aN/aD) + DC/D)**aD
L_chinch = lambda N, D: E_ + A_/N**al + B_/D**be

print("      D        Kaplan L    Chinchilla L")
for D in [3e9, 1e10, 3e10, 1e11, 3e11, 1.4e12, 1e13]:
    print("  %9.2e    %8.4f    %8.4f" % (D, L_kaplan(1.3e9, D), L_chinch(1.3e9, D)))

C3 = 6.0*175e9*300e9
print("  C = 6 x 175e9 x 300e9 = %.3e FLOPs(GPT-3 论文自报 3.14e23)" % C3)
print("  GPT-3 的 D/N = %.2f tokens/参数" % (300e9/175e9))

print("     C(FLOPs)      N*          D*=20N*")
for C in [1e21, 1e22, 3.15e23, 1e24, 1e25]:
    N = np.sqrt(C/120.0)
    print("  %10.2e  %11.3e  %11.3e" % (C, N, 20*N))
Ns = np.sqrt(3.15e23/120.0)
print("  GPT-3 算力下:N* = %.1fB, D* = %.0fB tokens" % (Ns/1e9, 20*Ns/1e9))

def optimal(Lfun, C):
    f = lambda lN: Lfun(np.exp(lN), C/(6.0*np.exp(lN)))
    r = minimize_scalar(f, bounds=(np.log(1e5), np.log(1e14)), method="bounded")
    return np.exp(r.x)
for name, f in [("Kaplan    ", L_kaplan), ("Chinchilla", L_chinch)]:
    h = 1e-4
    N1 = optimal(f, 1e24*np.exp(-h)); N2 = optimal(f, 1e24*np.exp(h))
    print("  %s  d log N*/d log C = %.4f" % (name, (np.log(N2)-np.log(N1))/(2*h)))

实跑输出

      D        Kaplan L    Chinchilla L
   3.00e+09      2.6202      2.9256
   1.00e+10      2.4543      2.6647
   3.00e+10      2.3779      2.4923
   1.00e+11      2.3445      2.3554
   3.00e+11      2.3341      2.2649
   1.40e+12      2.3299      2.1769
   1.00e+13      2.3289      2.1078
  C = 6 x 175e9 x 300e9 = 3.150e+23 FLOPs(GPT-3 论文自报 3.14e23)
  GPT-3 的 D/N = 1.71 tokens/参数
     C(FLOPs)      N*          D*=20N*
    1.00e+21    2.887e+09    5.774e+10
    1.00e+22    9.129e+09    1.826e+11
    3.15e+23    5.123e+10    1.025e+12
    1.00e+24    9.129e+10    1.826e+12
    1.00e+25    2.887e+11    5.774e+12
  GPT-3 算力下:N* = 51.2B, D* = 1025B tokens
  Kaplan      d log N*/d log C = 0.5556
  Chinchilla  d log N*/d log C = 0.4516

图表逐段读:

  1. 第一张表:数据从 30 亿加到 10 万亿 token,Kaplan 的损失从 2.62 降到 2.33 后几乎不动了
    (最后三档只降了 0.015),而 Chinchilla 的式子还在持续下降(2.93 → 2.11)。
    这个差别反映的就是"数据能救多少"的分歧:Chinchilla 认为数据的边际收益比 Kaplan 认为的大得多。
  2. $C=6ND$ 核对:$6\times175\times10^9\times300\times10^9 = 3.150\times10^{23}$,
    GPT-3 论文自报 $3.14\times10^{23}$,误差 0.3%。这个公式
    (前向 $2ND$ + 反向 $4ND$,见《深度学习》§3 关于反向传播代价的讨论)
    在 7 个数量级上都够用。
  3. $N^*=\sqrt{C/120}$ 的来历:把 $C=6ND$ 与 $D=20N$ 联立,

$$C = 6N\cdot(20N) = 120N^2 \;\Longrightarrow\; N^* = \sqrt{C/120},\quad D^* = 20N^*$$

用 GPT-3 的算力 $3.15\times10^{23}$ 代进去,得到 $N^*\approx 51$B、$D^*\approx 1.0$T token。
而 GPT-3 实际是 175B / 300B,参数多了 2.4 倍,数据少了 71%。
这就是"GPT-3 训欠了"的量化表述。
4. 最后两行是指数的数值核对。我不直接引用论文的指数,而是从拟合式反推:
对 $\log N^*$ 与 $\log C$ 的曲线做有限差分。结果 Kaplan 式给 0.556,
Chinchilla 式给 0.452。
- 0.452 和 Chinchilla 正文口径 0.50 基本一致(差别来自 Approach 3 的拟合系数,见下)。
- 0.556 和 Kaplan 正文的 0.73 对不上。这不是我算错了,见下面的诚实说明。

⚠️ 一处必须如实标注的不自洽:

Kaplan 论文的联合式(用它的系数)解出来的最优指数是 0.556,不是正文口径的 0.73。
这个差别有来源:正文的 0.73 是从 $L(C)$ 那条单独拟合的曲线推的,
而联合式在数据量很大时拟合得并不好。两套说法出自同一篇论文,本身就不完全自洽。

Chinchilla 也有一模一样的问题:用它的 Approach 3 系数算 GPT-3 算力下的最优分配,
得到 $N^*\approx24.5$B、$D^*\approx2.1$T,$D^*/N^*\approx 87$,
和它自己的"20 token/参数"招牌对不上。
Epoch AI 在 2024 年重新复现了这篇论文(Chinchilla Scaling: A replication attempt,
arXiv:2404.10102),发现是因为拟合时优化器没跑完就停了;重新拟合后
$\beta\approx0.45$,$D^*/N^*$ 回到 20~26 区间,和 Approach 1、2 一致。

所以本页的处理是:把"$N,D$ 大致等比例缩放、约 20 token/参数"当作结论
(这是 Approach 1、2 和后续复现都支持的),把 Approach 3 的那组系数只当参考。
你在论文里看到任何一组具体系数,都要先问它是怎么拟合的。

3.5 边界与批评


4 GPT 系列的技术路线

这一节按时间顺序走一遍 GPT-1 → GPT-4。每一步都要回答同一个问题:它解决了上一代的什么问题?
(层数、头数、注意力怎么算这些架构细节见《深度学习》§8,这里不重复。)

4.1 GPT-1(2018):预训练 + 微调

Radford, Narasimhan, Salimans & Sutskever, Improving Language Understanding by
Generative Pre-Training
, OpenAI 技术报告, 2018。这篇没有 arXiv 编号,
是 OpenAI 直接发布的技术报告(引用时写 "OpenAI Technical Report")。

规模:1.17 亿参数,12 层,12 头,$d_{\text{model}}=768$,上下文 512。
数据:BooksCorpus,约 7000 本未出版的书。

它解决的问题:2018 年之前,每个 NLP 任务都是从随机初始化开始训,
需要大量标注数据。GPT-1 提出两阶段:

阶段一:无监督预训练    用海量无标注文本,训练"预测下一个词"
                        (这一阶段不需要任何人工标注)
                        ↓
阶段二:有监督微调      拿少量标注数据,改全部权重,适配具体任务
                        (这一步还是要标注,还是要改权重)

它没解决的问题:每个任务还是要一份独立的权重和一份标注数据。
你有 10 个任务,就要存 10 份 GPT-1。这跟 BERT 的思路没有本质区别,
只是把"随机初始化"换成了"语言模型初始化"。

4.2 GPT-2(2019):zero-shot,但当时没人信

Radford et al., Language Models are Unsupervised Multitask Learners,
OpenAI 技术报告, 2019。同样没有 arXiv 编号。

规模:15 亿参数(最大版本),48 层,$d_{\text{model}}=1600$,上下文 1024。
数据:WebText,40GB,来自 4500 万个 Reddit 高赞帖子外链的网页。

它想解决的问题:既然语言模型在预测下一个词时,
必须隐式地"会"翻译、总结、问答,那能不能干脆不再微调,直接把任务写成一段文本?

比如做翻译,不给它任何标注,只给它一段:

把下面的英文翻译成中文:
"the cat sat on the mat" -> "猫坐在垫子上"
"the dog ran" -> 

GPT-2 的论文标题里那两个字 "Unsupervised Multitask" 就是这个意思:
多任务学习可以在"预测下一个词"这一个目标里隐式地发生。

它没解决的问题:效果还不够好。 GPT-2 的 zero-shot 在许多任务上
明显不如当时的微调模型。所以这篇论文当时更多被当成一个"有趣的现象",
而不是范式转变。它的历史意义是提了一个赌注,然后等 GPT-3 把赌注兑现。
(另一个值得一提的细节:OpenAI 最初以"担心滥用"为由分阶段发布这个模型,
先放 1.24 亿的小版本,几个月后才放全量。这是"模型发布策略"进入公共讨论的起点。)

4.3 GPT-3(2020):in-context learning

Brown et al., Language Models are Few-Shot Learners,
NeurIPS 2020, arXiv:2005.14165。

规模:1750 亿参数,96 层,$d_{\text{model}}=12288$,96 头,上下文 2048。
数据:3000 亿 token(Common Crawl 过滤后 + WebText2 + 两套书 + 英文维基,
按质量加权混合,不是按原始体量)。
算力:论文自报约 $3.14\times10^{23}$ FLOPs(§2 实跑核对过,$6ND$ 给出 $3.15\times10^{23}$)。

它解决的问题:GPT-2 的 zero-shot 效果不行,GPT-3 把规模拉到 100 倍以上,
few-shot 就真的能用了(§1.3 的实跑演示了它的机制)。

为什么这是"质变"而不是"量变":GPT-3 的架构和 GPT-2 几乎一样,
训练目标也完全一样。变的只有规模。而 in-context learning 这个能力
没有出现在任何一版的设计文档里,它是规模变大的副产品。
这是"规模能带来质变"的第一个硬证据,也是 §5 那场争论的起点。

它的代价:提示变长 → 每次推理都更贵;不能靠微调提高上限;
对提示的措辞、例子顺序极其敏感;而且能力上限被上下文长度锁死
(2048 token,放不下太多例子和长文档)。

4.4 InstructGPT(2022):RLHF,把"续写器"变成"助手"

Ouyang et al., Training language models to follow instructions with human feedback,
arXiv:2203.02155, 2022(OpenAI)。

它解决的问题(这一步最关键):GPT-3 的训练目标是"预测下一个词"。
你输入"法国的首都是哪里?",它完全可能续写出"德国的首都是哪里?",
因为在训练数据里,这两个问句经常挨在一起。
模型没有做错任何事,它在完美地执行 "续写" 这个目标,只是这不是你要的。

这个问题有自己的名字:misalignment(不对齐):模型在做我们训练它做的事,
不是我们想让它做的事。

做法:三步 RLHF,13k 条 SFT 数据 + 33k 条偏好比较 + 31k 条 PPO 提示。§4 整节讲这个。

结果(论文报告的数字):
- 13 亿参数的 InstructGPT,在人工偏好评测里胜过 1750 亿参数的 GPT-3,
参数少 100 倍,人更喜欢它的回答。
- 175B InstructGPT 对 175B GPT-3 的胜率约 85%。

能力(capability)和对齐(alignment)是两个独立的轴。
参数量决定"会不会",RLHF 决定"愿不愿意按你要的方式做"。这两件事不是同一件事,
所以"更大的模型会自动更听话"是错的。

(ChatGPT 就是这条路线上的产品:GPT-3.5 系列 + RLHF + 对话界面。
具体版本号和训练细节 OpenAI 没有完整公开,我不给不确定的细节。)

4.5 GPT-4(2023):能力跃升,但技术细节全部不公开

OpenAI, GPT-4 Technical Report, arXiv:2303.08774, 2023-03。

公开的部分:
- 多模态:接受图像 + 文本输入,输出文本。
- 评测成绩(论文自报):模拟律师资格考试进入前 10%(GPT-3.5 是后 10%);
5-shot MMLU 86.4%;5-shot CoT GSM-8K 92.0%;0-shot HumanEval 67.0%。
- 一个重要观察:各项考试能力主要来自预训练。
论文比较了基础模型和 RLHF 后的模型在多选题上的表现:
73.7% vs 74.0%,几乎没有差别。

不公开的部分:架构、参数量、训练算力、数据构成、训练方法,报告里真的没写,
理由是"竞争格局和安全考虑"。报告的大部分篇幅给了安全性测试、红队测试和 RLHF 部分。

那个观察说明,RLHF 改的主要是"怎么说话",不是"会不会"。
所以把 RLHF 理解成"让模型变聪明"是错的;它是"让模型的行为更符合人类偏好"。

4.6 一条线索

代 新增的东西 解决的上一代的问题 新的代价
GPT-1 预训练 + 微调 从零训练需要太多标注 每个任务一份权重
GPT-2 zero-shot 的赌注 每任务一份权重 效果不够好
GPT-3 in-context learning 效果不够好(靠规模) 推理贵、受上下文长度限制、对提示敏感
InstructGPT RLHF 模型不按指令做事 依赖标注员、奖励模型可被"作弊"
GPT-4 多模态 + 能力跃升 单模态 细节不公开,无法复现

贯穿这条线的一个更大的背景:Rich Sutton 在 2019 年的短文
The Bitter Lesson 里说:AI 的历史上,依赖人类先验知识的方法,
最后总会被"通用方法 + 更多算力"超过。
GPT 系列从 1 到 3 的路,几乎就是这条 lesson 的一次现场演示。
每次有人想加人类设计的结构,最后都是"把它变大 + 喂更多数据"赢。
(注意:Sutton 那篇是博客短文,不是同行评议论文,引用时要说清楚。)


5 RLHF(人类反馈强化学习)⭐

这一节把 RLHF 的三步拆开:监督微调(SFT)、奖励模型(RM)、PPO。

5.1 为什么需要 RLHF:因为"好回答"没有标准答案

假设你要用监督学习教模型"好好回答问题"。你需要什么?
每个问题配一个标准答案。但问题是:

"用一句话解释什么是熵",这句话有标准答案吗?

没有。可以有一万种都正确的写法。监督学习要求一个"唯一正确"的输出,
而这个任务本质上不存在唯一正确。问题不在数据够不够,而在任务的形态不对。

但有另一件事人是能做的:看到两个回答,说哪个更好。
这不需要你写出标准答案,只需要你能比较。

RLHF 的全部洞察就是这一句:把"打分"换成"排序",
再把"排序"变成可以反向传播的目标函数。

起源:这个思路最早由 Christiano 等人在 2017 年提出
(Deep Reinforcement Learning from Human Preferences, NeurIPS 2017, arXiv:1706.03741),
当时用在 Atari 游戏上,人类只看两段游戏片段,说哪段好,模型从比较中学习。
InstructGPT 把它搬到了语言模型上。

5.2 第一步:SFT(监督微调)

做了什么:雇标注员,给 13k 条提示(prompt),每条让他们手写一个理想的回答。
然后用标准的交叉熵(就是《自然语言处理》§3.3 的困惑度那个损失)
微调 GPT-3,跑 16 轮。

这一步解决什么:把模型从"续写器"变成"回答器"。它学会了"看到问题要回答"
这个格式。

这一步没解决什么:它只有"一个"标准答案。
同一个问题,标注员写了一种好回答,模型就只学那一种。
"回答得更好"这件事,SFT 学不到,因为它没有"更好"这个概念,只有"对/不对"。

5.3 第二步:奖励模型(RM):本节的公式核心

数据怎么来:

  一条提示 x
      │
      ├──► SFT 模型生成 K = 4 ~ 9 个不同的回答  y_1, y_2, ..., y_K
      │
      └──► 标注员把这 K 个回答从最好到最差排个序
              →  排序拆成 C(K,2) 个"成对比较":(y_w 比 y_l 好)

论文用了 33k 条提示。每条提示的排序都拆成若干对 $(y_w, y_l)$,
其中下标 $w$ = winner(被排得更靠前的),$l$ = loser。

模型结构:拿 SFT 模型,把最后一层(输出词表的那个)换成一个
输出单个标量的头。于是 $r_\theta(x,y) \in \mathbb{R}$。
给它一个提示和一个回答,它吐出一个分数。

目标函数(Bradley-Terry 模型):

$$\mathcal{L}(\theta) \;=\; -\,\mathbb{E}_{(x,\,y_w,\,y_l)\sim \mathcal{D}} \left[\log \sigma\Big(r_\theta(x, y_w) - r_\theta(x, y_l)\Big)\right]$$

逐项解释:

这个损失和逻辑回归是同一个东西。把它当成"用 $(y_w,y_l)$ 这对样本,
训练一个二分类器判断谁更好",完全等价。

梯度(记 $d = r_w - r_l$):

$$\frac{\partial \mathcal{L}}{\partial r_w} = -\big(1 - \sigma(d)\big),\qquad \frac{\partial \mathcal{L}}{\partial r_l} = +\big(1 - \sigma(d)\big)$$

方向解读:括号里恒为正,所以对 $r_w$ 的梯度是负的(梯度下降把它往上推),
对 $r_l$ 是正的(往下压)。而且 $d$ 越大(模型已经判对了),
$\sigma(d)\to1$,梯度 $\to 0$,已经判对的样本就自动不再出力。
这和《自然语言处理》§9 第 2 题里负采样的梯度行为是同一个模式。

实跑核对:

import numpy as np
np.set_printoptions(precision=4, suppress=True)

sigmoid = lambda x: 1.0/(1.0+np.exp(-x))
rm_loss = lambda rw, rl: -np.log(sigmoid(rw - rl))          # L = -log σ(r_w - r_l)

rw0, rl0, eps = 0.7, -0.2, 1e-6
num_w = (rm_loss(rw0+eps, rl0) - rm_loss(rw0-eps, rl0))/(2*eps)
num_l = (rm_loss(rw0, rl0+eps) - rm_loss(rw0, rl0-eps))/(2*eps)
ana_w = -(1.0 - sigmoid(rw0 - rl0))                          # 解析梯度
print("L = -log sigmoid(r_w - r_l) = %.6f" % rm_loss(rw0, rl0))
print("dL/dr_w  解析 %+.8f   数值 %+.8f" % (ana_w, num_w))
print("dL/dr_l  解析 %+.8f   数值 %+.8f" % (-ana_w, num_l))

rng = np.random.default_rng(0)
TRUE_W = np.array([1.5, -0.8, 2.0])
Fw = rng.normal(size=(400, 3))
Fl = Fw + rng.normal(scale=1.2, size=(400, 3))
keep = (Fw@TRUE_W + rng.normal(scale=0.5, size=400)
        > Fl@TRUE_W + rng.normal(scale=0.5, size=400))
Fw, Fl = Fw[keep], Fl[keep]
w = np.zeros(3)
for _ in range(400):
    p = sigmoid(Fw@w - Fl@w)
    grad = (-(1-p)[:, None]*Fw + (1-p)[:, None]*Fl).mean(axis=0)
    w -= 0.05*grad
print("%d 对比较,真实权重 %s" % (len(Fw), TRUE_W))
print("训出 w = %s   成对准确率 %.1f%%" % (w, ((Fw@w) > (Fl@w)).mean()*100))

实跑输出

L = -log sigmoid(r_w - r_l) = 0.341154
dL/dr_w  解析 -0.28905050   数值 -0.28905050
dL/dr_l  解析 +0.28905050   数值 +0.28905050
218 对比较,真实权重 [ 1.5 -0.8  2. ]
训出 w = [ 1.5318 -0.7295  1.8591]   成对准确率 94.0%

解析梯度和数值梯度逐位相同,推导正确。
下面那段是一个真的训出来的奖励模型:用 218 对比较、手写梯度下降,
训出的权重 $[1.53, -0.73, 1.86]$ 和真实权重 $[1.5, -0.8, 2.0]$ 方向一致、
相对大小一致,但绝对值不同,恰好印证了前面那句话:
奖励的绝对大小没有意义,只有相对大小有意义。

两个工程细节(论文里的做法):
1. 奖励模型只有 6B。论文说 175B 的奖励模型训练不稳定。
所以 175B 的策略,是由一个 6B 的"裁判"在打分。裁判比选手弱,
这是 RLHF 的一个结构性弱点。
2. 奖励归一化:训练前先把偏置调到"标注员手写的示范回答平均得 0 分"。
为什么?因为下一步的 KL 罚项需要一个有意义的尺度,
而奖励的零点本来是不可辨识的,必须人为锚定一个。

5.4 第三步:PPO,用强化学习优化策略

设定:把 SFT 模型当作策略 $\pi^{SFT}$(输入提示、输出回答的分布),
奖励模型当作环境(给出标量奖励)。我们要找一个新策略 $\pi^{RL}_\phi$,
让奖励模型的评分尽可能高。

目标函数(PPO-ptx 版本的核心项):

$$\max_{\phi}\;\; \mathbb{E}_{x\sim\mathcal{D},\; y\sim\pi^{RL}_\phi(\cdot\mid x)} \left[\; \underbrace{r_\theta(x,y)}_{\text{拿高分}} \;-\; \beta\, \underbrace{\log \frac{\pi^{RL}_\phi(y\mid x)}{\pi^{SFT}(y\mid x)}}_{\text{别跑太远}} \;\right]$$

逐项解释:

为什么必须有 KL 罚项:因为奖励模型只是人类偏好的有损代理。
它是用 33k 条比较训出来的一个 6B 模型,它不等于人类偏好。
如果你只最大化 $r_\theta$,模型会去找这个代理的漏洞,
生成那些在 RM 眼里分极高、但在人看来荒谬的回答。
这叫 reward hacking(奖励黑客),也叫 Goodhart 定律:
"当一个度量变成目标,它就不再是好度量。"

这个目标可以解析求解:

把目标写成 $\max_\pi \; \mathbb{E}_\pi[r] - \beta\,\mathrm{KL}(\pi\,\|\,\pi^{SFT})$,
用拉格朗日乘子对每个 $\pi(y)$ 求导并令其为零,得到

$$\boxed{\;\pi^*(y\mid x) \;=\; \frac{1}{Z(x)}\,\pi^{SFT}(y\mid x)\, \exp\!\left(\frac{1}{\beta}\,r_\theta(x,y)\right),\qquad Z(x)=\sum_{y}\pi^{SFT}(y\mid x)\exp\!\left(\frac{r_\theta(x,y)}{\beta}\right)\;}$$

怎么读这个解:它说最优策略就是把参考策略按奖励做指数倾斜。
奖励每高出一点,概率就乘一个 $\exp(r/\beta)$。
$\beta$ 越小,指数越陡,概率越集中到高奖励的输出上。

实跑验证闭式解,并看 $\beta$ 的作用:

import numpy as np
from scipy.optimize import minimize

r = np.array([0., 1., 2., 3., 5.])                  # 5 个输出各自的奖励
pi_ref = np.array([0.30, 0.25, 0.20, 0.15, 0.10])   # 参考策略(SFT 模型)
r = r - (pi_ref*r).sum()                            # 奖励减均值,规范化

kl = lambda p, q: float((p[p > 0]*np.log(p[p > 0]/q[p > 0])).sum())
def neg_obj(logits, beta):                          # 目标取负,交给 minimize
    z = logits - logits.max(); p = np.exp(z); p /= p.sum()
    obj = (p*r).sum() - beta*kl(p, pi_ref)          # E[r] - beta*KL
    g = p*(r - (p*r).sum()) - beta*p*(np.log(p/pi_ref) - kl(p, pi_ref))
    return -obj, -g

print("奖励 r = %s   pi_ref = %s" % (r, pi_ref))
print(" beta     pi*(闭式解)                       E[r]     KL")
for beta in [10.0, 2.0, 0.5, 0.1, 0.02]:
    wv = np.exp(r/beta); ps = pi_ref*wv; ps /= ps.sum()      # 闭式解
    z = minimize(lambda z: neg_obj(z, beta), np.zeros(5), jac=True,
                 method="BFGS", options={"gtol": 1e-12}).x
    z = z - z.max(); pn = np.exp(z); pn /= pn.sum()          # 数值解
    print(" %5.2f  %s  %6.3f  %7.4f   (数值解偏差 %.1e)"
          % (beta, np.array2string(np.round(ps, 4), precision=4, floatmode='fixed'),
             (ps*r).sum(), kl(ps, pi_ref), np.abs(pn-ps).max()))

实跑输出

奖励 r = [-1.6 -0.6  0.4  1.4  3.4]   pi_ref = [0.3  0.25 0.2  0.15 0.1 ]
 beta     pi*(闭式解)                       E[r]     KL
 10.00  [0.2525 0.2326 0.2056 0.1704 0.1388]   0.249   0.0127   (数值解偏差 8.4e-11)
  2.00  [0.0953 0.1310 0.1728 0.2137 0.3872]   1.454   0.3805   (数值解偏差 6.2e-12)
  0.50  [1.0000e-04 8.0000e-04 4.8000e-03 2.6600e-02 9.6770e-01]   3.329   2.1268   (数值解偏差 3.6e-12)
  0.10  [0.0000 0.0000 0.0000 0.0000 1.0000]   3.400   2.3026   (数值解偏差 4.5e-10)
  0.02  [0.0000 0.0000 0.0000 0.0000 1.0000]   3.400   2.3026   (数值解偏差 4.5e-13)

逐行读这张表:

最后两行就是 reward hacking 的样子:策略已经完全崩成一个确定性输出,
而"奖励"这个数字看起来完美(3.400,是理论最大值)。
如果那个 3.4 分的输出其实是一个钻了 RM 空子的荒谬回答,你在指标上完全看不出来。

PPO-ptx:论文还试了一个变体,在 PPO 的目标里额外混入预训练梯度。
为什么?因为纯 RLHF 之后,模型在标准 NLP 基准上会掉分,
这个现象叫 alignment tax(对齐税)。混入预训练梯度就是"交税"的方式。
论文里除非特别说明,InstructGPT 指的都是这个 PPO-ptx 版本。

5.5 边界与批评


6 涌现能力(emergent abilities)与它的争议 ⭐

这一节讲的是一种思维陷阱。

6.1 Wei et al. 2022:能力在规模上"突然出现"

Wei, Tay, Bommasani, Raffel, Zoph, Borgeaud, Yogatama, Bosma, Zhou, Metzler, et al.,
Emergent Abilities of Large Language Models,
TMLR 2022, arXiv:2206.07682。

定义:一个能力是涌现的,如果它
(1)在小模型上不存在,(2)在大模型上存在,
(3)而且不能通过外推小模型的性能来预测。

第(3)条是关键。如果一个小模型准确率 5%、大一点的 8%、再大一点的 12%,
你可以外推,这不叫涌现。涌现是一直贴着 0,然后突然跳到 30%。

论文在 BIG-Bench 等基准上找到了大量这种形状的曲线:三位数算术、
音译、多步推理、某些符号操作任务。而且是在 100 亿参数附近"跨过门槛"。

为什么这个说法重要(不只是学术趣味):如果真的存在不可预测的涌现,
那意味着安全评估不能只做小模型然后外推,你无法预知哪个规模会冒出哪种能力,
包括危险的能力。这是一个实打实的安全论证。

6.2 Schaeffer et al. 2023:这可能是个"海市蜃楼"

Schaeffer, Miranda & Koyejo, Are Emergent Abilities of Large Language Models a Mirage?,
NeurIPS 2023(Outstanding Paper), arXiv:2304.15004。

核心论断:对固定的任务和模型家族,涌现是研究者选择的指标造成的。
- 用非线性、不连续的指标 → 看起来涌现。
- 用线性、连续的指标 → 平滑、可预测,完全没有突变。

证据:他们统计了 BIG-Bench 上声称的涌现能力,发现 超过 92% 都只用两种指标:
Exact String Match(精确字符串匹配)和 Multiple Choice Grade(多选题评分),
而这两个都是非连续指标。在 39 个指标里,只有 4 个会产生涌现现象。

机制:

设一个模型在单个 token 上正确的概率是 $a(N)$,$a$ 随规模 $N$ 平滑上升。
一个需要 $L$ 个 token 全对的答案,它的 Exact Match 准确率是

$$\mathrm{EM}(N) = \underbrace{a(N)^L}_{\text{所有 } L \text{ 个 token 都对}}$$

$a(N)$ 是平滑的,但 $a(N)^L$ 不是。当 $a = 0.5$、$L=8$ 时,$a^L = 0.0039$,
在图上看不见。当 $a$ 涨到 0.85 时,$a^L = 0.27$,突然就看得见了。
中间只隔了 $a$ 从 0.5 到 0.85 的变化,而这条曲线本身没有任何拐点。

第二个机制:测试集的分辨率。如果测试集只有 200 题,
准确率的最小非零刻度是 $1/200 = 0.005$。
真实准确率 0.003 的模型,测出来必然是 0,看起来"完全不会"。
于是"从 0 到有"的那一段被人为压平了,跳变显得更陡。

6.3 实跑:亲眼看着"涌现"被制造出来

下面这段代码里,$a(N)$ 是一条完全光滑的 S 曲线,没有阈值、没有拐点、没有相变。
我们只改一件事:用哪个指标去测它。

import numpy as np

a_of = lambda N: 0.10 + 0.85/(1.0 + np.exp(-(np.log10(N) - 9.0)/1.2))
sizes = [1e6, 3e6, 1e7, 3e7, 1e8, 3e8, 1e9, 3e9, 1e10, 3e10, 1e11, 3e11]

print("   N(参数)   a(N)     EM(L=1)  EM(L=2)   EM(L=4)   EM(L=8)   EM(L=16)")
for N in sizes:
    a = a_of(N)
    print("  %6.0e  %.4f  %s" % (N, a, "".join("%9.2e" % a**L for L in [1, 2, 4, 8, 16])))

print()
print("   N(参数)   log a(N)   Δ 相对于上一行")
prev = None
for N in sizes:
    v = float(np.log(a_of(N)))
    print("  %6.0e  %9.6f  %s" % (N, v, "" if prev is None else "%+.6f" % (v-prev)))
    prev = v

print()
print("   N(参数)   EM(L=8)    200 题期望答对   P(一题都没答对)")
for N in sizes:
    e = a_of(N)**8
    print("  %6.0e   %9.2e     %8.4f      %8.5f" % (N, e, e*200, (1-e)**200))

实跑输出

   N(参数)   a(N)     EM(L=1)  EM(L=2)   EM(L=4)   EM(L=8)   EM(L=16)
   1e+06  0.1645   1.64e-01 2.71e-02 7.32e-04 5.36e-07 2.87e-13
   3e+06  0.1925   1.93e-01 3.71e-02 1.37e-03 1.89e-06 3.57e-12
   1e+07  0.2350   2.35e-01 5.52e-02 3.05e-03 9.31e-06 8.67e-11
   3e+07  0.2865   2.87e-01 8.21e-02 6.74e-03 4.54e-05 2.06e-09
   1e+08  0.3575   3.57e-01 1.28e-01 1.63e-02 2.67e-04 7.12e-08
   3e+08  0.4338   4.34e-01 1.88e-01 3.54e-02 1.26e-03 1.58e-06
   1e+09  0.5250   5.25e-01 2.76e-01 7.60e-02 5.77e-03 3.33e-05
   3e+09  0.6084   6.08e-01 3.70e-01 1.37e-01 1.88e-02 3.52e-04
   1e+10  0.6925   6.93e-01 4.80e-01 2.30e-01 5.29e-02 2.80e-03
   3e+10  0.7579   7.58e-01 5.74e-01 3.30e-01 1.09e-01 1.18e-02
   1e+11  0.8150   8.15e-01 6.64e-01 4.41e-01 1.95e-01 3.79e-02
   3e+11  0.8543   8.54e-01 7.30e-01 5.33e-01 2.84e-01 8.05e-02

   N(参数)   log a(N)   Δ 相对于上一行
   1e+06  -1.804970  
   3e+06  -1.647481  +0.157489
   1e+07  -1.448005  +0.199476
   3e+07  -1.250000  +0.198004
   1e+08  -1.028621  +0.221379
   3e+08  -0.835069  +0.193552
   1e+09  -0.644357  +0.190712
   3e+09  -0.496932  +0.147425
   1e+10  -0.367446  +0.129485
   3e+10  -0.277224  +0.090223
   1e+11  -0.204615  +0.072609
   3e+11  -0.157503  +0.047112

   N(参数)   EM(L=8)    200 题期望答对   P(一题都没答对)
   1e+06    5.36e-07       0.0001       0.99989
   3e+06    1.89e-06       0.0004       0.99962
   1e+07    9.31e-06       0.0019       0.99814
   3e+07    4.54e-05       0.0091       0.99096
   1e+08    2.67e-04       0.0534       0.94803
   3e+08    1.26e-03       0.2510       0.77789
   1e+09    5.77e-03       1.1543       0.31424
   3e+09    1.88e-02       3.7542       0.02260
   1e+10    5.29e-02      10.5777       0.00002
   3e+10    1.09e-01      21.7698       0.00000
   1e+11    1.95e-01      38.9158       0.00000
   3e+11    2.84e-01      56.7294       0.00000

三段输出,三个结论:

  1. 第一张表:看 EM(L=1) 那一列,它就是 $a(N)$ 本身,
    从 0.16 平滑爬到 0.85。这里看不出任何"涌现"。
    再看 EM(L=16) 那一列:从 $2.87\times10^{-13}$ 到 $8.05\times10^{-2}$,
    跨了 11 个数量级;而中间 (比如 $N=10^9$) 它只有 $3.33\times10^{-5}$,
    在图上就是 0。如果你画这张图,你会说"16 位算术能力在 $10^{10}$ 附近突然涌现"。
    但底层那条 $a(N)$ 从头到尾没有任何突变。
  2. 第二张表:把指标换成 $\log a(N)$,斜率从 $+0.16$ 到 $+0.22$ 再降到 $+0.047$,
    全程连续、单调、可外推。"涌现"消失了。
  3. 第三张表:加上 200 题的测试集分辨率。$N=3\times10^8$ 时真实 EM 是 0.00126,
    200 题里一题都答不对的概率是 77.8%,也就是说,这个模型有近八成的概率
    被测成"完全不会"。$N=10^9$ 时这个概率还有 31%。小模型那一段被压成了平地,
    台阶因此显得更陡。

6.4 这一课:指标决定结论

先说清楚双方各自对在哪:

为什么这一课对你特别重要:因为它和你在《实验方法论》里学的
"指标决定结论"是同一个东西,只是换了个场景。

同一个底层过程的平滑变化,被一个非线性指标放大成了台阶。

实践建议:以后看到任何"涌现"图,先问三个问题。

  1. 用的是什么指标? 是连续的(困惑度、对数似然、token 准确率)
    还是不连续的(Exact Match、全对才算对)?
  2. 换成连续指标,现象还在吗? 如果换完就平滑了,那"涌现"很可能是指标的产物。
  3. 测试集多大? 小于 1000 题的测试集,在低准确率区间没有分辨率。
    那里所有的"0"都不可信。

最后一个诚实的补充:Schaeffer 的结论不是"大模型不可能有涌现"。
它说的是"已有的涌现声称可能是指标造成的海市蜃楼"。
真正意义上的相变(如果存在)需要更强的证据。
这个问题到今天(截至本书写作时)仍然没有定论。


7 提示工程与思维链(CoT)

7.1 few-shot:把任务写进提示

这一块在 §1.2、§1.3 已经讲清了机制(前向里的最小二乘),这里只补工程要点:

为什么这一节标了 *(创新创业思维):提示工程是唯一一种不需要训练、
不需要数据标注、不需要 GPU 的"改造模型行为"的手段。
它的边际成本接近零,所以是小团队能做出差异化的地方。
但它也没有护城河,你的提示词别人复制走就能用。
这是这门课里"创业"和"技术"最直接冲突的一个点。

7.2 思维链(Chain-of-Thought, CoT)

Wei, Wang, Schuurmans, Bosma, Ichter, Xia, Chi, Le & Zhou,
Chain-of-Thought Prompting Elicits Reasoning in Large Language Models,
NeurIPS 2022, arXiv:2201.11903。

做法:在 few-shot 的例子里,不只给答案,把中间推理步骤也写出来。

【普通 few-shot】
  问:食堂有 23 个苹果,用掉 20 个,又买了 6 个,现在几个?
  答:9

【CoT】
  问:食堂有 23 个苹果,用掉 20 个,又买了 6 个,现在几个?
  答:食堂原来有 23 个。用掉 20 个,剩 23 - 20 = 3 个。
      又买了 6 个,3 + 6 = 9 个。答案是 9。

三个关键实验发现(Wei et al. 报告):

  1. 效果:PaLM 540B 在 GSM8K(小学数学应用题)上,
    只用 8 个 CoT 样例,准确率从 约 17.9% 提到约 56.5%,
    超过了当时专门微调过的 GPT-3 加验证器。
    (口径说明:不同来源引用的数字略有出入,PaLM 论文自身报的是 17% → 54%,
    差别来自是否外挂计算器。总之是三四倍的提升,不是几个百分点。)
  2. 只有大模型才受益:参数量小于约 100B 时,CoT 要么没用,要么反而更差,
    小模型会生成"读起来通顺、但逻辑不通"的推理链,然后照着错的链得出错的答案。
    所以这又是一个"涌现"断言,它同样要接受 §5 那套质疑。
  3. 只有多步任务才受益:GSM8K(多步)提升巨大,
    而 SingleOp(单步运算)几乎没有提升,个别设置下还略降。

为什么是"指数级"而不是"常数级"的提升,用可跑代码说清:

设一个问题需要 $m$ 步。不用 CoT 时,模型必须把 $m$ 步全部塞进一次前向里;
中间结果没有"落脚点"(没有写出来的 token 可以回头参照),
所以每步做对的概率退化到一个较低的值 $q$,整体准确率 $\approx q^m$。
用 CoT 时,每一步的结果被写成 token 反馈回输入,下一步是一次新的前向,
每步都能"看见"前面的结果,每步准确率维持在 $p$,整体准确率 $= p^m$。

取 $p=0.95$(用了 CoT)、$q=0.55$(没有中间结果的退化值):

from math import comb

print("   步数 m   直接答 q^m   加 CoT p^m   倍数")
for m in [1, 2, 3, 4, 6, 8, 12]:
    a, b = 0.55**m, 0.95**m
    print("   %4d   %10.4f   %10.4f   %7.1fx" % (m, a, b, b/a))

maj = lambda k, c: sum(comb(k, i)*(c**i)*((1-c)**(k-i)) for i in range(k//2+1, k+1))
ks = [1, 3, 5, 9, 21, 51, 101]
print()
print("   每条链准确率 c |" + "".join("%8s" % ("k=%d" % k) for k in ks))
for c in [0.3, 0.4, 0.45, 0.5, 0.565, 0.7]:
    print("      c = %.3f    |" % c + "".join("%8.3f" % maj(k, c) for k in ks))

实跑输出

   步数 m   直接答 q^m   加 CoT p^m   倍数
      1       0.5500       0.9500       1.7x
      2       0.3025       0.9025       3.0x
      3       0.1664       0.8574       5.2x
      4       0.0915       0.8145       8.9x
      6       0.0277       0.7351      26.6x
      8       0.0084       0.6634      79.2x
     12       0.0008       0.5404     705.2x

   每条链准确率 c |     k=1     k=3     k=5     k=9    k=21    k=51   k=101
      c = 0.300    |   0.300   0.216   0.163   0.099   0.026   0.001   0.000
      c = 0.400    |   0.400   0.352   0.317   0.267   0.174   0.074   0.021
      c = 0.450    |   0.450   0.425   0.407   0.379   0.321   0.236   0.156
      c = 0.500    |   0.500   0.500   0.500   0.500   0.500   0.500   0.500
      c = 0.565    |   0.565   0.597   0.621   0.656   0.728   0.826   0.906
      c = 0.700    |   0.700   0.784   0.837   0.901   0.974   0.999   1.000

上半张表(CoT 为什么有用):步数从 1 涨到 12,直接答的准确率从 0.55 崩到 0.0008,
加 CoT 只从 0.95 降到 0.54,倍数从 1.7 倍涨到 705 倍。
而且注意:$m=1$ 时倍数只有 1.7,这正好解释了为什么单步任务看不出 CoT 的好处。
CoT 不是提示词技巧,它改变的是模型需要在一层前向里完成多少计算。

变体:zero-shot CoT。Kojima et al.(Large Language Models are Zero-Shot Reasoners,
NeurIPS 2022, arXiv:2205.11916)发现,连例子都不用给,
只要在问题后面加一句 "Let's think step by step",就能拿到相当一部分 CoT 的收益。
这个发现的实用价值很高(省掉了写例子的功夫),但它也提示:
CoT 起作用的机制里,可能有相当一部分只是"让模型多生成一些 token"。

7.3 自洽性(self-consistency)

Wang, Wei, Schuurmans, Le, Chi, Narang, Chowdhery & Zhou,
Self-Consistency Improves Chain of Thought Reasoning in Language Models,
ICLR 2023, arXiv:2203.11171。

做法:不用贪婪解码取那一条推理链,而是采样 k 条不同的链,
对最终答案做多数投票。

为什么这在数学上会有用:如果一个复杂问题有多条推理路径都通向同一个正确答案,
那么"多条路径答案一致"就是一个置信度信号。
反过来,如果模型在瞎猜,k 条链会给出 k 个不同的答案,投票投不出什么。

如果每条链独立地以概率 $c$ 答对,$k$ 条链多数投票的准确率是

$$P_k = \sum_{i > k/2} \binom{k}{i}\, c^i (1-c)^{k-i}$$

这个公式是二项分布的上尾。它有一个著名的推论(孔多塞陪审团定理):
只要 $c > 0.5$,$k$ 越大 $P_k$ 越接近 1;只要 $c < 0.5$,$k$ 越大 $P_k$ 越接近 0。

下半张表(上一段代码的输出)把这个定理摊开了:

现实检查(必须说清):上表假设 k 条链相互独立。
但真实模型的多条链共享同一批权重、同样的偏见,错误是相关的。
所以真实收益比这张表小得多。对照 Wang et al. 的实际报告:
GSM8K 上从 CoT 的 56.5% 提到 74.4%(+17.9 个点),
而按上表 $c=0.565$、$k=40$ 左右应该能到 0.85 以上。
差的那一截,就是"链之间不独立"吃掉的。

自洽性的一个硬限制:它投票投的是最终答案,
所以只适用于答案可以对齐的任务(数值、选项、代码输出)。
开放式生成(写文章、解释概念)没有"多数答案"可投。

7.4 真实 API 调用长什么样(本页不跑)

下面这段是真实会跑通的代码,但它需要你自己申请 API key 并且联网,
所以本页没有运行它,也不提供输出。
放在这里是为了让你看到"提示工程"落到代码上是什么样子。
它比你想的简单得多,全部的智力内容在 PROMPT 那个字符串里。

# ⚠️ 需要先 pip install openai,并设置环境变量 OPENAI_API_KEY(需要自己申请)
# ⚠️ 本页不跑这个块。你需要自备 key 才能运行。
from collections import Counter
from openai import OpenAI

client = OpenAI()   # 从环境变量 OPENAI_API_KEY 读

PROMPT = """食堂有 23 个苹果,用掉 20 个,又买了 6 个,现在几个?

一步步算,最后一行写 "答案: <数字>"。

食堂有 23 个苹果。用掉 20 个,剩 23 - 20 = 3 个。
又买了 6 个,3 + 6 = 9 个。
答案: 9

小明有 15 块糖,给了弟弟 4 块,又买了 8 块,现在几块?
一步步算,最后一行写 "答案: <数字>"。"""

def ask(temperature):
    r = client.chat.completions.create(
        model="gpt-4o-mini",                       # 具体可用模型名请查官方文档,会变
        messages=[{"role": "user", "content": PROMPT}],
        temperature=temperature,                   # 见 §8
        max_tokens=256,
    )
    return r.choices[0].message.content

# --- 贪婪解码取一条链 ---
greedy = ask(0.0)
print("贪婪解码:", greedy.strip().splitlines()[-1])

# --- 自洽性:采样 k 条链,对最后一行做多数投票 ---
k, answers = 10, []
for _ in range(k):
    text = ask(0.8)                                # temperature 高一点,链才有差异
    answers.append(text.strip().splitlines()[-1])
print("投票结果:", Counter(answers).most_common())

几个工程要点:

7.5 边界与批评


8 高效微调:LoRA 与量化

这一节回答一个非常实际的问题:我只有一张消费级显卡,能改造一个几十亿参数的模型吗?
(这一节是 * 创新创业思维的另一个落点:这两项技术是"个人/小团队做 AI 产品"的物质基础。)

8.1 全量微调为什么贵

微调一个模型,不只是存一份权重。以 Adam 优化器为例,训练时需要保存:

要存的东西 每参数字节数(混合精度常见配置)
模型权重(fp16) 2
梯度的 fp32 副本 4
Adam 一阶矩(fp32) 4
Adam 二阶矩(fp32) 4
fp32 主权重 4
合计 约 18 字节/参数

对 1750 亿参数的 GPT-3,这大约是 3 TB 量级,远超任何单卡的显存。
即使不算优化器状态,光是把 175B 的 fp16 权重装进去就要约 350GB。

论文报告的实际数字是:LoRA 把 GPT-3 175B 的微调显存需求
从约 1.2 TB 降到约 350 GB(约 1/3),可训练参数降到约 0.01%。

还有一个更日常的痛点:如果你要微调 100 个不同的下游任务,
全量微调意味着存 100 份 175B 的模型。
LoRA 让你只存 100 份很小的适配器,底座共享。这是它被广泛采用的真正原因
(不只是省显存,更是省"版本管理")。

8.2 LoRA 的原理:低秩分解

Hu, Shen, Wallis, Allen-Zhu, Li, Wang, Wang & Chen,
LoRA: Low-Rank Adaptation of Large Language Models,
ICLR 2022, arXiv:2106.09685。

它基于一个观测:Aghajanyan et al.(Intrinsic Dimensionality Explains the
Effectiveness of Language Model Fine-Tuning
, ACL 2021, arXiv:2012.13255)
发现,把一个预训练模型适配到下游任务时,真正需要的自由度远小于参数量,
他们估计微调的"内在维度"只有几百到几千维,
相对于上亿的参数,这是一个极小的子空间。

数学做法:设预训练权重 $W_0 \in \mathbb{R}^{d\times k}$。
全量微调学的是 $W_0 + \Delta W$,其中 $\Delta W$ 和 $W_0$ 一样大。
LoRA 的做法是把 $W_0$ 冻结,只让 $\Delta W$ 取一个低秩分解的形式:

$$\Delta W = B A,\qquad B\in\mathbb{R}^{d\times r},\quad A\in\mathbb{R}^{r\times k}, \qquad r \ll \min(d,k)$$

前向计算变成:

$$h = W_0 x + \Delta W x = W_0 x + \frac{\alpha}{r}\, B A x$$

逐项解释:

实跑 1:低秩截断到底损失多少(用截断 SVD,它是 Frobenius 范数下的最优秩-$r$ 近似)

import numpy as np
np.set_printoptions(precision=4, suppress=True, linewidth=150)
rng = np.random.default_rng(3)

M = 256
W = rng.normal(size=(M, M))
U, s, Vt = np.linalg.svd(W, full_matrices=False)
print("  秩 r   参数量 r(M+M)   压缩比    相对误差 ||W-W_r||_F/||W||_F")
Wf = np.linalg.norm(W, "fro")
for rr in [1, 2, 4, 8, 16, 32, 64, 128]:
    Wr = (U[:, :rr]*s[:rr]) @ Vt[:rr]
    print("  %4d   %10d    %5.1fx    %10.5f"
          % (rr, rr*2*M, M*M/(rr*2*M), np.linalg.norm(W-Wr, "fro")/Wf))
print("\n  误差的解析式核对:||W-W_r||_F = sqrt(sum_{i>r} sigma_i^2)")
for rr in [1, 4, 16, 64]:
    Wr = (U[:, :rr]*s[:rr]) @ Vt[:rr]
    print("    r=%3d  数值 %.6f   解析 %.6f   偏差 %.1e"
          % (rr, np.linalg.norm(W-Wr, "fro"), np.sqrt((s[rr:]**2).sum()),
             abs(np.linalg.norm(W-Wr, "fro") - np.sqrt((s[rr:]**2).sum()))))

d = 12288
print("\nGPT-3 一个 %dx%d 投影矩阵,全量参数 %.2f 亿:" % (d, d, d*d/1e8))
print("   秩 r   LoRA 参数量 r(d+d)   占全量的比例")
for rr in [1, 4, 16, 64]:
    print("   %4d   %14d    %8.4f%%" % (rr, rr*(d+d), rr*(d+d)/(d*d)*100))

实跑输出

  秩 r   参数量 r(M+M)   压缩比    相对误差 ||W-W_r||_F/||W||_F
     1          512    128.0x       0.99249
     2         1024     64.0x       0.98517
     4         2048     32.0x       0.97073
     8         4096     16.0x       0.94281
    16         8192      8.0x       0.88949
    32        16384      4.0x       0.78998
    64        32768      2.0x       0.61392
   128        65536      1.0x       0.32585

  误差的解析式核对:||W-W_r||_F = sqrt(sum_{i>r} sigma_i^2)
    r=  1  数值 253.466024   解析 253.466024   偏差 0.0e+00
    r=  4  数值 247.909072   解析 247.909072   偏差 5.7e-14
    r= 16  数值 227.162686   解析 227.162686   偏差 2.8e-14
    r= 64  数值 156.786046   解析 156.786046   偏差 0.0e+00

GPT-3 一个 12288x12288 投影矩阵,全量参数 1.51 亿:
   秩 r   LoRA 参数量 r(d+d)   占全量的比例
      1            24576      0.0163%
      4            98304      0.0651%
     16           393216      0.2604%
     64          1572864      1.0417%

读法:

实跑 2:真的训一遍,看 LoRA 什么时候成立、什么时候不成立

import numpy as np
rng = np.random.default_rng(3)
DIO = 20
W0 = rng.normal(size=(DIO, DIO))*0.5                # 冻结的"预训练权重"

def make_data(W_star, n=400):
    X = rng.normal(size=(n, DIO))
    return X, X @ W_star.T
mse = lambda Wm, X, Y: ((X@Wm.T - Y)**2).mean()

def train_full(X, Y, steps=3000, lr=0.02):          # 全量微调:400 个参数都可训
    Wm = W0.copy()
    for _ in range(steps):
        Wm -= lr*(2.0*(X@Wm.T - Y).T@X/len(X))
    return Wm

def train_lora(X, Y, r, alpha=1.0, steps=4000, lr=0.02):
    A = rng.normal(size=(r, DIO))*0.1               # A 用高斯初始化
    B = np.zeros((DIO, r))                          # B 初始化为 0(关键)
    sc = alpha/r                                    # 缩放因子 alpha/r
    for _ in range(steps):
        G = 2.0*(X@(W0 + sc*(B@A)).T - Y).T@X/len(X)   # dL/dW,W = W0 + (a/r)BA
        A -= lr*sc*(B.T@G)
        B -= lr*sc*(G@A.T)
    return W0 + sc*(B@A)

# 情形 1:真实改动是低秩的(LoRA 的假设成立)
dW_low = rng.normal(size=(DIO, 2)) @ rng.normal(size=(2, DIO))
X1, Y1 = make_data(W0 + dW_low)
print("【情形 1】真实改动 dW 的秩 = 2(低秩)")
print("  全量微调 400 参数  测试 MSE = %.3e" % mse(train_full(X1, Y1), X1, Y1))
print("  LoRA r=2  80 参数  测试 MSE = %.3e" % mse(train_lora(X1, Y1, 2), X1, Y1))

# 情形 2:真实改动是满秩的(LoRA 的假设不成立)
X2, Y2 = make_data(W0 + rng.normal(size=(DIO, DIO))*0.5)
print("【情形 2】真实改动 dW 是满秩(LoRA 的假设不成立)")
print("  全量微调 400 参数  测试 MSE = %.3e" % mse(train_full(X2, Y2), X2, Y2))
print("  LoRA r=2  80 参数  测试 MSE = %.3e" % mse(train_lora(X2, Y2, 2), X2, Y2))
print("  LoRA r=8 320 参数  测试 MSE = %.3e" % mse(train_lora(X2, Y2, 8), X2, Y2))

实跑输出

【情形 1】真实改动 dW 的秩 = 2(低秩)
  全量微调 400 参数  测试 MSE = 2.036e-28
  LoRA r=2  80 参数  测试 MSE = 2.160e-30
【情形 2】真实改动 dW 是满秩(LoRA 的假设不成立)
  全量微调 400 参数  测试 MSE = 4.761e-29
  LoRA r=2  80 参数  测试 MSE = 3.520e+00
  LoRA r=8 320 参数  测试 MSE = 8.837e-01

三行数字说明两件事:

所以 LoRA 的全部赌注就是"下游需要的改动是低秩的"。
论文的实证结论是:这个赌注在真实任务上通常成立($r=4\sim16$ 对多数任务够用),
优先适配 Query 和 Value 投影矩阵效果最好。但它不是定理,
如果任务需要模型学全新的知识(而不是调整已有的行为),LoRA 会明显不如全量微调。

8.3 量化的基本思想

目标:用更少的比特存权重。FP32 是 32 位,INT8 是 8 位(省 4 倍),INT4 是 4 位(省 8 倍)。

对称量化(最简单的一种):

$$\text{scale} = \frac{\max|W|}{2^{b-1}-1},\qquad W_q = \mathrm{round}\!\left(\frac{W}{\text{scale}}\right),\qquad W \approx W_q \cdot \text{scale}$$

逐项解释:$b$ 是位宽。$2^{b-1}-1$ 是正半轴上有多少个整数格
($b=8$ 时有 127 个,$b=4$ 时只有 7 个)。
scale 是"一个整数格代表多大的数值",由绝对值最大的那个权重决定。
这个依赖是量化所有麻烦的来源,下面会看到。
$\mathrm{round}$ 把浮点投到最近的整数格上。

实跑:

import numpy as np
rng = np.random.default_rng(3)
W = rng.normal(size=(20, 20))*1.5                   # 待量化的权重

def q_tensor(Wm, b):                                # 整个矩阵共用一个 scale
    q = 2**(b-1) - 1; sc = np.abs(Wm).max()/q
    return np.round(Wm/sc).clip(-q, q)*sc
def q_channel(Wm, b):                               # 每个输出通道一个 scale
    q = 2**(b-1) - 1; sc = np.abs(Wm).max(axis=1, keepdims=True)/q
    return np.round(Wm/sc).clip(-q, q)*sc
def q_group(Wm, b, gs=4):                           # 每 4 个元素一组一个 scale
    q = 2**(b-1) - 1; out = np.empty_like(Wm)
    for j in range(0, Wm.shape[1], gs):
        blk = Wm[:, j:j+gs]; sc = np.abs(blk).max(axis=1, keepdims=True)/q
        out[:, j:j+gs] = np.round(blk/sc).clip(-q, q)*sc
    return out
rel = lambda A, Bm: np.linalg.norm(A-Bm)/np.linalg.norm(A)*100

print("   位宽 b   每-tensor   每-通道   每-4元素组   量化级数 2^(b-1)-1")
for b in [8, 4, 3, 2]:
    print("   %4d 位   %7.2f%%   %7.2f%%   %9.2f%%   %10d"
          % (b, rel(W, q_tensor(W, b)), rel(W, q_channel(W, b)),
             rel(W, q_group(W, b)), 2**(b-1)-1))

W_out = W.copy(); W_out[0, 0] = np.abs(W).max()*30     # 人为加一个 30 倍离群值
print("\n  加一个 30 倍离群值后的 INT4 误差:")
print("    每-tensor %.2f%%   每-通道 %.2f%%   每-4元素组 %.2f%%"
      % (rel(W_out, q_tensor(W_out, 4)), rel(W_out, q_channel(W_out, 4)),
         rel(W_out, q_group(W_out, 4))))

实跑输出

   位宽 b   每-tensor   每-通道   每-4元素组   量化级数 2^(b-1)-1
      8 位      0.75%      0.52%        0.34%          127
      4 位     14.29%      9.76%        6.00%            7
      3 位     32.01%     21.09%       13.02%            3
      2 位     82.49%     61.60%       40.16%            1

  加一个 30 倍离群值后的 INT4 误差:
    每-tensor 19.73%   每-通道 5.37%   每-4元素组 2.86%

读法:

量化为什么对推理特别重要:大模型推理的主要瓶颈通常不在算力,而在
显存带宽,每生成一个 token,都要把全部权重从显存读一遍。
把权重从 FP16 压到 INT4,要搬的数据就少了 4 倍,速度直接受益。
(这也是为什么"量化"和"LoRA"经常一起用:
LoRA 让你训得起,量化让你跑得动。)

8.4 边界与批评


9 推理与解码(temperature / top-k / top-p)

9.1 自回归生成:为什么解码策略是一等公民

语言模型每一步的输出是整个词表上的一个概率分布,而不是单个 token
(见《自然语言处理》§3.1、§3.5)。生成一个回答的过程是:

  [ 提示 ] → 模型 → 分布 p(·)  → 【选一个 token】→ 接回输入 → 再跑一次
                                       ↑
                              这一步就是"解码策略"

所以"模型说了什么"从来不只是模型的事。同一份权重、同一个提示,
换一个解码策略,输出可以完全不同。§8 剩下的部分全部在讲怎么选那个 token。

9.2 temperature(温度)

不要把 logits 直接 softmax。先把它们同除以一个数 $T$:

$$p_i = \frac{\exp(z_i/T)}{\sum_j \exp(z_j/T)}$$

等价形式(设 $p^{(1)}_i$ 是 $T=1$ 时的分布):

$$p_i \;\propto\; \big(p^{(1)}_i\big)^{1/T}$$

三种情形:

数学性质:$T$ 不改变概率的排序(单调变换),只改变"陡峭度"。
它把熵单调地拉高或压低。

(三种解码的实跑在 §8.5,那里有一段代码把温度、top-k、top-p 并排跑出来。)

9.3 top-k:固定留 k 个

定义:按概率从大到小排序,只保留最大的 $k$ 个,
把其余的置零,然后在保留的这 $k$ 个上重新归一化。

边界情形:$k=1$ 就是贪婪解码;$k=V$(词表大小)就是原分布,什么也没做。

9.4 top-p(核采样,nucleus sampling)

定义:按概率从大到小排序,累加,取最小的那个集合 $S$,
使得累计概率 $\sum_{i\in S} p_i \ge p$;然后把 $S$ 之外的置零,在 $S$ 内重新归一化。

$$\text{找最小的 } S:\quad \sum_{i\in S}p_i \ge p,\qquad q_i = \frac{p_i}{\sum_{j\in S}p_j}\;\;(i\in S),\qquad q_i=0\;(i\notin S)$$

和 top-k 的关键差别:保留的个数不固定,由分布的形状决定。
分布很尖时,一两个 token 就凑够 0.9 了;分布很平时,需要留十几个。

9.5 把四种解码并排跑一遍

import numpy as np
TOK = ["好", "不错", "晴朗", "糟糕", "冷", "热", "一般", "适合", "出门", "下雨"]
sm  = lambda z: (lambda e: e/e.sum())(np.exp(z - z.max()))
ent = lambda p: float(max(0.0, -(p[p > 0]*np.log(p[p > 0])).sum()))
def show(nm, p):
    print("  %-7s" % nm, " ".join("%s:%.4f" % (TOK[i], p[i]) for i in range(10)))
    print("  %-7s 熵 H = %.4f nats,最大概率 = %.4f" % ("", ent(p), p.max()))
def topk(p, k):
    idx = np.argsort(-p)[:k]; q = np.zeros_like(p); q[idx] = p[idx]; return q/q.sum()
def topp(p, th):
    o = np.argsort(-p); c = np.cumsum(p[o]); n = int(np.searchsorted(c, th)+1)
    q = np.zeros_like(p); q[o[:n]] = p[o[:n]]; return q/q.sum(), n

logits = np.array([3.2, 2.6, 2.1, 1.4, 1.0, 0.8, 0.5, 0.3, 0.1, -0.4])
p0 = sm(logits)
print("[temperature]  p_i ∝ p_i^(1/T)")
show("T=1.0", p0)
for T in [0.3, 0.5, 0.8, 1.5, 3.0, 10.0]:
    show("T=%.1f" % T, sm(logits/T))
print("\n[top-k]  只留最大的 k 个再归一化")
for k in [1, 2, 3, 5]:
    show("k=%d" % k, topk(p0, k))
print("\n[top-p]  留累计概率刚过 p 的那一撮")
for th in [0.5, 0.8, 0.9, 0.95, 0.99]:
    q, n = topp(p0, th)
    show("p=%.2f" % th, q); print("  %-7s 保留 %d 个 token" % ("", n))
print("\n[尖峰 vs 平坦]")
peak = sm(np.array([6., 1., .5, 0., -.5, -1., -1.5, -2., -2.5, -3.]))
flat = sm(np.array([1.]*10))
for nm, p in [("尖峰", peak), ("平坦", flat)]:
    qk = topk(p, 3); qp, n = topp(p, 0.9)
    print("  %s分布:top-k=3 最大概率 %.4f 熵 %.4f | top-p=0.9 留 %d 个 最大概率 %.4f 熵 %.4f"
          % (nm, qk.max(), ent(qk), n, qp.max(), ent(qp)))
print("\n[采样]  同一分布、同一随机种子,各采 20 次")
for nm, p in [("T=1.0", p0), ("T=0.5", sm(logits/0.5)), ("T=1.5", sm(logits/1.5)),
              ("top-k=3", topk(p0, 3)), ("top-p=0.9", topp(p0, 0.9)[0])]:
    idx = np.random.default_rng(0).choice(10, size=20, p=p)
    print("  %-9s" % nm, " ".join(TOK[i] for i in idx))

实跑输出

[temperature]  p_i ∝ p_i^(1/T)
  T=1.0   好:0.4093 不错:0.2246 晴朗:0.1362 糟糕:0.0677 冷:0.0454 热:0.0371 一般:0.0275 适合:0.0225 出门:0.0184 下雨:0.0112
          熵 H = 1.7256 nats,最大概率 = 0.4093
  T=0.3   好:0.8587 不错:0.1162 晴朗:0.0219 糟糕:0.0021 冷:0.0006 热:0.0003 一般:0.0001 适合:0.0001 出门:0.0000 下雨:0.0000
          熵 H = 0.4863 nats,最大概率 = 0.8587
  T=0.5   好:0.6802 不错:0.2049 晴朗:0.0754 糟糕:0.0186 冷:0.0084 热:0.0056 一般:0.0031 适合:0.0021 出门:0.0014 下雨:0.0005
          熵 H = 0.9683 nats,最大概率 = 0.6802
  T=0.8   好:0.4909 不错:0.2319 晴朗:0.1241 糟糕:0.0517 冷:0.0314 热:0.0244 一般:0.0168 适合:0.0131 出门:0.0102 下雨:0.0055
          熵 H = 1.5003 nats,最大概率 = 0.4909
  T=1.5   好:0.2931 不错:0.1965 晴朗:0.1408 糟糕:0.0883 冷:0.0676 热:0.0592 一般:0.0485 适合:0.0424 出门:0.0371 下雨:0.0266
          熵 H = 2.0185 nats,最大概率 = 0.2931
  T=3.0   好:0.1840 不错:0.1507 晴朗:0.1275 糟糕:0.1010 冷:0.0884 热:0.0827 一般:0.0748 适合:0.0700 出门:0.0655 下雨:0.0554
          熵 H = 2.2303 nats,最大概率 = 0.1840
  T=10.0  好:0.1219 不错:0.1148 晴朗:0.1092 糟糕:0.1018 冷:0.0978 热:0.0959 一般:0.0930 适合:0.0912 出门:0.0894 下雨:0.0850
          熵 H = 2.2964 nats,最大概率 = 0.1219

[top-k]  只留最大的 k 个再归一化
  k=1     好:1.0000 不错:0.0000 晴朗:0.0000 糟糕:0.0000 冷:0.0000 热:0.0000 一般:0.0000 适合:0.0000 出门:0.0000 下雨:0.0000
          熵 H = 0.0000 nats,最大概率 = 1.0000
  k=2     好:0.6457 不错:0.3543 晴朗:0.0000 糟糕:0.0000 冷:0.0000 热:0.0000 一般:0.0000 适合:0.0000 出门:0.0000 下雨:0.0000
          熵 H = 0.6501 nats,最大概率 = 0.6457
  k=3     好:0.5314 不错:0.2917 晴朗:0.1769 糟糕:0.0000 冷:0.0000 热:0.0000 一般:0.0000 适合:0.0000 出门:0.0000 下雨:0.0000
          熵 H = 1.0018 nats,最大概率 = 0.5314
  k=5     好:0.4634 不错:0.2543 晴朗:0.1543 糟糕:0.0766 冷:0.0514 热:0.0000 一般:0.0000 适合:0.0000 出门:0.0000 下雨:0.0000
          熵 H = 1.3422 nats,最大概率 = 0.4634

[top-p]  留累计概率刚过 p 的那一撮
  p=0.50  好:0.6457 不错:0.3543 晴朗:0.0000 糟糕:0.0000 冷:0.0000 热:0.0000 一般:0.0000 适合:0.0000 出门:0.0000 下雨:0.0000
          熵 H = 0.6501 nats,最大概率 = 0.6457
          保留 2 个 token
  p=0.80  好:0.4885 不错:0.2681 晴朗:0.1626 糟糕:0.0808 冷:0.0000 热:0.0000 一般:0.0000 适合:0.0000 出门:0.0000 下雨:0.0000
          熵 H = 1.2015 nats,最大概率 = 0.4885
          保留 4 个 token
  p=0.90  好:0.4447 不错:0.2441 晴朗:0.1480 糟糕:0.0735 冷:0.0493 热:0.0403 一般:0.0000 适合:0.0000 出门:0.0000 下雨:0.0000
          熵 H = 1.4571 nats,最大概率 = 0.4447
          保留 6 个 token
  p=0.95  好:0.4218 不错:0.2315 晴朗:0.1404 糟糕:0.0697 冷:0.0467 热:0.0383 一般:0.0283 适合:0.0232 出门:0.0000 下雨:0.0000
          熵 H = 1.6206 nats,最大概率 = 0.4218
          保留 8 个 token
  p=0.99  好:0.4093 不错:0.2246 晴朗:0.1362 糟糕:0.0677 冷:0.0454 热:0.0371 一般:0.0275 适合:0.0225 出门:0.0184 下雨:0.0112
          熵 H = 1.7256 nats,最大概率 = 0.4093
          保留 10 个 token

[尖峰 vs 平坦]
  尖峰分布:top-k=3 最大概率 0.9893 熵 0.0663 | top-p=0.9 留 1 个 最大概率 1.0000 熵 0.0000
  平坦分布:top-k=3 最大概率 0.3333 熵 1.0986 | top-p=0.9 留 10 个 最大概率 0.1000 熵 2.3026

[采样]  同一分布、同一随机种子,各采 20 次
  T=1.0     晴朗 好 好 好 糟糕 热 不错 晴朗 不错 一般 糟糕 好 冷 好 晴朗 好 冷 不错 好 不错
  T=0.5     好 好 好 好 不错 晴朗 好 不错 好 晴朗 不错 好 不错 好 不错 好 不错 好 好 好
  T=1.5     糟糕 好 好 好 热 适合 晴朗 冷 晴朗 适合 热 好 一般 好 冷 好 一般 晴朗 不错 不错
  top-k=3   不错 好 好 好 不错 晴朗 不错 不错 不错 晴朗 不错 好 晴朗 好 不错 好 晴朗 不错 好 好
  top-p=0.9 不错 好 好 好 晴朗 冷 不错 晴朗 不错 冷 晴朗 好 糟糕 好 晴朗 好 糟糕 不错 好 好

逐段读:

  1. temperature:$T=1$ 时最大概率 0.4093,熵 1.7256。
    $T=0.3$ 时最大概率涨到 0.8587,熵掉到 0.4863,分布变得非常确定。
    $T=10$ 时最大概率只有 0.1219,熵 2.2964,几乎均匀了(均匀分布的熵是 $\log 10 = 2.3026$)。
    注意排序从头到尾没变过("好"永远是第一),变的只是陡峭度。
  2. top-k:$k=1$ 就是贪婪,熵 0。$k$ 从 1 加到 5,最大概率从 1.0 降到 0.4634,熵从 0 涨到 1.3422。
  3. top-p:$p=0.5$ 时只留 2 个;$p=0.99$ 时留 10 个(全部)。
    保留的个数随阈值变化,这就是它的自适应之处。
  4. 尖峰 vs 平坦,这一段是 top-p 存在的理由。
    - 尖峰分布下:top-k=3 保留了三个 token,最大概率 0.9893(还行);
    top-p=0.9 只留 1 个,直接变成确定输出。
    - 平坦分布下:top-k=3 把 10 个 token 砍到 3 个,
    丢弃了约 70% 的概率质量,最大概率被硬抬到 0.3333,熵从 2.3026 掉到 1.0986。
    而 top-p=0.9 保留了全部 10 个,因为均匀分布里每一个都"需要"才能凑够 0.9。
    - 同一个 $k=3$,在两种分布下一个合适、一个灾难。 top-p 不会犯这个错,
    所以它是今天更常用的默认选择。
  5. 采样:同一份分布、同一个随机种子,只换解码参数,20 次采样的结果完全不同。
    $T=1.5$ 时出现了 5 次"糟糕"和"适合",而 $T=0.5$ 时几乎全是"好"。
    这就是"模型的输出不完全由模型决定"的直观样子。

9.6 边界与批评

截至本书写作时:temperature/top-k/top-p 是 2020 年以来的标准配置,
但解码策略本身仍在演化(min-p、typical sampling、
以及"推理模型"用更长的链而不是更好的采样)。
具体哪种是当前最优,我不下结论。


10 局限与风险

这一节要诚实。前面八节讲了大模型能做什么,这一节讲它在什么条件下会坑你。
每一条都会写清:机制是什么、现在的缓解手段是什么、能不能根治。

10.1 幻觉(hallucination)

定义:生成流畅、自信、但没有事实依据的内容。
综述见 Ji et al., Survey of Hallucination in Natural Language Generation,
ACM Computing Surveys 55(12):248, 2023, arXiv:2202.03629。该综述把幻觉分成两类:

为什么必然发生(三个机制,都是结构性的):

  1. 训练目标是"预测下一个词",不是"说真话"。
    一个高频出现的错误说法,和一个正确说法,在"下一个词预测"这个目标下
    没有本质区别,只要它在训练数据里出现得够多,模型就会高概率地生成它。
    流畅性和真实性是两个不同的轴,而模型只被优化了前者。
  2. 模型没有"我不知道"这个默认输出。它每一步必须从词表里吐一个分布出来。
    面对一个它从没见过的实体,它不会沉默,它会生成一个统计上像答案的东西。
    (这一点在 §4 之后更严重:RLHF 训练出的助手倾向于给出一个确定的回答,
    因为标注员偏好确定的回答。)
  3. 知识存在权重里,是统计性的、无溯源的。模型无法告诉你
    "这句话来自训练数据的哪一段",所以也没有办法自查。

能不能根治:不能。工程上的缓解手段有:
- RAG(检索增强生成):先把相关文档检索出来放进上下文,让模型"照着材料说"。
这能大幅降低幻觉,但不能消除,模型仍可能误读或超出材料(外在幻觉)。
- 要求引用:让模型给出出处。这有用,但模型编造出处的情况也被反复报告过。
- 工具调用:算数交给计算器、事实查询交给搜索。这是目前最有效的一类手段,
因为它把"生成"换成了"执行"。

一条重要的提醒:模型自报的置信度不能当作可靠信号。
模型给出的概率(或它在回答里表现出的笃定程度)与它实际正确的概率之间,
校准(calibration)并不好。所以"模型很确定"不能作为"它是对的"的证据。

10.2 知识截止(knowledge cutoff)

机制:权重在训练结束后就冻结了。之后世界上发生的事,
模型在参数里完全没有任何信息。

为什么这比听起来更麻烦:因为模型不会主动告诉你它不知道,
见 §9.1 机制 2。对于一个截止日期之后的事件,它会流畅地编一个听起来合理的答案。

还有一层容易忽略的:知识截止不是某一天。
它是一个统计上的模糊边界。训练数据的采集有滞后,
不同来源(网页、书、代码)的截止时间不同,而且数据在训练集里的采样权重不同。
所以"模型的截止日期是 2023 年 10 月"这种说法本身就是个近似。

缓解:RAG、联网搜索。这个问题在实践中基本被"给模型接一个检索器"解决了大半,
但代价是:模型不再是一个自足的、离线的系统,
它的回答质量开始依赖检索到的内容的质量。

10.3 上下文长度

三层问题,一层比一层隐蔽:

  1. 名义长度 ≠ 有效长度。一个模型标称支持 128k token 上下文,
    不代表它能把 128k 里的信息都用上。
    Liu et al.(Lost in the Middle: How Language Models Use Long Contexts,
    TACL 12:157–173, 2024, arXiv:2307.03172)发现:
    把关键信息放在上下文的开头或结尾时,模型表现好;
    放在中间时,表现显著变差,曲线呈 U 形。
    所以"信息放进上下文"不等于"模型会用到它"。
  2. 计算代价是平方的。自注意力的计算量随序列长度平方增长
    (见《深度学习》§8.2 的注意力矩阵)。长上下文的推理成本因此增长很快。
    (现代实现用 KV cache、滑窗注意力、稀疏注意力等手段缓解,
    但基本约束还在;这些是工程技巧,不改变注意力的数学形式。)
  3. 上下文里的内容会互相干扰。上下文塞得越满,
    模型的注意力被稀释得越厉害。更多的上下文不总是更好。

10.4 对齐问题

10.5 一份诚实的清单

风险 机制 现有缓解手段 能否根治
幻觉 目标是"预测下一个词";没有"我不知道"输出;无溯源 RAG、引用、工具调用 不能,只能压低
知识截止 权重冻结;截止边界是统计性的、模糊的 RAG、联网搜索 能"绕过",但模型不再自足
上下文长度 中间信息被忽略(U 形);注意力 $O(n^2)$ 长上下文训练、稀疏注意力 部分;有效长度仍是短板
奖励黑客 奖励模型是偏好的有损代理 KL 罚项、更大更好的奖励模型 不能,是结构性的
迎合 标注员偏好"让人舒服的回答" 无公认的有效手段 未知
不可复现 提示/解码参数/模型版本都会变 完整报告配置(这是最实际的) 能,靠规范,不靠技术

这一节的每一条都源于同一个原因,目标函数本身就在这么做。把目标定成"预测下一个词 + 让人打高分",
就必然会得到"流畅、自信、讨人喜欢、偶尔编造"的系统。
要改变这些性质,得改变目标,而不是改变规模。


11 和你专业的关系

这一节是这一页对认知科学专业学生的回报:前面八节的技术,
到这里全部变成关于语言的科学证据(或者不是证据)。

11.1 大模型作为"语言习得的反例"

先摆出被挑战的两条经典论断(见《心理语言学》与《语言学概论》):

  1. 输入贫乏论证(poverty of the stimulus)。乔姆斯基传统认为:
    儿童接触到的语言输入不足以唯一确定语法,
    所以语法知识中必然有先天的成分。
    另一个相关论点是:儿童只听到正例(合法的句子),
    几乎得不到负面证据("这句话不合法"这个信息),
    那他们怎么知道哪些句子不能说?
  2. 关键期假设。Lenneberg 等人在 1960 年代提出:
    语言习得有一个时间窗,错过之后难以完全习得。

大模型看起来同时否定了这两条:

但是,不要让论证停在这里。三条必须说清的限定:

限定一:输入量的量级完全不同,这不是可比的实验。
儿童在 4 岁前累计接触的词量,量级在 $10^7 \sim 10^8$;
GPT-3 训练了 $3\times10^{11}$ 个 token(换算成词也还是 $10^{11}$ 量级)。
两边差 3~4 个数量级。
(我给的是量级,不是精确值,具体数字取决于如何统计"输入"
(词还是 token?听到的还是接触到的?),不同研究给出的估计差异很大,
而且这个领域里某些被广泛引用的结论(比如"词汇量差距")已经被后续工作质疑过。
所以我不引用任何具体的差距数字。)
关键在于:"模型能学会"和"儿童能学会"是两个输入规模差了几个数量级的现象。
把它们直接对比、然后得出"儿童也不需要先天语法",是跨过了最关键的变量。

限定二:模型展示的是"形式能力",不是"功能能力"。
Mahowald et al.(Dissociating language and thought in large language models,
Trends in Cognitive Sciences 28(6):517–540, 2024)提出这个二分:
- 形式语言能力(formal competence):产出和判断合法句子的能力。
- 功能语言能力(functional competence):把语言用在真实交际和世界知识里的能力
(理解言外之意、指称真实世界、推理说话人的意图)。

模型在第一条上表现很好,在第二条上表现明显较差。"它学会了语法"
只覆盖了第一条。认知科学关心的很多东西在第二条里。

限定三(最重要,也最容易被忽略):没有关键期,可能只是因为"没有发育"。
关键期在生物学上不是凭空的一个时间窗,它与
突触修剪、髓鞘化、神经发育这些过程绑定。
而大模型是固定架构、参数量不变的一个优化过程,
它本来就不该被期待出现关键期。
所以"大模型没有关键期"并不构成对关键期假设的反驳,
它只是说"这个系统和大脑的机制不一样",而这一点我们从一开始就知道。
把"模型没有关键期"当成"儿童的关键期是错觉",是一个范围错误(scope error)。

一个正在做的直接检验:既然"输入量"是关键变量,
那就用儿童规模的输入量去训模型,看能学到多少语法。
这个方向有一个公开的竞赛(BabyLM Challenge),
(我记得是 Warstadt 等人 2023 年组织的,但 arXiv 编号我不确定,标"出处待查")。
这类工作的思路正好是"控制输入量这个变量",是最值得跟的后续方向。

一个重要的相关立场:Piantadosi & Hill(Meaning without reference in
large language models
, arXiv:2208.02957, 2022)主张,
一个足够好的语言模型可以从形式中恢复出大量世界结构,
所以"没有指称就没有意义"这个论断下得太早了。
反方立场见 Bender & Koller(Climbing towards NLU,
ACL 2020, arXiv:2005.04118)。
Frank(Baby steps in evaluating the capacities of large language models,
Nature Reviews Psychology 2:451–452, 2023)的立场是:
评测要"一小步一小步来",不要用笼统的"理解/不理解"下结论。

11.2 模型-脑对齐(model-brain alignment)

思路:把模型的内部表示和脑活动(fMRI、EEG/MEG)对齐,
看模型的哪一层、哪种表征最能预测脑响应。

已发表的两条关键结果(《自然语言处理》§8.1 已引用,这里只讲它们对本节的意义):

第二条对本课特别有意义:它把 §2 的规模定律和 §10.2 连起来了。
"模型-脑对齐度随规模提高"意味着,
规模带来的不只是任务分数,还有"更像大脑"这个属性。

但必须保留三条:

  1. 对齐性好 ≠ 机制相同。两个系统可以给出相同的预测、
    却用完全不同的内部计算。这正是《深度学习》§10.3 讨论过的那个争议。
  2. "更强的模型更匹配大脑"可以有很平庸的解释:
    更强的模型对语言的统计结构拟合得更好,
    而大脑本来就在拟合这个统计结构。这不必然说明模型"像大脑"。
  3. 这条研究线的价值恰恰在于它可以被证伪。你可以设计实验,
    让"机制相同"和"机制不同"两类模型给出不同的神经预测,然后去测。
    这是这个领域里少有的、能真正推进问题的做法。

11.3 它到底"懂"语言吗

这是认知科学最前沿的问题之一。本页不给结论,只给三方的立场和一个操作建议。

立场 代表 核心主张
形式 ≠ 意义 Bender & Koller 2020 只从形式训练的系统学不到"指称"(语言与世界的连接)
意义可以从形式中恢复 Piantadosi & Hill 2022 足够好的世界模型可以从语言里恢复出大量世界结构
拆开来看 Mahowald et al. 2024 模型强在形式语言能力,弱在需要世界模型和语用的功能性任务

操作建议(这一条比结论有用):遇到"它到底懂不懂 X",
先把它拆成一个可操作的子问题。

这和你在《实验方法论》学的第一件事是一样的:把一个笼统的问题
变成一个能出对错的具体假设。


12 习题(带答案)

怎么用:先盖住答案在纸上做。第 3、4、7 题必须能手推(数字都是我实跑核对过的)。
第 8 题没有标准答案,但必须写出可检验的方案。

12.1 第 1 题(规模定律的算术)★

问:一个团队有 $C = 3.15\times10^{24}$ FLOPs 的预算(是 GPT-3 的 10 倍)。
(a) 按 Kaplan 口径,最优的 $N$ 和 $D$ 是多少?
(b) 按 Chinchilla 口径呢?
(c) 已知 $C=3.15\times10^{23}$ 时 Kaplan 口径的最优是 $N=137$B、$D=383$B。
用这两个点验证一下 Kaplan 口径下 $N$ 的指数,并说明这个指数说明了什么。

答案:

(b) Chinchilla 先算(因为它有闭式解,最简单):
由 $N^* = \sqrt{C/120}$ 和 $D^* = 20N^*$:

$$N^* = \sqrt{3.15\times10^{24}/120} = \sqrt{2.625\times10^{22}} \approx 1.62\times10^{11} = 162\text{B}$$
$$D^* = 20 \times 162\text{B} = 3.24\text{T tokens}$$

(a) Kaplan 口径:$N \propto C^{0.556}$(§2.4 实跑拟合出来的)。于是

$$\frac{N_2}{N_1} = 10^{0.556} \approx 3.60 \;\Longrightarrow\; N_2 = 137\text{B} \times 3.60 \approx 493\text{B}$$

同法 $D \propto C^{0.444}$:$D_2 = 383\text{B}\times 10^{0.444} \approx 383\times2.78 \approx 1066\text{B}$。
(这两个数我用 §2 的代码实跑核对过:$N=492.6$B、$D=1066$B。)

(c) 用两个算力点反推指数(指数 = 对数斜率):

$$\frac{\log(N_2/N_1)}{\log(C_2/C_1)} = \frac{\log(493/137)}{\log(10)} = \frac{\ln 3.599}{\ln 10} = \frac{1.281}{2.303} \approx 0.556$$

核对通过($\log$ 用自然对数;换任何底数比值都一样)。
$0.556$ 明显大于 $0.5$,说明 Kaplan 口径下参数应该涨得比数据快
这就是它和 Chinchilla 的核心分歧。

但要在这里加一个方法论提醒:这个"验证"其实是循环的。
$493$ 这个数是我用 $0.556$ 算出来的,再反推当然得到 $0.556$。
要检验一个外推公式,必须在公式没用过的数据点上测。
§2.4 的实跑用的是有限差分(在 $C=10^{24}$ 附近取两个很近的点),
那是在没有预先假设指数的前提下把斜率量出来的。
这才算验证。这个坑在所有"拟合 + 外推"的工作里都存在。

结论:Kaplan 口径下 $N \approx 493$B、$D \approx 1066$B($D/N \approx 2.2$);
Chinchilla 口径下 $N \approx 162$B、$D \approx 3.2$T($D/N = 20$)。
模型大小差 3 倍,数据量差 3 倍,而数据参数比 $D/N$ 差了将近 9 倍
($2.2$ vs $20$),这个比值才是两派分歧的实质。
它不是学术细节,它直接决定你花几千万买什么。


12.2 第 2 题(为什么两派结论不同)★★

问:Kaplan 和 Chinchilla 用的是同样的数学形式(都是幂律),
为什么得出相反的最优分配?这个分歧是数学问题还是实验问题?
并说明"外推"的前提是什么。

答案:

是实验问题,不是数学问题。两篇用的都是幂律,形式几乎一样。

分歧出在数据点的质量上。Kaplan 的实验里,
不同规模的模型用了同一套学习率调度,
而观测时有些模型并没有跑到"各自配置下能到达的最优状态",
损失被高估了。这个偏置在小模型上更严重,
于是拟合出来的曲线把最优点推向了大模型那一侧。
Chinchilla 把每个规模都训到各自的最优(这也是它要花更多算力的原因),
最优点就移回了"参数和数据等比例缩放"。

外推的前提是:每一个数据点本身都是"该配置下能做到的最好"。
如果你拿一堆"没调好"的配置去拟合趋势,你拟合的是
"你的调参水平的趋势",不是"规模的趋势"。
这个教训对所有实验科学都成立,对照必须匹配,每个条件都要跑到自己的最优。

一个补充:这个解释本身也有后续讨论。
我在本页(§2.4)复算时还发现,Kaplan 论文的联合式解出的指数(0.556)
和它正文口径(0.73)不一致;Chinchilla 的 Approach 3 系数也和它自己的
"20 token/参数"规则不一致(复现文献 arXiv:2404.10102 指出了这一点)。
所以"读规模定律论文"的正确姿势是:看它的方法,不只看它的结论数字。


12.3 第 3 题(奖励模型的梯度推导)★★

问:写出奖励模型的目标
$\mathcal{L} = -\log\sigma\big(r_\theta(x,y_w) - r_\theta(x,y_l)\big)$
对 $r_w$ 和 $r_l$ 的梯度(记 $d = r_w - r_l$),
并说明它和逻辑回归的关系。为什么 $\mathcal{L}$ 只依赖差值?

答案:

用两个导数事实:$\dfrac{d}{dx}\log\sigma(x) = 1-\sigma(x)$,以及
$\dfrac{d\sigma}{dx} = \sigma(x)\big(1-\sigma(x)\big)$。

(a) 对 $r_w$:链式法则,外层是 $-\log\sigma$:

$$\frac{\partial \mathcal{L}}{\partial r_w} = -\big(1 - \sigma(d)\big)\cdot\frac{\partial d}{\partial r_w} = -\big(1-\sigma(d)\big)$$

(b) 对 $r_l$:注意 $\partial d/\partial r_l = -1$,负号两次抵消:

$$\frac{\partial \mathcal{L}}{\partial r_l} = +\big(1-\sigma(d)\big)$$

方向解读:括号里恒为正(因为 $\sigma<1$),
所以 $r_w$ 的梯度是负的,梯度下降把它往上推;
$r_l$ 的梯度是正的,把它往下压。
而且 $d$ 越大(模型已经判对了),$\sigma(d)\to1$,梯度 $\to0$:
已经判对的样本自动不再出力。这和《自然语言处理》§9 第 2 题里
负采样的梯度行为是同一个模式。

和逻辑回归的关系:把"$y_w$ 比 $y_l$ 好"当成一个二分类问题,
正类的 logit 设为 $d = r_w - r_l$,则
$-\log\sigma(d)$ 就是这个二分类的交叉熵损失。
所以奖励模型就是一个逻辑回归,只不过它的 logit 是由一个 Transformer 算出来的。

为什么只依赖差值:因为偏好排序提供的只有相对信息。
把所有人的分数同时加 100,"A 比 B 好"这个事实完全不变。
所以奖励的绝对零点不可辨识,
这决定了损失函数只能依赖差值。这不是简化,是数据的信息量决定的。

数值核对(§4.3 实跑):取 $r_w=0.7$、$r_l=-0.2$,
解析梯度 $-0.28905050$ 与数值梯度逐位相同。


12.4 第 4 题(KL 正则的闭式解)★★★

问:推导
$\displaystyle\max_{\pi}\;\mathbb{E}_{y\sim\pi}[r(y)] - \beta\,\mathrm{KL}\big(\pi\,\|\,\pi_{ref}\big)$
(约束 $\sum_y \pi(y)=1$)的最优解
$\pi^*(y) \propto \pi_{ref}(y)\exp\big(r(y)/\beta\big)$,
并说明 $\beta\to0$ 和 $\beta\to\infty$ 两个极限。

答案:

把 KL 展开,写成对每个 $y$ 的求和:

$$\mathcal{J}(\pi) = \sum_y \pi(y) r(y) - \beta\sum_y \pi(y)\log\frac{\pi(y)}{\pi_{ref}(y)}$$

约束是 $\sum_y\pi(y)=1$。引入拉格朗日乘子 $\lambda$:

$$\mathcal{L} = \sum_y \pi(y) r(y) - \beta\sum_y \pi(y)\log\frac{\pi(y)}{\pi_{ref}(y)} - \lambda\left(\sum_y \pi(y) - 1\right)$$

对单个 $\pi(y)$ 求偏导并令其为零(每一步的推导):

$$\frac{\partial \mathcal{L}}{\partial \pi(y)} = r(y) - \beta\left(\log\frac{\pi(y)}{\pi_{ref}(y)} + 1\right) - \lambda = 0$$

(第二项求导时注意 $\frac{d}{d\pi}\big[\pi\log(\pi/\pi_{ref})\big]
= \log(\pi/\pi_{ref}) + 1$。)

整理:

$$\log\frac{\pi(y)}{\pi_{ref}(y)} = \frac{r(y) - \lambda}{\beta} - 1 \;\Longrightarrow\; \pi(y) = \pi_{ref}(y)\,\exp\!\left(\frac{r(y)}{\beta}\right) \cdot \underbrace{\exp\!\left(-\frac{\lambda}{\beta}-1\right)}_{\text{与 } y \text{ 无关的常数}}$$

那个常数由 $\sum_y\pi(y)=1$ 定出来,就是 $1/Z$:

$$\boxed{\;\pi^*(y) = \frac{1}{Z}\pi_{ref}(y)\exp\!\left(\frac{r(y)}{\beta}\right), \qquad Z = \sum_y \pi_{ref}(y)\exp\!\left(\frac{r(y)}{\beta}\right)\;}$$

两个极限:
- $\beta\to0^+$:指数项 $\exp(r/\beta)$ 由 $r$ 最大的那一项支配,
所以 $\pi^*$ 收敛到 $\arg\max_y r(y)$ 上的确定性分布(贪婪)。
- $\beta\to\infty$:$r/\beta\to0$,$\exp(\cdot)\to1$,
所以 $\pi^*\to\pi_{ref}$,完全不动。

数值验证:§4.4 的实跑里,这个闭式解与直接数值优化目标函数的结果
偏差全程 $\le 10^{-10}$,两者是同一个东西。

这个解的物理意义:它就是统计物理里的玻尔兹曼分布 / 指数族倾斜:
$r$ 是能量(取负),$\beta$ 是温度。温度越高($\beta$ 大),分布越接近参考分布;
温度越低($\beta$ 小),分布越集中在最低能量(最高奖励)处。


12.5 第 5 题(涌现是指标造成的)★★

问:取 §5 实跑表里的 $N=3\times10^9$ 和 $N=3\times10^{11}$ 两个模型。
(a) 每个 token 的准确率 $a(N)$ 涨了多少倍?
(b) $\mathrm{EM}(L=8)$ 涨了多少倍?
(c) 如果测试集只有 200 题,这两个模型测出来的 EM 大概各是多少?
(d) 这个对比说明了什么?

答案:

(a) $a(3\times10^9) = 0.6084$,$a(3\times10^{11}) = 0.8543$。

$$\frac{0.8543}{0.6084} \approx 1.40\quad\text{(涨了 40%)}$$

(b) $\mathrm{EM}(8) = a^8$:

$$\frac{(0.8543)^8}{(0.6084)^8} = \frac{0.28365}{0.018771} \approx 15.1\quad\text{(涨了 15 倍)}$$

(c) 测试集 200 题,期望答对题数 $= \mathrm{EM}\times200$:

再往下看一格:$N=3\times10^8$ 的 $\mathrm{EM}(8) = 0.00126$,
期望答对 0.25 题,200 题里一题都不对的概率是 77.8%。
也就是说,这个模型有近八成的概率被测成 0,"完全不会"。

(d)
- 底层能力只涨了 40%,Exact Match 涨了 15 倍。
同一个 $a(N)$ 曲线,被 $a^L$ 这个非线性指标放大了一个数量级。
- 加上小测试集,低准确率那一段被压成了平地,
台阶因此显得更陡。这正是 Schaeffer et al. 2023 说的"海市蜃楼"的两个机制。
- 如果换成 $\log a(N)$ 这个连续指标(同一段实跑的第二张表),
斜率从 $+0.16$ 到 $+0.22$ 再降到 $+0.047$,全程连续、单调、可外推,
完全看不到"涌现"。

所以答案是:这个"涌现"是 $a^L$ 这个指标 + 测试集分辨率共同制造出来的视觉效果,
不是底层能力的突变。


12.6 第 6 题(LoRA 的参数账)★

问:一个 4096×4096 的注意力投影矩阵。
(a) 全量微调要训多少参数?
(b) LoRA 取 $r=8$,要训多少参数?占全量的百分之几?
(c) 如果模型有 32 层,每层 4 个这样的矩阵,全量微调总量是多少?LoRA 总量是多少?

答案:

(a) $4096\times4096 = 16{,}777{,}216 \approx 1678$ 万。

(b) LoRA 的参数量是 $r(d+k) = 8\times(4096+4096) = 65{,}536 \approx 6.55$ 万。

$$\frac{65{,}536}{16{,}777{,}216} = 0.003906 = \mathbf{0.3906\%}$$

(这个数我在写这一页时用 numpy 算过,就是它。)

(c) 全量:$16{,}777{,}216\times4\times32 = 2{,}147{,}483{,}648 \approx 21.5$ 亿。
LoRA:$65{,}536\times4\times32 = 8{,}388{,}608 \approx 839$ 万。

比例还是 0.3906%(因为两边同乘同一个因子)。

一个附加问题:为什么 LoRA 参数量是 $r(d+k)$ 而不是 $d\times k$?
因为 $B$ 是 $d\times r$、$A$ 是 $r\times k$,
参数量是 $dr + rk = r(d+k)$。这就是"低秩分解"的全部算术:
把"乘两次"($dk$ 次乘法)换成"先降维再升维"($r(d+k)$ 次乘法),
当 $r \ll \min(d,k)$ 时大幅省参数。


12.7 第 7 题(解码手算)★

问:词表只有 4 个 token,模型输出的分布是
$p = [0.5, 0.25, 0.125, 0.125]$(依次对应 A、B、C、D)。
(a) 取 $T=0.5$ 后的分布是多少?
(b) 取 top-p = 0.8,保留哪些 token?重归一化后各是多少?
(c) 取 top-k = 2 后的分布是多少?
(d) 从 (b) 和 (c) 的结果,说明为什么 top-p 比 top-k 更不容易"误伤"。

答案:

(a) $p_i \propto p_i^{1/T} = p_i^{2}$:

$$[0.5^2, 0.25^2, 0.125^2, 0.125^2] = [0.25, 0.0625, 0.015625, 0.015625]$$

和 $= 0.25+0.0625+0.015625+0.015625 = 0.34375$。归一化:

$$p^{(0.5)} = \left[\frac{0.25}{0.34375}, \frac{0.0625}{0.34375}, \frac{0.015625}{0.34375}, \frac{0.015625}{0.34375}\right] = [0.7273,\;0.1818,\;0.04545,\;0.04545]$$

(实跑核对:与 §8.5 的代码一致。)

(b) 从大到小排序:$A=0.5$、$B=0.25$、$C=0.125$、$D=0.125$。
累计:$0.5$、$0.75$、$0.875$、$1.0$。
最小的使累计 $\ge0.8$ 的集合是 $\{A,B,C\}$(累计 0.875)。$D$ 被丢掉。

重归一化(除以 0.875):

$$\left[\frac{0.5}{0.875}, \frac{0.25}{0.875}, \frac{0.125}{0.875}, 0\right] = [0.5714,\;0.2857,\;0.1429,\;0]$$

(c) top-k = 2:保留 A、B。除以 $0.5+0.25=0.75$:

$$[0.6667,\;0.3333,\;0,\;0]$$

(d)
注意这两个结果:top-p=0.8 保留了 3 个,top-k=2 只保留了 2 个。
在这个比较尖锐的分布上,两者差别不大。
但把分布换成均匀分布($[0.25,0.25,0.25,0.25]$):

结论:$k$ 是一个绝对数量,不管分布的形状;
$p$ 是一个概率质量的阈值,会随分布自动伸缩。
在平坦分布上,top-k 会造成"过度自信",而 top-p 不会。
这就是 §8.5 那张"尖峰 vs 平坦"对比表要说明的事。


12.8 第 8 题(认知科学设计题)★★★

问:有人主张:"大模型证明了语言习得既不需要关键期,也不需要先天语法,
所以乔姆斯基错了。"请写出三条对这条论证的反驳,
并设计一个能区分"模型和儿童用同样机制学语言"与"用不同机制"的实验。
要求写清自变量、因变量、控制变量,以及什么结果会支持哪一方。

答案:

三条反驳(详见 §10.1):

  1. 输入量的量级完全不同,这不是可比的实验。
    儿童的累计输入量级在 $10^6 \sim 10^7$ 词,GPT-3 是 $3\times10^{11}$ token,
    差 4~5 个数量级。"模型能学会"和"儿童能学会"跨过了最关键的变量。
  2. 模型展示的是形式能力,不是功能能力。
    它产出/判断合法句子的能力(formal competence)和
    把语言用在真实交际与世界知识里的能力(functional competence)是两回事
    (Mahowald et al. 2024)。"学会了语法"只覆盖了第一条。
  3. "没有关键期"可能只是因为"没有发育过程"。
    关键期在生物学上与突触修剪、髓鞘化绑定;
    一个固定架构、参数量不变的优化过程本来就不该被期待出现关键期。
    所以这不构成对关键期假设的反驳,只是"两个系统机制不同"的正常表现。
    把"模型没有关键期"当成"儿童的关键期是错觉",是一个范围错误。

实验设计(一个可行的方案):


13 出处索引

本页提到的所有文献,按主题分类。标注了本页出现的位置。
凡是我不能确认的,明确写"出处待查",没有编造。

13.1 规模定律

文献 出处 本页位置
Kaplan, McCandlish, Henighan, Brown, Chess, Child, Gray, Radford, Wu & Amodei, Scaling Laws for Neural Language Models arXiv:2001.08361, 2020 §2.2, §2.4
Hoffmann, Borgeaud, Mensch, et al., Training Compute-Optimal Large Language Models(Chinchilla) arXiv:2203.15556, 2022 §2.3, §2.4
Besiroglu, Erdil, Barnett & You, Chinchilla Scaling: A replication attempt arXiv:2404.10102, 2024(Epoch AI) §2.4(指出 Approach 3 的不自洽)

13.2 GPT 系列与 RLHF

文献 出处 本页位置
Radford, Narasimhan, Salimans & Sutskever, Improving Language Understanding by Generative Pre-Training(GPT-1) OpenAI 技术报告, 2018(无 arXiv 编号) §3.1
Radford, Wu, Child, Luan, Amodei & Sutskever, Language Models are Unsupervised Multitask Learners(GPT-2) OpenAI 技术报告, 2019(无 arXiv 编号) §3.2
Brown, Mann, Ryder, et al., Language Models are Few-Shot Learners(GPT-3) NeurIPS 2020, arXiv:2005.14165 §1.2, §3.3
Ouyang, Wu, Jiang, et al., Training language models to follow instructions with human feedback(InstructGPT) arXiv:2203.02155, 2022 §3.4, §4 全节
OpenAI, GPT-4 Technical Report arXiv:2303.08774, 2023 §3.5
Christiano, Leike, Brown, Martic, Legg & Amodei, Deep Reinforcement Learning from Human Preferences NeurIPS 2017, arXiv:1706.03741 §4.1
Gao, Schulman & Hilton, Scaling Laws for Reward Model Overoptimization ICML 2023, arXiv:2210.10760 §4.5
Rafailov, Sharma, Mitchell, Ermon, Manning & Finn, Direct Preference Optimization(DPO) NeurIPS 2023, arXiv:2305.18290 §4.5
Amodei, Olah, Steinhardt, Christiano, Schulman & Mané, Concrete Problems in AI Safety arXiv:1606.06565, 2016 §9.4
Sutton, The Bitter Lesson 个人博客短文, 2019(非同行评议) §3.6

13.3 涌现能力

文献 出处 本页位置
Wei, Tay, Bommasani, Raffel, Zoph, Borgeaud, et al., Emergent Abilities of Large Language Models TMLR 2022, arXiv:2206.07682 §5.1
Schaeffer, Miranda & Koyejo, Are Emergent Abilities of Large Language Models a Mirage? NeurIPS 2023(Outstanding Paper), arXiv:2304.15004 §5.2, §5.3, §5.4

13.4 提示与思维链

文献 出处 本页位置
Wei, Wang, Schuurmans, Bosma, Ichter, Xia, Chi, Le & Zhou, Chain-of-Thought Prompting Elicits Reasoning in Large Language Models NeurIPS 2022, arXiv:2201.11903 §6.2
Kojima, Gu, Reid, Matsuo & Iwasawa, Large Language Models are Zero-Shot Reasoners NeurIPS 2022, arXiv:2205.11916 §6.2
Wang, Wei, Schuurmans, Le, Chi, Narang, Chowdhery & Zhou, Self-Consistency Improves Chain of Thought Reasoning in Language Models ICLR 2023, arXiv:2203.11171 §6.3
Turpin, Michael, Perez & Bowman, Language Models Don't Always Say What They Think arXiv:2305.04388, 2023 §6.5

13.5 高效微调与量化

文献 出处 本页位置
Hu, Shen, Wallis, Allen-Zhu, Li, Wang, Wang & Chen, LoRA: Low-Rank Adaptation of Large Language Models ICLR 2022, arXiv:2106.09685 §7.1, §7.2
Aghajanyan, Gupta & Zettlemoyer, Intrinsic Dimensionality Explains the Effectiveness of Language Model Fine-Tuning ACL 2021, arXiv:2012.13255 §7.2
Eckart & Young, The approximation of one matrix by another of lower rank Psychometrika 1(3):211–218, 1936 §7.2(截断 SVD 最优性的定理)

13.6 局限与评测

文献 出处 本页位置
Ji, Lee, Frieske, Yu, Su, Xu, Ishii, Bang, Madotto & Fung, Survey of Hallucination in Natural Language Generation ACM Computing Surveys 55(12):248, 2023, arXiv:2202.03629 §9.1
Liu, Lin, Hewitt, Paranjape, Bevilacqua, Petroni & Liang, Lost in the Middle: How Language Models Use Long Contexts TACL 12:157–173, 2024, arXiv:2307.03172 §9.3
von Oswald, Niklasson, Randazzo, Sacramento, Mordvintsev, Zhmoginov & Vladymyrov, Transformers learn in-context by gradient descent arXiv:2212.07677, 2022 §1.3

13.7 认知科学交叉

文献 出处 本页位置
Bender & Koller, Climbing towards NLU: On Meaning, Form, and Understanding in the Age of Data ACL 2020, arXiv:2005.04118 §10.1, §10.3
Piantadosi & Hill, Meaning without reference in large language models arXiv:2208.02957, 2022 §10.1, §10.3
Frank, Baby steps in evaluating the capacities of large language models Nature Reviews Psychology 2:451–452, 2023 §10.1
Mahowald, Ivanova, Blank, Kanwisher, Tenenbaum & Fedorenko, Dissociating language and thought in large language models Trends in Cognitive Sciences 28(6):517–540, 2024 §10.1, §10.3
Schrimpf, Blank, Tuckute, Kauf, Hosseini, Kanwisher, Tenenbaum & Fedorenko, The neural architecture of language PNAS 118(45), 2021 §10.2
Goldstein, Zada, Buchnik, et al., Shared computational principles for language processing in humans and deep language models Nature Neuroscience 25:369–380, 2022 §10.2

13.8 我明确没能确认的地方,如实标注

  1. GPT-1 和 GPT-2 的出处。这两篇没有 arXiv 编号,
    它们是 OpenAI 直接发布的技术报告,不是会议/期刊论文。
    引用时写 "OpenAI Technical Report, 2018/2019"。这两篇确实没有 arXiv 编号,不是我没查到。

  2. GPT-4 的架构、参数量、算力、数据。官方没有公开,
    报告(arXiv:2303.08774)明确说明出于竞争和安全考虑不披露。
    本页所有涉及 GPT-4 的空白格都是"官方未公开",不是我没查到。

  3. BabyLM Challenge 的确切出处(§10.1)。
    我记得是 Warstadt 等人 2023 年组织的,
    但无法确认确切的 arXiv 编号和会议信息,故标"出处待查"。

  4. sycophancy(迎合)的权威文献(§9.4)。它被反复报告,
    但这个现象的命名和研究还在演化,我不给一个确定的出处。

  5. 儿童输入量的具体数字(§10.1)。我给的是量级($10^6\sim10^7$ 词),
    不给精确值。这个领域里有若干著名结论后来被后续工作质疑过,
    所以我不引用任何具体的"词汇量差距"数字。

  6. §2.4 里 Kaplan 的 0.73 与我从其联合式复算出的 0.556 不一致,
    以及 Chinchilla 的 Approach 3 系数与其"20 token/参数"规则不一致。
    这两处都是如实报告我自己的复算结果,
    后者有复现文献(arXiv:2404.10102)支撑,前者我只报告现象、不强行解释。

  7. 关于"截至写作时"的所有表述(§6.4 的模型名、§8.6 的解码策略、
    §4.5 的 DPO 是否已成为主流)。这门课时效性最强,
    凡是我没有把握的,都写成了"截至本书写作时"或标注不确定,请不要当定论。

最后一句:这一页的所有可运行的代码我都实跑过,
标注 >>> 实跑输出 的输出是原样粘贴的,与代码逐字对应。
所有引用的数字要么来自我实际运行的输出(§2、§4、§5、§7、§8),
要么标注了文献出处。
如果哪一处你复现不出来,请以你的实际输出为准。这可能是我漏掉的环境差异。

14 相关

最后更新:2026-09-26