本站内容由 AI 生成,可能存在错误。如发现问题,欢迎到 GitHub Issues 反馈。

lm-eval-harness 实操指南

lm-eval-harness 实操指南

更新于 2026-04-23

上一篇 benchmark 生态全景 §6 介绍了 lm-eval-harness 是开源评测生态的核心框架、覆盖 200+ 任务。那篇讲的是”这个工具在生态里的位置”和”为什么值得学”。本篇转向实操:Task YAML 系统如何运作、17+ 模型后端怎么接入、评测结果怎么解读。读完应能跑通一次完整评测、理解每个字段的含义、规避常见坑。

1 三层架构

lm-eval 的设计干净地分成三层,职责互不交叠:

lm-eval 三层架构三层解耦架构Task 层YAML 配置定义 benchmarkMMLUHellaSwagGSM8KHumanEvalModel 层HuggingFace / vLLM / GGUF 适配器hfvllmggufopenaiMetric 层accuracy / perplexity / BLEU / 自定义accpplBLEUexact_match发 prompt返 logprob原始输出评分结果Mix and match: 任意 task × model × metric

1.1 Task 层

YAML 驱动的任务定义。每个 task 就是一个 YAML 文件,用 Jinja2 模板定义 prompt 格式、用 metric_list 指定评估指标。框架内置 208+ 任务,通过 lm_eval/tasks/ 下的目录自动注册。任务可以单独用(如 hellaswag),也可以组合成 group(如 mmlu 包含 57 个子任务)。

1.2 Model 层

统一抽象 LM 基类,定义三个核心方法:

  • loglikelihood(context, continuation):给定 context,计算 continuation 的条件 log-probability。用于 yes/no 判断、真假命题、选择题等概率比较场景。
  • loglikelihood_rolling(string):无条件计算整段文本的 perplexity。用于 WikiText 等纯语言建模评估。
  • generate_until(context, stop_tokens):模型从 context 开始自由生成,直到遇到停止符。用于 GSM8K、HumanEval 等需要生成完整答案的任务。

17+ 后端适配器实现此接口——hf(HuggingFace transformers)、vllmopenai-chat-completionsanthropicopenvinoggufsglangnemo 等。对评测框架来说,模型只是”能回答这三个方法的黑盒”。

1.3 Evaluation Loop

编排层。把 Task 和 Model 粘在一起:

  1. 从 task YAML 读取数据集
  2. 用 Jinja2 模板把每条样本渲染成 prompt
  3. training_split 采样 few-shot 示例拼装
  4. 打包成 request 发给 Model 后端
  5. 收到 logprobs 或生成文本
  6. 执行 filter pipeline(如 CoT 提取)
  7. 计算 metric、聚合、输出 JSON
output_type 决策树模型需要产出什么?output_type 决策树loglikelihood选择题 / 分类MMLU, BoolQgenerate_until开放式文本生成HumanEval, GSM8Krolling perplexity语言建模评估WikiText, Lambada每种 output_type 对应不同的请求构造和评分方式

1.4 output_type 的四种模式

Task YAML 用 output_type 字段告诉 Evaluation Loop 如何构造请求、如何打分。一共四种:

output_type用途典型任务
loglikelihood给定 context,计算指定 continuation 的 logprobBoolQ(yes/no)、真假命题
multiple_choice对每个选项做 loglikelihood,取概率最高者MMLU、HellaSwag、ARC
generate_until模型自由生成,用 regex / exact match / 代码执行判分GSM8K、HumanEval、TriviaQA
loglikelihood_rolling计算整段文本的 perplexity,无 contextWikiText、Lambada
lm-eval 三层架构与请求生命周期
切换 output_type 观察 Evaluation Loop 中变化的环节
multiple_choice|对每个选项做 loglikelihood, 选概率最高
典型任务: HellaSwag, MMLU
task
读取 Task YAML
eval
Jinja2 渲染 prompt
eval
Few-shot 拼装
eval
每个选项构造请求
eval
发送到 Model 后端
model
hf / vllm / ...
model
返回 logprob / 文本
eval
选 logprob 最大选项
eval
Metric 计算 + 聚合
output_type 模式:

上面的组件展示了一个评测请求的完整生命周期,切换底部的 output_type 可以看到 Evaluation Loop 中变化的环节——例如 multiple_choice 会对每个选项各发一次 loglikelihood 请求、generate_until 会多出一个 filter 阶段。

2 Task YAML 深度剖析

以 HellaSwag 为主例,逐字段拆解完整 YAML 配置:

task: hellaswag
dataset_path: Rowan/hellaswag
dataset_name: null
output_type: multiple_choice
training_split: train
validation_split: validation
doc_to_text: "{{query}}"           # Jinja2 模板,从数据集字段渲染 prompt
doc_to_target: "{{label}}"         # 正确答案(多选场景是选项索引)
doc_to_choice: "choices"           # 选项列表字段名
num_fewshot: 0
process_docs: !function utils.process_docs   # Python 预处理函数
metric_list:
  - metric: acc
    aggregation: mean
    higher_is_better: true
  - metric: acc_norm
    aggregation: mean
    higher_is_better: true
metadata:
  version: 1.0

2.1 关键字段详解

  • doc_to_text / doc_to_target / doc_to_choice:Jinja2 模板,可访问数据集任意字段,支持条件、循环等完整 Jinja2 语法。HellaSwag 的 doc_to_text: "{{query}}" 会直接取数据集的 query 字段作为 prompt。
  • process_docs!function 标签引用 Python 函数做预处理。HellaSwag 的实现做了文本清理(去除方括号和标题标记,正则移除括号内容),让 prompt 更干净。
  • metric_list:支持 accacc_norm(长度归一化)、exact_matchbleurougeperplexity 等。可以同时指定多个 metric。
  • num_fewshot:few-shot 示例数。从 training_split 采样,0 表示 zero-shot。
  • filter_list:后处理 pipeline,用于 CoT 等场景。典型管线是先 regex 提取答案再 take_first 选第一个匹配。

2.2 Task Group 与继承

  • Group YAML:用 task 字段列出子任务聚合,aggregate_metric_list 定义聚合方式。例如 MMLU 的 57 个学科子任务按主题打包成一个 group。
  • include 继承:子 task 可以 include: parent_task.yaml 继承父配置,只覆盖差异字段。减少重复配置。

2.3 外部 Task 注册

--include_path /my/tasks/ 自动扫描目录下的所有 YAML 并注册。自定义 task 不需要改源码,只需写 YAML + Python utils(如果需要 process_docs / 自定义 metric)。

Task YAML 字段浏览器
点击左侧带高亮的字段行, 右侧显示该字段的类型、含义和示例
切换示例任务:
task: hellaswag
dataset_path: Rowan/hellaswag
dataset_name: null
output_type: multiple_choice
training_split: train
validation_split: validation
doc_to_text: "{{query}}"
doc_to_target: "{{label}}"
doc_to_choice: "choices"
num_fewshot: 0
process_docs: !function utils.process_docs
metric_list:
- metric: acc
aggregation: mean
higher_is_better: true
- metric: acc_norm
aggregation: mean
higher_is_better: true
metadata:
version: 1.0
点击左侧任意字段行查看说明

组件的三个 tab 展示三种 output_type 的典型配置——HellaSwag(multiple_choice)、GSM8K(generate_until + filter_list)、WikiText(loglikelihood_rolling),点击任意字段查看说明。

3 模型后端接入

挑 5 个最常用后端讲配置。所有后端共用 --tasks--batch_size--num_fewshot--limit 等 CLI 参数,只是 --model--model_args 形式不同。

3.1 hf(HuggingFace transformers)— 参考基线

lm_eval --model hf \
  --model_args pretrained=meta-llama/Llama-3-8B,dtype=float16 \
  --tasks hellaswag --batch_size auto
  • 默认后端,数值结果最权威。其他后端的准确性以 hf 为 reference。
  • dtypefloat16 / bfloat16 / float32
  • parallelize=True:model parallelism(单机多卡跑大模型)
  • accelerate launch -m lm_eval ...:data parallelism(多卡并行跑同一模型、加速)

3.2 vllm — 高吞吐

lm_eval --model vllm \
  --model_args pretrained=meta-llama/Llama-3-8B,tensor_parallel_size=2,gpu_memory_utilization=0.8 \
  --tasks mmlu --batch_size auto
  • 吞吐量远高于 hf,适合大规模评测(200k+ 样本)。
  • 注意:vLLM 的数值可能与 hf 有微小差异(浮点精度、连续批处理策略)。paper-quality 的结果应以 hf 为准。
  • 可以用 scripts/model_comparator.py 对比两个后端输出,定位差异来源。

3.3 openai-chat-completions — API 模型

