Дообучаване на модел с 350 млн. параметъра за по-добри структурирани изходи в 100 стъпки на GRPO
Структурираният изход е една от най-често срещаните задачи от реалния свят за LLM модели, но повечето бенчмаркове го включват в по-широки оценки за разсъждение или извличане, вместо да го измерват самостоятелно. Дали даден модел надеждно връща валиден и разбираем за парсване изход в зададения формат и структура — тоест спазва схемата — често определя дали изобщо може да бъде свързан с последваща система.
Имайте предвид, че описаният тук процес на обучение не е този, използван за обучението на RL модела, описан в статията за IFStruct. Този notebook не цели да възпроизведе резултата от бенчмарка IFStruct, а да покаже как специфичното за задачата дообучаване на по-малки модели може да подобри представянето им и да го изравни с това на значително по-големи модели.
Предварителни изисквания
Това ръководство има две части, които се изпълняват на различни места:
- Дообучаването се изпълнява върху GPU. Придружаващият notebook е съобразен с безплатна GPU инстанция в Colab или Kaggle.
- Оценяването може да се изпълни локално на MacBook (в случая MacBook Pro с Apple M5 Max и 36 GB обединена памет) чрез
llama.cpp, който предоставя съвместим с OpenAI сървър, към който се свързва оценителят на IFStruct.
Ще са ни нужни uv за инструментите на Python и 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. Ще използваме BF16 GGUF (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 върху структурирани изходи
Целият изпълним процес е в придружаващия notebook. В този раздел ще разгледаме само релевантните части.
Данни за обучение
Използваме 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 GB:
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,
)
Както можете да видите в notebook-а, по време на изпълнението и трите компонента на наградата се увеличават, 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 остава почти същият. Макар това все още да е под резултата от 33,15% на Qwen3.5-2B, то показва, че дори лекото специфично за задачата дообучаване може да доближи малък модел до по-голям.
Заключение
Кратко изпълнение на GRPO с около 500 извадки и 100 стъпки може да повиши резултата на малък модел със 350 млн. параметъра от 22,6% на 29,7% в IFStruct. Изводът е, че евтиният сигнал за награда, специфичен за задачата, може значително да повиши надеждността на малък модел по отношение на формата, като намали голяма част от разликата спрямо модели, няколко пъти по-големи от него.
За да възпроизведете или разширите тази работа, вижте оригиналната статия за IFStruct v1.0, хранилището на бенчмарка Liquid4All/ifstruct и набора от данни LiquidAI/ifstruct-v1.0.
Модели, споменати в тази статия 2
Набори от данни, споменати в тази статия 2
Общност
· Регистрирайте се или влезте в профила си, за да коментирате
Модели, споменати в тази статия 2
Набори от данни, споменати в тази статия 2
Преведено автоматично от английски. Оригиналната статия е на връзката по-долу.