﻿---
title: "梯度下降早就有了，大模型为什么最近几年才出现"
description: "训练语言模型时打出的损失，换个单位就是“每个字要花几个比特”：训练一个大模型，也是在训练一个压缩器。梯度下降是 1847 年 Cauchy 为了算天体轨道写下的，“预测就是压缩”是香农 1948 年说清楚的，“压缩即智能”的理论在六十年代就有了，大模型为什么等到 2020 年前后才出现？我们沿着时间往下走：会学习的机器为什么被一道 XOR 题难住，反向传播的本质是什么，为什么 1986 年以后神经网络冷了二十年、压缩榜上赢的一直是手写模型；算力、数据各差了多少个数量级，Transformer 和规模定律补上了哪两块；最后把数学、算法、算力、数据四条线放在一起，回答一个压缩器凭什么看起来有智能。"
pubDate: 2026-10-27
updatedDate: 2026-10-27
tags: ["AI", "大模型", "历史", "梯度下降", "信息论", "入门"]
hero: "./hero.svg"
clc: "TP18"
refs:
  - id: cauchy
    title: "Méthode générale pour la résolution des systèmes d'équations simultanées"
    url: https://gtfp.cs.rhul.ac.uk/pulskamp/Cauchy/Orbits/1847%20CR%20536%28383%29.pdf
    author: Augustin-Louis Cauchy
    site: Comptes Rendus de l'Académie des Sciences 25
    date: "1847"
    note: "1847 年 10 月 18 日提交科学院的短文，第 536–538 页，动机是天体轨道计算。"
  - id: lemarechal
    title: "Cauchy and the Gradient Method"
    url: https://www.math.uni-bielefeld.de/documenta/vol-ismp/40_lemarechal-claude.pdf
    author: Claude Lemaréchal
    site: Documenta Mathematica, Extra Volume ISMP
    date: "2012"
    note: "梯度法的来历考证：Cauchy 的短文没有做严格的收敛分析。"
  - id: stigler
    title: "Gauss and the Invention of Least Squares"
    url: https://doi.org/10.1214/aos/1176345451
    author: Stephen M. Stigler
    site: The Annals of Statistics 9(3)
    date: "1981"
    note: "Legendre 1805 年发表最小二乘法，Gauss 1809 年发表并称自己 1795 年就在用；Gauss 用它算出了谷神星的轨道。"
  - id: robbins
    title: "A Stochastic Approximation Method"
    url: https://doi.org/10.1214/aoms/1177729586
    author: Herbert Robbins, Sutton Monro
    site: The Annals of Mathematical Statistics 22(3)
    date: "1951"
    note: "用带噪声的观测一步步逼近解，随机梯度下降的数学源头。"
  - id: mcculloch
    title: "A Logical Calculus of the Ideas Immanent in Nervous Activity"
    url: https://doi.org/10.1007/BF02478259
    author: Warren McCulloch, Walter Pitts
    site: Bulletin of Mathematical Biophysics 5
    date: "1943"
  - id: rosenblatt
    title: "The Perceptron: A Probabilistic Model for Information Storage and Organization in the Brain"
    url: https://doi.org/10.1037/h0042519
    author: Frank Rosenblatt
    site: Psychological Review 65(6)
    date: "1958"
  - id: perceptron-wiki
    title: "Perceptron"
    url: https://en.wikipedia.org/wiki/Perceptron
    site: Wikipedia
    note: "Mark I 感知机的硬件：20×20 共 400 个光电管、512 个关联单元，权重存在电位器里，学习时由电机转动电位器。"
  - id: nyt1958
    title: "New Navy Device Learns By Doing"
    url: https://www.nytimes.com/1958/07/08/archives/new-navy-device-learns-by-doing-psychologist-shows-embryo-of.html
    site: The New York Times
    date: 1958-07-08
  - id: minsky
    title: "Perceptrons: An Introduction to Computational Geometry"
    url: https://mitpress.mit.edu/9780262630221/perceptrons/
    author: Marvin Minsky, Seymour Papert
    site: MIT Press
    date: "1969"
  - id: griewank
    title: "Who Invented the Reverse Mode of Differentiation?"
    url: https://www.math.uni-bielefeld.de/documenta/vol-ismp/52_griewank-andreas-b.pdf
    author: Andreas Griewank
    site: Documenta Mathematica, Extra Volume ISMP
    date: "2012"
    note: "反向模式自动微分的来历：Linnainmaa 1970 年的硕士论文，用来估计舍入误差；Werbos 1974 年的博士论文把它用到神经网络上。"
  - id: rumelhart
    title: "Learning representations by back-propagating errors"
    url: https://www.nature.com/articles/323533a0
    author: David Rumelhart, Geoffrey Hinton, Ronald Williams
    site: Nature 323
    date: "1986"
  - id: lecun89
    title: "Backpropagation Applied to Handwritten Zip Code Recognition"
    url: https://doi.org/10.1162/neco.1989.1.4.541
    author: Yann LeCun 等
    site: Neural Computation 1(4)
    date: "1989"
    note: "布法罗邮局的 9298 个手写邮编数字；网络有 64,660 个连接、9760 个独立参数。"
  - id: karpathy
    title: "Deep Neural Nets: 33 years ago and 33 years from now"
    url: https://karpathy.github.io/2022/03/14/lecun1989/
    author: Andrej Karpathy
    date: 2022-03-14
    note: "复现 LeCun 1989：原文在 SUN-4/260 工作站上训练 3 天，M1 MacBook Air 的 CPU 上约 90 秒。"
  - id: hochreiter
    title: "Untersuchungen zu dynamischen neuronalen Netzen"
    url: https://people.idsia.ch/~juergen/SeppHochreiter1991ThesisAdvisorSchmidhuber.pdf
    author: Sepp Hochreiter
    site: 慕尼黑工业大学毕业论文
    date: "1991"
    note: "德文，首次系统分析了梯度在深层 / 长序列中指数衰减的问题。"
  - id: bengio94
    title: "Learning long-term dependencies with gradient descent is difficult"
    url: https://doi.org/10.1109/72.279181
    author: Yoshua Bengio, Patrice Simard, Paolo Frasconi
    site: IEEE Transactions on Neural Networks 5(2)
    date: "1994"
  - id: lstm
    title: "Long Short-Term Memory"
    url: https://doi.org/10.1162/neco.1997.9.8.1735
    author: Sepp Hochreiter, Jürgen Schmidhuber
    site: Neural Computation 9(8)
    date: "1997"
  - id: svm
    title: "Support-vector networks"
    url: https://doi.org/10.1007/BF00994018
    author: Corinna Cortes, Vladimir Vapnik
    site: Machine Learning 20
    date: "1995"
  - id: shannon
    title: "Prediction and Entropy of Printed English"
    url: https://doi.org/10.1002/j.1538-7305.1951.tb01366.x
    author: Claude Shannon
    site: Bell System Technical Journal 30(1)
    date: "1951"
  - id: bengio03
    title: "A Neural Probabilistic Language Model"
    url: https://www.jmlr.org/papers/v3/bengio03a.html
    author: Yoshua Bengio 等
    site: JMLR 3
    date: "2003"
  - id: cray1
    title: "Cray-1"
    url: https://en.wikipedia.org/wiki/Cray-1
    site: Wikipedia
    note: "1976 年首台装机于洛斯阿拉莫斯，峰值 160 MFLOPS，售价约 800 万美元。"
  - id: ascired
    title: "ASCI Red"
    url: https://en.wikipedia.org/wiki/ASCI_Red
    site: Wikipedia
    note: "1997 年第一台 LINPACK 实测超过 1 TFLOPS 的计算机。"
  - id: hinton06
    title: "A Fast Learning Algorithm for Deep Belief Nets"
    url: https://doi.org/10.1162/neco.2006.18.7.1527
    author: Geoffrey Hinton, Simon Osindero, Yee-Whye Teh
    site: Neural Computation 18(7)
    date: "2006"
  - id: raina
    title: "Large-scale Deep Unsupervised Learning using Graphics Processors"
    url: https://doi.org/10.1145/1553374.1553486
    author: Rajat Raina, Anand Madhavan, Andrew Ng
    site: ICML 2009
    date: "2009"
    note: "用 GPU 训练深度信念网，比双核 CPU 快最多约 70 倍。"
  - id: relu
    title: "Deep Sparse Rectifier Neural Networks"
    url: https://proceedings.mlr.press/v15/glorot11a.html
    author: Xavier Glorot, Antoine Bordes, Yoshua Bengio
    site: AISTATS 2011
    date: "2011"
  - id: alexnet
    title: "ImageNet Classification with Deep Convolutional Neural Networks"
    url: https://papers.nips.cc/paper/2012/hash/c399862d3b9d6b76c8436e924a68c45b-Abstract.html
    author: Alex Krizhevsky, Ilya Sutskever, Geoffrey Hinton
    site: NeurIPS 2012
    date: "2012"
    note: "6000 万参数，两张 GTX 580（各 3GB）训练五到六天。"
  - id: brown-corpus
    title: "Brown Corpus"
    url: https://en.wikipedia.org/wiki/Brown_Corpus
    site: Wikipedia
  - id: ptb
    title: "Building a Large Annotated Corpus of English: The Penn Treebank"
    url: https://aclanthology.org/J93-2004/
    author: Mitchell Marcus, Beatrice Santorini, Mary Ann Marcinkiewicz
    site: Computational Linguistics 19(2)
    date: "1993"
  - id: mnist
    title: "Gradient-based learning applied to document recognition"
    url: https://doi.org/10.1109/5.726791
    author: Yann LeCun, Léon Bottou, Yoshua Bengio, Patrick Haffner
    site: Proceedings of the IEEE 86(11)
    date: "1998"
  - id: imagenet
    title: "ImageNet"
    url: https://en.wikipedia.org/wiki/ImageNet
    site: Wikipedia
    note: "约 4.9 万名 Mechanical Turk 工人，来自 167 个国家，标注从 2008 年 7 月做到 2010 年 4 月。"
  - id: commoncrawl
    title: "Common Crawl"
    url: https://commoncrawl.org/
    site: Common Crawl
  - id: bahdanau
    title: "Neural Machine Translation by Jointly Learning to Align and Translate"
    url: https://arxiv.org/abs/1409.0473
    author: Dzmitry Bahdanau, Kyunghyun Cho, Yoshua Bengio
    date: "2014"
  - id: adam
    title: "Adam: A Method for Stochastic Optimization"
    url: https://arxiv.org/abs/1412.6980
    author: Diederik Kingma, Jimmy Ba
    date: "2014"
  - id: resnet
    title: "Deep Residual Learning for Image Recognition"
    url: https://arxiv.org/abs/1512.03385
    author: Kaiming He 等
    date: "2015"
  - id: vaswani
    title: "Attention Is All You Need"
    url: https://arxiv.org/abs/1706.03762
    author: Ashish Vaswani 等
    site: NeurIPS 2017
    date: "2017"
  - id: hooker
    title: "The Hardware Lottery"
    url: https://arxiv.org/abs/2009.06489
    author: Sara Hooker
    date: "2020"
  - id: kaplan
    title: "Scaling Laws for Neural Language Models"
    url: https://arxiv.org/abs/2001.08361
    author: Jared Kaplan 等
    date: "2020"
  - id: gpt3
    title: "Language Models are Few-Shot Learners"
    url: https://arxiv.org/abs/2005.14165
    author: Tom Brown 等
    date: "2020"
    note: "1750 亿参数、3000 亿 token；附录 D 给出训练计算量 3.14×10²³ FLOP。"
  - id: megatron
    title: "Efficient Large-Scale Language Model Training on GPU Clusters Using Megatron-LM"
    url: https://arxiv.org/abs/2104.04473
    author: Deepak Narayanan 等
    site: SC21
    date: "2021"
    note: "估算 1024 张 A100 训练 GPT-3 约需 34 天。"
  - id: chinchilla
    title: "Training Compute-Optimal Large Language Models"
    url: https://arxiv.org/abs/2203.15556
    author: Jordan Hoffmann 等
    date: "2022"
  - id: instructgpt
    title: "Training language models to follow instructions with human feedback"
    url: https://arxiv.org/abs/2203.02155
    author: Long Ouyang 等
    date: "2022"
  - id: llama3
    title: "The Llama 3 Herd of Models"
    url: https://arxiv.org/abs/2407.21783
    author: Llama Team, Meta
    date: "2024"
    note: "约 15 万亿 token；405B 模型训练计算量 3.8×10²⁵ FLOP。"
  - id: sutton
    title: "The Bitter Lesson"
    url: http://www.incompleteideas.net/IncIdeas/BitterLesson.html
    author: Rich Sutton
    date: 2019-03-13
  - id: epoch
    title: "Notable AI Models"
    url: https://epoch.ai/data/notable-ai-models
    site: Epoch AI
    note: "历代模型训练计算量的数据库，AlexNet 等早期模型的计算量量级可在此对照。"
    further: true
  - id: shannon48
    title: "A Mathematical Theory of Communication"
    url: https://doi.org/10.1002/j.1538-7305.1948.tb01338.x
    author: Claude Shannon
    site: Bell System Technical Journal 27(3)
    date: "1948"
  - id: rissanen
    title: "Generalized Kraft Inequality and Arithmetic Coding"
    url: https://doi.org/10.1147/rd.203.0198
    author: Jorma Rissanen
    site: IBM Journal of Research and Development 20(3)
    date: "1976"
    note: "算术编码的奠基论文之一，同年 Pasco 的博士论文也独立提出了类似方法。"
  - id: solomonoff
    title: "A Formal Theory of Inductive Inference, Part I"
    url: https://doi.org/10.1016/S0019-9958(64)90223-2
    author: Ray Solomonoff
    site: Information and Control 7(1)
    date: "1964"
  - id: kolmogorov
    title: "Three approaches to the quantitative definition of information"
    author: Andrey Kolmogorov
    site: Problems of Information Transmission 1(1)
    date: "1965"
  - id: hutter
    title: "500'000€ Prize for Compressing Human Knowledge"
    url: http://prize.hutter1.net/
    author: Marcus Hutter
    site: Hutter Prize
    note: "2006 年设立，最初用 100MB 的 enwik8，2020 年改为 1GB 的 enwik9、奖金池增至 50 万欧元；主办方认为文本压缩和 AI 是等价的问题。历届获奖者见 Wikipedia 的 Hutter Prize 词条。"
  - id: ppm
    title: "Data Compression Using Adaptive Coding and Partial String Matching"
    url: https://doi.org/10.1109/TCOM.1984.1096090
    author: John Cleary, Ian Witten
    site: IEEE Transactions on Communications 32(4)
    date: "1984"
  - id: ltcb
    title: "Large Text Compression Benchmark"
    url: https://mattmahoney.net/dc/text.html
    author: Matt Mahoney
    note: "enwik9 压缩榜，表中的 gzip、bzip2、xz、PPMd、paq8hp12、cmix、nncp 各版本的大小都取自这里。nncp 的作者是 Fabrice Bellard。"
  - id: ilya
    title: "An Observation on Generalization"
    url: https://simons.berkeley.edu/news/observation-generalization
    author: Ilya Sutskever
    site: Simons Institute
    date: "2023"
  - id: deletang
    title: "Language Modeling Is Compression"
    url: https://arxiv.org/abs/2309.10668
    author: Grégoire Delétang 等
    site: ICLR 2024
    date: "2023"
    note: "Chinchilla 70B 把 ImageNet 图块压到 43.4%、LibriSpeech 压到 16.4%，PNG 和 FLAC 分别是 58.5% 和 30.3%；不计模型本身的大小。"
