技术博客

困惑度的信息论含义:有效分支因子与每 token 比特数

第 21 / 147 篇 · 2025-08-27 · ai · 困惑度信息论评测

语言模型的评测表上总有一列 perplexity。直觉说法人人会背:"模型平均在多少个候选之间犹豫"。但这个数字的确切定义、它和 loss 的换算、什么时候它比、什么时候它比了也白比,值得花一篇的篇幅钉死。困惑度被误用的频率在所有评测指标里大概排第一。

先看一个真实场景把问题立起来:两个开源模型报告同一基准上的 PPL,一个 9.2 一个 11.5,差了 25%。直接下结论"前者显著更强"之前,至少要核对三件事:两个模型的 tokenizer 是否相同、评测的滑窗口径是否一致、语料版本是否相同。任何一项不同,这 25% 都可能是尺子的刻度差而不是模型的能力差。这篇的目标就是让你拿到 PPL 数字时,先本能地去查这些口径。

定义:交叉熵的指数化

设模型在 $N$ 个 token 上的平均交叉熵(沿用上一篇的记号):

$$ H=\frac1N\sum_{t=1}^N-\log_2 p_\theta(x_t\mid x_{

困惑度就是把它指数化:

$$ \boxed{\ \mathrm{PPL}=2^{H}=\exp\!\Big(\frac1N\sum_t-\ln p_\theta(x_t\mid x_{

底数无所谓,2 和 $e$ 只是单位换算(bit 与 nat),关键是 PPL 与交叉熵互为严格单调变换——对同一批数据,按 PPL 排名和按 loss 排名永远一致。它不改变任何优化与比较结论,改变的只是数字的可读性。

换算公式记一组就够:$\log_2\mathrm{PPL}=\frac{\text{loss(nats)}}{\ln 2}$,即 $\mathrm{PPL}=2^{\text{loss}/0.693}$。loss 2.0 nats → $2^{2.89}\approx7.4$;loss 3.0 → $2^{4.33}\approx20$。训练日志里 nat 单位的 loss 想心算成 PPL,除以 0.7 再做 2 的幂即可。

再补一组大模型时代的参考坐标,方便建立量级感。LLaMA-2-7B 级别的模型在通用网页文本上 token 交叉熵约 1.8~2.0 nats(PPL 6~7);同模型在代码上约 0.6~0.9 nats(PPL 1.8~2.5,代码的重复结构多、可预测性强);在数学证明类文本上则显著更高。领域间 PPL 差数倍是常态,不是模型问题——各领域的内在熵不同,单看一个数必须问"在哪个语料上测的"。

有效分支因子:直觉的正确版本

"平均在 PPL 个候选之间犹豫"这个说法的精确化:假想一个简化世界,模型在每个位置面对 $k$ 个等概率选项,则逐位置交叉熵是 $\log_2 k$ 比特,PPL 恰好是 $k$。所以 PPL 是等效均匀分支数:真实分布既不均匀也不固定分支数,但它产生的不确定性(以交叉熵度量)等同于每一步在一个 PPL 选 1 的均匀分布里猜。

这个"等效"有两层限制要说清。其一,它只保持交叉熵这一个数字不变,分布的其他方面(置信度形态、尾部)全被抹平——两个 PPL 相同的模型采样行为可能完全不同(后文展开)。其二,分支因子的画面在因果建模里对应"在给定全部上文后选下一个 token",不是独立重抽——PPL 度量的是条件预测,不是独立同分布猜词。

这里有个信息论坐标可以校准直觉。Shannon 在 1951 年用人类预测实验估出英文字母的熵约 $0.6\sim1.3$ bit/字符;现代大模型把英文文本压到约 $0.9$ bit/字节量级。换算到 token 粒度:英文一个 BPE token 约 4 字符,好模型的 token 级交叉熵约 $2.5\sim3.5$ bit,即 PPL $6\sim11$。也就是说当前最强的模型在通用文本上,等效于每步在 10 个左右的等概率候选里选——剩下的是语言本身的不可约不确定性。这个底不是零:语言有内在随机性,PPL=1 的完美模型只在信息论错误的世界里存在。

这组坐标还有一个用法:估训练所需的数据量量级。文本总信息量 ≈ 字节数 × BPB,语料库的信息论"含金量"由此可算——它决定了模型能从中榨出的预测能力上限,数据规模的讨论因此有了物理单位。

跨 tokenizer 比较为什么无效

这是困惑度最大的坑。PPL 的分母是 token 数 $N$,而 token 数由 tokenizer 决定:同一段文本,GPT-2 词表切成 $N_1$ 片,LLaMA 词表切成 $N_2$ 片,$N_1\ne N_2$。总的每序列负对数似然是可比的(它度量整段文本的概率),但除以不同的 $N$ 再指数化之后,数值不再可比。词表更大、切分更细的 tokenizer 单 token 信息量少,token 级交叉熵天然更低——模型没变好,尺子变了。

举个具体的数。一段 1000 字节的英文,tokenizer A 切成 250 个 token、模型逐位 loss 合计 500 nats,token PPL $=e^{500/250}\approx7.4$;tokenizer B 切得更细,400 个 token、同样的总信息量下逐位合计仍是约 500 nats(总概率不变是链式法则决定的),token PPL $=e^{500/400}\approx3.5$。同一个模型换把尺子,PPL 差一倍——这就是为什么严肃评测一律报 BPB 或总 NLL。

修法是换公共分母:bits per byte,把总 NLL 除以原始文本字节数:

$$ \mathrm{BPB}=\frac{\sum_t-\log_2 p_\theta(x_t\mid x_{

字节是 tokenizer 无关的单位,BPB 因此可以跨模型、跨词表公平比较。HuggingFace 的评估实践和 llama.cpp 的日志里都走这条路。看到论文只报 token PPL 而不报词表细节时,心里要先打一个问号。

同样的逻辑也适用于跨语言比较:中文一个 UTF-8 字符占 3 字节,不同 tokenizer 对中文的切分粒度差异更大(一个字 1~3 个 token 不等),token PPL 的口径漂移比英文更严重。多语言模型报告里真正可比的只有 BPB 或每字节对数似然。

作为指标的盲区

即使单位问题解决了,PPL 还有两个结构性盲区。一是它是几何平均:$\mathrm{PPL}=\exp(\text{平均对数损失})$,对极端值敏感——模型把一个 token 的概率从 $10^{-6}$ 降到 $10^{-9}$,PPL 明显变差,但这种尾部事件对生成质量几乎无影响;反之少数高困惑 token(罕见专名、长程依赖)被大量容易 token 平均掉,长程能力的退化在 PPL 上可能完全看不见。二是它度量的是对真实文本的拟合,不是生成质量:死记硬背训练集可以把测试 PPL 刷得很低,生成却一塌糊涂。这两个盲区正是后来 Needle-in-Haystack、下游任务评测兴起的部分原因。

盲区的存在不等于指标无用——PPL 仍然是训练过程监控的第一指标:平滑、密集、与优化目标同构,曲线形态的每一次抖动都有诊断价值(loss spike 那篇会用到)。要警惕的只是把它从"训练监控指标"升格为"模型能力结论"的那一步跳跃。

还有第三个操作性盲区:PPL 与采样行为解耦。评测时模型对每个位置给出全词表分布,PPL 只读取其中真实 token 那一格的概率;而实际生成时用户看到的是 temperature、top-p 采样之后的轨迹。两个 PPL 相同的模型,一个分布尖锐一个平坦,生成风格可能天差地别。把 PPL 当成"模型能力"的全部度量,等于只看考试均分不看答卷分布。

困惑度换算链:nats → bits → PPL;跨 tokenizer 比较要先落到 bits-per-byte loss(nats/token) 训练日志原始值 bits/token ÷ ln2 ≈ ÷0.693 PPL = 2^bits 等效均匀分支数 参考坐标(通用英文文本) Shannon 1951 人类估计 0.6~1.3 bit/字符 现代 LLM ~0.9 bit/byte ≈ PPL 6~11 跨模型比较用 BPB 总 NLL ÷ 原始字节数

几何平均的敏感性:一个数值演示

"对极端值敏感"值得给个具体数。设 1000 个 token 里 999 个的逐位置损失是 2.0 nats,一个是 20 nats(某罕见词几乎被赋零概率):

$$ \bar H=\frac{999\times2.0+20}{1000}=2.018\ \Rightarrow\ \mathrm{PPL}\approx7.53, $$

对比"全部 2.0"的 $e^{2}\approx7.39$:单个灾难性 token 只把 PPL 推高了 2%。反过来,把单 token 概率从 $10^{-3}$ 修到 $10^{-1}$(损失 6.9 → 2.3),PPL 同样只动千分之几。PPL 的刻度把长尾事件压成了噪声——这对衡量"平均文本拟合"是优点,对发现"模型在某类 token 上崩了"是灾难。工程上的补救是分桶看:按词频、按领域、按上下文长度分别统计逐 token 损失,任何一桶的恶化都会在分布视图里显形,而在总 PPL 里沉底。

历史上 PPL 来自语音识别与统计语言模型时代(Jelinek 等人在 70 年代就用它评 n-gram 模型),那时 tokenizer 概念稀薄、文本以词为单位,跨模型比较的单位问题不突出。词表时代把这个指标的隐性前提——分母单位一致——打破了,误用也随之而来。

补一个直觉校验帮记忆:"几何平均"意味着 PPL 是各位置困惑度的 $\big(\prod_t \mathrm{ppl}_t\big)^{1/N}$ 形式。几何平均小于等于算术平均,且对接近零的因子极其敏感——一个位置概率近零,乘积立刻塌掉。这就是为什么"尾部 token 崩了"在 PPL 上要么爆炸($p\to0$)要么消失(被平均),几乎没有中间态。

工程落地的三个细节

实际评测代码里,PPL 计算还有几处容易踩的坑。其一,滑窗与上下文的处理:长文档评测时若把文本切成不重叠定长块,每块开头的 token 缺少上下文,loss 天然偏高,整体 PPL 被系统性抬高;标准做法是用带重叠的滑窗(stride < window),只对每个窗口的后半段计损失,GPT-2 时代起 WikiText 的评测就是这么跑的。其二,序列级 vs token 级平均:先把每句 loss 平均再对句子平均,与全部 token 摊平后再平均,结果不同——短句的权重在前者里被放大,跨论文比较时要确认口径。其三,log 的数值稳定:实现里永远不要先算概率再取 log($p$ 下溢到 0 时 $-\log p=\infty$),要从 logits 直接走 log-softmax,这正是框架把两者融合成 log_softmax/cross_entropy 单算子的原因。

其四,训练侧的 loss 与评测侧的 PPL 统计口径未必一致:训练 loss 常含 packing 后跨文档边界的预测位置,评测则严格按文档内条件概率算。两边差几个百分点是常态,对不齐不是 bug 而是定义差异——排查时先对齐"哪些位置计入分母"。

换算代码与 sanity check

import math

def nats_to_ppl(loss_nats: float) -> float:
    return math.exp(loss_nats)                 # PPL = e^(nats) = 2^(bits)

def nats_to_bpb(loss_nats: float, n_tokens: int, n_bytes: int) -> float:
    return loss_nats * n_tokens / (math.log(2) * n_bytes)

# loss 2.0 nats/token
print(nats_to_ppl(2.0))                        # 7.39
# 假设该 tokenizer 平均 1 token ≈ 4 byte
print(nats_to_bpb(2.0, n_tokens=1000, n_bytes=4000))   # 0.72 bit/byte

顺手验证 tokenizer 依赖性:同一份总 NLL,假设换成切分粒度加倍的 tokenizer(token 数翻倍、逐 token loss 减半),nats_to_ppl(1.0) 给出 $e\approx2.72$——PPL 从 7.39 掉到 2.72,而 BPB 纹丝不动。这就是上一节数值例子在代码里的样子,两行函数足以把坑演示出来。

两个反查也很有用:PPL 翻倍对应 bits/token 加 1(loss 加 0.693 nats);loss 降 0.1 nats,PPL 约除以 $e^{0.1}\approx1.105$。看曲线时记住这个非线性刻度,loss 后段的 0.05 改善比前段的 0.05 含金量高得多。

再把代码和上一篇的恒等式接上:$\sum_t-\log_2 p_\theta(x_t)$ 既是评测的分子,也是算术编码的码长。也就是说上面的 nats_to_bpb 不只是评测换算——它告诉你用这个模型当压缩器,平均每字节花多少比特。Chinchilla 级别的模型压英文网页文本能到 0.7~0.9 bit/byte,对比 gzip 的大约 3 bit/byte,"预测即压缩"在这里有具体的数字落差。

从压缩理论到生成质量的边界

把整篇的核心再拧紧一次:PPL 度量的是"预测下一个 token 有多好",这是压缩问题;用户要的是"生成的文本有多好",这是另一个问题。两者在训练分布附近相关(预测准的模型通常也生成得顺),但离训练分布越远(长程连贯性、事实一致性、指令遵循),相关性越弱。知道这个边界,就知道什么时候该放下 PPL 去看任务评测与人工偏好。

这条边界也解释了评测生态的演化顺序:早期模型差距大,PPL 足以区分;模型能力越接近,PPL 的区分度越先耗尽,评测重心被迫转向下游任务与偏好打分。指标的退役顺序,本身就是领域成熟度的一条曲线。

小结

困惑度是交叉熵的指数化,信息论含义是"等效均匀分支数",换算链是 nats ÷0.693 → bits → $2^{\text{bits}}$。它的两大使用纪律:跨 tokenizer 比较必须落到 bits-per-byte;作为平均指标它对尾部与长程能力不敏感,不能单独代表生成质量。掌握换算之后,评测表上的 PPL 数字就能翻译成"每步在多少个等效候选里猜"的直觉图景。

留一条使用建议收束全篇:报 PPL 时永远连带报四件事——语料、tokenizer(或改报 BPB)、滑窗口径、序列/token 级平均方式。四件齐了,这个数字才可复现、可比较;缺任何一件,它就只是一个好看的数。下一篇换一个问题:softmax 为什么是指数形式,答案藏在最大熵原理里。