Benchmark(基准测试 / 评测集构建)

1. 定义

Benchmark 是一套标准化、可复现的评测,用来在同一把尺子下衡量模型 / 算法 / 系统的某方面能力。它通常由「任务 + 数据 + 指标 + 评测流程」四件套组成。

一句话类比:Benchmark 像是考试——题目(数据)、考纲(任务)、评分标准(指标)、监考流程(协议)都固定下来,才能让不同考生(模型)的成绩可比。没有统一考试,说”我模型很强”就只是自卖自夸。

关联:Benchmark 衡量的是模型在 ../ai-agent-guide/foundation_model.md 预训练 / 对齐之后”到底行不行”;它常作为 Scaling Law(见 scaling-law.md)之外更贴近真实能力的补充证据。

2. 一套 Benchmark 解决什么问题 / 能做什么

作用说明
能力度量把”模型好不好”从主观感受变成可量化分数
横向对比同一评测下公平比较不同模型 / 版本 / 方案
进度追踪随训练 / 迭代看分数变化,判断改进是否有效
暴露短板细分维度下定位模型弱项(如推理、长文本、安全)
引导研究公开榜单会牵引社区往某方向卷(也带来过拟合榜单风险)

3. 做一套 Benchmark 的典型步骤

下面是一套从 0 到 1 的通用流程,难度从「搭个内部小评测」到「发一篇 benchmark 论文」都适用。

1. 定目标与范围 (Scope)
   │  明确测什么能力、给谁用、要公平还是要难
   ▼
2. 设计任务与维度 (Task Design)
   │  拆成可测的子能力,定题型(选择/生成/工具调用…)
   ▼
3. 采集与构造数据 (Data)
   │  来源 + 清洗 + 标注,控制分布与质量
   ▼
4. 定义指标与评分 (Metric)
   │  怎么算对、怎么打分(精确匹配 / LLM 判分 / 人工)
   ▼
5. 制定评测协议 (Protocol)
   │  输入格式、prompt 模板、few-shot、采样参数
   ▼
6. 实现与试运行 (Pipeline)
   │  搭自动化脚本,跑 baseline,查 bug 与漏标
   ▼
7. 校验与去污染 (Validate)
   │  看信度/难度/区分度,剔除训练集泄露
   ▼
8. 发布与维护 (Release)
      写文档、建榜单、持续更新防过拟合

3.1 步骤拆解

  1. 定目标与范围:先回答”测谁、测哪方面、给什么决策用”。例:测代码模型在真实仓库里的 bug 修复能力,供内部选型用。范围决定后面的所有设计。
  2. 设计任务与维度:把大能力拆成可测子集。例:推理 = 数学 + 逻辑 + 常识;安全 = 越狱 + 偏见 + 隐私。题型选好(单选最易自动化,开放生成最贴近真实但难评)。
  3. 采集与构造数据:来源有公开数据集、人工编写、模型生成 + 人工校验、真实业务日志。要控制领域分布、难度分布、语言分布;标注需规范(给出标准答案 / 评分要点)。
  4. 定义指标与评分:分类常用准确率 / F1;生成常用 BLEU/ROUGE(偏表面)或更靠谱的 LLM-as-judge、人工评。要规定”什么算对”,避免歧义。
  5. 制定评测协议 (protocol):固定一切会引入随机性的环节——prompt 模板、是否给 few-shot、采样温度、最大长度、超时。协议不一致会让分数不可比。
  6. 实现与试运行 (pipeline):写批处理脚本,统一输入输出格式,先跑一两个 baseline 验证链路通不通。常见坑:解析失败、截断、API 限流、字段错位。
  7. 校验与去污染 (decontamination):用统计手段检查——题目太难(全员 0 分)或太易(全员满分)都无意义;还要检查测试题是否出现在训练集里(数据污染会让分数虚高)。可算 Human/Inter-rater 一致性看评分可靠。
  8. 发布与维护:写清使用说明、许可协议、榜单提交方式。热门 benchmark 会被”刷榜”过拟合,需定期更新题目或分私/公开集。

