Lecture 11 · 预测即压缩:从香农到"压缩通向通用智能" · Compression for AGI

Advances in Foundation Models · Lecture 11。个人学习讲义与配套实验,按主题整理并持续修订。

课程源码 ZIP ↓

个人整理的学习笔记,非官方讲义;课程来源与整理说明见课程介绍。配套示例未逐一运行验证。

本篇目录

对应 Maps to:Stanford CS324 Winter 2023 Advances in Foundation Models,2/27 客座报告 Jack Rae(当时在 OpenAI,此前在 DeepMind 领导大语言模型研究,Gopher 的第一作者、Chinchilla 的作者之一):Compression for AGI。 依据说明:这次报告通过 Stanford MLSys Seminar 公开录像,但我没有拿到录像的文字稿或 slides,所以报告的具体内容只能依据二手材料:一篇中文的报告整理(智源社区,2023 年 3 月)和几篇引用了这次报告的文章。讲义里凡是”Rae 说……”的地方,都依据这些转述,并在用到具体数字(LLaMA-65B 的那笔账)时注明。论证本身是信息论和机器学习里成熟的内容,§2–§5 依据原始文献:Shannon 1948(信息论)、Shannon 1951(英语的熵)、算术编码(Witten, Neal & Cleary 1987)、Solomonoff(1964)与 Kolmogorov 复杂度、Rissanen 的最小描述长度(MDL,1978)、Blier & Ollivier 2018(The Description Length of Deep Learning Models)。§6–§7 补充了 Delétang et al. 2023(Language Modeling Is Compression)和 Huang et al. 2024(Compression Represents Intelligence Linearly)。文献里的数字都是我在原文里读到的。 配套代码 Code:code/lecture11/(PyTorch + 标准库;3 个脚本 + 1 个模块。代码 01 约 5 秒,02 约 65 秒,03 约 4 秒) 预计阅读时间:3 小时(含运行代码)


0. 本讲导航 · How to read this lecture

0.1 学习目标 Learning goals

学完这一讲,你应该能够:

  1. 用香农的码长 −log⁡2p-\log_2 p 解释”预测”和”压缩”为什么是同一件事,以及交叉熵 = 平均码长。
  2. 说明算术编码的原理(不断缩小区间),以及它为什么能让任何一个语言模型变成一个真正的无损压缩器。
  3. 区分两种给数据编码的方式:两段式编码(先发模型、再发数据)和在线(prequential)编码(边学边编码,码长 = 训练损失曲线下的面积),并解释 Rae 为什么说”不需要发送权重”。
  4. 复述 Rae 的核心论断——“智能的目标是对有用信息的最大无损压缩”——以及支持它的论证链:MDL、奥卡姆剃刀、Solomonoff 归纳。
  5. 解释为什么在”每条数据只看一遍”的训练里,训练损失曲线就是泛化曲线,以及这和规模定律(第 4 讲)的关系。
  6. 用代码里的实验说出这个视角的边界:压缩什么数据、怎样给模型记账、压缩了没用的信息怎么办、为什么一个只会压缩的模型不会”回去抄”。
  7. 了解 2023 年之后的证据:大语言模型压缩图像和音频、压缩能力与基准分数近似线性相关。

0.2 先修与本讲范围 Prerequisites & scope

默认你已经会 Assumed本讲只给直觉,后面几讲再展开 Previewed only
第 1 讲 §2.4:熵、交叉熵、bit/字符、困惑度induction head 与”复制”的机制(第 12 讲)
第 1 讲 §2.5:n-gram 模型与数据稀疏记忆与隐私:模型”压缩”了训练数据里的哪些具体内容(第 16 讲)
第 3 讲 §1:BPE 分词(它本身就是一种压缩算法)
第 4 讲:规模定律、C≈6NDC \approx 6ND、单遍训练(one epoch)

0.3 本讲地图 Map

§1  一个大胆的论断:Rae 的报告想说什么
§2  预测与压缩:香农码长、交叉熵、哈夫曼与算术编码                         ← 代码 01 演示 1
§3  用语言模型压缩:算术编码实战;解码器必须有同一个模型;模型本身的代价    ← 代码 01 演示 2–4
§4  训练过程就是压缩器:在线编码、损失曲线下的面积、LLaMA 的账、
    为什么更大的模型是更好的压缩器                                         ← 代码 02
§5  最小描述长度与奥卡姆剃刀:Kolmogorov、Solomonoff、MDL;用码长选模型     ← 代码 03 演示 1
§6  这个论证的边界:压缩"哪个世界"、没用的信息、复制、计算                   ← 代码 03 演示 2、3
§7  2023 年之后:语言模型是通用压缩器吗;压缩与能力的相关性
§8–12  小结、常见误区、练习、延伸阅读、下一讲预告

0.4 记号 Notation(只列本讲新增的,其余见第 1 讲 §0.4)

符号含义例子
ℓ(x)=−log⁡2p(x)\ell(x) = -\log_2 p(x)用分布 pp 编码符号 xx 的码长(bit)p=0.5p = 0.5 → 1 bit
H(p)H(p)熵:最优平均码长4 个等概率符号:2 bit
H(p,q)H(p, q)交叉熵:真实分布 pp、用模型 qq 编码时的平均码长=H(p)+KL(p ∥ q)= H(p) + \mathrm{KL}(p \,\|\, q)
K(x)K(x)Kolmogorov 复杂度:能输出 xx 的最短程序的长度不可计算
L(M)+L(D∣M)L(M) + L(D \mid M)两段式码长:描述模型 + 用模型描述数据MDL 原则:选使它最小的模型
Lpreq(D)L_{\text{preq}}(D)在线(prequential)码长:∑t−log⁡2pθt−1(xt)\sum_t -\log_2 p_{\theta_{t-1}}(x_t)= 训练损失曲线下的面积
bpc / bptbit/字符、bit/token

0.5 关于数字 · About the numbers

讲义里的实验数字都来自 code/lecture11/ 的实际输出(固定种子、PyTorch 单线程,同一台机器上重复运行逐字相同)。这一讲的压缩是真的:代码 01 把文本压成比特流,再解压回来逐字对照。和前几讲一样,第 1 讲的 base model 只有约 11 万参数、只见过一个模板生成的”迷你互联网”,所以它演示的是机制,不是大模型的量级。几处结果和我写代码前的预想不一样(完整清单在代码 README 的”关于实验结果”里),最重要的是:在我们的玩具世界里,bz2 比所有在线训练的神经网络都压得好(§4.4);以及用码长选模型时,两段式码长会因为模型编码得太浪费而选错(§5.3)。


