Мой блог
Дообучение языковой модели для структурированного вывода за 100 шагов GRPO
Получение предсказуемого результата в заданном формате — это одна из самых востребованных прикладных задач при работе с языковыми моделями, обеспечивающая надежный структурированный вывод в реальных рабочих пайплайнах. Тем не менее, большинство стандартных бенчмарков объединяют эту способность с общими показателями рассуждений или извлечения данных, вместо того чтобы оценивать ее обособленно. Способность модели стабильно выдавать валидный, легко поддающийся парсингу ответ в нужной форме и структуре зачастую определяет, получится ли вообще интегрировать ее в проект. Важно отметить, что описываемый здесь процесс обучения отличается от того, который применялся для RL-модели в оригинальном блоге IFStruct. Цель этого руководства заключается не в том, чтобы повторить рекордный балл из бенчмарка, а в том, чтобы продемонстрировать, как специализированная дообученка компактных архитектур повышает общую эффективность и позволяет им соперничать с гораздо более крупными аналогами.
Организация рабочего процесса и требования
Представленный материал разделен на две части, выполняющиеся в совершенно разных окружениях. Сам процесс дообучения требует наличия графического процессора, а сопутствующий блокнот рассчитан на бесплатные тарифные планы в Google Colab или Kaggle. Оценку результатов можно проводить локально на ноутбуке (в данном случае использовался MacBook Pro с чипом Apple M5 Max и 36 ГБ объединенной памяти) посредством утилиты llama.cpp. Она поднимает совместимый с OpenAI сервер, с которым взаимодействует инструмент оценки IFStruct. Для управления зависимостями Python применяется менеджер uv, а для развертывания используется llama.cpp.
Опираясь на официальную документацию Liquid AI по развертыванию, установите утилиту через Homebrew и убедитесь в работоспособности сервера:
brew install llama.cppllama-server --version
Первичная оценка и проверка соответствия схеме
Прежде чем приступать к изменениям, полезно протестировать базовую модель LFM2.5-350M на бенчмарке IFStruct и проверить, удастся ли повторить заявленный показатель структурированного вывода на уровне 21,1%. Сам бенчмарк представляет собой открытый инструмент для проверки валидности ответов языковых моделей и соблюдения схем. Репозиторий проекта доступен в открытом доступе на GitHub по адресу Liquid4All/ifstruct, а датасет для тестов размещен на платформе Hugging Face как LiquidAI/ifstruct-v1.0.
Для загрузки репозитория используется стандартная команда клонирования:
git clone https://github.com/Liquid4All/ifstruct.git
Для сравнительного тестирования модель запускается локально на компьютере Mac с помощью инструментария llama.cpp. В качестве рабочей версии задействуется файл в формате BF16 GGUF — LiquidAI/LFM2.5-350M-GGUF.
Запуск серверной части базовой модели выполняется с помощью утилиты llama-server с конкретными параметрами конфигурации: указывается репозиторий LiquidAI/LFM2.5-350M-GGUF:BF16, контекст промпта размером 32768, обработка четырех запросов параллельно благодаря параметру -np 4, а флаг -ngl 99 задействует графический ускоритель для разгрузки всех слоев, если это возможно. Дополнительно задается псевдоним модели --alias LiquidAI/LFM2.5-350M, который утилита IFStruct передает на эндпоинт, совместимый с OpenAI, а также локальный адрес и порт 127.0.0.1:8080.
После активации сервера запускается полный бенчмарк, включающий 2000 тестовых примеров, через команду uv run ifstruct-eval с обращением к локальному базовому URL, фиктивным ключом API, набором данных в файле data/test.jsonl, четырьмя потоками и ограничением максимального числа токенов в 2048. Локальные замеры демонстрируют общую результативность базовой модели на уровне 452 успешно пройденных тестов из 2000, что составляет 22,6%, при среднем значении задержки в 1453 миллисекунды. В официальном релизном блоге IFStruct для данной архитектуры зафиксирован показатель 21,1%, поэтому полученные на локальной связке llama.cpp и BF16 результаты практически полностью совпадают с исходными данными и служат надежной отправной точкой для дальнейшего сравнения на том же стеке обслуживания.
Детализированные результаты базового прогона распределяются следующим образом:
- По форматам: формат JSON набрал 180 успешных прохождений из 1000 (18,0%), а YAML — 272 из 1000 (27,2%).
- По структуре верхнего уровня: наличие ключа-обертки показало 288 успешных результатов из 1011 (28,5%), в то время как чистый список справился в 164 случаях из 989 (16,6%).
- По типам сущностей: результаты варьируются от 4,3% для рецептов (3 из 70) и 6,4% для обзоров графических процессоров (6 из 94) до 37,0% для пакетов обращений в службу поддержки (27 из 73) и 37,8% для объявлений о недвижимости (31 из 82).
Наиболее распространенными ошибками при тестировании базовой версии стали пропуск обязательных полей (7228 случаев), неверное количество элементов (738 случаев), несоответствие типов данных (540 случаев), незакрытые блоки кода (317 случаев), а также появление лишних полей вроде notes, path, constraints и type, отсутствие нужных блоков кода и путаница между чистым списком и оберткой.
Полный рабочий конвейер доступен в сопутствующем блокноте, однако в данном разделе рассматриваются только ключевые фрагменты. В качестве обучающего набора задействован датасет nvidia/Nemotron-RL-instruction_following-structured_outputs, где каждый промпт сопоставлен с целевой схемой JSON Schema и ожидаемым количеством полей. Для тренировки задействовано около 500 примеров. Поскольку распределение данных в Nemotron отличается от оценочного набора IFStruct, промпты подвергаются искусственной аугментации для устранения выявленных расхождений: сорок процентов примеров получают дополнительную инструкцию возвращать результат внутри блока с обрамляющими символами кода, чтобы модель училась следовать формату вместо генерации чистого JSON, а еще двадцать процентов преобразуются в задачи с массивами верхнего уровня для отработки навыков формирования списков и соблюдения точного количества элементов. На финальном этапе загружается модель LiquidAI/LFM2.5-350M и к ней подключается адаптер LoRA для настройки под требуемый формат.
Настройка LoRA и ключевые функции вознаграждения
Поскольку модель LFM2.5 базируется на гибридной архитектуре, сочетающей в себе механизмы внимания и сверточные слои, настройка затрагивает специфичные для нее модули. Конфигурация LoRA задается с параметрами ранга r=16 и альфа lora_alpha=32 при отсутствии смещения (bias="none") и типе задачи CAUSAL_LM. В список целевых модулей включаются q_proj, k_proj, v_proj, out_proj, in_proj, а также слои w1, w2 и w3. Такой подход позволяет обучить около 6 миллионов параметров, что составляет приблизительно 1,66% от общего объема модели.
Для оценки результатов формируются три функции вознаграждения, каждая из которых оценивает любую генерацию по шкале от 0 до 1 в зависимости от корректности извлеченной структуры:
- json_format_reward определяет, поддается ли полученный результат парсингу и соответствует ли запрошенному формату. Высший балл в 1.0 начисляется за точный формат (с обрамлением в виде блоков кода или в сыром виде), 0.2 дается за валидный для парсинга, но неверный по форме вариант, а 0.0 — за нечитаемый вывод.
- field_count_reward проверяет наличие ожидаемого количества полей верхнего уровня. Точное совпадение приносит 1.0, а при расхождениях оценка линейно снижается.
- schema_validation_reward сверяет полученный ответ с JSON-схемой конкретной строки, учитывая каждое нарушение ограничений и распределяя частичные баллы на основе покрытия обязательных ключей.
Все три компонента объединяются в виде взвешенной суммы с весами reward_weights=[1.0, 0.5, 2.0]. Обучение проводится в течение 100 шагов с генерацией 8 вариантов на каждую группу промптов, что оптимизировано под параметры бесплатного уровня графического процессора с 16 ГБ видеопамяти. Параметры обучения включают скорость обучения 5e-5, 10 шагов разогрева, размер мини-выборки на устройство, равный 4, и накопление градиентов за 8 шагов, обеспечивающее обработку 4 групп промптов за один шаг оптимизатора.
Кроме того, конфигурация задает 2 шага на генерацию, максимальную длину завершения в 1024 токена для корректной работы с вложенными структурами JSON и отказ от маскирования обрезанных завершений. Температура выборки установлена на уровне 1.1 для поддержания разнообразия в группах, коэффициент KL-штрафа равен 0.01, а логирование и сохранение настроены соответствующим образом. Анализ блокнота показывает, что в ходе тренировки показатели всех трех вознаграждений растут, расхождение по KL-дивергенции с базовой моделью начинает увеличиваться после завершения фазы разогрева, а доля обрезанных генераций остается близкой к нулю.
На финальном этапе адаптер LoRA объединяется с базовыми весами, после чего сохраняется в виде единой автономной контрольной точки, полностью готовой к конвертации в формат GGUF для развертывания. Для проведения оценки IFStruct после завершения процесса градиентного обучения с подкреплением по предпочтениям полученный объединенный чекпоинт необходимо перевести в формат BF16 GGUF. Соответствующий скрипт поставляется вместе с исходным кодом утилиты llama.cpp, поэтому репозиторий клонируется один раз, после чего устанавливается входящий в его состав пакет gguf.
Экспорт модели и финальное тестирование
Чтобы запустить получившуюся нейросеть локально и оценить её возможности, сначала необходимо клонировать репозиторий инструментов, установить зависимости и конвертировать объединенные веса в формат GGUF. Для этого выполняются следующие команды в терминале:
git clone --depth 1 https://github.com/ggml-org/llama.cpp
pip install ./llama.cpp/gguf-py
mkdir -p models
python llama.cpp/convert_hf_to_gguf.py \
PATH_TO_YOUR_MERGED_MODEL \
--outfile ./models/lfm25-350m-grpo-bf16.gguf \
--outtype bf16
После завершения конвертации можно развернуть локальный сервер для обслуживания модели, используя утилиту llama-server. Команда задает имя алиаса, размер контекста 32768 токенов, параллельный запуск четырех потоков и максимальную разгрузку на графический процессор:
llama-server \
-m ./models/lfm25-350m-grpo-bf16.gguf \
--alias lfm25-350m-grpo-structured-output \
-c 32768 \
-np 4 \
-ngl 99 \
--host 127.0.0.1 \
--port 8081
Далее запускается полноценная оценка фреймворка IFStruct с дообученной нейросетью, которая обращается к локальному эндпоинту:
uv run ifstruct-eval \
--model lfm25-350m-grpo-structured-output \
--base-url http://localhost:8081/v1 \
--api-key dummy \
--dataset data/test.jsonl \
--results-file results/lfm25-350m-grpo.json \
--n-threads 4 \
--max-tokens 2048 \
-v
Результаты бенчмарка и анализ метрик
Сводные результаты тестирования конфигурации lfm25-350m-grpo-structured-output показывают общую успешность выполнения на уровне 29,7% (594 пройденных теста из 2000) при средней задержке ответа 1518 мс. Детализация по форматам демонстрирует 31,9% для JSON (319 из 1000) и 27,5% для YAML (275 из 1000). По типу структуры результаты распределились следующим образом: для ключа-обертки — 29,7% (300 из 1011), а для чистого списка — тоже 29,7% (294 из 989).
Если разобрать результаты по конкретным категориям и типам сущностей, показатели заметно варьируются:
- test__camera_review: 5/83 (6,0%)
- test__clinical_trial: 31/104 (29,8%)
- test__conference_schedule: 11/87 (12,6%)
- test__escaping__bug_report_batch: 32/89 (36,0%)
- test__escaping__config_snippet_audit: 24/85 (28,2%)
- test__escaping__customer_email_thread: 9/73 (12,3%)
- test__escaping__dialogue_sample: 17/95 (17,9%)
- test__escaping__interview_transcript_segment: 13/80 (16,2%)
- test__escaping__log_parser_examples: 33/72 (45,8%)
- test__escaping__pr_discussion: 26/87 (29,9%)
- test__escaping__repro_steps_batch: 23/73 (31,5%)
- test__escaping__screenplay_scene: 34/92 (37,0%)
- test__escaping__short_story_chapter: 24/84 (28,6%)
- test__escaping__support_ticket_batch: 36/73 (49,3%)
- test__escaping__terminal_session_notes: 23/70 (32,9%)
- test__event_ticket_booking: 62/107 (57,9%)
- test__gpu_review: 7/94 (7,4%)
- test__invoice: 36/86 (41,9%)
- test__job_posting: 33/85 (38,8%)
- test__real_estate_listing: 32/82 (39,0%)
- test__recipe: 7/70 (10,0%)
- test__rental_car_booking: 37/79 (46,8%)
- test__scientific_experiment: 14/69 (20,3%)
- test__travel_itinerary: 25/81 (30,9%)
Анализ наиболее частых ошибок помогает понять ограничения модели: в 7331 случае фиксировалось отсутствие обязательного поля, 890 раз не совпадало количество элементов, а 555 ошибок пришлись на несоответствие типов данных. Также встречались ситуации, когда вместо ожидаемого чистого списка модель выдавала обертку (102 раза), добавляла лишние поля вроде metadata.tone (62 раза), speaker_labels (49 раз), tone (47 раз) или notes (44 раза), либо превышала максимальное ограничение элементов (55 срабатываний) и использовала недопустимые единицы измерения вроде cups при разрешенных mg, g, kg, oz, lb, ml, l, cl, dl (44 раза).
Сравнение с базовой версией и выводы
Сопоставление двух прогонов на абсолютно одинаковом стеке обслуживания наглядно показывает эффективность примененного подхода:
- Overall (Общий показатель): база — 22,6%, после GRPO — 29,7% (прирост +7,1)
- JSON: база — 18,0%, после GRPO — 31,9% (прирост +13,9)
- YAML: база — 27,2%, после GRPO — 27,5% (прирост +0,3)
- Wrapper key (Ключ-обертка): база — 28,5%, после GRPO — 29,7% (прирост +1,2)
- Bare list (Чистый список): база — 16,6%, после GRPO — 29,7% (прирост +13,1)
Полученные улучшения приходятся ровно на те аспекты, на которые и было нацелено обучение: процент успешных ответов в формате JSON вырос почти на 14 пунктов, в то время как YAML остался практически на прежнем уровне. Хотя этот результат всё еще уступает показателю модели Qwen3.5-2B, составляющему 33,15%, он доказывает, что даже легкая специализированная настройка способна приблизить компактную нейросеть к более крупным аналогам.
Короткий цикл GRPO, включающий около 500 примеров и 100 шагов, позволяет поднять результативность крошечной модели с 350 миллионами параметров с 22,6% до 29,7% в бенчмарке IFStruct. Главный практический вывод заключается в том, что недорогой целевой сигнал вознаграждения делает небольшую модель существенно надежнее в соблюдении заданной формы, значительно сокращая отставание от систем, превосходящих её по размеру в несколько раз.
Источник: huggingface.co
