Modelos Abertos, Fine-Tuning e Destilação
Ajustar um modelo parece o caminho natural para "ensinar a empresa à IA", e quase nunca é o primeiro. Este módulo ensina quando vale a pena, como preparar os dados (a parte que decide o resultado), como o LoRA funciona por dentro, como um modelo grande ensina um pequeno e como medir se tudo isso compensou.
Ao final do módulo você vai
- Decidir entre prompt, RAG, ajuste fino e modelos próprios com critérios claros.
- Rodar modelos abertos localmente e entender quantização e licenças.
- Preparar datasets de ajuste limpos, sem dados pessoais e sem vazamento.
- Explicar e implementar LoRA, e aplicá-lo com bibliotecas reais.
- Destilar um modelo grande num pequeno e combiná-los numa cascata.
- Avaliar modelos ajustados contra os genéricos, inclusive fora da distribuição do treino.
cd o-arco/curso-ia/codigo/modulo-11/python
pip install -r requirements.txt
python 01_preparar_dataset.py && python 02_lora_do_zero.py && python 03_destilacao.py
11.1 Quando ajustar um modelo (e quando não)
Suba os degraus na ordem, e só quando o anterior comprovadamente não bastar (medido no seu eval):
| Degrau | Resolve | Não resolve |
|---|---|---|
| 1. Prompt + exemplos | Formato, tom, regras, a maioria das tarefas | Volume altíssimo com custo apertado |
| 2. RAG (Módulo 4) | Conhecimento da empresa, fatos que mudam, citações | Estilo ou comportamento muito específico |
| 3. Modelo menor + bom prompt | Custo e latência | Tarefas que o modelo menor não domina |
| 4. Ajuste fino / destilação | Tarefa estreita, repetitiva, em alto volume; formato muito rígido; modelo pequeno ou local que precisa chegar perto do grande | Injetar conhecimento que muda (desatualiza e não cita fonte) |
| 5. Treinar do zero | Quase nunca faz sentido fora de laboratórios de pesquisa | — |
Quiz rápido
Qual problema é o melhor candidato a fine-tuning ou destilação?
Conhecimento que muda pede RAG. Tarefa estreita, estável e de alto volume, com um professor que já acerta, é onde ajuste e destilação pagam.
11.2 Modelos abertos e inferência local
- Famílias de pesos abertos: Llama, Qwen, Mistral, Gemma, DeepSeek e outras, de menos de 1 bilhão a centenas de bilhões de parâmetros. Compare pelo seu eval, não pelo ranking.
- Licenças: "aberto" nem sempre é livre para uso comercial; algumas licenças limitam escala de usuários ou usos. Leia antes de construir em cima.
- Quantização: guardar pesos em 8 ou 4 bits em vez de 16 reduz memória (um modelo de 7B cai de ~14 GB para ~4 GB) com perda pequena de qualidade, a ser medida.
- Para rodar: Ollama e llama.cpp (notebook e desenvolvimento), vLLM e servidores de inferência (produção, alto volume, GPUs; veja a aula 11.9).
# Ollama: baixar e rodar um modelo pequeno localmente
ollama pull qwen2.5:3b
ollama run qwen2.5:3b "Classifique: 'cobraram duas vezes no cartão' → cobranca, entrega, defeito ou duvida?"
# API local em http://localhost:11434, para usar do seu código
| API de modelo proprietário | Modelo aberto próprio | |
|---|---|---|
| Capacidade | Em geral, a maior | Menor, mas suficiente para muitas tarefas estreitas |
| Custo | Por token, sem custo fixo | GPU fixa; barato só com volume alto e constante |
| Dados | Saem para o provedor (com contrato) | Ficam na sua infraestrutura |
| Operação | Nenhuma | Sua: escala, atualização, monitoramento, segurança |
11.3 Preparando datasets de ajuste
A qualidade do ajuste é a qualidade dos dados. O lab transforma uma exportação bruta de chamados num dataset pronto:
LAB 11.3 — Da exportação bruta ao JSONL
python 01_preparar_dataset.py
LIMPEZA
lidos 428
mascarado [CPF] 103
mascarado [EMAIL] 93
mascarado [TELEFONE] 93
descartado: sem rótulo 1
descartado: duplicata 172
descartado: vazio ou curto 2
exemplos finais 253
DIVISÃO (estratificada)
treino 175 cobranca=58 defeito=37 duvida=12 entrega=68
validacao 39 cobranca=13 defeito=8 duvida=3 entrega=15
teste 39 cobranca=13 defeito=8 duvida=3 entrega=15
Repare nas 172 duplicatas: muitas só apareceram depois do mascaramento, porque eram o mesmo
chamado com CPFs diferentes. Deduplicar antes de dividir evita que o mesmo texto esteja no treino e
no teste, o que inflaria a nota. O script confere isso com um assert.
- Formato de conversa (
{"messages": [system, user, assistant]}), o mais comum nas APIs e bibliotecas. - Dados pessoais fora: um modelo ajustado pode repetir trechos do treino.
- Equilíbrio: 12 exemplos de "duvida" contra 68 de "entrega" fazem o modelo errar justamente a classe rara. Colete mais ou gere exemplos sintéticos (revisados).
- Diversidade importa mais que volume: centenas de exemplos variados e corretos valem mais que milhares repetitivos.
11.4 Fine-tuning gerenciado por API
Vários provedores e nuvens oferecem ajuste fino gerenciado: você envia o JSONL, escolhe alguns hiperparâmetros e recebe um modelo ajustado, hospedado por eles. A disponibilidade muda por modelo e por provedor; confira a documentação de quem você usa.
- Épocas: poucas (1 a 3) para datasets médios. Validação subindo enquanto o treino cai é overfitting (você viu isso no Módulo 1).
- Custo: treino cobrado por token processado (tokens × épocas), mais o uso do modelo ajustado, às vezes com preço diferente do modelo base.
- Dependência: o modelo ajustado vive no provedor e pode ser descontinuado junto com o modelo base. Guarde os dados e o processo para refazer.
11.5 LoRA e QLoRA, por dentro
Em vez de ajustar todos os pesos W, o LoRA congela W e treina duas matrizes
pequenas A (d×r) e B (r×k): W' = W + (α/r)·A·B. Com posto r
pequeno, o número de parâmetros treinados cai para uma fração. A hipótese por trás: a mudança
necessária para adaptar um modelo a uma tarefa nova tem dimensão baixa.
LAB 11.5 — LoRA do zero, em NumPy
python 02_lora_do_zero.py # ~2 segundos, sem GPU
Modelo base: 17.668 parâmetros · acerto na tarefa A: 88.8%
abordagem treinados % do total acerto B acerto A depois
sem ajuste (base) 0 0.0% 63.3% 88.8%
ajuste completo 17.668 100.0% 80.3% 65.1%
LoRA r=1 (só em W1) 320 1.8% 82.3% 88.8%
LoRA r=2 (só em W1) 640 3.6% 86.3% 88.8%
LoRA r=4 (só em W1) 1.280 7.2% 85.6% 88.8%
LoRA r=8 (só em W1) 2.560 14.5% 84.0% 88.8%
- Esquecimento catastrófico: o ajuste completo aprendeu a tarefa B e caiu de 88,8% para 65,1% na A. Com LoRA, a base fica intacta: tirando o adaptador, a tarefa A volta inteira.
- O melhor posto foi r=2, exatamente a dimensão da mudança entre as tarefas (o experimento foi construído assim). Na vida real você não conhece essa dimensão: r é um hiperparâmetro a medir.
- Vários adaptadores, uma base: um adaptador por cliente ou por tarefa, trocados na hora, sobre o mesmo modelo carregado.
QLoRA é o mesmo com a base quantizada em 4 bits, o que permite ajustar modelos de
bilhões de parâmetros numa única GPU modesta. O 04_lora_peft_colab.py aplica LoRA com a
biblioteca PEFT num modelo aberto de 0,5 B de parâmetros, com a perda calculada só na resposta. Ele
precisa de GPU (o Colab gratuito serve) e não roda no ambiente de testes do curso; lá passa só por
checagem de sintaxe.
11.6 Destilação e cascata
Destilação: um modelo grande (professor) rotula muitos exemplos; um modelo pequeno (aluno) aprende com esses rótulos. Na cascata, o aluno responde quando está confiante e passa para o professor quando não está.
LAB 11.6 — Aluno, professor e o mundo real
python 03_destilacao.py # offline
python 03_destilacao.py --professor api # o claude-opus-5-5 rotula o treino
Acerto do aluno:
teste (mesma origem do treino) 39/39 = 100% confiança média 0.96
chamados novos (escritos de outro jeito) 8/20 = 40% confiança média 0.56
CASCATA nos chamados novos: o aluno responde se estiver confiante; senão, o professor.
limiar vai p/ professor acerto*
0.0 0% 40%
0.5 40% 70%
0.7 85% 100%
0.9 100% 100%
A lição mais importante do módulo está aqui. O aluno tirou 100% no teste que veio da mesma origem do treino e 40% em 20 chamados escritos à mão com outro vocabulário. Ele aprendeu as palavras do treino, não o problema. Um eval só com dados parecidos com o treino teria aprovado um modelo que falha no mundo real. Por sorte, a confiança dele cai junto, e a cascata usa isso. (No modo offline, os rótulos do dataset fazem o papel do professor; o acerto da cascata supõe que o professor acerta os casos que recebe, o que você deve medir.)
Quiz rápido
O modelo destilado acertou 100% no conjunto de teste do desenvolvimento. O que falta?
No lab, o mesmo modelo caiu para 40% em chamados novos. A cascata (pequeno primeiro, grande quando incerto) protege contra isso.
11.7 Avaliar, comparar e versionar modelos
- Compare com a linha de base certa: modelo ajustado contra o modelo grande com um bom prompt, no mesmo eval (Módulo 3), em qualidade, custo por tarefa e latência.
- Teste fora da distribuição: dados reais recentes, escritos por outras pessoas, com outros formatos. É onde modelos ajustados quebram.
- Regressões em outras capacidades: o ajuste pode piorar o que o modelo fazia bem (esquecimento). Mantenha alguns casos gerais no eval.
- Versione tudo junto: dataset (com hash), código de preparação, hiperparâmetros, modelo base, adaptador e resultado do eval. Sem isso, não dá para reproduzir nem fazer rollback.
- Lance como qualquer mudança de modelo: canário pelo gateway (Módulo 9), monitoramento e caminho de volta.
11.8 Dados sintéticos: gerar, filtrar e provar
Faltam exemplos? Um modelo grande pode escrever milhares. O risco não é a falta de volume, é o lixo: duplicatas, cópias do treino, rótulos errados e, o pior, exemplos parecidos com a avaliação, que fazem a nota subir sem o modelo melhorar. O lab gera, filtra e mede o efeito no aluno do Lab 11.6, que cai de 100% para 40% nos chamados escritos de outro jeito.
python 05_dados_sinteticos.py # usa um conjunto pronto, com problemas plantados
python 05_dados_sinteticos.py --api # o claude-opus-5-5 gera (36 lotes: categoria × tom × canal) e julga
Filtros:
✗ duplicata sim=1.00 «gente??? me cobraram o frete…» [plantado: duplicata exata]
✗ cópia do treino sim=1.00 «O purificador parou de ligar…» [plantado: cópia do treino]
✗ contaminação sim=0.69 «o código de rastreio que vocês mandaram não f…»
✗ contaminação sim=1.00 «Vi duas transações iguais no extrato…» [plantado]
? rótulo contestado pelo aluno (revisar): 2
Problemas plantados pegos pelos filtros: 8/9
Diversidade (bigramas distintos / total): treino 0.12 · sintéticos 0.92
treino usado teste chamados novos
só reais 100% 40%
reais + sintéticos filtrados 100% 50%
reais + sintéticos SEM filtro 100% 55%
- Sem filtro parece melhor, e é mentira. O script mostra de onde vem cada ganho: o ponto extra sem filtro é exatamente o chamado da avaliação que vazou para o treino.
- Contaminação acontece sem querer. O conjunto pronto foi escrito pelo Claude que montou este curso, sem consultar os chamados novos. Mesmo assim, uma das frases dele ("o código de rastreio que vocês mandaram não funciona…") era quase igual a um chamado da avaliação. Geradores escrevem frases naturais, e avaliações também: o filtro é obrigatório.
- Filtro por palavras tem limite. Um dos ganhos ("O controle remoto não funciona de jeito nenhum") tem um sintético parecido em sentido, mas não em palavras (similaridade 0,25), que o filtro deixou passar; e uma quase duplicata plantada escapou. Em produção, combine palavras com embeddings (Módulo 4) e revise uma amostra à mão.
- O ganho real é pequeno e precisa ser lido caso a caso: com 20 chamados de avaliação, cada um vale 5 pontos. Os outros três ganhos não têm sintético parecido: o aluno aprendeu vocabulário novo. Um chamado que ele acertava passou a errar.
Receita
- Varie de propósito: categorias × tom × canal × tamanho, com sementes reais mostradas como tema, nunca para copiar.
- Deduplique dentro do lote e contra o treino; números e nomes viram marcadores antes de comparar.
- Separe a avaliação antes de gerar e remova tudo que se pareça com ela.
- Revise rótulos: um juiz (outro modelo ou o mesmo com outra instrução) e amostra humana.
- Meça o efeito numa avaliação fora da distribuição, caso a caso.
Licenças: confira se os termos do provedor permitem usar as saídas para treinar o modelo que você vai usar.
11.9 Servir modelos abertos em produção
Rodar um modelo no notebook com o Ollama é um passo; servir para muitos usuários é outro. Servidores de inferência como vLLM, TGI e llama.cpp (llama-server) usam lote contínuo: a cada passo, geram um token para todas as conversas ativas juntas. Isso multiplica a vazão e muda as contas de capacidade.
Parte 1 · Quanto de GPU
KV cache por token (16 bits):
Qwen2.5 7B 56 KiB/token · uma conversa de 8.000 tokens = 0.43 GiB
Llama 3.1 8B 128 KiB/token · uma conversa de 8.000 tokens = 0.98 GiB
Llama 3.1 70B 320 KiB/token · uma conversa de 8.000 tokens = 2.44 GiB
Conversas simultâneas com 8.000 tokens (— = não cabe):
modelo precisão L4 24 GB L40S 48 GB A100/H100 80 GB
Qwen2.5 7B 16 bits 8 50 106
Llama 3.1 8B 16 bits 3 21 45
Llama 3.1 8B 4 bits 14 32 57
Llama 3.1 70B 4 bits — 1 10
- Os pesos definem se o modelo cabe; o KV cache define quantas conversas cabem. Ele cresce com o contexto e com o número de usuários.
- O Qwen2.5 7B guarda metade das cabeças de atenção no cache em relação ao Llama 3.1 8B (4 contra 8, atenção agrupada) e, por isso, comporta mais que o dobro de conversas na mesma GPU.
- É uma estimativa de ordem de grandeza: confira a arquitetura no
config.jsondo modelo e meça no servidor real.
Parte 2 · Teste de carga
python servidor_simulado.py & # sem GPU: imita o lote contínuo
python 07_teste_de_carga.py
# servidor real (vLLM, llama-server ou Ollama):
python 07_teste_de_carga.py --url http://localhost:8000/v1 --modelo Qwen/Qwen2.5-7B-Instruct
simult. TTFT p50 TTFT p95 ITL p50 lat p50 lat p95 vazão tok/s erros
1 19ms 22ms 13ms 1.6s 1.7s 78 0
4 34ms 37ms 15ms 1.9s 1.9s 270 0
16 84ms 90ms 22ms 2.9s 2.9s 712 0
32 154ms 164ms 32ms 4.2s 4.2s 975 0
64 4329ms 4339ms 32ms 8.4s 8.4s 978 0
Os números vêm de um servidor simulado (lote máximo de 32), não de uma GPU, mas a forma das curvas é a de um servidor real: a vazão total sobe até o lote encher (78 → 975 tokens/s), enquanto cada usuário vê o texto sair mais devagar (ITL de 13 para 32 ms). Passando do limite, a vazão para de crescer e as conversas esperam na fila: o TTFT salta para mais de 4 segundos. O ponto de operação é a maior concorrência em que o p95 ainda cumpre o seu SLO.
Escolhas de produção
- vLLM para vazão em GPU NVIDIA ou AMD, com API compatível com a da OpenAI; TGI e SGLang são alternativas a comparar com o seu modelo e hardware; llama.cpp para CPU, Apple Silicon e GPUs pequenas; Ollama para desenvolvimento.
- Quantização (AWQ, GPTQ, FP8) libera memória para mais conversas; meça a qualidade com as evals do Módulo 3 antes de adotar.
- Coloque o servidor atrás do gateway do Módulo 9: limites, reserva para outro provedor e custo por requisição continuam valendo.
- Compare o custo por milhão de tokens da GPU (preço da hora ÷ vazão medida no seu SLO) com o de uma API antes de decidir hospedar.
Projeto do módulo: modelo pequeno especializado
Escolha uma tarefa estreita do seu contexto (classificação, extração, roteamento) e entregue:
- Dataset preparado com o pipeline do lab 11.3: limpeza, mascaramento, deduplicação, divisão estratificada, checagem de vazamento.
- Um conjunto "mundo real" separado, escrito ou coletado de forma independente do treino.
- Três concorrentes no mesmo eval: modelo grande com prompt, aluno destilado (lab 11.6) e modelo aberto ajustado com LoRA (lab 11.5b, no Colab).
- Uma cascata com limiar escolhido na validação.
- Recomendação final com números: qual usar, em que volume compensa, e o plano de reajuste quando os dados mudarem.
Critérios de avaliação
| Critério | Peso |
|---|---|
| Pipeline de dados com checagens | 25% |
| Avaliação fora da distribuição | 20% |
| Comparação justa dos três concorrentes | 25% |
| Cascata com limiar justificado | 15% |
| Recomendação e plano de manutenção | 15% |
Recursos para aprofundar
- LoRA (2021) e QLoRA (2023) · os artigos originais
- Hugging Face PEFT · a biblioteca usada no lab para Colab
- Ollama, llama.cpp e vLLM · inferência local e em servidor
- Distilling the Knowledge in a Neural Network (2015) · o artigo clássico de destilação