Per-instance images are 1-4GB each; a --limit 10 run pulls up to 10 of them and previously stranded the whole footprint on Ctrl+C/exit. Every image THIS process pulls is now registered and released by an atexit hook (containers first, then rmi) -- images that already existed locally are never touched. EVALHARNESS_KEEP_SWE_IMAGES=1 opts out for prefetch-style runs that want to keep them. Co-Authored-By: Claude <noreply@anthropic.com>
EvalHarness
插件式 LLM / Agent 评测框架:28 个 benchmark 开箱即用,官方口径 prompt 与判分,任意 OpenAI 兼容端点,断点续跑,与 evalscope 对齐。
提供端点 → 拉数据 → 渲染官方 prompt → 并发生成 → 官方判分 → Excel/JSON 报告
特性
- 28 个 benchmark:数学 / 知识 / 问答 / 长上下文 / 代码(Docker 沙箱)/ Agent 工具调用,prompt 与判分器按官方实现逐字节复刻,与 evalscope 双层验证对齐(见 §10)
- 任意端点:单端点、多端点池(轮询 + 自适应并发 + failover)、云 API 全兼容;vLLM / sglang / lmdeploy / ollama / OpenAI 协议直连
- 断点续跑:每条预测完成即落盘,中断重跑只补缺口;
repeats多轮采样自动隔离断点 - YAML 配置:逐 bench 生成参数(temperature / max_tokens / repeats …)集中管理,命令行保持极简
- 长输入安全:tokenizer 中段截断(
max_input_tokens)+ 上下文溢出自适应收缩重试 - 性能统计:延迟 / 吞吐 / token 用量默认采集;
--perf追加流式 TTFT / ITL - 全链路插件化:数据集、判分、prompt 渲染、适配器、沙箱、进度条、主题、钩子均可单文件注册扩展
- 模型指纹核验:fp_fusion 五维探针电池,检测 API 换模 / 降配 / 冒充 / 套壳
0. Benchmarks
| 族 | benchmark |
|---|---|
| 数学 | gsm8k competition_math aime24/25/26 hmmt26 imo_answerbench |
| 知识/选择题 | mmlu mmlu_pro cmmlu gpqa_diamond arc hellaswag winogrande bbh |
| 问答 | trivia_qa drop simple_qa hle |
| 长上下文 | longbench_v2 openai_mrcr |
| 代码(Docker 沙箱) | humaneval bigcodebench live_code_bench |
| Agent/工具 | bfcl_v3 general_fc tau2_bench swe_bench_verified |
evalharness data list # 28 个数据集:源/子集/few-shot/split
evalharness eval list # 28 个判分 recipe
1. 安装
| 依赖 | 说明 |
|---|---|
| Python ≥ 3.10 | 核心零三方依赖(pydantic 除外) |
| Docker(可选) | 代码执行类 benchmark(humaneval 等)需要;镜像判分时自动拉取 |
rich(可选) |
进度条;缺失自动降级纯文本 |
git clone https://git.meta-stone.net/sora/EvalHarness.git
cd EvalHarness
conda create -n evalharness python=3.10 -y
conda activate evalharness
pip install .
离线自检(不联网、不接模型,应得 acc 100%):
evalharness eval run gsm8k --model mock-boxed --limit 8
2. 快速开始
六个基准一次跑完(默认用法;逐 bench 生成参数自动读 evalharness/config/default.yaml,aime 系列按配置自动跑 12 遍取均值):
evalharness eval run humaneval aime25 aime26 gpqa_diamond mmlu_pro longbench_v2 \
--api-url http://174.1.60.4:30000/v1 \
--model /data/hf_models/GLM-5.3-NVFP4 \
--disable-thinking \
--resume \
--concurrency 4 \
--out-dir /data1/sora/temp/results \
--cache-dir /data1/sora/temp \
--hf-endpoint https://hf-mirror.com
单端点最小示例:
evalharness eval run gsm8k \
--api-url http://localhost:8000/v1 \
--model qwen3-8b \
--disable-thinking \
--limit 200 --resume \
--out-dir results/run1
3. 配置(YAML)
逐 bench 生成参数集中在 evalharness/config/<name>.yaml。目录里只有一个 yaml 时自动加载;--config <name> 显式指定。
default: # 所有 bench 继承的协议级默认
temperature: 0.0
top_p: 1.0
max_tokens: 32768
aime25: # 逐 bench 覆盖
temperature: 1.0 # 采样温度(temp=1 方差测量)
max_tokens: 8192 # 生成预算
repeats: 12 # 跑 12 遍报均值 ± 极差(断点按轮隔离,互不串用)
longbench_v2:
max_input_tokens: 128000 # 超长输入的 tokenizer 中段截断预算
优先级(低 → 高):
DatasetSpec.gen_config < default 段 < bench 段 < 命令行显式参数
当前支持的键:temperature top_p max_tokens max_input_tokens repeats。
4. 进阶用法
多端点池(轮询 + 自适应并发 + failover):
evalharness eval run mmlu \
--provider openai-pool \
--api-url 'http://gpu1:{8123..8130}/v1,http://gpu2:{8200..8203}/v1' \
--model qwen3-8b --disable-thinking
需要 judge 的 bench(hle / simple_qa / imo):
evalharness eval run hle \
--api-url http://localhost:8000/v1 --model qwen3-8b --disable-thinking \
--judge-model dp4-flash \
--judge-api-url http://judge-host:30000/v1 \
--limit-per-task 25
Agent bench(多轮工具调用):
evalharness eval run bfcl_v3 --api-url http://localhost:8000/v1 --model qwen3-8b \
--env bfcl_mock
Python API:
import evalharness
rep = evalharness.run('gsm8k', 'openai/http://localhost:8000/v1?qwen3-8b', limit=200)
rep.save('gsm8k.report.json')
# notebook / async 环境用 await evalharness.arun(...)
Agent 环境的额外依赖
tau2_bench 需要官方引擎(PyPI 上的 tau2 是同名无关项目,别装错):
pip install -e /path/to/tau2-bench # 本机源码(es 仓库 tools/ 下有)
# 或: pip install git+https://github.com/sierra-research/tau2-bench
swe_bench_verified 需要 pip install swebench + 逐题 Docker 镜像(见 §8)。
bfcl_v3 / general_fc 无额外依赖。
5. 参数速查
| 参数 | 作用 |
|---|---|
--api-url --model --provider |
端点、模型名(纯名字,无需拼 spec)、协议(openai-chat/openai-pool) |
--api-key |
显式 key(优先于环境变量;只进请求头,不写入 spec/报告) |
--disable-thinking |
enable_thinking=false(Qwen3/GLM 类模型推荐;带 tools 的请求自动退回兼容软开关) |
--judge-model --judge-api-url --judge-api-key --judge-provider |
judge 端四件套,语义与主模型对称 |
--config NAME |
指定 evalharness/config/<NAME>.yaml(省略时唯一 yaml 自动加载) |
--profile NAME |
命名生成参数集(dp4-nothink / qwen3-es-parity / t1-short 或自定义) |
--limit N / --limit-per-task N |
全局前 N / 每子集前 N(多科目 bench 用后者;可组合取交集) |
--subset --split --source |
覆盖子集 / split / 数据源(可指本地路径离线跑) |
--concurrency N |
并发(默认 32;长输出 bench 建议 8-16) |
--concurrency auto |
自适应并发门:从 2 起步,健康且供不应求时 +1 爬坡,请求失败 ×0.7 退避(服务端 /metrics 可用时按排队信号调节);当前值显示在进度条 gate N |
--resume [PATH] |
断点续跑;默认 <cache-dir>/ckpt/<bench>.jsonl |
--env NAME |
agent 环境(bfcl_mock 等) |
--perf |
采集流式 TTFT / ITL / 重试率入报告 |
--hf-endpoint URL |
数据下载端点(如 https://hf-mirror.com,免手动 export) |
--cache-dir DIR |
缓存根目录(数据缓存 + 断点同根;默认 $EVALHARNESS_CACHE 或 ~/.cache/evalharness) |
--out FILE / --out-dir DIR |
报告落盘,见 §6 输出结构 |
--style text|md|md_compare|excel|radar|errors |
结果渲染样式 |
--no-progress |
关闭 Rich 进度条(重定向日志时用) |
API key 解析顺序:--api-key > 按端点域名的环境变量(api.openai.com→OPENAI_API_KEY、anthropic.com→ANTHROPIC_API_KEY、dashscope→DASHSCOPE_API_KEY、bigmodel→ZAI_API_KEY)> OPENAI_API_KEY。自建端点无鉴权可不管。
6. 输出结构
--out-dir 产出(不传时自动保存到 evalharness-results/<时间戳>-<模型>/):
<out-dir>/
├── summary.xlsx 总表(Summary / Perf / Categories / Samples 四 sheet,主入口)
├── summary.csv 同一张总表的 csv 版
└── <bench>/ 每个 benchmark 一个目录
├── report.jsonl 完整报告,流式行格式(首行报告头,之后每行一个样本;grep/tail 友好)
└── <bench>.xlsx 该 bench 的独立 Excel(Summary/Perf/Categories/Samples)
查看与对照:
evalharness viz show gsm8k.report.json # 控制台表格
evalharness viz show a.json b.json --style md_compare # 多模型对照(含差值标记)
evalharness viz show report.json --style excel # 4-sheet 仪表盘
evalharness viz show report.json --style errors # 失败样本下钻
7. 性能统计
每轮运行的延迟与 token 用量默认采集,进 summary.csv 的 Perf 列与 metric_groups['perf']:
| 指标 | 采集条件 |
|---|---|
latency_mean/p50/p90/p95/p99_s |
始终采集(逐请求墙钟) |
output_tps request_qps input/output_tokens success_rate retry_rate |
始终采集 |
ttft_mean/p90/p99_s、tpot_*(逐 token 延迟) |
仅 --perf(流式才有首 token 时刻) |
repeats 模式下:分数报均值(<metric>_last_run 保留末轮值),时间 / token 报全部轮次总和。
8. 缓存与断点
evalharness data fetch gsm8k mmlu --workers 8 # 预取(首次运行也会自动下载)
evalharness data stats cmmlu # 条数/长度/答案分布
evalharness data show gsm8k -n 2 # 看前 2 条样本
evalharness data unload gsm8k # 删缓存
缓存根目录由 --cache-dir 指定(或 $EVALHARNESS_CACHE,默认 ~/.cache/evalharness):
<cache-dir>/
├── datasets/<bench>/<subset>_<split>-<hash>/ 数据缓存(raw/ 原始字节 + samples.jsonl 统一样本)
├── .raw/<repo-hash>/ 跨条目共享的下载 blob(多子集只下载一次)
└── ckpt/<bench>[_<subset>][:repN]-<hash>.jsonl 预测断点(每条完成即追加;repeats 按轮隔离)
- 数据缓存内容寻址(subset/split/source 变更自动新条目)
- 改了 prompt 模板须删旧断点(
rm <cache-dir>/ckpt/<bench>*.jsonl),否则复用旧预测 - 网络抖动三层防护:15s 连接超时快速失败 → 池内换端点 → 指数退避重试,断网不丢批次
- 判分与生成解耦:换 recipe / grader 对存量预测直接重判(
evaluate(ds, preds)),模型不被重复调用 - Docker 镜像源回退链可用
EVALHARNESS_DOCKER_MIRRORS覆盖(逗号分隔模板,{img}占位)
9. 扩展
加数据集(单文件放入 evalharness/data/datasets/,自动注册):
@register_dataset(DatasetSpec(name='mybench', source='org/mybench',
split='test', task_type='mcq'))
def mybench():
return FieldSpec(input='question', choices='options', target='answer_key')
绑定判分(recipe = 注册原语的声明式组合):
@register_eval('mybench')
def mybench():
return EvalRecipe(
extract=['my_answer', 'answer_phrase'], # 级联,首个成功者胜
scorers={'acc': 'exact'}, # math_equal/em_f1/execution/env_reward/llm_judge
aggregators={'acc': 'mean'}, # pass_at_k/grouped_avg/binned_avg
)
其余插件点同构:@register_prompt_renderer、@register_extractor/scorer/aggregator、@register_adapter、@register_sandbox、@register_env、@register_renderer。
运行外壳同样是插件:
| 插件点 | 注册 | 说明 |
|---|---|---|
| 进度报告 | @register_progress('rich'/'plain'/...) |
--progress-plugin 选择;rich 是终端进度条,plain 是纯叙事行(CI/日志) |
| 叙事主题 | @register_theme('default'/...) |
--theme 选择;图标/配色/句子高亮的映射表 |
| 生命周期钩子 | @register_hook('on_benchmark_failed'/'on_benchmark_done') |
观察/扩展运行(webhook 通知、失败重试策略),钩子报错不影响主流程 |
| 端点探针 | @register_prober('ping'/...) |
运行前的端点可用性验证策略(EVALHARNESS_PROBER 环境变量选择) |
10. 对齐验证
prompt 与判分器经双层验证(字符串级:同一记录双侧渲染逐字节一致;分数级:同题同参数对比 evalscope):
- Qwen3-8B:23/28 分差 < 0.05
- DeepSeek-V4-Flash:20+/25 分差 < 0.05(mmlu_pro 0.0000)
残差均已定性(金标集差异 / 排列敏感 / benchmark 侧缺陷),见各 recipe 注释。
附录 A:模型指纹核验(fp_fusion)
回答一个问题:API 背后跑的,到底是不是它声称的那个模型? 向 OpenAI 兼容端点发送探针电池(回答分布 / 自我身份 / 元知识 / 能力边界 / 文风五维),与内置参考指纹库比对,输出五档裁决 + 0~1 融合分 + 证据链。检测偷梁换柱、降配缩水、主动冒充(伪身份注入屈服)、套壳拼装与中转代理;--mode full 一次运行产出 verify / attribution / variant / adversarial / robustness 五个视图。
evalharness fingerprint list # 内置参考指纹库(fp_fusion 口径 + detector 旧口径)
evalharness fingerprint run \
--api-url http://localhost:8000/v1 --model Qwen3-8B \
--mode full --cells core16 --text-skip pruned7 \
--d-samples 25 --baseline-samples 5 --timeout 90 \
--impersonate "You are Kimi, Moonshot AI virtual assistant." \
--reference glm53 \
--report-path reports/fp_qwen.json
--reference接受短名(如glm53,见fingerprint list)或 JSON 路径;省略 = 自证模式(裁决上限 LIKELY_MATCH)--impersonate注入伪身份启用对抗视图(冒充检测);剪枝定稿协议即上例参数,单次 505 条请求,公网 14.5–28 min,本地 vllm 5.6–7 min- 报告写入
--report-path,同目录raw_answers.jsonl存全部探针原文 - 离线分析与参考采集脚本(
cell_snr.py/validate_*.py/collect_ref.sh等,可直接python <script>运行)与完整方法论文档见evalharness/fingerprint/fp_fusion_介绍.md
附录 B:架构
data/ 统一 Sample schema,惰性物化,内容寻址缓存(28 个单文件插件)
model/ adapter(协议)+ pool(端点池/AIMD/failover)+ prompt_renderers + gen_profiles
eval/ extract → score → aggregate 流水线 + recipes
sandbox/ docker 硬隔离执行 / local;镜像引用计数
agent/ 消息泵 + Environment 插件(bfcl/tau2/swe)
fingerprint/ fp_fusion 模型指纹基准(探针电池 → 并发采集 → 五视图打分;独立纵向,
不走 data/eval 管线,自带 engine 与参考库)
viz/ text/md/md_compare/excel/radar/errors
progress/ Rich 每样本进度(缺 rich 自动降级)
层间严格分离:数据层只回答"题目与金标",判分层只回答"如何评判",模型层只回答"如何触达";预测是不可变 artifact。
附录 C:常见问题
端点不可达(ConnectError,跑样本前即中止)
探针在首个样本前发 1-token ping,失败即给出 curl 验证命令并中止,零 token 浪费。按提示 curl -m 5 <api-url>/chat/completions ... 手工确认服务在跑。
上下文溢出(400 maximum context length)
自动截短 prompt 15% 重试(跨 tokenizer 安全边际,1-2 次收敛);max_input_tokens(YAML)可预先做中段截断,长上下文 bench 建议配置。
思考模式(thinking)关不掉?
--disable-thinking 走 chat_template_kwargs,非流式请求完全生效(sglang/vLLM 已验证)。注意:部分网关在流式路径会剥掉该参数——本框架仅在 max_tokens > 100000 时自动转流式(聚合还原为非流式响应),常规 8k/32k 预算不受影响。智谱云 API 风格的 thinking: {"type": ...} 参数 sglang 不识别,自建端点请用本框架的开关。
GLM 大预算生成挂起 非流式大 body(100k+ token 输出)部分网关会缓冲到超时;自动流式路径已内置(见上条),无需手工干预。
Docker 拉镜像慢/失败
EVALHARNESS_DOCKER_MIRRORS="mirror.example.com/{img},docker.io/{img}" 覆盖回退链。