Discesa del gradiente: SGD, momentum, Adam
Scegliere come scendere è scegliere quale modello otterrai. Stesso paesaggio, stessa inizializzazione, stessa loss: optimizer diversi portano in regioni diverse, con generalizzazione diversa, in tempi diversi. Questo capitolo è il manuale operativo per fare quella scelta con cognizione di causa.
Perché questo capitolo
Sezione intitolata “Perché questo capitolo”Il capitolo precedente — Gradienti e derivate direzionali senza analisi — ha installato il gradiente come oggetto geometrico e ha mostrato che muoversi nel verso opposto al gradiente fa diminuire la loss. È la base teorica. Ma da quella base alla riga optimizer = torch.optim.AdamW(model.parameters(), lr=1e-5, weight_decay=0.1, betas=(0.9, 0.95)) che apre quasi ogni training script di un modello moderno c’è un salto enorme. Quel salto è fatto di settant’anni di esperimenti, errori, fix e convenzioni accumulate. Questo capitolo lo ricostruisce.
La prima ragione per dedicarci un capitolo intero è che ogni training run di un large language model — pretraining, fine-tuning supervisionato, RLHF, distillation, LoRA — gira su una variante di Adam. Capire le scelte numeriche dietro quella riga — perché AdamW e non Adam, perché lr 1e-5 e non 1e-4, perché un warmup di duecento step, perché weight decay 0.1, perché clipping a 1.0 — separa training che converge da training che diverge a metà notte e brucia decine di migliaia di euro di GPU. Non è iperbole: bug di optimizer hanno fatto saltare run da milioni di dollari, perché l’instabilità si manifesta solo dopo che la maggior parte del compute è già speso.
La seconda è che le patologie tipiche del training — loss che diverge, gradienti che esplodono, plateau infiniti, generalizzazione misteriosamente cattiva — non si diagnosticano leggendo lo stack trace. Si diagnosticano leggendo le curve di gradient norm, le statistiche del secondo momento di Adam, lo schedule effettivo del learning rate. Sapere come ognuno di questi numeri viene calcolato è il prerequisito per sapere come va guardato.
La terza è che gli optimizer moderni sono un caso di studio bellissimo di un fenomeno tipico del campo: una pila di hack pragmatici che funzionano molto bene insieme, ognuno motivato da un fallimento osservato nella versione precedente. Adam corregge un problema di Adagrad, AdamW corregge un problema di Adam, warmup corregge un problema dei primi step di Adam, clipping corregge un problema dei picchi di gradiente, e così via. Ricostruire la catena è capire come si ragiona in deep learning quando la teoria è guida ma non legge.
Contesto
Sezione intitolata “Contesto”Il punto di partenza storico è la stessa nota di tre pagine di Augustin-Louis Cauchy vista al capitolo 106: 18 ottobre 1847, Académie des Sciences. Cauchy descrive l’aggiornamento x ← x − η ∇f(x) come metodo numerico per risolvere sistemi di equazioni. È vanilla gradient descent. Per un secolo questo metodo resta una curiosità teorica e un esercizio nei libri di analisi numerica.
La svolta operativa arriva nel 1951 con Herbert Robbins (1915-2001, statistico americano della Columbia) e Sutton Monro, A Stochastic Approximation Method (Annals of Mathematical Statistics 22(3), pp. 400-407). I due dimostrano che, anche se al posto del vero gradiente si dispone di un suo stimatore rumoroso, l’iterazione converge a patto che lo step size η_t soddisfi due condizioni: la somma degli η_t diverge (si fanno passi sufficienti), la somma dei loro quadrati converge (il rumore è controllato). È il fondamento teorico di stochastic gradient descent (SGD), che oggi sta sotto ogni training di rete neurale: il “gradiente” calcolato su un minibatch di dati è esattamente uno stimatore rumoroso del gradiente sul dataset intero.
Tre anni dopo i tredici anni successivi alla Robbins-Monro, Boris Polyak (1935-2023, matematico sovietico) introduce il momentum classico. Il paper è Some methods of speeding up the convergence of iteration methods (USSR Computational Mathematics and Mathematical Physics, 1964, vol. 4(5), pp. 1-17). L’idea: il passo di update non dipende solo dal gradiente attuale ma anche dal passo precedente, come una pallina pesante che eredita inerzia. Il metodo si chiama heavy ball.
Diciannove anni dopo, Yurii Nesterov (1956-, matematico russo-belga ora alla UCLouvain) propone una variante apparentemente piccola — calcolare il gradiente nel punto “look-ahead” anziché in quello attuale — e dimostra che il tasso di convergenza passa da O(1/T) a O(1/T²) su funzioni smooth convesse (A method for unconstrained convex minimization problem with the rate of convergence O(1/k²), Soviet Math. Doklady 1983). È un risultato ottimale: nessun metodo del primo ordine può fare meglio.
Il filone moderno degli optimizer adattivi parte nel 2011 con John Duchi, Elad Hazan e Yoram Singer, che pubblicano Adagrad (Adaptive Subgradient Methods for Online Learning and Stochastic Optimization, JMLR 12, 2121-2159). L’innovazione: ogni coordinata ha il proprio learning rate, scalato dalla storia dei suoi gradienti. Coordinate con gradienti rari ricevono passi più grandi.
Nel 2012 Geoffrey Hinton introduce RMSProp in una lecture del corso Coursera Neural Networks for Machine Learning, sostituendo la somma cumulativa di Adagrad con una media mobile esponenziale. RMSProp non ha mai avuto un paper formale ma è entrato nella pratica tramite citazioni delle slide.
Nel 2014, Diederik Kingma (1982-, ricercatore olandese poi a OpenAI e Google DeepMind) e Jimmy Ba (Toronto) pubblicano Adam (Adam: A Method for Stochastic Optimization, arXiv:1412.6980, ICLR 2015). Adam combina momentum (primo momento dei gradienti) e RMSProp (secondo momento), con bias correction per i primi step. Diventa l’optimizer di default del deep learning per oltre un decennio.
Nel 2017 Ilya Loshchilov e Frank Hutter (entrambi Università di Friburgo) pubblicano Decoupled Weight Decay Regularization (arXiv:1711.05101, ICLR 2019), introducendo AdamW. La tesi del paper: in optimizer adattivi, weight decay e L2 regularization NON sono equivalenti, e l’uso storico di L2 in Adam è subottimale. AdamW è oggi il default per pretraining LLM (GPT-3, Llama, Mistral, Qwen, Gemini).
Sulla diagnosi del paesaggio di loss, Yann Dauphin e collaboratori in Identifying and attacking the saddle point problem in high-dimensional non-convex optimization (NeurIPS 2014) mostrano che in spazi ad alta dimensione i punti critici sono in stragrande maggioranza saddle, non minimi locali. Questo cambia la diagnosi standard: “il modello è bloccato in un minimo locale” è quasi sempre falsa.
Decoder dei termini chiave che ricorrono nel capitolo, alla prima menzione:
- L-smooth: funzione il cui gradiente è Lipschitz-continuo con costante L. Significa che il gradiente non può cambiare troppo bruscamente: ‖∇f(x) − ∇f(y)‖ ≤ L ‖x − y‖.
- convessa: funzione la cui retta che congiunge due punti del grafico sta sopra il grafico. Implica un solo bacino di attrazione.
- strongly convex: convessa con curvatura minima μ > 0. Convergenza più rapida.
- numero di condizione κ: rapporto fra autovalore massimo e minimo dell’Hessiana, misura quanto la valle è “stretta e lunga”.
- bias correction: in Adam, fattore moltiplicativo che corregge la sotto-stima dei momenti dovuta all’inizializzazione a zero.
- warmup: fase iniziale del training in cui il learning rate cresce da zero (o quasi) al suo valore massimo, lentamente.
Differenziazione esplicita rispetto al capitolo precedente. Capitolo 106 risponde alla domanda “cosa è il gradiente, perché esiste, perché GD funziona in linea di principio”. Questo capitolo (107) risponde alla domanda “data una rete con miliardi di parametri, COSA si esegue nel training script, con QUALI scelte numeriche, e PERCHÉ una scelta è meglio di un’altra”. È la differenza fra capire la fisica del piano inclinato e progettare un sistema frenante per un’auto.
L’intuizione
Sezione intitolata “L’intuizione”Due angoli prima del formalismo. Vanno tenuti entrambi.
Angolo 1 — la pallina con attrito e inerzia
Sezione intitolata “Angolo 1 — la pallina con attrito e inerzia”Immagina di lasciar cadere una pallina dentro una valle a forma di parabola allungata: stretta in una direzione, larga nell’altra. La direzione di massima discesa, il gradiente, punta quasi sempre verso le pareti laterali strette, non lungo il fondo della valle. Una pallina senza inerzia — vanilla gradient descent — rimbalza da una parete all’altra, oscillando, e progredisce lentamente lungo il fondo. Una pallina con inerzia — momentum — accumula velocità lungo il fondo della valle (dove la direzione si conserva) e smorza le oscillazioni laterali (dove la direzione cambia segno ad ogni passo). Arriva al minimo molto prima.
Aggiungi attrito: la pallina non rotola all’infinito, si ferma in fondo. L’attrito è il coefficiente β che decay il momentum. Senza attrito (β=1) la pallina passa il minimo e oscilla. Con troppo attrito (β piccolo) torna a comportarsi come vanilla GD. Β tipico 0.9 è un compromesso che funziona molto bene su quasi tutto.
Angolo 2 — il passo che si adatta a ogni dimensione
Sezione intitolata “Angolo 2 — il passo che si adatta a ogni dimensione”Secondo angolo, ortogonale al primo. In un modello con miliardi di parametri, le scale dei gradienti per parametri diversi sono molto diverse. Un peso del primo layer di un transformer riceve gradienti tipicamente più piccoli di un peso del layer finale; un peso di un embedding di un token raro riceve gradienti che sono zero la maggior parte del tempo, più un picco occasionale; un peso di una norma layer riceve gradienti regolari ma di magnitudo piccola.
Un learning rate unico per tutti questi parametri è un compromesso brutale: o è troppo grande per i parametri “rumorosi” e li fa esplodere, o è troppo piccolo per i parametri “lenti” e non li muove abbastanza. La soluzione adattiva: ogni parametro ha il proprio learning rate effettivo, dimensionato dalla scala storica del proprio gradiente. Adagrad usa la somma cumulativa, RMSProp usa una media mobile, Adam combina questo con momentum. Il risultato pratico è che si può scegliere un singolo lr globale e l’optimizer si occupa di calibrare il passo coordinata per coordinata.
I due angoli convergono in Adam. Momentum — angolo 1 — accelera il movimento lungo direzioni stabili. Scaling adattivo — angolo 2 — calibra l’ampiezza del passo per coordinata. Insieme, danno un metodo che funziona ragionevolmente bene su quasi qualsiasi problema senza tuning fine.
La meccanica
Sezione intitolata “La meccanica”Tutte le formule usano solo aritmetica vettoriale e prodotto componente-wise. Indichiamo con θ il vettore dei parametri (i pesi della rete), con L(θ) la loss, con g_t = ∇L(θ_t) il gradiente al passo t.
Vanilla gradient descent
Sezione intitolata “Vanilla gradient descent”L’aggiornamento è:
Una sola riga, un solo iperparametro η (learning rate). Su funzioni convesse e L-smooth, con η = 1/L, vale il seguente risultato classico (TEOREMA, dimostrato in Boyd-Vandenberghe 2004 e Bottou-Curtis-Nocedal 2018):
cioè la suboptimalità decresce come O(1/T). Su funzioni μ-strongly convex e L-smooth, decresce esponenzialmente con tasso (1 − μ/L)^T: convergenza lineare, controllata dal numero di condizione κ = L/μ.
In pratica, deep learning non è convesso e L non è nota. Il valore di η si sceglie empiricamente, partendo da un default (1e-3 per Adam, 0.1 per SGD su CNN, 1e-5 per fine-tuning di LLM) e adattando.
Stochastic gradient descent
Sezione intitolata “Stochastic gradient descent”In una rete neurale moderna L è una somma su tutto il dataset. Calcolare g_t esattamente richiede un forward+backward su tutti i dati, costo proibitivo. SGD usa uno stimatore: un minibatch B di esempi, con
Lo stimatore è non-distorto: E[g_t] = ∇L(θ_t). La varianza decresce come 1/|B|. EQUIVALENZA definitionale: SGD è gradient descent con batch size 1; minibatch SGD è il caso intermedio fra batch=1 e batch=N (full batch).
Il TEOREMA di convergenza di Robbins-Monro: se Σ η_t = ∞ e Σ η_t² < ∞ (per esempio η_t = c/t), SGD converge a un punto stazionario. Per funzioni convesse non-smooth il tasso è O(1/√T).
Pseudo-codice PyTorch minimo:
optimizer = torch.optim.SGD(model.parameters(), lr=1e-2)for batch in loader: optimizer.zero_grad() loss = criterion(model(batch.x), batch.y) loss.backward() optimizer.step()loss.backward() calcola g_t via autograd (chain rule applicata al grafo di computazione), optimizer.step() applica θ ← θ − η g.
Momentum (Polyak heavy ball)
Sezione intitolata “Momentum (Polyak heavy ball)”Si introduce un vettore v che accumula i gradienti:
β tipico 0.9. L’effetto: v_t è una media esponenziale dei gradienti recenti, pesata con β. Direzioni stabili (gradiente con segno costante) si rinforzano; oscillazioni laterali (gradiente che cambia segno) si cancellano in media. Su quadratiche mal-condizionate il numero di condizione effettivo passa da κ a √κ — accelerazione drammatica per κ grande.
Nesterov accelerated gradient
Sezione intitolata “Nesterov accelerated gradient”Variante: il gradiente viene valutato nel punto “anticipato” θ_t + β (θ_t − θ_{t-1}) anziché in θ_t. Equivalente a:
Su smooth convesse, raggiunge il rate ottimale O(1/T²). In deep learning non-convex il vantaggio è meno netto ma resta spesso positivo. PyTorch lo offre con il flag nesterov=True.
Adagrad
Sezione intitolata “Adagrad”Per ogni coordinata, accumula i quadrati dei gradienti:
Coordinate con gradienti grandi accumulano G_t grande e ricevono passi piccoli; coordinate “lente” mantengono passi grandi. Funziona bene su problemi convessi sparsi (NLP classico, click prediction). Difetto: G_t cresce monotonicamente, l’effective learning rate tende a zero, il training rallenta indefinitamente.
RMSProp
Sezione intitolata “RMSProp”Sostituisci la somma cumulativa con una media mobile esponenziale:
β₂ tipico 0.9 o 0.999. v_t “dimentica” il passato distante; l’effective lr non collassa.
Combina momentum (primo momento m_t) e RMSProp (secondo momento v_t), con bias correction:
Default: β₁ = 0.9, β₂ = 0.999, ε = 1e-8, η = 1e-3. La bias correction è necessaria perché m_0 = v_0 = 0 e ai primi step le medie sono distorte verso zero — senza correzione i primi update sarebbero artificiosamente piccoli.
Pseudo-codice:
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3, betas=(0.9, 0.999), eps=1e-8)Il punto delicato. Storicamente, “weight decay” in Adam è stato implementato aggiungendo λ ‖θ‖² / 2 alla loss, cioè aggiungendo λθ a g_t. Questo è L2 regularization. EQUIVALENZA da argomentare: in SGD vanilla, weight decay (sottrai λθ direttamente dal parametro) e L2 regularization sono esattamente equivalenti, perché il gradiente del termine λ‖θ‖²/2 è esattamente λθ e il passo di SGD è lineare in g.
In Adam, NON sono equivalenti. Se metti λθ dentro g_t, quel termine entra in m_t, in v_t, e viene scalato da √v̂_t. Risultato: parametri con gradienti grandi ricevono regolarizzazione MENO efficace, perché √v̂_t è grande e divide via il termine di decay. Esattamente l’opposto di quello che si vorrebbe: i parametri grandi e attivi sono quelli che dovrebbero essere “tirati indietro” di più.
AdamW separa i due. L’aggiornamento è:
Il termine λ θ_t è applicato direttamente al parametro, fuori dalla normalizzazione di Adam. Loshchilov-Hutter mostrano gain consistenti, generalmente di qualche punto percentuale, su molteplici benchmark. AdamW è ora il default per LLM.
Pseudo-codice:
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, betas=(0.9, 0.95), weight_decay=0.1)Da notare β₂ = 0.95 (non 0.999) tipico per LLM training: media più “corta” del secondo momento, reagisce meglio a cambi di scala dei gradienti durante long pretraining.
Cenno. Xiangning Chen e collaboratori (Google, 2023) in Symbolic Discovery of Optimization Algorithms (NeurIPS 2023) hanno usato program search per scoprire un nuovo optimizer. Il risultato, Lion, usa solo il segno del momentum:
Memoria dimezzata rispetto ad Adam (un solo momento). Risultati comparabili o leggermente migliori a parità di compute su training di vision e LLM. Adoption ancora limitata.
Learning rate schedule
Sezione intitolata “Learning rate schedule”Il learning rate non resta costante per tutto il training. Schedule tipici:
- Linear warmup: η cresce linearmente da 0 a η_max in W passi. Critico per Adam: ai primi step v̂_t è instabile e gradient norm è grande, partire con η_max pieno fa esplodere il training.
- Cosine decay: dopo il warmup, η decresce come η(t) = η_min + 0.5 (η_max − η_min)(1 + cos(π t / T)). Smooth, niente drop step da scegliere, dominante in LLM pretraining.
- Step decay: η /= γ ogni N step. Classico per training di CNN.
- OneCycle (Leslie Smith 2017): warmup + decay simmetrico, con momentum inverso. Permette lr massimi più alti per cicli brevi.
Schedule tipico LLM pretraining: warmup 2000 step, poi cosine decay fino al 10% di η_max sull’intera lunghezza del training. Schedule tipico fine-tuning: warmup 50-200 step, cosine o constant.
Gradient clipping
Sezione intitolata “Gradient clipping”Se la norma del gradiente supera una soglia c, scala il gradiente:
if ‖g_t‖ > c: g_t ← c · g_t / ‖g_t‖
Si chiama norm clipping. Default per LLM: c = 1.0. Motivazione: spike rari di gradient norm avvelenano m_t e v_t per molti step seguenti, possono divergere il training. Pascanu, Mikolov e Bengio in On the difficulty of training recurrent neural networks (ICML 2013) hanno documentato per primi il problema su RNN; transformer lo subiscono in misura minore ma sempre presente, soprattutto dopo cambi di distribuzione del dato (shuffle di shard, transizione fra fasi del curriculum). Il clipping è economico, robusto, quasi sempre attivato.
Second-order: cenno
Sezione intitolata “Second-order: cenno”Se H è l’Hessiana di L in θ_t, il metodo di Newton aggiorna come:
Convergenza quadratica vicino al minimo, ma costo proibitivo: O(d²) memoria, O(d³) per inversione, con d = numero di parametri (10⁹ per un LLM piccolo). Quasi-Newton (BFGS, L-BFGS) approssimano H⁻¹ con storia di gradienti, costo O(md) — usati per problemi piccoli (logistic regression, calibration) ma non in deep learning. Ragione operativa: il deep learning è non-convex, H ha autovalori negativi vicino ai saddle, Newton va in direzione sbagliata. Approssimazioni curvature-aware scalabili come K-FAC (Martens 2015) e Shampoo (Gupta 2018) esistono ma adoption è ancora limitata.
flowchart LR
A[batch input] --> B[forward pass]
B --> C[loss]
C --> D[backward pass<br/>autograd]
D --> E[gradiente g_t]
E --> F[clip g_t<br/>‖g‖ ≤ c]
F --> G[update m_t, v_t<br/>stato Adam]
G --> H[bias correction<br/>m̂_t, v̂_t]
H --> I[applica update<br/>θ ← θ − η m̂/√v̂ − η λ θ]
I --> J[step dello scheduler<br/>η ← schedule]
Figura 6 — pipeline di un singolo step di training con Adam, gradient clipping, weight decay e scheduler — forward, loss, backward, clip, update m/v, bias correction, applica update, scheduler step
Tre esempi eterogenei, in ordine crescente di realismo.
Esempio 1 — quadratica mal-condizionata
Sezione intitolata “Esempio 1 — quadratica mal-condizionata”Funzione di prova: f(x, y) = x² + 100 y². Minimo in (0, 0). Numero di condizione κ = 100: la valle è cento volte più stretta in y che in x.
Vanilla GD con η = 0.01 partendo da (1, 1):
def f_grad(x, y): return (2*x, 200*y)x, y = 1.0, 1.0for t in range(50): gx, gy = f_grad(x, y) x -= 0.01 * gx y -= 0.01 * gyDopo 50 step la traiettoria mostra oscillazioni violente in y (perché 0.01 · 200 = 2 > 1, il sistema in y è instabile) — in realtà con questo lr il training diverge. Si è costretti a usare η ≤ 1/200 = 0.005, e allora la convergenza in x è lentissima (1000+ step).
GD con momentum (β = 0.9) a η = 0.005 converge in circa 100 step: il momentum smorza le oscillazioni in y e accelera in x. Adam con η = 0.1 (sì, lr cento volte più alto: la normalizzazione adattiva permette passi grandi senza esplodere) converge in 30 step. È la dimostrazione operativa dell’angolo intuitivo: il scaling per coordinata permette lr più aggressivi.
Numericamente, dopo 30 step di Adam con η = 0.1, β₁ = 0.9, β₂ = 0.999: x ≈ 0.04, y ≈ 0.0002. Vanilla GD a η = 0.005 dopo 30 step: x ≈ 0.74, y ≈ 0.001. Differenza un ordine di grandezza in x.
Esempio 2 — MLP su dataset giocattolo
Sezione intitolata “Esempio 2 — MLP su dataset giocattolo”Codice training di un MLP a due hidden layer su un dataset generato (due gaussiane in 2D, classificazione binaria). 1000 esempi, 100 epoch, batch 32.
model = nn.Sequential(nn.Linear(2, 64), nn.ReLU(), nn.Linear(64, 64), nn.ReLU(), nn.Linear(64, 2))loss_fn = nn.CrossEntropyLoss()
# Run A: SGD lr=0.01opt_a = torch.optim.SGD(model.parameters(), lr=0.01)
# Run B: Adam lr=0.001opt_b = torch.optim.Adam(model.parameters(), lr=0.001)
for epoch in range(100): for x, y in loader: opt.zero_grad() loss = loss_fn(model(x), y) loss.backward() opt.step()Risultato tipico: SGD raggiunge accuracy 0.96 in 60 epoch, Adam in 15. La curva di loss di SGD scende monotonicamente; quella di Adam scende più rapidamente all’inizio poi rallenta. Su questo problema giocattolo la differenza in accuracy finale è trascurabile, ma il tempo per convergere differisce di un fattore 4.
Se invece di Adam si usa AdamW con weight_decay=0.01, l’accuracy finale a volte è leggermente più alta (0.97 vs 0.96) per via della migliore generalizzazione su test set; il training loss è leggermente più alto del puro Adam. Trade-off classico.
Esempio 3 — fine-tuning di un LLM
Sezione intitolata “Esempio 3 — fine-tuning di un LLM”Scenario reale: fine-tuning di un modello da 7 miliardi di parametri (Llama-2-7B, Mistral-7B-base, simili) su un dataset di 50.000 esempi di istruzioni custom. Configurazione tipica, riga per riga:
optimizer = torch.optim.AdamW( model.parameters(), lr=1e-5, # piccolo: il prior è prezioso betas=(0.9, 0.95), # β₂=0.95: secondo momento "corto" eps=1e-8, weight_decay=0.1, # forte: AdamW separa correttamente)
scheduler = get_cosine_schedule_with_warmup( optimizer, num_warmup_steps=100, # ~1% dei 10.000 step totali num_training_steps=10000,)
# In training loop:torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)optimizer.step()scheduler.step()Spiegazione voce per voce:
- lr 1e-5: due ordini di grandezza più piccolo del default Adam. Il modello pre-trained ha già un prior eccellente; lr più alti lo distruggono.
- β₂ = 0.95: la stima del secondo momento “dimentica” più rapidamente. Su long training di LLM la distribuzione dei gradienti cambia (curriculum, shard transitions), una memoria troppo lunga (β₂ = 0.999) rallenta la reazione. Default GPT-3, Llama, PaLM.
- weight_decay 0.1: forte regolarizzazione. In AdamW non interferisce con la normalizzazione adattiva. Senza, si overfittano i 50k esempi distruggendo le capability generali del modello.
- warmup 100 step: protegge i primi step dalla bias correction estrema (β₁^t e β₂^t lontani da zero solo dopo decine di step) e da gradienti non ancora “calibrati”.
- cosine decay: smooth, finisce a 10% del lr massimo sui 10k step pianificati.
- clip_grad_norm 1.0: previene spike rari ma catastrofici da batch outlier.
Cambiare uno di questi parametri ha conseguenze osservabili. lr 1e-4: training spesso diverge a step 50. weight_decay 0.0: fine-tuning loss bassissima ma il modello “dimentica” benchmark generali (catastrophic forgetting parziale). Niente warmup: nei primi 20 step la loss spara verso l’alto, talvolta NaN. Niente clipping: un singolo batch sfortunato può rovinare un’epoca.
Applicazioni pratiche
Sezione intitolata “Applicazioni pratiche”L’optimizer è una scelta pervasiva. Alcuni regimi standard:
-
Pretraining LLM (GPT-3, Llama, Mistral, Qwen, Gemma): AdamW con (β₁=0.9, β₂=0.95), weight_decay 0.1, lr massimo 6e-4 per modelli piccoli, 1.2e-4 per modelli da 70B+, schedule cosine con warmup di alcune migliaia di step, gradient clipping a 1.0, batch size in token (1-4M), bfloat16 per pesi e gradient, master copy fp32 dei parametri Adam.
-
Supervised fine-tuning (SFT) di un base model: AdamW, lr 1e-5 a 5e-5, weight_decay 0.01-0.1, warmup 100-500 step, cosine decay, clipping 1.0. Runs brevi (1-3 epoch sul dataset di fine-tuning).
-
RLHF con PPO: due optimizer in gioco, uno per la policy network, uno per il value network. Entrambi AdamW con lr piccoli (1e-6 a 5e-6 per la policy), ratio clipping nel loss (PPO clip), gradient clipping 1.0. Estremamente delicato: lr troppo alto distrugge la policy, troppo basso non sposta il modello.
-
DPO e successori (IPO, KTO, ORPO): AdamW, lr 5e-7 a 5e-6 (più piccolo di SFT perché il segnale è di preferenza, non supervisionato hard), warmup, cosine.
-
LoRA / QLoRA / PEFT (parameter-efficient fine-tuning): AdamW solo sui parametri LoRA (tipicamente <1% del totale), lr 1e-4 a 5e-4 (più alto di full fine-tuning perché i parametri sono meno e meglio inizializzati), weight_decay 0.0 o piccolo, schedule cosine. Memoria dimezzata o più.
-
Distillation (KD): AdamW, lr e weight_decay simili a SFT. Loss combinata teacher-student, ma optimizer e schedule sono standard.
-
Diffusion model training: AdamW con β₁=0.9, β₂=0.999 (originali, non 0.95), weight_decay 0.01, EMA dei pesi mantenuto separatamente per inferenza. Schedule cosine o constant con warmup.
In tutti i casi la scelta dell’optimizer è secondaria rispetto a: qualità dei dati, dimensione del modello, scelta dell’architettura. Ma è il livello dove i bug più subdoli vivono e dove la differenza fra “training che funziona” e “training che spreca compute” si gioca.
Dove si rompe
Sezione intitolata “Dove si rompe”Sezione lunga. Gli optimizer falliscono in modi caratteristici, e riconoscere il pattern dirige la diagnosi.
Adam con weight decay implementato come L2. Il bug storico più comune fino a ~2018. Il termine λθ entrava nei gradienti, veniva scalato da √v̂, e la regolarizzazione effettiva era distorta. Il fix è AdamW. In codici PyTorch vecchi è frequente trovare ancora Adam(..., weight_decay=0.01) invece di AdamW(..., weight_decay=0.01): la riga compila ma il comportamento è subottimale.
Saddle point in alta dimensione. Dauphin 2014 dimostra che in spazi ad alta dimensione la frazione di punti critici che sono minimi locali (tutti gli autovalori dell’Hessiana positivi) decresce esponenzialmente con la dimensione. La frase “il modello è bloccato in un minimo locale” è quasi sempre falsa: è bloccato vicino a un saddle. SGD con rumore aiuta a uscire dai saddle in modo sorprendentemente efficace; gradient descent senza rumore può rallentare di molto.
Loss che esplode senza clipping. Soprattutto in transformer pretraining e in RNN, la distribuzione di ‖g‖ ha code pesanti. Un singolo batch fortunato (o sfortunato) produce gradient norm 100x il tipico. Senza clipping, m_t e v_t di Adam vengono avvelenati per migliaia di step seguenti. Sintomo: loss che va a 1000 e poi a NaN. Fix: gradient clipping, sempre, per qualsiasi training di LLM.
Learning rate troppo alto. Sintomo: loss aumenta nei primi step, poi diverge a NaN. Diagnosi: dimezzare lr, riprovare. Per Adam: ricorda che con bias correction i primi step sono effettivamente amplificati; aggiungere warmup spesso risolve.
Learning rate troppo basso. Sintomo: loss decresce lentissima, modello underfitta. Diagnosi: aumentare lr di un fattore 3-10, vedere se il training accelera senza divergere. Su LLM, fine-tuning con lr 1e-7 produce un modello quasi indistinguibile dal base: troppo poco segnale.
Batch size troppo piccolo. Rumore alto, training instabile. Linear scaling rule (Goyal et al. 2017): se moltiplichi batch per k, moltiplica lr per k. La regola vale empiricamente fino a un certo limite (intorno a 8k-32k token-batch per LLM), oltre la quale il warm-up e altre correzioni diventano necessarie.
Mixed precision e underflow. In fp16 il range degli esponenti è ridotto; gradient piccoli underflow a zero, parametri non vengono aggiornati. Fix: loss scaling (moltiplica la loss per S grande prima di backward, dividi i gradienti per S prima di optimizer step). bf16 ha range più ampio, generalmente non richiede loss scaling.
Non-convexity. TEOREMA di convergenza GD su L-smooth convesse (tasso O(1/T)) cessa di valere su deep learning. Ogni risultato di convergenza disponibile per non-convex smooth dice solo che l’iterazione tende a un punto stazionario (∇L = 0), che può essere minimo, saddle o maximum. In pratica empirica, SGD e AdamW raggiungono soluzioni che generalizzano bene, ma le ragioni sono ancora oggetto di ricerca attiva.
Sharp vs flat minima e generalizzazione. Keskar et al. 2017 sostengono che batch grandi convergano a minimi “sharp” (alta curvatura) che generalizzano peggio dei minimi “flat” trovati da batch piccoli. Dinh et al. 2017 contestano la definizione di sharpness via riparameterizzazione. ANALOGIA didattica utile, ma non ci sono garanzie teoriche pulite. Il debate è aperto.
Bias correction e warmup. Senza warmup, in Adam i primi step hanno β₁^t e β₂^t vicini a 1, quindi 1 − β₁^t e 1 − β₂^t vicini a zero. La bias correction divide per quantità piccole, amplificando m̂ e v̂. Combinato con i primi gradient grandi tipici di un modello non ancora “rodato”, produce update enormi che rovinano i pesi. Warmup mitiga.
Schedule non sincronizzato con la lunghezza del training. Un cosine decay tarato per 100k step e fermato a 50k finisce a metà del decay, lr ancora al 50%. Il modello non ha mai sperimentato il regime di lr basso necessario per il fine-tuning finale. Fix: tarare T sul vero numero di step pianificato.
Gradient accumulation e batch size effettivo. Se accumuli k micro-batch prima di chiamare optimizer.step(), il batch size effettivo è k volte più grande, e dovresti adattare lr di conseguenza (linear scaling). Errore comune: lasciare lr invariato e sotto-utilizzare il batch grande.
Optimizer state che esplode nella RAM/VRAM. Adam mantiene m_t e v_t per ogni parametro: per un modello da 7 miliardi in fp32 servono 7B × 4 byte × 2 momenti = 56 GB solo per lo state dell’optimizer, oltre ai 28 GB dei parametri e ai 28 GB dei gradienti. Su una singola GPU da 80 GB il margine si stringe rapidamente. Mitigations: optimizer state in fp16 o int8 (bitsandbytes 8-bit Adam), Adafactor (Shazeer-Stern 2018) che sostituisce m con uno stimatore fattorizzato, ZeRO-1 di DeepSpeed che shard lo state fra le GPU. Da diagnosticare prima di partire, non a metà del primo step quando OOM.
β₂ troppo alto in long pretraining. Default Adam β₂ = 0.999 implica che la stima di v_t mediato esponenzialmente “dimentica” con time-constant ~1000 step. Su un pretraining da 200k step, questo è molto: se la distribuzione dei gradienti cambia (transizione fra fasi del curriculum, shard switch), v_t reagisce lento. β₂ = 0.95 (time-constant ~20 step) reagisce 50 volte più rapidamente. Ragione operativa per cui i config di Llama, GPT-3 e simili usano 0.95 invece del default ICLR 2015.
ε di Adam troppo piccolo in fp16. ε = 1e-8 è il default di Adam. In fp16 il più piccolo valore positivo rappresentabile è circa 6e-5: ε underflow a zero. La divisione √v̂ / 0 può produrre Inf. Fix: ε = 1e-6 in fp16, o tenere lo state di Adam in fp32 con i pesi in fp16 (master copy fp32, lettura/scrittura in fp16 solo nel forward/backward).
Non-IID-ness del minibatch. Robbins-Monro assume che gli stimatori g_t siano non-distorti e indipendenti. In pratica, batch da uno stesso shard sono correlati, batch dello stesso utente lo sono di più, batch ordinati per lunghezza (sorting per efficiency) introducono bias sistematici. SGD funziona molto meglio di quanto la teoria pulita garantirebbe, ma sotto stress (curriculum learning estremo, dataset molto squilibrato) le assumptions si rompono e il training si comporta in modo impredicibile.
flowchart LR
A[batch input] --> B[forward pass]
B --> C[loss]
C --> D[backward pass<br/>autograd]
D --> E[gradiente g_t]
E --> F[clip g_t<br/>‖g‖ ≤ c]
F --> G[update m_t, v_t<br/>stato Adam]
G --> H[bias correction<br/>m̂_t, v̂_t]
H --> I[applica update<br/>θ ← θ − η m̂/√v̂ − η λ θ]
I --> J[step dello scheduler<br/>η ← schedule]
Figura 6 — Gradient clipping schema: input gradient vector with large norm, clipping operation rescaling to threshold c, output vector aligned but bounded
Una nota di sintesi: gli optimizer del 2026 sono molto più una collezione di hack utili che una teoria pulita. Vanilla GD ha tassi di convergenza dimostrabili sotto ipotesi di convessità. SGD ha tassi sotto ipotesi di rumore controllato. Momentum accelera per via di teoremi precisi su quadratiche convesse. Ma Adam e AdamW, che sono ciò che si usa, hanno bound di convergenza solo per casi semplificati e l’analogia con i casi convessi è euristica, non prova. Funzionano. Funzionano molto bene. Ma il “perché” rigoroso è ancora oggetto di ricerca attiva, in tensione fra teoria e pratica come molte parti di deep learning.
Collegamenti
Sezione intitolata “Collegamenti”- Gradienti e derivate direzionali senza analisi — il prerequisito immediato; questo capitolo presuppone la nozione di ∇f e l’idea di scendere lungo −∇f.
- Entropia, cross-entropy, KL divergence — la loss che si minimizza nella maggior parte dei training di LLM è cross-entropy, e capire la sua forma serve per leggere le curve.
- Prodotto scalare come proiezione e somiglianza — la “direzione di discesa” è interpretabile come prodotto scalare, e momentum è una somma pesata di gradienti che vive nello stesso spazio.
- Probabilità, distribuzioni, valore atteso — SGD è l’iterazione di Robbins-Monro su uno stimatore non-distorto; capire varianza e attesa è fondamentale.
- backpropagation (in preparazione) — il modo concreto in cui g_t viene calcolato in PyTorch tramite autograd.
- mlp-backprop (in preparazione) — il primo training non-banale dove tutti questi pezzi si incontrano.
- regolarizzazione (in preparazione) — weight decay, dropout, early stopping; questo capitolo introduce solo weight decay.
- pretraining-dati (in preparazione) — il regime concreto in cui AdamW domina.
- rlhf-ppo (in preparazione) — dove la scelta dell’optimizer diventa particolarmente delicata.
- lora-peft (in preparazione) — Adam su un sottoinsieme minuscolo dei parametri.
Per andare oltre
Sezione intitolata “Per andare oltre”- Goodfellow I., Bengio Y., Courville A. (2016). Deep Learning, MIT Press, capitolo 8 “Optimization for Training Deep Models”. Trattazione canonica e leggibile, con un buon equilibrio fra teoria e pratica.
- Bottou L., Curtis F.E., Nocedal J. (2018). Optimization Methods for Large-Scale Machine Learning. SIAM Review 60(2), 223-311. Survey moderno, rigoroso, con tutti i tassi di convergenza spiegati.
- Loshchilov I., Hutter F. (2019). Decoupled Weight Decay Regularization. ICLR 2019. Il paper di AdamW; corto, leggibile, contiene gli esperimenti che hanno cambiato la pratica.
- Kingma D., Ba J. (2015). Adam: A Method for Stochastic Optimization. ICLR 2015. Il paper originale di Adam; vale la pena leggerlo direttamente, sia per la formulazione pulita sia per capire le convenzioni.
- Boyd S., Vandenberghe L. (2004). Convex Optimization. Cambridge University Press. Per chi vuole le prove dei tassi di convergenza GD su funzioni convesse, con eleganza e rigore.
- Nesterov Y. (2018). Lectures on Convex Optimization, 2nd ed., Springer. Trattazione completa dell’accelerated gradient e dei lower bound per metodi del primo ordine, dalla mano dell’autore originale.
- Smith L. (2018). A disciplined approach to neural network hyper-parameters, arXiv:1803.09820. Manuale pratico di tuning di lr, momentum, weight decay e batch size, denso di ricette empiriche.
- Reddi S., Kale S., Kumar S. (2018). On the Convergence of Adam and Beyond. ICLR 2018. Per capire i limiti teorici di Adam e perché AMSGrad e altre varianti sono state proposte.
- Chen X. et al. (2023). Symbolic Discovery of Optimization Algorithms. NeurIPS 2023. Il paper di Lion: program search applicato alla scoperta di optimizer, con esperimenti su scala reale.