跳转至

第 11 章:LLM 评估

1. 本章要解决的问题

第 10 章里,我们已经回答了一个很重要的问题:

为什么在大模型时代,大家会持续增加参数、数据和算力。

但紧接着,一个更现实的问题就会出现:

模型变大了,loss 下降了,我们到底该怎样证明它“真的更强了”?

这就是评估要解决的事情。

如果没有评估,很多讨论都会变得非常空泛:

  • 某个模型“感觉更聪明”,但不知道强在哪
  • 某次微调“看起来有效”,但不知道是不是偶然样本
  • 某个 prompt“似乎不错”,但换一批问题就失效
  • 某个版本上线后“用户好像没意见”,但其实已经发生能力回退

所以评估不是最后补的一张成绩单,而是整个 LLM 开发流程里的方向盘。

从全书结构上看,这一章有三个作用:

  • 它承接第 10 章,把“模型为什么继续 scale”推进到“scale 之后该怎么判断收益”
  • 它为第 12 章的 SFT 做准备,因为后训练目标本质上也是在优化某类评估结果
  • 它也为第 16 章的 RAG、以及第 17 章的 Agent 评估打基础,因为应用层评估会比通用模型评估更复杂

如果第 10 章回答的是:

为什么我们愿意花更大成本去训练更大的模型。

那么这一章要回答的就是:

一个 LLM 到底应该从哪些维度被衡量,不同评估方法各自可信到什么程度,以及我们怎样建立一套真正能服务开发迭代的评估体系。

2. 你学完后应该会什么

  • 能解释为什么 LLM 评估比普通分类任务更复杂
  • 能区分 benchmark、自动指标、人工评估分别适合解决什么问题
  • 能理解 perplexity、Exact Match、F1、BLEU、ROUGE、pass@k 这些指标各自的边界
  • 能说清 LLM-as-a-judge 为什么有用、为什么不能盲信
  • 能设计一个最小可用的 evaluation set 和回归测试集
  • 能把“评估”这件事自然衔接到后续的 SFT、RAG 和 Agent 开发

3. 为什么 LLM 评估比传统任务难得多

先看一个对比。

在二分类任务里,我们通常有:

  • 明确标签
  • 固定输出空间
  • 统一指标,比如 accuracy、precision、recall

例如垃圾邮件分类,模型输出要么是 spam,要么是 ham,对错边界很清楚。

但 LLM 不一样。

它的输出往往是开放生成的,自然语言答案可能有很多种等价表达。比如同一个问题:

问:为什么 Transformer 适合并行训练?

模型可能回答:

  • “因为 self-attention 不像 RNN 那样依赖严格的时间步递归。”
  • “因为序列位置可以同时计算,只需要用 mask 控制信息流。”
  • “因为它去掉了 RNN 的逐步传递结构,所以更容易做 GPU 并行。”

这三种答案本质上都对,但字符串并不相同。

所以 LLM 评估的第一个难点是:

正确答案往往不是唯一字符串。

第二个难点是,LLM 的能力不是单轴的。

我们评估的可能包括:

  • 知识问答
  • 推理
  • 数学
  • 代码
  • 指令遵循
  • 格式稳定性
  • 安全性
  • 多轮对话体验

这意味着“模型强不强”通常不是一个数字就能说清。

第三个难点是,评估目标和使用场景高度相关。

一个擅长写摘要的模型,不一定擅长代码生成; 一个 MMLU 很高的模型,也不一定适合客服机器人; 一个通用 benchmark 很强的模型,到了企业内部文档问答场景,可能依然表现很差。

所以这一章最重要的一个判断是:

评估从来不是找一个万能分数,而是要建立“任务目标 - 数据集 - 指标 - 人工检查”之间的对应关系。

4. 我们到底在评估什么

说“评估模型”,其实常常混着几种不同层次的问题。

4.1 评估模型的语言建模能力

这一层最贴近预训练本身。

它关心的是:

  • 模型对文本分布拟合得怎么样
  • 下一个 token 预测能力强不强
  • 在通用语料上的 loss 是否足够低

这类指标里最经典的是 perplexity

4.2 评估模型的任务完成能力

这一层更像“模型能不能把事情做对”。

例如:

  • 选择题能不能选对
  • 数学题最终答案对不对
  • 代码能不能通过测试
  • 摘要有没有覆盖关键信息

这里常见的是 benchmark 和任务指标,比如:

  • MMLU
  • GSM8K
  • HumanEval
  • Exact Match
  • F1
  • pass@k

