Sakhanda Wire
NVDA $230.86 +1.09% MSFT $512.80 -0.02% GOOGL $338.24 -1.70% META $725.93 +0.10% AMZN $248.23 -0.37%
← К новостям

Дообучение модели на 350 млн. параметров для улучшения структурированных выходных данных за 100 шагов GRPO

Дообучение модели на 350 млн параметров для улучшения структурированных ответов за 100 шагов GRPO
Опубликовано 3 сентября 2026 года
Обновить на GitHub
Это руководство представляет собой полностью открытый и недорогой рецепт, позволяющий значительно улучшить соответствие небольшой модели требованиям к структурированным ответам. Мы дообучаем LFM2.5-350M с помощью групповой относительной оптимизации политики (GRPO), используя библиотеку TRL, и оцениваем её на бенчмарке IFStruct. Полный запуск включает около 500 примеров и 100 шагов обучения — этого достаточно для бесплатного GPU в Colab или Kaggle; код доступен на GitHub. Результаты показывают, что даже лёгкая процедура дообучения повышает производительность с 22,6% до 29,7% на бенчмарке IFStruct.

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

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

Предварительные требования

Это руководство состоит из двух частей, которые выполняются в разных местах:

  • Дообучение выполняется на GPU. Сопроводительный ноутбук рассчитан на бесплатный GPU в Colab или Kaggle.
  • Оценивание можно выполнять локально на MacBook (в данном случае — на MacBook Pro с Apple M5 Max и 36 ГБ объединённой памяти) через llama.cpp, который предоставляет совместимый с OpenAI сервер, используемый оценщиком IFStruct.

Для инструментов Python нам понадобится uv, а для запуска сервера — llama.cpp. Следуя документации Liquid AI по развёртыванию llama.cpp, установите llama.cpp с помощью Homebrew и убедитесь, что llama-server доступен:

brew install llama.cpp
llama-server --version

Оценивание IFStruct на LFM2.5-350M (базовая модель)

Перед началом оценим LFM2.5-350M на бенчмарке IFStruct и проверим, сможем ли мы воспроизвести заявленный результат в 21,1%.

IFStruct — это бенчмарк для проверки корректности ответов LLM и соблюдения схемы. Бенчмарк имеет открытый исходный код в репозитории Liquid4All/ifstruct, а общедоступный набор данных доступен на Hugging Face по адресу LiquidAI/ifstruct-v1.0.

git clone https://github.com/Liquid4All/ifstruct.git

Для сравнения результатов мы запускаем модель локально на MacBook с помощью llama.cpp. Мы будем использовать GGUF в формате BF16 (LiquidAI/LFM2.5-350M-GGUF).

Затем запускаем сервер базовой модели следующей командой:

llama-server \
  -hf LiquidAI/LFM2.5-350M-GGUF:BF16 \
  -c 32768 \
  -np 4 \
  -ngl 99 \
  --alias LiquidAI/LFM2.5-350M \
  --host 127.0.0.1 \
  --port 8080
  • --alias: имя модели, которое IFStruct отправляет совместимому с OpenAI эндпоинту
  • -ngl 99: указывает llama.cpp выгрузить все слои на GPU, если он доступен
  • -np 4: обслуживает четыре запроса параллельно
  • -c 32768: размер контекста запроса

После запуска сервера можно выполнить полный бенчмарк на 2000 примерах:

uv run ifstruct-eval \
  --model LiquidAI/LFM2.5-350M \
  --base-url http://localhost:8080/v1 \
  --api-key dummy \
  --dataset data/test.jsonl \
  --results-file results/lfm2.5-350m-llamacpp-base.json \
  --n-threads 4 \
  --max-tokens 2048 \
  -v
============================================================
Model: LiquidAI/LFM2.5-350M
============================================================
Overall: 452/2000 passed (22.6%)
Average latency: 1453ms

By format:
  JSON: 180/1000 passed (18.0%)
  YAML: 272/1000 passed (27.2%)

By top-level structure:
  Wrapper key 288/1011 passed (28.5%)
  Bare list   164/989 passed (16.6%)

