【转载】从零开始理解大语言模型评估的四大主流方法

原文地址: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从零搭建所有组件。

figure00

图0:推理能力是近年来LLM领域最激动人心、也最重要的进展之一,但如果只听过“推理”这个名词、只读过理论层面的介绍,也很容易产生误解。因此在《Build a Reasoning Model (From Scratch)》(https://mng.bz/Nwr7)中,我采用了手把手实操的思路,从零搭建推理型LLM。

该书目前处于抢先阅读阶段,已有超过100页内容上线,我刚刚又完成了约30页的内容,目前正在由排版团队处理。如果你已经加入了抢先阅读计划(非常感谢你的支持!),内容上线后你会收到邮件通知。

附言:当下LLM研究领域日新月异,我还在陆续消化我收藏夹里越来越多的论文,计划在下一篇文章中重点介绍其中最有意思的几项研究。

不过现在,让我们先来讨论LLM评估的四大核心方法,搭配从零开始的代码实现,帮你更好地理解它们各自的优缺点。


理解LLM的主流评估方法

实践中,训练好的LLM常见的评估方式有四种:选择题测试、验证器评估、排行榜和LLM评审器,如图1所示。学术论文、宣传材料、技术报告以及模型卡(LLM专属的技术报告)通常会包含其中两类或更多类方法的结果。

figure01

图1: 本文涵盖的四种评估模型概览

此外,这里介绍的四大类方法可以归为两大阵营:基于基准测试的评估和基于评判的评估,如上图所示。

(还有一些其他指标,比如训练损失、困惑度、奖励值等,但它们通常用于模型开发的内部环节。)

下面的小节将简要概述每一种方法,并给出相应示例。

方法1:评估选项作答准确率

我们从一种基于基准测试的方法说起:选择题作答。

历史上,应用最广泛的评估方法之一就是选择题基准测试,比如MMLU(全称为Massive Multitask Language Understanding,大规模多任务语言理解,https://huggingface.co/datasets/cais/mmlu)。为了说明这种方法,图2展示了MMLU数据集中的一道典型题目。

figure02

图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展示的思路一致,为方便阅读,下图再次放出。

figure02

图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 datasetsuv 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

figure04

图4: 基座模型与推理模型在MMLU高中数学子集上的表现

假设每个选项出现概率均等,随机猜测者(均匀选择A、B、C、D)的预期准确率是25%。所以基座模型和推理模型的表现都不算理想。

选择题作答的其他形式

注意本节为了演示,实现的是简化版的选择题评估,直接比对答案字母。实践中更常用的是对数概率评分(log-probability scoring)这类变体:我们衡量模型认为每个候选答案的可能性,而不只是检查最终输出的字母。(我们会在第4章讨论基于概率的打分。)对于推理模型,评估还可以包含:当把正确答案作为输入时,模型生成它的概率有多大。

figure05

图5: 其他MMLU打分方法已在这个仓库中说明和共享:https://github.com/rasbt/reasoning-from-scratch/tree/main/chF/02_mmlu

但无论使用哪种MMLU打分变体,评估的本质都是检验模型能否从预设的选项中做出选择。

MMLU这类选择题基准测试的局限在于:它们只衡量LLM从预设选项中做选择的能力,因此除了检查模型相比基座版本遗忘了多少知识之外,对评估推理能力的作用有限。它无法衡量自由写作能力,也无法反映模型在真实场景中的实用性。

尽管如此,选择题基准测试依然是简单实用的诊断工具:比如,MMLU高分不一定代表模型实际应用能力强,但低分却能凸显出潜在的知识缺口。


方法2:用验证器检查答案

和上一节讨论的选择题作答相关,基于验证的方法也通过准确率指标来量化LLM的能力。但与选择题基准测试不同的是,验证方法允许LLM给出自由形式的答案,然后我们提取出相关的答案部分,用所谓的“验证器”将答案与数据集提供的标准答案做对比,如图6所示。

figure06

图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 。)

figure07
figure07

图7: 基于验证的评估方法节选,代码见:https://github.com/rasbt/reasoning-from-scratch/blob/main/ch03/01_main-chapter-code/ch03_main.ipynb


方法3:用偏好度和排行榜对比模型

到目前为止,我们介绍了两种能给出可量化指标(比如模型准确率)的方法。但上述方法都没有从更整体的角度评估LLM,包括对回答风格的评判。本节我们讨论一种基于评判的方法——LLM排行榜,如图8所示。

figure08

图8: 本书覆盖主题的思路框架,聚焦于附录中介绍的基于评判和基于基准的评估方法。前面已经讲了基于基准的方法(选择题、验证器),现在我们引入衡量LLM性能的基于评判的方法,本小节重点讲排行榜。

这里介绍的排行榜方法是一种基于评判的思路:模型的排名不是依据准确率或其他固定基准分数,而是依据用户(或其他LLM)对其输出的偏好度。

一个很有名的排行榜是LM Arena(https://lmarena.ai/,前身为Chatbot Arena):用户对比两个指定或匿名模型的回答,投票选出自己更喜欢的那个,如图9所示。

figure09

图9: 基于评判的排行榜界面示例(LM Arena)。给两个LLM输入相同的提示,并排展示回答,用户投票选出更偏好的答案。

这些偏好投票会像上图那样收集起来,再聚合到所有用户中,生成一个按用户偏好度排名的模型排行榜。图10是LM Arena排行榜的当前快照(数据获取于2025年10月3日)。

figure10

图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,说明双方势均力敌。这种情况下,更新幅度为:

  • 获胜者:rating_winner + k_factor * (1 - 0.5) = rating_winner + 16

  • 失败者:rating_loser + k_factor * (0 - (1 - 0.5)) = rating_loser - 16

如果是大热门(高评级模型)赢了,expected_winner ≈ 1,胜者只加很少的分,败者也只扣很少的分:

  • 获胜者:rating_winner + 32 * (1 - 0.99) = rating_winner + 0.32

  • 失败者:rating_loser + 32 * (0 - (1 - 0.99)) = rating_loser - 0.32

但如果是冷门选手(低评级模型)赢了,expected_winner ≈ 0,胜者几乎拿到全部k_factor的分数,败者也扣差不多的分数:

  • 获胜者:rating_winner + 32 * (1 - 0.01) = rating_winner + 31.68

  • 失败者:rating_loser + 32 * (0 - (1 - 0.01)) = rating_loser - 31.68

顺序的影响

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

figure11

图11: Elo与Bradley-Terry排名对比;源代码见:https://github.com/rasbt/reasoning-from-scratch/tree/main/chF/03_leaderboards


方法4:用其他LLM评判回答

早期,LLM是用统计和启发式方法来评估的,包括BLEU指标——一种粗略衡量生成文本与参考文本匹配程度的标准。这类指标的问题在于,它们要求精确的单词匹配,没有考虑同义词、语序变化等情况。

如果我们想从整体上评判生成的回答文本,一个解决方案是使用上一节讲的相对排名和基于排行榜的方法。但排行榜的缺点在于,偏好对比带有主观性,因为需要人类反馈(以及收集反馈带来的种种挑战)。

另一种相关方法是:使用另一个LLM,搭配预定义的评分标准(即评估指南),将待评估LLM的回答与参考答案做对比,按照预设标准评判回答质量,如图12所示。

figure12

图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:

  • macOS和Windows用户:打开下载的Ollama应用,如果提示安装命令行工具,选择“是”。

  • Linux用户:使用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在后台运行,有三种实现方式:

  1. 在终端运行ollama serve命令(推荐)。这会将Ollama后端作为服务运行,通常地址是http://localhost:11434。注意,在通过API调用之前,它不会加载模型。

  2. 像之前一样运行ollama run gpt-oss:20b命令,但保持窗口打开,不要输入/bye退出会话。如前所述,这相当于在本地Ollama服务外包了一层简易的交互封装,底层用的是和ollama serve一样的服务API。

  3. Ollama桌面应用。打开桌面应用会自动运行相同的后端,并在上面提供图形界面,如图12所示。

figure13

图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

figure14

图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评审器)

    • 优点:可跨多种任务扩展

    • 优点:允许自由形式作答

    • 缺点:依赖评审器的能力(集成多个评审器可以提升鲁棒性)

    • 缺点:依赖评分标准的选择

虽然我平时不太喜欢雷达图,但在这里用它来可视化这些不同的评估维度还是很有帮助的,如下图所示。

figure15

图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工作原理的讲解深度是你在别处找不到的。)

感谢阅读,也感谢你支持独立研究!

figure12

《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 。这对我们作者帮助很大!

你的支持意义重大!谢谢!

Leave a Reply

Your email address will not be published. Required fields are marked *

*