4.3 评估模型的交互质量

这一层开始接近真实产品体验。

它关心的问题通常是:

  • 是否听懂指令
  • 是否按格式输出
  • 是否啰嗦
  • 是否稳定
  • 是否愿意承认不知道
  • 是否容易胡编

这时单纯靠自动指标往往不够,需要人工评估,或者引入 LLM-as-a-judge 做近似筛选。

4.4 评估模型在特定场景中的业务价值

这已经不是“通用 LLM 排名”问题,而是“你的系统有没有变好”。

例如在一个 RAG 系统里,我们可能真正关心的是:

  • 回答是否引用了正确文档
  • 是否答非所问
  • 是否把检索噪声带进答案
  • 用户问题的解决率有没有提高

也就是说,越往后走,评估对象就越不像“裸模型”,而更像“整条系统链路”。

5. benchmark:为什么大家都爱排行榜,但又不能只看排行榜

benchmark 可以理解为:

一组公开、相对标准化的测试任务,用来让不同模型在相似条件下可比较。

它的好处很明显。

5.1 benchmark 的价值

  • 它提供了一个公共语言,方便横向比较模型
  • 它能快速暴露模型在哪些任务上强、哪些任务上弱
  • 它对研究和工程都很有用,因为大家至少能先在同一套题上对齐

比如常见 benchmark:

  • MMLU:偏知识与学科理解
  • GSM8K:偏小学到中学水平的数学推理
  • HumanEval:偏代码生成
  • HellaSwag:偏常识与续写判断

5.2 benchmark 的局限

但 benchmark 也有明显边界。

第一,它可能和真实场景不一致。

你的业务是电商客服,结果天天盯着数学题分数;你的业务是企业知识库问答,结果只看通识考试题,这样就很容易发生“榜单很好,产品不好”的错位。

第二,它容易被“刷榜”。

当某个 benchmark 被反复公开使用后,模型开发者会逐渐围绕它优化,甚至训练数据里可能已经混入类似题目。这时高分不一定代表真正泛化。

第三,它通常覆盖不了稳定性和体验问题。

同一个模型也许平均分不错,但:

  • 温度稍微调高就开始飘
  • 指令格式经常不遵守
  • 多轮对话容易遗忘约束

这些问题常常不会直接反映在公开 benchmark 分数里。

所以 benchmark 最适合做的是:

作为通用参照,而不是作为唯一结论。

6. 自动指标:快,但要知道它到底在测什么

自动指标的优点是便宜、稳定、可批量运行,所以它是工程迭代的基础设施。

但自动指标的危险也在于:

如果你不知道它到底测的是什么,就很容易把“可计算”误当成“真正重要”。

6.1 perplexity:最贴近预训练,但不等于用户体验

perplexity 可以理解为模型对测试文本“不惊讶”的程度。

如果交叉熵是:

\[ H = - \frac{1}{T} \sum_{t=1}^{T} \log P(x_t \mid x_{<t}) \]

那么困惑度通常写作:

\[ \text{PPL} = \exp(H) \]

直觉上:

  • PPL 越低,说明模型越擅长给真实 token 更高概率
  • 它很适合衡量语言建模质量

但它不能直接回答:

  • 模型是否会遵循指令
  • 模型是否会拒答危险请求
  • 模型是否会输出结构化 JSON

所以在 LLM 时代,perplexity 很重要,但通常不够。

6.2 Exact Match 与 F1:适合答案相对短、边界较清楚的任务

在问答、抽取、阅读理解里,经常使用:

  • Exact Match:预测答案和标准答案是否完全一致
  • F1:预测文本和标准答案在 token 层面的重合程度

这类指标的优点是:

  • 简单
  • 易复现
  • 对短答案任务比较有效

但局限也明显:

如果标准答案是“北京”,模型输出“北京市”,有时语义上可以接受,但 EM 会直接判错。

6.3 BLEU 和 ROUGE:适合看重叠,不适合单独代表生成质量

BLEU 常见于机器翻译,偏 precision; ROUGE 常见于摘要,偏 recall。

它们衡量的是:

生成文本与参考答案之间的 n-gram 重叠程度。

这对批量比较模型版本很方便,但问题是:

  • 表达不同但意思相同,分数可能不高
  • 语言自然但没覆盖重点,人工觉得差,指标有时不一定低

所以今天大家一般不会只靠 BLEU / ROUGE 评价一个开放生成系统。

6.4 pass@k:代码生成里比“只看一发”更合理

