Ottimizzare un modello da 350M per output strutturati in 100 passaggi GRPO
Questa guida è una ricetta pubblica ed economica per migliorare significativamente un modello piccolo nella conformità degli output strutturati. Effettuiamo il fine-tuning del modello LFM2.5-350M con Group Relative Policy Optimization (GRPO) utilizzando la libreria TRL e lo valutiamo sul benchmark IFStruct. L'intero processo richiede circa 500 campioni e 100 passaggi di addestramento, sufficienti per una GPU di livello gratuito su Colab o Kaggle, e è disponibile su GitHub. I risultati mostrano che anche una procedura di fine-tuning leggera migliora le prestazioni dal 22,6% al 29,7% sul benchmark IFStruct.
L'output strutturato è uno dei compiti più comuni nel mondo reale per i modelli linguistici, ma la maggior parte dei benchmark lo includono in punteggi più ampi di ragionamento o estrazione piuttosto che misurarlo da solo. Che un modello restituisca in modo affidabile output validi e analizzabili nel formato e nella forma richiesti — conformità allo schema — è spesso ciò che decide se può essere collegato a un sistema a valle.
Nota che il pipeline di addestramento descritto qui non è lo stesso utilizzato per addestrare il modello RL descritto nel blog IFStruct. Questo notebook non mira a riprodurre il punteggio del benchmark IFStruct, ma a mostrare come il fine-tuning specifico del compito di modelli più piccoli possa migliorare le prestazioni e avvicinarsi a quelle di modelli molto più grandi.
Prerequisiti e valutazione iniziale
Questa guida ha due parti che si eseguono in luoghi diversi: avremo bisogno di uv per gli strumenti Python e llama.cpp per il servizio. Seguendo la documentazione di Liquid AI per il deployment di llama.cpp, installiamo llama.cpp con Homebrew e verifichiamo che llama-server sia disponibile.
Prima di iniziare, valutiamo LFM2.5-350M sul benchmark IFStruct per vedere se riusciamo a riprodurre il punteggio riportato del 21,1%. IFStruct è un benchmark per testare la validità degli output degli LLM e l'aderenza allo schema. Il benchmark è open-source in Liquid4All/ifstruct, con il dataset pubblico disponibile su Hugging Face in LiquidAI/ifstruct-v1.0.
Per il confronto di valutazione, serviamo il modello localmente sul MacBook con llama.cpp. Utilizzeremo il BF16 GGUF (LiquidAI/LFM2.5-350M-GGUF). Avviamo il server del modello base con il seguente comando:
Una volta avviato il server, possiamo eseguire l'intero benchmark con 2000 campioni:
Il blog di rilascio di IFStruct riporta il 21,1% per LFM2.5-350M. La nostra configurazione locale llama.cpp/BF16 misura il 22,6%, vicino al 21,1% riportato nel blog IFStruct. Utilizziamo questo risultato locale come linea di base per il confronto dello stesso stack di servizio.
Fine-tuning con GRPO
Il pipeline completo ed eseguibile si trova nel notebook allegato. Copriremo solo le parti rilevanti in questa sezione. Utilizziamo nvidia/Nemotron-RL-instructionfollowing-structuredoutputs, che abbinato a ogni prompt con uno schema JSON target e un conteggio di campi attesi. Utilizziamo circa 500 campioni per l'addestramento.
Poiché la distribuzione dei dati Nemotron differisce dalla valutazione IFStruct, aumentiamo i prompt per colmare due lacune tra loro:
- Mancanza di prompt di tipo "extract" nel dataset Nemotron.
- Mancanza di prompt di tipo "classify" nel dataset Nemotron.
Carichiamo LiquidAI/LFM2.5-350M e attacchiamo un adattatore LoRA. Poiché LFM2.5 utilizza un'architettura ibrida di attenzione/convoluzione, miriamo ai nomi dei moduli specifici di LFM:
Questo addestra circa 6M di parametri, circa l'1,66% del modello. Definiamo quindi tre funzioni di ricompensa, ciascuna su una scala [0, 1], che valutano ogni completamento sulla correttezza della struttura estratta:
- Schema JSON valido.
- Numero di campi corretto.
- Tipo di campo corretto.
Combinamo le tre come una somma ponderata con reward_weights=[1.0, 0.5, 2.0]. Addestriamo per 100 passaggi con 8 generazioni per gruppo di prompt, dimensionati per una GPU di livello gratuito da 16 GB:
Come si può vedere nel notebook, durante l'esecuzione, tutti e tre i componenti di ricompensa aumentano, il KL dal modello di riferimento si alza da zero dopo il riscaldamento e la frazione di completamento troncato rimane vicina a zero.
Valutazione finale e risultati
Infine, fondiamo l'adattatore LoRA nuovamente nei pesi di base e lo salviamo come un singolo checkpoint autosufficiente, pronto per la conversione in GGUF per il servizio:
Dopo il fine-tuning GRPO, riavviamo la valutazione IFStruct. Per questo, dobbiamo convertire il checkpoint del modello unito in un BF16 GGUF. Lo script di conversione è fornito con la fonte di llama.cpp, quindi cloniamo il repo una volta e installiamo il pacchetto gguf del convertitore.
Poi serviamo il modello unito con il seguente comando:
Poi eseguiamo nuovamente la valutazione completa IFStruct con il modello fine-tunato:
Confrontando le due esecuzioni sullo stesso stack di servizio:
I guadagni si concentrano esattamente dove l'addestramento mirava: il tasso di passaggio JSON aumenta di quasi 14 punti (18,0% → 31,9%), mentre YAML rimane più o meno lo stesso. Sebbene ciò sia ancora al di sotto del punteggio di Qwen3.5-2B del 33,15%, dimostra che anche un leggero fine-tuning specifico del compito può avvicinare un modello piccolo a uno più grande.
Una breve esecuzione GRPO con circa 500 campioni e 100 passaggi può portare un modello piccolo da 350M parametri dal 22,6% al 29,7% su IFStruct. La conclusione è che un segnale di ricompensa economico e specifico del compito può rendere un modello piccolo sostanzialmente più affidabile nella forma, colmando gran parte del divario con modelli diverse volte più grandi.
Per riprodurre o estendere questo lavoro, consulta il post originale del blog IFStruct v1.0, il repository del benchmark Liquid4All/ifstruct e il dataset LiquidAI/ifstruct-v1.0.