1. 一个大胆的论断 · A bold claim

1.1 为什么”预测下一个词”能学会那么多

第 1 讲提出过一个问题:GPT-3 只被训练去做一件事——预测下一个 token——为什么它学会了翻译、问答、写代码、做算术?第 5 讲从”涌现”和”上下文学习”的角度讨论过这个问题。这一讲换一个角度:信息论。

Jack Rae 在 2023 年 2 月的这次报告里给出了一个非常简洁的答案:**预测下一个 token 就是在压缩数据,而压缩就是理解。**一个模型越能准确地预测文本,它就越能把文本压小;要把人类写的文字压到极致,就必须”理解”文字背后的世界——谁住在哪里、算术怎样算、代码怎样运行。据报告的整理,他的核心论断是:通用人工智能的基础模型,目标是对有用信息做最大限度的无损压缩。

这听起来像一个口号。这一讲要做的,是把它拆成几个可以检验的命题:

  1. 预测和压缩在数学上是等价的(§2,这是定理,不是口号)。
  2. 一个语言模型可以变成一个真正的压缩器,压缩率就是它的交叉熵(§3,代码 01 逐字节验证)。
  3. “训练”本身可以被看成压缩,而且不需要把模型参数算进成本(§4,Rae 论证的核心)。
  4. 最短的描述往往就是泛化得最好的描述(§5,奥卡姆剃刀的信息论版本)。
  5. 以及——这个论断在哪里会失效(§6)。

1.2 讲者的背景

Rae 在 DeepMind 领导过大语言模型的研究:2021 年的 Gopher(2800 亿参数)是他的工作,2022 年的 Chinchilla(第 4 讲的主角)他也是作者之一;做这次报告时他在 OpenAI。这个背景很重要:他是从”怎样把一个模型训练得更好”的工程经验出发,来找一个”为什么要这样做”的理论。规模定律(第 4 讲)告诉我们损失会怎样随规模下降;Rae 的报告想回答的是:为什么损失下降就意味着更聪明。


2. 预测与压缩 · Prediction is compression

2.1 香农的码长

1948 年,香农在《通信的数学理论》里证明了一件事:如果一个符号出现的概率是 pp,那么给它编码最少需要

ℓ=−log⁡2p个 bit.\ell = -\log_2 p \quad \text{个 bit}.

概率 0.5 的符号值 1 bit,概率 0.25 的值 2 bit,概率 0.01 的值 6.64 bit。直觉是:越常见的东西,越应该用短的码字——摩尔斯电码给最常见的字母 E 用了一个点,也是同一个道理。对整个信源取平均,最优的平均码长就是熵 H(p)=∑xp(x)⋅(−log⁡2p(x))H(p) = \sum_x p(x) \cdot (-\log_2 p(x))(香农的信源编码定理:任何无损编码的平均码长都不能低于熵,而且可以任意接近它)。

这就把”预测”和”压缩”连在了一起。**一个预测器给真实发生的事情的概率越高,编码它花的 bit 越少。**反过来,一个压缩器也隐含着一个预测器:码字越短的东西,它”认为”越可能发生。

2.2 交叉熵就是码长

真实的分布 pp 我们不知道,只能用一个模型 qq 去近似。用 qq 来设计码字,真实数据按 pp 出现,平均码长是交叉熵:

H(p,q)=∑xp(x)⋅(−log⁡2q(x))=H(p)+KL(p ∥ q).H(p, q) = \sum_x p(x) \cdot (-\log_2 q(x)) = H(p) + \mathrm{KL}(p \,\|\, q).

第二个等号说明:用不完美的模型编码,要多花 KL(p ∥ q)\mathrm{KL}(p \,\|\, q) 个 bit——这多出来的部分,就是模型的”无知”。熵 H(p)H(p) 是数据本身的不确定性,任何模型都消除不了;KL 是模型可以改进的部分。

第 1 讲 §2.4 定义过的”bit/字符”(模型在验证集上的平均交叉熵)因此有了一个非常具体的含义:用这个模型压缩这段文本,平均每个字符要多少 bit。语言模型训练时最小化的损失,字面意义上就是一个压缩率。

2.3 为什么需要算术编码

香农只证明了”存在”接近熵的编码。最常见的实际编码是哈夫曼编码(1952):给每个符号分配一个整数长度的码字,常见的短、少见的长。它的问题是”整数”:每个符号至少要 1 bit。

代码 01 演示 1 比较了两个信源:

信源熵 HH哈夫曼平均码长算术编码实测
A:概率是 2 的幂(0.5, 0.25, 0.125, 0.125)1.75001.75001.7518
B:很偏的分布(0.9, 0.05, 0.03, 0.02)0.61751.15000.6326

概率都是 2 的幂时,哈夫曼编码正好达到熵。但对信源 B,概率 0.9 的那个符号只值 0.15 bit,哈夫曼却必须给它至少 1 bit,平均码长比熵多了一倍多。语言模型的预测分布恰恰常常是”很偏”的:模型对下一个字符很有把握时,正确字符的概率可能是 0.99。

算术编码(arithmetic coding,Rissanen、Pasco 1976;Witten, Neal & Cleary 1987 给出了经典的整数实现)没有这个限制。它的想法是:把 [0,1)[0, 1) 区间看成”所有可能的消息”:

第 1 个字符:按概率把 [0, 1) 切成几段,真实字符 "t" 占 [0.30, 0.42)       → 区间长度 0.12
第 2 个字符:在 [0.30, 0.42) 里再按 p(·|"t") 切,真实字符 "h" 占其中 70%    → 区间长度 0.084
第 3 个字符:……                                                            → 区间长度 = 所有概率的乘积