---

在[《Transformer 是怎么训练的》](/blog/transformer-training-by-hand/)里，我们手算过一个只有一个旋钮的机器：损失是 $L=(2w-6)^2$，每一步按下面这条规则拧一下旋钮。

```text
w ← w − 学习率 × dL/dw
```

后面那个 10 个参数的 Transformer 用的也是这一行，只不过 $w$ 从一个数变成了十个数，损失换成了交叉熵，训练时屏幕上打出 0.615、0.594、0.572……。交叉熵这个数还有一个更朴素的读法：把对数的底换成 2，它就是模型每猜一个字，平均要花几个**比特**去纠正自己。花得越少，拿它去压缩文字就压得越小。所以训练一个语言模型，换个说法就是在训练一个压缩器。

顺着这个读法往前找，会碰到一句更大胆的话：**压缩即智能**。2006 年起有人专门悬赏，谁能把一份维基百科压得更小，谁就领奖，理由是压缩文字和人工智能本来就是同一个问题。

这样看，大模型像是几件旧东西拼起来的：梯度下降是 1847 年一位法国数学家为了算行星轨道写下的，“预测就是压缩”是香农在 1948 年说清楚的，求导的链式法则更是莱布尼茨在 1676 年就写出来了。**既然这些都早就有了，为什么大模型直到最近几年才出现？之前的一百多年里，这些办法没被拿来做这件事吗？还是做过，没做成？** 一个压缩器，又凭什么看起来像有了智能？

我们顺着时间往下走，在每一个路口停下来看两件事：当时的人手里有什么工具，他们能把世界上的文字“压”到多小。

## 一、梯度下降的来历

### 最小二乘：用误差去调参数

1801 年元旦，意大利天文学家皮亚齐发现了一颗新的小行星，也就是后来的谷神星。他跟踪了 40 天，谷神星就转到太阳背后看不见了。要在半年后重新找到它，得从这 40 天的观测里反推出它的轨道。

一条椭圆轨道由 6 个数确定（轨道的大小、形状、倾角等，天文学上叫轨道根数）。观测有误差，6 个数怎么取都不可能让每一次观测都对上，所以问题变成：**哪一组数让总误差最小？** 高斯的回答是把每次观测的误差平方、加起来，让这个和最小。他用这个办法算出的轨道，让天文学家在年底重新找到了谷神星。勒让德在 1805 年先发表了这个方法，高斯 1809 年发表时说自己 1795 年就在用，这就是**最小二乘法**[^stigler]。

这里已经出现了训练的全部要素：一组要调的**参数**（6 个轨道根数），一个衡量好坏的**损失**（误差平方和），还有一批**数据**（观测记录）。只是最小二乘的损失形状特别好，令导数等于零，解一组线性方程就能一步得到答案，用不着“一步步走”。

换个角度看，这也是一次压缩。40 天的观测记录被概括成了 6 个数，再加上一串很小的误差：只要记住这 6 个数和误差，就能把全部观测原样“解压”回来，规律找得越准，误差越小，要另外记的东西就越少。这种“规律 + 残差”的写法，在 [30u30 那篇](/blog/ilya-30u30/)里叫两部分码，后面还会反复出现。

### Cauchy：一步步往下走

方程不是线性的时候，就没有一步到位的公式了。1847 年 10 月，柯西（Cauchy）向法国科学院提交了一篇只有三页的短文，讨论的还是天文计算：要解一组复杂的方程，不妨把所有方程的误差平方加起来变成一个函数，然后**沿着这个函数下降最快的方向，走一小步，再重新看方向，再走一小步**[^cauchy]。这就是梯度下降。今天的优化教科书都把这个方法记在柯西名下，不过他在文中并没有认真分析这样走会不会收敛[^lemarechal]。

一百年后的 1951 年，Robbins 和 Monro 又补上了一块：如果每次只能看到带噪声的测量，比如每次只抽一部分数据来算梯度，这样一步步走下去仍然能逼近答案[^robbins]。这是今天“随机梯度下降”每次只取一小批样本的数学源头。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 336" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="lh1-title lh1-desc">
<title id="lh1-title">同一个公式的三次使用</title>
<desc id="lh1-desc">Cauchy 1847 年算轨道、本站手算的 10 参数 Transformer 和 GPT-3 用的是同一条梯度下降规则，参数个数从 6 涨到 1750 亿。</desc>
<defs>
<marker id="lh1-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="lh1-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="336" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="768" height="60" rx="8"/>
<text class="t-zone" x="32" y="44">同一条更新规则</text>
<text class="t-mono" x="32" y="64">参数 ← 参数 − 学习率 × 损失对参数的导数</text>
<text class="t-say" x="768" y="64" text-anchor="end">三处用的是同一行</text>
<rect class="dd-zone" x="16" y="92" width="768" height="228" rx="8"/>
<rect class="dd-card" x="32" y="108" width="240" height="162" rx="6"/>
<path class="dd-card-head" d="M32.6 140 V114 Q32.6 108.6 38 108.6 H266 Q271.4 108.6 271.4 114 V140 Z"/>
<text class="t-name" x="48" y="129">Cauchy</text>
<text class="t-note" x="256" y="129" text-anchor="end">1847 · 算轨道</text>
<text class="t-mono" x="48" y="157">参数个数</text>
<text class="t-val t-acc" x="256" y="157" text-anchor="end">6 个轨道根数</text>
<line class="dd-hair" x1="32" y1="166" x2="272" y2="166"/>
<text class="t-mono" x="48" y="183">损失</text>
<text class="t-val" x="256" y="183" text-anchor="end">观测误差平方和</text>
<line class="dd-hair" x1="32" y1="192" x2="272" y2="192"/>
<text class="t-mono" x="48" y="209">求导</text>
<text class="t-val" x="256" y="209" text-anchor="end">手算</text>
<line class="dd-hair" x1="32" y1="218" x2="272" y2="218"/>
<text class="t-mono" x="48" y="235">训练步数</text>
<text class="t-val" x="256" y="235" text-anchor="end">几步</text>
<line class="dd-hair" x1="32" y1="244" x2="272" y2="244"/>
<text class="t-mono" x="48" y="261">一步的乘法</text>
<text class="t-val" x="256" y="261" text-anchor="end">约几十次</text>
<rect class="dd-card" x="280" y="108" width="240" height="162" rx="6"/>
<path class="dd-card-head" d="M280.6 140 V114 Q280.6 108.6 286 108.6 H514 Q519.4 108.6 519.4 114 V140 Z"/>
<text class="t-name" x="296" y="129">手算 Transformer</text>
<text class="t-note" x="504" y="129" text-anchor="end">本站 · 10 参数</text>
<text class="t-mono" x="296" y="157">参数个数</text>
<text class="t-val t-acc" x="504" y="157" text-anchor="end">10</text>
<line class="dd-hair" x1="280" y1="166" x2="520" y2="166"/>
<text class="t-mono" x="296" y="183">损失</text>
<text class="t-val" x="504" y="183" text-anchor="end">交叉熵</text>
<line class="dd-hair" x1="280" y1="192" x2="520" y2="192"/>
<text class="t-mono" x="296" y="209">求导</text>
<text class="t-val" x="504" y="209" text-anchor="end">反向传播</text>
<line class="dd-hair" x1="280" y1="218" x2="520" y2="218"/>
<text class="t-mono" x="296" y="235">训练步数</text>
<text class="t-val" x="504" y="235" text-anchor="end">几十步</text>
<line class="dd-hair" x1="280" y1="244" x2="520" y2="244"/>
<text class="t-mono" x="296" y="261">一步的乘法</text>
<text class="t-val" x="504" y="261" text-anchor="end">约几百次</text>
<rect class="dd-card" x="528" y="108" width="240" height="162" rx="6"/>
<path class="dd-card-head" d="M528.6 140 V114 Q528.6 108.6 534 108.6 H762 Q767.4 108.6 767.4 114 V140 Z"/>
<text class="t-name" x="544" y="129">GPT-3</text>
<text class="t-note" x="752" y="129" text-anchor="end">2020</text>
<text class="t-mono" x="544" y="157">参数个数</text>
<text class="t-val t-acc" x="752" y="157" text-anchor="end">1750 亿</text>
<line class="dd-hair" x1="528" y1="166" x2="768" y2="166"/>
<text class="t-mono" x="544" y="183">损失</text>
<text class="t-val" x="752" y="183" text-anchor="end">交叉熵</text>
<line class="dd-hair" x1="528" y1="192" x2="768" y2="192"/>
<text class="t-mono" x="544" y="209">求导</text>
<text class="t-val" x="752" y="209" text-anchor="end">反向传播</text>
<line class="dd-hair" x1="528" y1="218" x2="768" y2="218"/>
<text class="t-mono" x="544" y="235">训练步数</text>
<text class="t-val" x="752" y="235" text-anchor="end">约 9.4 万</text>
<line class="dd-hair" x1="528" y1="244" x2="768" y2="244"/>
<text class="t-mono" x="544" y="261">一步的乘法</text>
<text class="t-val" x="752" y="261" text-anchor="end">约 3.4×10¹⁸</text>
<text class="t-say" x="32" y="304">公式一个字没改，变的是参数个数（强调色）和每一步要做的乘法。</text>
</svg>
</div>
<figcaption>图 1 · 从 1847 年到 2020 年，梯度下降的公式没变，变的是参数个数和每一步的计算量</figcaption>
</figure>

图 1把三个用了同一条规则的问题摆在一起。柯西的问题只有几个参数，手算几步就够了；GPT-3 有 1750 亿个参数，一共走了大约 9.4 万步，每一步要做的乘法是 $10^{18}$ 量级。公式本身一个字也没改。

所以，**调参数的数学在 19 世纪就齐了**。可是要把它用在文字上，还得先回答一个问题：对一个读文字的机器来说，什么叫“调好了”？损失应该衡量什么？

## 二、预测与压缩

### 香农：一个字值几个比特

1948 年，贝尔实验室的香农发表了《通信的数学理论》[^shannon48]，回答的是一个工程问题：一段消息最少要用几个比特才能传过去？

