llama.cpp llama-eval:面向多服务器并发的 LLM 评测工具实践(多数据集、可插拔 Grader、断点续跑)
examples/llama-eval/ 是 llama.cpp 仓库内置的 HTTP 评测工具集:它以 Python 客户端身份对接 llama-server 的 OpenAI 兼容接口,对数学与科学推理数据集(AIME 系列、GSM8K、GPQA)进行批量评测,内置正则/脚本/LLM 三种评分器、多服务器共享任务队列并发、Wilson 置信区间统计与逐案例增量落盘能力。读完本文,你能复现完整的评测命令、理解并发与断点续跑的实现细节,并会用仓库自带的 llama-server-simulator 在无模型环境下验证整条评测链路。
一、工具组成与总体数据流
评测目录包含三个文件(见 examples/llama-eval/):
- llama-eval.py:评测主程序,负责数据集加载、任务调度、请求发送、答案抽取与评分、结果落盘;
- llama-server-simulator.py:一个可配置正确率的假
llama-server,用于在没有真实模型/显卡时验证评测脚本本身; - test-simulator.sh:针对 simulator 的端到端自检脚本。
主程序的执行链路为:加载数据集 → 按 seed 生成任务列表 → 逐个服务器探活 → 多线程并发请求 /v1/chat/completions → Grader 抽取并比对答案 → 每完成一个案例就增量 dump JSON/HTML 状态 → 结束输出带置信区间的汇总。整个过程中每个案例的 prompt、期望答案、模型响应、抽取得到的答案、token 数与生成速度都会被记录,方便事后逐题复盘。
README 中还指向了上游 PR #21152 的完整设计讨论与样例结果,可结合当前源码对照阅读。
二、快速上手(Quick Start)
README.md 给出两类最小命令。
单服务器评测 GSM8K 前 100 题,使用正则评分器,单服务器 32 并发:
python3 llama-eval.py \
--server http://localhost:8033 \
--model my-model \
--dataset gsm8k --n_cases 100 \
--grader-type regex --threads 32
多服务器评测 AIME 2025 全量 240 题(逗号分隔 URL 与各自并发数):
python3 llama-eval.py \
--server http://server1:8033,http://server2:8033 \
--server-name server1,server2 \
--threads 16,16 \
--dataset aime2025 --n_cases 240 \
--grader-type regex
几点参数含义(与 llama-eval.py 的参数解析 对应):
--server:一个或多个llama-server地址,逗号分隔,默认http://localhost:8033;--threads:每个服务器分配的工作线程数,逗号分隔,数量必须与--server一致,否则直接报错退出;--server-name:可选,用于结果中标记案例由哪个服务器完成,缺省回退为 URL 本身;--model:请求体中的model字段,也用于校验断点续跑时的模型一致性;--n_cases:评测题数,超过数据集大小时会分多轮(chunk)循环取题;--grader-type:regex/cli/llm,默认llm。
三、内置数据集与提问模板
EvalState.load_dataset 支持 5 种数据集(llama-eval.py#L178-L190):
--dataset 取值 |
数据来源 | Split / 配置 | 说明 |
|---|---|---|---|
aime |
AI-MO/aimo-validation-aime |
train | 经典 AIME 验证集,答案字段做数字归一化 |
aime2025 |
opencompass/AIME2025 |
AIME2025-I + AIME2025-II 两个配置拼接,split=test |
即 2025 年 I/II 卷合计 30 题 |
aime2026 |
MathArena/aime_2026 |
train | 2026 卷 |
gsm8k |
openai/gsm8k(main) |
test | 加载时从 answer 的 #### 后缀中预先抽取数字标准答案(Gsm8kDataset._load_dataset) |
gpqa |
OpenAI 公开的 gpqa_diamond.csv |
— | 加载时按固定 seed 打乱四选一选项并记录正确字母(GpqaDataset) |
数据集统一缓存到 ~/.cache/huggingface/datasets,脚本在启动时强制设置 HF_DATASETS_CACHE 并禁用 Hub 遥测(llama-eval.py#L39-L42),重复运行时直接使用缓存。
提问模板集中在 TEMPLATE_REGISTRY(llama-eval.py#L79-L110):AIME 系列与 GSM8K 都要求模型把最终数值答案写进 \boxed{} 中,GPQA 则要求末行输出 Answer: A/B/C/D。模板与评分器是配套的——正则评分正是依赖这种格式约定才能可靠地抽取答案。
任务 ID 的格式为 {dataset}_{chunk_idx:03d}_{problem_idx:03d}(llama-eval.py#L210),例如 gsm8k_000_042;--n_cases 大于数据集长度时,chunk 号会递增,意味着同一道题会被多轮重复评测,这也是 HTML 报表能按题聚合多次运行统计的原因。
四、三种 Grader:regex、cli、llm
评分逻辑封装在 Grader 类(llama-eval.py#L977-L1114),入口为 grade(),按 --grader-type 分派。
1. regex(离线、零成本,README 示例默认用它)
各数据集的正则模式定义在 GRADER_PATTERNS(llama-eval.py#L44-L49),AIME 系列与 GSM8K 均为 \boxed{(\d+)}|\b(\d+)\b 一类的数字抽取。抽取策略有两层特殊处理(_extract_answer_regex):
- AIME 系列优先用
\boxed{...}模式,并取最后一个 boxed 结果(最终答案通常出现在推理末尾); - 其他数据集则从文本末尾向前取数字,使“最后出现的数”优先于推理过程中的中间数字。
抽取结果与 gold 做字符串相等比较。另外注意:送入评分的响应先经 _truncate_response(..., max_lines=10) 截断为最后 10 行(llama-eval.py#L1211),避免超长推理文本干扰匹配。
2. cli(自定义评分脚本)
调用外部脚本,参数固定为 --answer <模型答案> --expected <标准答案>,以退出码 0 判对,超时 30 秒(_grade_cli)。适合容差比较、单位换算、多解判分等正则难以表达的场景。需要配合 --grader-script 指定脚本路径,脚本不存在会直接抛错。
3. llm(默认,用模型抽取答案)
向 Grader 服务器发送一个 few-shot 抽取请求:system prompt 附上 SAMPLE_ANSWERS 中的示例答案(如 "42"、"-123"、GPQA 的 "A"/"D"/"C"),要求模型只输出抽取出的答案本身,temperature=0;随后与 gold 忽略大小写比较(_grade_llm)。
相关约束(来自 main() 的参数校验):
llm评分必须提供--grader-model或--model,否则报错退出;--grader-server缺省为第一个主服务器,此时会打印“正在用同一台服务器做 LLM 评分”的警告;- GPQA 数据集强制要求
--grader-type llm(选项内容本身是自由文本,数字正则无法覆盖),见 llama-eval.py#L1488-L1491。
五、完整命令行参数速查
以下默认值与取值范围均来自 llama-eval.py 的参数定义:
| 参数 | 默认值 | 说明 |
|---|---|---|
--server |
http://localhost:8033 |
逗号分隔的服务器 URL 列表 |
--server-name |
空(回退 URL) | 逗号分隔的服务器显示名,数量须与 --server 一致 |
--dataset |
aime |
aime / aime2025 / aime2026 / gsm8k / gpqa |
--n_cases |
全部题目 | 评测题数;超过数据集大小则分 chunk 循环 |
--seed |
1234 |
题目洗牌随机种子(GPQA 选项打乱也用它) |
--n_predict |
-1 |
每题最大生成 token 数,-1 表示不限制 |
--temperature / --top-k / --top-p / --min-p |
不传 | 采样参数,仅在你显式给出时才会写进请求体 |
--threads |
32 |
每服务器并发线程数,逗号分隔 |
--model |
无 | 请求的 model 名,并用于断点续跑一致性校验 |
--verbose |
关 | 每案例打印期望/响应/抽取答案/状态 |
--output |
llama-eval-state.json |
状态文件路径(HTML 报表同名加 .html) |
--grader-type |
llm |
regex / cli / llm |
--grader-script |
无 | cli 评分器脚本路径 |
--grader-server |
第一个主服务器 | LLM 评分器地址 |
--grader-model |
--model |
LLM 评分器使用的模型名 |
--resume |
关 | 基于已有状态文件续跑未完成案例 |
请求体的组装在 _make_request:固定包含 model、单条 user 消息(即数据集模板渲染后的 prompt)与 n_predict;四个采样参数则遵循“未指定则不发送”的原则,由 llama-server 侧使用自身默认值,避免评测工具无意覆盖服务端采样配置。
六、并发架构:共享任务队列 + 每服务器独立线程池
多服务器并发的核心在 Processor.evaluate()(llama-eval.py#L1261-L1343):
- 服务器探活:对每个服务器 GET
/v1/models,任一不可达则打印错误并退出,同时打印各服务器挂载的模型清单; - 共享任务队列:所有任务先入一个
Queue;为每个服务器创建独立的ThreadPoolExecutor(max_workers=该服务器线程数),其 worker 从同一个队列抢任务(_worker)。这意味着快服务器会自然多抢活,负载是动态均衡的,而非按题均分; - 优雅退出:队列尾部放入“总线程数个 None 哨兵”,每个 worker 取到哨兵后自行结束;
- 结果回收:worker 把
TaskState推入结果队列,主线程实时打印进度行(序号、任务 ID、期望答案、抽取答案、tokens、t/s、生成耗时、对错、当前正确率、服务器名); - 终止原因校验:若响应
finish_reason不是stop(例如达到n_predict上限被length截断),该案例直接标记error: finish_reason=...,不进入评分,避免把截断回答误判为错答(llama-eval.py#L1200-L1209)。
进度行与报表还会记录 reasoning_content(思考型模型的推理内容)与 usage.completion_tokens、timings.predicted_per_second 等指标,性能信息随正确性一起入库。
七、状态持久化、断点续跑与置信区间
增量落盘:EvalState.dump() 在每完成一个案例后被调用(llama-eval.py#L1233),把 id、model_name、任务列表、各案例的完整记录(prompt/expected/response/answer/grader_log/tokens/tps/t_gen/服务器/chunk 号)与聚合统计写入 --output 指定的 JSON,并同时生成同名 .html 报表。因此进程中途被 kill 也不会丢失已完成案例。
断点续跑语义(main 中的状态恢复分支):
- 若
--output文件已存在,程序先加载并打印全部任务与已有汇总; - 若所有案例都已
ok,直接结束; - 未完成且未加
--resume:提示“Run with --resume to continue”并退出; - 加
--resume后仅对status != ok的 pending 任务重新评测,历史结果保留; - 恢复时会校验
--model与状态文件中记录的模型名一致,不一致直接报错,防止把两个模型的案例混进同一份报表。
统计口径:汇总与 HTML 头部的正确率均附带 Wilson 95% 置信区间(wilson_interval,z=1.96,llama-eval.py#L29-L37),终端输出形如 Results: 87/100 correct (87.0%) [79.6%, 92.4%]。相比裸频率,小样本下(例如 AIME 30 题)这一区间能直观呈现结果的波动幅度。
八、输出产物:JSON 状态与 HTML 报表
JSON(llama-eval-state.json) 结构见 dump():顶层含 id(数据集)、model_name、tasks、sampling_config;task_states 内含 total、correct、total_time、ci_lower/ci_upper 及 cases 字典;未跑到的任务会以 status: "pending" 占位,这是断点续跑的数据基础。
HTML(llama-eval-state.html) 由 dump_html() 生成,包含:
- 顶部信息栏:数据集、模型、正确率及置信区间、正确/完成数、pending 数、总耗时、采样参数串;
- Detailed 页签:每行一个任务(ID、对错符号、Gold、抽取答案、tokens、t/s、生成秒数、服务器),点击行展开该案例的 Prompt、Response、Reasoning 与 Grader 日志;
- Summary 页签:按
problem_idx聚合,给出每题的 Run 次数、正确次数,以及 tokens / t/s / 生成秒数的 min-avg-max,适合评估同一道题多轮采样(chunk 循环)的稳定性。
九、llama-server-simulator:没有模型也能测链路
llama-server-simulator.py 用标准库 HTTPServer 实现了一个假 llama-server,暴露与真服务器相同的两个端点:GET /v1/models 和 POST /v1/chat/completions(RequestHandler)。它的行为(Simulator):
- 从 AIME/AIME2025 数据集中载入真实题目,收到请求后先剥离模板前缀,再通过子串匹配、去 LaTeX 匹配、以及基于 bigram 的 Dice 系数(阈值 0.3)在题集中定位对应题目;
- 按
--success-rate概率返回正确数字答案(否则返回“答案+1”的干扰答案),并伪造 OpenAI 风格的usage(10 万级 completion tokens)与timings(90–110 t/s)字段——正好覆盖评测脚本会读取的全部字段; - 未匹配到题目时返回
{"error": "No matching question found"},模拟服务端异常分支; - 调试日志写入
/tmp/simulator-debug.log。
启动参数(main):--port(默认 8033)、--host(默认 localhost)、--success-rate(默认 0.8)、--dataset(aime/aime2025)、--dataset-split(默认 train)。
test-simulator.sh 以 80% 正确率启动 simulator 后做四类断言:已知题目应返回标准答案 116、无匹配输入应返回错误 JSON、连续 10 次请求的正确数应落在 8/10 附近,用于验证随机正确率的行为符合预期。典型用法是先在 simulator 上调通 llama-eval.py(参数、评分器、断点续跑),再原样指向真实 llama-server 运行正式评测。
十、运行依赖与适用边界
- Python 依赖:
requests(HTTP 客户端)、datasets(HuggingFace 数据集加载,仅首次需要联网下载)、tqdm;GPQA 额外依赖pandas(直接从 OpenAI 公开 CSV 加载)。建议先建虚拟环境安装后再运行,test-simulator.sh中也体现了venv的用法(test-simulator.sh#L16); - 被评对象:任何暴露
/v1/chat/completions的llama-server实例,支持多实例横向并发; - 评测性质:这是基于 HTTP 的黑盒评测,指标只有最终答案正确率(附置信区间)与附带吞吐统计,不覆盖逐 token 对数似然、perplexity 等指标——仓库内的
llama-bench、llama-perplexity等工具覆盖的是另一侧的性能/困惑度测量,两者可互补使用; - 已知约束:GPQA 必须用 llm 评分器;regex 评分依赖模型遵循
\boxed{}输出约定(模板已内置该指令);--n_predict -1时若模型不自行停止,长回答可能受服务器侧上限约束。
综上,examples/llama-eval/ 提供了一条“数据集 → 并发推理 → 多策略判分 → 可断点、可复盘报表”的完整评测流水线,其设计特点(每案例增量落盘、服务器动态抢活、finish_reason 校验、Wilson 区间)都直接体现在源码中,可作为自建 LLM 评测脚本的参考实现。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00