最后得到一个很小的区间,长度等于整条消息的概率 ∏tq(xt∣x<t)\prod_t q(x_t \mid x_{<t})。要指明这个区间里的一个点,大约需要 −log⁡2∏tq(⋅)=∑t−log⁡2q(xt∣x<t)-\log_2 \prod_t q(\cdot) = \sum_t -\log_2 q(x_t \mid x_{<t}) 个 bit——正好是模型在这段文本上的交叉熵之和,不再受”每个符号整数个 bit”的限制。实际实现用 32 位整数表示区间的上下界,区间落进左半或右半时就输出一个确定的 bit 并把区间放大一倍,所以不需要无限精度(codec_tools.py 里的 ArithmeticEncoder)。

最关键的一点:算术编码对模型没有任何要求——只要模型能在每个位置给出一个概率分布。n-gram 可以,神经网络可以,一个大语言模型也可以。

2.4 香农的另一个实验

香农本人在 1951 年做过一个著名的实验(Prediction and Entropy of Printed English):让人去猜一段英文的下一个字母,用猜测的结果来估计英语的熵。他的估计是每个字母大约 0.6 到 1.3 bit。这个实验和今天的语言模型做的是同一件事——只不过当年的”预测器”是一个人。从这个角度看,“语言模型的损失降到多少”一直就是一个关于”它离人类的语言理解有多近”的问题。


3. 用语言模型压缩 · Compressing with a language model

3.1 算术编码实战

代码 01 演示 2 把第 1 讲迷你互联网的验证集(14,085 个字符,每个字符原本 1 字节)用不同的模型做算术编码,再解压回来逐字对照:

压缩器理论 bit/字符(交叉熵)实际 bit/字符压缩后压缩率
均匀分布(44 个字符)5.45945.45949,612 字节1.5×
1-gram(在训练集上数数)4.54984.54998,011 字节1.8×
3-gram1.36151.36212,399 字节5.9×
5-gram0.64530.64581,137 字节12.4×
base model(TinyGPT,109k 参数)0.35550.3563628 字节22.4×
gzip -9—1.47852,603 字节5.4×
bz2 -9—1.19052,096 字节6.7×
lzma(xz)-9—1.40402,472 字节5.7×

所有模型都解压一致。几个观察:

  1. 实际码长和交叉熵只差千分之几 bit/字符(来自把概率量化成整数、以及结尾的两个 bit)。“交叉熵就是压缩率”在这里是字面意义上成立的。
  2. 模型越好,文件越小。base model 把验证集压成原来的约 1/22,比 bz2 还小 3 倍多。gzip、bz2、lzma 不”懂”这个世界的规律(谁住在哪里、评论的评分怎样由两个形容词决定),只能利用重复的片段和局部统计。
  3. 一个细节:base model 在这里是 0.356 bit/字符,比第 1 讲报告的验证集 0.409 更好。区别在于上下文:这里每个字符都有至少 48 个字符的上下文;第 1 讲的 bits_per_char 把文本切成互不重叠的窗口,每个窗口开头的字符几乎没有上下文。同一个模型,“怎样给它上下文”也会改变压缩率。

3.2 解码器必须有同一个模型

怎样解压?解码器手里只有比特流。它从文本开头出发:用模型算出第一个字符的分布,看比特流落在哪一段,解出第一个字符;把这个字符交给模型,算出第二个字符的分布,再解……代码 01 演示 3 真的这样跑了一遍:前 600 个字符压成 215 bit,解码器”边解边预测”,结果与原文一致。

这有两个后果:

  • 发送方和接收方必须有同一个模型——不只是同样的结构,而是逐位相同的参数和逐位相同的计算。一个浮点数的差别可能让某个概率被量化成不同的整数,从那一刻起解码就完全错了。这在实践中是个真问题:同一个模型在不同的 GPU、不同的 batch 大小下,浮点结果常常并不逐位相同(第 9 讲 §3.1、第 10 讲 §7.1 讨论过同类问题)。
  • 解压和压缩一样慢:每解一个字符就要跑一次模型。用大语言模型做压缩器,速度比 gzip 慢几个数量级。

3.3 别忘了模型本身

还有一个更根本的问题:**如果接收方没有模型呢?**那就得把模型也发过去。代码 01 演示 4 用 base model 压缩它自己的训练文本(262,986 个字符,256.8 KB):

大小
用 base model 编码训练文本(0.3503 bit/字符)11.2 KB
加上模型参数(109,056 × 32 bit)合计 437.2 KB
加上模型参数(× 16 bit)合计 224.2 KB
加上模型参数(× 8 bit)合计 117.7 KB
对照:bz2 -916.7 KB

(顺便一提:base model 在训练文本上是 0.350 bit/字符,在验证集上 0.356,几乎一样——这个世界的文本高度规则,模型学到的是规律,而不是逐字背下训练集。)

模型本身比数据还大。这种”先发模型、再发用模型压缩的数据”的方式叫做两段式编码(two-part code):总码长 L(M)+L(D∣M)L(M) + L(D \mid M)。对小数据集,它非常吃亏。如果”压缩 = 智能”要成立,就必须回答:**模型的大小算不算钱?**一个 650 亿参数的模型存下来要一百多 GB——“模型越大压缩越好”在两段式的记账下不再显然。

Rae 的回答是另一种记账方式。


4. 训练过程就是压缩器 · Training is compression

4.1 在线编码:不用发送权重

想象发送方和接收方约好了:用同一份训练代码、同一个随机种子初始化一个模型。然后:

发送方                                           接收方
用模型 θ₀ 给第 1 段数据编码 → 发送比特流          用 θ₀ 解出第 1 段数据
用第 1 段数据训练一步 → θ₁                       用第 1 段数据训练一步 → θ₁(和发送方逐位相同)
用 θ₁ 给第 2 段数据编码 → 发送比特流              用 θ₁ 解出第 2 段
……                                               ……

每一段数据都用”还没见过它”的模型来编码;接收方解出这段数据之后,做和发送方完全相同的一步训练,于是两边的模型始终相同。模型参数一个都不用发——接收方自己训练出来了。需要发送的只有:训练代码(很小),加上每一段数据的码长之和:

Lpreq(D)=∑t−log⁡2pθt−1(xt)  +  ∣训练代码∣.L_{\text{preq}}(D) = \sum_{t} -\log_2 p_{\theta_{t-1}}(x_t) \;+\; |\text{训练代码}|.