他的答案是，**消息里每个符号的代价取决于它有多出人意料**。一个出现概率是 $p$ 的符号，最少要用 $\log_2(1/p)$ 个比特来记：概率 1/2 的要 1 比特，概率 1/1024 的要 10 比特，必然出现的不花比特。所以压缩的关键不在怎么编码，而在**能不能把下一个符号猜准**。如果用来编码的概率是一个模型 $q$ 给的，而文字真实的规律是 $p$，平均每个符号要花的比特数就是两者的**交叉熵**。这正是今天训练语言模型时用的损失，只是对数的底一般取 $e$。**损失每降一点，就是这个模型当压缩器时压得更小一点。**

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 336" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="lhb-title lhb-desc">
<title id="lhb-title">每个字的码长</title>
<desc id="lhb-desc">用博客文章训练的 2 阶字符模型给“每个参数减去学习率乘它的偏导数”逐字计算码长，可预测的字只要不到 1 比特，意外的字要十几比特。</desc>
<defs>
<marker id="lhb-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="lhb-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="336" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="768" height="304" rx="8"/>
<text class="t-zone" x="32" y="44">2 阶字符模型给每个字报的码长（比特，越高越意外）</text>
<line class="dd-hair" x1="48" y1="236.0" x2="716" y2="236.0"/>
<text class="t-note" x="44" y="240.0" text-anchor="end">0</text>
<line class="dd-hair" x1="48" y1="170.4" x2="716" y2="170.4"/>
<text class="t-note" x="44" y="174.4" text-anchor="end">8</text>
<line class="dd-hair" x1="48" y1="104.8" x2="716" y2="104.8"/>
<text class="t-note" x="44" y="108.8" text-anchor="end">16</text>
<rect class="dd-bar" x="62" y="190.2" width="32" height="45.8" rx="2"/>
<text class="t-mono" x="78.0" y="184.2" text-anchor="middle">5.6</text>
<text class="t-name" x="78.0" y="258" text-anchor="middle">每</text>
<rect class="dd-fill-acc" x="106" y="223.2" width="32" height="12.8" rx="2"/>
<text class="t-mono" x="122.0" y="217.2" text-anchor="middle">1.6</text>
<text class="t-name t-acc" x="122.0" y="258" text-anchor="middle">个</text>
<rect class="dd-bar" x="150" y="184.9" width="32" height="51.1" rx="2"/>
<text class="t-mono" x="166.0" y="178.9" text-anchor="middle">6.2</text>
<text class="t-name" x="166.0" y="258" text-anchor="middle">参</text>
<rect class="dd-fill-acc" x="194" y="234.6" width="32" height="1.5" rx="2"/>
<text class="t-mono" x="210.0" y="228.6" text-anchor="middle">0.2</text>
<text class="t-name t-acc" x="210.0" y="258" text-anchor="middle">数</text>
<rect class="dd-bar" x="238" y="88.7" width="32" height="147.3" rx="2"/>
<text class="t-mono" x="254.0" y="82.7" text-anchor="middle">18.0</text>
<text class="t-name" x="254.0" y="258" text-anchor="middle">减</text>
<rect class="dd-bar" x="282" y="213.1" width="32" height="22.9" rx="2"/>
<text class="t-mono" x="298.0" y="207.1" text-anchor="middle">2.8</text>
<text class="t-name" x="298.0" y="258" text-anchor="middle">去</text>
<rect class="dd-bar" x="326" y="149.8" width="32" height="86.2" rx="2"/>
<text class="t-mono" x="342.0" y="143.8" text-anchor="middle">10.5</text>
<text class="t-name" x="342.0" y="258" text-anchor="middle">学</text>
<rect class="dd-bar" x="370" y="195.3" width="32" height="40.7" rx="2"/>
<text class="t-mono" x="386.0" y="189.3" text-anchor="middle">5.0</text>
<text class="t-name" x="386.0" y="258" text-anchor="middle">习</text>
<rect class="dd-bar" x="414" y="205.7" width="32" height="30.3" rx="2"/>
<text class="t-mono" x="430.0" y="199.7" text-anchor="middle">3.7</text>
<text class="t-name" x="430.0" y="258" text-anchor="middle">率</text>
<rect class="dd-bar" x="458" y="107.6" width="32" height="128.4" rx="2"/>
<text class="t-mono" x="474.0" y="101.6" text-anchor="middle">15.7</text>
<text class="t-name" x="474.0" y="258" text-anchor="middle">乘</text>
<rect class="dd-bar" x="502" y="147.6" width="32" height="88.4" rx="2"/>
<text class="t-mono" x="518.0" y="141.6" text-anchor="middle">10.8</text>
<text class="t-name" x="518.0" y="258" text-anchor="middle">它</text>
<rect class="dd-bar" x="546" y="211.5" width="32" height="24.5" rx="2"/>
<text class="t-mono" x="562.0" y="205.5" text-anchor="middle">3.0</text>
<text class="t-name" x="562.0" y="258" text-anchor="middle">的</text>
<rect class="dd-bar" x="590" y="143.7" width="32" height="92.3" rx="2"/>
<text class="t-mono" x="606.0" y="137.7" text-anchor="middle">11.3</text>
<text class="t-name" x="606.0" y="258" text-anchor="middle">偏</text>
<rect class="dd-bar" x="634" y="93.2" width="32" height="142.8" rx="2"/>
<text class="t-mono" x="650.0" y="87.2" text-anchor="middle">17.4</text>
<text class="t-name" x="650.0" y="258" text-anchor="middle">导</text>
<rect class="dd-bar" x="678" y="200.9" width="32" height="35.1" rx="2"/>
<text class="t-mono" x="694.0" y="194.9" text-anchor="middle">4.3</text>
<text class="t-name" x="694.0" y="258" text-anchor="middle">数</text>
<text class="t-say" x="32" y="288">“参”后面的“数”只要 0.2 比特，“它的偏导数”里的“导”要 17 比特：猜得准，就花得少。</text>
<text class="t-say" x="32" y="308">整句 15 个字合计 116 比特；用 UTF-8 原样存是 360 比特。</text>
</svg>
</div>
<figcaption>图 2 · 一个字的码长等于它的概率取负对数：模型猜得越准，存下这句话要的比特越少</figcaption>
</figure>

图 2是我们用本站文章训练的一个小模型（第七节还会用到它），给“每个参数减去学习率乘它的偏导数”这句话逐字报的码长。“参”后面跟“数”几乎是必然的，只花 0.2 比特；“减”“导”这几个字它没料到，各要十几比特。整句 15 个字合计 116 比特，而用 UTF-8 原样存要 360 比特。

1951 年，香农又做了一个实验：给人看一段英文的前半截，让人一个字母一个字母地猜后面，记下要猜几次才猜中[^shannon]。由此估出来，对一个熟悉英语的人，每个字母平均只有 1 比特左右的信息量，而计算机存一个字母要 8 比特。**一段文字里大部分内容都是可以猜出来的**，猜得越像人，压得越小。

### 算术编码：预测器就是压缩器

香农的定理说的是“最少要多少比特”，并没有给出一个真能达到它的编码方法。七十年代，里萨宁（Rissanen）等人发明的**算术编码**补上了这一环[^rissanen]：只要有一个模型能对下一个符号给出概率，算术编码就能把整段文字压到和 $\sum\log_2(1/q)$ 只差几个比特的长度，解压时用同一个模型一边猜一边还原。

从此预测和压缩成了一回事的两面：**任何一个预测器，配上算术编码就是一个压缩器；它压得多小，只看它猜得多准。**

### 最短的描述

还剩一个问题：怎样才能猜得最准？六十年代，所罗门诺夫（Solomonoff）和柯尔莫哥洛夫（Kolmogorov）分别给出了同一个方向的答案[^solomonoff][^kolmogorov]。一段数据的复杂度，是能输出它的**最短程序**的长度；预测下一个符号时，越短的解释越可信。一串 0101……0101 再长，用一行“重复 01”就能写出来；一串真随机的比特，最短的写法就是把它原样抄一遍。

这套理论把“找到规律”和“压缩”画上了等号：**能把数据压到最短的那个程序，就是找到了数据里全部规律的那个程序**，用它来预测也最准。这就是“压缩即智能”的理论根源。后来胡特（Marcus Hutter）在此基础上构造了一个理论上最优的智能体，并在 2006 年设立了开头提到的那个压缩奖[^hutter]。

问题是，最短程序是不可计算的，理论只指了方向，没有给出路。到六十年代为止，目标和工具其实都摆在桌上了：**目标是把下一个字猜准，工具是梯度下降**。缺的是一台能从数据里自己学出规律的机器。

## 三、会学习的机器：感知机

### 把神经元写成数学

1943 年，神经生理学家麦卡洛克和逻辑学家皮茨发表了一篇论文：把一个神经元简化成“把输入加权求和，超过阈值就输出 1，否则输出 0”，这样的单元连起来就能实现任何逻辑运算[^mcculloch]。这是人工神经网络的起点，但它里面的权重是人设计的，不会学习。

1958 年，康奈尔航空实验室的罗森布拉特（Rosenblatt）提出了**感知机**，让权重自己学[^rosenblatt]。规则很简单：拿一个样本，算出输出，如果答错了，就把权重往正确答案的方向挪一点；答对了就不动。他还把它造成了一台机器 Mark I：一块 20×20 共 400 个光电管的“视网膜”，接到 512 个关联单元上，权重存在电位器里，学习的时候由电机去拧电位器[^perceptron-wiki]。权重是实实在在的旋钮，学习就是电机在拧旋钮。

当时的反响非常热烈。《纽约时报》报道说，海军展示了一台电子计算机的“胚胎”，预计它将来能走路、说话、看东西、写字、复制自己，并意识到自己的存在[^nyt1958]。

### 一道 XOR 题

1969 年，明斯基和派珀特出版了《感知机》一书[^minsky]，书里用数学证明了单层感知机能做什么、不能做什么。最常被引用的例子是**异或**（XOR）：两个输入不同时输出 1，相同时输出 0。

