跳到主要内容

数据截至 (上游 commit 932e1f2f4c5a)

Lighteval — 架构与原理

30 秒导读: Lighteval 是 HuggingFace 的模型评测框架——评的是 LLM 本身(不是 agent 系统)。你给它一个模型 + 一串任务名,它把每个任务变成标准化样本、按指标需要的提问方式(「生成一段文字」还是「给几个选项打分」)批量请求模型,最后聚合成带标准误的分数表。它同时是 HF 训练栈的「评测插头」:能直接加载 nanotron 训练中的 checkpoint 评测,并把结果推到 Hub 和 TensorBoard。


1. 这是什么(零基础也能懂)

一句话定义

Lighteval 是一个给大语言模型跑基准测试(benchmark)的库:内置 1000+ 个预定义评测任务,你选任务、选后端,它负责出题、收答案、算分、出报告。

它要解决谁的什么问题

设想你在训练一个模型,或者想比较几个开源模型谁更强:

  • 基准有几千道题,格式千奇百怪(选择题、开放式问答、翻译、数学题)。
  • 有的题该让模型自由生成再对答案,有的题该比较几个候选答案的概率——两种问法对模型 API 的要求完全不同。
  • 你想换后端:本地用 vLLM 跑得快,调试时用 transformers,训练中想直接评 nanotron 的 checkpoint,或者干脆调远程 API。
  • 分数要有误差线,否则 0.2 分的差距说不清是不是噪声。
  • 结果要能复现、能推送、能逐条查(哪道题错了、模型具体输出了什么)。

Lighteval 把这些一次性管掉。它的定位由作者自己写明:灵感来自 EleutherAI 的 harness 和斯坦福 HELM(README「Acknowledgements」)。

它能做什么

能力具体支持
任务来源100+ 个内置任务文件(src/lighteval/tasks/tasks/)+ 多语言任务 + 社区任务 + 用户自定义任务文件
提问方式生成式(greedy_until)、选项打分式(loglikelihood)、整段困惑度(loglikelihood_rolling),见 src/lighteval/tasks/requests.py:35(SamplingMethod)
本地后端vLLM(同步/异步)、transformers(含 adapter/delta/VLM)、nanotron、SGLang(src/lighteval/models/model_loader.py:51,load_model)
远程后端TGI、HF Inference Endpoints、LiteLLM、HF Inference Providers(src/lighteval/models/endpoints/)
指标exact match、F1、loglikelihood 准确率、pass@k、maj@n、BLEU/ROUGE/BERTScore、LLM-as-judge 等(src/lighteval/metrics/metrics.py:146,Metrics 枚举)
结果本地 JSON + 逐样本 details parquet、推 HF Hub(dataset)、TensorBoard、wandb/trackio(src/lighteval/logging/evaluation_tracker.py:95,EvaluationTracker)
训练集成nanotron checkpoint 直评(src/lighteval/main_nanotron.py:42)、训后把结果推进训练 org 的 Hub 仓库

用起来什么样

最小形态是一条命令行——评一个 vLLM 模型在 gsm8k 上的表现:

# 命令形态摘自 README Quickstart,参数写法见 src/lighteval/main_vllm.py:52
lighteval vllm "model_name=HuggingFaceH4/zephyr-7b-beta" "gsm8k|0"

任务字符串 "gsm8k|0" 是 lighteval 的小语言:任务名|few_shot 数,解析逻辑在 src/lighteval/tasks/registry.py:184(_update_task_configs)。

Python API 可以评已在内存里的模型(README 的例子,签名见 src/lighteval/models/transformers/transformers_model.py:254,TransformersModel.from_model):

# 摘自 README「Quickstart」,已精简
model = AutoModelForCausalLM.from_pretrained(MODEL_NAME, device_map="auto")
model = TransformersModel.from_model(model, TransformersModelConfig(model_name=MODEL_NAME, batch_size=1))

pipeline = Pipeline(
model=model,
pipeline_parameters=PipelineParameters(launcher_type=ParallelismManager.NONE, max_samples=2),
evaluation_tracker=EvaluationTracker(output_dir="./results"),
tasks="gsm8k",
)
pipeline.evaluate() # 出题 → 跑模型 → 算分
pipeline.show_results()

一句话直觉

把 lighteval 想成一个「标准化考场」。 任务定义是考卷(题面 + 标准答案 + 评分标准),Doc 是发到每个考生手里的统一答题卡,后端是不同的「考生应答方式」(口头作答 = 生成,给选项涂卡 = loglikelihood),Pipeline 是监考流程:发卷、收卷、按评分标准打分、最后把全班成绩汇总成带误差线的成绩单。