求和的每一项就是第 tt 步训练时、更新之前的损失。所以:数据集的压缩后大小 = 训练损失曲线下的面积(横轴是 token 数)+ 训练代码的长度。这种编码在统计学里叫在线编码或预序列编码(prequential coding,Dawid 1984 的术语)。Blier & Ollivier(2018)专门研究了深度网络的描述长度,发现:用这种”增量”的编码方式,深度网络确实能把训练数据压得很好,即使把模型的代价考虑在内;而专门为最小化码长设计的变分方法反而给出很差的结果。

4.2 LLaMA 的账

据报告的整理,Rae 用 LLaMA 做了一笔账(以下数字来自对报告的转述,我没能核对原 slides,代码 02 演示 4 只核对算术):

  • LLaMA-65B 的训练数据约 1.4 万亿个 token,按每个 token 约 4 字节算,原始文本约 5.6 TB。
  • 训练损失曲线下的面积(整个训练过程中负对数似然之和)约 400 GB——相当于平均每个 token 2.29 bit(1.58 nats),也就是训练全程的平均损失。
  • 初始化和训练 LLaMA-65B 的代码大约 1 MB。
  • 所以 LLaMA-65B 把 5.6 TB 的训练数据”无损压缩”到了约 400 GB:约 14 倍。

就算改用两段式、把 65.2B 个 16 位参数(130 GB)也算进去,合计 530 GB,仍有约 10.6 倍——在这个规模上,参数的代价已经不重要了。Rae 由此得出:同样的数据、同样的训练代码,LLaMA-65B 的损失曲线比 LLaMA-33B 的低,所以 65B 是更好的压缩器——更大的模型更”智能”,在这个意义下是可以精确比较的。

4.3 迷你实验:一边学一边编码

代码 02 演示 1 在一个”更大的迷你互联网”上做了同样的事:同样的生成规则、同样的 60 个人,评论、故事、算术各 15,000 行,一共 1,731,979 个字符。4 种宽度的 TinyGPT 从随机初始化开始,按顺序读数据流,每 768 个字符先算损失(码长)、再做一步更新——每个字符只看一遍:

压缩器参数个数在线码长(bit/字符)
TinyGPT,宽度 168,8321.7270
TinyGPT,宽度 3229,9521.1190
TinyGPT,宽度 64109,0560.8282
TinyGPT,宽度 128414,7200.7198
自适应 3-gram(边数数边编码)—1.2323
自适应 5-gram—0.6293
自适应 8-gram—0.5970
gzip -9—0.9310
bz2 -9—0.4371
lzma -9—0.6873

在线码长随模型变大而单调变短:宽度 16 → 128,参数多了 47 倍,码长从 1.73 降到 0.72 bit/字符。这正是 Rae 的论点:大模型学得更快,损失曲线下的面积更小,而且不用为参数付钱。

演示 2 用两段式给同样 4 个模型记账(训练完的参数按 32 bit 存,再加上用训练完的模型编码整个数据流):

宽度参数 × 32 bit(折合 bit/字符)数据 | 训练完的模型两段式合计在线码长
160.16320.88971.05291.7270
320.55340.53631.08981.1190
642.01510.50172.51680.8282
1287.66300.48278.14570.7198

同一组模型,两种记账给出相反的排序:两段式偏爱小模型(宽度 128 的模型光参数就要摊上 7.66 bit/字符),在线编码偏爱大模型。“谁压缩得最好”这个问题,必须先说清楚”模型算不算钱、怎样算”。

4.4 一个和预想不同的结果:bz2 赢了

我原本期待在线训练的神经网络能在这个数据流上击败通用压缩器。实际上,bz2(0.44)比所有在线训练的神经网络和自适应 n-gram 都好。演示 3 把数据流分成十段,看每一段的边际码率:

数据流宽度 16宽度 32宽度 64宽度 128bz2
0–10%3.5153.0042.5912.0590.511
10–20%2.5142.0831.1610.7850.427
50–60%1.3400.6490.5490.5370.517
90–100%0.9320.5580.5070.4990.412

**学习曲线就是压缩曲线。**神经网络是从零开始学的,前 10% 的数据几乎都花在”学会这是什么语言”上(宽度 128 的模型要 2.06 bit/字符);它的边际码率一直在下降,最后 10% 降到 0.50 左右,但还没追上 bz2。原因有两个:这个世界的文本由几十种模板生成,信息量本来就很低(第 1 讲的 base model 充分训练后约 0.35 bit/字符),bz2 的块排序变换很擅长这种高度规则、大量相似片段的文本;而 173 万个字符对一个从零开始学的神经网络来说太少了——在线学习的压缩器要在足够长的数据流上才划算。LLaMA 读了 1.4 万亿个 token;我们只有 173 万个字符。

这个”失败”本身说明了 Rae 论证里一个容易被忽略的前提:“更大的模型是更好的压缩器”是在数据极多的条件下成立的。数据少的时候,学习的代价(损失曲线开头那一大块面积)会吃掉模型能力带来的好处。

4.5 为什么这暗示”更好的预测 = 更好的泛化”

在线编码还有一个很漂亮的性质。在 LLaMA 那样每条数据只看一遍(单遍训练,第 4 讲)的训练里,第 tt 步的训练损失是模型在一批从没见过的数据上的损失——它同时也是测试损失。所以:

  • 训练损失曲线就是泛化曲线。没有”训练损失低但测试损失高”的过拟合问题。
  • 曲线下的面积小,意味着模型用更少的样本学会了更多——不只是最终状态好,学习过程本身也高效。这是”泛化能力”的一个很自然的定义。

Rae 的报告据整理强调了这一点:目标不是”更好地预测训练数据”(一个把训练集背下来的模型可以在训练集上做到 0 损失,但这对理解训练数据背后的世界毫无帮助),而是用最短的描述刻画产生这些数据的世界。在线编码把这两件事区分开了:背诵只在”见过之后”才有用,而在线码长只计”见过之前”的损失。

这也给第 4 讲的规模定律一个新的读法:损失随算力的幂律下降,就是压缩率随算力的幂律提高。规模定律之所以重要,是因为它在预测”更多的算力能把世界压缩得多好”。

4.6 报告的”总纲”

据整理,Rae 把这些归结成一个研究方向:明确目标任务,收集尽可能多的有效数据,然后用一切可用的方法把它无损压缩到极致——一个任务的最短有效描述,代表了对这个任务最深的理解。整理中的一个例子是翻译:用一本词典逐词查,描述很长、效果也差;加上语法规则和固定搭配,描述更短,效果也更好。更短的描述 = 抓住了更多规律 = 更好的泛化。下一节把这个直觉放到它的理论基础上。