为什么单层感知机做不了？单层感知机的判断就是“加权和是否超过阈值”，在平面上就是用一条直线把点分成两边。图 3左边画了 XOR 的四个点，输出 1 的两个点在对角线上，输出 0 的两个点在另一条对角线上，**任何一条直线都没法把它们分开**。我们用 1958 年的规则实际跑了 100 轮，权重一直在摆，每一轮仍然错 4 个。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 360" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="lh2-title lh2-desc">
<title id="lh2-title">XOR 和两种激活函数</title>
<desc id="lh2-desc">单层感知机学不会 XOR；两层网络用阶跃开关时梯度处处为 0，换成 sigmoid 后反向传播能学会。</desc>
<defs>
<marker id="lh2-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="lh2-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="360" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="248" height="328" rx="8"/>
<text class="t-zone" x="32" y="44">XOR：两个输入不同才输出 1</text>
<line class="dd-edge" x1="64" y1="240" x2="238" y2="240"/>
<line class="dd-edge" x1="64" y1="240" x2="64" y2="66"/>
<text class="t-note" x="238" y="258" text-anchor="end">x₁</text>
<text class="t-note" x="56" y="74" text-anchor="end">x₂</text>
<circle class="dd-track" cx="64" cy="240" r="9"/>
<text class="t-mono" x="78" y="262" text-anchor="start">(0,0)→0</text>
<circle class="dd-track" cx="214" cy="90" r="9"/>
<text class="t-mono" x="200" y="76" text-anchor="end">(1,1)→0</text>
<circle class="dd-fill-acc" cx="64" cy="90" r="9"/>
<text class="t-mono" x="78" y="76" text-anchor="start">(0,1)→1</text>
<circle class="dd-fill-acc" cx="214" cy="240" r="9"/>
<text class="t-mono" x="200" y="262" text-anchor="end">(1,0)→1</text>
<line class="dd-edge dd-dash" x1="52" y1="95.0" x2="234" y2="225.0"/>
<text class="t-say" x="32" y="304">任何一条直线都没法把</text>
<text class="t-say" x="32" y="324">实心点和空心点分到两边</text>
<rect class="dd-zone" x="280" y="16" width="248" height="328" rx="8"/>
<text class="t-zone" x="296" y="44">两种“开关”</text>
<line class="dd-hair" x1="304" y1="168" x2="504" y2="168"/>
<line class="dd-hair" x1="404.0" y1="168" x2="404.0" y2="98"/>
<path class="dd-edge" fill="none" d="M304.0 168.0 L305.0 168.0 L306.0 168.0 L307.0 168.0 L308.0 168.0 L309.0 168.0 L310.0 168.0 L311.0 168.0 L312.0 168.0 L313.0 168.0 L314.0 168.0 L315.0 168.0 L316.0 168.0 L317.0 168.0 L318.0 168.0 L319.0 168.0 L320.0 168.0 L321.0 168.0 L322.0 168.0 L323.0 168.0 L324.0 168.0 L325.0 168.0 L326.0 168.0 L327.0 168.0 L328.0 168.0 L329.0 168.0 L330.0 168.0 L331.0 168.0 L332.0 168.0 L333.0 168.0 L334.0 168.0 L335.0 168.0 L336.0 168.0 L337.0 168.0 L338.0 168.0 L339.0 168.0 L340.0 168.0 L341.0 168.0 L342.0 168.0 L343.0 168.0 L344.0 168.0 L345.0 168.0 L346.0 168.0 L347.0 168.0 L348.0 168.0 L349.0 168.0 L350.0 168.0 L351.0 168.0 L352.0 168.0 L353.0 168.0 L354.0 168.0 L355.0 168.0 L356.0 168.0 L357.0 168.0 L358.0 168.0 L359.0 168.0 L360.0 168.0 L361.0 168.0 L362.0 168.0 L363.0 168.0 L364.0 168.0 L365.0 168.0 L366.0 168.0 L367.0 168.0 L368.0 168.0 L369.0 168.0 L370.0 168.0 L371.0 168.0 L372.0 168.0 L373.0 168.0 L374.0 168.0 L375.0 168.0 L376.0 168.0 L377.0 168.0 L378.0 168.0 L379.0 168.0 L380.0 168.0 L381.0 168.0 L382.0 168.0 L383.0 168.0 L384.0 168.0 L385.0 168.0 L386.0 168.0 L387.0 168.0 L388.0 168.0 L389.0 168.0 L390.0 168.0 L391.0 168.0 L392.0 168.0 L393.0 168.0 L394.0 168.0 L395.0 168.0 L396.0 168.0 L397.0 168.0 L398.0 168.0 L399.0 168.0 L400.0 168.0 L401.0 168.0 L402.0 168.0 L403.0 168.0 L404.0 168.0 L405.0 98.0 L406.0 98.0 L407.0 98.0 L408.0 98.0 L409.0 98.0 L410.0 98.0 L411.0 98.0 L412.0 98.0 L413.0 98.0 L414.0 98.0 L415.0 98.0 L416.0 98.0 L417.0 98.0 L418.0 98.0 L419.0 98.0 L420.0 98.0 L421.0 98.0 L422.0 98.0 L423.0 98.0 L424.0 98.0 L425.0 98.0 L426.0 98.0 L427.0 98.0 L428.0 98.0 L429.0 98.0 L430.0 98.0 L431.0 98.0 L432.0 98.0 L433.0 98.0 L434.0 98.0 L435.0 98.0 L436.0 98.0 L437.0 98.0 L438.0 98.0 L439.0 98.0 L440.0 98.0 L441.0 98.0 L442.0 98.0 L443.0 98.0 L444.0 98.0 L445.0 98.0 L446.0 98.0 L447.0 98.0 L448.0 98.0 L449.0 98.0 L450.0 98.0 L451.0 98.0 L452.0 98.0 L453.0 98.0 L454.0 98.0 L455.0 98.0 L456.0 98.0 L457.0 98.0 L458.0 98.0 L459.0 98.0 L460.0 98.0 L461.0 98.0 L462.0 98.0 L463.0 98.0 L464.0 98.0 L465.0 98.0 L466.0 98.0 L467.0 98.0 L468.0 98.0 L469.0 98.0 L470.0 98.0 L471.0 98.0 L472.0 98.0 L473.0 98.0 L474.0 98.0 L475.0 98.0 L476.0 98.0 L477.0 98.0 L478.0 98.0 L479.0 98.0 L480.0 98.0 L481.0 98.0 L482.0 98.0 L483.0 98.0 L484.0 98.0 L485.0 98.0 L486.0 98.0 L487.0 98.0 L488.0 98.0 L489.0 98.0 L490.0 98.0 L491.0 98.0 L492.0 98.0 L493.0 98.0 L494.0 98.0 L495.0 98.0 L496.0 98.0 L497.0 98.0 L498.0 98.0 L499.0 98.0 L500.0 98.0 L501.0 98.0 L502.0 98.0 L503.0 98.0 L504.0 98.0"/>
<text class="t-name" x="304" y="90">阶跃：0 或 1</text>
<text class="t-note" x="304" y="188">除跳变点外斜率处处为 0</text>
<line class="dd-hair" x1="304" y1="296" x2="504" y2="296"/>
<line class="dd-hair" x1="404.0" y1="296" x2="404.0" y2="226"/>
<path class="dd-edge-acc" fill="none" d="M304.0 295.8 L305.0 295.8 L306.0 295.8 L307.0 295.8 L308.0 295.8 L309.0 295.8 L310.0 295.8 L311.0 295.7 L312.0 295.7 L313.0 295.7 L314.0 295.7 L315.0 295.7 L316.0 295.6 L317.0 295.6 L318.0 295.6 L319.0 295.6 L320.0 295.5 L321.0 295.5 L322.0 295.5 L323.0 295.5 L324.0 295.4 L325.0 295.4 L326.0 295.4 L327.0 295.3 L328.0 295.3 L329.0 295.2 L330.0 295.2 L331.0 295.1 L332.0 295.1 L333.0 295.0 L334.0 295.0 L335.0 294.9 L336.0 294.8 L337.0 294.8 L338.0 294.7 L339.0 294.6 L340.0 294.5 L341.0 294.4 L342.0 294.3 L343.0 294.2 L344.0 294.1 L345.0 294.0 L346.0 293.9 L347.0 293.8 L348.0 293.7 L349.0 293.5 L350.0 293.4 L351.0 293.2 L352.0 293.0 L353.0 292.9 L354.0 292.7 L355.0 292.5 L356.0 292.3 L357.0 292.1 L358.0 291.8 L359.0 291.6 L360.0 291.3 L361.0 291.1 L362.0 290.8 L363.0 290.5 L364.0 290.2 L365.0 289.8 L366.0 289.5 L367.0 289.1 L368.0 288.8 L369.0 288.4 L370.0 287.9 L371.0 287.5 L372.0 287.0 L373.0 286.6 L374.0 286.1 L375.0 285.5 L376.0 285.0 L377.0 284.4 L378.0 283.8 L379.0 283.2 L380.0 282.6 L381.0 281.9 L382.0 281.2 L383.0 280.5 L384.0 279.8 L385.0 279.0 L386.0 278.3 L387.0 277.4 L388.0 276.6 L389.0 275.8 L390.0 274.9 L391.0 274.0 L392.0 273.1 L393.0 272.1 L394.0 271.2 L395.0 270.2 L396.0 269.2 L397.0 268.2 L398.0 267.2 L399.0 266.2 L400.0 265.2 L401.0 264.1 L402.0 263.1 L403.0 262.0 L404.0 261.0 L405.0 260.0 L406.0 258.9 L407.0 257.9 L408.0 256.8 L409.0 255.8 L410.0 254.8 L411.0 253.8 L412.0 252.8 L413.0 251.8 L414.0 250.8 L415.0 249.9 L416.0 248.9 L417.0 248.0 L418.0 247.1 L419.0 246.2 L420.0 245.4 L421.0 244.6 L422.0 243.7 L423.0 243.0 L424.0 242.2 L425.0 241.5 L426.0 240.8 L427.0 240.1 L428.0 239.4 L429.0 238.8 L430.0 238.2 L431.0 237.6 L432.0 237.0 L433.0 236.5 L434.0 235.9 L435.0 235.4 L436.0 235.0 L437.0 234.5 L438.0 234.1 L439.0 233.6 L440.0 233.2 L441.0 232.9 L442.0 232.5 L443.0 232.2 L444.0 231.8 L445.0 231.5 L446.0 231.2 L447.0 230.9 L448.0 230.7 L449.0 230.4 L450.0 230.2 L451.0 229.9 L452.0 229.7 L453.0 229.5 L454.0 229.3 L455.0 229.1 L456.0 229.0 L457.0 228.8 L458.0 228.6 L459.0 228.5 L460.0 228.3 L461.0 228.2 L462.0 228.1 L463.0 228.0 L464.0 227.9 L465.0 227.8 L466.0 227.7 L467.0 227.6 L468.0 227.5 L469.0 227.4 L470.0 227.3 L471.0 227.2 L472.0 227.2 L473.0 227.1 L474.0 227.0 L475.0 227.0 L476.0 226.9 L477.0 226.9 L478.0 226.8 L479.0 226.8 L480.0 226.7 L481.0 226.7 L482.0 226.6 L483.0 226.6 L484.0 226.6 L485.0 226.5 L486.0 226.5 L487.0 226.5 L488.0 226.5 L489.0 226.4 L490.0 226.4 L491.0 226.4 L492.0 226.4 L493.0 226.3 L494.0 226.3 L495.0 226.3 L496.0 226.3 L497.0 226.3 L498.0 226.2 L499.0 226.2 L500.0 226.2 L501.0 226.2 L502.0 226.2 L503.0 226.2 L504.0 226.2"/>
<text class="t-name" x="304" y="218">sigmoid：平滑的 S 形</text>
<text class="t-note" x="304" y="316">每一处都有斜率，可以求导</text>
<rect class="dd-zone" x="544" y="16" width="240" height="328" rx="8"/>
<text class="t-zone" x="560" y="44">实测</text>
<rect class="dd-card" x="560" y="64" width="208" height="58" rx="6"/>
<path class="dd-card-head" d="M560.6 96 V70 Q560.6 64.6 566 64.6 H762 Q767.4 64.6 767.4 70 V96 Z"/>
<text class="t-name" x="576" y="85">单层感知机</text>
<text class="t-note" x="752" y="85" text-anchor="end">1958 规则</text>
<text class="t-mono" x="576" y="113">100 轮后</text>
<text class="t-val" x="752" y="113" text-anchor="end">每轮仍错 4 个</text>
<rect class="dd-card" x="560" y="136" width="208" height="58" rx="6"/>
<path class="dd-card-head" d="M560.6 168 V142 Q560.6 136.6 566 136.6 H762 Q767.4 136.6 767.4 142 V168 Z"/>
<text class="t-name" x="576" y="157">两层 · 阶跃</text>
<text class="t-note" x="752" y="157" text-anchor="end">数值梯度</text>
<text class="t-mono" x="576" y="185">∂L/∂W₁</text>
<text class="t-val" x="752" y="185" text-anchor="end">0  0  0  0</text>
<rect class="dd-card" x="560" y="208" width="208" height="84" rx="6"/>
<path class="dd-card-head" d="M560.6 240 V214 Q560.6 208.6 566 208.6 H762 Q767.4 208.6 767.4 214 V240 Z"/>
<text class="t-name" x="576" y="229">两层 · sigmoid</text>
<text class="t-note" x="752" y="229" text-anchor="end">反向传播</text>
<text class="t-mono" x="576" y="257">100 个起点</text>
<text class="t-val t-acc" x="752" y="257" text-anchor="end">78 个学会</text>
<line class="dd-hair" x1="560" y1="266" x2="768" y2="266"/>
<text class="t-mono" x="576" y="283">中位步数</text>
<text class="t-val" x="752" y="283" text-anchor="end">629</text>
<text class="t-say" x="560" y="320">换成平滑开关，梯度才流得动</text>
</svg>
</div>
<figcaption>图 3 · XOR 难住了单层感知机；多层网络要能用梯度训练，开关必须换成平滑的</figcaption>
</figure>

多加一层就能表示 XOR：先用两个单元分别判断“x₁ 且非 x₂”“x₂ 且非 x₁”，再用一个单元把两者“或”起来。问题在于**怎么训练中间那一层**。感知机的规则是“看输出对不对，再改权重”，可中间层的单元没有自己的标准答案，它们答得对不对，只能间接从最后的输出上看出来。这叫作“功劳分配”问题：最后错了，该怪中间哪个单元？

### 第一次尝试：直接对阶跃网络求导

既然链式法则 1676 年就有了，我们自然会想：把误差对中间层的权重求导，不就知道该怪谁了吗？

这条路在感知机上走不通。感知机的开关是**阶跃函数**，输入过了阈值就是 1，没过就是 0，图 3中间上面那条线。它除了跳变的那一点，斜率处处是 0：把权重稍微拧一点，输出纹丝不动，损失也纹丝不动。我们给一个用阶跃开关的两层网络算了数值梯度，第一层四个权重的导数全是 0。导数没有提供任何“往哪边调”的信息。

### 解决：换一个平滑的开关

办法是把开关换成平滑的 **sigmoid** 函数 $\sigma(z)=1/(1+e^{-z})$，图 3中间下面那条 S 形曲线。它在 0 附近从 0 平滑地升到 1，每一处都有斜率，于是每个权重都能求出“拧一点，损失变多少”。同样两层的网络换上 sigmoid，用梯度下降训练，100 个随机起点里有 78 个学会了 XOR。

剩下的 22 个，后面还会再提到。这里先回到功劳分配：**平滑开关让中间层的导数存在了，但网络一大，怎么把这么多导数算出来？**

## 四、反向传播：求导的代价

### 现象：导数存在，不等于算得起

求一个参数的导数，最朴素的办法是**数值差分**：把这个参数加一点点，跑一遍网络算损失；再减一点点，再跑一遍；两个损失相减，除以改动量。我们在训练那篇文章里就用它来核对手算的梯度。

这个办法的问题在于，**每个参数都要单独跑两遍**。网络有 N 个参数，一次完整的梯度就要跑 2N 遍前向。下面是我们实测的一个三层小网络，参数从 84 个加到 106 万个。

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="lh3-title">算一次完整梯度要多久（3 层 tanh 网络，64 个样本）</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="lh3-title">
<colgroup><col style="width:20%"><col><col><col><col class="hl"></colgroup>
<thead>
<tr><th scope="col">参数个数</th><th scope="col">一次前向</th><th scope="col">反向传播</th><th scope="col">数值差分</th><th scope="col" class="acc">差分 / 反传</th></tr>
</thead>
<tbody>
<tr><th scope="row" class="mono num">84</th><td class="mono num">10 µs</td><td class="mono num">30 µs</td><td class="mono num">2.0 ms</td><td class="mono num acc">68 倍</td></tr>
<tr><th scope="row" class="mono num">528</th><td class="mono num">22 µs</td><td class="mono num">70 µs</td><td class="mono num">23.3 ms</td><td class="mono num acc">332 倍</td></tr>
<tr><th scope="row" class="mono num">5,184</th><td class="mono num">50 µs</td><td class="mono num">170 µs</td><td class="mono num">423.6 ms</td><td class="mono num acc">2,485 倍</td></tr>
<tr><th scope="row" class="mono num">69,888</th><td class="mono num">230 µs</td><td class="mono num">800 µs</td><td class="mono num">32.3 秒*</td><td class="mono num acc">40,297 倍</td></tr>
<tr><th scope="row" class="mono num">1,065,984</th><td class="mono num">1.8 ms</td><td class="mono num">8.5 ms</td><td class="mono num">57 分钟*</td><td class="mono num acc">406,838 倍</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">数值差分要把每个参数各推一下、各算两次前向。带 * 的两行只实测了 2000 个参数，按比例外推。两种方法算出的梯度最大相差 1e-11。</p>
</div>
<figcaption>表 4 · 反向传播的耗时只是前向的几倍，和参数个数无关；数值差分的耗时随参数个数线性增长</figcaption>
</figure>