2. 顶层全景(它大概怎么转)

2.1 顶层图

┌───────────────────────────────────────────┐
lighteval vllm │ Pipeline │
"模型参数" │ pipeline.py:117 │
"任务字符串" ──► │ │
│ ① Registry 注册表:任务名 → LightevalTask │
│ ② get_docs:数据集行 → Doc(+few-shot) │
│ ③ 按采样方式聚类 Doc │
└───┬───────────────┬───────────────────────┘
▼ ▼
┌────────────────┐ ┌────────────────────┐
│ GENERATIVE │ │ LOGPROBS/PPL │
│ greedy_until │ │ loglikelihood* │
└───────┬────────┘ └─────────┬──────────┘
▼ ▼
┌────────────────────────────────────┐
│ 可插拔后端(LightevalModel 契约) │
│ vLLM / transformers / nanotron / │
│ SGLang / 远程 endpoint │
└───────┬────────────────────────────┘
▼ ModelResponse(文本或 logprobs)
┌────────────────────────────────────┐
│ ④ apply_metric:逐样本打分 │
│ ⑤ MetricsLogger.aggregate: │
│ 语料级聚合 + stderr + 任务平均 │
└───────┬────────────────────────────┘

EvaluationTracker:本地 JSON/details
→ 推 Hub / TensorBoard / wandb

怎么读这张图: 上面是控制流入口(CLI 各后端命令都收敛到同一个 Pipeline);中间是「任务 → 样本 → 请求」的转换层;再往下是模型后端;最下面是打分与输出。②③ 之间有个关键设计:先知道指标要什么,才决定怎么问模型——详见第 3 章。

2.2 部件职责

部件干什么在哪个文件
Pipeline总控:建任务、建模型、跑请求、算指标、存结果src/lighteval/pipeline.py:117
Registry扫描 tasks 目录收 TASKS_TABLE,解析 `taskfew_shot` 任务字符串
LightevalTask / LightevalTaskConfig一个任务的完整定义:数据集、prompt 函数、指标、生成参数src/lighteval/tasks/lighteval_task.py:205:48
Doc统一样本模型:query、choices、gold_index、few-shot、生成参数src/lighteval/tasks/requests.py:43
PromptManager / FewShotSampler把 Doc 拼成最终 prompt(chat template 或纯文本);按策略抽 few-shot 示例src/lighteval/tasks/prompt_manager.py:42:199
LightevalModel后端抽象基类,三个抽象方法 + 分词工具src/lighteval/models/abstract_model.py:220
VLLMModel / TransformersModel / NanotronLightevalModel三个主要本地后端src/lighteval/models/vllm/vllm_model.py:198src/lighteval/models/transformers/transformers_model.py:200src/lighteval/models/nanotron/nanotron_model.py:163
DynamicBatchDataset按长度排序样本、切 split、记住原始顺序src/lighteval/data.py:44
Metric / Metrics 枚举指标五元组定义;内置指标花名册src/lighteval/metrics/utils/metric_utils.py:32src/lighteval/metrics/metrics.py:146
apply_metric按任务 × 采样方式分组,批量算样本级指标src/lighteval/metrics/__init__.py:29
MetricsLogger聚合:语料级指标 + stderr + 子任务平均 + 全体平均src/lighteval/logging/info_loggers.py:308
EvaluationTracker输出:results JSON、details parquet、推 Hub/TensorBoard/wandbsrc/lighteval/logging/evaluation_tracker.py:95
SampleCache / @cached按 (模型配置 hash, 任务配置 hash, 样本 id) 落盘缓存模型回答src/lighteval/utils/cache_management.py:64:364

2.3 主线走一遍(一次评测)

对应 Pipeline.evaluate()(src/lighteval/pipeline.py:271):

① 建任务 _init_tasks_and_requests (pipeline.py:210)
Registry 解析 "gsm8k|0" → 找到 LightevalTaskConfig → 建 LightevalTask
→ 下载数据集 → 每行走 prompt_function 变 Doc → 注入 few-shot 与生成参数


② 聚类 pipeline.py:227-231
按 doc.sampling_methods 把 Doc 分成 GENERATIVE / LOGPROBS / PERPLEXITY 三桶
(一个 Doc 可进多个桶,比如同时算生成式和对数概率指标)


