PPL 是什么:把模型困惑度理解成平均岔路数
今天 Day 10 跑了第一份 llama-perplexity 结果:
Qwen2.5-7B-Instruct-Q4_K_M
WikiText-2 validation
ctx=512, chunks=16
Final estimate: PPL = 6.8790 +/- 0.30335
看到 PPL=6.8790 最容易产生一个问题:
这到底好不好?它在说模型错了 6.879 次吗?
不是。
这篇只讲一个直觉:PPL 可以先理解成模型预测下一个 token 时的“平均有效岔路数”。
如果 PPL=8,人话是:
模型在这个评测语料上,每一步预测正确下一个 token 的平均不确定性,
大致相当于在 8 个同样可能的 token 里选。
这个说法不是严格描述每一个位置都有 8 个候选,而是一个很好用的等价直觉。真实文本里,有的位置很容易,有的位置很难;PPL 把这些难度压成一个数。
1. 先用考试题理解:越不确定,PPL 越高
想象一个选择题考试,每题都有一个正确答案。
如果你每题都很确定,给正确答案很高概率:
正确答案概率 ≈ 1.0
那你的困惑度接近:
PPL ≈ 1
这表示几乎没有岔路:模型基本知道下一个 token 应该是什么。
如果你每题都在 8 个选项之间平均瞎猜:
每个选项概率 = 1/8
正确答案概率 = 1/8
那困惑度就是:
PPL = 8
这就是“平均岔路数”的来源。
LLM 的下一个 token 预测也是类似的题目。每一步,模型面对整个 vocab,比如 Qwen2.5 的输出词表行数大约是 15 万级,然后给每个 token 一个概率。评测时,我们已经知道真实文本的下一个 token 是什么,于是只看一件事:
模型给真实下一个 token 分了多高概率?
分得越高,模型越不困惑。分得越低,模型越困惑。
2. 最小数学地基:概率、log、交叉熵、PPL
先不要怕公式。这里只需要四个小概念。
2.1 概率:模型押中了多少信心
假设真实下一个 token 是 学习。
模型可能输出这样的概率:
P("学习") = 0.50
P("工作") = 0.20
P("模型") = 0.10
...
这表示模型给正确答案 学习 的信心是 50%。
另一个模型可能是:
P("学习") = 0.05
P("工作") = 0.20
P("模型") = 0.18
...
这时它虽然不一定“输出错”,但它对真实答案的信心很低。PPL 惩罚的就是这种低信心。
2.2 负 log:把“低概率”变成“高惩罚”
交叉熵里会出现 -log(p)。
先看几个数:
p = 1.00 -> -ln(p) = 0.000
p = 0.50 -> -ln(p) = 0.693
p = 0.25 -> -ln(p) = 1.386
p = 0.125 -> -ln(p) = 2.079
你不需要先背自然对数,只要抓住方向:
正确答案概率越低,-log(p) 越大,惩罚越重。
为什么不用 1 - p 这么简单的惩罚?因为语言模型是连乘问题。一篇文本里每个 token 的概率要连起来,很多小概率相乘会变成极小的数字。log 可以把连乘变成求和,计算更稳定,也更容易平均。
2.3 交叉熵:平均每一步有多意外
如果一段文本有 N 个 token,模型每一步都给真实 token 一个概率:
p1, p2, p3, ..., pN
交叉熵就是:
cross_entropy = - (log(p1) + log(p2) + ... + log(pN)) / N
人话:
平均每一步,真实答案让模型有多意外。
越意外,交叉熵越高。
2.4 PPL:把交叉熵还原成“有效岔路数”
PPL 是 perplexity,通常这样算:
PPL = exp(cross_entropy)
也就是把交叉熵从 log 空间还原回来。
如果:
cross_entropy = ln(8)
那么:
PPL = exp(ln(8)) = 8
所以 PPL=8 可以读成:
平均不确定性约等于每步在 8 个同样可能的候选里选。
更严格一点,它等价于正确 token 概率倒数的几何平均:
PPL = (1 / p1 * 1 / p2 * ... * 1 / pN)^(1/N)
如果这句看着绕,就记前面那句:平均有效岔路数。
3. 一个三步小例子:为什么不是简单平均
假设只有 3 个 token 要预测,模型给真实 token 的概率分别是:
第 1 步:0.50
第 2 步:0.25
第 3 步:0.125
对应的倒数是:
2, 4, 8
如果粗暴做算术平均:
(2 + 4 + 8) / 3 = 4.67
但 PPL 用的是几何平均:
(2 * 4 * 8)^(1/3) = 4
为什么是几何平均?因为整段文本的概率是每一步概率相乘,不是相加:
0.50 * 0.25 * 0.125
乘法世界里,对应的平均方式就是几何平均。交叉熵里的 log,就是为了把这个乘法世界搬到加法世界里算。
4. 回到今天的实验:PPL=6.8790 怎么读
今天命令是:
llama-perplexity \
-m ./models/Qwen2.5-7B-Instruct-Q4_K_M.gguf \
-f ./data/wikitext-2-valid.txt \
-ngl 99 \
-c 512 \
--chunks 16 \
--no-warmup
语料是 WikiText-2 validation 的一个本地副本,来源是 PyTorch examples 里的 wikitext-2 数据文件:
https://raw.githubusercontent.com/pytorch/examples/main/word_language_model/data/wikitext-2/valid.txt
这次只跑了 16 个 chunks,所以它是 Day 10 的入门基线,不是完整排行榜评测。结果是:
Final estimate: PPL = 6.8790 +/- 0.30335
人话解释:
在这段 WikiText-2 validation 切片上,
这个 Q4_K_M 量化模型预测下一个 token 的平均不确定性,
大约相当于每步在 6.88 个同样可能的 token 里选。
它不是说模型每 6.88 个 token 错一次,也不是说模型每次真的只看 6.88 个候选。它说的是一种平均不确定性。
+/- 0.30335 是估计误差范围。因为只跑了有限 chunks,结果会有波动。跑更多 chunks、固定同一套评测设置,数字会更稳定。
5. 为什么量化实验常用 PPL 当质量标尺
量化的核心问题是:
模型变小、变快以后,质量掉了多少?
如果只看生成样例,会很不稳定。同一个 prompt,采样参数一变、随机种子一变,输出就会变。人肉读 20 段回答也很主观。
PPL 的价值在于它更可重复:
同一个模型
同一个 tokenizer
同一个评测语料
同一个 context length
同一个 perplexity 命令
在这些条件固定时,你可以比较:
FP16: PPL = ?
Q8_0: PPL = ?
Q5_K_M: PPL = ?
Q4_K_M: PPL = ?
如果 Q4_K_M 比 FP16 的 PPL 只高一点点,但模型小很多、decode 更快,那就是很好的速度/质量 tradeoff。
所以 PPL 是量化实验里常见的第一道质量门槛:
PPL 没明显变差 -> 量化大概率没有严重损伤 next-token 能力
PPL 明显变差 -> 量化可能把模型的语言建模能力打坏了
注意这只是“第一道门槛”,不是全部质量。
6. PPL 的边界:它能说明什么,不能说明什么
PPL 能比较的是:
模型对某个语料的 next-token prediction 有多好。
它很适合看:
同模型不同量化版本
同语料下的质量退化
同设置下的 checkpoint 对比
但它不能直接说明:
这个模型是不是更会聊天
这个模型是不是更安全
这个模型是不是更会写代码
这个模型是不是更懂中文用户意图
原因很简单:WikiText 这种语料测的是语言建模,不是指令遵循。一个模型 PPL 更低,通常表示它更会预测这类文本;但真实产品还关心对齐、推理、工具调用、长上下文、格式遵循和安全边界。
所以正确用法是:
PPL 用来做量化质量的基础体检;
任务评测和人工检查用来做真实能力诊断。
7. 最后用三句话记住
第一句:
PPL 是 exp(cross_entropy)。
第二句:
PPL=8 可以理解成平均每步有 8 条有效岔路。
第三句:
量化实验用 PPL,是因为它能在固定语料和设置下,稳定衡量 next-token 预测质量有没有明显退化。
如果下一次看到:
Q4_K_M PPL = 6.88
Q5_K_M PPL = 6.72
Q8_0 PPL = 6.65
你应该先问:
它们是不是同一个语料、同一个 tokenizer、同一个 context length、同一个评测命令?
如果不是,这些数字不能直接比。
自检
不看上文,试着回答三题:
PPL=8是不是“每 8 个 token 错一个”?为什么?- 交叉熵和 PPL 的关系式是什么?
- 为什么 PPL 可以比较量化质量,但不能完全代表聊天体验?