一、起因:我对榜单的信任,已经跌到和”网友自我介绍”一个水平了
每次有新模型发布,整个互联网的反应都像过年——
“史诗级!MMLU 92 分!全面超越 GPT-4!”
然后我兴冲冲地接上 API,问了三个问题,吐了三次血。
于是在某个深夜,我盯着又一个”吊打一切”的模型榜单,做出了一个理性的决定:自己写一个跑分工具。
原因很简单,榜单这东西,坑实在太多了:
坑一:分数可能是背出来的。 公开基准一旦进了训练语料,那就不是考试,是默写。你考 MMLU,它背 MMLU,这跟学生提前偷看答案没区别——这叫训练数据污染(contamination)。
坑二:你调的模型可能不是你以为的那个模型。 你付的是满汉全席的钱,端上来的可能是泡面——只不过叉烧是画上去的。API 中转站偷换模型,是真实存在且相当普遍的现象。
坑三:榜单测的,往往不是你要的。 榜单比你关心的”代码能力”,但你的活儿是”写小红书文案”。榜单拿了 95 分,跟你有什么关系?
所以就有了 model-bench——一个用 Go 写的、单文件、双击即用的本地大模型跑分工具。
自己出题、自己判卷、自己说了算。 至于能不能考出真相……我们往下看。
二、先补课:模型测试到底在测什么
在炫耀我的玩具之前,得先把场子热一下。模型评测这玩意儿,看着就是”出题、答题、对答案”,实际上水比想象中深。
2.1 评测的三个段位
| 段位 | 玩法 | 代表 | 人话版 |
|---|---|---|---|
| 青铜:客观题 | 标准答案精确匹配 | GSM8K、MMLU、BBEH | 对就是对,错就是错,没有感情 |
| 黄金:执行型 | 真把代码跑起来 | HumanEval、LiveCodeBench | 不看你吹,看你跑不跑得通 |
| 钻石:主观题 | 让大模型当裁判 | AlpacaEval、Arena | 请一个 AI 来点评另一个 AI,然后祈祷它不偏心 |
成熟的做法是三段位混搭。只考客观题,“写个网页""画张图”这种开放活儿根本没法测;只靠主观题,那评测就成了玄学——这次的 8 分和下次的 8 分,可能不是同一个 8 分。
2.2 几个你迟早要搞懂的指标
- Pass@1:给你一次机会,一次过不过。代码界最爱用的指标——毕竟用户不会给你第二次机会。
- Token:模型世界的计价单位,输入算
prompt_tokens,输出算completion_tokens,你的钱包按这个缩水。 - TTFT(首字延迟):从你发问,到它吐出第一个字。决定”它多久开始打字”。
- TPS(每秒 token 数):它打字的速度。决定”它打字有多快”。
一个模型可以 TTFT 很短(反应神速)但 TPS 很低(一个字一个字往外蹦),体验就像一个抢答特别积极、但说话特别慢的人——着急又无能为力。
- 温度(Temperature):越低越死板,越高越发疯。评测时必须调到接近 0,否则同一道题你问两遍,能收获两个不同的错误答案——这不叫评测,叫抽奖。
2.3 两个能把人逼疯的坑
坑一:请 AI 当裁判,结果裁判是个”追星族”。
用大模型当裁判很省事,但它自带偏见:
- 品牌偏置:它要是知道这答案是 GPT 写的,笔就开始软了。解法:裁判 prompt 里一个字都不许提被测模型的名字。
- 位置偏置:两个答案一比,它永远觉得前面那个更好。解法:把顺序换一下再评一次。
- 自己还发热:裁判也是有温度的,它随机起来比被测模型还离谱。解法:裁判强制
temperature ≈ 0,让它像个没有感情的机器。
model-bench的策略是:三管齐下。裁判 prompt 不提模型名、强制超低温(0.0001)、输出强制 JSON。
坑二:把”话没说完”当成”答错了”。
这一条我踩过,气得想砸键盘。
推理模型(o1、R1 那一类)有个习惯:先在心里默念三千字,最后才说正事。你要是不懂事地把 max_tokens 设成了 2048——很遗憾,它思考还没结束,额度先没了,正文一个字没吐。
这时候你的跑分结果是什么?全线飘红,惨不忍睹。
但模型做错什么了吗?没有。是你的预算太小,让它”想太多,还没开口就晕过去了”。
解法:识别 finish_reason == "length",把它标成配置错误,而不是判它错。这一条,是我认为一个评测工具”讲不讲武德”的分水岭。
2.4 数据污染:题目得比模型的记性”新”
评测题目最好晚于模型的训练截止时间,否则你测的不是智商,是记忆力。
一个体面的题库,会给每道题标上”出生年月”,并且优先挑那些”模型出生之前还没发生的题”。你拿 2023 年的题考 2026 年的模型,它答对了也不值得鼓掌。
2.5 模型测谎:你调的到底是谁?
这是评测之外的另一门手艺:验证你手上的 API 是不是货真价实。
思路是扔一堆精心设计的”探针”进去,收集它的行为指纹:
- 拒答阶梯:给它一串从”人畜无害”到”你确定要问这个”的问题,看它在第几级翻脸。不同家族的脾气不一样,翻脸的位置也不一样。
- 格式口音:项目符号用
-还是*?代码块标不标语言?——这些是模型微调时留下的口音,跟东北人和广东人说普通话的差别一个道理。 - 不确定性习惯:让它估个数,看它爱给整数还是零头。
- 自述交叉验证:直接问它是谁、知识截止到几号,再和行为指纹对一对。
重点来了:单一证据全都不可信。 一个模型说自己是 GPT-4,这句话的可信度,约等于一个网友在自我介绍里写”本人幽默风趣”。
得靠交叉验证 + 明确的置信度。而且结论里必须敢说那句最难说的话——“证据不足,我不知道。“
三、登场:model-bench 长什么样
3.1 一句话介绍
一个用 Go 写的、单文件、双击就用的本地大模型跑分工具:内置六大维度题库,能客观判分、能跑代码沙箱、能请 AI 当裁判,还附赠一个”测谎仪”。
3.2 为什么这么选技术栈
| 部分 | 选型 | 心里话 |
|---|---|---|
| 后端 | Go 1.26 | 编成一个 exe,甩给别人就能用,不用教他配环境 |
| 数据库 | SQLite + GORM(纯 Go 驱动) | 免 CGO、零配置,数据跟着 exe 走,体面 |
| 前端 | Vue,go:embed 塞进二进制 | 一个文件搞定,用户不需要知道 Node 是什么 |
| 题库 | 内嵌 JSON | 离线可用,题库跟程序版本锁死,不会对不上 |
| 限速 | x/time/rate | 令牌桶控 RPM,防止把人家 API 薅秃 |
build.bat 两步走:前端 build → Go embed → 吐出一个 model-bench.exe。双击,自动开浏览器,开跑。 没有安装向导,没有依赖地狱,没有”请先阅读 500 页文档”。
3.3 架构一览
main.go
└── api HTTP 路由、SSE、静态托管
└── runner 测试执行引擎(队列 / 并发 / 取消 / 落库)
├── llm OpenAI 兼容客户端(限速 / 重试 / 超时)
├── judge 判分器(按题型分发)
└── sandbox Python 代码沙箱
└── audit 模型鉴定引擎(94 支探针 + 行为指纹)
└── dataset 题库加载
└── store GORM / SQLite3.4 六大维度:一次全方位体检
| 维度 | 中文名 | 题目出处 | 题量 | 判法 |
|---|---|---|---|---|
reasoning | 逻辑推理 | BBEH | 20 | 对答案 |
math | 数学能力 | AIME 2026 | 30 | 对答案 |
coding | 代码能力 | USACO 2025-26 赛季 | 15 | 真·跑代码 |
knowledge | 知识问答 | HLE 纯文本子集 | 20 | 对答案 |
chinese | 中文能力 | Chinese-SimpleQA | 20 | 对答案 |
frontend | 前端生成 | 自建 | 10 | 请裁判 |
一共 115 题。其中推理、数学、代码三个维度完全不依赖裁判——这构成了”免裁判模式”:你要是懒得配裁判模型,也能把它按在地上摩擦。
3.5 数据模型:六张表撑起一切
ModelConfig:模型配置。用Role区分test(考生)和judge(阅卷老师)。任何 OpenAI 兼容端点都能接,不挑食。TestRun:一次考试。状态机pending → running → completed/cancelled/failed,记录进度和总分。QuestionResult:每道题的答卷。原始答案、提取后的答案、对错、评语、耗时、token 用量,全都留着。DimensionScore:各维度成绩单。FunResult/AuditResult:趣味测试和测谎报告。
四、判分引擎:这才是技术活儿
模型不会乖乖配合。它不会只写一个”B”,它会先抒情五百字,再在最后一句把答案藏着掖着。所以判分的第一要务,不是”比答案”,而是”把答案从废话里刨出来”。
4.1 六种题型,六种打法
switch q.Type {
case "choice": // 选择题
case "numeric": // 数值题
case "short_answer": // 短答案
case "code": // 代码(丢进沙箱真跑)
case "qa_judged": // 裁判评分问答
case "frontend_html": // 前端生成(请裁判)
}4.2 绝活:三级退化提取
以选择题为例。模型可能回:
“经过我严谨的分析……(此处省略八百字)……综合来看,答案是 B。” 或者更离谱,就回一个孤零零的字母:“B” 或者回:“(B)”
所以提取要层层兜底,一条路走不通就换下一条:
- 先找”答案是 B""answer is B”这种明示的;
- 找不到就从结尾往前翻,找一个孤零零的字母(还得容忍它后面跟着个句号);
- 再找不到,就全文捞出最后一个独立的大写字母,赌一把。
数值题同理,“刨答案”的顺序是:#### 42(GSM8K 祖传格式)→ \boxed{42}(LaTeX 风格)→ “答案是”关键词 → 兜底取全文最后一个数字(赌徒做法,但经常能赌赢)。刨出来再做浮点比较,容差 1e-6;实在比不了就退化成字符串裸比。
短答案题更狠,四路比对:直接比 → 数值比 → 词表比。最后这个是给”排序题”准备的——只要元素集合一样,你排的顺序对不对、用逗号还是空格,我全当没看见。
4.3 代码判分:不看你吹,看你跑不跑得通
代码题光比字符串是耍流氓,必须真执行。项目上了两套:
① LiveCodeBench 风格(标准输入输出)——正经做法:
# 逐个测试用例跑 solution.py,比对输出
for i, t in enumerate(tests, 1):
p = subprocess.run([sys.executable, "solution.py"], input=t["input"], ...)
if p.returncode != 0: print(f"RUNTIME_ERROR_ON_TEST_{i}"); exit(1)
if p.stdout.strip() != t["output"].strip(): print(f"WRONG_ANSWER_ON_TEST_{i}"); exit(1)
print("ALL_TESTS_PASSED")全部用例通过、且屏幕上打出 ALL_TESTS_PASSED,才算这题过关。
② HumanEval 风格(断言式)——保留兼容:把解题函数和测试断言拼一块儿,让断言自己喊”我过了”。
4.4 沙箱:跑陌生代码,得先把笼子锁好
跑一个陌生模型生成的代码,性质等同于捡起路边一个来路不明的 U 盘插电脑上。必须沙箱。
sandbox 包干了四件事,每一件都是被现实教育出来的:
- 临时目录隔离:每次执行建个临时文件夹,跑完就删,来无影去无踪。
- 超时强杀进程树:Windows 下 Python 启动器会偷偷派生子进程,普通 kill 杀不干净,所以直接祭出
taskkill /T /F——连根拔起,绝不手软。 - 堵住
input():把标准输入接成一个空管子,防止程序卡在那儿等一个永远不会来的输入,像个等人回消息的舔狗。 - 自动找 Python:依次试
python → py -3 → python3,全都没有就把这个维度标记为”无法判分”,而不是当场崩溃。
4.5 前端生成:请裁判,但先把丑话说在前面
前端任务没有标准答案,只能请裁判。但项目把丑话说在了前头:
- 先做静态体检:从回复里刨出 HTML,要是长度不到 100、或者连个
<都没有,直接判 0,裁判的调用费省了。 - 四个维度打分:结构、需求符合度、美观、交互,各 0
2.5 分,合计 010。 - 不信裁判的算术:要求它输出 JSON,但每一项都做边界裁剪(
clamp(0, 2.5)),而且总分由代码自己重算——怕裁判自己算错总分(它真的会)。 - 解析失败就重问一次。
- 全程不提被测模型的名字,防追星。
4.6 趣味测试:鹈鹕与糖果
严肃评测之外,藏了两个社区名场面,用来一眼感受模型的”手感”:
- 鹈鹕测试:让模型生成”一只鹈鹕骑自行车”的 SVG。这个 prompt 由 Simon Willison 在 2025 年带火,堪称 AI 圈的祖传考试——考验的是空间组合想象力和 SVG 生成功底。生成的 SVG 还会被丢进 iframe 沙箱预览,外加
Content-Security-Policy禁脚本,双保险,防止它偷偷藏点恶意脚本。 - 糖果测试:一道精心设计的抽屉原理题,答案是 21。题面里塞了个”西瓜味”当干扰项,所有最坏情况都停在 20 颗,第 21 颗必中。专治”看到数字就上头”的模型。
4.7 测谎仪:94 支探针伺候
audit 包是个完整的换模检测引擎,探针库来自开源项目 BazaarLink LLMprobe-engine(AGPL-3.0):
- 五大组:身份识别、子模型识别、安全探针、完整性检查、品质测试。
- 多种判法:精确匹配、关键词(支持正则)、特征采集、token 计量校验(防虚报用量)、流式检测、长上下文金丝雀。
- 性能实测:TTFT 采样 8 次取中位数;TPS 用一段 400 字长文现场计时。
- 结论分级:一致 / 家族相符 / 疑似换模 / 资料不足 / 模棱两可。
最精彩的场景:API 名字写着 A 家族,行为指纹却指向 B 家族——妥妥的”嫌疑人”,直接标”疑似换模”。
而且报告末尾老老实实写着免责声明:“相符 ≠ 保证没换过,单次通过 ≠ 长期诚实。”
一个工具最性感的品质,不是它宣称自己多准,而是它知道自己哪里不准。
五、分数怎么算:克制,再克制
分数怎么算,直接决定榜单能不能信。这里的规则,朴素得近乎无聊——因为我怕它花里胡哨地骗我。
5.1 维度分
普通维度:答对题数 ÷ 有效作答数 × 100
frontend:裁判均分 ÷ 10 × 100注意分母是”有效作答数”,把超时、被截断、API 报错全踢出去了。否则你家网络一抖,模型就无辜掉分——这锅模型不背。
5.2 综合分
综合分 = 所有"有效维度"(answered > 0)的等权平均两个讲究:
- 等权:不搞加权,省得被人说”你这就是为了抬高某家分数才这么配权重的”。
- 没测出来的维度不参与:某个维度全军覆没(比如没装 Python),它就不进平均,而不是拿 0 分去拖累总分。要是所有维度都没数据,综合分就是
NULL——坦坦荡荡地显示”没测出来”,而不是拿个 0 假装很严格。
5.3 排行榜
每个模型只取最近一次成功完成的考试成绩。重测就覆盖旧成绩,不会出现同一个模型霸占半个榜单的奇观。
六、执行引擎:并发、进度、以及一个怕抢锁的程序员
上百道题,串行太慢,无脑并发又会被 API 限流教做人。
6.1 生产者—消费者
生产者(投喂题目)──► jobs 队列 ──► N 个 worker(问模型 + 判分)──► resultsCh ──► 单个 writer(落库)- 并发数:默认 4,上限 8。并发开太高,只会被 API 限流揍得更惨。
- 单题超时 180 秒:给推理模型留足”思考人生”的时间。
- 裁判超时 120 秒:单独控制。
6.2 为什么只留一个收银员
SQLite 的写入是串行的,几个 goroutine 同时写就会撞锁。所以设计成:一群 worker 只管干活,结果全丢给一个 writer,排队落库。
这是一个非常典型、也非常香的取舍——用架构消灭锁竞争,而不是用锁去硬刚。
6.3 实时进度:SSE 直播
前端用 SSE(Server-Sent Events) 实时收进度,不用轮询。事件有:
progress:每做完一题推一次dimension_done:某个维度全部完成,推该维度得分run_done/error:收工或翻车
于是你能亲眼看着雷达图一个角一个角地长出来——这大概是我做过最有仪式感的进度条。
6.4 一些”不性感但救命”的细节
- 同时只允许一场考试,免得几场跑分互相抢 API 额度。
- 随时可取消,已做完的题目结果照样留着。
- 指数退避重试:撞上
429 / 5xx / 网络抖动,就按2s → 4s → 8s道歉式重试,最多 3 次;至于4xx这种”你请求本身就有问题”的,直接失败,别浪费重试。 - 令牌桶限速,按你配的
RPMLimit走,优雅地控制请求节奏。
七、上手:三步开始折磨模型
:: 1. 构建(需要 Node + Go 环境)
build.bat
:: 2. 运行(双击或命令行)
model-bench.exe启动后浏览器自动打开 http://127.0.0.1:8080。开整:
- 在模型管理里加一个被测模型——填
BaseURL、APIKey、ModelName,任何 OpenAI 兼容端点都欢迎; - 想跑”前端生成/裁判类”维度时,再加个裁判模型(建议请个聪明点的当考官);
- 新建运行、勾维度、开跑,欣赏雷达图慢慢长出来。
环境变量(想改就改):
| 变量 | 默认值 | 说明 |
|---|---|---|
BENCH_PORT | 8080 | 监听端口 |
BENCH_DB | model-test.db | 数据库文件 |
BENCH_LOG_DIR | logs | 日志目录 |
八、题库从哪来:新,且诚实
题库不是随便扒的,每个数据集都标了来源和许可证(dataset/LICENSES.md)。核心原则两条:能新则新,不能新就老实交代。
- 数学用 AIME 2026、代码用 USACO 2025-26 赛季——都是模型出生之后才发生的真·新题,天然抗污染。
- **推理(BBEH)、中文(Chinese-SimpleQA)**是”如实标注的遗留项”——没有更新的同类基准,就承认,而不是硬装它很新。
- 知识维度换成 HLE 之后难度飙升,前沿模型也只能拿 10~30 分。分数掉下来是正常现象,别慌——维度之间的相对比较依然有意义。
抗污染的真谛,不是找一个”永不过期”的题库——那种东西不存在。 而是清楚知道自己的题是新是旧、脏不脏,然后在结论里如实说出来。
九、写在最后:几条血泪心得
做完这个项目,有几句话想留给同样对模型评测感兴趣的同行:
- 能用客观判分就别用裁判。 精确匹配 > 跑代码 > 请 AI 评分。LLM 裁判是最后手段,不是第一选择——请神容易送神难,请个追星的裁判更难。
- 把”配置错误”和”模型答错”分开。 被
max_tokens截断、超时、API 报错,这些不是模型的锅。一个评测工具的公信力,全建立在这种细节的较真上。 - 分数要敢为空。 没测出来就显示”空”,别拿 0 分糊弄。0 分和”没数据”是两码事,混为一谈就是耍流氓。
- 承认能力边界。 换模检测不可能 100% 准,那就明说。越是敢承认自己局限的工具,用户越敢信它。
- 工程细节决定生死。 单文件分发、自动找 Python、SSE 直播进度、优雅退出……这些不性感的活儿,才是一个工具能不能被真正用起来的关键。漂亮的功能让人惊艳,扎实的细节让人留下。
评测这件事,说到底就是在回答一个问题:
“这个模型,到底行不行?”
而一个负责任的答案,从来不是一句”它得了 92 分”,而是——
“在你关心的场景下,用可复现的方法、抗污染的题,测出来它表现如何……以及,我有多大把握。”
至于那个 92 分?先让它在我这儿考完再说。
model-bench 是个本地自用的小工具,结构清晰、依赖极少,如果你想练手,是个相当不错的选择。祝你和 API 的每一次对话,都不会被”疑似换模”温暖。
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时