表 4的最后一列就是问题所在：参数越多，数值差分比反向传播慢得越多，到一百万个参数时已经慢了几十万倍。照这个比例推到 GPT-3 的 1750 亿个参数，数值差分算一次梯度要跑 3500 亿遍前向，走完一步训练的时间够反向传播走完整个训练好多遍。

### 原因：链式法则里有大量重复计算

为什么反向传播的耗时和参数个数无关？在训练那篇文章的第五节，我们从损失往回算过 10 个偏导数：先算损失对输出的导数，再用它算出对上一层的导数，一层一层往回传，**每一层的中间结果只算一次，被后面所有参数共享**。数值差分没有这种共享，每推一个参数都从头算一遍。

所以反向传播并不是新的数学，它只是**链式法则的一种算法安排**：从输出往输入的方向套链式法则，代价大约是前向的两三倍，表 4里实测是 3 到 5 倍。这也是今天估算训练计算量时常说的“前向加反向约等于 3 倍前向”的来历。

### 它被发明了好几次

这个安排有好几个独立的发明者[^griewank]。1970 年，芬兰的林纳因马（Linnainmaa）在硕士论文里写出了**反向模式自动微分**，用来估计程序里舍入误差的累积，跟神经网络没有关系。1974 年，沃博斯（Werbos）在哈佛的博士论文里把它用到了神经网络上，但没有引起注意。直到 1986 年，鲁梅尔哈特、辛顿和威廉姆斯在《自然》上发表论文，演示了用反向传播训练的多层网络能学出有意义的中间表示，这个方法才在神经网络圈子里流行起来[^rumelhart]。

到 1986 年，**训练一个多层网络需要的东西已经全部齐了**：平滑的开关、反向传播、随机梯度下降，再加上第二节的目标，猜准下一个字。那为什么大模型又等了三十多年？

## 五、1986 年以后的二十年

### 它其实被用起来了

先回答开头那个问题：这些能力并不是没人用。1989 年，贝尔实验室的杨立昆（LeCun）用反向传播训练了一个卷积网络，识别美国邮政信件上的手写邮编，错误率约 1%[^lecun89]。这个网络有 9760 个参数，在一台 SUN-4/260 工作站上训练了 3 天。2022 年卡帕西（Karpathy）用 PyTorch 复现了它，在一台 M1 MacBook Air 的 CPU 上跑完只要 90 秒左右[^karpathy]，算法一样，快了约 3000 倍。后来这一系列网络被用来识别支票上的手写数字，进入了真实的银行业务。

用神经网络猜下一个词也有人做了。2003 年，本吉奥（Bengio）等人做出了第一个神经网络语言模型，猜下一个词比当时主流的统计方法更准[^bengio03]。

所以 1986 年以后的问题不是“没人想到”，而是**做出来的东西只在小问题上管用，一做深、做大就不灵了**。

### 现象：网络一深，前面的层就学不动

1991 年，霍赫赖特（Hochreiter）在毕业论文里分析了一个现象[^hochreiter]，1994 年本吉奥等人又从另一个角度证明了它[^bengio94]：梯度从输出往回传的时候，每经过一层要乘一次这一层开关的斜率。sigmoid 的斜率最大只有 0.25，层数一多，梯度就按指数缩小。

我们搭了一个 20 层的网络，在刚初始化的时候量了每一层权重梯度的大小，结果画在图 5里。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 360" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="lh4-title lh4-desc">
<title id="lh4-title">梯度消失</title>
<desc id="lh4-desc">20 层 sigmoid 网络从输出往回传，梯度每层缩小，到第 1 层只剩 1e-12；ReLU 网络各层梯度在同一量级。</desc>
<defs>
<marker id="lh4-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="lh4-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="360" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="768" height="328" rx="8"/>
<text class="t-zone" x="32" y="44">20 层网络刚初始化时，每一层权重梯度的大小（对数坐标）</text>
<line class="dd-hair" x1="96" y1="300.0" x2="760" y2="300.0"/>
<text class="t-note" x="88" y="304.0" text-anchor="end">10⁻¹³</text>
<line class="dd-hair" x1="96" y1="252.8" x2="760" y2="252.8"/>
<text class="t-note" x="88" y="256.8" text-anchor="end">10⁻¹⁰</text>
<line class="dd-hair" x1="96" y1="205.6" x2="760" y2="205.6"/>
<text class="t-note" x="88" y="209.6" text-anchor="end">10⁻⁷</text>
<line class="dd-hair" x1="96" y1="158.4" x2="760" y2="158.4"/>
<text class="t-note" x="88" y="162.4" text-anchor="end">10⁻⁴</text>
<line class="dd-hair" x1="96" y1="111.2" x2="760" y2="111.2"/>
<text class="t-note" x="88" y="115.2" text-anchor="end">10⁻¹</text>
<line class="dd-hair" x1="96" y1="64.0" x2="760" y2="64.0"/>
<text class="t-note" x="88" y="68.0" text-anchor="end">10²</text>
<text class="t-note" x="96.0" y="320" text-anchor="middle">第 1 层</text>
<text class="t-note" x="243.6" y="320" text-anchor="middle">第 5 层</text>
<text class="t-note" x="428.0" y="320" text-anchor="middle">第 10 层</text>
<text class="t-note" x="612.4" y="320" text-anchor="middle">第 15 层</text>
<text class="t-note" x="760.0" y="320" text-anchor="middle">第 19 层</text>
<path class="dd-edge" fill="none" d="M96.0 86.8 L132.9 79.9 L169.8 77.7 L206.7 76.3 L243.6 75.6 L280.4 75.3 L317.3 74.1 L354.2 73.6 L391.1 74.0 L428.0 73.0 L464.9 72.3 L501.8 72.0 L538.7 70.6 L575.6 72.0 L612.4 70.9 L649.3 70.9 L686.2 69.6 L723.1 69.7 L760.0 69.9"/>
<circle class="dd-dot" cx="96.0" cy="86.8" r="3"/>
<circle class="dd-dot" cx="132.9" cy="79.9" r="3"/>
<circle class="dd-dot" cx="169.8" cy="77.7" r="3"/>
<circle class="dd-dot" cx="206.7" cy="76.3" r="3"/>
<circle class="dd-dot" cx="243.6" cy="75.6" r="3"/>
<circle class="dd-dot" cx="280.4" cy="75.3" r="3"/>
<circle class="dd-dot" cx="317.3" cy="74.1" r="3"/>
<circle class="dd-dot" cx="354.2" cy="73.6" r="3"/>
<circle class="dd-dot" cx="391.1" cy="74.0" r="3"/>
<circle class="dd-dot" cx="428.0" cy="73.0" r="3"/>
<circle class="dd-dot" cx="464.9" cy="72.3" r="3"/>
<circle class="dd-dot" cx="501.8" cy="72.0" r="3"/>
<circle class="dd-dot" cx="538.7" cy="70.6" r="3"/>
<circle class="dd-dot" cx="575.6" cy="72.0" r="3"/>
<circle class="dd-dot" cx="612.4" cy="70.9" r="3"/>
<circle class="dd-dot" cx="649.3" cy="70.9" r="3"/>
<circle class="dd-dot" cx="686.2" cy="69.6" r="3"/>
<circle class="dd-dot" cx="723.1" cy="69.7" r="3"/>
<circle class="dd-dot" cx="760.0" cy="69.9" r="3"/>
<path class="dd-edge-acc" fill="none" d="M96.0 280.6 L132.9 257.5 L169.8 247.5 L206.7 238.8 L243.6 228.9 L280.4 218.3 L317.3 208.2 L354.2 198.9 L391.1 188.5 L428.0 179.0 L464.9 168.4 L501.8 158.0 L538.7 148.4 L575.6 139.1 L612.4 129.4 L649.3 119.7 L686.2 110.4 L723.1 100.8 L760.0 90.4"/>
<circle class="dd-fill-acc" cx="96.0" cy="280.6" r="3"/>
<circle class="dd-fill-acc" cx="132.9" cy="257.5" r="3"/>
<circle class="dd-fill-acc" cx="169.8" cy="247.5" r="3"/>
<circle class="dd-fill-acc" cx="206.7" cy="238.8" r="3"/>
<circle class="dd-fill-acc" cx="243.6" cy="228.9" r="3"/>
<circle class="dd-fill-acc" cx="280.4" cy="218.3" r="3"/>
<circle class="dd-fill-acc" cx="317.3" cy="208.2" r="3"/>
<circle class="dd-fill-acc" cx="354.2" cy="198.9" r="3"/>
<circle class="dd-fill-acc" cx="391.1" cy="188.5" r="3"/>
<circle class="dd-fill-acc" cx="428.0" cy="179.0" r="3"/>
<circle class="dd-fill-acc" cx="464.9" cy="168.4" r="3"/>
<circle class="dd-fill-acc" cx="501.8" cy="158.0" r="3"/>
<circle class="dd-fill-acc" cx="538.7" cy="148.4" r="3"/>
<circle class="dd-fill-acc" cx="575.6" cy="139.1" r="3"/>
<circle class="dd-fill-acc" cx="612.4" cy="129.4" r="3"/>
<circle class="dd-fill-acc" cx="649.3" cy="119.7" r="3"/>
<circle class="dd-fill-acc" cx="686.2" cy="110.4" r="3"/>
<circle class="dd-fill-acc" cx="723.1" cy="100.8" r="3"/>
<circle class="dd-fill-acc" cx="760.0" cy="90.4" r="3"/>
<text class="t-name" x="752.0" y="57.9" text-anchor="end">ReLU：3.6 … 42</text>
<text class="t-name t-acc" x="108.0" y="284.6">sigmoid：第 1 层只有 1.7×10⁻¹²</text>
<text class="t-say" x="354.2" y="111.2">sigmoid 的斜率最大 0.25，每往回一层再乘一次</text>
</svg>
</div>
<figcaption>图 5 · 用 sigmoid 的深层网络，梯度传到前几层几乎为零，前面的层学不动</figcaption>
</figure>

用 sigmoid 时，最后一层的梯度是 2.1，第一层只剩 $1.7\times10^{-12}$，差了 12 个数量级；作为对照，$0.25^{19}\approx3.6\times10^{-12}$，几乎就是这个比例。梯度下降按梯度的大小调参数，第一层的参数等于根本没动。这叫作**梯度消失**。图里另一条线换成了 ReLU 开关 $\max(0,z)$，它在正半边的斜率恒为 1，各层梯度在同一个量级。

### 第一次尝试：绕开它

九十年代的人知道这个问题，也找到了一些绕开的办法。1997 年的 LSTM 给循环网络加了一条梯度能原样通过的“传送带”，让它能记住几十步以前的内容[^lstm]，这个结构后来用了二十年。但在大多数任务上，研究者干脆不用深网络。

同一时期，**支持向量机**（SVM）出现了[^svm]。它的训练是一个凸优化问题，只有一个最低点，保证找得到，背后还有一套漂亮的统计学习理论。相比之下，神经网络的损失地形坑坑洼洼，可能卡在局部最低点里出不来，调参全靠经验。前面 XOR 实验里那 22 个没学会的起点，就是卡在了这样的地方。在当时的数据量下，SVM 的效果往往不比神经网络差，于是神经网络在主流学术界冷了大约十年。

### 压缩榜上的手写模型

压缩这条线上，情形也一样。这二十年里最会“猜下一个字节”的程序，都是人手写的统计模型。1984 年的 PPM 按前面几个字节出现过的次数，统计下一个字节的概率[^ppm]，第七节的小实验就是这一类。后来 Mahoney 的 PAQ 系列把几百个这样的手写模型放在一起，再用一个很小的神经网络，按各自最近猜得准不准来给它们加权。

2006 年，胡特设立了压缩奖：把 100MB 的英文维基百科（enwik8）压得更小，就按压小的比例发奖金，理由是压缩文字和人工智能是同一个问题[^hutter]。从 2006 到 2017 年，每一次破纪录的都是在 PAQ 基础上改出来的手写混合模型。神经网络语言模型猜得更准，但慢到没法拿来压 100MB 的文字。“压缩即智能”这个想法有了，比赛也有了，赢的却是手工的统计。

回头看，这二十年里缺的算法其实不多。ReLU 就是 $\max(0,z)$，1990 年也写得出来；到 2011 年格洛罗（Glorot）等人系统地说明它在深层网络里好用[^relu]，才普及开来。那段时间更大的问题在别处：XOR 实验里，隐藏层从 2 个单元加到 3 个，100 个起点里能学会的从 78 个变成 99 个，到 4 个单元就全部学会了。**网络大一点反而更好训练**，可那时的机器养不起大网络，数据也喂不饱它。下面我们分别看这两样差多少。

## 六、算力：差了多少个数量级

### 训练一次要多少次运算

一个大语言模型训练一次要多少次浮点运算，有一个很好用的估算：每个参数、每个 token，前向约 2 次运算，加上反向一共约 6 次。GPT-3 有 1750 亿个参数、看了 3000 亿个 token：

$$6\times1.75\times10^{11}\times3\times10^{11}\approx3.1\times10^{23}$$

