Skip to content

LLM Benchmark 实操与污染防控

2026 年的 LLM 榜单已经进入"污染常态化"阶段:MMLU、GSM8K 之类的老榜接近天花板,新榜单刚发布就被各家训练数据爬走。但企业不能不看 Benchmark——选型、采购、合规审计都要。这一篇讲清楚:哪些榜还能信、怎么读榜不被骗、怎么自建私有 Benchmark、怎么用 n-gram 重合 + canary token 检测污染,并给出 lm-evaluation-harness 与 OpenCompass 的完整跑分脚本。

教学导读

**定位:**这一章解决"看了榜单选了模型,结果业务上效果差一截"的真实痛点。它和第 43 篇《LLM 评测科学与 Judge 校准》互补——评测科学讲"你自己的评测怎么不出错",本篇讲"别人公开的评测怎么甄别和复现"。 **前置依赖:**建议已读 第 43 篇 LLM 评测科学 中的 Bootstrap 置信区间与 Slice 评测,以及 第 46 篇 偏见与公平性测试 的反事实方法。 **适用场景:**模型选型、技术尽调、合规备案、采购评估、对外宣传素材审核。 **学完产出:**你能独立用 lm-evaluation-harness 跑 MMLU-Pro,用 OpenCompass 跑 C-Eval,用 n-gram 重合度脚本检测候选模型对你 holdout 数据集的污染程度,并能在 24 小时内为团队搭出一套"动态 + 时效 + 业务场景"三合一的私有 Benchmark。

第1章:为什么 2026 年单一榜单不可信

1.1 老榜单已经"打满"

2020-2023 年是 Benchmark 的黄金期,那时 MMLU、GSM8K、HumanEval 三大榜还有明显区分度。但到 2026 年初,主流前沿模型在这些老榜的成绩已经接近天花板,区分度全部丢失:

老榜单2023 SOTA2026 SOTA满分区分度
MMLU(5-shot)86.4 (GPT-4)92.8 (GPT-5)100已饱和,前 8 名差 < 1.5 分
GSM8K92.0 (GPT-4)98.7 (Claude Opus 4.6)100已饱和
HumanEval67.0 (GPT-4)96.3 (GPT-5-Codex)100已饱和
HellaSwag95.3 (GPT-4)97.9 (Gemini 3 Pro)100近年没人单独看
ARC-Challenge96.3 (GPT-4)97.5 (DeepSeek-V3.5)100已饱和

当一个榜单的前 10 名差距小于这个榜单本身的统计噪声(一般是 1-2 分),它就已经丧失了"模型选型决策依据"的价值。

1.2 新榜单几个月内就被爬

2024 年底业内出现了一种新现象:每发布一个新 Benchmark,3-6 个月后多家头部模型的成绩就会"奇迹般"跃升。社区追溯发现,主要原因是 Benchmark 题目被各家训练时的爬虫扫到、被加入数据混合或者被合成数据生成器"参考"。 典型案例:

  • GPQA-Diamond(2024-04 发布,198 题):发布时 GPT-4 只有 39.4%,6 个月后某模型直接报 65%。后被独立审计发现该模型在训练集中包含了部分 GPQA 解析。
  • SWE-Bench Verified(2024-08 发布,500 题):3 个月内出现"分数翻倍"模型,被怀疑训练时拉了原始 commit。
  • LiveCodeBench v1(2024-03 发布):因为题目固定,6 个月后被刷到 80+。所以 v3(2025-02)开始改成"按时间窗口动态切片"。

对测试团队的现实意义。

2026 年你看到一个模型的"惊艳跑分",第一反应应该是怀疑而不是兴奋。需要立刻问三个问题:(1) 这个 Benchmark 多久前发布的?(2) 模型训练截止时间在前还是在后?(3) 同一家自己的模型,在新发布的"未公开榜"上表现如何?

1.3 单一榜单的三个失真维度

即使没有污染,单一榜单也会因为下面三个维度天然失真:

  1. 题型失真:MMLU 是多选题,HumanEval 是函数补全,但你的业务可能是开放对话、Agent 调度、长文档总结——这些场景的能力在老榜里完全测不出来。
  2. 语言失真:英文榜单上的强者不一定是中文的强者。Claude Opus 4.6 在中文上比英文低 4-6 个百分点,DeepSeek-V3.5 反过来。只看英文榜会做出错误中文采购决策。
  3. 分布失真:MMLU 学科覆盖偏理工和美国通识,问到中国法律、医学、地方文化时模型表现可能完全不同。

这意味着,"读榜"在 2026 年是一个需要训练的专业技能——而不是看分数排行就能得出结论。

第2章:Benchmark 的三个时代

把 Benchmark 的演进划成三个时代,能帮你快速判断手上看到的榜单"在哪个进化阶段"。

时代 1 · 学术评测榜(2018-2022) → 时代 2 · 商业刷分榜(2023-2024) → 时代 3 · 污染防御榜(2025-2026)

2.1 时代一 · 学术评测榜(2018-2022)

代表:GLUE、SuperGLUE、SQuAD、HellaSwag、MMLU。这一代榜的设计目的是"学术研究比较",假设大家"诚实评测"。所以题目都公开、答案都公开、评测脚本都公开。这种"完全开放"在学术语境下没问题——因为研究者关心的是"模型架构哪个好",不是"哪家公司模型更好"。 这一代榜单的共同特征:题目数量大(5w+)、题型固定、评测自动化、可重复。

2.2 时代二 · 商业刷分榜(2023-2024)

代表:HumanEval、GSM8K(之前是学术,但变成了商业 KPI)、MMLU-Redux、MT-Bench。从 2023 年开始,"模型分数"变成了厂商的核心营销数据。SOTA 之争从论文页扩展到发布会。结果是:

  • 测试集被反复曝光,污染加速
  • 厂商开始针对榜单做"对抗优化",不是真正提升能力
  • 新榜单发布即过时,因为训练数据更新太快

这个时代催生了 "Goodhart's Law in LLM":当一个指标变成目标,它就不再是好指标。

2.3 时代三 · 污染防御榜(2025-2026)

代表:LiveCodeBench(按月切片)、Arena-Hard(用户实时投票)、SWE-Bench Verified(每月加入新 Issue)、SimpleBench(有保密 holdout 集)、OpenCompass-2.0 Live。 这一代的核心设计原则有三个:

设计原则实现方式典型代表
动态题目每月 / 每季度抽换部分题目;保留 holdout 私有集LiveCodeBench, Arena-Hard, SWE-Bench Verified
时效约束题目截取的时间晚于模型 cutoff,物理隔离LiveCodeBench, FrontierMath(每年新增)
实时人类反馈真实用户配对盲投,规避评测题污染Arena-Hard / Chatbot Arena

测试启发。

判断一个榜单值不值得当真,最简单的方法是看它"防污染设计":(1) 公开题目占比;(2) 是否有 holdout 私有集;(3) 是否有时效切片机制;(4) 是否有第三方独立审计。如果四项全无,这个榜分数高低基本只是营销噪声,不能作为采购依据。

第3章:主流英文 Benchmark 全景

2026 年仍有参考价值的英文 Benchmark 一共 8 个,按"能力维度"和"防污染等级"分类如下。

3.1 通用知识与推理类

(1) MMLU-Pro

地址: github.com/TIGER-AI-Lab/MMLU-Pro 2024-05 发布,是 MMLU 的"加难版"。把 MMLU 的 4 选 1 改成 10 选 1,增加 reasoning-heavy 题目,从 14 类学科扩展到 14 个广度学科 + 12000 题。题目大量从 STEM 教材、公务员考试、专业资格考试中抽取。 满分:100 2026 SOTA 分布