5. 最小描述长度与奥卡姆剃刀 · MDL and Occam’s razor

5.1 Kolmogorov 与 Solomonoff

“最短的描述”可以被严格定义。Kolmogorov 复杂度 K(x)K(x) 是能输出字符串 xx 的最短程序的长度:一个由一百万个 “ab” 组成的字符串,KK 很小(“打印 ‘ab’ 一百万次”);一串真正随机的字符,KK 约等于它本身的长度——没有比”直接把它写出来”更短的描述。

Solomonoff 归纳(1964)把它变成了一个预测理论:给每个可能解释数据的程序一个先验概率 2−程序长度2^{-\text{程序长度}},然后用所有与已观察数据一致的程序做贝叶斯预测。这是”奥卡姆剃刀”的数学版本:短的解释先验概率更高。Solomonoff 证明了这种预测器在一个很强的意义下是最优的——它犯的错误总量是有界的。Hutter 的 AIXI 理论把它推广到行动的智能体;他在 2006 年设立的 Hutter 奖正是奖励”把一段维基百科压得更小”的人,理由和 Rae 的报告一样:更好的压缩需要更多的理解。

问题是,K(x)K(x) 和 Solomonoff 归纳都不可计算(要遍历所有程序)。所以这个理论只能当作”北极星”:它告诉我们方向,但不能直接用。

5.2 最小描述长度(MDL)

Rissanen(1978)的最小描述长度原则是一个能用的近似:在一族模型里,选使”描述模型 + 用模型描述数据”总长度最小的那一个——也就是 §3.3 的两段式码长 L(M)+L(D∣M)L(M) + L(D \mid M)。复杂的模型让 L(D∣M)L(D \mid M) 变小,但 L(M)L(M) 变大;简单的模型反之。MDL 在两者之间找平衡,这正是防止过拟合的思想:一个把训练集全部背下来的模型,L(D∣M)L(D \mid M) 几乎是 0,但 L(M)L(M) 和数据本身一样长——没有任何压缩。

§4 的在线编码是 MDL 的另一种实现:它不需要显式地给模型编码,模型的”复杂度”体现为学习的代价——复杂的模型开头学得慢,损失曲线开头那一段面积更大。

5.3 迷你实验:用码长选模型

代码 03 演示 1 用这个想法给 n-gram 模型选阶数 nn(1 到 10)。四种选法:训练集损失、验证集损失、在线码长(自适应 n-gram 一边数数一边编码)、两段式码长(朴素地把计数表也发过去:每个非零计数记一条,上下文和字符 nlog⁡244n \log_2 44 bit,计数用 Elias γ 码)。训练集 30,015 个字符时:

nn训练集验证集在线码长两段式
31.3451.4431.6522.467
40.7070.9051.1612.839
50.5480.8221.1033.722
70.4230.7961.1405.968
90.3850.8171.2408.709
100.3870.8481.30510.258

(加粗的是每一列的最小值。)换三种训练集大小,每种选法选出的 nn:

训练集字符数按训练集损失按验证集损失按在线码长按两段式码长
10,0169552
30,0159753
262,986101084
  • 训练集损失总是偏爱大 nn——上下文越长,越能背下训练集。它不能用来选模型。
  • 验证集损失在中间某处最好,而且最好的 nn 随数据量增加:1 万个字符时 5,3 万个时 7,26 万个时到 10 还在下降。数据越多,越复杂的模型越划算。
  • 在线码长完全不看验证集,却在 1 万个字符时正好选中 5,3 万个时选 5(验证集最好是 7),全部数据时选 8——偏保守,但跟着数据量一起变大,方向是对的。大 nn 的模型开头学得慢(大部分上下文都没见过),这段学习的代价被计进了码长:这就是奥卡姆剃刀。
  • 两段式码长选的 nn 太小(2–4)。原因是我们的计数表编码很浪费,模型的代价被高估了;更聪明的模型编码会小得多。MDL 的结论依赖”模型怎样编码”——这是两段式 MDL 在实践中最麻烦的地方,也是在线编码的一个优点:它回避了这个问题。

6. 这个论证的边界 · Where the argument breaks

“压缩 = 智能”是一个有力的视角,但把它当作定义会遇到几个问题。

6.1 压缩的是哪个世界

代码 03 演示 2 把同一份验证集做了几种变换(bit/字符;均匀分布是 5.46):

数据base modelgzipbz2lzma
原样0.3551.4781.1901.404
每一行倒过来写10.7341.4741.1691.402
每一行内部打乱字符11.5955.0484.7634.744
随机字符15.4385.6295.5795.732

把文本倒过来写,信息量一点没变,bz2 几乎不受影响;base model 却要 10.7 bit/字符——比什么都不知道的均匀分布还差一倍。它”自信地猜错”:它的预测建立在”迷你互联网是正着写的”这个先验上。随机字符没有任何规律,base model 要 15.4 bit/字符,代价最大。

所以”压缩 = 智能”要加一个前提:压缩的是我们关心的那个世界的数据。一个强的先验在它的世界里是巨大的优势,在别的世界里是负担。这也是 Solomonoff 归纳里那个”通用”先验的意义:它对所有可计算的世界都不太差。大语言模型之所以能”通用”,靠的是训练数据极其广泛(§7.1)。

6.2 有用的信息与没用的信息

无损压缩要求把每一个 bit 都编码下来,包括没用的那些。一张照片的像素噪声、一段日志里的时间戳、一串随机生成的 ID——它们的信息量很大(不可压缩),压缩它们花掉了大部分的码长,但”理解”它们没有任何意义。强化学习里有一个类似的问题,叫”嘈杂的电视”:一个以”减少预测误差”为目标的智能体,可能会盯着一台播放雪花噪声的电视不放。

Rae 的表述里有一个关键的限定词:有用的信息。但”什么是有用的”本身就不是压缩能回答的问题——它需要一个目标、一个任务、一个价值判断。这也是为什么 Rae 的”总纲”第一步是”明确目标任务”:压缩是在一个给定的数据分布上衡量理解的尺子,而选择哪个分布,是尺子之外的事。

