Avanzato 25 minFine-tuning

Fine-tuning di LLM in locale: esempio passo passo LoRA / QLoRA

Il fine-tuning locale di un LLM con LoRA e QLoRA permette di adattare un modello da 8-9B (come Qwen 3.5 9B) al tuo lessico professionale, al tuo stile di scrittura o a un formato di risposta specifico — tutto su una RTX 3090 usata. Non serve più un cluster A100. Questa guida ti accompagna dai primi passi, senza alcuna esperienza, fino al modello GGUF caricato in Ollama, passando per il formato del dataset, il notebook Unsloth passo passo e l’esportazione finale.

Di Mohamed Meguedmi·Agg. 2026-08-27·Testato su Windows, macOS e Linux

#Perché fare fine-tuning di un LLM locale?

Un LLM di base è versatile, ma non è perfetto per il tuo uso specifico. Il prompt engineering e il RAG risolvono l'80% dei casi — il fine-tuning serve per il restante 20%. Dovresti valutare il fine-tuning quando il tuo modello deve rispondere in un formato rigoroso (JSON, tag, struttura interna), rispettare uno stile aziendale (tono, vocabolario), padroneggiare un ambito specialistico (medico, giuridico, tecnico del tuo settore) o riprodurre comportamenti che nessun prompt riesce davvero a stabilizzare.

Al contrario, non usare il fine-tuning per inserire conoscenze fattuali (il RAG lo fa meglio e rimane aggiornato), correggere una singola allucinazione (basta un prompt migliore) o competere con GPT-5 sui benchmark (non vincerai).

i
Regola empirica
Se puoi esprimere ciò che desideri con meno di 5 esempi in un prompt, usa un system prompt. Se hai 50+ esempi e il modello continua comunque a deviare, fai un fine-tuning.

#LoRA vs full fine-tune: perché LoRA vince

Il kit IA Locale

Il tuo ChatGPT privato e gratuito sulla tua macchina in 1 ora — LM Studio, Ollama, Open WebUI, i tuoi documenti, senza cloud.

  • Spazio online a vita
  • PDF + file
  • Aggiornamenti a vita

Il full fine-tuning aggiorna tutti i parametri del modello. Per un 7B in FP16, servono 14 GB per i pesi + circa 30 GB per i gradienti e l'ottimizzatore Adam. Totale: un minimo di 40–60 GB di VRAM. Inaccessibile senza una A100 80GB o una H100.

LoRA (Low-Rank Adaptation) congela i pesi originali e addestra solo piccole matrici di adattamento di rango r, inserite negli strati di attenzione. In pratica, per un modello 7B con r=16, addestri circa 20 milioni di parametri invece di 7 miliardi, cioè lo 0,3% del modello. La VRAM necessaria per i gradienti e l'ottimizzatore si riduce drasticamente.

Fine-tune completo 7B
~60 GB VRAM, 2-4× A100, diverse ore, file di output da 14 GB.
LoRA 7B
~16 GB di VRAM, una RTX 4080 basta, da 30 min a 2 ore, adattatore di soli 30-200 MB.
QLoRA 7B
~6 GB di VRAM, RTX 3060 12 GB sufficiente, qualità quasi identica a quella di LoRA classico.
→
Quasi equivalente in pratica
Su dataset di dimensioni moderate (< 10.000 esempi), LoRA raggiunge il 95-99% della qualità di un full fine-tune. La differenza diventa visibile solo su compiti molto lontani dal pre-training. Per il tuo primo fine-tune, non porti nemmeno il problema: LoRA.

#QLoRA: la rivoluzione VRAM

QLoRA va oltre: il modello base viene caricato in 4 bit (NF4, NormalFloat 4 bit) invece di FP16. Durante l'addestramento, i pesi rimangono quantizzati; solo gli adattatori LoRA in FP16 ricevono i gradienti. Risultato: un 7B entra in 5-6 GB di VRAM, un 13B in 10 GB, un 70B in 48 GB.

La perdita di qualità è trascurabile grazie a due tecniche: la quantizzazione NF4 (calibrata sulla distribuzione gaussiana dei pesi) e la doppia quantizzazione (le costanti di quantizzazione sono a loro volta quantizzate). In pratica, nella maggior parte dei benchmark, QLoRA si discosta da LoRA FP16 di meno dell'1%.

i
La scelta predefinita consigliata
Per un primo fine-tuning locale, QLoRA è quasi sempre la scelta giusta. Risparmi il 60-70% di VRAM con una perdita di qualità impercettibile. Passa a LoRA classico solo se punti a un uso critico in produzione con una RTX 4090 o superiore.

#VRAM necessaria in base alla dimensione del modello