模型MMLU-Pro 分数测评配置
GPT-587.20-shot, CoT
Claude Opus 4.686.50-shot, CoT
Gemini 3 Pro85.90-shot, CoT
o3-pro87.80-shot, reasoning high
o488.40-shot, reasoning high
DeepSeek-V3.583.60-shot, CoT
DeepSeek-R285.10-shot, reasoning
Qwen3-Max84.70-shot, CoT
Claude Sonnet 4.683.90-shot, CoT
Gemini 3 Flash81.60-shot, CoT

MMLU-Pro 在 2026 年仍是"通用知识"的事实标准榜,但已经接近 90 分饱和——再过 18 个月就会失去区分度。

(2) GPQA-Diamond

2024-04 发布,198 道由博士级别专家撰写的研究生级选择题,覆盖物理、化学、生物。Diamond 是其中"非专业人士即使用 30 分钟搜索也答不对"的最难子集。 满分:100 2026 SOTA

  • o4: 64.8
  • o3-pro: 62.3
  • GPT-5: 60.7
  • Claude Opus 4.6: 61.2
  • Gemini 3 Pro: 58.9
  • DeepSeek-R2: 57.4

GPQA 的特点是"上限低、防污染好"。198 题这个体量足够小、足够专业,被无意污染的概率很低,所以仍是"高难度推理"的可信指标。

(3) SimpleBench

2024-09 发布,是反思性常识题集合(10 道公开 + 200 道保密)。它专门攻击"模型在简单题上反而崩溃"的现象,例如"冰块掉进冰水里,水温会升高还是降低"这种类型。 SimpleBench 的特殊之处在于它有一个保密 holdout 集,所有跑分必须由作者团队代理执行——这是 2026 年防污染设计的标杆。

3.2 代码类

(4) SWE-Bench Verified

地址: swebench.com 2024-08 发布,由 OpenAI 联合普林斯顿做的"verified 子集"——从原始 SWE-Bench 的 2294 题中筛选 500 道质量更高、可自动验证、有清晰评测信号的题目。 每道题是一个真实的 GitHub Issue,模型需要:(a) 读懂 issue 描述;(b) 在多文件代码库中定位修改点;(c) 输出 patch;(d) patch 必须通过该项目原本的测试用例。 2026 SOTA

模型SWE-Bench Verified(pass@1)备注
GPT-5-Codex71.4%专门为代码优化
Claude Opus 4.669.8%含 Computer Use 工具调度
Qwen3-Coder67.2%开源模型最高
GPT-566.5%通用版本
DeepSeek-R265.1%推理模型
Gemini 3 Pro63.8%需启用 code execution
Claude Sonnet 4.662.4%性价比之选

SWE-Bench Verified 是 2026 年代码能力的事实标准榜,因为它有三大优势:题目真实、自动可验证、不易污染(每月轮换部分题目并增补 Issue)。但它有强工具/Agent 依赖,跑分不能简单用 single-shot prompt——必须实现完整的"读代码-定位-改代码-跑测试"循环。

(5) LiveCodeBench

2024-03 发布,最初是固定题库 LeetCode 集合。但作者很快意识到污染严重,2025-02 改成 LiveCodeBench v3——每月从 LeetCode、AtCoder、Codeforces 抽取最新题目,自动剔除模型 cutoff 之前的题。 2026 年通用的引用方式是 LiveCodeBench v3 (2025-09 至 2026-02 切片),必须标注切片日期,否则跑分没有意义。

3.3 对话与指令类

(6) Arena-Hard

从 LMSys Chatbot Arena 中筛选的 500 道高质量对抗题。所有题目由 Arena 真实用户提交,且有"模型间 ELO 分差较大"的判别力。 2026 各模型 Arena-Hard 胜率(vs GPT-5 baseline 50%):

模型胜率95% CI
Claude Opus 4.654.2%(52.0, 56.4)
GPT-550.0%baseline
Gemini 3 Pro49.6%(47.4, 51.8)
o3-pro52.7%(50.5, 54.9)
DeepSeek-V3.546.3%(44.1, 48.5)
Qwen3-Max44.8%(42.6, 47.0)
Claude Sonnet 4.644.1%(41.9, 46.3)
Gemini 3 Flash40.5%(38.3, 42.7)

Arena-Hard 的优点是用真实用户偏好作为信号,不易被刷分;缺点是依赖 GPT-4 或更强模型做 Judge,存在 Judge 偏向风格问题。

(7) IFEval(Instruction Following Eval)

2023-11 发布,专门测"严格指令遵循"。每道题给一个明确可验证的指令约束,例如:"用 100-150 词回答""每段必须以编号开头""答案必须是 JSON 格式"。 IFEval 的特殊价值在于它是规则验证而不是 Judge 判断——所以分数没有 Judge 噪声,复现性极好。2026 SOTA:

  • GPT-5: 91.3
  • Claude Opus 4.6: 92.7
  • Gemini 3 Pro: 89.8
  • DeepSeek-V3.5: 87.6

3.4 数学类

(8) Math-500 与 AIME

Math-500 是 OpenAI 从 MATH 数据集中精选的 500 道题,覆盖代数、几何、数论、概率。AIME(美国数学邀请赛)则是 30 道国家级竞赛题,2026 年常用 AIME 2024 + AIME 2025 两届合计 60 道。 SOTA 数据:

模型Math-500AIME 2024+2025
o497.883.3 (50/60)
o3-pro96.578.3
GPT-595.271.7
Claude Opus 4.694.668.3
DeepSeek-R295.876.7
Gemini 3 Pro93.466.7

第4章:主流中文 Benchmark 全景

2026 年中文 Benchmark 也已形成稳定格局,下面 6 个是最常用的。

4.1 C-Eval

地址: cevalbenchmark.com 2023 年由清华、上海交大联合发布。13948 道多选题,覆盖人文、社科、理工、其他四大类共 52 个学科。test 集 12342 题,dev 集 1346 题。 C-Eval 的设计初衷是"中国知识体系下的 MMLU"。它专门覆盖 MMLU 不会涉及的中国法律、中国历史、中医、公务员行测等领域。 2026 SOTA:

  • Qwen3-Max: 89.4
  • DeepSeek-V3.5: 88.7
  • GPT-5: 86.2
  • Claude Opus 4.6: 84.5
  • Gemini 3 Pro: 84.8

注意国产模型在 C-Eval 上明显领先海外模型——这是真实的"中文知识训练量"差异。

4.2 CMMLU

2023 年由 MBZUAI 发布,11528 题覆盖 67 个学科。和 C-Eval 类似但更侧重通用知识,少量交叉。 建议把 C-Eval 和 CMMLU 配对使用——前者偏教育/专业资格,后者偏通用 + 文化常识。

4.3 SuperCLUE

由 SuperCLUE 团队维护的中文综合榜,是 2026 年中文 LLM 公开榜的"事实主流"。覆盖:

  • SuperCLUE-V(多模态)
  • SuperCLUE-Math6(小学到大学数学)
  • SuperCLUE-Agent(中文 Agent 能力)
  • SuperCLUE-RAG(中文检索增强)
  • SuperCLUE-Safety(中文安全合规)

SuperCLUE 每月更新榜单,并保留 30% 私有 holdout 题目防污染。

4.4 OpenCompass-2.0

由上海人工智能实验室维护,是中国境内最完善的评测平台而不仅仅是榜单。它聚合了 70+ 个测试集(中英文混合),支持自动化跑分、可视化报告、组合榜单。后面第 10 章会做完整实操。

4.5 GAOKAO-Bench

把高考真题(2010-2025 年)做成 LLM 可评测的格式。覆盖语数外、文综理综所有学科。GAOKAO-Bench 的价值在于"真实题目 + 难度梯度清晰 + 公众熟知"——很适合做对外宣传素材。

4.6 SuperCLUE-Math6

覆盖从小学一年级到高考、大学的数学题。等级化设计很适合"按场景选模型"——客服 Bot 的算账场景只需要小学数学过 95%,金融分析场景需要高中数学过 90%。

