# 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` | ```bash evalharness data list # 28 个数据集:源/子集/few-shot/split evalharness eval list # 28 个判分 recipe ``` ## 1. 安装 | 依赖 | 说明 | |---|---| | Python ≥ 3.10 | 核心零三方依赖(pydantic 除外) | | Docker(可选) | 代码执行类 benchmark(humaneval 等)需要;镜像判分时自动拉取 | | `rich`(可选) | 进度条;缺失自动降级纯文本 | ```bash 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%): ```bash evalharness eval run gsm8k --model mock-boxed --limit 8 ``` ## 2. 快速开始 六个基准一次跑完(默认用法;逐 bench 生成参数自动读 `evalharness/config/default.yaml`,aime 系列按配置自动跑 12 遍取均值): ```bash 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 ``` 单端点最小示例: ```bash 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/.yaml`。目录里只有一个 yaml 时**自动加载**;`--config ` 显式指定。 ```yaml 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): ```bash 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): ```bash 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(多轮工具调用): ```bash evalharness eval run bfcl_v3 --api-url http://localhost:8000/v1 --model qwen3-8b \ --env bfcl_mock ``` Python API: ```python 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(...) ``` ## 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/.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) | | `--auto-concurrency` | 自适应并发门:按端点健康状况自动决定并发(健康且供不应求时 +1 爬坡,请求失败 ×0.7 退避,服务端 `/metrics` 可用时按排队信号调节);当前值显示在进度条 `gate N`。此时 `--concurrency` 是起点不是上限 | | `--resume [PATH]` | 断点续跑;默认 `/ckpt/.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/<时间戳>-<模型>/`): ``` / ├── summary.xlsx 总表(Summary / Perf / Categories / Samples 四 sheet,主入口) ├── summary.csv 同一张总表的 csv 版 └── / 每个 benchmark 一个目录 ├── report.jsonl 完整报告,流式行格式(首行报告头,之后每行一个样本;grep/tail 友好) └── .xlsx 该 bench 的独立 Excel(Summary/Perf/Categories/Samples) ``` 查看与对照: ```bash 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` 模式下:分数报**均值**(`_last_run` 保留末轮值),时间 / token 报**全部轮次总和**。 ## 8. 缓存与断点 ```bash 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`): ``` / ├── datasets//_-/ 数据缓存(raw/ 原始字节 + samples.jsonl 统一样本) ├── .raw// 跨条目共享的下载 blob(多子集只下载一次) └── ckpt/[_][:repN]-.jsonl 预测断点(每条完成即追加;repeats 按轮隔离) ``` - 数据缓存内容寻址(subset/split/source 变更自动新条目) - **改了 prompt 模板须删旧断点**(`rm /ckpt/*.jsonl`),否则复用旧预测 - 网络抖动三层防护:15s 连接超时快速失败 → 池内换端点 → 指数退避重试,断网不丢批次 - 判分与生成解耦:换 recipe / grader 对存量预测直接重判(`evaluate(ds, preds)`),模型不被重复调用 - Docker 镜像源回退链可用 `EVALHARNESS_DOCKER_MIRRORS` 覆盖(逗号分隔模板,`{img}` 占位) ## 9. 扩展 加数据集(单文件放入 `evalharness/data/datasets/`,自动注册): ```python @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 = 注册原语的声明式组合): ```python @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 五个视图。 ```bash 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