又一个应用型大语言模型基准测试
这是我为自己制作的基准测试,用于测试语言模型在我关心的任务上的表现。我知道我关心这些任务,因为每个测试都直接源于我过去一年中要求大语言模型执行的任务。
例如,该基准测试中包含的测试可以评估模型以下能力:
- 将Python函数转换为等效但更快的C函数;
- 将Python字节码反编译为可执行的源代码;
- 解释经过压缩的JavaScript的功能;
- 识别某些数据的编码格式(在这种情况下是uuencode编码);
- 根据类似BNF的语法编写解析器;
- 将英语句子转换为SQL查询;或者,
- 编写一些bash单行命令
这个基准测试有两个特点使其变得有趣:
-
我实现了一种简单的数据流领域特定语言,使我(或其他任何人!)能够轻松添加新的测试,以真实评估模型的能力。
-
正是由于这一点,我编写了近100个测试,涵盖了在使用大语言模型作为助手时实际遇到的不同情况。
例如,这里有一个完整的测试,用于评估模型是否能编写"hello world"程序。
'Write a "hello world" program in python' >> LLMRun() >> PythonRun() >> SubstringEvaluator("hello world")
我首先要求模型编写hello world,然后运行该程序(在Docker容器中,在幕后无缝运行),然后检查stdout是否包含"hello world"。
(你应该将>>
运算符理解为"然后执行"。所以"a >> b"意味着"执行a,然后执行b"。)
更有趣的是,这里有一个测试用例,首先要求模型生成绘制国旗的代码。为了评估它,我运行代码,用另一个语言模型部分判断,然后通过与参考解决方案比较来全面评估输出。
"Write a C program that draws an american flag to stdout." >> LLMRun() >> CRun() >> \
LLMRun("What flag is shown in this image?") >> \
(SubstringEvaluator("United States") | SubstringEvaluator("USA") | SubstringEvaluator("America"))
这种DSL使我能够评估比我所知的任何其他评估基准更多样化、更复杂的行为。 这有助于确定模型是否能够执行我真正关心的任务。
结果
我已经在这个基准测试上评估了几个模型。以下是它们的表现:
- Claude 3.5 Sonnet: 48% 通过
- GPT 4o: 47% 通过
- Claude 3 Opus: 42% 通过
- GPT 4o Mini: 36% 通过
- Gemini 1.5 Pro: 32% 通过
- Mistral Large: 28% 通过
- GPT 3.5: 26% 通过
- Mistral Medium: 23% 通过
- Gemini 1.0 Pro: 17% 通过
完整的评估网格可以在这里查看。
这不是什么
一个严肃的学术基准测试。
更详细地说:这并不是为了严格评估模型在任何特定任务上的能力。它不是用来决定哪个模型更有能力、更有知识、更真实、偏见更少、危害更小、更一致、更有帮助,或其他任何东西。
这些问题并没有经过最优的提示工程。完全有可能-实际上很可能!-对某些问题的更好表述会让模型给出更好的答案。
但我很懒。
我不想提醒模型它是PYTHON专家, 告诉它如果给出正确答案我会给它10万美元小费, 否则我就杀死一只小猫,但请暂停...深呼吸...然后一步一步地思考后再回答。 (或者无论人们现在用什么咒语让模型发挥最佳效果。)
我只想输入我的问题并得到正确的答案。 所以这个基准测试就是为了测试这一点, 针对我实际上关心的问题类型。
失败一个问题并不意味着什么
由于我(经常是有意的)缺乏提示工程, 当一个模型没有通过一个问题时,你不会学到很多。也许我的问题措辞不当。也许在某些方面有歧义。
相反,这些测试的设计是让我在模型通过时学到东西。 你不可能在没有一些语言技能的情况下侥幸正确编译Rust程序。但你可能会因为给函数取了个我没预料到的名字而侥幸失败,所以你正确的代码就永远不会被调用。
这是什么
再说一遍,这只是我实际上要求语言模型为我解决的问题集合,
用于帮助各种编程任务,
穿插着一些我出于好奇问语言模型的问题。
这些问题大多是我输入时未经修改的原始问题。
这意味着它们可能不是措辞最清晰的
(例如,In python what __thing__ do I use for ~, kind of like how __add__ is for +
,
我期望的答案是__inv__
)。
其他问题是"不公平的",因为它们需要最新的知识
(例如,"llama-2 70b的隐藏维度是多少?")。
但我关心模型是否能为我正确回答这些问题。
安装
让这个基准测试运行起来相当简单。
Python 依赖
在Python方面,你只需要运行
pip install -r requirements.txt
来安装Python依赖项。
如果你想运行并评估广泛的模型,你还需要
pip install -r requirements-extra.txt
来安装其他模型。
Podman (首选)
我想在容器中运行东西以保持基本安全。 Docker更好,安全控制稍微好一些(所以如果你愿意,可以在下面使用),但在Linux上你需要是root用户或给你的用户几乎root级别的权限才能启动新的Docker作业。这让我有点担心。
所以我更喜欢使用podman。根据你的系统应该如何安装它。
Docker (可选)
同样,这在很大程度上取决于系统,所以你需要去其他地方找出如何为你的系统安装它。
为什么我需要docker/podman?
这个基准测试中的测试用例是通过直接执行语言模型输出的代码来评估的。 一些测试要求模型重命名文件、移动文件,或对你的机器进行其他改变状态的操作。
虽然我不认为这些模型会出于恶意或怨恨而输出rm -rf /
,
但它们完全有可能(甚至很可能!)会产生有bug的代码,只是意外地破坏你的电脑。
因此,为了防止这种情况,所有LLM输出都在一个临时的docker容器中评估,
该容器在测试完成后立即删除。
(还有另一个原因:一些测试假设是在全新安装的Ubuntu上,在各个地方有特定的依赖项。这些测试在你的本地机器上的行为可能与在docker虚拟机中的行为不同。)
如果你喜欢冒险(非常不推荐),那么代码中有一个标志
I_HAVE_BLIND_FAITH_IN_LLMS_AND_AM_OKAY_WITH_THEM_BRICKING_MY_MACHINE_OR_MAKING_THEM_HALT_AND_CATCH_FIRE
你可以将其设置为True,然后这将直接在你的机器上eval()从LLM输出的所有内容。
设置
一旦你安装好了所有东西, 在运行基准测试之前还有几个设置步骤。
添加API密钥
你应该为任何你想评估的模型添加API密钥。密钥存储在config.json文件中。你可以在config.json.example找到一个模板
无论你正在测试哪个模型,你还需要加载OpenAI的API密钥作为默认评估模型。这是因为一些问题需要第二个语言模型来判断正确性。 这些二次评估尽可能简单,但在这里使用高质量模型有助于确保结果的一致性。
我使用gpt-4-turbo作为评估模型取得了很好的效果,但你可以配置任何你想要的模型作为评估器。在我的实验中,我使用(更便宜的)gpt-3.5-turbo得到了几乎相同的结果,但在少数情况下,使用更强大的评估模型会给出更可靠的结果。
设置docker/podman容器 [强烈推荐]
首先,你需要创建测试将在其中运行的docker容器。 这首先需要你在机器上安装docker。 完成后,你可以构建镜像:
docker build -t llm-benchmark-image . # 如果你使用docker
podman build -t llm-benchmark-image . # 如果你使用podman
设置selenium/chrome
一些测试用例需要Selenium和Chrome来测试模型是否可以生成有效的html/javascript程序。安装requirements文件应该会为你安装selenium,但你还需要确保安装了chrome。如果你使用的是ubuntu,那么你可以直接运行
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo dpkg -i google-chrome-stable_current_amd64.deb
运行基准测试
一旦你设置好环境,你就可以用一行命令运行整个基准测试:
python main.py --model gpt-3.5-turbo --run-tests --generate-report
这个命令将在一个模型上运行每一个配置的测试。
因此,这将需要一些时间,也会花费你几美元的语言模型查询费用。之后你可以在
evaluation_examples
目录中查看完整的结果html文件。
它还会保存这次运行的缓存,以便下次你可以运行一个新模型并并排查看两个结果。这些默认保存在 results/[current git commit hash]/[model name]目录中。
如果你想运行单个测试用例,你也可以用两种方式来做。 一种是直接运行测试
PYTHONPATH='.' python tests/print_hello.py
- 探索
run_a_simple_testcase.ipynb
笔记本,在Colab上快速运行一个示例测试用例。
另一种方式是,如果你想保存这次运行的结果以便以后加载, 可以运行main脚本并指定你想运行的测试。 (但要小心,因为这样做会覆盖任何先前的运行。)
python main.py --run-tests --test print_hello --model gpt-3.5-turbo-0125
如果你之前生成了许多保存的运行,你可以将它们加载到一个网格中
python main.py --load-saved --generate-report --model [model1] --model [model2]
最后,如果你之前在某个git提交上运行过测试,并且只想运行自那以来发生变化的测试,你可以运行
python main.py --run-tests --only-changed [commit hash] --model gpt-3.5-turbo-0125
修改基准测试
添加你自己的LLM
这个框架相当模块化。 如果你想测试一个新的LLM,你只需要在llms/[model_name]_model.py中添加一个新文件, 实现一个推理函数。然后,修改llm.py以包含适当的模型。
添加新的测试用例
如上所述,我构建了一个小型数据流DSL来实现测试用例。 这应该让任何人都可以相当容易地添加新的测试用例。 让我重复一下最初的测试用例示例:
TestHelloWorld = 'Print "hello world" in python' >> LLMRun() >> \
ExtractCode() >> PythonRun() >> SubstringEvaluator("hello world")
记住,你应该将>>
运算符理解为"然后执行"。
如果你是bash用户,它就像|
管道。
如果你是haskell用户,它就像$
运算符。
- 我们首先用提示"Print "hello world" in python"运行我们想要测试的LLM。
- 模型可能会给出一些代码,但也可能会给出解释或以"当然!我可以回答你的问题。"开头。所以我们取模型输出的任何内容,并将其传递给一个函数,只提取第一个代码块。
- 然后我们实际去运行这个Python代码,无论它是什么。为了保持某种程度的安全,我们通过产生一个新的docker环境并在那里运行代码来做到这一点。
- 最后,我们验证代码是否正确,Python的输出是否包含"hello world"子字符串。 这个数据流管道还可以让你进行更复杂的验证。 以下是与前面相同的示例,我在其中评估模型理解如何绘制.bmp图像的能力。
TestFlagDraw = "编写一个C程序,将美国国旗以bmp格式输出到stdout。" >> \
ExtractCode() >> CRun() >> LLMVisionRun("这幅图像中显示的是什么国旗?") >> \
(SubstringEvaluator("United States") | \
SubstringEvaluator("USA") | \
SubstringEvaluator("America"))
在这里,在要求模型绘制国旗并运行生成的C代码后,我通过询问另一个模型画出了什么国旗来评估模型,并检查它是否说类似美国国旗的内容。 这是一个完美的检查吗?不是。 但验证通常比生成更容易,所以这可能是我想要的一个足够好的近似。
贡献
如果你想为这个基准测试添加自己的测试,欢迎提交PR! 我很乐意接受几乎任何有趣的内容。
添加新测试
添加测试只有几个要求。
-
测试用例必须是机械可验证的。我知道这很有限制。我使用LLM的很多内容都无法以这种方式验证。特别是当我给它们大块代码并要求进行难以单元测试的特定更改时。但为了使这些测试有用,你的测试必须易于验证。
-
测试用例应该快速完成。我不想仅仅为了运行一个测试就等待几分钟。
-
测试在构建过程中不应该针对LLM进行评估。不要因为模型给出了你不喜欢的答案就修改测试。大多数LLM都足够随机,只要经过足够的试错,就可以以某种方式引出大多数行为。我想看看模型如何回答人工编写的测试,就像它们通常被问到的那样,在LM细化之前。
-
测试的设计应该使通过能够展示一些有趣的模型能力。 设计旨在显示模型在某些方面失败的"陷阱"测试在这种设置中是没有用的。
-
测试用例不得从互联网下载大量数据。 其他人不应该为每次运行这个基准测试而付费。 如果你需要测试一个库,请将其添加到Dockerfile中。
修复测试
这里有任何损坏的测试吗?我尽力使它们都正确,但不能保证绝对正确。如果有,我很乐意接受修复。
但请注意:损坏的测试意味着答案是客观错误的。比如一个说6是质数的测试。一个只是期望对模糊问题给出特定答案的测试并不是错误的。例如,有一个测试问 "我应该如何修改AutoModel.from_pretrained以使其成为带有lm head的auto model" 并期望模型告诉我应该使用"AutoModelForCausalLM"类; 尽管"AutoModelWithLMHead"类确实存在,但那不是我想要的。
我想在学术论文中引用这个
你可能不应该这么做。至少,如果你试图比较为什么你的新模型更好或类似的内容,你可能不应该这么做。 这不是为学术论文设计的,而且只评估了一组非常具体的能力。 出于前面提到的所有原因,我不认为这个基准测试能准确捕捉学术人士应该关心的模型特性。 对于"对我有用吗?"很好:是的。对于"我的模型更好吗?":我不这么认为。 但现在已经有至少几个人问我这个问题,他们似乎不为上述论点所动。
所以这是我的答案:如果你想在论文中使用这个, 那么链接到这个github项目并包含你使用的GIT COMMIT HASH。 我不保证不会在没有警告的情况下随意编辑测试用例。 事实上,这已经在#1、#3和#6中发生过了! 所以如果你希望你的论文具有科学性,请确保包含git commit hash。
许可证
版权所有 (C) 2024,Nicholas Carlini nicholas@carlini.com。
本程序是自由软件:你可以根据自由软件基金会发布的GNU通用公共许可证的条款重新分发和/或修改它, 可以选择使用该许可证的第3版或(由你选择)任何更新的版本。
本程序的发布是希望它能有用, 但没有任何保证;甚至没有对适销性或特定用途适用性的暗示保证。 更多细节请参阅GNU通用公共许可证。
你应该已经收到了一份GNU通用公共许可证的副本 连同本程序。如果没有,请参阅http://www.gnu.org/licenses/。