13 KiB
EvalHarness 对标 evalscope 全程踩坑记录
28 bench 双框架对比过程中发现的所有问题(我方 bug / es 方 bug / 口径差),按层归类。 每条含:现象 → 根因 → 修复 → 验证结果。
一.5、fix2 轮新增修复(2026-09-02 晚,prompt 逐字对齐 + 基建)
16.5. es bfcl 单轮全 0 = 依赖版本崩溃(已帮修,evalscope/.../bfcl/v3/utils.py)
- 现象:es bfcl simple/multiple/parallel 等 10 个单轮类全部 0 分,irrelevance 类却 0.9+
- 根因:utils.py convert_format_language() import bfcl_eval.constants.enums(BFCL v4 新版枚举),环境装的是 bfcl_eval 2025.8.6.2(旧版,ast_checker 直接比较字符串 "Java"/"JavaScript"/"Python")→ ModuleNotFoundError → except 吞掉 → 全部判 ast_decoder:decoder_failed
- 修复:utils.py 两个 convert 函数改为版本兼容(try 新版枚举,fallback 旧版字符串,注意 JavaScript 大写 S)
- 验证:es bfcl 20/类 0.1736 → 0.3563(simple 0.55、live_simple 0.7、parallel 0.45-0.5 全部起来)
- 遗留:我方 0.266 vs es 修复版 0.356 仍差 0.09——我方 bfcl_mock 的官方后端转换细节(underscore_to_dot 等)待对齐
- 多段 pool spec range 替换 bug(
model/runner.py::_make_adapter)- 现象:多段逗号池(105+138+140+143 共 20 口)全军覆没 ConnectError,单段池正常;curl 同口 200
- 根因:
re.sub(r'\{..\}', port, u, count=1)每次只替换最前的 range → 20 个成员 URL 全带字面量{8200..8203},httpx 连畸形 host 必败 - 修复:按逗号拆段,每段独立 search+替换自己的 range
- 验证:20 成员展开正确,4/4 请求成功;停滞的 mmlu_pro 恢复 1 samples/s
- 网络抖动三层自愈(集群路由分钟级断通交替,17:07-18:15 反复)
- adapter:httpx.Timeout(connect=15, read=300, write=30, pool=15)——半开 TCP 快速失败
- pool:实例级 failover(已有)
- runner.run_one:6 次退避重试(10/20/40/60/90s)跨过断网窗口——gather 不再被单点异常炸掉
- prompt 逐字对齐 10 处(双侧渲染 diff 器验证,全部 IDENTICAL):
- mmlu 域匹配失效(subject/category 键名不一致 → 全局前 5,es 是每科目 5 范例)
- mmlu_pro 三处(header 应在范例前 / cot_content 变换单 ANSWER / 测试选项
A) x) - bbh 测试题缺
Q:/A: Let's think包装 - aime 缺尾部
Remember to put your answer inside \boxed{}.+ 前导\n - gsm8k
\boxed{}.句号;math fewshot 单换行分隔;imo/hmmt/cmmlu/trivia 尾换行 - lcb 缺
### Format:前缀 + starter_code 进代码块 + expert 头移到 system role - hle judge prompt 换 es 逐字版 + 三态契约移 system role
- trivia
list(str)拆字符隐患
- es limit 是 per-subset 语义:es mmlu limit=200 = 每科目 200 = 全量 14042!同题跑法必须 per-subset k(mmlu 4/科目=228、cmmlu 3、hle 25、imo 25、lb2 66/长度)
- gfc ckpt 版本漂移:数据镜像重物化后 metadata 变化 → key_for 失配 restored 0;同口径 f1 直接从本次 report.samples 算更稳
一、我方框架 bug(16 个,全部已修)
判分 / 聚合层
-
few_shot_hook 从未挂载(
data/registry.py)- 现象:bbh 全程 0-shot(无官方 3-shot CoT),15 个 MCQ 子集接近 0 分
- 根因:
register_dataset装饰器从不把模块级<name>_few_shot函数挂到 provider 上,runnergetattr(prov, 'few_shot_hook')永远 None - 修复:装饰器里
factory.__globals__.get(f'{spec.name}_few_shot')自动拾取(注意__globals__是 dict 要用.get不是getattr) - 验证:bbh 27子集×10 = 0.819 vs es 0.8185
-
MCQ 抽取格式不匹配(
eval/recipes/qa.py)- 现象:bbh 全部 MCQ 子集判分全 miss
- 根因:
mcq_letter返回裸字母'A',target 是'(A)' - 修复:bbh recipe 按 target 形态格式化为
f'({val})'
-
llm_judge 标签子串扫描(
eval/scorer.py)- 现象:hle 虚高 1.0、simple_qa 解析错乱
- 根因:
label in upper全文子串匹配——judge 解释文本里 "CONSISTS" 命中 'C'、"ANSWER" 命中 'A' - 修复:三级解析——
label_pattern正则(如GRADE:\s*([CI]))→ 尾行词边界匹配 → 全文词边界兜底 - 验证:hle 1.0→0.031(es 0.050 同族)、simple_qa 见下条
-
dict 聚合器主指标被派生值均值覆盖(
eval/runner.py)- 现象:simple_qa 真值 0.035 被报成 0.403
- 根因:
simpleqa_official返回 5 个派生指标 dict,代码取mean(全部值)= mean(0.035, 0.945, 0.02, 0.98, 0.036) = 0.403 - 修复:dict 展开时主指标取同名键
out[metric],无同名键才退化均值 - 验证:simple_qa 同题 0.050 vs es 0.0503
-
judge adapter 拿不到 API key
- 现象:judge 全 401
- 根因:launcher 没 export OPENAI_API_KEY(spec URL 里不含 key)
- 修复:启动脚本统一带 key
配对 / 选样层
-
双重
_apply_limits依赖原地 shuffle 别名(model/runner.py)- 现象:per-task 截断时判分配对错位(mmlu 一度 0.275)
- 根因:run_eval 和 generate_predictions 各调一次
_apply_limits,旧实现靠「in-place shuffle 使两处列表别名同步」侥幸正确;per-group 重写返回新列表后别名断裂 → samples 顺序 ≠ preds 顺序 - 修复:run_eval 用确定性重算得到与 generate_predictions 完全一致的 work 列表再
zip判分 - 验证:mmlu 恢复 0.730+
-
checkpoint key 不含 subset
- 现象:lb2 三个长度互相污染缓存
- 修复:ckpt 名加
:{subset}后缀
-
checkpoint key 不含 prompt / 生成参数
- 现象:改 prompt 模板后重跑,复用旧模板的预测(hswag 渲染实验多次被坑;humaneval 指令头实验同)
- 教训:改任何影响 prompt 的东西必须删 ckpt(此后每次模板修改都显式
rm ckpt)
-
no_shuffle没管到 per_task 分支- 现象:以为在跑「同题前 N」,实际 per-group 内仍 Random(42).shuffle 取前 N(hswag 同题实验一度假对齐)
- 修复:per_task 分支尊重
shuffle=False(组内不洗直接前 N)
-
选样语义与 es 不同(
model/runner.py)- es:每个 subset 独立
Random(42).shuffle后取前 N - 我方旧实现:全局 shuffle 后按组截断 → 同池不同题
- 修复:per_task 时按组分组、组内独立 Random(42)、组序内取前 N
- es:每个 subset 独立
生成 / prompt 层
-
/no_think裸拼污染题面- 现象:prompt 尾部最后一个选项后面跟
/no_think字样 - 根因:Qwen3 软开关实现把标记 append 到最后一条 user 消息
- 修复:普通请求改
chat_template_kwargs: {enable_thinking: false}(服务端验证支持);tools 请求保留软开关(模板 400 规避) - 验证:gpqa 0.263 → 0.460(es 0.480)
- 现象:prompt 尾部最后一个选项后面跟
-
MCQ 选项渲染字符级差异(strict/cot_letter 分支)
- 现象:hswag 同题下
A.渲染 = 0.7825、A)渲染 = 0.565——两个字符差 22 分(no-think Qwen3 对格式极端敏感) - 修复:所有 MCQ 分支逐字对齐 es:
A) 选项+one of A,B,C,D(mmlu_pro 例外:es 用A空格分隔) - 验证:wino 0.655 = es 0.655;hswag 对齐中
- 现象:hswag 同题下
-
prompt 模板逐 bench 缺失/走样(最大一类,累计 12+ 处)
- mmlu/cmmlu/mmlu_pro:我方 strict「整个回复只能是 ANSWER」 vs es「Think step by step + last line ANSWER」→ 全部对齐(mmlu 0.730→0.795,es 0.800)
- few-shot 渲染:es 范例=裸题+
A)+ANSWER:,我方=Question:/A./Answer: → 对齐 + 按科目域匹配范例(es biology 题配 biology 范例) - mmlu_pro:es 专属模板(
Question:/Options:/A x+ CoT 范例 + 按域 header)→ 0.471→0.579(es 0.629) - gsm8k:es 范例=题+
Reasoning:全文+ANSWER: \boxed{}→ runner 加 reasoning 元数据渲染分支 - math:es 范例=
Problem:/Solution:裸答案 → 同上 - aime×3:es=「Solve the following math problem step by step. Put your answer inside \boxed{}.」指令前置,我方是题后 suffix → 加
aime_es - imo/hmmt26:es=
Problem:\n{题}\n\nPlease reason boxed(Problem: 前缀),我方裸题 → 套imo_es(imo 0.138→0.19;hmmt 验证中) - simple_qa:es=
Answer the question:\n\n{q}且无 Answer 行契约,我方自加契约 →simple_qa_es - trivia:es 开放卷(
Content: {wiki证据列表}+ es 模板),我方闭卷(镜像键名读错wiki_content≠wiki_context)→ 修键名+开模板(0.555→0.700,es 0.660) - lb2:es=
<text>包装 + CoT 契约,我方裸文档+strict →lb2_es - humaneval:es 指令头("Read the following function signature...")→ 加了头后 harness 仍用 input 拼程序导致指令进 main.py 全 0 → metadata 存原始 prompt,harness 用 metadata 版
- LCB:我方无代码契约(模型输出 LaTeX 数学推导!)→ 官方 system+
### Question:/### Answer:+```python 格式 →lcb_es - drop:es=
Think step by step + "Answer: [ANSWER]"→ prompt_suffix;范例=完整 Passage 式
-
tau2 env 内部调用不传生成参数(
agent/envs/tau2_official.py)- 现象:agent 调用走 adapter 默认 4096 tokens/无温度
- 修复:
run_task(gen_kwargs=)透传(temp 0/16k;16k+长对话会超 131072 上下文 → 8192)
-
tau2 reward 读不存在的字段
- 根因:读
environment_reward/communication_reward,tau2 官方 reward_info 字段是reward/db_check/...→ 恒 0 - 修复:取
rewards['reward'](es 同款官方综合分)
- 根因:读
-
HF 下载同步阻塞事件循环
- 现象:18 任务并发时全卡死(py-spy 见主线程
_hf_download) - 修复:
asyncio.to_thread物化
- 现象:18 任务并发时全卡死(py-spy 见主线程
基础设施
- 根盘写满(Errno 28)两次:HF 缓存迁移 /data2、清 modelscope 196G、truncate docker 巨型日志
pkill -f <pattern>会误杀自己所在 shell(cmdline 含 pattern)→ 用 launcher 文件 + 短命令- bash 工具 timeout 会杀未及 detach 的后台任务 →
nohup ... < /dev/null & disown+ 立即返回 - checkpoint 恢复打印
restored N (M to generate):N≠M 命中时警惕 key 失配(多次救场信号)
二、es(evalscope)侧问题(代码级证据)
- tau2 崩溃:
tau2_bench/generation.py:118openai_chat_choices(include_reasoning=False)丢弃 Qwen3 思考通道 → 空 content → 官方引擎message.py:116校验 raise(temp=0 确定性复现) - gfc 误判(已帮修):
general_fc_adapter.pyis_call_tool = finish_reason=='tool_calls',vLLM/Qwen3 输出 tool_calls 时 finish_reason 常为 'stop' → FN 虚高。patch:or bool(tool_calls) - BCB 执行器空跑(已帮修):
bigcodebench_adapter.pypassed = status=='success'——沙箱命令完成≠测试通过,198/200 空输出判对(0.99 虚高)。patch:unittest 显式 runner +__BCB_RESULT__ PASS标记 → 0.365(与我们 0.375、交叉判分三方一致) - LCB 本地执行全体 -2:
use_sandbox=false本机跑 codegen_metrics 全失败(环境缺依赖);docker sandbox 修复后 0.413 有效 - gfc 0.0 失败运行残骸:断连+空回复产物;重跑 0.456
- swe 无评测断点:198/200 评完后报告步骤遇断连作废,重跑=整段 4h(我们用日志聚合出真值 0.0)
三、口径差(非 bug,须同口径才可比)
| 类别 | 内容 |
|---|---|
| 选样 | 双方 shuffle42 的 base 行序不同 → 不同题集。统一方案:双侧 shuffle=False 取原始前 N(数据源行序一致,已验证 400/400 同题) |
| 金标集合 | drop:es=answer+validated_answers 合并(任一算对),我们镜像无该字段 → 严 ~0.065 |
| 指标定义 | gfc:es 报 tool_call_f1,我们 acc(已重算同口径);imo:judge vs math_equal(已统一 judge:0.188 vs 0.198) |
| 数据构成 | math 全量:我们随机 200(易题占比高)vs es 5 Level 均衡——对齐档 L×N 才可比 |
| 执行环境 | bfcl/gfc/tau2:es+Qwen3 的 agent 栈兼容性差;LCB:判定器不同(官方 codegen_metrics vs 自写 runner) |
| 截断 | lb2 文档中位 41.7 万字符天然超 131072——head+tail 截断双方同构(非 bug) |
四、方法论教训
- 分数不一致时先做同题实验(同 prompt 双跑 / 同题双侧)——hswag 0.2 之谜三小时才定位到两个字符
- 交叉判分(A 的输出 × B 的判分器)是分离「判分层差 vs 生成层差」的唯一利器
- 隔离变量实验:同题×同 prompt×只换渲染 → 定位
A.vsA) - no-think 模型对 prompt 字符级格式极端敏感(22 分级别的摆动)
- ckpt key 必须覆盖 prompt 语义,否则改模板=白改