Как заставить LLM эволюционировать

Как заставить LLM эволюционировать

Привет, Хабр! На связи Олег и Камилла из команды применения больших языковых моделей ecom.tech. Сейчас мы разрабатываем сервис по генерации кода с учётом внутренних конвенций и правил. В процессе работы часто сталкиваемся с ситуацией, когда агент сначала генерирует код, а затем пишет к нему тесты. Они выглядят корректно, но не всегда соответствуют внутренним стандартам и в полной мере покрывают нужный функционал.

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

Мы задались вопросом: можно ли адаптировать небольшую LLM, например Qwen3-4B-Instruct, для генерации качественных unit-тестов на Kotlin с учётом специфики нашей команды? Решили попробовать сделать это с помощью эволюционного алгоритма — довольно экзотического способа дообучения. А затем сравнили его с классическими методами: SFT и GRPO.

Как можно дообучать LLM?

Часто путают цель дообучения и технику обновления параметров. Например, фраза «дообучаю модель с помощью LoRA» не совсем точна. SFT и RL — это способы оптимизации. LoRA — это эффективный метод снижения числа обучаемых параметров, который можно использовать, например, вместе с SFT.

RL-подходы, такие как GRPO (основной инструмент для обучения современных LLM), полезны, когда нужно оптимизировать модель не только по эталонным ответам, но и по функции вознаграждения (reward).

В нашем случае это особенно важно: качество unit-теста нельзя свести к «похожести на эталон». Нас интересует не только форма кода, но и его практическая полезность. Однако RL чувствителен к настройке reward-функции. Если награда слабо связана с реальной ценностью теста, оптимизация может стать нестабильной.

Эволюционные алгоритмы: голодные игры для моделей

В стандартных RL-методах мы исследуем пространство действий через одну точку — параметры текущей модели. А что, если исследовать всё пространство сразу — множеством точек и агрегировать результаты? Именно это предлагает алгоритм Evolution Strategies (ES).

Возьмём, например, 30 копий модели. К каждому параметру добавим гауссовский шум — получим 30 «возмущённых» версий. Каждая модель генерирует ответ, за который начисляется награда.

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

Интуитивно: мы создаём множество случайных версий модели, оцениваем их, и двигаемся в сторону тех, что показали лучшие результаты. Вместо градиента ES использует усреднение по множеству направлений в пространстве параметров.

Этот подход не нов. Его впервые описали ещё в 1973 году. В 2017 году OpenAI возродил ES для обучения агентов в MuJoCo и Atari, достигнув результатов, сопоставимых с TRPO. Тогда модели были небольшими — до 1 млн параметров.

Преимущества ES: не нужно хранить градиенты, активации и состояние оптимизатора. Это снижает потребление памяти и упрощает распараллеливание.

В 2025 году Cognizant AI Lab вернулся к ES, применив его к LLM с 10 млрд параметров. При этом оказалось, что достаточно всего 30 «особей» — в отличие от тысяч в ранних реализациях. Это своего рода «парадокс благословения размерности».

Практические преимущества ES

ES даёт не только экономию ресурсов и простоту параллелизации. Он также:

  • Менее чувствителен к выбору стартовой модели: там, где RL может не сойтись, ES стабильно прогрессирует.
  • Реже страдает от reward hacking: вместо поиска «читерского» способа максимизировать награду, ES оптимизирует распределение решений, которое сложнее взломать.
  • Не деградирует на длинных последовательностях.
  • Проще в использовании: требует меньше тюнинга и даёт более воспроизводимые результаты.

В задаче Countdown (составление арифметического выражения) ES превзошёл лучшие RL-методы на 10–20% по accuracy. В Math reasoning (на базе Qwen2.5-Math-7B) он показал результаты на уровне SOTA или даже лучше. Хорошо справляется и с головоломками вроде Судоку и ARC-AGI.

Технические детали реализации

Ключевые особенности реализации ES:

  • Шум к параметрам не хранится целиком — сохраняется только random seed, из которого его можно восстановить. Это экономит память.
  • Шум добавляется и вычитается слой за слоем. Пиковое потребление GPU-памяти определяется размером самого большого слоя.
  • Награды нормализуются с помощью z-оценки, что стабилизирует шкалу между итерациями.
  • Все «возмущённые модели» работают в режиме greedy decoding — без случайности. Различия в ответах объясняются исключительно различиями в весах.

Хранение шума через seed и детерминированная генерация позволяют восстановить модель на любой итерации, если логировать награды. Правда, для этого нужно написать свой код — «из коробки» такая функция пока не поддерживается.

Важно: ES в статье применяется только к моделям, уже прошедшим pretrain. Насколько он эффективен при обучении с нуля — предстоит выяснить.

Описание данных

Мы решили проверить ES на реальной задаче: генерации unit-тестов для бэкенда на Kotlin.