中文榜单规模侧重2026 推荐度
C-Eval1.4w 题中国教育/资格考试★★★★★
CMMLU1.2w 题通用知识 + 文化★★★★☆
SuperCLUE动态多榜综合 + 月度更新★★★★★
OpenCompass-2.070+ 子集平台型,自动化★★★★★
GAOKAO-Bench真题集对外可解释★★★★☆
SuperCLUE-Math6分级数学按业务难度选型★★★★☆

第5章:数据污染的发生机制与检测原理

5.1 污染的四种发生路径

"数据污染"听起来像一个事件,但实际上有四种完全不同的发生路径,每种检测和缓解方法都不同。

路径 A · 直接污染(Direct Contamination)

训练数据爬虫直接抓取了 Benchmark 题目原文。最常见,最容易检测。 **检测:**n-gram 重合度,或者直接精确匹配子串。

路径 B · 翻译污染(Translation Leak)

英文 Benchmark 被翻译成中文混入训练,或反之。题目本质相同但 n-gram 完全不同。 **检测:**语义相似度(embedding cosine)+ 双语回译比对。

路径 C · 解析污染(Solution Leak)

题目本身没泄露,但解题思路被详细解析(如 Stack Overflow / 课件)混入训练。 **检测:**给模型只给"问题骨架"看是否能给出和参考答案一致的步骤。

路径 D · 间接污染(Indirect Leak)

题目被合成数据生成器借鉴。模型没看到原题,但学到了"这种题怎么解"的模式。 **检测:**对原题做 minor perturbation 看分数是否大幅下跌。

5.2 n-gram 重合度算法

n-gram 重合是最经典的污染检测方法。原理:把 Benchmark 题目和模型训练数据都切成 n-gram(n 通常取 8-13),看重合比例。

python
def ngram_overlap(text_a: str, text_b: str, n: int = 13) -> float:
    """
    计算 text_b(训练样本)对 text_a(评测题目)的 n-gram 覆盖率。
    返回 0-1 之间的浮点数。
    """
    def ngrams(text, n):
        tokens = text.split()
        return set(tuple(tokens[i:i+n]) for i in range(len(tokens) - n + 1))

    a_grams = ngrams(text_a, n)
    b_grams = ngrams(text_b, n)
    if not a_grams:
        return 0.0
    overlap = a_grams & b_grams
    return len(overlap) / len(a_grams)

eval_question = "What is the capital of the country whose flag has a red maple leaf?"
training_sample = "...a question asked: What is the capital of the country whose flag has a red maple leaf? Answer: Ottawa..."
print(ngram_overlap(eval_question, training_sample, n=8))
# 0.85  ← 高度可疑

实际工程中,业界普遍采用 OpenAI 在 GPT-4 技术报告中公布的方法:n=13 时如果训练样本对评测题目的 13-gram 重合度 ≥ 70%,则视为污染样本。 但单纯 n-gram 检测漏洞也很大:(1) 题目改个词就绕过;(2) 翻译后完全不一致;(3) 数学题数字一改 n-gram 就变。所以企业级污染检测一般用三层组合:精确子串匹配 + n-gram 重合 + embedding 语义相似度。

5.3 Canary Token 技术

Canary(金丝雀)是英文 Benchmark 创始人在题目里"故意嵌入的标记字符串"——一段毫无语义、不影响题目可读性的随机 ID。例如:

json
{
  "id": "BBQ-2024-0001",
  "canary": "BENCHMARK_DATA_SHOULD_NOT_BE_INCLUDED_8d4ae3f9c1b2",
  "question": "...",
  "choices": [...]
}

这个 canary 字符串:(1) 在自然文本中不会出现;(2) 出现频率极低;(3) 唯一对应这个 Benchmark。如果模型的训练数据中包含了 canary,意味着这个 Benchmark 被爬过。 检测方法:

  1. 提示注入测试:让模型续写 canary 后续内容,看是否能生成原题。
  2. perplexity 探针:测量模型对 canary 字符串的 perplexity。如果 perplexity 异常低,说明模型见过它。
  3. 反向 prompt 测试:直接问模型"你训练数据里有没有出现过 'BENCHMARK_DATA_SHOULD_NOT_BE_INCLUDED' 这个字符串?"——前沿模型会承认看过(如果真的见过)。

5.4 训练时间戳交叉

第三种检测原理是"时间戳交叉"。如果模型 cutoff 是 2025-04,那么 2025-04 之后才发布的 Benchmark 题目,模型理论上不可能见过。如果它在这种"未来题"上仍然得高分,要么模型有训练污染(cutoff 公布不准确),要么真的能力强。 这是 LiveCodeBench / FrontierMath / Arena-Hard 这类"持续更新榜"的核心防御机制。它的本质是用时间作为天然 holdout

测试启发。

污染检测的三大技术——n-gram 重合、canary token、时间戳交叉——都不是单点能解决问题的银弹。在企业内部跑模型选型时,建议三者全做:n-gram 解决直接污染,canary 探测厂商爬虫渠道,时间戳过滤数据时效。三者交叉才能给出可信结论。

第6章:模型选型时如何用 Benchmark

6.1 选型阶段的三步走

第一步 · 公开榜筛掉明显不行的 → 第二步 · 私有榜 / 业务榜复测 → 第三步 · 灰度业务上跑 A/B

公开榜的角色是"快速排除"——比如某模型 MMLU-Pro 只有 60,那它绝对不适合通用助手。但公开榜不能用来做最终决策,因为:(1) 污染嫌疑;(2) 题型和你的业务不匹配;(3) 排名前 5 的差距通常在统计噪声范围内。

6.2 公开榜的"权重组合"读法

不要看单一榜单分数,而要按你的业务需求做权重组合。下面是三个典型场景的权重模板:

业务场景关键榜单建议权重
通用客服 / 助手MMLU-Pro 30% + Arena-Hard 30% + IFEval 20% + 中文 C-Eval 20%等权偏对话
编程 / IDE 集成SWE-Bench Verified 50% + LiveCodeBench 30% + IFEval 10% + MMLU-Pro 10%重 SWE
金融 / 法律分析MMLU-Pro 25% + GPQA-Diamond 20% + Math-500 20% + IFEval 15% + C-Eval(财经法律子集) 20%重推理 + 知识
RAG 文档问答IFEval 30% + Long-context (RULER) 30% + MMLU-Pro 20% + C-Eval 20%重指令 + 长文
Agent 工具调度SWE-Bench Verified 30% + tau-bench 30% + IFEval 20% + Arena-Hard 20%重 Agent / Tool Use

6.3 多模型横评矩阵

下面是 2026 年 Q1 的一份完整横评(综合官方报告 + 第三方独立复跑数据):

模型MMLU-ProGPQA-DSWE-Bench VArena-HardIFEvalC-EvalMath-500
GPT-587.260.766.550.091.386.295.2
GPT-5-Codex83.457.271.446.889.582.791.5
o3-pro87.862.364.252.787.483.596.5
o488.464.867.953.588.684.197.8
Claude Opus 4.686.561.269.854.292.784.594.6
Claude Sonnet 4.683.956.362.444.191.282.192.7
Gemini 3 Pro85.958.963.849.689.884.893.4
Gemini 3 Flash81.652.457.340.587.180.589.7
DeepSeek-V3.583.654.159.846.387.688.791.2
DeepSeek-R285.157.465.148.786.487.395.8
Qwen3-Max84.755.961.544.888.389.492.5
Qwen3-Coder80.252.767.241.387.086.689.8