I numeri qui sotto sono valori minimi realistici con Unsloth, batch size 2, sequenza di 2048 token e gradient checkpointing attivato. Aggiungi un margine del 20% per i picchi e il sistema operativo.

Modello 2-3B (Qwen 3.5 2B, Granite 4.2 3B)
QLoRA: 4 GB VRAM · LoRA FP16: 8 GB · GTX 1660 6 GB o RTX 3050 sono sufficienti in QLoRA.
Modello 8-9B (Granite 4.2 8B, Qwen 3.5 9B)
QLoRA: 6 GB di VRAM · LoRA FP16: 16 GB · RTX 3060 12 GB con un buon margine, RTX 3090 ideale.
Modello 12B (Gemma 4 12B)
QLoRA: 10 GB VRAM · LoRA FP16: 28 GB · RTX 3090/4090 24 GB in QLoRA, A100 40GB in LoRA.
Modello 27-35B (Qwen 3.8 27B, Qwen 3.6 35B-A3B)
QLoRA: 22 GB di VRAM · LoRA FP16: impossibile su hardware consumer · RTX 3090/4090 o RTX 5090 esclusivamente con QLoRA.
Modello 70B (denso di fascia alta)
QLoRA: 48 GB VRAM · 2× RTX 3090 o 1× A100 80GB · multi-GPU obbligatorio con Unsloth Pro.

Per questa guida, l'obiettivo è un modello da 8–9B in QLoRA su una RTX 3090 da 24 GB. È la combinazione più universale: basta anche una RTX 3060 da 12 GB, ma una 3090 usata (prezzo variabile) consente di gestire comodamente tutte le taglie fino a 12B in QLoRA. Una RTX 4090 o 5090 offre un'accelerazione da 1,5 a 2×, ma non è indispensabile.

#Requisiti hardware e software

GPU NVIDIA con CUDA 11.8+
RTX 3060 12 GB come minimo, RTX 3090 consigliata. Le GPU AMD con ROCm funzionano parzialmente, ma Unsloth è ottimizzato per CUDA — per questa prima guida, resta su NVIDIA.
Python 3.10 o 3.11
Non 3.12 (incompatibilità con bitsandbytes al momento della stesura). Usa conda o pyenv.
32 GB di RAM di sistema
16 GB possono bastare ma il caricamento iniziale del modello e la preparazione del dataset sono più comodi con 32 GB.
50 GB di spazio disco
Modello base + checkpoint + adattatore fuso + esportazione GGUF Q4_K_M. Prevedi un ampio margine di spazio.
Connessione adeguata
Il download iniziale del modello 8-9B pesa 5-8 GB. Una sola volta.

Dal lato software, si utilizza Unsloth (github.com/unslothai/unsloth), un framework che riscrive i kernels Triton per guadagnare 2× di velocità e 60% di memoria rispetto al standard Hugging Face PEFT. Installazione in un ambiente dedicato:

Installazione di Unsloth
conda create -n unsloth python=3.11 -y
conda activate unsloth
pip install --upgrade pip
pip install "unsloth[cu121-torch240] @ git+https://github.com/unslothai/unsloth.git"
pip install --no-deps trl peft accelerate bitsandbytes
!
Versioni CUDA
Adatta cu121-torch240 alla tua installazione: cu118 per CUDA 11.8, cu124 per CUDA 12.4. Verifica con nvidia-smi, in alto a destra. Una versione errata causerà errori criptici al primo import.

#Preparare il dataset nel formato JSONL

Il formato standard per il fine-tuning basato su istruzioni è JSONL (un oggetto JSON per riga). Sono comuni tre varianti:

Formato Alpaca (il più semplice)
{"instruction": "Traduis en français formel.", "input": "Hey, what's up?", "output": "Bonjour, comment allez-vous ?"}
{"instruction": "Résume en une phrase.", "input": "Le chat noir a sauté...", "output": "Un chat noir saute sur la table."}
Formato ShareGPT (multi-turno)
{"conversations": [
  {"from": "system", "value": "Tu es un assistant juridique."},
  {"from": "human", "value": "Qu'est-ce qu'une clause léonine ?"},
  {"from": "gpt", "value": "Une clause léonine est une disposition contractuelle..."}
]}

Per un primo fine-tune, scegli Alpaca: un solo turno, formato chiaro, supportato nativamente da Unsloth. Alcune regole fondamentali per la qualità:

Volume minimo
300 esempi per osservare un effetto, 1000-5000 per un fine-tune solido, 10.000+ per un risultato serio. Meno di 300, perdi il tuo tempo.
Qualità > quantità
100 esempi impeccabili battono 5000 esempi rumorosi. Rileggi. Fai rileggere. Il modello impara letteralmente il tuo dataset, difetti inclusi.
Diversità degli input
Se tutti i tuoi input iniziano con 'Traduci', il modello non saprà più fare nulla di diverso. Varia le formulazioni.
Lunghezze bilanciate
Se tutti i tuoi output sono di 2 frasi, il modello non saprà più produrre risposte lunghe. Varia.
Riserva il 10% per la validazione
Suddividi in modo casuale i dati tra train.jsonl e val.jsonl per misurare l'overfitting.
→
Generare un dataset sintetico
Non hai a disposizione 1000 esempi? Usa un modello grande (Claude, GPT-5, o Qwen 3.8 27B in locale) per generare coppie sintetiche di istruzione e output a partire dai tuoi documenti interni. È la tecnica standard, detta "self-instruct". Prevedi da 1 a 2 ore di generazione per 1000 esempi.

#Il notebook Unsloth commentato

Ecco uno script completo e minimale per eseguire il fine-tuning di Qwen 3.5 9B in QLoRA sul tuo dataset. Da eseguire come file .py o in un notebook Jupyter.

finetune.py — passo 1: caricamento
from unsloth import FastLanguageModel
import torch

MODEL = "unsloth/Qwen3.5-9B-Instruct-bnb-4bit"
MAX_SEQ = 2048

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = MODEL,
    max_seq_length = MAX_SEQ,
    dtype = None,           # auto : bf16 sur Ampere+, fp16 sinon
    load_in_4bit = True,    # QLoRA : modèle quantifié 4-bit
)

Unsloth ospita versioni pre-quantizzate a 4 bit della maggior parte dei modelli popolari su Hugging Face (prefisso unsloth/). Download più veloce, avvio immediato. La prima esecuzione scarica circa 5 GB.

finetune.py — passo 2: configurazione LoRA
model = FastLanguageModel.get_peft_model(
    model,
    r = 16,                  # rang de la décomposition LoRA
    target_modules = [
        "q_proj", "k_proj", "v_proj", "o_proj",
        "gate_proj", "up_proj", "down_proj",
    ],
    lora_alpha = 16,
    lora_dropout = 0,
    bias = "none",
    use_gradient_checkpointing = "unsloth",  # économie VRAM
    random_state = 42,
)

Il rango r=16 è un buon valore predefinito. Aumentalo a 32 o 64 se hai molti dati (>10k) e un dominio molto distante da quello del preaddestramento. Un rango più alto = più parametri addestrabili = maggiore capacità ma maggiore rischio di sovraddestramento.

finetune.py — passo 3: dataset
from datasets import load_dataset

ALPACA_PROMPT = """### Instruction:
{}

### Input:
{}

### Réponse:
{}"""

EOS = tokenizer.eos_token

def format_prompt(ex):
    texts = [
        ALPACA_PROMPT.format(i, inp or "", out) + EOS
        for i, inp, out in zip(ex["instruction"], ex["input"], ex["output"])
    ]
    return {"text": texts}

ds = load_dataset("json", data_files="train.jsonl", split="train")
ds = ds.map(format_prompt, batched=True)
!
Non dimenticare il token EOS
Senza EOS alla fine di ogni esempio, il modello impara a non fermarsi mai. Sintomo classico: il tuo modello sottoposto a fine-tuning genera all'infinito, si ripete o prosegue con una nuova domanda inventata. Verifica sempre che tokenizer.eos_token sia effettivamente aggiunto.
finetune.py — passo 4: addestramento
from trl import SFTTrainer
from transformers import TrainingArguments

trainer = SFTTrainer(
    model = model,
    tokenizer = tokenizer,
    train_dataset = ds,
    dataset_text_field = "text",
    max_seq_length = MAX_SEQ,
    args = TrainingArguments(
        per_device_train_batch_size = 2,
        gradient_accumulation_steps = 4,    # batch effectif = 8
        warmup_steps = 10,
        num_train_epochs = 3,
        learning_rate = 2e-4,
        fp16 = not torch.cuda.is_bf16_supported(),
        bf16 = torch.cuda.is_bf16_supported(),
        logging_steps = 10,
        optim = "adamw_8bit",
        weight_decay = 0.01,
        lr_scheduler_type = "linear",
        seed = 42,
        output_dir = "outputs",
    ),
)

trainer.train()

Su una RTX 3090 con un dataset di 1000 esempi e 3 epoche, considera da 20 a 40 minuti. Su una RTX 4090, da 12 a 25 minuti. Su una RTX 5090, circa da 8 a 15 minuti. Monitora la loss: deve diminuire regolarmente e poi stabilizzarsi. Se aumenta sul set di validazione, il modello sta andando in overfitting.

→
Quando fermarsi
Un valore predefinito di 3 epoche è un buon punto di partenza per un dataset di 1k-5k esempi. Oltre 5 epoche, il rischio di sovraapprendimento diventa serio: il modello ripete a memoria invece di generalizzare. Se hai più di 20k esempi, scendi a 1-2 epoche.