GPT-3 论文附录给出的数字是 $3.14\times10^{23}$[^gpt3]。这个数有多大？我们把历史上几台有代表性的机器拿来对比。

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="lh6-title">如果在当年的机器上训练 GPT-3（3.14×10²³ 次浮点运算）</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="lh6-title">
<colgroup><col style="width:22%"><col style="width:10%"><col><col class="hl"><col></colgroup>
<thead>
<tr><th scope="col">机器</th><th scope="col">年份</th><th scope="col">峰值</th><th scope="col" class="acc">要多久</th><th scope="col">备注</th></tr>
</thead>
<tbody>
<tr><th scope="row">Cray-1</th><td class="mono">1976</td><td class="mono num">1.6 亿次/秒</td><td class="mono num acc">约 6218 万年</td><td>约 800 万美元</td></tr>
<tr><th scope="row">ASCI Red 超算</th><td class="mono">1997</td><td class="mono num">1.07 万亿次/秒</td><td class="mono num acc">约 9,316 年</td><td>第一台过万亿次的机器</td></tr>
<tr><th scope="row">一张 GTX 580</th><td class="mono">2010</td><td class="mono num">1.58 万亿次/秒</td><td class="mono num acc">约 6,297 年</td><td>AlexNet 用了两张</td></tr>
<tr><th scope="row">一张 V100</th><td class="mono">2017</td><td class="mono num">125 万亿次/秒</td><td class="mono num acc">约 80 年</td><td>张量核心，半精度</td></tr>
<tr><th scope="row">一张 H100</th><td class="mono">2022</td><td class="mono num">989 万亿次/秒</td><td class="mono num acc">约 10 年</td><td>张量核心，BF16</td></tr>
<tr class="dt-group"><th colspan="5" scope="rowgroup">真实做法：成千上万张卡一起算</th></tr>
<tr><th scope="row">1024 张 A100</th><td class="mono">2021</td><td class="mono num">按实测效率</td><td class="mono num acc good">约 34 天</td><td>Megatron-LM 论文的估算</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">按峰值满负荷算，是最乐观的情形；真实训练的利用率通常只有峰值的三到五成。</p>
</div>
<figcaption>表 6 · 同样的算法，放在 2010 年以前的任何一台机器上都要跑几千年以上</figcaption>
</figure>

1976 年的 Cray-1 是当时最快的超级计算机，峰值每秒 1.6 亿次，售价约 800 万美元[^cray1]。用它训练 GPT-3 要 6000 多万年。1997 年的 ASCI Red 是第一台每秒过万亿次的计算机[^ascired]，也要 9000 多年。即使只看内存，GPT-3 的参数用半精度存要 350GB，训练时连同梯度和优化器状态要好几 TB；Cray-1 的内存是 8MB，AlexNet 用的 GTX 580 每张 3GB。

这张表的意思很清楚：**就算 1986 年有人写出了 Transformer 和全部训练技巧，也不可能训练出 GPT-3**。不是没想到，是做不到。

### GPU：给游戏造的矩阵乘法机器

转机来自一个意想不到的地方。游戏画面要给几百万个像素各算一遍颜色，显卡因此被造成了成千上万个简单计算单元一起干活的样子，最擅长的恰好是大批量的乘加运算，也就是矩阵乘法。神经网络的前向和反向，绝大部分也是矩阵乘法。

2007 年英伟达推出 CUDA，显卡可以直接用 C 语言风格的代码编程了。2009 年，吴恩达的团队用 GPU 训练深度网络，比双核 CPU 快了最多约 70 倍[^raina]。2006 年辛顿用逐层预训练让深层网络重新训得动[^hinton06]，再加上 ReLU、GPU，几条线开始汇合。

2012 年，克里热夫斯基、苏茨克维和辛顿用**两张 GTX 580 游戏显卡**，在 ImageNet 图像识别比赛上把错误率从 26% 降到了 15% 左右[^alexnet]。这个网络叫 AlexNet，有 6000 万个参数，训练了五到六天，因为一张卡 3GB 显存放不下，模型被拆在两张卡上。算法上它没有什么 1990 年想不到的东西，卷积、反向传播、随机梯度下降都是老的，新的是 ReLU、Dropout 和两张显卡。

### 压缩榜的换手

压缩榜上的换手来得更晚一些。Mahoney 维护的大文本压缩榜比的是把 1GB 的英文维基百科（enwik9）压到多小，不限硬件[^ltcb]。

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="lhz-title">把 1GB 英文维基百科（enwik9）压到多小</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="lhz-title">
<colgroup><col style="width:24%"><col style="width:9%"><col><col style="width:16%"><col class="hl" style="width:13%"></colgroup>
<thead>
<tr><th scope="col">压缩器</th><th scope="col">年份</th><th scope="col">怎么猜下一个字节</th><th scope="col">压缩后</th><th scope="col" class="acc">比特/字节</th></tr>
</thead>
<tbody>
<tr><th scope="row">gzip -9</th><td class="mono"></td><td>在前面 32KB 里找重复片段</td><td class="mono num">322.6 MB</td><td class="mono num">2.58</td></tr>
<tr><th scope="row">bzip2 -9</th><td class="mono"></td><td>整块重排后统计</td><td class="mono num">254.0 MB</td><td class="mono num">2.03</td></tr>
<tr><th scope="row">xz -9e</th><td class="mono"></td><td>在 1GB 窗口里找重复片段</td><td class="mono num">197.3 MB</td><td class="mono num">1.58</td></tr>
<tr><th scope="row">PPMd（7-Zip）</th><td class="mono"></td><td>按前几个字节统计下一个字节</td><td class="mono num">179.0 MB</td><td class="mono num">1.43</td></tr>
<tr><th scope="row">paq8hp12</th><td class="mono">2007</td><td>几百个手写上下文模型加权混合</td><td class="mono num">132.0 MB</td><td class="mono num">1.06</td></tr>
<tr><th scope="row">cmix v1</th><td class="mono">2014</td><td>上千个手写模型混合</td><td class="mono num">128.6 MB</td><td class="mono num">1.03</td></tr>
<tr><th scope="row" class="acc">nncp（LSTM）</th><td class="mono">2019</td><td>神经网络边读边训练，CPU</td><td class="mono num">125.6 MB</td><td class="mono num acc">1.00</td></tr>
<tr><th scope="row">cmix v18</th><td class="mono">2019</td><td>手写模型 + 小 LSTM 混合</td><td class="mono num">115.7 MB</td><td class="mono num">0.93</td></tr>
<tr><th scope="row" class="acc">nncp v3.1（Transformer）</th><td class="mono">2021</td><td>约 2 亿参数，GPU</td><td class="mono num">108.4 MB</td><td class="mono num acc">0.87</td></tr>
<tr><th scope="row" class="acc">nncp v3.2（Transformer）</th><td class="mono">2023</td><td>同上，再调参</td><td class="mono num">106.6 MB</td><td class="mono num acc">0.85</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">数据来自 Matt Mahoney 的大文本压缩榜，只列压缩后大小，不含解压程序本身。年份为该版本提交日期，经典工具不标。</p>
</div>
<figcaption>表 7 · 压缩榜的前排一直是“猜下一个字节”的模型；手写的统计模型被神经网络超过，是 GPU 普及以后的事</figcaption>
</figure>

2019 年，写过 QEMU 和 FFmpeg 的贝拉（Fabrice Bellard）发布了 nncp：一个神经网络一边读维基百科一边训练，一边给下一个字节报概率，再用算术编码写下来。第一版是 LSTM，在 CPU 上压 1GB 要好几天，成绩还不如手写模型最好的 cmix。2021 年他换成了约 2 亿参数的 Transformer，放到 GPU 上跑，登上了榜首，2023 年的版本压到 106.6MB，平均每个字节约 0.85 比特（表 7）。对照开头那句话：这台“压缩器”里装的，就是一个边读边学的小语言模型。

### 训练所需涨得比单卡更快

AlexNet 之后，训练用的算力增长得非常快。图 8把几个有代表性的模型的训练计算量，和同期一台机器满负荷跑一天的运算量画在一起。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 400" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="lh5-title lh5-desc">
<title id="lh5-title">训练计算量与单机算力</title>
<desc id="lh5-desc">LeNet 到 Llama 3.1 的训练计算量，对比同期一台机器满负荷一天的运算量。</desc>
<defs>
<marker id="lh5-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="lh5-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="400" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="768" height="368" rx="8"/>
<text class="t-zone" x="32" y="44">浮点运算次数（对数坐标）</text>
<line class="dd-hair" x1="96" y1="336.0" x2="744" y2="336.0"/>
<text class="t-note" x="88" y="340.0" text-anchor="end">10¹⁰</text>
<line class="dd-hair" x1="96" y1="268.0" x2="744" y2="268.0"/>
<text class="t-note" x="88" y="272.0" text-anchor="end">10¹⁴</text>
<line class="dd-hair" x1="96" y1="200.0" x2="744" y2="200.0"/>
<text class="t-note" x="88" y="204.0" text-anchor="end">10¹⁸</text>
<line class="dd-hair" x1="96" y1="132.0" x2="744" y2="132.0"/>
<text class="t-note" x="88" y="136.0" text-anchor="end">10²²</text>
<line class="dd-hair" x1="96" y1="64.0" x2="744" y2="64.0"/>
<text class="t-note" x="88" y="68.0" text-anchor="end">10²⁶</text>
<text class="t-note" x="96.0" y="358" text-anchor="middle">1975</text>
<text class="t-note" x="225.6" y="358" text-anchor="middle">1985</text>
<text class="t-note" x="355.2" y="358" text-anchor="middle">1995</text>
<text class="t-note" x="484.8" y="358" text-anchor="middle">2005</text>
<text class="t-note" x="614.4" y="358" text-anchor="middle">2015</text>
<text class="t-note" x="744.0" y="358" text-anchor="middle">2025</text>
<path class="dd-edge dd-dash" fill="none" d="M109.0 282.6 L381.1 217.6 L549.6 214.7 L640.3 182.4 L705.1 167.2"/>
<circle class="dd-dot" cx="109.0" cy="282.6" r="4"/>
<text class="t-note" x="99.0" y="300.6" text-anchor="end">Cray-1</text>
<circle class="dd-dot" cx="381.1" cy="217.6" r="4"/>
<text class="t-note" x="391.1" y="235.6" text-anchor="start">ASCI Red 整台</text>
<circle class="dd-dot" cx="549.6" cy="214.7" r="4"/>
<text class="t-note" x="539.6" y="232.7" text-anchor="end">GTX 580</text>
<circle class="dd-dot" cx="640.3" cy="182.4" r="4"/>
<text class="t-note" x="650.3" y="200.4" text-anchor="start">V100</text>
<circle class="dd-dot" cx="705.1" cy="167.2" r="4"/>
<text class="t-note" x="715.1" y="185.2" text-anchor="start">H100</text>
<path class="dd-edge-acc" fill="none" d="M277.4 322.2 L575.5 205.6 L679.2 106.6 L731.0 71.1"/>
<circle class="dd-fill-acc" cx="277.4" cy="322.2" r="5"/>
<text class="t-name t-acc" x="287.4" y="314.2" text-anchor="start">LeNet</text>
<circle class="dd-fill-acc" cx="575.5" cy="205.6" r="5"/>
<text class="t-name t-acc" x="565.5" y="197.6" text-anchor="end">AlexNet</text>
<circle class="dd-fill-acc" cx="679.2" cy="106.6" r="5"/>
<text class="t-name t-acc" x="669.2" y="98.6" text-anchor="end">GPT-3</text>
<circle class="dd-fill-acc" cx="731.0" cy="71.1" r="5"/>
<text class="t-name t-acc" x="721.0" y="63.1" text-anchor="end">Llama 3.1 405B</text>
<text class="t-say" x="112" y="84">实线：训练一个模型要的运算量</text>
<text class="t-say" x="112" y="104">虚线：当年一台机器或一张卡，满负荷跑一天</text>
<text class="t-say" x="251.5" y="149.0">2012 年以后，实线越过了虚线，</text>
<text class="t-say" x="251.5" y="169.0">差距只能靠成千上万张卡补</text>
</svg>
</div>
<figcaption>图 8 · 训练一个模型要的运算量涨得比单张卡的算力更快，大模型只能靠大集群</figcaption>
</figure>

LeNet 的训练量还在一台工作站几天的能力之内；AlexNet 大约等于两张 GTX 580 跑满两天（实际利用率不到一半，所以用了五六天）；到 GPT-3，实线已经比虚线高出三四个数量级，一张 H100 满负荷也要跑 10 年。这些差距只能靠把成千上万张卡连起来补：Megatron-LM 的作者估算，1024 张 A100 训练 GPT-3 大约要 34 天[^megatron]。怎么让一万张卡像一台机器一样干活，又是一整套工程：模型怎么切开放在不同的卡上，梯度怎么汇总，网络带宽够不够，一张卡坏了怎么续上。在[显卡对比那篇](/blog/gpu-accelerator-compare/)里我们看过这些卡的价格和性能，单张 H100 就要几万美元。

算力在 2012 年前后够了，**可是拿什么去喂它？**

## 七、数据：标签是瓶颈

### 现象：数据集停在百万级