读这张表要注意几个细节:

  • Codex/Coder 系列在 SWE-Bench 上比对应通用版高 4-5 分,但 MMLU-Pro 反而低 4 分——专业化的代价。
  • DeepSeek-V3.5 和 Qwen3-Max 在 C-Eval 上比海外模型高 3-5 分,是真实的"中文优势"。
  • o 系列在数学和 GPQA 上明显领先(reasoning 时代特征),但在 Arena-Hard 上不一定第一(人类偏好风格不一定喜欢长 reasoning)。

测试启发。

横评表的核心价值不是"看谁第一",而是"看每个模型的能力轮廓"。GPT-5 是均衡型,Claude Opus 4.6 是对话/SWE 双强型,o4 是推理至上型,DeepSeek-V3.5 是中文 + 性价比型,Qwen3-Coder 是开源代码王者。选型 = 业务需求 × 能力轮廓的匹配,不是看分数加和。

第7章:私有 Benchmark 设计

公开 Benchmark 永远只能"排除",不能"决策"。最终决策必须依赖你自己设计的私有 Benchmark。下面给出 2026 年企业落地的四个核心设计原则。

7.1 原则一 · 动态生成(Dynamic)

题目不能固定。每次跑评测,至少 30% 题目需要动态生成。两种实现:

  • 模板化生成:定义题目模板(如"求函数 f(x) = ax² + bx + c 在 x = d 的导数"),每次随机抽取参数 a/b/c/d。
  • LLM 改写生成:用一个独立的强 LLM 把核心题目重新表述(保持答案不变,但 n-gram 完全不同)。
python
import random
from typing import Tuple

def generate_calculus_question() -> Tuple[str, str]:
    """生成一道二次函数求导题,返回 (题目, 答案)"""
    a = random.randint(2, 9)
    b = random.randint(-9, 9)
    c = random.randint(-9, 9)
    x0 = random.randint(-5, 5)
    derivative = 2 * a * x0 + b
    question = f"求函数 f(x) = {a}x² + {b:+d}x + {c:+d} 在 x = {x0} 处的导数值。"
    return question, str(derivative)

for _ in range(3):
    q, a = generate_calculus_question()
    print(f"Q: {q}\nA: {a}\n")

7.2 原则二 · 时效信息(Time-Sensitive)

题目应该包含模型 cutoff 之后才能知道的事实——如果模型答对,要么它真有联网/RAG,要么它"知道未来"(不可能)。这种题对"模型 cutoff 是否真实"做了最直接的检验。 例子:

问题模板:
- "请说出 2026 年 1 月 X 日 NBA 总冠军是哪支球队"
- "请说出 2025 年 11 月 X 日上证指数收盘点位(精确到个位)"
- "请说出 2026 年 1 月 X 日 GitHub trending 第一名仓库的名称"
- "请说出 2026 年 X 月发布的 GPT-5 改进版本号"

合格行为:
- 标榜 "2025-04 cutoff" 的模型应该回答 "我不知道 / 未在我训练数据中"
- 不能瞎编(瞎编要扣分)
- 如果有联网工具,应主动调用工具回答

7.3 原则三 · 业务场景(Domain-Specific)

这是大多数公开榜单覆盖不到的部分。你的业务有什么独特的术语、流程、数据格式、合规约束?把它们做成测试题。 例如金融业务专属题目:

  • "用户在咨询基金赎回时间,请按照我司话术规范回答(话术规范 PDF 附后)"
  • "用户问可不可以用花呗买理财,请回答并标注合规依据"
  • "给出一份 2024 年年报关键数据的 Markdown 表格"

业务场景题需要业务方深度参与,测试团队负责把它们转换成可自动评测的格式。这是测试团队最有 leverage 的部分——比"能否跑公开榜"价值高得多。

7.4 原则四 · 多语言(Multi-lingual)

如果你的业务涉及中文、英文、粤语、东南亚语种,必须每种语言独立成测试集——不能用"翻译同一套题"。 原因:模型的多语言能力是不均匀的。某些模型在英文上炸,到中文上掉 5%;某些模型在简体中文 90 分,繁体中文只有 78。这些差距在"翻译题"上完全测不出来——必须用每种语言原生的真实任务。

四原则总结。

动态生成解决污染问题;时效信息验证 cutoff 真实性;业务场景解决"分数高但业务不能用";多语言解决跨地区部署。四个原则缺一不可——少一个,私有 Benchmark 就退化成"另一个公开榜"。

7.5 私有 Benchmark 治理

私有 Benchmark 一旦做完不是结束,而是要建立治理机制:

  1. 版本管理:每季度发布一个 minor 版本,每年发布一个 major 版本。
  2. 访问控制:只有评测团队能看完整题集,业务和算法团队只能看汇总分数(防止"针对性优化")。
  3. 污染监控:每次模型升级后用 canary 检查是否泄露。
  4. 题目轮替:每年退役 20% 老题目并加入 20% 新题目,保持新鲜度。

第8章:污染检测的工程化流程

8.1 完整污染防御 Checklist

下面是 2026 年企业级 Benchmark 团队应该跑的污染防御 checklist,建议作为内部 SOP: 污染防御 Checklist(≥ 8 条)

  1. 所有公开 Benchmark 题目入库前,跑 n-gram 重合(n=13)扫描候选模型已知预训练语料样本,标记可疑题目。
  2. 所有自建 Benchmark 题目嵌入 canary token(每题独立、长度 ≥ 16 字符)。
  3. 每个 canary token 在跑分前用反向 prompt 测试:让模型续写或承认是否见过。
  4. 对每个候选模型记录其官方 cutoff 时间,私有 Benchmark 题目优先选 cutoff 之后的事实。
  5. 对老 Benchmark 题目做 minor perturbation(改数字 / 换词序),看分数是否大幅下跌(> 5%),下跌即可疑。
  6. 禁止把 holdout 集任何形式上传到第三方 LLM API(包括做翻译、做润色、做评分),只能在私有部署或经审计的白名单 API 上跑。
  7. 评测团队和算法团队物理隔离 holdout 集,算法团队只能看到 dev 子集和最终汇总分。
  8. 每次模型版本更新后,对 holdout 集分数做"大幅跃升"告警——分数突然涨 ≥ 8% 自动触发污染审查。
  9. 把私有 Benchmark 题目用 zero-width characters / 同形字符做"隐写指纹",可定向追溯哪一家泄露了数据。
  10. 所有第三方模型供应商合同中加入"不得将我方提交的数据用于训练"条款,并保留审计权。

8.2 污染检测的工程流程图

原始题库 → canary 注入 → n-gram 扫描候选模型 → canary 反向探针 → 扰动测试 → 输出污染报告

8.3 污染严重度分级

等级判定条件处置
L0 · 干净n-gram < 30% 且 canary 探针无响应正常计入排名
L1 · 可疑n-gram 30-50% 或扰动后分数下跌 3-5%标记,附 footnote
L2 · 中度污染n-gram 50-70% 或 canary 探针有部分匹配剔除该子集分数,全榜重算
L3 · 重度污染n-gram ≥ 70% 或 canary 完整复现该模型该榜分数作废,记录备查

第9章:lm-evaluation-harness 完整实操

EleutherAI 维护的 lm-evaluation-harness(简称 lm-eval)是 2026 年事实标准的开源评测工具。它支持 200+ 个 task,能直接对接 OpenAI、Anthropic、Google、HF Hub 上的几乎所有模型。

9.1 安装

bash
pip install lm-eval==0.4.5
# 如果要跑 OpenAI / Anthropic / Google 模型,安装可选依赖
pip install "lm-eval[openai,anthropic,google]"
# 验证安装
lm_eval --help

9.2 跑 MMLU-Pro 单榜

下面命令会把 MMLU-Pro 12000+ 题全部跑一遍 GPT-5。注意预算约 $40-60 / 次。

# 设置 API Key
export OPENAI_API_KEY="sk-..."

