原文地址:Understanding the 4 Main Approaches to LLM Evaluation (From Scratch),by Sebastian Raschka, on 2025-10-05
从零开始理解大语言模型评估的四大主流方法
选择题基准测试、验证器、排行榜与LLM评审器(附代码示例)
我们究竟该如何评估大语言模型(LLM)?这个问题看似简单,却往往能引出一场宏大的讨论。
在为项目提供咨询或协作开发时,我最常被问到的问题之一就是:如何在不同模型之间做选择?如何解读市面上五花八门的评估结果?(当然,还有在微调或自研模型时,该如何衡量进展。)
由于这个问题反复出现,我觉得有必要简要梳理一下业界用于对比LLM的主流评估方法。诚然,LLM评估是一个极其庞大的议题,单篇文章无法穷尽,但我认为,对这几类核心方法建立清晰的认知框架,能大幅帮助我们解读各类基准测试、排行榜和学术论文。
我原本打算把这些评估技术写入即将出版的新书《Build a Reasoning Model (From Scratch)》(https://mng.bz/Nwr7),但最终发现它们略超出了全书的核心范围(该书更聚焦于基于验证器的评估方法)。因此我想把这部分内容整理成一篇长文,搭配从零实现的代码示例分享给大家。
在《Build a Reasoning Model (From Scratch)》(https://mng.bz/Nwr7)一书中,我采用了动手实践的方式,从零开始构建一个推理型LLM。如果你喜欢《Build A Large Language Model (From Scratch)》,那么新书也延续了同样的风格——全程用纯PyTorch从零搭建所有组件。

图0:推理能力是近年来LLM领域最激动人心、也最重要的进展之一,但如果只听过“推理”这个名词、只读过理论层面的介绍,也很容易产生误解。因此在《Build a Reasoning Model (From Scratch)》(https://mng.bz/Nwr7)中,我采用了手把手实操的思路,从零搭建推理型LLM。
该书目前处于抢先阅读阶段,已有超过100页内容上线,我刚刚又完成了约30页的内容,目前正在由排版团队处理。如果你已经加入了抢先阅读计划(非常感谢你的支持!),内容上线后你会收到邮件通知。
附言:当下LLM研究领域日新月异,我还在陆续消化我收藏夹里越来越多的论文,计划在下一篇文章中重点介绍其中最有意思的几项研究。
不过现在,让我们先来讨论LLM评估的四大核心方法,搭配从零开始的代码实现,帮你更好地理解它们各自的优缺点。
理解LLM的主流评估方法
实践中,训练好的LLM常见的评估方式有四种:选择题测试、验证器评估、排行榜和LLM评审器,如图1所示。学术论文、宣传材料、技术报告以及模型卡(LLM专属的技术报告)通常会包含其中两类或更多类方法的结果。

图1: 本文涵盖的四种评估模型概览
此外,这里介绍的四大类方法可以归为两大阵营:基于基准测试的评估和基于评判的评估,如上图所示。
(还有一些其他指标,比如训练损失、困惑度、奖励值等,但它们通常用于模型开发的内部环节。)
下面的小节将简要概述每一种方法,并给出相应示例。
方法1:评估选项作答准确率
我们从一种基于基准测试的方法说起:选择题作答。
历史上,应用最广泛的评估方法之一就是选择题基准测试,比如MMLU(全称为Massive Multitask Language Understanding,大规模多任务语言理解,https://huggingface.co/datasets/cais/mmlu)。为了说明这种方法,图2展示了MMLU数据集中的一道典型题目。

图2: 在MMLU上评估LLM:将模型的选择题预测结果与数据集的正确答案做对比
图2只是MMLU数据集中的一个例子。完整的MMLU数据集涵盖57个学科(从高中数学到生物学),总共约1.6万道选择题,性能用准确率来衡量(答对题目的占比)——比如1.6万道题中答对1.4万道,准确率就是87.5%。
MMLU这类选择题基准测试,以一种直接、可量化的方式考察LLM的知识回忆能力,类似于标准化考试、各类学业考试或者驾照理论考试。
注意图2展示的是简化版的选择题评估方式:直接将模型预测的答案字母与正确答案做比对。还有另外两种更常用的方法涉及对数概率评分(log-probability scoring),我已经在这个仓库中实现了它们:https://github.com/rasbt/reasoning-from-scratch/tree/main/chF/02_mmlu 。(由于这部分内容建立在本文讲解的概念之上,建议读完本文后再去查看。)
下面的小节将演示如何用代码实现图2所示的MMLU打分方式。
1.2 加载模型
首先,在MMLU上评估模型之前,我们首先要加载预训练模型。这里我们使用纯PyTorch从零实现的Qwen3 0.6B模型,它只需要约1.5GB内存。
注意Qwen3的模型实现细节不是本节重点,我们只把它当作一个待评估的LLM即可。如果你感兴趣,可以在我之前的文章《Understanding and Implementing Qwen3 From Scratch》中看到从零实现的完整教程,源代码也可以在Github获取:
我们不用复制粘贴大段Qwen3源代码,而是直接从我的Python库reasoning_from_scratch中导入,安装方式如下:
pip install reasoning_from_scratch
或者
uv add reasoning_from_scratch
代码块1:加载预训练模型
from pathlib import Path
import torch
from reasoning_from_scratch.ch02 import get_device
from reasoning_from_scratch.qwen3 import (
download_qwen3_small, Qwen3Tokenizer,
Qwen3Model, QWEN_CONFIG_06_B
)
device = get_device()
# Set matmul precision to "high" to
# enable Tensor Cores on compatible GPUs
torch.set_float32_matmul_precision("high")
# Uncomment the following line
# if you encounter device compatibility issues
# device = "cpu"
# Use the base model by default
WHICH_MODEL = "base"
if WHICH_MODEL == "base":
download_qwen3_small(
kind="base", tokenizer_only=False, out_dir="qwen3"
)
tokenizer_path = Path("qwen3") / "tokenizer-base.json"
model_path = Path("qwen3") / "qwen3-0.6B-base.pth"
tokenizer = Qwen3Tokenizer(tokenizer_file_path=tokenizer_path)
elif WHICH_MODEL == "reasoning":
download_qwen3_small(
kind="reasoning", tokenizer_only=False, out_dir="qwen3"
)
tokenizer_path = Path("qwen3") / "tokenizer-reasoning.json"
model_path = Path("qwen3") / "qwen3-0.6B-reasoning.pth"
tokenizer = Qwen3Tokenizer(
tokenizer_file_path=tokenizer_path,
apply_chat_template=True,
add_generation_prompt=True,
add_thinking=True,
)
else:
raise ValueError(f"Invalid choice: WHICH_MODEL={WHICH_MODEL}")
model = Qwen3Model(QWEN_CONFIG_06_B)
model.load_state_dict(torch.load(model_path))
model.to(device)
# Optionally enable model compilation for potential performance gains
USE_COMPILE = False
if USE_COMPILE:
torch._dynamo.config.allow_unspec_int_on_nn_module = True
model = torch.compile(model)
1.3 检查生成的答案字母
本节我们实现最简单、也最直观的MMLU打分方法:检查模型生成的选择题答案字母是否与正确答案匹配。这和之前图2展示的思路一致,为方便阅读,下图再次放出。

图3: 在MMLU上评估LLM:将模型的选择题预测结果与数据集的正确答案做对比
我们以MMLU数据集中的一道题为例:
example = {
"question": (
"How many ways are there to put 4 distinguishable"
" balls into 2 indistinguishable boxes?"
),
"choices": ["7", "11", "16", "8"],
"answer": "D",
}
接下来,我们定义一个函数来格式化LLM的输入提示。
代码块2:格式化提示
def format_prompt(example):
return (
f"{example['question']}\n"
f"A. {example['choices'][0]}\n"
f"B. {example['choices'][1]}\n"
f"C. {example['choices'][2]}\n"
f"D. {example['choices'][3]}\n"
"Answer: "
)
# Trailing space in "Answer: " encourages a single-letter next token
我们对上面的MMLU例题执行这个函数,看看格式化后的LLM输入是什么样的:
prompt = format_prompt(example)
print(prompt)
输出为:
How many ways are there to put 4 distinguishable balls into 2 indistinguishable boxes?
A. 7
B. 11
C. 16
D. 8
Answer:
如上所示,模型提示会列出所有候选答案,最后以Answer:结尾,引导模型生成正确答案。
虽然不是必须的,但有时也可以在输入中附带几道例题和正确答案,让模型观察解题的规范格式。(比如提供5道例题的设置也叫5-shot MMLU。)不过对于当前世代的LLM,哪怕基座模型也已经具备足够的能力,这一步并非必需。
加载不同的MMLU样本
你可以通过datasets库直接加载MMLU数据集的样本(安装方式:pip install datasets 或 uv add datasets):
from datasets import load_dataset
configs = get_dataset_config_names("cais/mmlu")
dataset = load_dataset("cais/mmlu", "high_school_mathematics")
# Inspect the first example from the test set:
example = dataset["test"][0]
print(example)
上面我们使用了“高中数学”子集;要查看其他子集列表,可以使用以下代码:
from datasets import get_dataset_config_names
subsets = get_dataset_config_names("cais/mmlu")
print(subsets)
接下来,我们对提示进行分词,并用PyTorch张量包装,作为LLM的输入:
prompt_ids = tokenizer.encode(prompt)
prompt_fmt = torch.tensor(prompt_ids, device=device)
# Add batch dimension:
prompt_fmt = prompt_fmt.unsqueeze(0)
准备工作完成后,我们定义核心打分函数:生成少量token(这里默认8个),并提取模型输出中出现的第一个A/B/C/D字母。
代码块3:提取生成的字母
from reasoning_from_scratch.ch02_ex import (
generate_text_basic_stream_cache
)
def predict_choice(
model, tokenizer, prompt_fmt, max_new_tokens=8
):
pred = None
for t in generate_text_basic_stream_cache(
model=model,
token_ids=prompt_fmt,
max_new_tokens=max_new_tokens,
eos_token_id=tokenizer.eos_token_id,
):
answer = tokenizer.decode(t.squeeze(0).tolist())
for letter in answer:
letter = letter.upper()
# stop as soon as a letter appears
if letter in "ABCD":
pred = letter
break
if pred:
break
return pred
然后我们就可以用上面的函数检查生成的字母了:
pred1 = predict_choice(model, tokenizer, prompt_fmt)
print(
f"Generated letter: {pred1}\n"
f"Correct? {pred1 == example['answer']}"
)
结果为:
Generated letter: C
Correct? False
可以看到,这个例子中模型生成的答案是错误的(False)。
这只是MMLU高中数学子集中270道题里的一道。下图(图4)展示了基座模型和推理版本在完整子集上的表现,对应代码可以在这里查看:https://github.com/rasbt/reasoning-from-scratch/blob/main/chF/02_mmlu/1_letter_matching.py 。

图4: 基座模型与推理模型在MMLU高中数学子集上的表现
假设每个选项出现概率均等,随机猜测者(均匀选择A、B、C、D)的预期准确率是25%。所以基座模型和推理模型的表现都不算理想。
选择题作答的其他形式
注意本节为了演示,实现的是简化版的选择题评估,直接比对答案字母。实践中更常用的是对数概率评分(log-probability scoring)这类变体:我们衡量模型认为每个候选答案的可能性,而不只是检查最终输出的字母。(我们会在第4章讨论基于概率的打分。)对于推理模型,评估还可以包含:当把正确答案作为输入时,模型生成它的概率有多大。

图5: 其他MMLU打分方法已在这个仓库中说明和共享:https://github.com/rasbt/reasoning-from-scratch/tree/main/chF/02_mmlu
但无论使用哪种MMLU打分变体,评估的本质都是检验模型能否从预设的选项中做出选择。
MMLU这类选择题基准测试的局限在于:它们只衡量LLM从预设选项中做选择的能力,因此除了检查模型相比基座版本遗忘了多少知识之外,对评估推理能力的作用有限。它无法衡量自由写作能力,也无法反映模型在真实场景中的实用性。
尽管如此,选择题基准测试依然是简单实用的诊断工具:比如,MMLU高分不一定代表模型实际应用能力强,但低分却能凸显出潜在的知识缺口。
方法2:用验证器检查答案
和上一节讨论的选择题作答相关,基于验证的方法也通过准确率指标来量化LLM的能力。但与选择题基准测试不同的是,验证方法允许LLM给出自由形式的答案,然后我们提取出相关的答案部分,用所谓的“验证器”将答案与数据集提供的标准答案做对比,如图6所示。

图6: 用基于验证的方法在自由问答场景下评估LLM。模型生成自由形式的答案(可能包含多步推导)和最终的框选答案,提取后与数据集的正确答案做对比。
在对比提取出的答案和标准答案时,我们可以使用外部工具,比如代码解释器或者计算器类工具/软件。
这种方法的缺点是:只能应用于容易(最好是确定性)验证的领域,比如数学和代码。此外,这种方法会引入额外的复杂度和依赖项,并且可能把部分评估负担从模型本身转移到了外部工具上。
但由于它支持通过编程生成无限多的数学题目变体,并且能受益于逐步推理过程,它已经成为推理模型评估和开发的基石。
我在《Build a Reasoning Model (From Scratch)》一书中用整整35页的篇幅详细讲解了这个主题,所以这里就跳过代码实现了。(我上周刚提交了这一章。如果你有抢先阅读版,内容上线后你会收到邮件,到时候就可以阅读了。与此同时,你可以在这里找到分步代码:https://github.com/rasbt/reasoning-from-scratch/blob/main/ch03/01_main-chapter-code/ch03_main.ipynb 。)


图7: 基于验证的评估方法节选,代码见:https://github.com/rasbt/reasoning-from-scratch/blob/main/ch03/01_main-chapter-code/ch03_main.ipynb
方法3:用偏好度和排行榜对比模型
到目前为止,我们介绍了两种能给出可量化指标(比如模型准确率)的方法。但上述方法都没有从更整体的角度评估LLM,包括对回答风格的评判。本节我们讨论一种基于评判的方法——LLM排行榜,如图8所示。

图8: 本书覆盖主题的思路框架,聚焦于附录中介绍的基于评判和基于基准的评估方法。前面已经讲了基于基准的方法(选择题、验证器),现在我们引入衡量LLM性能的基于评判的方法,本小节重点讲排行榜。
这里介绍的排行榜方法是一种基于评判的思路:模型的排名不是依据准确率或其他固定基准分数,而是依据用户(或其他LLM)对其输出的偏好度。
一个很有名的排行榜是LM Arena(https://lmarena.ai/,前身为Chatbot Arena):用户对比两个指定或匿名模型的回答,投票选出自己更喜欢的那个,如图9所示。

图9: 基于评判的排行榜界面示例(LM Arena)。给两个LLM输入相同的提示,并排展示回答,用户投票选出更偏好的答案。
这些偏好投票会像上图那样收集起来,再聚合到所有用户中,生成一个按用户偏好度排名的模型排行榜。图10是LM Arena排行榜的当前快照(数据获取于2025年10月3日)。

图10: LM Arena排行榜截图,展示了基于用户文本任务偏好度的当前领先LLM
在本节余下部分,我们将实现一个简单的排行榜示例。
为了构造具体的例子,假设用户在类似图9的设置下向不同LLM提问。下面的列表代表两两投票结果,第一个模型是获胜者:
votes = [
("GPT-5", "Claude-3"),
("GPT-5", "Llama-4"),
("Claude-3", "Llama-3"),
("Llama-4", "Llama-3"),
("Claude-3", "Llama-3"),
("GPT-5", "Llama-3"),
]
上面的列表中,votes里的每个元组代表两个模型的两两偏好,格式为(获胜者,失败者)。比如("GPT-5", "Claude-3")表示用户更偏好GPT-5的回答,而非Claude-3。
接下来,我们把这个投票列表转换成排行榜。我们会使用很流行的Elo评级体系(https://en.wikipedia.org/wiki/Elo_rating_system),它最初是为棋手排名设计的。
在看具体代码实现之前,先简单说下原理:每个模型以基准分起步,每次对比和投票后,模型的评级会更新。(在Elo体系中,更新幅度取决于结果的“冷门程度”。)
具体来说,如果一个当前模型战胜了排名远高于自己的模型,它的排名会得到较大幅度的提升,在排行榜上位置更靠前。反之,如果战胜的是排名很低的对手,提升幅度就很小。(如果当前模型输了,也会按类似逻辑扣分。)
将两两排名转化为排行榜的代码如下所示。
代码块4:构建排行榜
def elo_ratings(vote_pairs, k_factor=32,
initial_rating=1000):
# Initialize all models with the same base rating
ratings = {
model: initial_rating
for pair in vote_pairs
for model in pair
}
# Update ratings after each match
for winner, loser in vote_pairs:
# Expected score for the current winner
expected_winner = 1.0 / (
1.0 + 10 ** (
(ratings[loser] - ratings[winner])
/ 400.0
)
)
# k_factor determines sensitivity of updates
ratings[winner] = (
ratings[winner]
+ k_factor * (1 - expected_winner)
)
ratings[loser] = (
ratings[loser]
+ k_factor * (0 - (1 - expected_winner))
)
return ratings
上面定义的elo_ratings函数接收投票数据,输出排行榜,用法如下:
ratings = elo_ratings(votes, k_factor=32, initial_rating=1000)
for model in sorted(ratings, key=ratings.get, reverse=True):
print(f"{model:8s} : {ratings[model]:.1f}")
输出的排名如下,分数越高表现越好:
GPT-5 : 1043.7
Claude-3 : 1015.2
Llama-4 : 1000.7
Llama-3 : 940.4
这背后的原理是什么?对于每一组对决,我们用下面的公式计算获胜者的预期得分:
expected_winner = 1 / (1 + 10 ** ((rating_loser - rating_winner) / 400))
expected_winner是基于当前评级,模型在无平局设定下的预测获胜概率,它决定了评级的更新幅度。
首先,每个模型的初始分initial_rating = 1000。如果两个模型评级相等,那么expected_winner = 0.5,说明双方势均力敌。这种情况下,更新幅度为:
如果是大热门(高评级模型)赢了,expected_winner ≈ 1,胜者只加很少的分,败者也只扣很少的分:
但如果是冷门选手(低评级模型)赢了,expected_winner ≈ 0,胜者几乎拿到全部k_factor的分数,败者也扣差不多的分数:
顺序的影响
Elo方法是每次对决后更新评级,因此后面的结果建立在已经更新过的评级之上。这意味着同样的一组比赛结果,如果呈现顺序不同,最终分数可能略有差异。这种影响通常不大,但如果冷门对局出现在早期和晚期,结果可能会不一样。
为了降低这种顺序效应,我们可以打乱投票对的顺序,多次运行elo_ratings函数,然后取评级的平均值。
上面介绍的这类排行榜方法,比静态基准分数更能动态反映模型质量。但结果会受到用户群体构成、提示选择、投票偏差的影响。基准测试和排行榜也都存在被“刷分”的可能,而且用户可能更看重回答风格而非正确性。最后,和自动化基准测试相比,排行榜无法对新开发的模型变体提供即时反馈,因此在活跃的模型开发阶段使用起来更困难。
其他排名方法
LM Arena最初使用本节介绍的Elo方法,但最近换成了基于布拉德利-特里(Bradley–Terry)模型的统计方法。Bradley-Terry模型的主要优势是:由于有统计学基础,它可以构建置信区间来表达排名的不确定性。此外,和Elo评级不同,Bradley-Terry模型是基于整个数据集做统计拟合,同时估算所有评级,因此不受顺序效应影响。
为了让分数保持在大家熟悉的区间,Bradley-Terry模型拟合后会输出和Elo相当的数值。尽管排行榜官方已经不再正式使用Elo评级,但“Elo”这个说法在LLM研究者和从业者对比模型时依然被广泛使用。Elo评级的代码示例可以在这里查看:https://github.com/rasbt/reasoning-from-scratch/tree/main/chF/03_leaderboards 。

图11: Elo与Bradley-Terry排名对比;源代码见:https://github.com/rasbt/reasoning-from-scratch/tree/main/chF/03_leaderboards
方法4:用其他LLM评判回答
早期,LLM是用统计和启发式方法来评估的,包括BLEU指标——一种粗略衡量生成文本与参考文本匹配程度的标准。这类指标的问题在于,它们要求精确的单词匹配,没有考虑同义词、语序变化等情况。
如果我们想从整体上评判生成的回答文本,一个解决方案是使用上一节讲的相对排名和基于排行榜的方法。但排行榜的缺点在于,偏好对比带有主观性,因为需要人类反馈(以及收集反馈带来的种种挑战)。
另一种相关方法是:使用另一个LLM,搭配预定义的评分标准(即评估指南),将待评估LLM的回答与参考答案做对比,按照预设标准评判回答质量,如图12所示。

图12: LLM评审器评估示例。待评估模型生成答案,然后由另一个独立的评审LLM根据评分标准和参考答案打分。
实践中,只要评审LLM足够强,图12这种基于评审的方法效果就很好。常见的设置是通过API调用顶尖的闭源LLM(比如GPT-5 API),不过也有专门的评审模型。(比如这篇论文就是众多例子之一:https://arxiv.org/abs/2405.08029 ;归根结底,大多数这类专用模型都是小模型经过微调,获得了和闭源GPT模型相近的打分行为。)
评审方法之所以有效,原因之一是:评判一个答案往往比生成一个答案更容易。
要在Python中编程实现图12所示的基于评审的模型评估,我们既可以用PyTorch加载一个更大的Qwen3模型,给它输入评分标准和待评估的模型回答;也可以通过API调用其他LLM,比如ChatGPT或者Ollama API。
既然我们已经知道如何用PyTorch加载Qwen3模型,为了更有意思,本节剩下的部分我们将用Python调用Ollama API,实现图12的评审式评估。
具体来说,我们会使用OpenAI的200亿参数gpt-oss开源模型,它在能力和效率之间取得了很好的平衡。想了解更多关于gpt-oss的信息,可以看我的文章《From GPT-2 to gpt-oss: Analyzing the Architectural Advances》:
https://magazine.sebastianraschka.com/p/from-gpt-2-to-gpt-oss-analyzing-the
https://magazine.sebastianraschka.com/p/from-gpt-2-to-gpt-oss-analyzing-the
https://magazine.sebastianraschka.com/p/from-gpt-2-to-gpt-oss-analyzing-the
https://magazine.sebastianraschka.com/p/from-gpt-2-to-gpt-oss-analyzing-the
https://magazine.sebastianraschka.com/p/from-gpt-2-to-gpt-oss-analyzing-the https://substack.com/profile/27393275-sebastian-raschka-phd
·
2025年8月9日
https://magazine.sebastianraschka.com/p/from-gpt-2-to-gpt-oss-analyzing-the
4.1 在Ollama中实现LLM-as-a-judge方法
https://ollama.com/ 是一款高效的开源工具,用于在本地电脑上运行LLM。它封装了开源的llama.cpp库(https://github.com/ggerganov/llama.cpp)——后者用纯C/C++实现LLM,以最大化运行效率。不过注意,Ollama只是利用LLM生成文本(推理)的工具,不支持训练或微调LLM。
要运行下面的代码,请先访问Ollama官网https://ollama.com/,按照对应操作系统的说明安装Ollama:
在实现模型评估代码之前,我们先下载gpt-oss模型,通过命令行终端使用Ollama,验证它能正常运行。
在命令行(不是Python会话中)执行以下命令,体验200亿参数的gpt-oss模型:
ollama run gpt-oss:20b
第一次执行这个命令时,会自动下载200亿参数的gpt-oss模型,占用14GB存储空间。输出大致如下:
$ ollama run gpt-oss:20b
pulling manifest
pulling b112e727c6f1: 100% ▕██████████████████████▏ 13 GB
pulling fa6710a93d78: 100% ▕██████████████████████▏ 7.2 KB
pulling f6035677647: 100% ▕██████████████████████▏ 11 KB
pulling d8ba2f9a17b3: 100% ▕██████████████████████▏ 18 B
pulling 55c108d8e936: 100% ▕██████████████████████▏ 489 B
verifying sha256 digest
writing manifest
removing unused layers
success
可选的Ollama模型
注意ollama run gpt-oss:20b命令中的gpt-oss:20b指的是200亿参数的gpt-oss模型。运行这个模型大约需要13GB内存。如果你的机器内存不够,可以试试更小的模型,比如40亿参数的qwen3:4b,命令为ollama run qwen3:4b,只需要约4GB内存。
配置更高的电脑也可以用更大的1200亿参数gpt-oss模型,把gpt-oss:20b换成gpt-oss:120b即可。但请注意,这个模型需要的计算资源会显著增加。
模型下载完成后,我们就进入命令行界面,可以和模型对话了。比如问问模型“1+2等于几?”:
>>> What is 1+2?
Thinking...
User asks: "What is 1+2?" This is simple: answer 3. Provide explanation? Possibly ask for simple arithmetic. Provide answer: 3.
...done thinking.
1 + 2 = **3**
输入/bye就可以结束这个ollama run gpt-oss:20b会话。
在本节剩下的部分,我们将使用Ollama API。这种方式要求Ollama在后台运行,有三种实现方式:
-
在终端运行ollama serve命令(推荐)。这会将Ollama后端作为服务运行,通常地址是http://localhost:11434。注意,在通过API调用之前,它不会加载模型。
-
像之前一样运行ollama run gpt-oss:20b命令,但保持窗口打开,不要输入/bye退出会话。如前所述,这相当于在本地Ollama服务外包了一层简易的交互封装,底层用的是和ollama serve一样的服务API。
-
Ollama桌面应用。打开桌面应用会自动运行相同的后端,并在上面提供图形界面,如图12所示。

图13: 两种保持Ollama服务(或应用)运行的方式,以便我们在Python中通过Ollama API调用它
Ollama服务IP地址
Ollama在本地机器上运行时,会启动一个类似本地服务的进程。在终端运行ollama serve时,你可能会遇到报错:Error: listen tcp 127.0.0.1:11434: bind: address already in use.
如果出现这种情况,可以尝试命令OLLAMA_HOST=127.0.0.1:11435 ollama serve(如果这个地址也被占用,就把数字逐个加1,直到找到空闲地址。)
下面的代码会在我们用Ollama评估上一节生成的测试集回答之前,验证Ollama会话是否正常运行:
代码块5:检查Ollama是否运行
import psutil
def check_if_running(process_name):
running = False
for proc in psutil.process_iter(["name"]):
if process_name in proc.info["name"]:
running = True
break
return running
ollama_running = check_if_running("ollama")
if not ollama_running:
raise RuntimeError(
"Ollama not running. "
"Launch ollama before proceeding."
)
print("Ollama running:", check_if_running("ollama"))
确保执行上面代码后输出Ollama running: True。如果显示False,请确认ollama serve命令或者Ollama应用正在运行(见图13)。
在本文余下部分,我们将通过Python调用Ollama REST API,与运行在本地机器上的gpt-oss模型交互。下面的query_model函数演示了API的使用方法:
代码块6:调用本地Ollama模型
import json
import urllib.request
def query_model(
prompt,
model="gpt-oss:20b",
# If you used
# OLLAMA_HOST=127.0.0.1:11435 ollama serve
# update the address below
url="http://localhost:11434/api/chat"
):
# Create the data payload as a dictionary:
data = {
"model": model,
"messages": [
{"role": "user", "content": prompt}
],
# Settings required for deterministic responses:
"options": {
"seed": 123,
"temperature": 0,
"num_ctx": 2048
}
}
# Convert the dictionary to JSON and encode it to bytes
payload = json.dumps(data).encode("utf-8")
# Create a POST request and add headers
request = urllib.request.Request(
url,
data=payload,
method="POST"
)
request.add_header("Content-Type", "application/json")
response_data = ""
# Send the request and capture the streaming response
with urllib.request.urlopen(request) as response:
while True:
line = response.readline().decode("utf-8")
if not line:
break
# Parse each line into JSON
response_json = json.loads(line)
response_data += response_json["message"]["content"]
return response_data
下面是使用我们刚实现的query_model函数的示例:
ollama_model = "gpt-oss:20b"
result = query_model("What is 1+2?", ollama_model)
print(result)
输出结果是“3”。(和直接运行ollama run或者桌面应用的结果会有差异,因为默认设置不同。)
利用query_model函数,我们就可以评估模型生成的回答了:我们构造一个包含评分标准的提示,让gpt-oss模型以1到5分的尺度,根据参考答案来打分。
我们使用的提示如下:
代码块7:设置包含评分标准的提示模板
def rubric_prompt(instruction, reference_answer, model_answer):
rubric = (
"You are a fair judge assistant. You will be "
"given an instruction, a reference answer, and "
"a candidate answer to evaluate, according to "
"the following rubric:\n\n"
"1: The response fails to address the "
"instruction, providing irrelevant, incorrect, "
"or excessively verbose content.\n"
"2: The response partially addresses the "
"instruction but contains major errors, "
"omissions, or irrelevant details.\n"
"3: The response addresses the instruction to "
"some degree but is incomplete, partially "
"correct, or unclear in places.\n"
"4: The response mostly adheres to the "
"instruction, with only minor errors, "
"omissions, or lack of clarity.\n"
"5: The response fully adheres to the "
"instruction, providing a clear, accurate, and "
"relevant answer in a concise and efficient "
"manner.\n\n"
"Now here is the instruction, the reference "
"answer, and the response.\n"
)
prompt = (
f"{rubric}\n"
f"Instruction:\n{instruction}\n\n"
f"Reference Answer:\n{reference_answer}\n\n"
f"Answer:\n{model_answer}\n\n"
f"Evaluation: "
)
return prompt
rubric_prompt中的model_answer代表实际场景中我们自己的模型生成的回答。为了演示,这里我们硬编码一个合理的模型回答,而不是动态生成。(不过你也可以用本文开头加载的Qwen3模型来生成真实的model_answer。)
接下来,我们生成渲染后的提示,传给Ollama模型:
rendered_prompt = rubric_prompt(
instruction=(
"If all birds can fly, and a penguin is a bird, "
"can a penguin fly?"
),
reference_answer=(
"Yes, according to the premise that all birds can fly, "
"a penguin can fly."
),
model_answer=(
"Yes – under those premises a penguin would be able to fly."
)
)
print(rendered_prompt)
输出如下:
You are a fair judge assistant. You will be given an instruction, a reference answer, and a candidate answer to evaluate, according to the following rubric:
1: The response fails to address the instruction, providing irrelevant, incorrect, or excessively verbose content.
2: The response partially addresses the instruction but contains major errors, omissions, or irrelevant details.
3: The response addresses the instruction to some degree but is incomplete, partially correct, or unclear in places.
4: The response mostly adheres to the instruction, with only minor errors, omissions, or lack of clarity.
5: The response fully adheres to the instruction, providing a clear, accurate, and relevant answer in a concise and efficient manner.
Now here is the instruction, the reference answer, and the response.
Instruction:
If all birds can fly, and a penguin is a bird, can a penguin fly?
Reference Answer:
Yes, according to the premise that all birds can fly, a penguin can fly.
Answer:
Yes – under those premises a penguin would be able to fly.
Evaluation:
提示以Evaluation:结尾,引导模型生成分数。我们来看看gpt-oss:20b模型对这个回答打几分:
result = query_model(rendered_prompt, ollama_model)
print(result)
回答如下:
**Score: 5**
The candidate answer directly addresses the question, correctly applies the given premises, and concisely states that a penguin would be able to fly. It is accurate, relevant, and clear.
可以看到,回答得到了最高分,这很合理,因为它确实是正确的。这只是一个手动演示的简单例子,我们可以把这个思路进一步扩展,写一个循环,用评估数据集里的问题逐个查询模型(比如前面加载的Qwen3模型),通过gpt-oss进行评估,然后计算平均分。你可以在这里找到这样的脚本实现:我们在MATH-500数据集上评估Qwen3模型,地址是https://github.com/rasbt/reasoning-from-scratch/tree/main/chF/04_llm-judge 。

图14: Qwen3 0.6基座版与推理版在MATH-500前10道例题上的对比,由gpt-oss:20b担任评审。代码见:https://github.com/rasbt/reasoning-from-scratch/tree/main/chF/04_llm-judge
用过程奖励模型为中间推理步骤打分
和符号验证器、LLM评审器相关的,还有一类被称为过程奖励模型(Process Reward Models,PRMs)的训练模型。和评审器一样,PRMs不止评估最终答案,还能评估推理轨迹;但和通用评审器不同的是,PRMs专门聚焦于推理的中间步骤。而且和验证器不同——验证器通常只在结果层面做符号化的正确性检查——PRMs会在强化学习训练过程中提供逐步骤的奖励信号。我们可以把PRMs归类为“步骤级评审器”,它们主要是为训练开发的,而非纯粹的评估。(实践中,大规模可靠训练PRMs难度很高。比如DeepSeek R1就没有采用PRMs,而是结合了验证器来做推理训练。)
基于评审的评估相比基于偏好的排行榜,优势在于可扩展性和一致性,因为它不依赖大量人类投票者。(从技术上讲,排行榜背后的偏好评级也可以外包给LLM评审器。)但LLM评审器也和人类投票者有类似的弱点:结果会受到模型偏好、提示设计、回答风格的偏差影响。此外,它高度依赖评审模型和评分标准的选择,并且缺乏固定基准测试的可复现性。
结论
本文我们介绍了四种不同的评估方法:选择题测试、验证器评估、排行榜和LLM评审器。
我知道这篇文章很长,但希望它能帮你全面了解LLM的评估方式。这种从零开始的讲解方式可能略显繁琐,但却是理解这些方法底层原理的绝佳途径,反过来也能帮我们发现它们的不足和改进方向。
说到这里,你可能会问:“那评估LLM的最佳方式是什么?”遗憾的是,没有唯一的最优解,因为正如我们所见,每种方法都有不同的取舍。简而言之:
-
选择题测试
-
优点:大规模运行速度相对较快、成本较低
-
优点:跨论文(或模型卡)标准化、可复现
-
缺点:只衡量基础的知识回忆能力
-
缺点:无法反映LLM在真实世界中的使用方式
-
验证器评估
-
优点:对有标准答案的领域,评分标准化、客观
-
优点:允许自由形式作答(对最终答案格式有一定约束)
-
优点:如果使用过程验证器或过程奖励模型,也可以为中间步骤打分
-
缺点:只适用于可验证的领域(比如数学或代码),并且构建优秀的验证器难度不小
-
缺点:只看结果的验证器只评估最终答案,不评估推理质量
-
竞技场式排行榜(人类两两偏好)
-
LLM-as-a-judge(LLM评审器)
虽然我平时不太喜欢雷达图,但在这里用它来可视化这些不同的评估维度还是很有帮助的,如下图所示。

图15: 雷达图示意:理想情况下,我们需要从不同维度评估LLM,才能识别它的优势和劣势。
比如,选择题得分高说明模型的通用知识扎实。如果再加上验证器得分高,那模型很可能也能正确回答技术问题。但如果模型在LLM评审器和排行榜评估中表现差,那它可能在写作或清晰表达方面有困难,可以通过RLHF来改善。
所以,最好的评估方式是多维度结合。但理想情况下,还要使用和你的目标或业务问题直接对齐的数据。比如,如果你要部署一个LLM来辅助法律相关工作,那跑一遍MMLU这类标准基准做快速 sanity check 是合理的,但最终你还是要针对目标领域(比如法律)定制评估。你可以在网上找到公开的基准测试作为很好的起点,但最终还是要用自己的专有数据来测试。只有这样,你才能比较有把握地确认模型在训练时没有见过这些测试数据。
总之,模型评估是一个非常庞大且重要的议题。希望这篇文章能帮你理解主流方法的工作原理,也希望下次你看模型评估或者自己做评估时,能从中获得一些有用的洞见。
一如既往,快乐折腾!
这本杂志是我的个人心血之作,你的支持就是它持续更新的动力。
如果你愿意支持我的工作,可以考虑我的这本书《Build a Large Language Model (From Scratch)》(https://amzn.to/4fqvn0D),或者它的续作《Build a Reasoning Model (From Scratch)》(https://mng.bz/Nwr7)。(我相信你会从中学到很多,它们对LLM工作原理的讲解深度是你在别处找不到的。)
感谢阅读,也感谢你支持独立研究!

《Build a Large Language Model (From Scratch)》现已在亚马逊上架:https://amzn.to/4fqvn0D
《Build a Reasoning Model (From Scratch)》抢先阅读地址:https://mng.bz/Nwr7
如果你读过这本书,能抽出几分钟时间的话,我会非常感谢你在亚马逊留下评价:https://www.amazon.com/Build-Large-Language-Model-Scratch/dp/1633437167 。这对我们作者帮助很大!
你的支持意义重大!谢谢!