6.3 复制、上下文与”回去抄”

代码 03 演示 3 构造了一种特别的数据:每一行是一个 20 字符的随机串,空一格,再把同一个串写一遍(bit/字符;随机串的熵是 log⁡226=4.70\log_2 26 = 4.70):

压缩器第一次出现第二次出现
base model13.50813.835
自适应 8-gram5.5762.243
gzip4.9820.794
lzma4.9750.400

第一次出现谁也压不了。第二次出现时,gzip、lzma 只要 0.4–0.8 bit/字符:它们的核心机制(LZ77)就是”这一段前面出现过,回去抄”。自适应 8-gram 也部分学会了。但 base model 第二次出现的代价和第一次几乎一样:它不会回去抄。第 5 讲已经发现,这个模型在训练中没有形成 induction head——“找到前面出现过的同一段,照着往下写”的注意力电路。

这个对比提醒我们两件事。第一,“损失低”不等于”什么都会”:base model 在它的世界里是最好的压缩器,却缺少一个 gzip 都有的能力。第二,大语言模型在上下文里学习新规律的能力(第 5 讲),从压缩的角度看就是”在当前文档里找到可以复用的结构”——induction head 可以说是神经网络版的 LZ77。第 12 讲会专门训练一个形成了 induction head 的小模型,看看这个电路长什么样。

6.4 计算与速度

Kolmogorov 复杂度和 Solomonoff 归纳不在乎计算量:最短的程序可能要运行一万年。而现实中的智能是有资源限制的:一个能在一秒钟里给出答案的系统,和一个要算一万年的系统,不是一回事。用大语言模型做压缩器也有同样的问题:压缩和解压都要跑一遍模型,比 gzip 慢几个数量级(§3.2)。所以”更好的压缩 = 更好的智能”至少要加上”在同样的计算预算下”——这恰好又把问题带回了第 4 讲的规模定律:在给定的算力下,怎样得到最低的损失。

6.5 压缩能力不等于一切能力

最后,压缩衡量的是”预测数据”的能力。它不直接衡量:按指令行事(第 7 讲:base model 在续写文档,不是在完成任务)、做出决策、在世界上采取行动、诚实(一个完美的压缩器会同样准确地预测真话和谎话,取决于训练数据里有什么)。Rae 的论断说的是基础模型——“基础”二字很关键:压缩可能是通用能力的基础,但从”基础”到”有用的助手”,还有第 6、7 讲的整个适配与对齐。


7. 2023 年之后 · Since 2023

Rae 的报告之后,“语言模型就是压缩器”这个视角得到了更多直接的实验检验。这里只列和本讲直接相关、比较确定的几条。

7.1 语言模型是通用压缩器吗

Delétang et al.(2023,Language Modeling Is Compression,DeepMind)把这件事做成了系统的实验:用大语言模型加算术编码去压缩不是文本的数据。主要训练在文本上的 Chinchilla 70B,把 ImageNet 的图像块压到原始大小的 43.4%,把 LibriSpeech 的语音样本压到 16.4%,分别好于专门的压缩格式 PNG(58.5%)和 FLAC(30.3%)。这支持了”大语言模型是通用的预测器”——它们能在上下文里认出新模态数据的规律(§6.3 说的上下文学习)。

但同一篇论文也强调了 §3.3 的问题:如果把模型本身的大小算进去,这些大模型在这些(相对小的)数据集上就不再是好的压缩器——两段式记账下,模型越大,越需要海量的数据才能把参数的代价摊薄。这和我们代码 02 演示 2 的结论一致。论文还指出,压缩的视角为规模定律、分词和上下文学习提供了新的理解;以及反过来,任何压缩器都可以变成一个生成模型(例如用 gzip 来采样)。

7.2 压缩能力与基准分数

Huang et al.(2024,Compression Represents Intelligence Linearly)比较了 31 个公开的大语言模型:用它们压缩外部文本语料的效率,和它们在 12 个基准(知识与常识、编程、数学推理)上的平均分,几乎是线性相关的。他们由此主张压缩效率可以作为一种不需要人工标注的评测指标。这是对 Rae 论断的一个直接的经验支持。需要注意的是:相关性是在”同样被训练来做下一个 token 预测”的一族模型之间观察到的;它说明在这一族模型里,压缩得好的通常也更强,但不说明压缩是能力的定义(§6 的那些边界仍然存在)。

7.3 用码长评测模型

在实践中,“在一份没见过的语料上的 bit/byte”已经成了比较语言模型的标准指标之一(例如 Pile 上的 bit/byte)。用 byte 而不是 token 作单位,是为了让不同分词器的模型可以公平比较——这本身就是压缩视角的产物:分词器不同,“每个 token 多少 bit”不可比,“每个字节多少 bit”可比。第 4 讲 §11 提到的推理时计算、思维链也给压缩视角带来了新问题:一个在回答前”想”了很久的模型,它的”码长”该怎样算?这还没有公认的答案。

7.4 哪些结论仍然成立

仍然成立:预测与压缩的等价(定理);交叉熵就是码长;单遍训练的损失曲线是泛化曲线;更低的损失在同一族模型里通常对应更强的能力。需要谨慎的:“压缩 = 智能”作为定义;“模型越大压缩越好”在两段式记账下不成立;压缩衡量不了有用性、对齐和行动。比较稳妥的说法是:压缩是衡量一个模型对它所学世界的理解程度的一把好尺子,而不是智能的全部。