# 跑 MMLU-Pro on GPT-5
lm_eval --model openai-chat-completions \
  --model_args model=gpt-5,temperature=0,max_tokens=4096 \
  --tasks mmlu_pro \
  --batch_size 8 \
  --output_path ./results/gpt5_mmlu_pro \
  --log_samples \
  --apply_chat_template

# 跑结束后会输出类似:
# |        Task        |Version|Filter|n-shot|  Metric  |Value |   |Stderr|
# |--------------------|------:|------|-----:|----------|-----:|---|-----:|
# |mmlu_pro            |      2|none  |     0|exact_match|0.872|±  |0.003 |

9.3 多模型批量跑分对比

下面 shell 脚本会依次跑 GPT-5 / Claude Opus 4.6 / DeepSeek-V3.5 / Qwen3-Max 四个模型,并把结果汇总到一张 csv。

#!/bin/bash
set -e

MODELS=(
  "openai-chat-completions:model=gpt-5"
  "anthropic-chat:model=claude-opus-4-6"
  "openai-chat-completions:model=deepseek-v3.5,base_url=https://api.deepseek.com/v1"
  "openai-chat-completions:model=qwen3-max,base_url=https://dashscope.aliyuncs.com/compatible-mode/v1"
)
TASKS="mmlu_pro,gpqa_diamond,ifeval,math_500"

mkdir -p results
for spec in "${MODELS[@]}"; do
  IFS=':' read -ra parts <<< "$spec"
  backend="${parts[0]}"
  args="${parts[1]}"
  name=$(echo "$args" | sed 's/.*model=\([^,]*\).*/\1/')

  echo "==> Running $name on $TASKS"
  lm_eval --model "$backend" \
    --model_args "$args,temperature=0,max_tokens=4096" \
    --tasks "$TASKS" \
    --batch_size 8 \
    --output_path "./results/${name}" \
    --log_samples \
    --apply_chat_template
done

echo "==> Aggregating to CSV"
python aggregate.py ./results > results/summary.csv

对应的 aggregate.py

python
import json
import sys
from pathlib import Path
import csv

def parse_results(root: Path):
    rows = []
    for model_dir in root.iterdir():
        if not model_dir.is_dir():
            continue
        for f in model_dir.rglob("results_*.json"):
            data = json.loads(f.read_text())
            for task, metrics in data.get("results", {}).items():
                main_metric = next((k for k in metrics if k.endswith(",none")), None)
                if main_metric:
                    rows.append({
                        "model": model_dir.name,
                        "task": task,
                        "metric": main_metric,
                        "value": metrics[main_metric],
                        "stderr": metrics.get(main_metric.replace("value", "stderr"), 0),
                    })
    return rows

if __name__ == "__main__":
    rows = parse_results(Path(sys.argv[1]))
    writer = csv.DictWriter(sys.stdout, fieldnames=["model", "task", "metric", "value", "stderr"])
    writer.writeheader()
    writer.writerows(rows)

9.4 跑分参数细节注意事项

  • temperature 必须 = 0。Benchmark 跑分需要可重复,不能用采样。
  • max_tokens 至少 4096。CoT 评测题,模型经常需要长 reasoning 才能回答完整。
  • batch_size 别开太大。OpenAI / Anthropic 都有 RPM 限制,过大会触发 429。建议从 8 起步,根据 rate limit 调整。
  • --apply_chat_template 必须开。chat 模型如果不套 chat template,会被当成 base model 测试,分数虚低 5-10 分。
  • --log_samples 要开。记录每道题的输入输出,方便后续做错例分析和污染审查。

9.5 跑分预算估算(2026 Q1 报价)

任务题目数典型 prompt+output tokenGPT-5 单次成本Claude Opus 4.6 成本
MMLU-Pro12,032~1500/题~$48~$54
GPQA-Diamond198~3000/题~$1.6~$1.8
IFEval541~1200/题~$1.7~$1.9
Math-500500~3500/题~$4.5~$5.1
SWE-Bench Verified500~80000/题(Agent 多轮)~$280~$340

跑一轮完整横评(4 模型 × 5 榜)大约 $1500-1800。建议在公司内做"季度预算"统一规划,不要让算法 team 每个月各自跑。

第10章:OpenCompass 中文榜实操

10.1 安装与初始化

bash
git clone https://github.com/open-compass/opencompass
cd opencompass
pip install -e .
pip install -r requirements/runtime.txt

10.2 跑 C-Eval + CMMLU 双榜

OpenCompass 用 Python 配置文件描述实验。下面是一个最小完整配置:

# configs/eval_chinese_baseline.py
from mmengine.config import read_base
from opencompass.models import OpenAI, ClaudeChatModel

with read_base():
    from opencompass.configs.datasets.ceval.ceval_gen import ceval_datasets
    from opencompass.configs.datasets.cmmlu.cmmlu_gen import cmmlu_datasets

datasets = [*ceval_datasets, *cmmlu_datasets]

models = [
    dict(
        type=OpenAI,
        abbr='gpt-5',
        path='gpt-5',
        key='ENV',  # 从 OPENAI_API_KEY 读
        max_seq_len=8192,
        max_out_len=4096,
        batch_size=8,
        temperature=0,
    ),
    dict(
        type=OpenAI,
        abbr='deepseek-v3.5',
        path='deepseek-v3.5',
        key='ENV_DEEPSEEK',
        openai_api_base='https://api.deepseek.com/v1/chat/completions',
        max_seq_len=8192,
        max_out_len=4096,
        batch_size=8,
        temperature=0,
    ),
    dict(
        type=OpenAI,
        abbr='qwen3-max',
        path='qwen3-max',
        key='ENV_DASHSCOPE',
        openai_api_base='https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
        max_seq_len=8192,
        max_out_len=4096,
        batch_size=8,
        temperature=0,
    ),
    dict(
        type=ClaudeChatModel,
        abbr='claude-opus-4-6',
        path='claude-opus-4-6',
        key='ENV_ANTHROPIC',
        max_seq_len=8192,
        max_out_len=4096,
        batch_size=4,
        temperature=0,
    ),
]

启动评测:

bash
export OPENAI_API_KEY="sk-..."
export ENV_DEEPSEEK="sk-..."
export ENV_DASHSCOPE="sk-..."
export ENV_ANTHROPIC="sk-ant-..."

python run.py configs/eval_chinese_baseline.py \
  --work-dir outputs/chinese_baseline_2026q1 \
  --max-num-workers 4 \
  --debug

跑完后 OpenCompass 会自动生成一份 HTML 报告,含每个学科的明细分数、置信区间、模型对比柱状图。

10.3 OpenCompass 私有 LLM 接入

如果你跑的是公司内部部署的私有模型(vLLM / TGI),OpenCompass 可以通过自定义 model class 接入:

# models/my_internal.py
from opencompass.models import OpenAI

class InternalLLM(OpenAI):
    is_api = True

internal_model = dict(
    type=InternalLLM,
    abbr='internal-finance-v3',
    path='finance-v3',
    openai_api_base='http://internal-llm.corp:8000/v1/chat/completions',
    key='internal',
    max_seq_len=32768,
    max_out_len=4096,
    batch_size=16,
    temperature=0,
)

10.4 OpenCompass 子集筛选

跑全榜成本很高,OpenCompass 支持只跑某些学科:

# 只跑 C-Eval 的财经、法律、医学三个学科
from opencompass.configs.datasets.ceval.ceval_gen import ceval_datasets

ceval_subset = [d for d in ceval_datasets if d['abbr'] in [
    'ceval-accountant',
    'ceval-tax_accountant',
    'ceval-civil_servant',
    'ceval-clinical_medicine',
    'ceval-traditional_chinese_medicine',
    'ceval-physician',
    'ceval-mao_zedong_thought',
]]
datasets = ceval_subset

第11章:私有 Benchmark 自建脚本

11.1 整体架构

业务样本采集 → canary 注入 → 动态参数化 → 分发到候选模型 → Judge 打分 → 污染检测 → 汇总报告

