🧙 Maestro Yoda Cap. 20 · Training, fine-tuning, allineamento

Puntata 218

Puntata 218 — LoRA, QLoRA, PEFT

Livello: 🧙 Maestro Yoda · Capitolo 20 · Training, fine-tuning, allineamento

Full fine-tuning di un 70B richiede 8 H100. LoRA richiede una RTX 3090. Stessa qualità, sul 90% dei task. È il “sport per tutti” del fine-tuning.

due laboratori a confronto. Sinistra: hangar con 8 server e cavi ovunque (full fine-tuning). Destra: un ragazzo a casa col portatile, una RTX 3090 di lato (QLoRA). Stesso modello in uscita

Il problema: il fine-tuning “pieno” è troppo costoso

Aggiornare tutti i pesi di un Llama-3 70B richiede:

  • Modello in BF16: 140 GB di VRAM.
  • Gradients (uguale dim): 140 GB.
  • Optimizer state Adam (2× pesi): 280 GB.
  • Attivazioni durante backward: 50-200 GB a seconda del batch/context.

Totale: ~600-800 GB di VRAM. Anche su 8× H100 80 GB (640 GB) sei al limite, e devi distribuire con ZeRO + offload.

Per il 99% degli sviluppatori, full fine-tuning è inaccessibile. Ed è inutile: la maggior parte delle “modifiche” che vuoi fare al modello (un task specifico, un nuovo dominio, uno style) non richiede di toccare tutti i pesi.

Da qui nascono i PEFT — Parameter-Efficient Fine-Tuning. Tecniche che aggiornano una piccola frazione dei pesi (tipicamente <1%).


LoRA — Low-Rank Adaptation

Paper: Hu et al., Microsoft 2021. arXiv:2106.09685. Il PEFT che ha vinto.

Idea matematica: invece di aggiornare i pesi W ∈ R^{d×k}, fattorizza l’aggiornamento ΔW come prodotto di due matrici a basso rango:

ΔW = B · A

dove B ∈ R^{d×r} e A ∈ R^{r×k}, con r << min(d, k) (tipicamente r = 4, 8, 16, 32, 64).

Forward pass diventa:

y = (W + B·A) · x = W·x + B·(A·x)

W resta congelato. Si addestrano solo A e B.

Quanto risparmi

Per un Llama-3 70B, i layer più importanti sono Q, K, V, O (~ 64 layer × 4 matrici × ~50M params/each = ~12B params). Con LoRA r = 16:

  • LoRA params per matrice: (d + k) · r ≈ 250k
  • Total LoRA params: ~64M

~190× meno parametri trainabili. Optimizer state proporzionale → VRAM totale crolla.

Inferenza

A inferenza puoi:

  1. Merge: W' = W + B·A. Il modello adattato è identico al base in shape, zero overhead.
  2. Adapter mode: tieni separati W e (A, B). Caricabile/scaricabile a runtime. Permette multi-tenant LoRA: 1 modello base + 1000 LoRA = 1000 modelli personalizzati con 1 sola istanza di base.

Quest’ultimo pattern è il “trucco” su cui sono basati molti servizi cloud (es. together.ai LoRA serving, replicate.com).


QLoRA — Quantized LoRA

Paper: Dettmers, Pagnoni, Holtzman, Zettlemoyer (UW + Allen AI, 2023). arXiv:2305.14314.

Idea: combina LoRA con quantization del modello base.

  • Base model in 4-bit (NF4, NormalFloat 4): 140 GB diventano ~35 GB.
  • LoRA adapter in BF16: ~250 MB.
  • Optimizer state solo per il LoRA: ~500 MB.
  • Forward in BF16 (de-quantizzato on-the-fly), backward solo sul LoRA.

Risultato: fine-tuning di un 70B su una singola A100 80 GB o 48 GB, e di un 33B su una RTX 3090 24 GB.

Caratteristiche tecniche:

  • NF4 quantization: data type info-teoricamente ottimale per weights normali.
  • Double quantization: anche i quantization constant sono quantizzati (risparmio ulteriore).
  • Paged optimizers: gestione paged della memoria (usa shared CPU se VRAM in sovraccarico).

QLoRA ha democratizzato il fine-tuning di modelli grandi. Senza esagerazione: prima del paper, fine-tunare un 65B richiedeva un cluster. Dopo, lo fai sul portatile da gaming.


Altri PEFT (oltre LoRA)

Quasi nessuno li usa più, ma per cultura:

  • Prefix Tuning (Li & Liang, 2021): impari un piccolo “prefisso” di token virtuali aggiunti al prompt.
  • Prompt Tuning (Lester et al., 2021): variante più semplice del prefix tuning.
  • Adapter modules (Houlsby et al., 2019): aggiungi piccoli MLP “in mezzo” ai layer. Storico, prima di LoRA.
  • (IA)³ (Liu et al., 2022): scalatori imparati per chiave/valore/feed-forward.
  • BitFit (Zaken et al., 2021): aggiorna solo i bias. Surprisingly effective per task semplici.
  • DoRA (Liu et al., 2024): decomposizione di LoRA in magnitudo + direzione. Migliora leggermente LoRA in alcuni regimi.
  • Bone, AdaLoRA, VeRA, …: tante varianti che ottimizzano dettagli.

Tendenza 2026: LoRA / QLoRA + DoRA è lo standard. Le altre tecniche restano per casi specifici.