8. 小结 · Summary

  1. 预测就是压缩:概率 pp 的符号最优码长 −log⁡2p-\log_2 p;用模型 qq 编码真实分布 pp,平均码长是交叉熵 H(p,q)=H(p)+KL(p∥q)H(p, q) = H(p) + \mathrm{KL}(p \| q)。模型的损失字面意义上就是它的压缩率。
  2. 算术编码让任何能给出概率分布的模型变成真正的无损压缩器,码长几乎等于交叉熵;哈夫曼编码受”每个符号至少 1 bit”的限制。base model 把迷你互联网的验证集压到 0.356 bit/字符(22 倍),比 bz2 小 3 倍多。
  3. 解码器必须有逐位相同的模型和计算;模型本身的代价在两段式记账下会压倒一切(base model 的参数比它的训练文本还大)。
  4. 在线编码:发送方和接收方运行同一份训练代码,码长 = 训练损失曲线下的面积 + 代码长度,不用发送参数。据转述,Rae 用它算出 LLaMA-65B 把 5.6 TB 压到约 400 GB(14 倍)。我们的实验里在线码长随模型变大而变短(1.73 → 0.72 bit/字符),两段式则相反。
  5. 单遍训练的训练损失就是泛化损失,曲线下的面积小 = 学得快 = 泛化好;规模定律就是压缩率的规模定律。
  6. MDL 与奥卡姆剃刀:Kolmogorov 复杂度、Solomonoff 归纳是不可计算的理想;MDL 是可用的近似。在线码长不看验证集,就能选出接近最优的 n-gram 阶数;朴素的两段式会因为模型编码太浪费而选错。
  7. 边界:压缩的是哪个世界(倒过来写的文本让 base model 比均匀分布还差);有用的信息 vs 噪声;不会”回去抄”(没有 induction head);计算资源;压缩不等于有用、对齐、行动。
  8. 2023 年之后:大语言模型能压缩图像和音频(Chinchilla 70B:ImageNet 43.4%、LibriSpeech 16.4%),但算上模型大小就不划算;31 个模型的压缩效率与基准分数近似线性相关。

9. 常见误区 · Common pitfalls

  1. “交叉熵只是一个训练目标,和压缩是两回事。” 用算术编码,交叉熵就是实际的码长,只差千分之几 bit/字符(代码 01 演示 2)。
  2. “把模型在训练集上的损失降到 0,就是最好的压缩。” 那是背诵:模型本身就成了数据的一份拷贝。在线编码只计”见过之前”的损失;两段式编码要把背下数据的模型也算进去。
  3. “更大的模型总是更好的压缩器。” 在在线编码、数据很多时是这样;在两段式记账、数据较少时正好相反(代码 02 演示 2)。在小数据上,学习的代价也可能让它输给 bz2(演示 1)。
  4. “一个好的压缩器对什么数据都好。” 压缩器的好坏取决于它的先验和数据是否匹配。base model 在倒过来写的文本上比均匀分布还差一倍(代码 03 演示 2)。
  5. “压缩得越好就越智能,所以压缩是智能的定义。” 无损压缩也要编码噪声和没用的信息;它不衡量按指令行事、决策、诚实;它忽略计算成本。它是一把好尺子,不是定义。
  6. “Kolmogorov 复杂度给出了最优的压缩器,只是我们还没找到。” 它是不可计算的——不存在一个程序能对所有字符串算出它。我们只能用 MDL、在线编码这类可计算的近似。
  7. “用验证集选模型和用 MDL 选模型是一回事。” 它们常常选得相近(代码 03 演示 1),但 MDL 不需要留出数据;而且两段式 MDL 的结论依赖于模型怎样编码。
  8. “在线码长就是最后的损失。” 它是整条损失曲线下的面积——开头学得慢的那一段占了很大比例(代码 02 演示 3 的第一行)。

10. 练习 · Exercises

练习 1(码长) 模型给真实的下一个字符分别打出概率 0.9、0.5、0.01,编码它各要多少 bit?一个模型对 99% 的字符给出 0.9、对 1% 给出 0.01,平均码长大约是多少?

参考答案

−log⁡20.9=0.152-\log_2 0.9 = 0.152,−log⁡20.5=1-\log_2 0.5 = 1,−log⁡20.01=6.64-\log_2 0.01 = 6.64 bit。平均约 0.99×0.152+0.01×6.64≈0.2170.99 \times 0.152 + 0.01 \times 6.64 \approx 0.217 bit/字符。少数”意外”贡献了接近三分之一的码长——这就是为什么语言模型的损失对”自信地猜错”很敏感(代码 03 演示 2 里 base model 在倒过来写的文本上要 10.7 bit/字符)。

练习 2(交叉熵的分解) 真实分布是 4 个等概率的符号,模型用的是 (0.5, 0.25, 0.125, 0.125)。熵、交叉熵、KL 各是多少?多出来的 bit 意味着什么?

参考答案

H(p)=2H(p) = 2 bit。H(p,q)=0.25×(1+2+3+3)=2.25H(p, q) = 0.25 \times (1 + 2 + 3 + 3) = 2.25 bit。KL=0.25\mathrm{KL} = 0.25 bit。每个符号多花 0.25 bit,是因为模型”以为”第一个符号更常见,给了它短码、给了其他符号长码;这多出来的部分就是模型的无知,换一个更好的模型可以消除。熵的 2 bit 则是数据本身的不确定性,谁也消除不了。

练习 3(把符号打包) 信源 B 是 (0.9, 0.05, 0.03, 0.02)。如果把每两个符号打包成一个”超级符号”(16 种),再做哈夫曼编码,平均每个原始符号要多少 bit?和熵 0.6175、单个符号的哈夫曼 1.15 比较。

参考答案

用代码 01 的 huffman_lengths 对 16 个两两乘积的概率做哈夫曼编码,平均每个原始符号约 0.769 bit:比 1.15 好得多,但离 0.6175 还有距离。打包得越长越接近熵,但码表的大小随打包长度指数增长。算术编码相当于”把整条消息打包成一个符号”,却不需要码表——这是它在实践中胜出的原因。

练习 4(压缩率) base model 的实际码长是 0.3563 bit/字符,bz2 是 1.1905。原始文本每个字符 8 bit,两者的压缩率各是多少?

参考答案

8/0.3563≈22.58 / 0.3563 \approx 22.5 倍,8/1.1905≈6.78 / 1.1905 \approx 6.7 倍——和代码 01 演示 2 表里的 22.4×、6.7× 一致(表里按整字节算,略有差别)。

练习 5(LLaMA 的平均损失) 据转述,LLaMA-65B 的损失曲线下面积约 400 GB,训练了 1.4 万亿个 token。平均每个 token 的损失是多少 nats?LLaMA 论文里 65B 模型训练结束时的损失大约在 1.5 nats 附近,两者为什么能对得上?

参考答案

400×109×8/1.4×1012≈2.29400 \times 10^9 \times 8 / 1.4 \times 10^{12} \approx 2.29 bit/token ≈1.58\approx 1.58 nats/token。这是整个训练过程的平均损失,包括开头损失很高的那一段;由于训练的绝大部分 token 是在损失已经降到 1.5–1.8 附近之后才看到的,平均值只比最终损失高一点。(这也说明:数据越多,开头”学习的代价”在总码长里占的比例越小。)

