第 4 章:巧妙之处、边界与横向对比
这章收尾:先讲几个值得抄进自己评测系统的技巧,再诚实划出它不做什么、会在哪崩,最后和货架兄弟项目对比并给总代码地图。
4.1 巧妙之处(可借鉴的技术)
防泄题:XOR + SHA256 加密题库
妙在哪: 评测题库一旦明文进了网络,就会被未来的模型「背下来」(数据污染 / contamination),分数失真。BrowseComp 的题目和答案在 CSV 里是加密存放的,运行时才用每行自带的 canary 口令解密。
机制极简(browsecomp_eval.py:50-63):用 SHA256 把口令拉成定长密钥,与密文逐字节 XOR:
# 示意,非源码 —— 提炼自 derive_key / decrypt
def decrypt(ciphertext_b64, password):
encrypted = base64.b64decode(ciphertext_b64)
key = derive_key(password, len(encrypted)) # SHA256(password) 重复填到等长
return bytes(a ^ b for a, b in zip(encrypted, key)).decode()
解密在判分前一刻才做(browsecomp_eval.py:97-98)。这不是为了保密强度(XOR 很弱),而是为了让题库不以明文形式出现在公开仓库,降低被爬进训练集的概率。
GPQA:选项乱序消除位置偏差
妙在哪: 模型对选项位置有偏好(比如偏爱 C)。如果正确答案总在固定位置,分数会被这种偏差污染。GPQA 给每道题随机一个 permutation,把四个选项打乱后再问,正确答案落在哪个字母是随机的(gpqa_eval.py:33、45-47)。
配合 n_repeats(默认 10,simple_evals.py:366),同一题用不同乱序跑多遍,把位置偏差「平均掉」。
bootstrap 误差棒
妙在哪: 一个 92.9 的分数,换一批题会抖多少?库提供 bootstrap_std(common.py:175-178):对分数列表有放回重采样 1000 次、各算均值、取这些均值的标准差,作为分数的不确定度。HealthBench 默认就报它(healthbench_eval.py:231-236)。报分数时附误差棒,是诚实评测的基本功。
处处固定随机种子 → 可复现
妙在哪: 抽子集、打乱选项、采少样本,全部用固定种子:MMLU/MATH/GPQA/SimpleQA 用 random.Random(0)(如 mmlu_eval.py:93、gpqa_eval.py:28),DROP 用 random.Random(42)(drop_eval.py:238)。于是「随机抽 10 道题」对所有人都是同一批 10 道题——--examples 10 的结果可复现、可对比。
推理模型的 reasoning_effort 旋钮
妙在哪: 同一个模型在不同「思考深度」下分数差别很大,所以注册表把 o3_high / o3_low 等当不同模型注册(simple_evals.py:155-179),让 README 的表能逐档汇报(README.md:16-22)。评测把「推理预算」当成一个一等的实验变量。
4.2 边界与局限(诚实)
它刻意不做 / 做不了:
- 已停止维护。 README 顶部明示 2025 年 7 月起不再更新模型与跑分,只保留 HealthBench/BrowseComp/SimpleQA 作参考实现(
README.md:1-3);明确不收新 eval(README.md:66)。 - 不是综合评测平台。 自陈不打算替代
openai/evals(README.md:71)。 - 无缓存、无断点续跑。 主循环就是直接
eval_obj(sampler)跑完写/tmp(simple_evals.py:474-507);跑到一半挂了,得从头再来。大规模跑(尤其 HealthBench 要为每条 rubric 各调一次裁判)会很费 API。 - 结果只落
/tmp。 报告/JSON 全写/tmp/...(simple_evals.py:481-505),非持久化存储设计。
会在哪「崩」或失真:
- 判分依赖正则命中。 模型若不照「Answer: X」格式输出,正则抽不到答案,即便答案对也判错(
mmlu_eval.py:107-114抽不到时extracted_answer为None)。归一化(§2.1)缓解但不能根治。 - 裁判模型即天花板。 SimpleQA/BrowseComp/HealthBench 的分数上限由裁判模型的判断力决定;裁判偏了,分数就偏了——这正是 §3.7 meta-eval 要量化的风险。
- MGSM 把异常吞成空串。 采样抛异常时
response_text = ""(mmlu… 不,见mgsm_eval.py:169-170),该题直接判错——网络抖动会被记成模型答错。 - 依赖外网题库。 题库都从
openaipublic.blob.core.windows.net现拉(如mmlu_eval.py:87-90),离线跑不了。
4.3 横向对比(货架内)
本项目属于 ai-frontier-reference 的 evals-observability 区。它的定位与取舍:
| 维度 | simple-evals 的取舍 |
|---|---|
| 范围 | 窄而精:少数高信号基准 + 参考判分逻辑,不求大而全 |
| 提示风格 | 坚持零样本 + 思维链,反对少样本/角色扮演(README.md:60-64) |
| 抽象 | 极薄:只有 Sampler + Eval 两个接口,无插件系统、无配置 DSL |
| 判分 | 从正则到 LLM 裁判到加权 rubric,判分逻辑本身就是被开源的主角 |
| 工程化 | 故意不做缓存/续跑/持久化——它是「参考实现」,不是生产评测平台 |
一句话:它和重型评测框架(harness/平台)的根本区别是——它把「怎么判分」当成要讲清楚的内容本身,而不是藏在框架背后的实现细节。
4.4 总代码地图(全库导航)
| 主题 | 文件 | 符号 |
|---|---|---|
| 编排:模型注册表 + eval 工厂 + 主循环 | simple_evals.py | main、get_evals |
| 两个核心接口 | types.py | SamplerBase、Eval、SamplerResponse、EvalResult |
| 并发 / 聚合 / 报告 / 正则 / 归一化 | common.py | map_with_progress、aggregate_results、make_report、MULTILINGUAL_ANSWER_REGEXES、normalize_response |
| 模型适配器(4 个) | sampler/*.py | ChatCompletionSampler、OChatCompletionSampler、ResponsesSampler、ClaudeCompletionSampler |
| 选择题(正则判分) | mmlu_eval.py、gpqa_eval.py | MMLUEval、GPQAEval |
| 数学(等价裁判) | math_eval.py、common.py | MathEval、check_equality |
| 阅读理解(F1 + 匈牙利) | drop_eval.py | DropEval、_align_bags、_compute_f1 |
| 多语言数学 | mgsm_eval.py | MGSMEval、parse_answer |
| 代码(执行判分) | humaneval_eval.py | HumanEval、evaluate_functional_correctness |
| 事实性(LLM 裁判) | simpleqa_eval.py、browsecomp_eval.py | SimpleQAEval、BrowseCompEval、decrypt |
| 医疗(加权 rubric) | healthbench_eval.py、healthbench_meta_eval.py | HealthBenchEval、RubricItem、calculate_score、HealthBenchMetaEval |