Come scegliere r (rank)

Il rank è il principale iperparametro di LoRA. Linee guida pratiche:

  • Task di style / persona / tono: r = 4-8. Molto poca capacity necessaria.
  • Task di knowledge transfer (nuovo dominio): r = 16-32.
  • Task complessi (code, math, multi-step reasoning): r = 64-128.
  • Vuoi avvicinarti al full fine-tuning: r = 256+, ma diventa meno “efficient”.

Regola empirica: parti da r = 16, alza se la loss non converge. Doppio rank ≈ doppio costo.

Altro iperparametro: lora_alpha (scaling factor). Tipicamente lora_alpha = 2 × r o = r.

Quali layer LoRA-fy

Default: solo Q, V (paper originale). Modernamente: Q, K, V, O + spesso anche MLP layers (up_proj, down_proj, gate_proj in Llama). Più layer = più capacity, più memoria.

target_modules: ["q_proj", "k_proj", "v_proj", "o_proj",
                 "gate_proj", "up_proj", "down_proj"]

è il setting “completo” che molti dataset richiedono.


Limiti di LoRA

  1. Non sostituisce sempre il full fine-tuning: per modificare profondamente capacità (es. aggiungere una nuova lingua mai vista, o un dominio molto diverso), LoRA può non bastare. Continued pretraining full è preferibile.
  2. Rank bottleneck: se il “vero” ΔW ha rank intrinseco alto, LoRA fatica.
  3. Catastrofic forgetting parziale: se LoRA divaga, può comunque erodere capacità del base, anche se in misura minore del full fine-tuning.
  4. Iperparametri sensibili: r, alpha, dropout, target modules — combinazioni sub-ottimali producono risultati mediocri.

Stack pratico nel 2026

Libreria standard: PEFT di HuggingFace (pip install peft).

from peft import LoraConfig, get_peft_model
from transformers import AutoModelForCausalLM

model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3.1-8B")

lora_config = LoraConfig(
    r=16, lora_alpha=32, lora_dropout=0.05,
    target_modules=["q_proj","k_proj","v_proj","o_proj"],
    task_type="CAUSAL_LM"
)
model = get_peft_model(model, lora_config)
print(model.print_trainable_parameters())
# trainable: 0.5% di total params

Per QLoRA aggiungi BitsAndBytesConfig(load_in_4bit=True, ...).

Per training: trl’s SFTTrainer integra LoRA out-of-the-box. Alternative: axolotl, unsloth (2-5× più veloce di transformers per LoRA), LLaMA-Factory.


LoRA in produzione

Pattern moderno per servizi multi-tenant:

GPU (H100 80GB)
├── Base model in 4-bit (Llama-3.1-70B, ~35 GB)
├── LoRA #1 (cliente A, italiano legale) — 200 MB
├── LoRA #2 (cliente B, inglese medico) — 200 MB
├── LoRA #3 (cliente C, codice Rust) — 200 MB
└── ... fino a 50-100 LoRA caricabili

Frameworks come vLLM, LoRAX, S-LoRA permettono switching dinamico di LoRA tra richieste. Una GPU serve decine di “modelli specializzati” contemporaneamente.

Questo è il “Stable Diffusion model” applicato agli LLM: 1 base + tanti adapter custom. Aziende come Replicate, Together, Modal vendono questa esperienza.


Glossario lampo

  • Low-rank decomposition — fattorizzare una matrice come prodotto di due matrici piccole.
  • Quantization (4-bit, 8-bit) — rappresentare i pesi con meno bit per ridurre la memoria.
  • NF4 — NormalFloat 4-bit, format ottimizzato per weights con distribuzione normale.
  • Merge — fondere LoRA nel base model per ottenere un modello “monolitico”.
  • Multi-LoRA serving — servire più LoRA su un singolo base model in produzione.

TEST hands-on

  1. Su una macchina con almeno 24 GB VRAM (es. RTX 3090 o A4000) o Colab Pro, fai QLoRA di meta-llama/Llama-3.1-8B-Instruct su un dataset SFT tuo (es. 1k esempi della tua nicchia).
  2. Usa unsloth:
    from unsloth import FastLanguageModel
    model, tokenizer = FastLanguageModel.from_pretrained(
        "unsloth/Llama-3.1-8B-Instruct-bnb-4bit",
        max_seq_length=2048, load_in_4bit=True
    )
    model = FastLanguageModel.get_peft_model(model, r=16, target_modules=[...])
    # SFTTrainer come al solito
  3. Train per ~30 min su 1 epoca. Confronta risposte prima/dopo su 10 prompt.
  4. Misura il rank effettivo: prova r = 4, 16, 64 su stesso dataset. Quando smette di migliorare la loss?
  5. Bonus: prova DoRA (è una flag in peft: use_dora=True). Guadagno marginale ma misurabile.

Take-away

LoRA non ha inventato una nuova matematica: ha applicato la fattorizzazione a basso rango al problema giusto al momento giusto. Il risultato è che oggi una singola GPU consumer può fine-tunare modelli che 3 anni fa richiedevano cluster da supercomputer. È la più importante “democratizzazione” della storia dell’AI, ed è ironicamente un trucco di algebra lineare di 60 anni fa.


➡️ Prossima puntata: Distillation — modelli piccoli da modelli grandi.