③ 跑模型 _run_model_sync (pipeline.py:313)
每桶调一次对应的模型方法:greedy_until / loglikelihood / loglikelihood_rolling
后端内部负责排序、动态 batch、截断、缓存;返回 ModelResponse 列表


④ 后处理 _post_process_outputs (pipeline.py:347)
默认剥掉 <think>...</think> 推理段(remove_reasoning_tags)


⑤ 打分 _compute_metrics (pipeline.py:362)
按 (任务, 采样方式) 分组 → apply_metric 逐样本出分 → 逐条 log


⑥ 聚合 MetricsLogger.aggregate (info_loggers.py:330)
语料级聚合 → bootstrap/解析式 stderr → 子任务平均(如 mmlu 各科 → mmlu)→ 全体平均


⑦ 输出 EvaluationTracker.save (evaluation_tracker.py:251)
results JSON + details parquet 落盘;可选推 Hub / TensorBoard / wandb

这条主线最值得记住的两点:

  1. 指标驱动请求,而不是请求驱动指标。 ② 的 sampling_methods 来自任务所选指标的 category(lighteval_task.py:245)。选了 pass@k 还会把 num_samples 一路传到 vLLM 的 SamplingParams.n(第 3 章展开)。
  2. Doc 是唯一通货。 任务侧产出 Doc,模型侧消费 Doc 返回 ModelResponse,指标侧吃 (Doc, ModelResponse) 对。三层互不感知对方实现,这就是「同一套任务跑十个后端」的结构基础。

3. 阅读地图(建议顺序)

四章由浅入深。时间有限的话,读 01 → 03 就能抓住 lighteval 区别于「直接调模型 API 对答案」的全部要害。

顺序章节讲什么适合谁
101-task-registry-and-prompts.md任务怎么注册、任务字符串语法、Doc、few-shot 采样、prompt 拼装所有人必读;想加自定义任务的人
202-model-backends.md三种请求类型、后端契约、分词对齐的坑、动态 batch、缓存想接新模型后端、或好奇分数怎么被实现细节影响的人
303-metrics-aggregation.md指标五元组、logprob 归一化、pass@k、语料级聚合、stderr想自定义指标、或想读懂分数表的人
404-training-integration.mdnanotron checkpoint 直评、结果推送、details 与 hash、inspect-ai 入口做训练管线集成、做评测平台的人

4. 巧妙之处(可借鉴的技术)

每条先白话点出「妙在哪」,再给锚点;细节在各章节展开。

  1. 指标类别反向决定请求类型。 任务不用声明「我要怎么调模型」,它声明「我要算哪些指标」;指标自带的 category 决定 pipeline 该发生成请求还是 logprob 请求(src/lighteval/tasks/lighteval_task.py:245)。任务定义因此与后端能力解耦——换个不支持 logprob 的远程 API,只需要换指标或换任务,不改框架。

  2. 用 vLLM 的「生成接口」算 loglikelihood。 vLLM 没有暴露打分 API,lighteval 让它以 max_tokens=1, prompt_logprobs=1 做「假生成」,再从 prompt_logprobs 里把 continuation 各 token 的概率抠出来(src/lighteval/models/vllm/vllm_model.py:443-449:522-541)。一次 prefill 出一条候选的完整分数,多选题 N 个选项就是 N 次 prefill。

  3. context/continuation 分词对齐当一等公民。 tok_encode_pair 默认把 context 结尾的空格挪到 continuation 上(「 Paris」→ token ĠParis),并剥掉 context 末尾的 EOS,防止模型「无视上下文」(src/lighteval/models/abstract_model.py:297-354)。这类分词细节正是不同 harness 分数对不上的主要来源,lighteval 把它做成了显式开关(pairwise_tokenizationmultichoice_continuations_start_spacemove_trailing_context_space)。

  4. 动态 batch 的三件套。 按长度降序排(OOM 在最前面暴露)、find_executable_batch_size OOM 自动减半重试、每跑完一个 split 把下一个 split 的起始 batch 翻倍(src/lighteval/data.py:161-183src/lighteval/utils/parallelism.py:57src/lighteval/models/transformers/transformers_model.py:941-950)。

  5. 缓存键 = 模型配置 hash × 任务配置 hash × 样本 id。 改任何生成参数或任务定义都会得到新 hash,缓存自动失效;命中则按样本粒度复用(src/lighteval/utils/cache_management.py:141-183)。调试期改 prompt 后重跑,不用重算没变的任务。

  6. per-sample details 的可复现 hash。 每个任务的样本内容、完整 prompt、输入/续写 token 分别做 xxhash 聚合,跨任务再聚一次(src/lighteval/logging/info_loggers.py:277-305,DetailsLogger.aggregate)。两次 run 的 prompt 是否一字不差,对一下 hash 就知道。

  7. stderr 按聚合方式分流。 均值类指标用解析式标准误,其余自动转 bootstrap 重采样(1000 次),避免对 F1 这类非线性指标误用公式(src/lighteval/metrics/utils/stderr.py:95-106,get_stderr_function)。


