Hai un budget di calcolo fisso e devi decidere come spenderlo: un modello più grande addestrato più a lungo su pochi dati, o uno più piccolo addestrato su molti? Prima del 2020 la risposta era un'intuizione; oggi è un problema di ottimizzazione con una soluzione in forma chiusa, a patto di accettare che l'errore di un modello linguistico segua leggi di potenza regolari in tre quantità: parametri, dati e calcolo. Questo articolo costruisce quelle leggi su un esperimento in miniatura dove la risposta vera è nota, ricava la regola di allocazione di Hoffmann e colleghi (2022) e la controlla numericamente, e poi guarda con un modello giocattolo perché certe capacità sembrano comparire all'improvviso. Non si addestra nessun modello linguistico: ciò che si misura è come ragionare sui grafici di scala, senza fidarsi dei loro estremi.
I listati sono stati eseguiti due volte con uscita identica con numpy 2.4.6 e scipy 1.17.1, su dati sintetici a seme fisso. Le cifre sui modelli reali vengono solo dai tre articoli scientifici elencati nelle fonti, e dove le ho riprodotte lo dico.
Che cos'è una legge di scala
Kaplan e colleghi (2020) misurano la perdita di entropia incrociata di Transformer linguistici al variare delle dimensioni e riportano che la perdita segue una legge di potenza nella dimensione del modello, in quella del dataset e nel calcolo, con andamenti che coprono più di sette ordini di grandezza. Per il solo numero N di parametri non-embedding, addestrando fino a convergenza su dati sufficienti, scrivono L(N) = (Nc/N)αN con αN ≈ 0,076; per i soli dati D, con αD ≈ 0,095 (paragrafo 1.2). Un esponente così piccolo significa che per dimezzare la parte riducibile dell'errore bisogna moltiplicare la dimensione per 21/0,076, circa 9000 volte: le leggi di potenza promettono miglioramenti regolari, non economici.
Tre avvertenze valgono per ogni legge di scala. È un adattamento empirico a una famiglia di modelli, a un insieme di dati e a un intervallo di scale: fuori dall'intervallo è un'estrapolazione. Descrive la perdita, non una capacità specifica: il legame fra una perdita e ciò che il modello sa fare va misurato a parte. Infine contiene quasi sempre un pavimento, un errore che nessuna quantità di parametri o dati toglie, e come si tratta quel pavimento cambia gli esponenti stimati.
Una legge di potenza in miniatura, con la risposta nota
Per vedere una legge di scala senza addestrare un modello linguistico serve un problema in cui la verità si conosce. Il listato usa una regressione lineare con 4000 caratteristiche gaussiane indipendenti e coefficienti veri wk = k-1: le caratteristiche di indice alto contano sempre meno. Un «modello di dimensione m» usa solo le prime m caratteristiche. L'errore atteso su dati nuovi ha una forma chiusa: la somma dei wk² delle caratteristiche escluse (la loro varianza non spiegata) più il rumore di misura σ² = 0,001. Come secondo calcolo indipendente si adattano davvero i minimi quadrati su 8000 esempi e si misura l'errore su altri 8000. La somma dei quadrati dei coefficienti esclusi è circa 1/m, quindi mi aspetto una legge di potenza con esponente vicino a 1 sulla sola parte riducibile.
import numpy as np
from scipy.optimize import curve_fit
rng = np.random.default_rng(0)
D, a = 4000, 1.0 # 4000 caratteristiche; coefficienti veri w_k = k^-1
w = np.arange(1, D + 1, dtype=float) ** -a
sigma2 = 0.001 # rumore di misura: il pavimento che nessun modello toglie
n = 8000
X = rng.standard_normal((n, D))
y = X @ w + np.sqrt(sigma2) * rng.standard_normal(n)
Xt = rng.standard_normal((n, D))
yt = Xt @ w + np.sqrt(sigma2) * rng.standard_normal(n)
ms = np.array([4, 8, 16, 32, 64, 128, 256])
chiusa, misurata = [], []
print(f"{'m':>4} {'errore (forma chiusa)':>22} {'errore misurato':>16}")
for m in ms:
tail = (w[m:] ** 2).sum() # varianza delle caratteristiche escluse
chiusa.append(tail + sigma2) # formula: coda + rumore (modello ideale)
coef, *_ = np.linalg.lstsq(X[:, :m], y, rcond=None)
misurata.append(np.mean((yt - Xt[:, :m] @ coef) ** 2))
print(f"{m:>4} {chiusa[-1]:>22.5f} {misurata[-1]:>16.5f}")
chiusa, misurata = np.array(chiusa), np.array(misurata)
red = chiusa - sigma2
print("\npendenza log-log della sola parte riducibile:", round(np.polyfit(np.log(ms), np.log(red), 1)[0], 3),
"- attesa 1-2a =", 1 - 2 * a)
print("pendenza log-log dell'errore totale (con il pavimento):", round(np.polyfit(np.log(ms), np.log(misurata), 1)[0], 3))
f = lambda m, c, A, al: c + A * m ** -al
(c, A, al), _ = curve_fit(f, ms.astype(float), misurata, p0=[0.001, 1, 1.0])
print(f"adattamento c + A*m^-alpha sulle misure: c={c:.4f} (vero {sigma2}), alpha={al:.3f} (atteso {2 * a - 1})")
# Output:
# m errore (forma chiusa) errore misurato
# 4 0.22207 0.22523
# 8 0.11826 0.12097
# 16 0.06134 0.06283
# 32 0.03152 0.03150
# 64 0.01625 0.01639
# 128 0.00853 0.00862
# 256 0.00465 0.00477
#
# pendenza log-log della sola parte riducibile: -0.988 - attesa 1-2a = -1.0
# pendenza log-log dell'errore totale (con il pavimento): -0.937
# adattamento c + A*m^-alpha sulle misure: c=-0.0014 (vero 0.001), alpha=0.909 (atteso 1.0)
Forma chiusa e misura coincidono entro il 3%: l'eccesso che resta si spiega con l'errore di stima, perché 8000 esempi non sono infiniti (circa m/n, il 3% a m = 256). La pendenza sul piano log-log della sola parte riducibile è −0,988, vicina a −1; ma quella dell'errore totale, che include il pavimento di 0,001, è già −0,937: il pavimento appiattisce la curva alle scale grandi. Il risultato più istruttivo è l'ultima riga. Il tentativo di adattare c + A·m-α alle sole sette misure restituisce un pavimento negativo (−0,0014, contro 0,001 vero) e α = 0,909 invece di 1: con pochi punti e un po' di rumore, il pavimento e l'esponente si compensano a vicenda e l'adattamento non li separa. È la ragione per cui gli esponenti pubblicati hanno intervalli di confidenza, e perché estrapolare di molti ordini di grandezza da un adattamento solo è fragile.
Come spendere un budget: Chinchilla
Hoffmann e colleghi (2022) propongono di descrivere la perdita finale di un modello con N parametri addestrato su D token come L(N, D) = E + A/Nα + B/Dβ (paragrafo 3.3). Il primo termine è l'entropia del testo, il secondo il limite di un modello di taglia finita, il terzo il fatto di non addestrare fino a convergenza. Sostituendo il vincolo di calcolo C ≈ 6ND, che gli autori prendono da Kaplan e colleghi, si ottiene una dimensione ottima Nopt = G·(C/6)a e Dopt = G-1·(C/6)b, con a = β/(α+β), b = α/(α+β) e G = (αA/βB)1/(α+β). Le costanti riportate nell'appendice D.2 sono E = 1,69, A = 406,4, B = 410,7, α = 0,34, β = 0,28.
Il listato usa quelle costanti. Per quattro budget confronta la formula chiusa con una minimizzazione a forza bruta su 400.001 valori di N (con D = C/6N), cioè un secondo calcolo che non conosce l'algebra. Poi calcola la perdita prevista dalla formula per due configurazioni della tabella 1 del lavoro: Gopher (280 miliardi di parametri, 300 miliardi di token) e Chinchilla (70 miliardi, 1,4 mila miliardi di token).
import numpy as np
# costanti riportate nel paragrafo D.2 dell'articolo di Hoffmann et al. (2022)
E, A, B, alpha, beta = 1.69, 406.4, 410.7, 0.34, 0.28
L = lambda N, D: E + A / N ** alpha + B / D ** beta
G = (alpha * A / (beta * B)) ** (1 / (alpha + beta))
a, b = beta / (alpha + beta), alpha / (alpha + beta)
print(f"esponenti: a = beta/(alpha+beta) = {a:.3f}, b = alpha/(alpha+beta) = {b:.3f}")
print(f"{'C (FLOP)':>9} | {'N chiuso':>10} {'N griglia':>10} | {'D chiuso':>10} {'D/N':>6} | {'L':>6}")
for C in (1e18, 1e20, 5.76e23, 1e26):
N_c = G * (C / 6) ** a # forma chiusa, equazione (4) dell'articolo
D_c = (C / 6) / N_c
N = np.logspace(6, 14, 400001) # griglia fitta: minimizzo L(N, C/(6N)) a forza bruta
N_g = N[np.argmin(L(N, C / (6 * N)))]
print(f"{C:>9.2e} | {N_c:>10.3e} {N_g:>10.3e} | {D_c:>10.3e} {D_c / N_c:>6.1f} | {L(N_c, D_c):>6.3f}")
print("\nConfronto a parità di calcolo, secondo la formula:")
for nome, N, D in (("280B su 300B token", 280e9, 300e9), ("70B su 1,4T token", 70e9, 1.4e12)):
print(f"{nome:>20}: C = 6ND = {6 * N * D:.2e} FLOP, L = {L(N, D):.3f}")
# Output:
# esponenti: a = beta/(alpha+beta) = 0.452, b = alpha/(alpha+beta) = 0.548
# C (FLOP) | N chiuso N griglia | D chiuso D/N | L
# 1.00e+18 | 8.058e+07 8.058e+07 | 2.068e+09 25.7 | 3.535
# 1.00e+20 | 6.449e+08 6.449e+08 | 2.585e+10 40.1 | 2.600
# 5.76e+23 | 3.219e+10 3.219e+10 | 2.982e+12 92.6 | 1.931
# 1.00e+26 | 3.305e+11 3.305e+11 | 5.043e+13 152.6 | 1.799
#
# Confronto a parità di calcolo, secondo la formula:
# 280B su 300B token: C = 6ND = 5.04e+23 FLOP, L = 1.993
# 70B su 1,4T token: C = 6ND = 5.88e+23 FLOP, L = 1.937
La formula chiusa e la ricerca a forza bruta danno lo stesso ottimo a quattro cifre, quindi l'algebra è giusta. Il rapporto fra token e parametri dell'ottimo non è costante: sale da 26 a 153 con il budget, perché gli esponenti a e b non sono uguali (0,452 e 0,548 con le costanti arrotondate; il lavoro riporta 0,46 e 0,54). Il lavoro riassume il risultato dicendo che a ogni raddoppio della dimensione del modello dovrebbero raddoppiare anche i token di addestramento, cioè esponenti vicini a 0,5 in tutti e tre gli approcci che usa. Due avvertenze sui miei numeri. La prima: a 5,76·1023 FLOP, il budget di Gopher dichiarato nel lavoro, la formula con le costanti arrotondate dà 32 miliardi di parametri, mentre la didascalia della figura 4 dell'articolo indica 40 miliardi: l'ottimo è sensibile alle costanti, e arrotondarle a due cifre non basta per riprodurre la cifra pubblicata.
La seconda riguarda il calcolo: 6ND è un'approssimazione: per i due modelli dà 5,04 e 5,88·1023 FLOP, mentre il lavoro li considera a pari calcolo con un conteggio più dettagliato (appendice F). Con la formula Chinchilla ha una perdita prevista più bassa (1,937 contro 1,993), coerente con ciò che il lavoro riporta di averli misurati; i numeri della tabella sono quelli del modello e non una misura.
Il messaggio pratico è un altro. Kaplan e colleghi (2020) concludevano che il training ottimale rispetto al calcolo consiste nell'addestrare modelli molto grandi su una quantità relativamente modesta di dati, fermandosi molto prima della convergenza; Hoffmann e colleghi, con più di 400 modelli da 70 milioni a oltre 16 miliardi di parametri su 5-500 miliardi di token, trovano che dimensione e dati vadano scalati insieme, e indicano nel modello 70B addestrato su 1,4 mila miliardi di token un esempio che a pari calcolo supera i modelli più grandi di quel confronto. Le due conclusioni sono diverse, e questo articolo non ha verificato da che cosa dipenda la differenza: è un buon motivo per non trattare «la legge» come una costante di natura.
Le capacità «emergenti» e il metro con cui le misuri
Si parla di capacità emergenti quando un'abilità sembra assente nei modelli piccoli e presente di colpo in quelli grandi, e imprevedibile dalle scale più piccole. Schaeffer e colleghi (2023) propongono una spiegazione alternativa: per un compito e una famiglia di modelli, l'apparente salto dipende dalla scelta della metrica, perché metriche non lineari o discontinue deformano un errore per token che scende in modo liscio. Nel loro esempio la corrispondenza esatta su una sequenza di k token richiede che tutti siano giusti. Lo schema in miniatura è facile da riprodurre: l'errore per token cala come una legge di potenza (err = 0,5·(N/106)-0,2, un esponente scelto da me, senza legame con nessun modello reale); la metrica discreta è la probabilità (1−err)10 che dieci token siano tutti giusti; la metrica continua è il numero atteso di token sbagliati, 10·err. Il secondo calcolo è una simulazione di 2000 sequenze per scala.
import numpy as np
rng = np.random.default_rng(0)
k = 10 # il compito richiede 10 token tutti giusti
N = 10.0 ** np.arange(6, 12.5, 0.5) # dimensione del modello, da 1e6 a 1e12
err = 0.5 * (N / 1e6) ** -0.2 # errore per singolo token: scende liscio con N
p = 1 - err # probabilità che un token sia giusto
exact = p ** k # metrica discreta: la sequenza è giusta solo se lo sono tutti i token
edit = k * err # metrica continua: numero atteso di token sbagliati
items = 2000 # secondo calcolo: simulazione con 2000 sequenze per scala
tok = rng.random((len(N), items, k)) < p[:, None, None]
exact_sim = tok.all(axis=2).mean(axis=1)
edit_sim = (~tok).sum(axis=2).mean(axis=1)
print(f"{'N':>8} {'err/token':>9} | {'exact (formula)':>15} {'exact (sim.)':>13} | {'errori attesi':>13} {'(sim.)':>7}")
for i in range(len(N)):
print(f"{N[i]:>8.1e} {err[i]:>9.3f} | {exact[i]:>15.4f} {exact_sim[i]:>13.4f} | {edit[i]:>13.3f} {edit_sim[i]:>7.3f}")
z = (exact_sim - exact) / np.sqrt(exact * (1 - exact) / items)
print(f"\nscarto massimo simulazione-formula: {np.abs(z).max():.2f} errori standard (atteso: sotto 3 su 13 punti)")
# Output:
# N err/token | exact (formula) exact (sim.) | errori attesi (sim.)
# 1.0e+06 0.500 | 0.0010 0.0015 | 5.000 5.034
# 3.2e+06 0.397 | 0.0063 0.0015 | 3.972 3.983
# 1.0e+07 0.315 | 0.0226 0.0255 | 3.155 3.170
# 3.2e+07 0.251 | 0.0559 0.0650 | 2.506 2.461
# 1.0e+08 0.199 | 0.1087 0.1100 | 1.991 1.968
# 3.2e+08 0.158 | 0.1789 0.1875 | 1.581 1.575
# 1.0e+09 0.126 | 0.2613 0.2655 | 1.256 1.246
# 3.2e+09 0.100 | 0.3496 0.3555 | 0.998 0.963
# 1.0e+10 0.079 | 0.4380 0.4365 | 0.792 0.786
# 3.2e+10 0.063 | 0.5220 0.5360 | 0.629 0.602
# 1.0e+11 0.050 | 0.5987 0.5895 | 0.500 0.511
# 3.2e+11 0.040 | 0.6668 0.6675 | 0.397 0.413
# 1.0e+12 0.032 | 0.7257 0.7185 | 0.315 0.323
#
# scarto massimo simulazione-formula: 2.73 errori standard (atteso: sotto 3 su 13 punti)
La metrica continua cala di continuo da 5 a 0,3 errori attesi e la simulazione la segue a meno di pochi centesimi. La corrispondenza esatta, invece, parte da 0,001 a 106 parametri, resta sotto 0,03 fino a 107 e poi sale rapidamente: sembra un'abilità che «compare» fra 108 e 1010, benché l'errore per token scenda di un fattore 1,26 per ogni mezzo ordine di grandezza, senza nessun salto. Il confronto fra formula e simulazione resta sotto tre errori standard su tredici punti. Questo non dimostra che ogni capacità emergente sia un artefatto: dimostra che un salto in una metrica discreta non è, da solo, una prova di discontinuità del modello. Gli stessi autori scrivono nell'abstract che le abilità presunte svaniscono con metriche diverse o con statistiche migliori e che potrebbero non essere una proprietà fondamentale della scala: lo sostengono su alcune famiglie di modelli e su certi compiti, non su tutti, e l'ipotesi resta tale.
Che cosa si può e non si può dire di una legge di scala
Ricapitolando le tre esperienze, una legge di scala è un buon modo di pianificare e un cattivo modo di prevedere. Pianifica bene perché trasforma un budget in una scelta fra parametri e dati con un conto verificabile, come nel secondo listato. Prevede male fuori dalla propria gamma, perché le costanti sono sensibili e i parametri di un adattamento si compensano, come nel primo. E dice poco sul compito: la perdita scende in modo regolare, ma ciò che serve a un utente è una metrica di compito, e come quella metrica si deforma dipende da come è definita, come nel terzo.
FAQ - Domande frequenti sulle leggi di scala
Una legge di scala vale per qualunque modello?
No: è un adattamento a una famiglia di modelli, a un insieme di dati e a un intervallo di scale. Nel primo listato l'adattamento con pavimento libero ha dato α = 0,909 invece di 1 con sette punti, e i due lavori citati arrivano a conclusioni diverse sull'allocazione ottima del calcolo.
Perché le capacità emergenti possono essere un effetto della metrica?
Perché una metrica come la corrispondenza esatta richiede che tutti i token siano giusti: se l'errore per token scende in modo liscio, la probabilità che dieci token siano tutti giusti cresce in modo ripido. Nel terzo listato passa da 0,001 a 0,73 mentre l'errore per token scende di un fattore 16.
Quanti token servono per parametro, secondo Chinchilla?
Dipende dal budget: con le costanti del lavoro il rapporto ottimo cresce da 26 a 153 fra 1018 e 1026 FLOP nel secondo listato. Non esiste un rapporto unico da ricordare: ciò che il lavoro afferma è che dimensione e dati crescono insieme, con esponenti vicini a 0,5.
Che cosa sai fare adesso
Sai riconoscere una legge di potenza su un grafico log-log e sai che il pavimento d'errore e l'esponente si compensano quando i punti sono pochi. Sai ricavare e controllare numericamente l'allocazione ottima di un budget secondo la forma parametrica di Hoffmann e colleghi, e sai quanto sia sensibile alle costanti. Sai spiegare, con un esperimento che riproduci in un secondo, perché un salto in una metrica discreta non prova da solo una discontinuità. Il prossimo articolo passa dal modello alla conoscenza che il modello non ha: 03 - RAG fondamentale. Se ti serve prima l'architettura, parti da 01 - Architettura Transformer; l'errore di generalizzazione di un modello classico è trattato in 04 - Bias-Variance Tradeoff.
Più avanti nella serie trovi anche 07 - Reasoning models: o1, DeepSeek-R1, RLHF, sul calcolo speso al momento della risposta, e 08 - Inferenza on-prem con Ollama, vLLM, llama.cpp, su dove quel calcolo gira e quanta memoria richiede.
Fonti
- Kaplan et al., Scaling Laws for Neural Language Models (arXiv:2001.08361) - abstract, esponenti αN e αD (paragrafo 1.2), stima C ≈ 6NBS del calcolo; letta nel testo del PDF il 2026-11-13.
- Hoffmann et al., Training Compute-Optimal Large Language Models (arXiv:2203.15556) - forma parametrica (paragrafo 3.3), costanti dell'appendice D.2, tabelle 1 e 2, risultato di Chinchilla; letta nel testo del PDF il 2026-11-13. Le cifre dei miei listati sono calcolate da quelle costanti e non sono quelle del lavoro.
- Schaeffer, Miranda, Koyejo, Are Emergent Abilities of Large Language Models a Mirage? (arXiv:2304.15004) - abstract e introduzione, ruolo della metrica; lette il 2026-11-13. Le tre analisi sperimentali del lavoro non sono state riprodotte né lette per intero.
Commenti
Caricamento commenti...
Accedi per commentare