4. 关键概念辨析

概念是什么易混点
Benchmark完整评测套件(任务+数据+指标+协议)≠ 单个数据集
Dataset数据集合,可能只是 benchmark 的一部分数据集未必带评分标准
Metric怎么打分选错指标会误导结论(如 BLEU 不等于质量)
Protocol评测执行规范不固定协议 → 结果不可复现
Baseline对照用的起点模型/方法没有 baseline 无法判断提升是否显著
Leaderboard公开排名榜刷榜会让 benchmark 失效

5. 常用指标速查(代码块示例)

# 选择/分类任务
Accuracy = 答对数 / 总数
F1       = 2·P·R / (P+R)        # 类别不均衡时比准确率更稳
 
# 生成任务(表面相似度,谨慎使用)
BLEU / ROUGE                      # 偏向 n-gram 重叠,不代表语义对
 
# 更可靠的生成评分
LLM-as-judge                      # 用强模型按 rubric 打分(需防位置/偏好偏置)
Human evaluation                  # 金标准,但贵且慢
 
# 协议相关的可复现项
temperature = 0                   # 评测常用 0 减小随机性
max_tokens / timeout              # 防止截断或挂死

6. 典型工作流(内部搭一个评测集)

1. 明确要支持的决策:选模型?盯迭代?查安全?
2. 列出要测的 3~5 个能力维度
3. 先攒 50~100 条种子题,人工把答案/评分标准定死
4. 写评测脚本跑通一个 baseline,确认分数能算出来
5. 扩到几百~上千条,做质量与分布检查
6. 去污染:grep/embedding 查与训练集重叠
7. 固定 prompt 与采样参数,写成 protocol 文档
8. 接入 CI,每次发版自动跑,分数回退就报警

7. 常见误区

  • ❌ “题越多越好” → 关键是质量与覆盖,100 条精心设计远胜 1 万条噪声;还要看难度/区分度。
  • ❌ “BLEU/ROUGE 高 = 模型好” → 这些只衡量 n-gram 表面重叠,常和真实质量弱相关;开放生成更该用 LLM-judge 或人工。
  • ❌ “评一次就完事” → 不固定 protocol(温度、prompt、few-shot)会导致不同次结果不可比,迭代时尤其致命。
  • ❌ “榜单第一就是真强” → 公开榜会被针对性过拟合(数据污染 + 调参刷分),要分公开/私有集或定期换题。
  • ❌ “测试集可以拿去训练” → 一旦泄露进训练,分数虚高且失去区分意义,必须做 decontamination。
  • ❌ “指标越高越有意义” → 全员满分或全员零分的题不区分模型,要检查难度分布。

8. 实操:HumanEval 风格代码评测集怎么写题与自动判分

代码评测集(典型如 HumanEval、MBPP)的核心思路是:给函数签名 + 文档,让模型补全实现,再用预埋的单元测试在隔离环境里跑,全过即”通过”。它天然适配自动判分,是 LLM 代码能力最主流的衡量方式。

8.1 一道题的数据结构

每道题本质是一个 JSON 条目,包含四要素:

{
  "task_id": "HumanEval/0",
  "prompt": "def has_close_elements(numbers, threshold):\n    \"\"\"Check if any two numbers are closer than threshold.\n    >>> has_close_elements([1.0,2.0,3.0], 0.5)\n    False\n    \"\"\"\n",
  "canonical_solution": "    for i in range(len(numbers)):\n        for j in range(i+1, len(numbers)):\n            if abs(numbers[i]-numbers[j]) < threshold:\n                return True\n    return False",
  "test": "def check(candidate):\n    assert candidate([1.0,2.0,3.0], 0.5) == False\n    assert candidate([1.0,2.8,3.0], 0.3) == True\n    assert candidate([], 0.1) == False",
  "entry_point": "has_close_elements"
}
字段作用
prompt喂给模型的「上半截」:签名 + docstring(有时含 doctest 示例)
canonical_solution标准答案,用于校验评测链路、做难度/去污染分析
test预埋单元测试,含边界 / 异常 / 典型多组断言
entry_point被测函数名,自动判分时定位调用对象