神经网络要从例子里学。在很长时间里，“例子”指的是**带标签的例子**：一张图配一个“猫”，一句话配一棵句法树。标签要人来打，这就把数据集的规模卡住了。

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="lh7-title">几个有代表性的数据集</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="lh7-title">
<colgroup><col style="width:24%"><col style="width:12%"><col><col class="hl"></colgroup>
<thead>
<tr><th scope="col">数据集</th><th scope="col">年份</th><th scope="col">规模</th><th scope="col" class="acc">标签从哪来</th></tr>
</thead>
<tbody>
<tr><th scope="row">Brown 语料库</th><td class="mono">1961</td><td class="mono num">约 100 万词</td><td >人工挑选、人工标词性</td></tr>
<tr><th scope="row">Penn Treebank</th><td class="mono">1993</td><td class="mono num">约 450 万词</td><td >人工标句法树</td></tr>
<tr><th scope="row">MNIST</th><td class="mono">1998</td><td class="mono num">7 万张手写数字</td><td >人写、人标</td></tr>
<tr><th scope="row">ImageNet</th><td class="mono">2009</td><td class="mono num">约 1400 万张图</td><td >约 4.9 万名众包工人，标了近两年</td></tr>
<tr><th scope="row">GPT-3 训练集</th><td class="mono">2020</td><td class="mono num">3000 亿 token</td><td class="acc">下一个词本身</td></tr>
<tr><th scope="row">Llama 3 训练集</th><td class="mono">2024</td><td class="mono num">约 15 万亿 token</td><td class="acc">下一个词本身</td></tr>
</tbody>
</table>
</div>
</div>
<figcaption>表 9 · 要人来打标签的数据集停在百万到千万级；标签不用人打以后，规模一下子大了几万倍</figcaption>
</figure>

1961 年的 Brown 语料库有约 100 万词，是第一个计算机可读的大规模英语语料，每个词的词性都是人工标的[^brown-corpus]。三十年后的 Penn Treebank 有约 450 万词，标了句法树[^ptb]，九十年代到 2010 年代的大量语言研究都在它上面做。1998 年的 MNIST 有 7 万张手写数字[^mnist]。

2009 年李飞飞团队发布的 ImageNet 是一个转折：约 1400 万张图片，标注靠亚马逊的众包平台 Mechanical Turk，约 4.9 万名来自 167 个国家的工人，从 2008 年 7 月一直标到 2010 年 4 月[^imagenet]。AlexNet 正是在它的一个 120 万张图的子集上训练出来的。但这也几乎是人工标注能走到的上限了。

### 解决：把压缩当目标

语言模型绕开了这个瓶颈，靠的正是第二节的那个目标。**它要做的是压缩文字，也就是猜下一个词，而下一个词就写在原文里**：每一段人写的文字，本身就是成千上万个“题目 + 答案”，不用任何人来打标签。这种做法叫**自监督**。

标签不再需要人打，能用的数据就只受“人类写了多少字”的限制。2008 年开始，非营利组织 Common Crawl 定期抓取整个公开网页，把数据免费放出来[^commoncrawl]。GPT-3 的训练数据有 3000 亿个 token，大部分来自过滤后的 Common Crawl；2024 年的 Llama 3 用了约 15 万亿个 token[^llama3]。和 Penn Treebank 比，大了几百万倍。

### 数据越多，压得越小

数据量对压缩有多大影响？我们拿本站的文章做了一个小实验：用其中 53 篇训练字符级的“k 阶模型”，也就是按前 k 个字的出现次数去猜下一个字（和 PPM 同类），再用它压缩另外 4 篇没见过的文章。

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="lhn-title">用本站文章训练字符模型，压缩另外 4 篇（每字比特，越低越好）</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="lhn-title">
<colgroup><col style="width:24%"><col><col><col><col><col></colgroup>
<thead>
<tr><th scope="col">训练文字</th><th scope="col">0 阶</th><th scope="col">1 阶</th><th scope="col">2 阶</th><th scope="col">3 阶</th><th scope="col">4 阶</th></tr>
</thead>
<tbody>
<tr><th scope="row">3.8 万字</th><td class="mono num">8.64</td><td class="mono num acc hl">7.32</td><td class="mono num">7.42</td><td class="mono num">7.55</td><td class="mono num">7.60</td></tr>
<tr><th scope="row">15 万字</th><td class="mono num">8.44</td><td class="mono num">6.52</td><td class="mono num acc hl">6.41</td><td class="mono num">6.53</td><td class="mono num">6.62</td></tr>
<tr><th scope="row">60 万字</th><td class="mono num">8.35</td><td class="mono num">6.00</td><td class="mono num acc hl">5.78</td><td class="mono num">5.89</td><td class="mono num">6.00</td></tr>
<tr class="dt-group"><th colspan="6" scope="rowgroup">对照：通用压缩器（只看这四篇本身）</th></tr>
<tr><th scope="row">gzip / bzip2 / xz</th><td colspan="5" class="mono c">7.20 / 5.92 / 6.41</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">“k 阶”指按前 k 个字猜下一个字。测试的 4 篇共 42,255 字、89,154 字节，不在训练集里。每行最好的一格用强调色。</p>
</div>
<figcaption>表 10 · 训练文字越多，压得越好；模型越大越吃数据，数据少时大模型反而更差</figcaption>
</figure>

表 10里有两个现象。第一，训练文字从 3.8 万字加到 60 万字，最好的成绩从每字 7.32 比特降到 5.78 比特，已经比 bzip2 好，比 gzip 好得多：**模型见过的文字越多，越能猜中没见过的文字**。第二，模型越大越吃数据。只有 3.8 万字时，看前 1 个字的模型最好，看前 2 到 4 个字的反而更差，因为大部分“前 4 个字”的组合在训练集里根本没出现过，模型没有可以依据的统计；数据加到 15 万字以后，看前 2 个字的才追上来。60 万字对这样的模型来说仍然太少，4 阶始终没有翻身。

这正是大模型需要海量文字的原因：**参数越多，能记下的规律越多，要把它们都学到，就要越多的文字**。互联网把人类几十年写下的文字放到了同一个地方，自监督让这些文字不用标注就能直接用。**算力有了，数据也有了，还差什么？**

## 八、架构与规模：最后两块

### 现象：循环网络喂不饱 GPU

2017 年以前，处理文字的主力是循环网络（RNN 和 LSTM），nncp 的第一版用的也是它。它读一句话的方式和人一样，一个字一个字地读，每读一个字更新一次内部状态。问题在于，第 6 个字的计算要等前 5 个字算完，**整句话的计算是一条串行的链**。GPU 有上万个计算单元，却只能一步一步地等。

2014 年，巴达瑙（Bahdanau）等人在机器翻译里加入了**注意力**：翻译每个词时，直接回头看原文的所有位置，按相关程度加权取信息[^bahdanau]。2017 年谷歌的《Attention Is All You Need》走到底，把循环结构整个拿掉，只留注意力，这就是 Transformer[^vaswani]。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 416" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="lh8-title lh8-desc">
<title id="lh8-title">RNN 与 Transformer 的计算顺序</title>
<desc id="lh8-desc">RNN 读一句话要按字依次算，Transformer 把整句一次放进矩阵乘法。</desc>
<defs>
<marker id="lh8-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="lh8-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="416" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="768" height="192" rx="8"/>
<text class="t-zone" x="32" y="44">循环网络（RNN）：一个字算完，才能算下一个</text>
<rect class="dd-zone" x="16" y="224" width="768" height="176" rx="8"/>
<text class="t-zone" x="32" y="252">Transformer：所有位置一起算</text>
<line class="dd-edge-acc" x1="144" y1="92" x2="182" y2="92" marker-end="url(#lh8-arrow-acc)"/>
<line class="dd-edge-acc" x1="248" y1="92" x2="286" y2="92" marker-end="url(#lh8-arrow-acc)"/>
<line class="dd-edge-acc" x1="352" y1="92" x2="390" y2="92" marker-end="url(#lh8-arrow-acc)"/>
<line class="dd-edge-acc" x1="456" y1="92" x2="494" y2="92" marker-end="url(#lh8-arrow-acc)"/>
<line class="dd-edge-acc" x1="560" y1="92" x2="598" y2="92" marker-end="url(#lh8-arrow-acc)"/>
<line class="dd-edge" x1="112" y1="150" x2="112" y2="112" marker-end="url(#lh8-arrow)"/>
<line class="dd-edge" x1="216" y1="150" x2="216" y2="112" marker-end="url(#lh8-arrow)"/>
<line class="dd-edge" x1="320" y1="150" x2="320" y2="112" marker-end="url(#lh8-arrow)"/>
<line class="dd-edge" x1="424" y1="150" x2="424" y2="112" marker-end="url(#lh8-arrow)"/>
<line class="dd-edge" x1="528" y1="150" x2="528" y2="112" marker-end="url(#lh8-arrow)"/>
<line class="dd-edge" x1="632" y1="150" x2="632" y2="112" marker-end="url(#lh8-arrow)"/>
<rect class="dd-card" x="80" y="74" width="64" height="36" rx="6"/>
<text class="t-mono" x="112" y="97" text-anchor="middle">h1</text>
<text class="t-name" x="112" y="170" text-anchor="middle">大</text>
<text class="t-note" x="112" y="66" text-anchor="middle">第 1 步</text>
<rect class="dd-card" x="184" y="74" width="64" height="36" rx="6"/>
<text class="t-mono" x="216" y="97" text-anchor="middle">h2</text>
<text class="t-name" x="216" y="170" text-anchor="middle">模</text>
<text class="t-note" x="216" y="66" text-anchor="middle">第 2 步</text>
<rect class="dd-card" x="288" y="74" width="64" height="36" rx="6"/>
<text class="t-mono" x="320" y="97" text-anchor="middle">h3</text>
<text class="t-name" x="320" y="170" text-anchor="middle">型</text>
<text class="t-note" x="320" y="66" text-anchor="middle">第 3 步</text>
<rect class="dd-card" x="392" y="74" width="64" height="36" rx="6"/>
<text class="t-mono" x="424" y="97" text-anchor="middle">h4</text>
<text class="t-name" x="424" y="170" text-anchor="middle">为</text>
<text class="t-note" x="424" y="66" text-anchor="middle">第 4 步</text>
<rect class="dd-card" x="496" y="74" width="64" height="36" rx="6"/>
<text class="t-mono" x="528" y="97" text-anchor="middle">h5</text>
<text class="t-name" x="528" y="170" text-anchor="middle">什</text>
<text class="t-note" x="528" y="66" text-anchor="middle">第 5 步</text>
<rect class="dd-card" x="600" y="74" width="64" height="36" rx="6"/>
<text class="t-mono" x="632" y="97" text-anchor="middle">h6</text>
<text class="t-name" x="632" y="170" text-anchor="middle">么</text>
<text class="t-note" x="632" y="66" text-anchor="middle">第 6 步</text>
<text class="t-say" x="768" y="196" text-anchor="end">h6 要等 h1…h5 依次算完：6 步串行</text>
<rect class="dd-card" x="80" y="272" width="584" height="44" rx="6"/>
<text class="t-mono" x="372" y="299" text-anchor="middle">Q·Kᵀ：一次矩阵乘法算出 6×6 个注意力分数</text>
<line class="dd-edge" x1="112" y1="360" x2="112" y2="318" marker-end="url(#lh8-arrow)"/>
<text class="t-name" x="112" y="378" text-anchor="middle">大</text>
<line class="dd-edge" x1="216" y1="360" x2="216" y2="318" marker-end="url(#lh8-arrow)"/>
<text class="t-name" x="216" y="378" text-anchor="middle">模</text>
<line class="dd-edge" x1="320" y1="360" x2="320" y2="318" marker-end="url(#lh8-arrow)"/>
<text class="t-name" x="320" y="378" text-anchor="middle">型</text>
<line class="dd-edge" x1="424" y1="360" x2="424" y2="318" marker-end="url(#lh8-arrow)"/>
<text class="t-name" x="424" y="378" text-anchor="middle">为</text>
<line class="dd-edge" x1="528" y1="360" x2="528" y2="318" marker-end="url(#lh8-arrow)"/>
<text class="t-name" x="528" y="378" text-anchor="middle">什</text>
<line class="dd-edge" x1="632" y1="360" x2="632" y2="318" marker-end="url(#lh8-arrow)"/>
<text class="t-name" x="632" y="378" text-anchor="middle">么</text>
<text class="t-note" x="372" y="262" text-anchor="middle">第 1 步</text>
<text class="t-say" x="768" y="336" text-anchor="end">句子再长，</text>
<text class="t-say" x="768" y="356" text-anchor="end">也只是矩阵大一点：</text>
<text class="t-say t-acc" x="768" y="376" text-anchor="end">1 步，交给 GPU 并行</text>
</svg>
</div>
<figcaption>图 11 · RNN 的计算是一条链，Transformer 的计算是一块矩阵，后者正好是 GPU 擅长的形状</figcaption>
</figure>

图 11是两者的区别。Transformer 里每个位置看其他位置的注意力分数，可以写成一次矩阵乘法 $QK^\top$ 一起算出来，句子再长也只是矩阵大一点。这正是 GPU 最擅长的形状。胡克（Sara Hooker）把这种现象叫作“**硬件彩票**”：一种算法能不能胜出，很大程度上取决于它是否碰巧适合当时的硬件[^hooker]。Transformer 中了这张彩票，nncp 从 LSTM 换成 Transformer 以后登上压缩榜首，也是同一个原因。

