Что такое бенчмарк
Бенчмарк — это экзамен для промпта.
Берём пачку задач, к каждой заранее известен правильный ответ. Прогоняем твой промпт на живой модели по всем задачам подряд. Сравниваем то, что ответила модель, с правильным ответом, и считаем долю попаданий. В конце получается число.
Смысл в том, чтобы вместо «мне кажется, так промпт работает лучше» иметь «на двухстах примерах стало 0.83 против 0.71».
Экзамен состоит из трёх частей:
Сами задачи и эталонные ответы к ним. Одна задача — одна строка файла.
Небольшие куски кода, которые сравнивают ответ модели с эталоном и ставят оценку от 0 до 1. У каждой задачи свои — арифметику и перевод нельзя проверять одинаково.
На какой модели, сколько раз, с какими настройками. От этого зависит, повторяемая получилась цифра или случайная.
Какие датасеты здесь есть
В комплекте одиннадцать наборов. Четыре взяты из публичных научных корпусов, остальные собраны здесь: часть написана руками, часть сгенерирована по фиксированным правилам с зафиксированным зерном случайности (то есть пересобирается байт в байт).
| Датасет | Примеров | Что проверяет | Откуда |
|---|---|---|---|
entity-extraction | 6 | Вытащить людей и места из фразы в JSON. | Демо, чтобы пощёлкать кнопки |
entity-extraction-hard | 200 | То же, но с ловушками: титулы, отрицания, страна как прилагательное, повторы, случаи с пустым ответом. | 40 руками, 160 сгенерировано |
multiconer-en | 200 | Люди и места в предложениях, специально подобранных как неоднозначные. | публичный MultiCoNER v2 |
few-nerd | 200 | Люди, места, организации. | публичный Few-NERD |
gsm8k | 120 | Школьные текстовые задачи по арифметике в несколько действий. | публичный GSM8K |
mbpp | 80 | Написать функцию на Python по описанию. | публичный MBPP |
support-classification | 150 | Разложить обращения в поддержку по четырём категориям. 54 примера намеренно пограничные. | Сгенерировано |
summarization | 120 | Сжать документ до 170 символов, не потеряв ни одного имени, числа и даты. | Сгенерировано |
translation | 120 | Перевести с английского на испанский, обязательно применив заданный глоссарий. | Сгенерировано |
grounded-qa | 120 | Ответить строго по приложенным источникам. В 30 примерах ответа в источниках нет — надо честно сказать INSUFFICIENT_EVIDENCE, а не додумать. | Сгенерировано |
agents | 120 | Задачи, где нужно вызвать инструмент: калькулятор или счётчик слов. | Сгенерировано |
Не бери entity-extraction для выводов. Шесть примеров, и на них базовый промпт уже выдаёт 1.0 — расти некуда, любая техника выглядит одинаково хорошо. Это набор «посмотреть, как работает интерфейс». Реальный запас для улучшений есть у entity-extraction-hard, multiconer-en и few-nerd.
Что за публичные корпуса
Публичный корпус — это набор задач, который собрала и разметила чужая исследовательская группа и выложила открыто. Ценность в том, что данные никто здесь не подгонял под удобный результат.
| Корпус | Что это простыми словами | Лицензия |
|---|---|---|
| MultiCoNER v2 SemEval-2023, задача 2 | Соревнование по распознаванию имён, собранное специально из сложных случаев: названия, которые выглядят как обычные слова, редкие имена, неоднозначные фразы. Самый близкий публичный аналог нашего entity-extraction-hard. | CC-BY-4.0 |
| Few-NERD | Крупный размеченный набор предложений с людьми, местами и организациями. | CC-BY-SA-4.0 |
| GSM8K OpenAI | Школьные задачки «в лавке было столько-то, продали столько-то». Именно на нём в своё время показали, что просьба «рассуждай по шагам» даёт прирост. Здесь он нужен как противовес: все остальные наши задачи — чтение, а не счёт, и на чтении рассуждения обычно только мешают. | MIT |
| MBPP Google Research | Простые задачи на программирование, к каждой приложены свои тесты. Оценка ставится не сравнением текста, а запуском кода. | CC-BY-4.0 |
Импорт делается командой prompt-playoff import-hf, лицензия и ссылка на статью печатаются при каждом импорте.
Как импорт не портит данные
- Эталон вырезается прямо из готового предложения. Иначе имя в ответе и имя в тексте могли бы разойтись на пробел, и модель получала бы незачёт за чужую опечатку.
- Ненужный тип сущности превращается в пустой пример, а не в ошибку. Если в предложении из всех имён только название фильма, а схема просит людей и места — правильный ответ пустой, а не «незачёт».
- Часть пустых примеров оставляется намеренно. Если выкинуть все случаи, где отвечать нечего, промпт, который просто угадывает наугад, начнёт выглядеть отлично: лишним ответам негде проявиться.
- Ограничения корпуса записываются, а не прячутся. У Few-NERD, например, две соседние сущности одного типа слипаются в одну — это свойство самого корпуса, и оно указано в примечаниях к пресету.
Как проверяется ответ
Проверяльщик получает ответ модели и эталон, а возвращает число от 0 до 1. Их два семейства.
Проверки смысла — правильный ли ответ
field_f1 | Частичный зачёт по списку. Нашла три сущности из четырёх — примерно 0.86, а не ноль. Лишнее, чего в эталоне не было, тоже штрафуется. Это главная оценка для извлечения. |
exact_match | Совпало ли всё целиком, без поблажек. Почти всегда сильно ниже field_f1 — это нормально. |
label_accuracy | Угадана ли категория. Для классификации. |
numeric_close | Совпало ли число с эталоном. Оформление не учитывается — важен сам ответ. |
unit_tests | Код из ответа реально запускается и прогоняется через тесты задачи. Оценка — доля пройденных тестов. Запуск идёт в песочнице с ограниченным набором модулей. |
glossary_consistency | Доля обязательных терминов, переведённых так, как велел глоссарий. |
grounding_overlap | Какая доля слов ответа вообще встречается в приложенных источниках. Грубая, но рабочая проверка на выдумывание. |
contains_all | Все ли обязательные факты попали в ответ. |
coverage | Только полнота: сколько нужного нашлось. Лишнее не штрафуется. |
tool_success | Все ли вызовы инструментов вернули результат, а не ошибку. |
agreement | Насколько сходятся между собой несколько ответов, если техника генерирует их пачкой. |
Проверки формы — правильно ли оформлен
json_validity | Распарсился ли ответ как JSON. |
json_schema | Совпал ли со схемой полностью. |
schema_shape | Доля обязательных полей, которые на месте. Частичный зачёт по форме. |
no_prose | Нет ли болтовни вокруг ответа. |
allowed_labels | Метка взята из разрешённого списка, а не придумана. |
length_limit | Уложился ли ответ в лимит символов. |
omission_check | Не обрезан ли ответ и не раздут ли — длина сравнивается с длиной исходника. |
deduplication | Нет ли повторов внутри списков. |
python_syntax | Парсится ли код, который выдала модель. |
regex_match | Совпал ли ответ с заданным шаблоном. |
Судьи-модели здесь нет. Все проверки — обычный код. Один и тот же ответ всегда получает одну и ту же оценку, проверка ничего не стоит и не занимает времени, и её решение можно прочитать глазами.
Плата за это — нельзя оценить «красиво ли написано». Поэтому пересказ меряется через «все ли факты на месте и влез ли в лимит», а перевод — через «применён ли глоссарий и не потерялась ли половина текста». Про стиль бенчмарк ничего не скажет.
Как из проверок получаются две главные цифры
quality — это одна выбранная проверка смысла, та, что подходит задаче: field_f1 для извлечения, label_accuracy для классификации, numeric_close для арифметики, unit_tests для кода. В таблице Graders она помечена как headline, остальные показываются рядом для справки.
reliability — это доля правильно оформленных ответов, умноженная на стабильность (даёт ли модель один и тот же ответ на один и тот же вход).
Что с этими числами делать дальше — на странице Справка.
Пять правил, без которых цифра ничего не значит
1. Сто примеров минимум. На шести примерах разница в 5% — это один пример туда-сюда. На сорока примерах порог, ниже которого разница неотличима от шума, — примерно 0.04. Меньше этого не считается результатом.
2. Repeats = 3. При одном прогоне стабильность равна 1.000 по определению — не потому что стабильно, а потому что сравнивать не с чем.
3. Пустые примеры обязательны. Случаи, где правильный ответ — «ничего», держат промпт честным. Без них выигрывает тот, кто угадывает.
4. Оптимизацию смотри только на held-out. Оптимизатор подгоняется под примеры, которые видел. Прирост считается только на отложенной части, которую он не видел.
5. Цифра принадлежит паре «модель + датасет». Перенеси её на другую модель — и это снова догадка. Инструмент честно пишет measured там, где мерил, и prior only там, где предполагает.
Какие инструменты задействованы
| Что | Зачем | Нужно ставить? |
|---|---|---|
| Собственный движок | Прогон примеров, проверки, подсчёт качества, надёжности, стабильности, времени и токенов. Внешних зависимостей не требует. | Встроен |
| Ollama | Локальные модели на своей машине. Вариант по умолчанию: бесплатно и без ключей. | Отдельная программа |
| OpenAI-совместимые API | Облачные модели: OpenAI, Anthropic, DeepSeek, Together, OpenRouter, Groq, Fireworks. Годятся, когда локальная модель слишком слабая. | Нужен ключ в переменной окружения |
| Hugging Face datasets | Импорт публичных корпусов (import-hf). Hugging Face — главный публичный склад датасетов и моделей. | Дополнительно |
| DSPy | Библиотека автоматического подбора промптов. Даёт три алгоритма поиска для кнопки Optimize. | Дополнительно |
| promptfoo | Экспорт промпта в чужой прогонщик тестов, если у команды он уже используется. Выгружается только первая стадия техники. | Дополнительно |
| Langfuse / Phoenix | Трейсинг: складывают каждый вызов модели в общий журнал, чтобы потом посмотреть, что именно уходило и приходило. | Дополнительно |
Четыре алгоритма в Optimize
native | Встроенный жадный цикл: модель критикует свой же промпт и переписывает его, лучший вариант остаётся. Ничего доставлять не нужно. |
DSPy MIPROv2 | Байесовский подбор: предлагает варианты инструкции и примеров и учится на том, какие оценки они получили. На наших замерах это единственный, который дал прирост. |
DSPy GEPA | Эволюция: держит набор непобеждённых вариантов и скрещивает их дальше. |
DSPy BootstrapFewShot | Инструкцию не трогает, подбирает только примеры для промпта. |
Что уже намеряно
Готовые отчёты лежат в docs/benchmarks/. Коротко, что из них вышло:
- Тринадцать техник из научных статей на
few-nerd: одиннадцать из двенадцати проиграли простому базовому промпту. Порядок мест почти точно совпал с расходом токенов, только наоборот: чем больше техника «рассуждает», тем хуже читает. Извлечение сущностей — это чтение, а не рассуждение, и промежуточный пересказ уводит модель от исходного текста. - Свой оптимизатор против MIPROv2 на
entity-extraction-hard, одна и та же разбивка данных: встроенный жадный цикл дал ровно +0.000, MIPROv2 — +0.090, потратив вдвое меньше вызовов модели. - Облачная модель большого размера повела себя так же на чтении: отдельная стадия рассуждения ухудшила результат на 0.028 при 2.9× токенов и 4.4× времени. На арифметике обе техники упёрлись в потолок 1.000 — там разницу уже не видно.
Свои данные
Встроенные наборы — чтобы разобраться. Выводы про свою задачу можно делать только на своих данных. Файл .jsonl, по одной задаче на строку, загружается прямо в интерфейсе:
{"id": "1", "input": "Мара вошла в Вейр с Капитаном Орином.",
"expected": {"people": ["Мара", "Капитан Орин"], "places": ["Вейр"]}}
Обязательны только id и input. Проверяльщики подберутся сами по виду эталона; можно задать их вручную полем graders.
Главное правило. Эталонный ответ должен встречаться в тексте слово в слово. Если в тексте «Капитаном Орином», а в эталоне «Капитан Орин» — модель получит незачёт за то, что не угадала твою форму записи. И оставь примеры, где правильный ответ пустой.
Чего бенчмарк не умеет
- Оценить качество прозы. Судьи-модели нет — только проверяемые правила.
- Сказать что-то осмысленное на маленьком наборе. Шесть примеров — это не измерение.
- Дёргать любые инструменты в агентских задачах: работают только зарегистрированные, в комплекте идёт калькулятор.
- Выгрузить в promptfoo многостадийную технику — уезжает только первая стадия.
- Перенести результат на другую модель.
Подробности по каждому набору — в docs/datasets/, отчёты о прогонах — в docs/benchmarks/.