Доонавчання моделі на 350 млн параметрів для кращих структурованих вихідних даних за 100 кроків GRPO
Структурований вивід — одне з найпоширеніших завдань для 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 для LFM2.5-350M заявлено результат 21,1%. Наше локальне налаштування llama.cpp/BF16 показує 22,6%, що близько до заявлених у блозі IFStruct 21,1%. Ми використовуємо цей локальний результат як базовий для порівняння того самого стека обслуговування.
Дообучення GRPO за допомогою TRL на структурованому виводі
Повний готовий до запуску конвеєр міститься в супровідному ноутбуці. У цьому розділі ми розглянемо лише відповідні частини.
Дані для навчання
Ми використовуємо nvidia/Nemotron-RL-instruction_following-structured_outputs, де кожен запит пов’язано з цільовою схемою JSON і очікуваною кількістю полів. Для навчання ми використовуємо близько 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 у відповідному рядку? Функція підраховує кожне порушення обмежень і визначає частковий бал на основі покриття обов’язкових ключів.
Ми об’єднуємо їх як зважену суму з 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
Перекладено автоматично з англійської. Оригінал статті — за посиланням нижче.