Перейти к основному содержимому

Сломанный бенчмарк: почему eval-харнессы нуждаются в собственных регрессионных тестах

Прогон бенчмарка на 27 моделях выявил, что источником ошибки был харнесс, а не модели. Eval-код — это продакшен-софт: версионируйте его, запускайте канареечные тесты и проверяйте на известных ответах.

Макросъёмка печатной платы с дорожками, паяными соединениями и одним смещённым компонентом при малой глубине резкости.

Сломанный бенчмарк

В публичном разборе описывается команда, которая тестировала 27 моделей с открытыми весами и обнаружила, что источником ошибки был сам харнесс оценки, а не модели. Инстинктивно это воспринимается как конфуз. Это не так. Это норма. Код оценки дрейфует быстрее кода моделей, потому что пишется в условиях дедлайна, латается под неудобные выводы и редко рецензируется как самостоятельная система.

Цена этого дрейфа тихая и накопительная. Рейтинги становятся несопоставимыми от недели к неделе, и никто этого не замечает. Модель, набравшая 71 процент в марте и 68 в апреле, не обязательно деградировала; мог измениться экстрактор, мог обновиться зависимый пакет, или несколько заданий могли утечь в обучающий корпус. Число меняется, за ним следует нарратив, а измерительный аппарат под ним никогда не аудируется.

Три класса дефектов харнесса

Большинство отказов харнесса попадает в три категории. Они не экзотичны. Это обычные режимы отказа любого пайплайна данных, который принимает неструктурированный текст и выдаёт скаляр.

Дефекты парсинга

Экстрактор, который молча принимает некорректный вывод модели, завышает долю прохождений. Грейдер, ожидающий JSON-объект с полем answer, может откатиться к регулярному выражению, затем к поиску подстроки, затем к значению по умолчанию True. Каждый откат — это небольшая любезность к модели и небольшая ложь читателю. Отказ невидим, потому что выдаёт число, а не исключение.

Механизм стоит назвать точно. Типичная цепочка экстрактора выглядит так:

def extract(text):
    obj = try_json(text)
    if obj is not None:
        return obj.get("answer")
    m = re.search(r"answer[:\s]+(.+)", text, re.I)
    if m:
        return m.group(1).strip()
    return text.strip()  # last resort

Именно на финальном return text.strip() и умирают доли прохождений. Он превращает непарсибельный ответ в строку, которая может случайно совпасть с эталоном. Решение — не лучшее регулярное выражение, а строгий режим, который выбрасывает исключение при непарсибельном выводе и фиксирует отказ как ошибку грейдера, отдельно от ошибки модели.

Aerial view of rippled golden sand dunes under a cloudy sky in Penong, South Australia

Контаминация

Задания, утёкшие в обучающие данные, нельзя исправить промпт-инжинирингом. Задание бенчмарка, дословно встречающееся в предобучающем корпусе модели, измеряет запоминание, а не способность. Обнаружить контаминацию сложно, но первый шаг процедурный: выделите приватный сплит, захешируйте публичные задания и проверьте на почти-дубликаты по любому корпусу, который можете инспектировать. Там, где инспекция невозможна, сообщайте о риске, а не о балле.

Дрейф окружения

Версии токенизатора, заглушки инструментов и таймауты меняются между прогонами. Патч-релиз токенизатора может изменить, сколько токенов потребляет промпт, что меняет поведение усечения, что меняет баллы на заданиях с длинным контекстом. Заглушка инструмента, возвращающая заготовленный ответ в одном прогоне и живой ответ в следующем, меняет задачу целиком. Таймаут, который был 30 секунд в январе и 10 секунд в марте, непропорционально накажет модели, нагруженные рассуждениями. Ничто из этого не является изменением модели. Всё это двигает таблицу лидеров.

Харнессу нужен набор известных ответов

Лекарство то же, что и для любого продакшен-сервиса: набор тестов, который запускается при каждой оценке, а не от случая к случаю.

Включите намеренно сломанные решения, которые обязаны провалиться, и тривиальные, которые обязаны пройти. Решение, возвращающее пустую строку, должно получить ноль. Решение, возвращающее эталонный ответ дословно, должно получить единицу. Решение, возвращающее правдоподобный, но неверный ответ, должно получить ноль. Если любое из этих ожиданий переворачивается, харнесс сломан, а прогон недействителен. Это канарейка в строгом смысле: известный вход с известным выходом, проверяемый до того, как реальная нагрузка будет признана достоверной.