代码任务有个很典型的问题:

模型第一次生成不一定对,但多采样几次也许能出可运行解。

这时常见指标是 pass@k,意思是:

采样 k 个候选,只要其中至少一个通过测试,就算成功。

它更接近很多代码助手的真实使用方式,因为用户也常常会重试、采样多个答案、再人工选择。

但要注意,pass@k 会受到采样温度、样本数和去重策略影响,不能脱离评测设置单独解读。

7. 人工评估与 LLM-as-a-judge:为什么最终还是要看“人”

当任务开始变得开放,例如写作、解释、对话体验、指令遵循时,自动指标通常不够细。

这时就会进入人工评估。

7.1 人工评估通常在看什么

常见维度包括:

  • 正确性
  • 完整性
  • 有用性
  • 清晰度
  • 安全性
  • 风格一致性

人工评估最大的优点是:

它最接近真实用户感受。

但代价也很明显:

  • 主观性强
  • 不同标注员之间可能不一致

所以工程里常见做法不是“全部人工”,而是“自动筛 + 人工复核”。

7.2 pairwise comparison 往往比绝对打分更稳

相比让评审员直接打 1-5 分,很多团队更喜欢做 A/B 对比:

同一个问题,回答 A 和回答 B,哪一个更好?

原因是绝对分数容易漂,评审员标准不一致; 而二选一比较更接近“这个版本到底有没有变好”。

这也是很多偏好数据集和对齐数据集的来源。

7.3 LLM-as-a-judge 为什么流行

因为人工评估太贵,所以大家开始尝试让一个更强的模型去判断另一个模型的输出。

它的优点是:

  • 成本低于大规模人工
  • 可快速批量跑
  • 对开放生成任务比纯字符串指标更灵活

例如可以让 judge 模型从以下维度打分:

  • 是否回答了问题
  • 是否出现事实性错误
  • 是否遵守了格式要求
  • 是否比 baseline 更简洁清楚

7.4 为什么不能盲信 LLM judge

LLM-as-a-judge 很有用,但它不是客观真理。

它可能有:

  • 对某种文风的偏好
  • 对长答案的偏好
  • 对自己家模型风格的偏好
  • 对提示词非常敏感

更现实一点说:

judge 也是模型,模型就会有偏差。

所以更稳妥的做法通常是:

  • 先用 LLM judge 大规模筛选
  • 再抽样做人审
  • 对关键任务保留人工 golden set

8. 一个真正能用的评估集,应该怎么设计

这一节最重要,因为它最接近你以后做项目和面试时真正会遇到的问题。

很多人说“我做了评估”,其实只是随手找了十几个例子试一下。

这不叫评估,这更像演示。

8.1 先从任务拆解开始,而不是先找指标

正确顺序通常是:

  1. 先定义系统要解决什么任务
  2. 再拆成若干关键能力
  3. 再为每类能力收集样本
  4. 最后才决定每类样本用什么指标

例如一个企业知识库助手,能力可以拆成:

  • 是否理解用户问题
  • 是否检索到正确材料
  • 是否基于材料回答
  • 是否在不知道时明确说不知道
  • 是否按要求输出引用

这时你的评估集就不应该只有“问答题”,而应该分桶。

8.2 evaluation set 最好分层

一个实用的评估集,通常至少包括三层:

  • smoke set:十几到几十条,快速冒烟,几分钟能跑完
  • dev set:用于日常迭代比较,规模中等
  • holdout set:不频繁查看,用来防止对 dev set 过拟合

如果只盯着一套固定样本不断调,很容易把系统调成“会做这套题”,而不是“真的更强”。

8.3 一定要保留 bad cases

评估集里最有价值的样本,往往不是最普通的,而是那些曾经失败过、而且业务上真的重要的 case。

比如:

  • 检索到了错误文档
  • 指令中包含多个约束,但模型漏掉一个
  • 数学中间过程对,最终答案错
  • JSON 输出少了一个字段

这些 bad case 一旦修好,就应该沉淀进回归测试集。

因为工程上最怕的不是“今天有 bug”,而是“明天它又回来了”。

8.4 评估集要覆盖分布,而不是只挑顺手样本

很多新手容易只收集“最像 demo”的题。

但真实评估更应该覆盖:

  • 高频场景
  • 高价值场景
  • 高风险场景
  • 容易失败的边界场景

也就是说,评估集不是给模型表演用的,而是给系统挑毛病用的。

9. 一个最小可用的评估流程