lm_eval --model openai-chat-completions \
  --model_args model=gpt-4o \
  --tasks mmlu --apply_chat_template
  • 支持 OpenAI 全系、也支持 Anthropic(--model anthropic-chat-completions)。
  • --apply_chat_template:自动把 prompt 封装成 chat 格式。对 RLHF 模型几乎必加。
  • --system_instruction "You are...":注入 system prompt。
  • 注意 rate limit 和 cost:--batch_size 对 API 模型通常设小一点,配合 --num_concurrent

3.4 openvino — Intel 推理

lm_eval --model openvino \
  --model_args pretrained=OpenVINO/llama-3-8b-ov,dtype=int4 \
  --tasks hellaswag
  • 需要 Optimum-Intel 转换过的 OpenVINO IR 格式模型。
  • 支持 INT4/INT8 量化模型的精度评测——量化是否损失准确性,靠这个验证。
  • 在 Intel CPU / iGPU / NPU 上跑量化模型的精度回归测试非常实用。

3.5 gguf — llama.cpp 服务

lm_eval --model gguf \
  --model_args base_url=http://localhost:8080 \
  --tasks hellaswag
  • 通过 llama.cpp server 连接。后端实现文件为 gguf.py,该文件同时注册了 ggufggml 两个后端名(指向同一实现)。
  • 关键坑:必须在 --model_args 里明确 tokenizer,否则 tokenizer reconstruction 可能无限挂起。

3.6 多 GPU 配置

  • Model parallelism(大模型装不下单卡):hf 用 parallelize=True,vllm 用 tensor_parallel_size=N
  • Data parallelism(加速评测):accelerate launch --num_processes N -m lm_eval ...
  • 两者可以组合:大模型 × 数据并行 = tensor_parallel_size × 多进程。

4 结果解读与常见坑

4.1 结果 JSON 结构

{
  "results": {
    "hellaswag": {
      "acc": 0.7523,
      "acc,stderr": 0.0043,
      "acc_norm": 0.7891,
      "acc_norm,stderr": 0.0041
    }
  },
  "configs": { "hellaswag": { "...": "..." } },
  "versions": { "hellaswag": 1.0 },
  "n-shot": { "hellaswag": 0 },
  "n-samples": { "hellaswag": 10042 }
}
  • acc vs acc_normacc_norm 对选项长度做归一化(用 token 数归一化 logprob 之和)。HellaSwag 等选项长度差异大的 task 中 acc_norm 更公平。
  • ,stderr:标准误差。用来判断模型间分数差异是否在统计意义上显著(两个模型差 0.5% 但 stderr 是 0.4%,基本不显著)。
  • --log_samples:保存每条样本的输入、输出、logprob。做 error analysis 必开

4.2 常见坑(7 个)

  1. Base install 无 torch:v0.4+ 基础包不自带 torch / transformers,要单独 pip install lm-eval[hf] 或装你用的后端依赖。
  2. vLLM vs HF 数值差异:浮点精度和连续批处理导致 0.5–2% 分数差异。一篇论文两个模型都用 vllm 可以横向比,但和别人论文里 hf 的数字不要直接比。
  3. GGUF tokenizer 挂起:不显式配置 tokenizer 时 reconstruction 可能无限等待。必须在 --model_args 里写清楚。
  4. SGLang OOM--batch_size auto 在 SGLang 上可能 OOM。手动设 batch size + mem_fraction_static=0.7 更稳。
  5. MPS(Apple Silicon)精度问题:部分算子在 MPS 上与 CPU/CUDA 结果不完全一致。Apple Silicon 上跑的结果要和 CPU 交叉验证。
  6. CoT 模型推理链污染评分:带 <think> 块的模型(如 Qwen-QwQ)直接评分会把思考过程算进去。用 think_end_token 参数剥离推理链后再评分。
  7. Task 版本不匹配metadata.version 变化时分数不能直接对比。新旧 prompt 模板可能差 1–3%。跨版本比较一定要对齐 version 字段。

5 总结

  • 自定义 task 最佳实践:先 --limit 5 跑几条调模板 → --log_samples 看渲染出的 prompt 是否正确 → 确认 filter pipeline 抽出了期望答案 → 正式跑全量。
  • Python APIlm_eval.simple_evaluate() 用于脚本化的评测流水线,适合集成到 CI/CD 做回归测试。
  • 集成--wandb_args project=... 自动上传到 Weights & Biases;--hf_hub_log_args repo_id=... 推到 HuggingFace Hub,方便团队共享。

想看框架在评测生态中的定位,回去看 benchmark 生态全景 §6。想看具体 benchmark 的内容和难度分布,看 推理 benchmark代码 benchmark