By entity type:
  test__camera_review                 6/83 passed (7.2%)
  test__clinical_trial                20/104 passed (19.2%)
  test__conference_schedule           7/87 passed (8.0%)
  test__escaping__bug_report_batch    24/89 passed (27.0%)
  test__escaping__config_snippet_audit 15/85 passed (17.6%)
  test__escaping__customer_email_thread 5/73 passed (6.8%)
  test__escaping__dialogue_sample     14/95 passed (14.7%)
  test__escaping__interview_transcript_segment 21/80 passed (26.2%)
  test__escaping__log_parser_examples 21/72 passed (29.2%)
  test__escaping__pr_discussion       22/87 passed (25.3%)
  test__escaping__repro_steps_batch   16/73 passed (21.9%)
  test__escaping__screenplay_scene    16/92 passed (17.4%)
  test__escaping__short_story_chapter 15/84 passed (17.9%)
  test__escaping__support_ticket_batch 27/73 passed (37.0%)
  test__escaping__terminal_session_notes 20/70 passed (28.6%)
  test__event_ticket_booking          49/107 passed (45.8%)
  test__gpu_review                    6/94 passed (6.4%)
  test__invoice                       28/86 passed (32.6%)
  test__job_posting                   25/85 passed (29.4%)
  test__real_estate_listing           31/82 passed (37.8%)
  test__recipe                        3/70 passed (4.3%)
  test__rental_car_booking            27/79 passed (34.2%)
  test__scientific_experiment         13/69 passed (18.8%)
  test__travel_itinerary              21/81 passed (25.9%)

Common errors:
  7228x required field missing
  738x wrong item count
  540x type mismatch
  317x Unclosed code block
  190x extraneous field 'notes'
  181x extraneous field 'path'
  175x extraneous field 'constraints'
  170x extraneous field 'type'
  170x missing code block
  100x expected bare list, got wrapper

В статье о выпуске IFStruct сообщается результат 21,1% для LFM2.5-350M. В нашей локальной конфигурации llama.cpp/BF16 получено 22,6%, что близко к указанным в статье об IFStruct 21,1%. Этот локальный результат мы используем как базовый для сравнения в том же стеке запуска.

Дообучение GRPO на структурированных ответах с помощью TRL

Полный готовый к запуску конвейер находится в сопроводительном ноутбуке. В этом разделе мы рассмотрим только важные фрагменты.

Данные для обучения

Мы используем nvidia/Nemotron-RL-instruction_following-structured_outputs, где каждому запросу сопоставлены целевая JSON Schema и ожидаемое количество полей. Для обучения мы используем около 500 примеров.

Поскольку распределение данных Nemotron отличается от данных оценки IFStruct, мы дополняем запросы, чтобы устранить два различия между ними:

  • Для 40% добавляется инструкция «вернуть вывод внутри блока кода с ограждением», чтобы модель училась соблюдать указание о формате, а не всегда выдавала необработанный JSON.
  • Отдельные 20% преобразуются в задачи с массивом верхнего уровня (схема оборачивается в array с обязательным количеством элементов), что обучает выводу простых списков и соблюдению количества элементов.

Модель и LoRA

Мы загружаем LiquidAI/LFM2.5-350M и подключаем адаптер LoRA. Поскольку LFM2.5 использует гибридную архитектуру внимания и свёртки, мы указываем специфичные для LFM имена модулей:

