# 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 等)待对齐 17. **多段 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 18. **网络抖动三层自愈**(集群路由分钟级断通交替,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 不再被单点异常炸掉 19. **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)` 拆字符隐患 20. **es limit 是 per-subset 语义**:es mmlu limit=200 = 每科目 200 = 全量 14042!同题跑法必须 per-subset k(mmlu 4/科目=228、cmmlu 3、hle 25、imo 25、lb2 66/长度) 21. **gfc ckpt 版本漂移**:数据镜像重物化后 metadata 变化 → key_for 失配 restored 0;同口径 f1 直接从本次 report.samples 算更稳 --- ## 一、我方框架 bug(16 个,全部已修) ### 判分 / 聚合层 1. **few_shot_hook 从未挂载**(`data/registry.py`) - 现象:bbh 全程 0-shot(无官方 3-shot CoT),15 个 MCQ 子集接近 0 分 - 根因:`register_dataset` 装饰器从不把模块级 `_few_shot` 函数挂到 provider 上,runner `getattr(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 2. **MCQ 抽取格式不匹配**(`eval/recipes/qa.py`) - 现象:bbh 全部 MCQ 子集判分全 miss - 根因:`mcq_letter` 返回裸字母 `'A'`,target 是 `'(A)'` - 修复:bbh recipe 按 target 形态格式化为 `f'({val})'` 3. **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 见下条 4. **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 5. **judge adapter 拿不到 API key** - 现象:judge 全 401 - 根因:launcher 没 export OPENAI_API_KEY(spec URL 里不含 key) - 修复:启动脚本统一带 key ### 配对 / 选样层 6. **双重 `_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+ 7. **checkpoint key 不含 subset** - 现象:lb2 三个长度互相污染缓存 - 修复:ckpt 名加 `:{subset}` 后缀 8. **checkpoint key 不含 prompt / 生成参数** - 现象:改 prompt 模板后重跑,复用旧模板的预测(hswag 渲染实验多次被坑;humaneval 指令头实验同) - 教训:**改任何影响 prompt 的东西必须删 ckpt**(此后每次模板修改都显式 `rm ckpt`) 9. **`no_shuffle` 没管到 per_task 分支** - 现象:以为在跑「同题前 N」,实际 per-group 内仍 Random(42).shuffle 取前 N(hswag 同题实验一度假对齐) - 修复:per_task 分支尊重 `shuffle=False`(组内不洗直接前 N) 10. **选样语义与 es 不同**(`model/runner.py`) - es:**每个 subset 独立** `Random(42).shuffle` 后取前 N - 我方旧实现:全局 shuffle 后按组截断 → 同池不同题 - 修复:per_task 时按组分组、组内独立 Random(42)、组序内取前 N ### 生成 / prompt 层 11. **`/no_think` 裸拼污染题面** - 现象:prompt 尾部最后一个选项后面跟 ` /no_think` 字样 - 根因:Qwen3 软开关实现把标记 append 到最后一条 user 消息 - 修复:普通请求改 `chat_template_kwargs: {enable_thinking: false}`(服务端验证支持);tools 请求保留软开关(模板 400 规避) - 验证:gpqa 0.263 → 0.460(es 0.480) 12. **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 对齐中 13. **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=`` 包装 + 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 式 14. **tau2 env 内部调用不传生成参数**(`agent/envs/tau2_official.py`) - 现象:agent 调用走 adapter 默认 4096 tokens/无温度 - 修复:`run_task(gen_kwargs=)` 透传(temp 0/16k;16k+长对话会超 131072 上下文 → 8192) 15. **tau2 reward 读不存在的字段** - 根因:读 `environment_reward/communication_reward`,tau2 官方 reward_info 字段是 `reward/db_check/...` → 恒 0 - 修复:取 `rewards['reward']`(es 同款官方综合分) 16. **HF 下载同步阻塞事件循环** - 现象:18 任务并发时全卡死(py-spy 见主线程 `_hf_download`) - 修复:`asyncio.to_thread` 物化 ### 基础设施 - 根盘写满(Errno 28)两次:HF 缓存迁移 /data2、清 modelscope 196G、truncate docker 巨型日志 - `pkill -f ` 会误杀自己所在 shell(cmdline 含 pattern)→ 用 launcher 文件 + 短命令 - bash 工具 timeout 会杀未及 detach 的后台任务 → `nohup ... < /dev/null & disown` + 立即返回 - checkpoint 恢复打印 `restored N (M to generate)`:N≠M 命中时警惕 key 失配(多次救场信号) --- ## 二、es(evalscope)侧问题(代码级证据) 1. **tau2 崩溃**:`tau2_bench/generation.py:118` `openai_chat_choices(include_reasoning=False)` 丢弃 Qwen3 思考通道 → 空 content → 官方引擎 `message.py:116` 校验 raise(temp=0 确定性复现) 2. **gfc 误判**(已帮修):`general_fc_adapter.py` `is_call_tool = finish_reason=='tool_calls'`,vLLM/Qwen3 输出 tool_calls 时 finish_reason 常为 'stop' → FN 虚高。patch:`or bool(tool_calls)` 3. **BCB 执行器空跑**(已帮修):`bigcodebench_adapter.py` `passed = status=='success'`——沙箱命令完成≠测试通过,198/200 空输出判对(0.99 虚高)。patch:unittest 显式 runner + `__BCB_RESULT__ PASS` 标记 → 0.365(与我们 0.375、交叉判分三方一致) 4. **LCB 本地执行全体 -2**:`use_sandbox=false` 本机跑 codegen_metrics 全失败(环境缺依赖);docker sandbox 修复后 0.413 有效 5. **gfc 0.0 失败运行残骸**:断连+空回复产物;重跑 0.456 6. **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)| --- ## 四、方法论教训 1. **分数不一致时先做同题实验**(同 prompt 双跑 / 同题双侧)——hswag 0.2 之谜三小时才定位到两个字符 2. **交叉判分**(A 的输出 × B 的判分器)是分离「判分层差 vs 生成层差」的唯一利器 3. **隔离变量实验**:同题×同 prompt×只换渲染 → 定位 `A.` vs `A)` 4. **no-think 模型对 prompt 字符级格式极端敏感**(22 分级别的摆动) 5. ckpt key 必须覆盖 prompt 语义,否则改模板=白改