这一时期还补上了几块让深网络稳定训练的零件：Adam 优化器自动调整每个参数的步长[^adam]，残差连接让梯度可以跳过若干层直接往回传，网络因此能叠到上百层[^resnet]。它们解决的都是第五节那种“网络一深就训不动”的问题。

### 规模定律：压缩率可以预算

到 2018 年前后，架构、算力、数据都有了，但还有一个现实的问题：训练一个大模型要花几百万美元，**花了钱，模型会变好多少？** 如果事先不知道，没有人敢押这么大的注。

2020 年 1 月，OpenAI 的卡普兰（Kaplan）等人发表了**规模定律**：语言模型的测试损失随参数量、数据量和计算量按幂律平稳下降，可以用小模型的结果外推大模型的结果[^kaplan]。按第二节的读法，这张图的纵轴就是**每个 token 平均要花多少比特**，规模定律画的是一条压缩率曲线：多投多少算力，能把人类的文字多压掉多少，事先就能算出来。这让“做大”从一场赌博变成了一笔可以预算的投资。同年 5 月，GPT-3 发布[^gpt3]。2022 年 DeepMind 的 Chinchilla 进一步指出，在算力固定时参数和数据应该同比例增长，GPT-3 那一代模型其实数据给少了[^chinchilla]，这和表 10里“模型越大越吃数据”是同一回事。

压缩的视角在这几年被重新提了出来。2023 年苏茨克维（Ilya Sutskever）在伯克利的一次演讲里，用压缩来解释为什么只靠“猜下一个词”的无监督学习能学到有用的东西[^ilya]。同年 DeepMind 的论文《语言模型即压缩》把这件事做成了实验：主要用文字训练的 Chinchilla 70B，配上算术编码去压图片和音频，把 ImageNet 的图块压到原大小的 43.4%，把 LibriSpeech 的语音压到 16.4%，比专门的 PNG（58.5%）和 FLAC（30.3%）还小[^deletang]，前提是不把模型本身的体积算进去。

最后一块是让模型“听话”。只会预测下一个词的模型，你问它问题，它可能接着编出另一个问题。2022 年 3 月的 InstructGPT 用人类反馈做强化学习，把一个预测器调成了能按指令回答的助手[^instructgpt]。同年 11 月 30 日，ChatGPT 上线。

## 九、四条线的交汇

把前面几节放到一张图上，就能看出“为什么是最近几年”。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 420" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="lh9-title lh9-desc">
<title id="lh9-title">四条线的时间线</title>
<desc id="lh9-desc">数学在 19 世纪就绪，算法在 1986 年基本齐全，算力和数据要到 2010 年代才够。</desc>
<defs>
<marker id="lh9-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="lh9-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="420" rx="10"/>
<rect class="dd-derived" x="645" y="24" width="91" height="360" rx="6"/>
<text class="t-say" x="637" y="44" text-anchor="end">虚线框：2009–2022，四条线先后就绪</text>
<rect class="dd-zone" x="16" y="64" width="64" height="64" rx="8"/>
<text class="t-zone" x="48" y="101" text-anchor="middle">数学</text>
<line class="dd-edge" x1="96" y1="96" x2="740" y2="96"/>
<circle class="dd-dot" cx="115.7" cy="96" r="4"/>
<text class="t-note" x="109.7" y="84" text-anchor="start">1676 链式法则</text>
<circle class="dd-dot" cx="196.0" cy="96" r="4"/>
<text class="t-note" x="196.0" y="118" text-anchor="middle">1805 最小二乘</text>
<circle class="dd-fill-acc" cx="222.1" cy="96" r="5"/>
<text class="t-note t-acc" x="216.1" y="84" text-anchor="start">1847 梯度下降</text>
<circle class="dd-fill-acc" cx="341.4" cy="96" r="5"/>
<text class="t-note t-acc" x="341.4" y="84" text-anchor="middle">1948 信息论</text>
<circle class="dd-dot" cx="356.9" cy="96" r="4"/>
<text class="t-note" x="356.9" y="118" text-anchor="middle">1951 随机梯度</text>
<rect class="dd-zone" x="16" y="144" width="64" height="64" rx="8"/>
<text class="t-zone" x="48" y="181" text-anchor="middle">算法</text>
<line class="dd-edge" x1="96" y1="176" x2="740" y2="176"/>
<circle class="dd-dot" cx="393.2" cy="176" r="4"/>
<text class="t-note" x="393.2" y="164" text-anchor="middle">1958 感知机</text>
<circle class="dd-dot" cx="450.1" cy="176" r="4"/>
<text class="t-note" x="450.1" y="198" text-anchor="middle">1969 XOR 难题</text>
<circle class="dd-dot" cx="538.1" cy="176" r="4"/>
<text class="t-note" x="538.1" y="164" text-anchor="middle">1986 反向传播</text>
<circle class="dd-dot" cx="595.1" cy="176" r="4"/>
<text class="t-note" x="595.1" y="198" text-anchor="middle">1997 LSTM</text>
<circle class="dd-fill-acc" cx="698.6" cy="176" r="5"/>
<text class="t-note t-acc" x="712.6" y="164" text-anchor="end">2017 Transformer</text>
<rect class="dd-zone" x="16" y="224" width="64" height="64" rx="8"/>
<text class="t-zone" x="48" y="261" text-anchor="middle">算力</text>
<line class="dd-edge" x1="96" y1="256" x2="740" y2="256"/>
<circle class="dd-dot" cx="331.1" cy="256" r="4"/>
<text class="t-note" x="331.1" y="244" text-anchor="middle">1946 ENIAC</text>
<circle class="dd-dot" cx="486.4" cy="256" r="4"/>
<text class="t-note" x="486.4" y="244" text-anchor="middle">1976 Cray-1</text>
<circle class="dd-dot" cx="595.1" cy="256" r="4"/>
<text class="t-note" x="595.1" y="278" text-anchor="middle">1997 万亿次超算</text>
<circle class="dd-fill-acc" cx="672.7" cy="256" r="5"/>
<text class="t-note t-acc" x="672.7" y="244" text-anchor="middle">2012 两张 GTX 580</text>
<circle class="dd-fill-acc" cx="714.1" cy="256" r="5"/>
<text class="t-note t-acc" x="728.1" y="278" text-anchor="end">2020 万卡集群</text>
<rect class="dd-zone" x="16" y="304" width="64" height="64" rx="8"/>
<text class="t-zone" x="48" y="341" text-anchor="middle">数据</text>
<line class="dd-edge" x1="96" y1="336" x2="740" y2="336"/>
<circle class="dd-dot" cx="408.7" cy="336" r="4"/>
<text class="t-note" x="408.7" y="324" text-anchor="middle">1961 百万词语料</text>
<circle class="dd-dot" cx="574.4" cy="336" r="4"/>
<text class="t-note" x="588.4" y="358" text-anchor="end">1993 Treebank</text>
<circle class="dd-fill-acc" cx="657.2" cy="336" r="5"/>
<text class="t-note t-acc" x="657.2" y="324" text-anchor="middle">2009 ImageNet</text>
<circle class="dd-fill-acc" cx="714.1" cy="336" r="5"/>
<text class="t-note t-acc" x="728.1" y="358" text-anchor="end">2020 3000 亿 token</text>
<line class="dd-hair" x1="280" y1="380" x2="280" y2="56"/>
<text class="t-note" x="196" y="396" text-anchor="middle">1670–1940（压缩）</text>
<text class="t-note" x="352" y="396" text-anchor="middle">1950</text>
<text class="t-note" x="455" y="396" text-anchor="middle">1970</text>
<text class="t-note" x="559" y="396" text-anchor="middle">1990</text>
<text class="t-note" x="662" y="396" text-anchor="middle">2010</text>
</svg>
</div>
<figcaption>图 12 · 数学最早就绪，算法其次，算力和数据最晚；四条线要同时到位，大模型才出现</figcaption>
</figure>

图 12有四条线。**数学**最早就绪：链式法则、最小二乘、梯度下降，以及香农把“预测”和“压缩”连在一起的信息论，在 1951 年以前全部齐了。**算法**在 1986 年反向传播流行时基本齐全，剩下的 ReLU、注意力、Transformer 都是小改动，却要等算力和数据到了，才知道哪种改动值得。**算力**和**数据**是最晚的两条：一直到 2010 年前后，单机算力和人工标注的数据集都还差着好几个数量级，而它们的转机，GPU 和互联网文本，都不是为人工智能准备的。

这四条线有一个顺序上的依赖：没有足够的算力和数据，就看不出大网络的好处；看不出好处，就没人去发明适合大网络的架构，也没人去建万卡集群。压缩榜就是一个缩影：同样是“猜下一个字节”，手写模型领先了十几年，神经网络要等到 GPU 和 Transformer 都到位才超过它。所以四条线不是各自独立地“到期”，而是在 2012 到 2022 年这十年里，一条带动另一条，先后就位。

萨顿（Rich Sutton）2019 年写过一篇短文《苦涩的教训》，总结 AI 研究七十年的经验：**能随着算力增长而变好的通用方法，最终总是赢过精心设计的人类知识**，而且赢得很多[^sutton]。PAQ 和 cmix 里上千个手写模型，就是“精心设计的人类知识”；梯度下降简单到几乎没有可以加进去的知识，所以能一直扩展下去。

## 十、回到开头

回到开头那行规则：

```text
w ← w − 学习率 × dL/dw
```

从柯西到 GPT-3，它一个字也没改，损失也一直是香农 1948 年定义的那个比特数。我们觉得从梯度到 Transformer“好像也没那么复杂”，这个感觉是对的：大模型的原理确实不复杂，**复杂的是让这行规则在 1750 亿个参数上、在 3000 亿个 token 上走 9 万多步**。

这一百多年里，规则一直都在被使用：1801 年算谷神星，1958 年拧感知机的电位器，1989 年识别邮编，2006 年在压缩奖上挤维基百科，2012 年认 ImageNet 里的猫。每一次都做到了当时的硬件和数据所允许的最大规模。大模型之所以等到最近几年，是因为直到最近，这个“最大规模”才大到让一个压缩器把人类的文字压得足够小。

那么，一个压缩器凭什么像有了智能？回到第二节：要把一本物理教科书的下一个字猜准，光记住常见的词搭配是不够的，迟早要“知道”能量守恒；要把一段代码的下一行猜准，迟早要“知道”这段代码在干什么。损失每往下压一点，模型就被逼着多掌握一条这样的规律。谷神星的 6 个轨道根数是 40 天观测的压缩，规律找对了，就能预言半年后它在哪里；语言模型的参数是人类文字的压缩，规律找得够多，就能接着写出一段从没见过、但说得通的文字。“压缩即智能”说的就是这件事。至于这样的压缩能不能算真正的理解，还是一个开放的问题。

这也和本站另外几篇文章连在一起。[AdderBoard 那篇](/blog/adderboard-tiny-transformer/)里，36 个参数的 Transformer 就能把十位数加法学对，因为加法的规律很小，压缩它用不了多少参数；语言的规律大得多，所以要大得多的模型。[FPGA 那篇](/blog/transformer-vs-fpga/)问过“为什么 FPGA 的四十年里没有涌现智能”，答案的一半就在第三节：比特像阶跃开关一样没有梯度，没法用这行规则去调；另一半在第六、七节：即使能调，当年也没有足够的算力和数据。

## 小结

1. **梯度下降多老？** 链式法则 1676 年，最小二乘 1805 年，梯度下降 1847 年，随机梯度 1951 年。调参数的数学在一百多年前就齐了。
2. **训练要优化的是什么？** 香农 1948 年说明，一个字的码长是它的概率取负对数，交叉熵损失就是压缩后的比特数；算术编码让任何预测器都成了压缩器；所罗门诺夫和柯尔莫哥洛夫把“找到规律”和“压到最短”画上等号。这就是“压缩即智能”。
3. **会学习的机器为什么停在 1969 年？** 单层感知机分不开 XOR；多层网络能分开，但阶跃开关的导数处处为 0，没法用梯度训练。换成 sigmoid 这样的平滑开关才行。
4. **反向传播解决了什么？** 求导的代价。数值差分要跑 2N 遍前向，反向传播只要前向的几倍，和参数个数无关。
5. **1986 年以后为什么又冷了二十年？** 深网络有梯度消失，小数据上 SVM 更稳；压缩榜上赢的是手写统计模型。让大网络好训练的规模，当时的机器和数据都给不起。
6. **算力差多少？** GPT-3 要 $3.14\times10^{23}$ 次运算，在 1976 年的 Cray-1 上要 6000 多万年，在一张 2010 年的显卡上也要 6000 多年。GPU 和万卡集群把它缩到一个月，压缩榜首也在 GPU 上换成了 Transformer。
7. **数据差多少？** 人工标注的数据集停在百万到千万级；把压缩当目标就不用人打标签，训练数据到了万亿 token。模型越大越吃数据。
8. **最后几块是什么？** 适合 GPU 并行的 Transformer，把压缩率变成可以预算的规模定律，以及把预测器调成助手的人类反馈。
9. **为什么是最近几年？** 数学、算法、算力、数据四条线要同时到位，最晚的算力和数据在 2010 年代才到，并且一条带动另一条，在 2012 到 2022 年间先后就位。

本文的四个小实验（数值差分与反向传播的耗时、20 层网络的梯度消失、XOR 的三种训练、用字符模型压缩本站文章）都是几十到一百来行的 Python 脚本，在笔记本上几分钟就能跑完。

## 参考资料
