🧙 Maestro Yoda Cap. 21 · Open source e HuggingFace

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.

un nastro trasportatore industriale con 8 stazioni, ognuna con un piccolo robot che fa una cosa diversa — “DATA”, “FORMAT”, “BASE”, “TRAIN”, “EVAL”, “MERGE”, “QUANT”, “SERVE”. In fondo, un pacco con sopra “Modello pronto”. Sotto: “open source AI come catena di montaggio”

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

  1. Dataset troppo piccolo + troppe epoche → overfitting → modello che ripete il dataset.
  2. Chat template sbagliato → degrado drammatico inspiegabile.
  3. No eval before/after → “boh, sembra meglio?” → non sai se hai migliorato.
  4. Salvare solo l’adapter, perdere la config del base model → al deploy il modello non funziona.
  5. Skip della quantization eval → modello quantizzato peggiora rispetto al merged, te ne accorgi in produzione.
  6. Hyperparameters da paper copiati senza adattare alla taglia/hardware tua.
  7. 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.