11.2 完整脚本骨架

"""
private_bench.py - 企业级私有 Benchmark 评测框架
"""
import asyncio
import hashlib
import json
import secrets
import time
from dataclasses import dataclass, field, asdict
from pathlib import Path
from typing import Callable, Dict, List, Optional

import numpy as np
from openai import AsyncOpenAI
from anthropic import AsyncAnthropic

# ========== 数据结构 ==========
@dataclass
class TestCase:
    case_id: str
    category: str
    prompt: str
    reference: str
    canary: str = ""
    metadata: Dict = field(default_factory=dict)

@dataclass
class ModelResult:
    case_id: str
    model: str
    response: str
    latency_ms: float
    tokens_in: int = 0
    tokens_out: int = 0
    cost_usd: float = 0.0

# ========== Canary token ==========
CANARY_PREFIX = "PRIVATEBENCH_DO_NOT_TRAIN_"

def make_canary(seed: str) -> str:
    digest = hashlib.sha256(seed.encode()).hexdigest()[:16]
    return f"{CANARY_PREFIX}{digest}"

def inject_canary(prompt: str, canary: str) -> str:
    return f"<!-- {canary} -->\n{prompt}"

# ========== 动态题目生成 ==========
class CalculusGenerator:
    """动态生成微积分题目"""
    @staticmethod
    def generate(rng: np.random.Generator) -> TestCase:
        a = int(rng.integers(2, 10))
        b = int(rng.integers(-9, 10))
        c = int(rng.integers(-9, 10))
        x0 = int(rng.integers(-5, 6))
        deriv_at_x0 = 2 * a * x0 + b
        prompt = (f"求函数 f(x) = {a}x² + {b:+d}x + {c:+d} 在 x = {x0} 处的导数值。"
                  "只输出数字,不要解释。")
        canary = make_canary(f"calculus-{a}-{b}-{c}-{x0}")
        return TestCase(
            case_id=f"calc-{a}-{b}-{c}-{x0}",
            category="math.calculus",
            prompt=inject_canary(prompt, canary),
            reference=str(deriv_at_x0),
            canary=canary,
        )

class FinanceQARule:
    """业务场景题(金融客服话术合规)"""
    QA = [
        ("用户问:你们的余额宝当日赎回最快多久到账?请按合规话术回答。",
         "余额宝赎回 T+0 部分最快 2 小时到账,单日额度 1 万元;超过部分按 T+1 到账。"),
        ("用户问:可以用花呗买你们家的基金吗?请明确回答并说明依据。",
         "不可以。根据《基金销售机构监督管理办法》第 23 条,基金购买必须使用本人银行卡或货币基金账户,不得使用消费信贷资金。"),
        ("用户咨询:你们的私募基金有 100 万门槛但我只能拿 80 万,能否分两次买?",
         "不可以。合格投资者需单笔不少于 100 万,不得通过分单规避监管。建议您选择符合您资金规模的公募基金产品。"),
    ]

    @classmethod
    def generate_all(cls) -> List[TestCase]:
        cases = []
        for i, (q, a) in enumerate(cls.QA):
            canary = make_canary(f"finance-qa-{i}")
            cases.append(TestCase(
                case_id=f"finance-{i:04d}",
                category="finance.compliance",
                prompt=inject_canary(q, canary),
                reference=a,
                canary=canary,
            ))
        return cases

class TimeSensitiveBuilder:
    """时效信息题:用近期事实,验证 cutoff 是否真实"""
    FACTS = [
        ("2025-12-31 上证指数收盘点位(精确到个位整数)", "3408"),
        ("2025-11 GitHub trending 月榜第一仓库", "openai/codex-cli"),
    ]

    @classmethod
    def build(cls) -> List[TestCase]:
        cases = []
        for i, (fact_q, fact_a) in enumerate(cls.FACTS):
            canary = make_canary(f"timesens-{i}")
            prompt = f"请回答:{fact_q}。如果你的训练数据未涵盖该时间点,请回答“不知道”,不要瞎编。"
            cases.append(TestCase(
                case_id=f"ts-{i:04d}",
                category="timesensitive.cutoff_check",
                prompt=inject_canary(prompt, canary),
                reference=fact_a,
                canary=canary,
                metadata={"allow_idk": True},
            ))
        return cases

# ========== 模型适配 ==========
class ModelAdapter:
    def __init__(self, name: str, call_fn: Callable):
        self.name = name
        self.call_fn = call_fn

    async def generate(self, prompt: str) -> ModelResult:
        t0 = time.perf_counter()
        text = await self.call_fn(prompt)
        latency_ms = (time.perf_counter() - t0) * 1000
        return ModelResult(case_id="", model=self.name, response=text, latency_ms=latency_ms)

async def call_openai(model: str, prompt: str, base_url: Optional[str] = None) -> str:
    client = AsyncOpenAI(base_url=base_url) if base_url else AsyncOpenAI()
    resp = await client.chat.completions.create(
        model=model,
        messages=[{"role": "user", "content": prompt}],
        temperature=0,
        max_tokens=2048,
    )
    return resp.choices[0].message.content or ""

async def call_anthropic(model: str, prompt: str) -> str:
    client = AsyncAnthropic()
    resp = await client.messages.create(
        model=model,
        max_tokens=2048,
        messages=[{"role": "user", "content": prompt}],
        temperature=0,
    )
    return "".join(b.text for b in resp.content if b.type == "text")

# ========== Judge 打分 ==========
async def judge_with_reference(prompt: str, response: str, reference: str,
                                judge_model: str = "claude-opus-4-6") -> int:
    judge_prompt = f"""你是一个严格的评测员。判断模型回答是否符合参考答案。

【题目】{prompt}
【参考答案】{reference}
【模型回答】{response}

评分规则:
- 5 分:与参考答案完全一致或语义等价
- 4 分:核心答对但表述次优
- 3 分:部分正确
- 2 分:主要错误但有少量正确信息
- 1 分:完全错误或拒答(无合理理由)

只输出一个 1-5 的数字,不要解释。"""
    text = await call_anthropic(judge_model, judge_prompt)
    try:
        return int(text.strip()[0])
    except (ValueError, IndexError):
        return 1

# ========== 污染检测 ==========
def ngram_set(text: str, n: int = 13):
    tokens = text.split()
    return set(tuple(tokens[i:i+n]) for i in range(len(tokens) - n + 1))

def ngram_overlap(query: str, corpus: str, n: int = 13) -> float:
    q = ngram_set(query, n)
    if not q:
        return 0.0
    c = ngram_set(corpus, n)
    return len(q & c) / len(q)

async def canary_probe(model_call: Callable, canary: str) -> Dict:
    """让模型续写 canary,看它能否自动补出 benchmark 内容"""
    probe_prompt = f"请续写下面的字符串,至少 200 字符:\n{canary}"
    completion = await model_call(probe_prompt)
    leaked = canary in completion or "PRIVATEBENCH" in completion or len(completion) > 100
    return {
        "canary": canary,
        "completion": completion[:500],
        "suspected_leak": leaked,
    }

# ========== 评测主流程 ==========
async def run_one(adapter: ModelAdapter, case: TestCase) -> Dict:
    result = await adapter.generate(case.prompt)
    result.case_id = case.case_id
    score = await judge_with_reference(case.prompt, result.response, case.reference)
    return {
        "case_id": case.case_id,
        "category": case.category,
        "model": adapter.name,
        "response": result.response,
        "reference": case.reference,
        "score": score,
        "latency_ms": result.latency_ms,
    }

async def evaluate(adapter: ModelAdapter, cases: List[TestCase], concurrency: int = 6) -> List[Dict]:
    sem = asyncio.Semaphore(concurrency)
    async def guarded(c):
        async with sem:
            return await run_one(adapter, c)
    return await asyncio.gather(*[guarded(c) for c in cases])