На вход модель получала не просто код класса, а полный контекст, необходимый для осмысленной генерации тестов. Например, для класса CarService включались все зависимые классы, интерфейсы и конфигурации.

Датасет собирали в два этапа:

  1. Выделяли unit-тесты по naming patterns.
  2. LLM-агент собирал вокруг тестируемого класса полный контекст.

Получились пары: контекст → файл с тестами. Всего — 1500 примеров (1300 train, 200 test).

Для оценки использовали две метрики: Coverage и CodeBLEU.

Coverage — это functional coverage: пересечение публичных функций, покрытых в эталонном и сгенерированном тестах. Метрика показывает, насколько генерация затрагивает нужный функционал.

CodeBLEU — адаптация BLEU для кода. Учитывает:

  • Совпадение n-грамм (с весами: ключевые слова важнее).
  • Синтаксическую структуру через AST (игнорируя имена переменных).
  • Поток данных (DFG) — зависимости между переменными.

Итоговый скор — взвешенная сумма этих компонент. CodeBLEU отвечает на вопрос: «Насколько код похож на хороший тест по форме?», а Coverage — «Касается ли он нужной логики?».

Поскольку оригинальный фреймворк не поддерживал Kotlin, мы добавили поддержку через tree-sitter-kotlin — внедрили синтаксический и семантический анализ, а также список ключевых слов.

Reward-функция — взвешенная сумма CodeBLEU (вес 0.6) и Coverage (вес 0.4). Больший вес у CodeBLEU, так как он лучше отражает качество генерации.

Запускаем эксперимент

Мы использовали открытый репозиторий Evolution Strategies at Scale. Взяли версию с инференсом на vLLM — она ускоряет обучение в 10 раз.

Аппаратная база: кластер из 8 GPU H100.

ES пока не имеет специализированных фреймворков. На одном GPU можно эффективно использовать только одну копию модели.

Авторы оригинальной статьи проходили по всему датасету на каждой итерации. У нас — 1300 примеров, 30 моделей, 1000 итераций. Это слишком дорого. Поэтому мы внедрили батчинг: на каждой итерации выбирали случайные 32 примера.

Уже после 500 итераций наблюдался устойчивый рост метрик на валидации. К концу обучения:

  • CodeBLEU вырос на +21.3%,
  • Coverage — на +18.6%.

Лучший результат показал ES: максимальный coverage (0.7381) и лучший итоговый reward. Результаты превзошли даже Qwen3-Coder-480B.

SFT показал высокий CodeBLEU, но низкий coverage: модель генерирует синтаксически правильные, но бесполезные тесты. GRPO привёл к деградации обеих метрик — оптимизация оказалась нестабильной.

Катастрофическое забывание

Недавно исследователи из UC Berkeley выявили серьёзный побочный эффект ES — катастрофическое забывание. При дообучении модель теряет ранее усвоенные навыки.

В эксперименте сравнивали ES и GRPO на моделях 1B–1.5B параметров. Оценивали по бенчмарку HellaSwag — выбор логичного продолжения бытовой ситуации.

Результаты:

  • При обучении ES accuracy на HellaSwag постоянно падала.
  • GRPO сохранял уровень, лишь слегка колеблясь.

При этом на целевой задаче GRPO показал результат, сопоставимый с ES.

Причина — в «плотности» обновлений. В ES каждый параметр модели обновляется на каждой итерации. Анализ показал: разреженность обновлений у ES крайне низкая, а норма изменений — на порядки выше, чем у GRPO.

Градиентные методы концентрируют изменения в подпространствах, связанных с задачей. ES же вносит глобальное смещение, сильно отклоняя модель от исходной — отсюда и потеря общих навыков.

Мы проверили это на своей модели, оценив её по бенчмарку GPQA — сложным научным вопросам уровня аспирантуры.

Результаты подтвердились:

  • Zero-shot: снижение accuracy на 2.1%.
  • Five-shot CoT: падение на 5.3%.

При этом польза от five-shot упала на 41–72%, что указывает на ухудшение in-context learning. Похоже, специализация на генерации кода сместила внутренние представления модели, ослабив её способность к научным рассуждениям.

Evolution Strategies — мощная альтернатива RL

Мы лично убедились: на нашей задаче ES дал отличный прирост метрик и повторил закономерности из оригинальной статьи. Подход работает и хорошо переносится на новые кейсы.

Да, есть шероховатости — в первую очередь, катастрофическое забывание. Но темпы развития AI таковы, что элегантные решения этих проблем — вопрос ближайшего времени.

Если ресурсы не ограничены, а сохранение общих навыков критично — GRPO остаётся надёжным выбором. Но эволюция эволюционных алгоритмов идёт полным ходом. Запасайтесь попкорном — самое интересное только начинается.

Читать оригинал