Cosa ti serve

  • Una GPU NVIDIA da 24GB (RTX 3090/4090) — o noleggiane una a ore per ~2€ totali (vedi RunPod vs Vast.ai)
  • Linux o Windows + WSL2, Python 3.10+, ~40GB liberi su disco
  • Un dataset: 500–5.000 esempi del comportamento che vuoi (lo costruiamo al passo 1)

Passo 1 — Costruisci il dataset (la parte che decide tutto)

Un oggetto JSON per riga (train.jsonl), in formato chat:

{"messages": [ {"role": "system", "content": "Sei l'assistente di supporto di ACME S.p.A...."}, {"role": "user", "content": "Il cliente scrive: fattura non ricevuta..."}, {"role": "assistant", "content": "LA RISPOSTA ESATTA, nello stile/formato esatto che vuoi"} ]}

Le regole che contano più di qualsiasi iperparametro:

  • La coerenza batte il volume. 800 esempi con formattazione/tono identici battono 20.000 rumorosi. Ogni incoerenza nei dati diventa un comportamento che il modello impara.
  • Mostra il comportamento target, non descriverlo. I turni assistant devono ESSERE ciò che vuoi ottenere.
  • Tieni da parte 30–50 esempi in un eval.jsonl separato — mai usati nel training, serviranno al passo 6.

Passo 2 — Installa Unsloth (5 min)

$ python -m venv .venv && source .venv/bin/activate
$ pip install unsloth

Unsloth incapsula lo stack PEFT/TRL con ottimizzazioni pesanti — circa 2× più veloce e leggero in VRAM dei setup standard, ed è ciò che rende un 14B comodo su 24GB.

Passo 3 — Lo script di training (train.py)

from unsloth import FastLanguageModel from trl import SFTTrainer, SFTConfig from datasets import load_dataset model, tokenizer = FastLanguageModel.from_pretrained( "unsloth/phi-4", load_in_4bit=True, max_seq_length=2048) model = FastLanguageModel.get_peft_model( model, r=16, lora_alpha=16, lora_dropout=0, target_modules=["q_proj","k_proj","v_proj","o_proj", "gate_proj","up_proj","down_proj"]) dataset = load_dataset("json", data_files="train.jsonl", split="train") dataset = dataset.map(lambda ex: {"text": tokenizer.apply_chat_template( ex["messages"], tokenize=False)}) trainer = SFTTrainer(model=model, tokenizer=tokenizer, train_dataset=dataset, dataset_text_field="text", args=SFTConfig(per_device_train_batch_size=2, gradient_accumulation_steps=8, num_train_epochs=2, learning_rate=2e-4, lr_scheduler_type="cosine", logging_steps=10, output_dir="out", bf16=True)) trainer.train() model.save_pretrained_gguf("out_gguf", tokenizer, quantization_method="q4_k_m")

Le manopole, decodificate: r=16 è la capacità dell'adapter (8–32 è l'intervallo sano; più alto impara di più e dimentica di più), 2 epoche sono il default per il tuning di comportamento, e il batch effettivo = 2×8 = 16 via gradient accumulation. Questa configurazione usa circa 16–20GB di VRAM.

Passo 4 — Addestra e leggi la loss (30–90 min)

$ python train.py
  • Run sana: la loss parte da ~1,5–2,5 e scende dolcemente verso ~0,5–1,0.
  • Loss piatta dall'inizio → problema di formato dei dati (di solito la mappatura del chat template).
  • Loss vicino a zero → memorizzazione; il modello ripeterà a pappagallo gli esempi. Meno epoche.

Passo 5 — Carica in Ollama (5 min)

Lo script ha già esportato un GGUF Q4_K_M unito in out_gguf/. Crea un Modelfile:

FROM ./out_gguf/unsloth.Q4_K_M.gguf
PARAMETER num_ctx 8192
$ ollama create acme-assistant -f Modelfile
$ ollama run acme-assistant

Il tuo modello ora funziona ovunque funzioni Ollama — inclusa la Open WebUI del nostro primo tutorial e il server vLLM (per quel percorso usa l'export FP16 unito + AWQ).

Passo 6 — Valuta onestamente (il passo che tutti saltano)

  • Esegui le domande di eval.jsonl tenute da parte su ENTRAMBI, modello base e fine-tune. Conta: comportamento target raggiunto? formato corretto?
  • Poi fai a entrambi 10 domande GENERALI estranee al tuo dominio — se il modello tunato è peggiorato sensibilmente, è catastrophic forgetting: riaddestra con meno epoche o r più basso.
  • Conserva il set di eval per sempre: rieseguilo a ogni futuro retrain e upgrade del modello base.

Risoluzione dei problemi

  • CUDA out of memory — abbassa max_seq_length a 1024, poi il batch a 1 (alza gradient_accumulation_steps per mantenere il batch effettivo ~16).
  • Il modello risponde nel formato sbagliato dopo il tuning — dati di training incoerenti, o lo stai servendo con un system prompt diverso da quello dell'addestramento. Allineali.
  • Ripete a pappagallo gli esempi di training — overfitting: 1 epoca, o dati più vari.
  • Nessuna GPU da 24GB a disposizione — l'intero tutorial gira identico su una GPU cloud a noleggio: una sessione di 1–2 ore costa un paio di euro.

Prossimi passi