下面给一个足够小、但很实用的评估思路。

9.1 先准备统一格式的数据

例如用 jsonl 存:

{"id":"qa-001","question":"Transformer 为什么更容易并行化?","reference":"因为 self-attention 不需要像 RNN 一样按时间步串行计算。","tag":["reasoning","nlp"]}
{"id":"qa-002","question":"2 的 10 次方是多少?","reference":"1024","tag":["math"]}

这样做的好处是:

  • 不同任务容易批量处理
  • 可以按标签分桶统计
  • 后续容易追加人工标注字段

9.2 自动打分先解决“回归”问题

比如对短答案任务,可以先做一个非常朴素的脚本:

import json


def normalize(text: str) -> str:
    return "".join(text.lower().split())


def exact_match(pred: str, ref: str) -> int:
    return int(normalize(pred) == normalize(ref))


scores = []
with open("eval.jsonl", "r", encoding="utf-8") as f:
    for line in f:
        item = json.loads(line)
        pred = call_your_model(item["question"])
        scores.append(exact_match(pred, item["reference"]))

print(sum(scores) / len(scores))

这个脚本并不高级,但已经能解决一个关键工程问题:

每次改完 prompt、模型或数据后,你至少能知道某类题是不是退步了。

9.3 再补一层抽样人工检查

自动分数跑完后,再从以下几类样本里抽查:

  • 自动判错但你怀疑其实答对了
  • 自动判对但你怀疑质量不高
  • 新增功能相关样本
  • 高风险场景样本

这样才能避免“指标很好,但输出其实很怪”。

9.4 最后记录版本间差异,而不是只看单次分数

真正有用的评估,不是只记“这次多少分”,而是比较:

  • 相比上个版本,哪里提升了
  • 哪些 bad case 被修复了
  • 有没有新引入的退化

所以评估最好天然支持回归比较,而不是只做一次性展示。

10. 常见误区

10.1 误区一:benchmark 高分就等于产品表现好

这通常不成立。

benchmark 只能说明模型在某些公共任务上表现不错,但产品系统还受到检索、prompt、工具调用、输出格式、延迟和业务流程的共同影响。

10.2 误区二:只要有自动指标,就不需要人工看输出

这也不对。

自动指标擅长大规模、低成本比较,但很多真正影响用户体验的问题,最后还是得靠人发现。

10.3 误区三:评估就是项目最后再补一下

实际上,评估越晚做,返工通常越大。

更合理的方式是:从一开始就维护最小评估集,让每一轮改动都有反馈。

10.4 误区四:评估集越大越好

评估集当然不能太小,但也不是无脑越大越好。

如果样本很多却没有分桶、没有代表性、没有 bad case 沉淀,那只是“很多题”,不一定是“有效评估”。

11. 面试问题

Q1:为什么说 LLM 评估比传统分类任务更复杂?

因为 LLM 输出是开放生成的,正确答案常常不唯一,而且模型能力是多维的,单一指标很难覆盖知识、推理、格式遵循和交互体验。

Q2:perplexity 高低能不能直接代表模型是否适合做聊天助手?

不能。perplexity 更贴近语言建模质量,但聊天助手还涉及指令遵循、安全性、风格稳定性和多轮交互体验,这些都不是 PPL 能单独覆盖的。

Q3:为什么很多团队会同时使用自动评估、人工评估和 LLM judge?

因为三者各有边界。自动评估便宜稳定,适合回归;人工评估最接近真实体验,但成本高;LLM judge 能放大评估规模,但本身也有偏差,所以通常需要组合使用。

12. 本章小结

这一章最核心的结论不是“哪一个指标最重要”,而是:

评估必须服务目标。

对于预训练模型,你可能更关心 perplexity 和通用 benchmark; 对于后训练模型,你更关心指令遵循和回答质量; 对于应用系统,你更关心业务场景里的解决率、稳定性和回归风险。

所以一个成熟的 LLM 评估体系,通常不是单个分数,而是几层东西的组合:

  • 公共 benchmark 用来做通用参照
  • 自动指标用来做快速回归
  • 人工评估用来检查真实体验
  • 自定义 evaluation set 用来贴合自己的任务

到这里,我们就可以自然进入下一章了。

因为一旦你知道“什么叫一个更好的回答”,下一个问题就会变成:

我们怎样用带指令的数据,把预训练模型进一步塑造成一个更会回答人类问题的助手?

这就是第 12 章要讲的 Supervised Fine-Tuning