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.
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:
- Merge:
W' = W + B·A. Il modello adattato è identico al base in shape, zero overhead. - Adapter mode: tieni separati
We(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
- 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.
- Rank bottleneck: se il “vero” ΔW ha rank intrinseco alto, LoRA fatica.
- Catastrofic forgetting parziale: se LoRA divaga, può comunque erodere capacità del base, anche se in misura minore del full fine-tuning.
- 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
- 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-Instructsu un dataset SFT tuo (es. 1k esempi della tua nicchia). - 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 - Train per ~30 min su 1 epoca. Confronta risposte prima/dopo su 10 prompt.
- Misura il rank effettivo: prova
r = 4, 16, 64su stesso dataset. Quando smette di migliorare la loss? - 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.