Зафиксируйте версии модели, токенизатора, шаблона промпта и заглушки инструмента в едином манифесте. Один файл, закоммиченный в репозиторий, на который ссылается каждый результат. Если манифест изменился, это новый прогон, и результаты несопоставимы с предыдущим. Та же дисциплина, что и с lock-файлом, применённая к оценке.

Публикуйте доверительные интервалы, а не точечные оценки. Разрыв в два пункта на 200 заданиях — это шум. Публикация одного числа приглашает читателей ранжировать модели, сClose-up of a 3D printer print head and cooling fan forming an object.

Server rack closeup dark

Отчётность, выдерживающая проверку

Бенчмарк — это утверждение, а утверждения должны поддаваться аудиту.

Сообщайте результаты по категориям с размерами выборок. Агрегированные баллы скрывают тонкие срезы. Модель, показывающая хороший общий результат, но имеющая 12 заданий в категории, наиболее релевантной случаю читателя, не является доказательством для этого случая. Показывайте количество рядом с баллом, чтобы читатель мог сделать соответствующую скидку.

Храните сырые транскрипты и выводы грейдера. Когда балл оспаривается, спор должен разрешаться повторным разбором, а не повторным прогоном в надежде. Сохраняйте полный транскрипт, извлечённый ответ, эталон и решение грейдера. Это также позволяет переоценить исторические прогоны под исправленным грейдером — единственный честный способ сравнивать результаты после исправления харнесса.

Версионируйте eval-набор как код и считайте любое молчаливое изменение ломающим. Переформулированное задание, исправленный эталон, удалённое из-за неоднозначности задание: каждое из этих изменений меняет смысл всех баллов, вычисленных после него. Помечайте eval-набор тегом, записывайте тег в манифест, и когда правка необходима, публикуйте до и после, а не перезаписывайте молча.

Честный компромисс

Строгость стоит пропускной способности. Строгий экстрактор, выбрасывающий исключение при непарсибельном выводе, аннулирует больше прогонов, а набор известных ответов добавляет фиксированные накладные расходы к каждой оценке. Команды под давлением сроков будут соблазняться запускать в мягком режиме и чистить позже. Контраргумент: мягкий прогон даёт числа, которым нельзя доверять, а недостоверные числа хуже отсутствия чисел, потому что они проникают в решения.

Практический компромисс — запускать строго по умолчанию и допускать мягкий режим, явно помеченный в выводе. Метка путешествует с результатом. Читатель, видящий lenient: true, знает, что балл нужно дисконтировать. Читатель, видящий lenient: false и пройденный набор известных ответов, знает, что аппарат был проверен.

Что это меняет

Отношение к харнессу как к продакшен-софту меняет три вещи. Оно переносит eval-код в систему контроля версий с теми же стандартами ревью, что и код сервинга. Оно делает набор известных ответов воротами, а не запоздалой мыслью. И оно заставляет каждый опубликованный балл нести своё происхождение: какой манифест, какой тег eval-набора, какие размеры выборок, какой интервал.

Ничто из этого не делает бенчмаркинг лёгким. Оно делает бенчмаркинг честным. Команда, которая протестировала 27 моделей, а затем починила собственный бенчмарк, сделала работу, которую пропускает большинство публикуемых таблиц лидеров. Результат — число, которое можно защитить, а это единственный вид, достойный публикации.

ПоделитьсяXLinkedInПостоянная ссылка5 мин чтения

Связанное чтение

На какие вопросы это отвечает

What is a known-answer suite in an evaluation harness?

A set of submissions with predetermined correct scores, run on every evaluation. Deliberately broken submissions must fail; trivial ones must pass. If any expectation flips, the harness is broken and the run is void.

Why are benchmark rankings non-comparable across weeks?

Because the harness drifts. Tokeniser versions, prompt templates, tool stubs, timeouts, and extractor fallbacks change between runs, moving scores without any model change. Pinning these in a manifest makes drift visible.

How should benchmark results be reported?

Per-category with sample counts, with confidence intervals rather than point estimates, and with raw transcripts and grader outputs retained so disputed scores can be re-adjudicated.