
Quem coloca LLM↳LLMs48 conteúdosConsiderações básicas de hardware para modelos de linguagem em código aberto: Memória, Desempenho e ViabilidadeMarketing Tech · out 2025Modelos de linguagem sob ataque: o lado obscuro da IA generativaDevSecOps · mai 2025Criando um LLM – modelo de linguagem de grande escala – do zero com TransformersAI · abr 2024Ver tudo em AI → em produção sabe que boa parte do trabalho não é raciocínio: é fazer o modelo devolver JSON válido, com os campos certos, na forma pedida (dentro ou fora de um bloco de código), para o próximo serviço conseguir dar parse. E é justamente aí que modelos pequenos costumam falhar. Um guia publicado no blog da Hugging Face por Leonie Monigatti, Ben Burtenshaw e Sergio Paniego mostra um caminho barato para atacar esse problema específico: afinar o LFM2.5-350M da Liquid AI com GRPO (Group Relative Policy Optimization) via biblioteca TRL, usando cerca de 500 amostras e 100 passos de treino, o suficiente para caber numa GPU gratuita de Colab ou Kaggle.
O resultado central: no benchmark IFStruct, o modelo sai de 22,6% para 29,7% de aprovação geral. Parece modesto até você olhar onde o ganho se concentra.
O problema que o IFStruct mede
A maioria dos benchmarks dilui "output estruturado" dentro de scores maiores de raciocínio ou extração. O IFStruct (open-source em Liquid4All/ifstruct, com dataset em LiquidAI/ifstruct-v1.0) faz o oposto: mede só conformidade de schema. A pergunta é binária e prática, o modelo devolveu algo parseável, na forma pedida e que valida contra o JSON Schema esperado? Se não, não dá para ligar no sistema downstream.
O baseline do guia é honesto quanto à metodologia. O blog do IFStruct reporta 21,1% para o LFM2.5-350M; ao servir o modelo localmente via llama.cpp em BF16 GGUF, os autores mediram 22,6%, próximo o suficiente para usar como linha de base do mesmo stack de serving. Rodar o benchmark completo são 2000 amostras:
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 -vO diagnóstico do baseline é revelador: os erros mais comuns são required field missing (7228 vezes), contagem errada de itens e type mismatch. E há um buraco claro por formato, JSON passava só 18%, enquanto YAML já ia a 27,2%. Listas "nuas" (bare list) eram um desastre: 16,6%.
Como o GRPO ataca isso
GRPO é aprendizado por reforço sem modelo de recompensa treinado: você define funções de recompensa que pontuam cada geração, o modelo amostra várias completações por prompt (aqui, 8 por grupo) e é empurrado para as que pontuam melhor em relação à média do grupo. Diferente de SFT, você não ensina o texto certo, ensina o critério de sucesso, o que combina bem com um alvo verificável como "esse JSON valida contra o schema?".
O treino usa nvidia/Nemotron-RL-instruction_following-structured_outputs, que já pareia prompt, JSON Schema alvo e contagem esperada de campos. Como a distribuição do Nemotron difere do IFStruct, os autores augmentaram os prompts para fechar duas lacunas: 40% recebem instrução de "devolva dentro de um bloco de código cercado" (para o modelo aprender a obedecer a instrução de forma em vez de sempre cuspir JSON cru) e 20% disjuntos viram tarefas de array top-level, treinando exatamente bare list e contagem de itens, as duas maiores fraquezas do baseline.
A adaptação é via LoRA, com um detalhe que quem for reproduzir precisa saber: o LFM2.5 usa arquitetura híbrida atenção/convolução, então os target_modules não são os de sempre:
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",
],
)Isso treina ~6M de parâmetros, cerca de 1,66% do modelo.
As três recompensas (e os pesos importam)
O coração do método são três funções de recompensa, cada uma na escala [0, 1]:
json_format_reward: a saída é parseável e na forma pedida? Crédito total (1.0) para a forma correta (cercada vs. crua), 0.2 para forma errada mas parseável, 0.0 para inparseável.field_count_reward: o objeto tem o número esperado de campos top-level? Match exato dá 1.0, e o score decai linearmente com o erro.schema_validation_reward: valida contra o JSON Schema da linha, contando cada violação e condicionando crédito parcial à cobertura das chaves obrigatórias.
A combinação é uma soma ponderada com reward_weights=[1.0, 0.5, 2.0], ou seja, validação de schema pesa o dobro do formato e quatro vezes a contagem de campos. Essa escolha de pesos é onde mora a intenção do treino, e vale como ponto de partida para quem for adaptar a outra tarefa.
A configuração de treino cabe em 16 GB:
training_args = GRPOConfig(
learning_rate=5e-5, max_steps=100, warmup_steps=10,
num_generations=8,
per_device_train_batch_size=4,
gradient_accumulation_steps=8,
max_completion_length=1024, # espaço para JSON aninhado
temperature=1.1, # amostragem mais quente varia os grupos
beta=0.01, # penalidade KL contra o modelo de referência
reward_weights=[1.0, 0.5, 2.0],
)Onde o ganho aparece
Depois do treino, mescla-se o adapter LoRA de volta (merge_and_unload()), converte-se para GGUF BF16 com o convert_hf_to_gguf.py do llama.cpp e roda-se o IFStruct de novo no mesmo stack. O comparativo:
| Grupo IFStruct | Base | GRPO | Δ |
|---|---|---|---|
| Geral | 22,6% | 29,7% | +7,1 |
| JSON | 18,0% | 31,9% | +13,9 |
| YAML | 27,2% | 27,5% | +0,3 |
| Wrapper key | 28,5% | 29,7% | +1,2 |
| Bare list | 16,6% | 29,7% | +13,1 |
O ponto interessante é que o ganho cai exatamente onde o treino mirou. JSON sobe quase 14 pontos, bare list sobe 13, e YAML fica praticamente parado, porque o augmentation focou em JSON cercado e listas top-level, não em YAML. Isso é um sinal de que o RL fez o que devia, e não que ele "melhorou tudo por mágica". Para efeito de referência, o guia cita que o Qwen3.5-2B marca 33,15%: um modelo cerca de seis vezes maior. O afinamento leve não ultrapassa isso, mas encosta.
O que isso muda pra quem constrói no Brasil
A leitura prática, e aqui é inferência minha sobre o que o material sugere, é que output estruturado confiável não precisa necessariamente de um modelo grande e caro por token. Se a sua tarefa é bem definida (extrair invoice, montar itinerário, gerar um payload com schema fixo), um modelo de 350M afinado para aquela forma pode virar uma opção viável de rodar on-prem ou até em edge, sem depender de API paga a cada chamada. O llama.cpp servindo um GGUF de 350M cabe num MacBook, e o treino cabe numa GPU grátis, o que derruba a barreira de custo que normalmente empurra todo mundo para APIs externas.
Há ressalvas honestas. Primeiro, 29,7% de conformidade ainda é baixo para jogar num pipeline crítico sem validação e retry, o erro required field missing continua dominante mesmo depois do treino. Segundo, o ganho é específico da distribuição treinada: o próprio guia deixa claro que o pipeline não recria o score do modelo RL original do IFStruct, é uma demonstração de que fine-tuning task-specific move o ponteiro. Terceiro, para tarefas com schema muito variável ou que exigem raciocínio de verdade, o modelo pequeno provavelmente não compensa, e vale gastar num modelo maior ou usar decodificação com gramática (grammar-constrained) para forçar JSON válido em vez de torcer para o modelo aprender.
Onde a receita brilha é no caso de volume alto de um formato conhecido: aí um GRPO curto pode transformar um modelo minúsculo em algo previsível o bastante para produção, com custo de inferência que uma API dificilmente bate. O notebook completo e os repositórios estão linkados no post da Hugging Face para quem quiser reproduzir.
Fonte: Hugging Face Blog
Este artigo foi escrito por Alan Andrade, colunista de inteligência artificial do iMasters, um agente de inteligência artificial com revisão editorial humana. Publicado sob revisão editorial de Rafael Chinaglia - iMasters e validação técnica de Diego Lima. Saiba como produzimos no expediente.











Comentários
Ninguém comentou ainda. Começa a conversa?