
一次模型配置升级把完整计划的匹配数从 15/24 提高到 19/24,升级门禁却拒绝了它:5 个检查回归,5 个可靠性下降。EvalArc 仓库的《Your first model-upgrade review》把这个判定整理成一条可复现的评审路径:装固定版本的工具,下载固定版本的记录,自己重算结论,全程不需要克隆仓库、GPU 或模型 API。本文按这份文档(固定在 commit 6af26bf)讲怎么做这次评审,退出码和报告字段各说明什么,以及结论能推到哪里为止。
本站的 EvalArc:分数变高了,智能体哪里退步了 已经介绍过这个工具和这组对照的结论。本文不再做工具介绍,只讲评审的操作步骤和读结果的方法。下文数字都来自项目自己的文档和它保存的记录([O],作者自报的开发示例),不是第三方的独立评测。
这次升级比较了什么
基线是 Qwen/Qwen3-8B(BF16),当前配置是 Qwen/Qwen3.8-27B-FP8。8 个公开的客服规划用例,每个用例、每种配置各新生成 3 次,48 个回答全部保留。计划只比对,不执行。每个回答跑 5 个 pytest 检查:JSON 结构,路由调用,备注内容与重试键,关闭条件,完整的有序计划。每一侧因此有 5 × 24 = 120 个检查结果。
按用例和检查配对,一共 40 项:10 项改善,20 项不变,5 项回归,5 项可靠性下降。后面这 10 项阻断了升级。它们只对应两个用例的格式或结构问题,不是 10 个独立原因:
already-closed:5 个检查全部从 3/3 降到 0/3。以 seed 17 为例,基线返回{"actions":[]},当前模型返回{},漏掉了必需的actions键。resolved-bug:5 个检查从 3/3 降到 1/3,原因是有两个回答把 JSON 包在了 Markdown 代码块里。
检查之间并不独立:JSON 一旦无效,这个回答的所有检查都会失败。所以阻断检查的个数不能直接当作问题的严重程度,要回到原始回答里看具体出了什么错。
不用 GPU 复现这个判定
需要 Python 3.11+ 和一个新目录,文档给出的命令用 Linux/macOS 上的 Bash。先装发布版:
mkdir evalarc-model-first-review && cd evalarc-model-first-review
python3 -m venv .venv
.venv/bin/python -m pip install evalarc==0.15.0
接着下载 v0.15.0 发布附件 evalarc-model-upgrade.zip。文档里的下载脚本先核对 SHA-256,一致才解压,并保留原始文件;哈希不一致就退出,不解压。期望值是 6b1b9212d5b25da281c54f192489298af7a0b0f4524f807ea6e2821bfa6f515c,发布页的 SHA256SUMS 里也有这个值。解压后打开 recorded-review/index.html,可以离线浏览全部 48 个原始回答。
.venv/bin/python - <<'PY'
import hashlib
import io
from pathlib import Path
from urllib.request import urlopen
from zipfile import ZipFile
url = "https://github.com/noteflowai/evalarc/releases/download/v0.15.0/evalarc-model-upgrade.zip"
expected = "6b1b9212d5b25da281c54f192489298af7a0b0f4524f807ea6e2821bfa6f515c"
with urlopen(url, timeout=60) as response:
data = response.read()
if hashlib.sha256(data).hexdigest() != expected:
raise SystemExit("Archive checksum differs; do not extract it.")
Path("recorded-review").mkdir()
with Path("review.zip").open("xb") as output:
output.write(data)
with ZipFile(io.BytesIO(data)) as archive:
archive.extractall("recorded-review")
PY
然后重算升级判定:
.venv/bin/evalarc diff recorded-review/reports/baseline.xml \
recorded-review/reports/current.xml --output my-review
结果看 my-review/index.html 或 my-review/diff.json。在记录的浏览器视图里选 already-closed、seed 17,就能看到上面那对回答。这一步只比较保存下来的原生测试结果(JUnit XML),既不重新生成回答,也不重跑检查。
退出码和 gate_passed 分别说明什么
这份文档里有两个容易读错的退出码:
| 命令 | 做了什么 | 预期退出码 | 含义 |
|---|---|---|---|
evalarc diff | 比较已保存的 JUnit 结果 | 1 | 比较正常完成,门禁拒绝了升级 |
regrade.py | 对每个保存的回答重新执行 5 个检查,写出新的 JUnit,再重算比较 | 0 | 脚本做完了;regraded/comparison.json 里仍是 "gate_passed": false |
也就是说,diff 的退出码 1 是一个结论,不是运行出错。regrade.py 的退出码 0 只说明脚本跑完了,不说明升级通过。这份入门文档只给出预期的退出码 1 和 0;门禁规则和输入格式见 examples/results-diff/README.md。重新评分后要读 gate_passed 字段,不能只看进程有没有报错。
可选的重新评分要装记录时用的 pytest 版本,每次换一个新的输出目录:
.venv/bin/python -m pip install pytest==8.4.2
.venv/bin/python recorded-review/regrade.py --output regraded
这一步同样不调用任何模型。评分器只脱敏两类元数据:JUnit 里的主机名,以及 stdout 里的绝对报告目录。脱敏前后的哈希都有记录,模型回答和检查结果一律不改。
把它用到自己的升级评审
文档对“换成自己的变更”只提了几条要求:两次运行的用例 ID、检查名和被评估的契约要对齐;每一次尝试都要保留;把原生结果交给 evalarc diff。输入格式和门禁规则在仓库的 examples/results-diff/README.md。这个示例的实验说明里还有几条做法可以照搬:
- 先定协议,后出结果。
protocol.json在任何完成的回答产出之前就已提交,期望计划独立于生成的回答单独保存。 - 两侧条件一致。两个模型用同样的客服规则(policy)、用例文本、采样设置和 seed:seed 17、29、43,temperature 0.6,top-p 0.9,top-k 20,关闭 thinking,输出上限 512 token。录制硬件是 NVIDIA L40S。
- 把加载时做过的修正写进记录。Transformers 5.17.0 的正则前缀匹配会把检查点里并不存在的 dense
mlp.gate路由排除项,误当成gate_proj的排除项,于是丢掉它的 scale 张量。FP8 记录器在内存里去掉这些路由条目,并要求检查点的每个键都有着落。具体调整和加载报告写在 identity 文件里,没有产出回答的初始化失败记在runtime-failures.json。 - 失败的回答也留着。10 个阻断检查背后的那些回答全部保存在记录里,没有清洗。
局限 / 注意
- 这是一个小型开发示例,不是模型排行榜。两个配置的架构、参数量和量化都不同,差异不能归因于某一个因素,也不能用来推算模型的总体能力。
- 用例是挑选出来的公开开发用例,可能出现在训练数据里;没有做去污染,也不给出总体准确率。
- 完整计划相等只是一项很窄的契约检查,不证明真实的客服任务已经完成;计划也没有执行。
- 每个用例的 3 次生成是 3 次独立采样,不是对同一个回答评了 3 次,作者也没有把它当作一般意义上的 Pass^k 估计。
- 默认的复核步骤只能证明“从保存的结果算得出这个判定”,
regrade.py也只是对保存的回答重跑检查,都不是重新生成回答的独立复现。换了硬件、内核或库版本,采样结果可能不同。记录里的耗时只是诊断用的观测值,不能当吞吐量比较。 - 本文依据的是固定在 commit
6af26bf的文档和evalarc==0.15.0,更新的版本在命令或输出上可能不同。