def aggregate(results: List[Dict]) -> Dict:
    by_cat = {}
    for r in results:
        by_cat.setdefault(r["category"], []).append(r["score"])
    summary = {}
    for cat, scores in by_cat.items():
        arr = np.array(scores)
        # bootstrap mean CI
        boots = [np.random.choice(arr, size=len(arr), replace=True).mean() for _ in range(500)]
        lo, hi = np.percentile(boots, [2.5, 97.5])
        summary[cat] = {
            "n": len(arr),
            "mean": float(arr.mean()),
            "ci_lo": float(lo),
            "ci_hi": float(hi),
            "p1_rate": float((arr == 1).mean()),
        }
    return summary

# ========== 入口 ==========
async def main():
    rng = np.random.default_rng(20260417)
    cases: List[TestCase] = []
    for _ in range(40):
        cases.append(CalculusGenerator.generate(rng))
    cases.extend(FinanceQARule.generate_all())
    cases.extend(TimeSensitiveBuilder.build())

    Path("./bench_data").mkdir(exist_ok=True)
    Path("./bench_data/cases.jsonl").write_text(
        "\n".join(json.dumps(asdict(c), ensure_ascii=False) for c in cases),
        encoding="utf-8",
    )

    adapters = [
        ModelAdapter("gpt-5", lambda p: call_openai("gpt-5", p)),
        ModelAdapter("claude-opus-4-6", lambda p: call_anthropic("claude-opus-4-6", p)),
        ModelAdapter(
            "deepseek-v3.5",
            lambda p: call_openai("deepseek-v3.5", p, base_url="https://api.deepseek.com/v1"),
        ),
        ModelAdapter(
            "qwen3-max",
            lambda p: call_openai(
                "qwen3-max",
                p,
                base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
            ),
        ),
    ]

    summary_all = {}
    contamination_report = {}
    for adapter in adapters:
        print(f"==> Evaluating {adapter.name}")
        results = await evaluate(adapter, cases)
        summary_all[adapter.name] = aggregate(results)
        # canary 污染探针:抽 5 个 canary 做反向探测
        probes = []
        for c in cases[:5]:
            p = await canary_probe(adapter.call_fn, c.canary)
            probes.append(p)
        contamination_report[adapter.name] = probes

    Path("./bench_data/summary.json").write_text(
        json.dumps({"scores": summary_all, "contamination": contamination_report},
                   indent=2, ensure_ascii=False),
        encoding="utf-8",
    )
    print(json.dumps(summary_all, indent=2, ensure_ascii=False))

if __name__ == "__main__":
    asyncio.run(main())

11.3 输出报告示例

json
{
  "scores": {
    "gpt-5": {
      "math.calculus": {"n": 40, "mean": 4.85, "ci_lo": 4.70, "ci_hi": 4.97, "p1_rate": 0.025},
      "finance.compliance": {"n": 3, "mean": 4.33, "ci_lo": 3.67, "ci_hi": 5.00, "p1_rate": 0.0},
      "timesensitive.cutoff_check": {"n": 2, "mean": 4.50, "ci_lo": 4.00, "ci_hi": 5.00, "p1_rate": 0.0}
    },
    "claude-opus-4-6": {
      "math.calculus": {"n": 40, "mean": 4.92, "ci_lo": 4.80, "ci_hi": 5.00, "p1_rate": 0.0},
      "finance.compliance": {"n": 3, "mean": 4.67, "ci_lo": 4.00, "ci_hi": 5.00, "p1_rate": 0.0},
      "timesensitive.cutoff_check": {"n": 2, "mean": 5.00, "ci_lo": 5.00, "ci_hi": 5.00, "p1_rate": 0.0}
    }
  },
  "contamination": {
    "gpt-5": [
      {"canary": "PRIVATEBENCH_DO_NOT_TRAIN_3a8b...", "suspected_leak": false}
    ]
  }
}

11.4 集成到 CI

# .github/workflows/private-bench.yml
name: Private Benchmark
on:
  schedule:
    - cron: '0 18 * * 0'  # 每周日跑
  workflow_dispatch:

jobs:
  bench:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: '3.12' }
      - run: pip install -r requirements.txt
      - run: python private_bench.py
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
          DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
      - uses: actions/upload-artifact@v4
        with:
          name: bench-report
          path: bench_data/summary.json
      - name: Slack Notify
        run: |
          python notify_slack.py bench_data/summary.json

第12章:案例:某金融公司选型 GPT-5 vs Claude 4.6 的私有 Benchmark

12.1 背景

2026 年 Q1,某中型证券公司启动"智能投顾对话系统"建设,候选基础模型有 GPT-5、Claude Opus 4.6、DeepSeek-V3.5、Qwen3-Max 四款。决策层对各家公开榜单已经看花眼,要求技术评测团队在 4 周内给出"业务可信"的评测报告。

12.2 评测方案设计

评测团队拒绝直接照搬公开榜,而是按"业务能力 → 可测性 → 题目"三层倒推,最终设计出 6 个评测维度:

维度题量设计方法评分方式
合规话术准确性120 道从合规手册和监管问答中精炼规则匹配 + Judge
财报数据问答200 道真实 2024-2025 上市公司年报 + 自建问答精确数字匹配
金融术语解释80 道三级难度(散户/客户经理/分析师)多维 Judge
风险识别60 道构造高风险问题(诱导洗钱、内幕交易等)规则 + Judge
多轮对话连贯性40 组10 轮深度对话场景Judge + 人工审计 20%
时效新闻摘要50 道2026-01 至 2026-02 新闻(带 RAG)事实正确率 + Judge

所有题目都嵌入 canary,每月轮替 25%。

12.3 关键发现

4 周后跑出的报告关键数据如下:

维度GPT-5Claude Opus 4.6DeepSeek-V3.5Qwen3-Max
合规话术准确性3.924.363.453.78
财报数据问答4.454.214.184.32
金融术语解释4.504.624.104.25
风险识别4.184.653.924.05
多轮对话连贯性4.304.553.853.95
时效新闻摘要(RAG)4.404.324.204.45
加权综合分4.274.433.934.13

结合公开榜数据反观,发现:

  • 公开榜 GPT-5 ≈ Claude Opus 4.6,但在金融合规场景上 Claude Opus 4.6 显著领先(+0.44 分)。原因可能是 Claude 4.6 更倾向"保守拒答",正好契合合规话术。
  • DeepSeek-V3.5 在公开 C-Eval 上比海外模型高,但在金融业务的合规话术 + 风险识别两个维度上明显落后——说明它的中文优势是"通用知识"而不是"专业垂域"。
  • Qwen3-Max 在财报数据问答时效新闻摘要两个维度上表现最好——这两个维度都涉及最新中文数据,符合阿里训练数据更新频率高的预期。

12.4 最终决策

评测团队给出的建议是:

  1. 主对话引擎:Claude Opus 4.6(综合分最高,特别合规和对话强)
  2. 财报问答兜底:Qwen3-Max(财报维度最强 + 中文成本低)
  3. 降级方案:DeepSeek-V3.5(极端 traffic 高峰时切换,成本可下降 80%)

这是一个典型的"多模型组合"决策——在私有 Benchmark 出现前,公司很可能直接选 GPT-5 一家。

启示。

这个案例最大的价值不在最终选型结果,而在**"私有 Benchmark 推翻了直觉"**。如果只看公开榜,决策层会觉得 GPT-5 = Claude Opus 4.6 ≈ 平分秋色,最后大概率按品牌偏好选。但 4 周的私有评测让"直觉式选型"变成了"数据式选型",且每一个数据点都能追溯回业务真实场景。这就是测试团队的终极价值。

第13章:案例:检测到第三方 API 泄露 Benchmark 数据的过程

13.1 背景