lora_config = LoraConfig(
    r=16, 
    lora_alpha=32, 
    bias="none", 
    task_type="CAUSAL_LM",
    target_modules=[
        "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 Schema этой строки? Функция учитывает каждое нарушение ограничения и начисляет частичный балл с учётом покрытия обязательных ключей.

Мы объединяем эти три компонента в взвешенную сумму с помощью reward_weights=[1.0, 0.5, 2.0].

Обучение

Мы обучаем модель в течение 100 шагов, используя 8 генераций на группу запросов; конфигурация рассчитана на бесплатный GPU с 16 ГБ памяти:

from trl import GRPOConfig

training_args = GRPOConfig(
    output_dir="./outputs/lfm25-350m-nemotron-schema-grpo",
    learning_rate=5e-5,
    max_steps=100,
    warmup_steps=10,
    num_generations=8,              
    per_device_train_batch_size=4,
    gradient_accumulation_steps=8,  
    steps_per_generation=2,
    max_completion_length=1024,     
    mask_truncated_completions=False,
    temperature=1.1,                
    beta=0.01,                      
    reward_weights=[1.0, 0.5, 2.0], 
    logging_steps=1,
    save_steps=100,
)

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

Объединение и сохранение модели

Наконец, мы объединяем адаптер LoRA с базовыми весами и сохраняем модель как единую автономную контрольную точку, готовую к преобразованию в GGUF для запуска:

MERGED_DIR = f"{training_args.output_dir}-merged"

merged_model = trainer.model.merge_and_unload()
merged_model.save_pretrained(MERGED_DIR)
tokenizer.save_pretrained(MERGED_DIR)

Оценивание IFStruct на дообученной с помощью GRPO LFM2.5-350M

После дообучения GRPO мы повторно выполняем оценивание IFStruct. Для этого необходимо преобразовать объединённую контрольную точку модели в BF16 GGUF. Скрипт преобразования поставляется вместе с исходным кодом llama.cpp, поэтому мы один раз клонируем репозиторий и устанавливаем пакет преобразователя 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 \
  -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
============================================================
Model: lfm25-350m-grpo-structured-output
============================================================
Overall: 594/2000 passed (29.7%)
Average latency: 1518ms

By format:
  JSON: 319/1000 passed (31.9%)
  YAML: 275/1000 passed (27.5%)

By top-level structure:
  Wrapper key 300/1011 passed (29.7%)
  Bare list   294/989 passed (29.7%)

By entity type:
  test__camera_review                 5/83 passed (6.0%)
  test__clinical_trial                31/104 passed (29.8%)
  test__conference_schedule           11/87 passed (12.6%)
  test__escaping__bug_report_batch    32/89 passed (36.0%)
  test__escaping__config_snippet_audit 24/85 passed (28.2%)
  test__escaping__customer_email_thread 9/73 passed (12.3%)
  test__escaping__dialogue_sample     17/95 passed (17.9%)
  test__escaping__interview_transcript_segment 13/80 passed (16.2%)
  test__escaping__log_parser_examples 33/72 passed (45.8%)
  test__escaping__pr_discussion       26/87 passed (29.9%)
  test__escaping__repro_steps_batch   23/73 passed (31.5%)
  test__escaping__screenplay_scene    34/92 passed (37.0%)
  test__escaping__short_story_chapter 24/84 passed (28.6%)
  test__escaping__support_ticket_batch 36/73 passed (49.3%)
  test__escaping__terminal_session_notes 23/70 passed (32.9%)
  test__event_ticket_booking          62/107 passed (57.9%)
  test__gpu_review                    7/94 passed (7.4%)
  test__invoice                       36/86 passed (41.9%)
  test__job_posting                   33/85 passed (38.8%)
  test__real_estate_listing           32/82 passed (39.0%)
  test__recipe                        7/70 passed (10.0%)
  test__rental_car_booking            37/79 passed (46.8%)
  test__scientific_experiment         14/69 passed (20.3%)
  test__travel_itinerary              25/81 passed (30.9%)

Common errors:
  7331x required field missing
  890x wrong item count
  555x type mismatch
  102x expected bare list, got wrapper
   62x extraneous field 'metadata.tone'
   55x 6 is greater than maximum 5
   49x extraneous field 'speaker_labels'
   47x extraneous field 'tone'
   44x 'cups' not in allowed values ['mg', 'g', 'kg', 'oz', 'lb', 'ml', 'l', 'cl', 'dl'
   44x extraneous field 'notes'

Сравнение двух запусков на идентичном стеке:

Группа IFStruct базовая дообученная GRPO Δ
Всего 22.6% 29.7% +7.1
JSON 18.0% 31.9% +13.9
YAML 27.2% 27.5% +0.3
Ключ-обёртка 28.5% 29.7% +1.2
Простой список 16.6% 29.7% +13.1

Прирост наблюдается именно там, где было направлено обучение: доля успешных ответов в формате JSON увеличивается почти на 14 пунктов (с 18,0% до 31,9%), тогда как результат для YAML в основном не меняется. Хотя это всё ещё ниже, чем результат Qwen3.5-2B — 33,15%, это показывает, что даже лёгкое дообучение под конкретную задачу может приблизить небольшую модель к более крупной.

Заключение

Короткий запуск GRPO примерно на 500 примерах и 100 шагах может повысить результат небольшой модели на 350 млн параметров с 22,6% до 29,7% на IFStruct. Главный вывод заключается в том, что недорогой сигнал вознаграждения, ориентированный на конкретную задачу, способен значительно повысить надёжность небольшой модели в отношении формы ответа, существенно сократив отставание от моделей, в несколько раз превосходящих её по размеру.

Чтобы воспроизвести или расширить эту работу, ознакомьтесь с оригинальной статьёй о IFStruct v1.0, репозиторием бенчмарка Liquid4All/ifstruct и набором данных LiquidAI/ifstruct-v1.0.

Модели, упомянутые в этой статье 2

Наборы данных, упомянутые в этой статье 2

Сообщество

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

· Зарегистрируйтесь или войдите, чтобы оставить комментарий

Модели, упомянутые в этой статье 2

Наборы данных, упомянутые в этой статье 2

Переведено автоматически с английского. Оригинал статьи — по ссылке ниже.

Впервые опубликовано изданием Hugging Face

Читать оригинал на Hugging Face ↗

Текст и изображения принадлежат Hugging Face и приводятся здесь с указанием авторства и ссылкой на оригинальную публикацию.

← К новостям

Ещё новости

Все последние новости