Puntata 230
Puntata 230 — Fine-tunare un open model end-to-end
Livello: 🧙 Maestro Yoda · Capitolo 21 · Open source e HuggingFace
La puntata 222 ti ha mostrato il LoRA “didattico” su Colab. Qui ti porto attraverso il workflow reale, quello che porta un modello dal dataset alla produzione: data prep, training, eval, quantization, serving, monitoring. Niente magia, solo otto tappe fatte bene.
Il workflow in 8 tappe
Dataset → Format → Base model → Training → Eval → Merge → Quantize → Serve
Niente di queste 8 fasi è opzionale per un risultato production-grade. Saltarne anche solo una (es. “eval non serve, tanto si vede a occhio”) è il modo più comune per fare progetti che falliscono in produzione.
1) Dataset: la cosa che conta di più
3 fonti possibili:
a) Dataset pubblico già fatto
Pro: zero costo, qualità nota.
Contro: non è tuo, non è specifico.
Esempi: HuggingFaceH4/ultrachat_200k, databricks/databricks-dolly-15k, allenai/tulu-3-sft-mixture, mlabonne/orpo-dpo-mix-40k.
b) Dataset proprietario tuo
Pro: massima rilevanza per il tuo dominio. Contro: costoso da annotare (€0.50-5 per esempio). Sweet spot: 500-5000 esempi annotati da esperti del dominio.
c) Dataset sintetico (vedi puntata 220)
Pro: scalabile, economico. Contro: rischio model collapse, bias del generatore. Pattern: usi GPT-5 o Claude per generare N esempi, poi human-verify un sottoinsieme.
Pattern vincente: mix di 70% pubblico (general capability) + 20% proprietario (domain) + 10% sintetico (edge case). Per task molto specifici: ribalta a 70% proprietario.
Quality check obbligatorio
Prima di addestrare, leggi 50 esempi a caso del dataset. Sì, con gli occhi. È la pratica più sottovalutata. Se 10 sono spazzatura, ne hai 20% di rumore.
2) Format: chat template, multi-turn, system prompt
Ogni modello base ha un suo chat template (vedi puntata 215). Llama 3 usa <|begin_of_text|><|start_header_id|>user<|end_header_id|>.... Qwen usa <|im_start|>user\n...<|im_end|>. Mistral usa [INST]...[/INST].
Sbagliare il template = degrado drastico. Pattern sicuro:
from transformers import AutoTokenizer
tok = AutoTokenizer.from_pretrained(base_model)
text = tok.apply_chat_template(conversation, tokenize=False)
Per SFT: il dataset deve essere lista di:
{"messages":[
{"role":"system","content":"Sei un assistente legale italiano specializzato in privacy."},
{"role":"user","content":"Cosa serve per fare un DPIA?"},
{"role":"assistant","content":"Una DPIA richiede ..."}
]}
Formati alternativi ({"prompt": "...", "response": "..."}) funzionano ma sono più fragili tra modelli.
Loss masking: durante SFT vuoi che la loss penalizzi solo i token dell’assistant, non dell’user/system. Trl/unsloth lo gestiscono automaticamente se passi il chat template, ma controllalo con train_on_responses_only=True o equivalenti.
3) Base model: scegli con criterio
Decision tree veloce:
- Italiano dominante? → Qwen 2.5 (multilingua forte) o Llama 3.1/3.2.
- Reasoning richiesto? → parti da DeepSeek-R1-Distill o Qwen 3 thinking-on.
- Edge deployment? → Llama 3.2 1B/3B, Gemma 3 4B, Phi 3.5.
- License pulita per commerciale? → Mistral 7B, Qwen ≤14B Apache 2.0, OLMo, Phi 3.5.
- Vision multimodale? → Llama 3.2 11B-Vision, Qwen2-VL, Pixtral, Gemma 3 multimodal.
Per il primo run: parti dalla taglia più piccola che ha senso. Iteri più veloce, errori costano meno.
4) Training: SFT, DPO, o entrambi
SFT (Supervised Fine-Tuning, puntata 215)
Insegni al modello “data X → produci Y”. Adatto per:
- Cambiare tono/stile/format.
- Aggiungere capacità task-specifica.
- Insegnare struttura output.
Hyperparams tipici LoRA QLoRA su 7B:
r=16-64,lora_alpha=2*r, target_modules: tutti i proj.- LR: 2e-4 (con LoRA), 1e-5 (full fine-tune).
- Epochs: 1-3 (overfitting facile sopra).
- Batch effettivo: 16-64.
DPO/ORPO/KTO (puntata 217)
Insegni “preferisci X a Y”. Adatto per:
- Allineamento di style preferiti.
- Rimozione di pattern indesiderati (refusal, sycophancy).
- Affinamento post-SFT.
Richiede dataset di coppie preferite/rifiutate (~500-5000 coppie minimo utile).
Pattern moderno (2024-26): SFT base + DPO sopra. Se hai dataset prefferences ortogonale al SFT.
Stack
- unsloth + trl = veloce, semplice, ottimo per LoRA fino a 70B.
- axolotl = config-as-yaml, ottimo per pipeline ripetibili.
- LLaMA-Factory = GUI + CLI, popolarissimo in Cina e EU.
- TRL puro + accelerate = full control, più verboso.
5) Eval: senza non sai se hai migliorato
Prima del training: stabilisci 3 set di valutazione:
a) Eval set tuo dominio (50-500 esempi)
Tenuto fuori dal training set. Stessa distribuzione, con risposte gold. Metriche: exact match dove possibile, LLM-as-judge (un modello forte giudica le risposte del tuo) per output free-form.
b) Eval set generale (regression)
Sottoinsieme di MMLU, MMLU-Pro o GPQA (vedi puntata 221) per verificare che il tuo fine-tune non abbia degradato capacità generali (catastrophic forgetting).
c) Eval qualitativo a mano
20 prompt scelti rappresentativi del use case. Li leggi tu (o un esperto). È il check finale.
Pattern: se l’eval set generale degrada >5%, hai overfitted. Riduci LR o epochs.
Tool: lm-evaluation-harness (EleutherAI), lighteval (HuggingFace), o script custom.
6) Merge: dall’adapter al modello standalone
LoRA produce un adapter (~50-500 MB). Per serving e distribuzione, spesso conviene mergiarlo nel base model:
from peft import PeftModel
from transformers import AutoModelForCausalLM
base = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3.2-3B-Instruct", torch_dtype="bfloat16")
model = PeftModel.from_pretrained(base, "tuo-lora-adapter")
merged = model.merge_and_unload()
merged.save_pretrained("modello-merged")
Pro merge: load più semplice, performance leggermente migliore (no overhead LoRA). Contro merge: file enorme (~6 GB per 3B BF16), non puoi più swappare adapter.
Alternative: multi-LoRA serving in vLLM/SGLang → un base, N LoRA caricabili a runtime per task diversi.
7) Quantize: ridurre per servire
Vedi puntata 227 per i dettagli. Workflow tipico:
# Conversion HF → GGUF Q4_K_M
python convert_hf_to_gguf.py modello-merged --outfile tuo-modello.f16.gguf
./llama-quantize tuo-modello.f16.gguf tuo-modello-q4_k_m.gguf Q4_K_M
# Oppure AWQ per server GPU
python -m awq.quantize tuo-modello-merged \
--output_dir tuo-modello-awq \
--w_bit 4 --q_group_size 128
Test obbligatorio post-quantization: rilancia il tuo eval set sul modello quantizzato. Se degrada >3% torna a Q5 o Q8.
8) Serve & monitor
Vedi puntata 228 (runtime). In produzione:
- vLLM o TGI per GPU server.
- Ollama per dev/edge.
- Esponi OpenAI-compatible API dietro reverse proxy (nginx/Caddy/Traefik).
- Auth + rate limit + request logging.
Monitoring continuo (LLMOps)
- Logging strutturato (prompt, response, tokens, latency, model version).
- Eval set rilanciato settimanalmente (regression).
- Sample-based human review: 1-5% delle risposte revisionate da operatore (per task ad alto rischio).
- A/B test del nuovo fine-tune vs baseline su traffico reale (10% di canary).
- Tool: LangSmith, Langfuse, Helicone, Arize Phoenix, W&B Weave.
Quanto costa, davvero (stime 2026)
Per fine-tunare un Llama-3.1-8B con LoRA su 50k esempi:
- Colab gratis (T4): 4-8 ore, gratis. Va.
- Colab Pro+ (L4): 1-2 ore, ~$2.
- A100 spot rentata (es. Vast.ai, Runpod): 30-60 min, ~$1-3.
- H100 4-GPU per full fine-tune 70B: ~$50-200/run.
Per dataset proprietari annotati: il costo dominante è l’annotazione umana, non la GPU.
Errori che tutti fanno la prima volta
- Dataset troppo piccolo + troppe epoche → overfitting → modello che ripete il dataset.
- Chat template sbagliato → degrado drammatico inspiegabile.
- No eval before/after → “boh, sembra meglio?” → non sai se hai migliorato.
- Salvare solo l’adapter, perdere la config del base model → al deploy il modello non funziona.
- Skip della quantization eval → modello quantizzato peggiora rispetto al merged, te ne accorgi in produzione.
- Hyperparameters da paper copiati senza adattare alla taglia/hardware tua.
- No data deduplication → modello memorizza pattern ripetuti.
Glossario lampo
- Catastrophic forgetting — perdita di capacità generali durante fine-tuning su task specifico. Mitigato da: LR basso, LoRA invece di full FT, dataset mixto con esempi generali.
- LLM-as-judge — pattern di eval in cui un modello forte (GPT/Claude/grande open) giudica le risposte di un modello da valutare. Veloce ma con bias propri.
- Canary deployment — rilascio incrementale: il nuovo modello serve 5-10% del traffico iniziale, monitoraggio metriche, poi rollout completo se ok.
- Multi-LoRA serving — un base model + N adapter LoRA, switchabili per request. Risparmia VRAM e permette personalizzazione per cliente/tenant.
Take-away
Fine-tuning end-to-end non è solo “lancio trl SFTTrainer e vado a dormire”. È una catena di otto fasi in cui il dataset vale il 60%, i hyperparameters il 10%, e l’eval+monitoring il 30%. Saltare quest’ultima parte è il motivo principale per cui i progetti AI aziendali finiscono in “il PoC andava benissimo, in produzione no”. L’open source non ti salva da questa disciplina — te la rende solo accessibile.
➡️ Prossima puntata: pubblicare un modello sull’HF Hub — model card, evaluation, contribuire alla community.