练习 6(两段式什么时候划算) 宽度 128 的模型有 414,720 个参数,按 32 bit 存。假设训练好的模型比 bz2 每个字符少 0.14 bit,数据流至少要多长,两段式编码才能赢 bz2?

参考答案

参数要 414,720×32≈1.33×107414{,}720 \times 32 \approx 1.33 \times 10^7 bit。每个字符省 0.14 bit,需要 1.33×107/0.14≈9.5×1071.33 \times 10^7 / 0.14 \approx 9.5 \times 10^7 个字符,约 9500 万个字符——是代码 02 数据流的 55 倍。这就是”大模型要配大数据”在压缩视角下的一个算术版本。

练习 7(改一行代码,预测结果) 把代码 02 的学习率 LR 从 3e-3 改成 1e-3。你预测 4 个模型的在线码长会怎样变?顺序会变吗?

参考答案

学习率变小,模型学得更慢,损失曲线开头那一大块面积会更大,在线码长大概率都变长(具体数字以你运行的结果为准)。大模型是否仍然更好不一定:在很短的数据流上,学得慢对所有模型都不利,模型之间的差距可能缩小。这个练习说明在线码长衡量的是”模型 + 训练配方”的整体,而不只是模型结构——Rae 的论证里,“训练代码”本身就是压缩器的一部分。

练习 8(改一行代码,预测结果) 在代码 03 演示 1 里,把 n-gram 的平滑参数 k 从 0.01 改成 1。你预测按验证集选出的 nn 会变大还是变小?在线码长呢?

参考答案

k 变大,没见过的上下文和低计数的上下文会被”拉平”得更厉害,大 nn 的模型(上下文大多只见过几次)受伤最重,所以验证集和在线码长选出的 nn 大概率都会变小(具体数字以你运行的结果为准)。这说明”最好的模型复杂度”依赖于”怎样从数据里估计模型”——平滑、正则化本身也是模型的一部分。

练习 9(概念:单遍训练) 为什么说在每条数据只看一遍的训练里,训练损失就是测试损失?如果训练了 4 个 epoch,在线码长该怎样定义?

参考答案

单遍训练时,第 tt 步的 batch 是模型从没见过的,它在这个 batch 上的损失就是一个对”新数据”的损失估计。训练多个 epoch 时,第二遍看到的数据模型已经见过,损失会偏低,不再是泛化的估计;在线码长只能按”每条数据第一次出现时的损失”来算——后面几个 epoch 的训练相当于”不发新数据、只让接收方多训练几步”,不增加码长,但也不改变已经发送的数据的码长。这也是为什么在线编码的视角天然偏爱”多数据、单遍”的训练方式。

练习 10(概念:嘈杂的电视) 一个视频模型学会了完美地预测摄像头的传感器噪声(假设这种噪声有某种可学的规律)。它的压缩率因此提高了很多。它”更智能”了吗?Rae 的表述怎样回应这个问题?

参考答案

按”压缩率 = 智能”的字面定义,它更智能了;但直觉上,学会传感器噪声的规律对”理解视频里发生了什么”没有帮助。Rae 的表述里有”有用的信息”这个限定——压缩的是哪些数据、哪些信息值得压缩,取决于我们关心的任务(“总纲”的第一步是明确目标任务)。这说明压缩是衡量理解的尺子,但尺子要量什么,需要在尺子之外决定(§6.2)。

练习 11(概念:确定性) 你用一个大语言模型压缩了一个文件,在一张 GPU 上压缩,在另一种型号的 GPU 上解压,结果解出来的是乱码。最可能的原因是什么?怎样避免?

参考答案

两张 GPU 上的浮点计算不是逐位相同的(不同的 kernel、不同的累加顺序),某个位置的概率分布差了一点点,量化成整数频率表时就可能不同;从那个字符开始,解码器的区间和编码器不一致,后面全部错乱。避免方法:用确定性的推理(固定 kernel、固定累加顺序、甚至用整数或定点运算),或者把概率量化得更粗、让小的浮点误差不影响量化结果(仍然不能完全保证)。这也是用神经网络做实用压缩器的一个工程难点。


11. 延伸阅读 · Further reading

原课相关(2/27 Compression for AGI)

  • Jack Rae. Compression for AGI. Stanford MLSys Seminar, 2023 年 2 月(公开录像)。
  • Rae et al. Scaling Language Models: Methods, Analysis & Insights from Training Gopher. 2021. arXiv:2112.11446

信息论与 MDL(§2、§5 的依据)

  • Shannon. A Mathematical Theory of Communication. 1948.
  • Shannon. Prediction and Entropy of Printed English. 1951.
  • Witten, Neal & Cleary. Arithmetic Coding for Data Compression. Communications of the ACM, 1987.
  • Rissanen. Modeling by Shortest Data Description. Automatica, 1978.
  • Grünwald. The Minimum Description Length Principle. MIT Press, 2007.
  • Blier & Ollivier. The Description Length of Deep Learning Models. NeurIPS 2018. arXiv:1802.07044

2023 年之后

  • Delétang et al. Language Modeling Is Compression. ICLR 2024. arXiv:2309.10668
  • Huang et al. Compression Represents Intelligence Linearly. 2024. arXiv:2404.09937

12. 下一讲预告 · Next lecture

这一讲从外部衡量模型:它把数据压得多好。但压缩率不告诉我们模型内部在做什么——代码 03 演示 3 里,base model 明明是迷你互联网最好的压缩器,却不会”回去抄”。为什么?它内部到底学到了什么算法?

第 12 讲:机制可解释性——逆向工程 Transformer 学到的算法(1/30 客座 Stella Biderman)打开模型:残差流、注意力的 QK 和 OV 电路、logit lens;训练一个只有注意力层的小模型,看 induction head 怎样在训练中突然形成,并用消融和激活修补证明它就是”复制”的来源;看一个在模加法上 grokking 的网络怎样用傅里叶变换做加法;以及一个特征多于维度时,模型怎样把它们”叠加”在一起,稀疏自编码器又怎样把它们分开。

← 返回课程目录