#Esportazione GGUF verso Ollama

Il tuo adattatore LoRA si trova in outputs/. È un file di qualche decina di MB, separato dal modello di base. Per usarlo in Ollama, due passaggi: fondere l'adattatore con il modello, poi convertire il risultato in GGUF quantizzato.

finetune.py — passo 5: esportazione GGUF Q4_K_M
model.save_pretrained_gguf(
    "mon-modele-gguf",
    tokenizer,
    quantization_method = "q4_k_m",  # le défaut recommandé
)

Unsloth si occupa di tutto: fusione adattatore + base, conversione tramite llama.cpp, quantizzazione Q4_K_M. Il risultato: un file mon-modele-gguf/unsloth.Q4_K_M.gguf di circa 5,3 GB per un 9B. Q5_K_M e Q8_0 sono disponibili se vuoi maggiore precisione (e maggiore peso).

Per caricarlo in Ollama, che è in ascolto su localhost:11434, crea un Modelfile minimale e poi registra il modello:

Modelfile
cat > Modelfile <<EOF
FROM ./mon-modele-gguf/unsloth.Q4_K_M.gguf

TEMPLATE """### Instruction:
{{ .Prompt }}

### Réponse:
"""

PARAMETER temperature 0.7
PARAMETER stop "### Instruction:"
EOF

ollama create mon-modele -f Modelfile
ollama run mon-modele
i
Il template deve corrispondere
Il template Ollama deve riprodurre esattamente il formato utilizzato durante l'addestramento (qui Alpaca con ### Instruction: e ### Réponse:). Altrimenti il modello riceve prompt che non ha mai visto e genera allucinazioni. Se hai usato ShareGPT o ChatML, adatta di conseguenza.

#Suggerimenti e risoluzione dei problemi

OutOfMemoryError all'avvio
Riduci per_device_train_batch_size a 1 e aumenta gradient_accumulation_steps in proporzione. Riduci max_seq_length a 1024 se i tuoi esempi sono brevi.
Loss che non diminuisce
Tasso di apprendimento troppo basso (prova 5e-4) o formato del prompt errato. Stampa 2-3 esempi di ds[0]["text"] e verifica visivamente che assomiglino a ciò che desideri.
Perdita che esplode (NaN)
Tasso di apprendimento troppo alto. Riportalo a 1e-4. Oppure attiva bf16 se usavi fp16 su Ampere+ — fp16 tende a causare overflow.
Modello che ripete all'infinito
Token EOS mancante durante l'addestramento, o parametro stop mancante nel Modelfile. I due problemi si verificano spesso insieme.
Overfitting evidente
La loss di training scende, quella di validazione risale. Interrompi all’epoca in cui divergono. Riduci epochs o aumenta lora_dropout a 0.05.
Esportazione GGUF che va in crash
Unsloth scarica llama.cpp automaticamente al primo utilizzo — assicurati di avere git, cmake e build-essential installati su Linux. Su Mac, esegui xcode-select --install.
Modello che ignora le nuove istruzioni
Dataset non abbastanza vario o rango LoRA troppo basso. Imposta r=32 e lora_alpha=64 e riesegui l'addestramento.
→
Mantenere l'adattatore separato
Per iterare rapidamente, conserva anche una versione non fusa dell'adattatore (~100 MB) con model.save_pretrained("adapter"). Puoi riutilizzarla in seguito con un altro modello di base o combinarla con altri adattatori senza dover scaricare di nuovo tutto.

#Per approfondire

Hai un primo modello fine-tuned che funziona in Ollama. Tre percorsi naturali per approfondire:

Scegliere la quantizzazione giusta
Hai esportato per default in Q4_K_M. Il manuale sulla scelta della quantizzazione confronta Q4_K_M, Q5_K_M, Q8_0 e FP16 — utile per decidere se la qualità del tuo fine-tune merita Q5 o Q8.
RAG piuttosto che fine-tune per la conoscenza
Se vuoi che il modello conosca i tuoi documenti (e non solo uno stile), la guida introduttiva al RAG locale spiega perché il RAG è quasi sempre preferibile al fine-tuning per introdurre informazioni fattuali.
Hardware per passare ai grandi modelli
Per effettuare il fine-tuning di modelli da 32B o 70B in QLoRA, servono rispettivamente 24 GB e 48 GB di VRAM. La guida «Quale LLM per 24 GB di VRAM» illustra ciò che è fattibile su RTX 3090, 4090 e RX 7900 XTX.
Questa guida ti è stata utile?

Un feedback, un errore, una precisazione? Facci sapere, così la guida migliora per tutti.