某 SaaS 公司 2025 年底自建了一个 800 题的私有 Benchmark,用来评测三家"国产大模型聚合 API 提供商"(命名为 X、Y、Z)转发的 GPT-5/Claude 模型。一个月后,业务方反映"X 家的 GPT-5 跑分异常稳定地比官方高 1.5-2 分"。评测团队启动污染审查。

13.2 第一轮:n-gram 重合检测

团队先做了最基础的 n-gram 检测——把私有 Benchmark 题目和该 API 的请求/响应日志对比。但因为 API 提供商不会给客户日志,所以 n-gram 检测无法直接进行。改用第二层方法。

13.3 第二轮:canary token 反向探针

评测团队的 800 题里每题都嵌了 canary(格式 BENCH_CANARY_2025_xxxxxxxx)。他们写了一段脚本,对三家 API 做反向探针测试:

python
import asyncio
from openai import AsyncOpenAI

PROVIDERS = {
    "X": "https://api-x.example.com/v1",
    "Y": "https://api-y.example.com/v1",
    "Z": "https://api-z.example.com/v1",
}

CANARY_SAMPLES = [
    "BENCH_CANARY_2025_3a8b71f2",
    "BENCH_CANARY_2025_9c2d4e85",
    "BENCH_CANARY_2025_f7e1a8b3",
]

async def probe(provider: str, base_url: str, canary: str):
    client = AsyncOpenAI(base_url=base_url)
    resp = await client.chat.completions.create(
        model="gpt-5",
        messages=[{"role": "user", "content":
            f"请续写下面的字符串,至少 200 字符:\n{canary}"}],
        temperature=0,
        max_tokens=512,
    )
    text = resp.choices[0].message.content
    return {
        "provider": provider,
        "canary": canary,
        "completion": text[:500],
        "leaked": canary[:8] in text or "BENCH_CANARY" in text,
    }

async def main():
    tasks = []
    for p, url in PROVIDERS.items():
        for c in CANARY_SAMPLES:
            tasks.append(probe(p, url, c))
    results = await asyncio.gather(*tasks)
    for r in results:
        flag = "  LEAK" if r["leaked"] else "OK"
        print(f"[{flag}] {r['provider']:5s} canary={r['canary']} sample={r['completion'][:80]!r}")

asyncio.run(main())

结果发现 X 家对其中 2 个 canary 都"成功续写"出了类似 Benchmark 题目片段:

json
[  LEAK] X     canary=BENCH_CANARY_2025_3a8b71f2 sample='对应题目:求函数 f(x) = 5x² - 3x + 7 在 x = 2 处的导数...'
[  LEAK] X     canary=BENCH_CANARY_2025_9c2d4e85 sample='这是一道金融合规题,关于私募基金合格投资者...'
[OK    ] Y     canary=...
[OK    ] Z     canary=...

13.4 第三轮:扰动测试交叉验证

团队又对 X 家的 GPT-5 跑了一组"扰动版"题目(同一题改 1-2 个数字、调换句序),结果分数从 4.27 跌到 3.91。同样的扰动对 Y、Z 两家分数变化只有 0.05 之内。这证明 X 家的高分来自记忆而不是能力。

13.5 根因定位

把证据递交给 X 家技术对接,X 家承认其内部"性能优化层"会缓存常见 prompt 的回答,且其训练实验团队曾用客户 prompt 数据训练 routing 模型,违反了合同中"不得用于训练"条款。

13.6 整改与启示

整改措施:

  1. 切断 X 家 API(改成直连 OpenAI 官方)
  2. 追溯过去 6 个月所有上传 X 家的题目,全部退役并重新设计
  3. 所有第三方 API 接入合同新增"客户数据不得用于训练 / 不得缓存超过 24 小时"硬条款,且保留独立审计权
  4. 在 CI 中增加"季度性 canary 探针"任务,固化为流程

合规警告。

这个案例不是技术 bug,是合同违约 + 数据安全事件。在 2026 年,企业把数据上传给第三方 API 必须有完整审计链:(1) 合同条款;(2) 接入前的污染基线;(3) 持续的 canary 探针;(4) 异常分数告警机制。任何一环缺失,都可能让公司私有 Benchmark 变成竞争对手的训练数据。

13.7 测试团队的角色

这个案例里测试团队的核心贡献是:

  • 预先布点:在题目里嵌入 canary,是事发前 6 个月就做的事
  • 异常感知:设置了"分数异常稳定"的告警阈值(同一模型同一榜分数月度变化 < 0.2 就告警)
  • 证据闭环:用 canary 探针 + 扰动测试两条独立证据链证明污染,避免"对方狡辩"
  • 流程固化:把临时审查变成季度 SOP

13.8 同行借鉴:业内类似事件

2025 年至 2026 年初,业内已经出现至少 3 起类似的"第三方 API 污染私有 Benchmark"事件被公开复盘:

  • 某代理商被发现把客户企业用户的请求 + 响应统一发去另一家"低价模型"做 fine-tuning,按 token 价差套利。事件曝光后该代理商被多家头部企业拉黑。
  • 某海外一线供应商的"提示词缓存"功能默认开启,跨租户共享 KV cache。被研究者通过定向 prompt 探测到他人请求残留,紧急下线整改并发了致歉声明。
  • 某 RAG 服务商把客户上传的私有文档拿去做"通用知识增强训练",被一家律所通过水印文档发现并起诉成功,赔偿 470 万美元。

这三起事件加上本章的 X 家案例,背后规律都是同一条:免费/低价 = 你成为了产品。企业在选第三方 LLM API 时,价格异常便宜的供应商 = 数据泄漏概率高,几乎是 2026 年的行业经验法则。

第14章:课堂练习

  1. 读榜判断题:你看到一份新闻稿:"某模型在新发布的 ABC-Bench 上以 88.5 分超越 GPT-5 的 80.2 分,成为新 SOTA。"请列出至少 5 个你需要追问的问题来判断这个分数是否可信。
  2. 权重组合设计:你为公司客服 Bot 选型,业务画像是"中文为主,70% 退换货 + 20% 物流 + 10% 投诉"。请基于第 6 章的方法设计一个 5 项榜单的权重组合,并解释每项权重的依据。
  3. 污染检测脚本扩展:把 11.2 节的 private_bench.py 扩展一个新功能——对每个题目同时生成"3 个扰动版本",跑分后对比"原题 vs 扰动题"的分数差。如果差 > 0.5 分则标记为可疑污染。请写出关键代码(≤ 60 行)。
  4. canary 设计:你的私有 Benchmark 涉及客服日志类题目,canary 字符串如果直接拼在 prompt 里会被业务模型过滤。请设计一种"既不影响题目可读性又能在污染时被检测到"的 canary 嵌入方式(提示:可以考虑零宽字符 / 同形字符 / 元数据字段)。
  5. 合规对照:选定一个你公司在用的第三方 LLM API。对照本篇第 8.1 节 Checklist 的 10 条,列出你公司目前满足/未满足的项目。每个未满足项给出 1 句话整改思路。

本章小结。

2026 年的 Benchmark 已经不是"看分数排榜单"那么简单。它变成了一个"读 + 防 + 自建"的三位一体工程:(1) 读公开榜,要懂污染嫌疑、版本切片、权重组合;(2) 防污染,要会 n-gram 检测、canary 探针、扰动测试;(3) 自建私有榜,要符合动态/时效/业务/多语言四原则。任何一环薄弱,模型选型决策就有可能被噪声/污染/营销话术误导。这一篇提供的 lm-evaluation-harness、OpenCompass、private_bench.py 三套脚本,已经可以让你在 24 小时内搭起完整 Benchmark 体系——剩下的,是把它和你的业务真正绑定起来。

LLM Benchmark 实操与污染防控 大模型测试体系教程 · 第 47 篇 · 内部培训资料