8.2 写题的实操要点

  1. 只给签名不给实现:prompt 必须停在函数定义开头,逼模型自己写逻辑;docstring 要写清语义,避免歧义导致”对但被判错”。
  2. 测试用例要够硬:至少覆盖正常输入、边界(空列表、长度为 1、极值)、典型错误分支;单条 assert 太弱,容易被”蒙对”。
  3. 一题一能力:每题聚焦一个清晰子能力(解析、状态管理、边界判断),方便后续按维度统计短板。
  4. 难度梯度:混搭简单题和需多步推理的题,避免全员满分/零分。
  5. 去污染必须做:用 n-gram / embedding 比对,确认题目(尤其 prompt + test)没出现在模型训练语料里——HumanEval 因公开在网上,部分模型已”见过”,分数会虚高。

8.3 自动判分流程

对每道题 problem:
  code    = model.generate(problem.prompt)   # 补全函数体
  program = problem.prompt + code + "\n" + problem.test   # 拼成可运行脚本
  result  = sandbox.run(program, timeout=5s) # 隔离执行(禁网络/限资源)
  passed  = (result.exit_code == 0)          # 所有 assert 通过才算过
聚合 → 计算 pass@k

关键:模型生成的函数和标准测试在同一个脚本里执行,靠 Python 的 assert 自然判定对ded;不需要额外的”判分模型”,所以结果确定、可复现。

8.4 pass@k 是什么

模型每次采样不只有一个答案,常采 n 个(如 n=200)。定义 pass@k:从 n 次采样里随机取 k 个,至少有一个全过测试的概率:

pass@k = 1 - C(n - c, k) / C(n, k)       # c = n 次中通过的次数
  • pass@1:采 1 次就对的率,最贴近”一次生成直接用”的真实体验。
  • pass@10 / pass@100:允许多次采样挑一个,衡量”模型是否『能』做对”,常用于展示上限。

8.5 沙箱隔离(安全红线)

模型生成的代码绝不能在宿主机直接跑,否则一句 os.system("rm -rf /") 就完蛋。判分环境必须隔离:

Docker / gVisor / seccomp 容器
  ├─ 禁网络 (--network none)
  ├─ 限资源 (CPU / 内存 / 5s 超时,超时即判失败)
  ├─ 只读文件系统,禁危险 syscall
  └─ 每个问题独立容器,跑完即销毁

开源 harness(如 lm-evaluation-harness、OpenCompass)已内置沙箱,直接复用比自己手写更稳。

8.6 代码评测常见额外坑

  • ❌ “能跑通 = 写得好” → 自动判分只验功能正确性,不测时间/空间复杂度、可读性、安全性;硬核评测会额外卡超时与内存上限。
  • ❌ “测试覆盖一行就行” → 单分支测试会让”碰巧对”蒙混过关,务必多分支 + 边界。
  • ❌ “公开榜分数高就真强” → 训练数据若含 HumanEval 原题,分数失真;严肃对比要用去污染后的私有/改写集。

9. 延伸阅读 / 关联概念

  • 基座模型与对齐 — Benchmark 是检验预训练/对齐效果的尺子;见 ../ai-agent-guide/foundation_model.md
  • Scaling Law — 损失下降之外,benchmark 提供能力维度的补充证据;见 scaling-law.md
  • 强化学习 (RLHF) — 对齐阶段常用 benchmark 衡量偏好/安全;见 reinforcement-learning.md
  • 经典范例:MMLU(多学科知识)、HumanEval / MBPP(代码)、GSM8K(数学推理)、HELM / BIG-Bench(综合评测)、MTEB(嵌入模型)
  • 工具:EleutherAI eval harness (lm-evaluation-harness)、HELM、OpenCompass、ragas(RAG 评测)