5. 边界与局限

诚实清单(每条都有代码依据):

  • vLLM 后端不支持困惑度任务。 loglikelihood_rollingVLLMModel 里直接 raise NotImplementedError()(src/lighteval/models/vllm/vllm_model.py:555-557);要跑 perplexity 得用 transformers 或 nanotron 后端。
  • nanotron 后端不支持流水线并行。 PP>1 直接报错「PP parallelism is not supported yet」(src/lighteval/models/nanotron/nanotron_model.py:206-208)。
  • 多次采样不许 temperature=0。 num_samples > 1 且贪心会直接抛错(src/lighteval/models/vllm/vllm_model.py:439-442);pass@k 类指标必须开采样温度。
  • 同步 vLLM 的批内假设。 非 chat 模式下它假设同 batch 样本的 stop/max_tokens 一致,注释明说「因此我们只用 batch size 1」的考量(src/lighteval/models/vllm/vllm_model.py:354-358)——纯文本 + 任务级 stop 的场景吞吐会受影响。生成类任务靠 GenerativeTaskDataset 按生成参数分组来缓解(src/lighteval/data.py:186-226)。
  • 超长 prompt 左截断会丢 few-shot。 context + max_new_tokens 超过模型上限时,从左边砍 context(src/lighteval/models/vllm/vllm_model.py:374-397),few-shot 示例可能最先被砍掉而用户只看到一条 warning。
  • 聚合溢出记 NaN 不报错。 语料级聚合遇到 OverflowError 只记 warning 并写 nan(src/lighteval/logging/info_loggers.py:348-352);看结果表时要留意。
  • 数字不可与其他 harness 直接比。 分词对齐、few-shot 来源、prompt 模板都影响分数;源卡片也明写「Numbers are not automatically comparable to other harnesses」(aiRef/sources/lighteval.md)。lighteval 用 details hash(巧妙之处 6)来保证的是自己两次 run 的可比性。
  • Windows 不支持,README 明说完全未测试。

6. 横向对比

同书架上的分工要先说清:lighteval 评的是「模型」,不是「agent」。 书架上评 agent 的 eval 源回答的是「这个系统完成任务没有」(轨迹、工具调用、终态校验);lighteval 回答的是「这个 checkpoint 的基准分数是多少」(标准化考卷 + 统计显著性)。总库指南的 eval-observability 章目前只覆盖 agent 侧评测,两边是互补而非替代。

维度lighteval兄弟项目
lm-evaluation-harness 的关系后起、更小;自带任务集更窄,但后端更多(vLLM/SGLang/nanotron/远程 API),且原生为「训练中评 checkpoint」设计harness 是事实标准,任务覆盖最广;lighteval 的 harness_compatibility 目录(src/lighteval/metrics/harness_compatibility/)里保留了对齐 harness 的指标实现
open-r1 的关系评测底座:open-r1 这类 HF 复现项目用它出基准分open-r1 是训练 recipe,评测环节正是调 lighteval
olmo 的关系外来评测者视角:评别人训好的模型OLMo 是完整训练栈,自带评测集成;两者在「训练循环里嵌评测」上做法可对照

一句话取舍:要任务覆盖和学界可比性选 harness;要接 HF 训练栈、换后端自由、逐样本可复查选 lighteval。


7. 代码地图(入口级)

每章末尾有细粒度地图;这里列从零开始读源码的四个入口:

主题文件路径符号名
总控流程src/lighteval/pipeline.pyPipeline.evaluate_init_tasks_and_requests_run_model_sync_compute_metrics
任务体系src/lighteval/tasks/registry.pysrc/lighteval/tasks/lighteval_task.pyRegistry.load_all_task_configsLightevalTaskConfigLightevalTask.get_docs
后端契约与样例src/lighteval/models/abstract_model.pysrc/lighteval/models/vllm/vllm_model.pyLightevalModeltok_encode_pairVLLMModel._loglikelihood_tokens
打分与输出src/lighteval/metrics/__init__.pysrc/lighteval/logging/info_loggers.pyapply_metricMetricsLogger.aggregate