Temperature, Top-p e il determinismo che non hai
La temperature divide i logit prima della softmax: ecco perché non è una manopola della creatività.
In questa pagina
Ecco la stessa richiesta inviata allo stesso modello cinque volte. Stessi pesi, stesso prompt, stessa macchina, stesso seed casuale. L’unica cosa che cambia è un numero.
prompt: "Q: What is the capital of France?\nA:"
T = 0.0 " Paris\nWhat is the question and does the answer answer it? The
question is: What is the capital of France?..."
T = 0.7 " Paris\nWhat is the question: Which city is the capital of
France?..."
T = 1.0 " Paris\nWhat is a good geographical qualifier for describing
Paris concerning its location?\nA: Near the Mediterranean Sea..."
T = 1.5 " Paris\nWhat clue from premise allows we to conclude that Godwin
was &, He chose Healing Crimson Colour No:white flour Pure..."
T = 2.0 "安全感金华.ITEMT]]];\naims assume parental.st-importe.valtermination
Screens قطر_Zeroหมายเลข-zA ('$ספטמבר..."Non si è rotto nulla. Ogni token nell’ultima riga è stato estratto legittimamente dalla distribuzione di probabilità del modello sul suo vocabolario da 151.936 voci. Il numero che è cambiato si chiama temperature, nella maggior parte della documentazione viene descritto come una manopola della creatività, e questa descrizione è sbagliata in un modo che questo capitolo può dimostrare, non solo affermare.
Questo è anche il capitolo in cui arrivano a scadenza tre promesse precedenti. Il Capitolo 4 ha definito il logit senza poi usarlo davvero. Il riquadro sui floating point del Capitolo 2 terminava con un’istruzione: ricordatelo quando il Capitolo 17 chiederà perché lo stesso prompt, modello e seed possono produrre token diversi. E il riquadro sulla mixture-of-experts del Capitolo 9 prometteva un catalogo di quattro cause di non determinismo. Tutte e tre arrivano qui sotto.
La riga da cui dipende tutto il capitolo
Link alla sezione: La riga da cui dipende tutto il capitoloIl Capitolo 4 ha introdotto il logit come punteggio reale non normalizzato, uno per classe. Il Capitolo 8 ha fatto produrre a un modello linguistico un logit per ogni voce del vocabolario. La softmax trasforma quel vettore in probabilità:
La temperature entra qui — il nome è preso dalla fisica statistica, dove lo stesso parametro controlla quanto nettamente una distribuzione di Boltzmann si concentra sui suoi stati a bassa energia1 — e divide i logits prima dell’esponenziale:
Questa posizione è l’intero meccanismo, e bastano due righe di algebra per vedere perché non potrebbe stare da nessun’altra parte. Supponi di provare ad applicare la temperature alle probabilità invece: scalarle di e rinormalizzare. Otterresti
La costante si cancella. Scalare le probabilità non fa assolutamente nulla; la distribuzione torna invariata. La temperature ha effetto solo perché agisce sull’esponente, dove dividere per prima di esponenziare equivale a elevare ogni probabilità alla potenza : una rimodellazione non lineare che cambia i rapporti tra le voci, non la loro scala comune.
Da quella posizione, entrambi i limiti seguono senza altro lavoro. Quando , il logit più grande si stacca dal resto e collassa sul singolo token con il punteggio più alto: greedy decoding. Quando cresce, ogni tende a zero, ogni esponenziale tende a 1 e la distribuzione si appiattisce verso l’uniforme sull’intero vocabolario. Esattamente a la formula divide per zero, quindi ogni implementazione lo tratta come un caso speciale e usa il massimo aritmetico, incluso il widget qui sotto, che passa ad argmax a .
Un avvertimento, perché la collisione dei nomi genera vera confusione. In machine learning esiste una seconda cosa, non correlata, chiamata temperature: temperature scaling, un metodo di calibrazione che adatta un valore su un validation set in modo che la confidenza di un classificatore corrisponda alla sua accuratezza.2 Stessa formula, nulla a che vedere con la generazione. Quando i paper dicono «temperature», spesso intendono quella; questo capitolo mai.
Ecco quella distribuzione, con l’aritmetica davanti a te. I logits sono fissi e plausibili, quindi i numeri nel testo qui sotto possono essere verificati rispetto a ciò che vedi:
La temperature non è una manopola della creatività
Link alla sezione: La temperature non è una manopola della creativitàIl numero ␣banana è l’intero argomento in miniatura: alzare la temperature non può dare a un modello un’idea che non aveva. I logits sono già calcolati, il ranking è già fissato, e la temperature lo preserva esattamente: nessuna quantità di calore sposta mai un token con punteggio più basso sopra uno con punteggio più alto. Tutto ciò che fa è ridistribuire massa verso il basso nel ranking prodotto dal modello stesso. Una temperature alta non rende un modello più inventivo; lo rende più propenso a emettere i token che ha valutato come cattivi.
Su un vocabolario reale questo smette di essere una curiosità e diventa il motivo per cui l’output ad alta temperature è inutilizzabile. Misurato su Qwen/Qwen2.5-0.5B-Instruct, un forward pass, il prompt sopra, contando quanti token servono per accumulare una certa quota della massa di probabilità:
| temperature | probabilità top-1 | entropia | token che contengono l’80 % | 90 % | 95 % | 99 % |
|---|---|---|---|---|---|---|
| 0,5 | 99,98 % | 0,00 nats | 1 | 1 | 1 | 1 |
| 0,7 | 99,65 % | 0,03 nats | 1 | 1 | 1 | 1 |
| 1,0 | 96,01 % | 0,30 nats | 1 | 1 | 1 | 14 |
| 1,2 | 88,20 % | 0,88 nats | 1 | 2 | 13 | 252 |
| 1,5 | 62,83 % | 3,07 nats | 29 | 353 | 2.672 | 26.787 |
| 2,0 | 16,62 % | 8,19 nats | 13.516 | 32.966 | 55.231 | 101.205 |
Leggi lentamente l’ultima riga. A , su una domanda con una sola risposta corretta, 32.966 token diversi condividono il primo 90 % della massa di probabilità. Non è uno spazio creativo più ampio. È un modello a cui l’aritmetica ha detto di trattare una particella coreana e un identificatore C++ come opzioni attive per la parola dopo A:. La spazzatura nel blocco iniziale è la conseguenza diretta, e non è un bug del modello o della libreria: è ciò che la richiesta ha chiesto.
L’intervallo utile è stretto e dipende dal task più che dal gusto. Su una domanda fattuale, la risposta è un token e qualunque calore sopra circa 1,2 inietta errore senza motivo. Su una domanda aperta esiste davvero più di una buona continuazione, e un po’ di calore compra varietà che resta fluida:
"Write a two-sentence story about a lighthouse."
T = 0.0 "The lighthouse stood tall and proud, its beacon illuminating the
night sky above. A lone sailor, his eyes fixed on the distant
horizon..."
T = 0.7 "In the quiet, stormy waters of the sea, a lighthouse stood
sentinel over the horizon, its golden dome casting a warm glow
on the fog-shrouded streets below..."
T = 1.0 "In the quiet night, a lone lighthouse stood sentinel over the
sea, its shining beacon a beacon of hope and solace for sailors
and fishermen across the vast and endless ocean..."
T = 1.3 "In the gentle sunlight, now reflecting upon the opening of Jack's
lighthouse, Jim Trahan, a small-time individual difficult to
define in paperwork, wondered about a career where simplicity
reigns..."A 1,3 il modello ha inventato un nome proprio e una frase che non si analizza correttamente. La fascia tra «identico ogni volta» e «incoerente» è circa da 0,6 a 1,1 per questo modello su questo task, e il consiglio onesto è trovarla misurando sul tuo task, non copiando un numero da un blog post.
Perché il testo più probabile è cattivo testo
Link alla sezione: Perché il testo più probabile è cattivo testoSotto tutto questo si nasconde una domanda ovvia: se il modello ha una distribuzione di probabilità e un token è il più probabile, perché non prenderlo sempre? Il greedy decoding è gratis, riproducibile e non richiede parametri.
Perché il risultato è questo:
prompt: "In a shocking finding, scientists discovered a herd of unicorns
living in a remote valley."
greedy: " The unicorns were so rare that they were not even recognized by
the local people. The unicorns were so rare that they were not
even recognized by the local people. The unicorns were so rare
that they were not even recognized by the local people. ..."
repeated 4-grams: 87.6 %Otto frasi, una sola frase. Quasi nove finestre da quattro token su dieci erano già apparse prima nello stesso output. Questa è neural text degeneration, nominata e spiegata da Holtzman et al. nel paper che ha introdotto top-p.3 Il modello non è rotto; massimizzare la probabilità della sequenza è semplicemente l’obiettivo sbagliato per il testo aperto. La scrittura umana non è la sequenza di parole più probabile: porta sorpresa, con la sua probabilità per-token che vaga, scende e risale, mentre il percorso di massima probabilità è un punto fisso che, una volta entrato, non ha motivo di uscire.
È per questo che il sampling esiste. Ed è anche, e questa è la parte che viene omessa, non una legge universale. Il Capitolo 12 ha misurato 24 corrette su 24 su problemi di parole a due passaggi con semplice greedy decoding, e il sampling a temperature 0,8 ha fatto scendere il risultato all’81 %; la self-consistency ha poi speso sei volte i token per risalire dove il greedy era già arrivato. Entrambi i fatti sono veri nello stesso momento:
Generazione aperta. Non esiste una sola continuazione giusta, quindi quella più probabile è una trappola: va in loop, e l’87,6 % è copiato da sé stesso. Campiona.
Task con una sola risposta giusta. Una singola continuazione corretta esiste, quindi estrarre qualunque altra cosa significa estrarre un errore. Il 100 % del Capitolo 12 è diventato 81 % esattamente per questo. Non campionare.
La maggior parte dei prompt di produzione è del secondo tipo e viene configurata come il primo, perché la temperature è stata lasciata al valore usato dal codice di esempio.
Due modi per tagliare, e solo uno si adatta
Link alla sezione: Due modi per tagliare, e solo uno si adattaCampionare dalla distribuzione completa non è ciò che fa davvero qualcuno, perché la coda è enorme e piena di nonsense. Qualcosa deve essere tagliato. Ci sono due risposte classiche e differiscono in un aspetto che decide tutto.
Top-k mantiene un numero fisso di candidati. Ordina per probabilità, mantiene i primi , scarta il resto, rinormalizza.4 Top-p, chiamato anche nucleus sampling, mantiene una quantità fissa di massa: prende token in ordine decrescente finché la loro probabilità cumulativa raggiunge , poi si ferma.3 Formalmente, il nucleus è l’insieme più piccolo con
La differenza sembra cosmetica e non lo è, perché i due prompt che invii nello stesso minuto hanno forme di distribuzione completamente diverse. Entrambi questi sono lo stesso modello a temperature 1:
Q: What is the capital of France?\nA: | Once upon a time, | |
|---|---|---|
| probabilità top-1 | 96,01 % | 25,39 % |
| token che contengono il 90 % della massa | 1 | 467 |
| top-k = 40 mantiene | 99,61 % della massa | 78,87 % della massa |
| massa nei rank da 2 a 40 | 3,61 % | 53,48 % |
| token al rank 40 | ␣Av, 0,0093 % | ␣Dr, 0,128 % |
Un unico fisso, due fallimenti in direzioni opposte. Sul prompt fattuale, ammette 39 token che insieme valgono il 3,6 %: lascia passare spazzatura, incluso un candidato a nove millesimi di punto percentuale, perché la regola conta slot e non evidenza. Sul prompt narrativo, lo stesso butta via il 21 % della massa che il modello aveva assegnato davvero, perché il vero nucleus lì è largo 467 token.
Top-p fa fare a un solo numero esattamente entrambi i lavori. Imposta e mantiene 1 token sul primo prompt e 467 sul secondo, perché fa una domanda sulla distribuzione invece di imporle un conteggio. Guarda direttamente questo adattamento: stesso taglio, quattro temperature:
Quel widget chiude anche un fraintendimento che vale la pena nominare, perché costa soldi veri. Su una distribuzione sicura, top_p = 0.9 non è «un po’ di varietà». È greedy. A temperature 1, qui il token principale contiene il 96,90 %, che è già oltre 0,9, quindi il nucleus è largo un token e nient’altro potrà mai essere estratto. I team impostano top_p a 0,9 credendo di aver allentato qualcosa e poi si chiedono perché ogni risposta sia identica.
Imposta invece top-k e il fallimento opposto è altrettanto visibile:
Le penalties, con le formule, perché confonderle è endemico
Link alla sezione: Le penalties, con le formule, perché confonderle è endemicoTre meccanismi diversi circolano con nomi simili, fanno cose diverse, e la differenza è misurabile. Sia il numero di volte in cui il token è già apparso.
Presence penalty
Link alla sezione: Presence penaltySottrae una costante da qualunque token sia apparso almeno una volta. Apparire una volta e apparire quaranta volte vengono penalizzati allo stesso modo. È un interruttore, non una manopola.
Frequency penalty
Link alla sezione: Frequency penaltySottrae in proporzione al conteggio. Un token usato quattro volte viene penalizzato quattro volte più duramente di un token usato una volta, e la pressione si accumula mentre il testo cresce.
Repetition penalty (CTRL)
Link alla sezione: Repetition penalty (CTRL)L’originale, dal paper CTRL.7 Divide invece di sottrarre, con il caso del segno necessario perché dividere un logit negativo lo renderebbe più grande. La sua forza dipende quindi dalla magnitudine del logit, il che significa che lo stesso colpisce in modo diverso in punti diversi della stessa frase.
La stessa continuazione degenerata di prima, con ciascuna applicata. «Passi alterati» conta quanti dei 120 passaggi di generazione hanno scelto un token diverso da quello che il modello non penalizzato avrebbe scelto. Qui la run è di 120 passaggi contro i 140 del blocco sopra, motivo per cui la baseline non penalizzata legge 85,5 % invece di 87,6 %:
| impostazione | 4-grammi ripetuti | passi alterati |
|---|---|---|
| niente | 85,5 % | 0 / 120 |
| presence 0,5 | 65,0 % | 3 / 120 |
| presence 1,0 | 3,4 % | 11 / 120 |
| frequency 0,5 | 6,0 % | 12 / 120 |
| frequency 1,0 | 0,0 % | 20 / 120 |
| repetition 1,2 (CTRL) | 0,0 % | 35 / 120 |
Ne derivano tre cose. Presence a 0,5 ha cambiato tre decisioni su 120 e tagliato la ripetizione di un quarto: il loop era tenuto insieme da una manciata di token. Frequency a 0,5 ha cambiato quattro volte più decisioni per un effetto molto più grande, perché il moltiplicatore del conteggio continua a crescere mentre la costante di presence no. E la penalty CTRL al valore ampiamente copiato di 1,2 ha riscritto 35 decisioni su 120, che non è una spinta leggera: è un modello diverso.
Quel numero finale prepara il fallimento di cui nessuno avverte.
Che cosa fanno le penalties al testo che deve ripetersi
Link alla sezione: Che cosa fanno le penalties al testo che deve ripetersiIl codice si ripete. Le tabelle si ripetono. Le liste si ripetono. L’output strutturato si ripete per definizione: è questo che lo rende struttura. Una penalty non può distinguere tra un modello bloccato in un loop e un modello che emette correttamente la quarta riga di una tabella, perché entrambi appaiono come un token che compare di nuovo.
Gli stessi tre task, generati in tre modi:
| task | niente | frequency 0,5 | repetition 1,2 |
|---|---|---|---|
| tabella markdown, 6 righe | 0 / 56 passi alterati | 0 / 56 | 2 / 62 |
| funzione Python | 0 / 93 | 0 / 93 | 10 / 110 |
| elenco puntato, da 1 a 12 | 0 / 50 | 0 / 50 | 0 / 50 |
La frequency penalty a 0,5 si è rivelata innocua su tutti e tre, un risultato utile e leggermente sorprendente, e dice qualcosa di preciso: poiché nessuna decisione è cambiata, i token strutturali dovevano vincere le loro posizioni di più di quanto la penalty sottraesse, anche dopo essere apparsi cinque e sei volte. La penalty CTRL, che invece divide, li scalza, ed ecco cosa ha prodotto:
repetition 1.2, markdown table:
| n | 2^n |
| --- | --- |
| 0 | 1 |
| 1 | 2 |
| 2 | 4 |L’allineamento cade a pezzi: la quantità di padding dentro ogni cella cambia da riga a riga, perché la sequenza di spazi prima della pipe di chiusura è esattamente il tipo di ripetizione che la penalty esiste per rompere. Cosmetico, e costa sei token extra. Il caso Python non è cosmetico:
nothing / frequency 0.5:
total = 0
for i in range(1, n + 1):
total += i ** 2
return total
repetition 1.2:
# Initialize total_sum with 0
total_sum = 0
# Loop through numbers from 1 to n, incrementing by 2 each time
for i in range(1, n + 1,La penalty ha spinto il modello fuori da total — già usato nella docstring — verso total_sum, ha imbottito l’output con commenti inventati per spendere il budget su token inutilizzati, e poi è entrato in un range a tre argomenti con uno stride. Il commento dice incrementing by 2 each time, che è sbagliato per una somma di quadrati da 1 a . Una repetition penalty ha prodotto codice errato da un prompt a cui senza di essa era stata data una risposta corretta.
La regola che ne segue è breve: le penalties sono per prosa aperta, e dovrebbero essere spente per codice, output strutturato, dati tabellari e qualunque cosa abbia uno schema. Il Capitolo 18 parla esattamente di quella seconda categoria.
L’ordine di applicazione, e perché cambia la risposta
Link alla sezione: L’ordine di applicazione, e perché cambia la rispostaOgni implementazione reale applica questi elementi in una sequenza specifica:
penalties → temperature → top-k → top-p → sample
Non è contabilità arbitraria, e scambiare due fasi produce distribuzioni davvero diverse. Due misure, entrambe sul prompt fattuale.
Tagliare prima o dopo la temperature. Il nucleus viene calcolato sulla distribuzione che riceve, e la temperature cambia radicalmente quella distribuzione:
| top-p 0,9 dopo la temperature | top-p 0,9 prima della temperature | |
|---|---|---|
| 1 token | 1 token | |
| 353 token | 1 token | |
| 32.966 token | 1 token |
A la stessa impostazione nominale produce un insieme di candidati di 32.966 o di 1, solo in base a quale fase gira per prima. Se ti sei mai chiesto perché alzare la temperature «non fa nulla» su un provider e distrugge l’output su un altro con gli stessi due numeri, questa tabella è una risposta plausibile.
Penalizzare prima o dopo la temperature. Sottrarre una penalty e poi dividere per dà una penalty effettiva di ; dividere prima e poi sottrarre dà . Con una presence penalty di 1,0 applicata al token principale:
| temperature | penalizza, poi tempera | tempera, poi penalizza |
|---|---|---|
| 0,5 | 99,858 % | 99,948 % |
| 1,0 | 89,839 % | 89,839 % |
| 2,0 | 10,783 % | 6,830 % |
Identiche a , come devono essere. Separate da un fattore 1,58 a . «Presence penalty 1,0» non è una quantità di penalty ben definita a meno che tu non sappia anche dove viene applicata la temperature, e nessuna API lo documenta.
Mostra dettagli
Facoltativo: l’intera pipeline, nell’ordine sopra.
Sedici righe, e tutto il capitolo è lì dentro. È lo stesso calcolo eseguito dal widget, su un vero vettore di logits invece che su dieci numeri fissi.
def sample(logits, counts, presence=0.0, frequency=0.0,
temperature=1.0, top_k=0, top_p=1.0, generator=None):
z = logits.clone()
idx = torch.tensor(list(counts)) # 1. penalties
if len(idx):
z[idx] -= presence
z[idx] -= frequency * torch.tensor([float(c) for c in counts.values()])
if temperature <= 0: # 2. temperature
return int(z.argmax()) # T=0 is argmax
p = torch.softmax(z / temperature, -1)
p, order = p.sort(descending=True)
if top_k: # 3. top-k
p[top_k:] = 0
p = p * ((p.cumsum(0) - p) < top_p) # 4. top-p
p = p / p.sum() # 5. renormalise
return int(order[torch.multinomial(p, 1, generator=generator)])Il cumsum(0) - p nella riga top-p è la massa cumulativa escludendo il token corrente, ed è ciò che fa sì che il nucleus includa il token che supera la soglia invece di fermarsi appena prima. Sbaglialo di uno e top_p = 0.9 diventa silenziosamente un taglio leggermente più stretto di ogni altra implementazione.
Questo è uno dei pochi punti nella seconda metà del corso in cui Python è il linguaggio giusto, e il motivo è strutturale più che stilistico: ogni riga sopra richiede di avere in mano l’intero vettore di logits, e su un’API HTTP quel vettore non esiste. Puoi inviare temperature e top_p a un provider; non puoi implementarli, e non puoi vedere che cosa hanno fatto.
Non esiste una sampling API universale
Link alla sezione: Non esiste una sampling API universaleOgni provider accetta un sottoinsieme diverso di questi controlli, con intervalli diversi, e ignora il resto in silenzio. Non è una lamentela astratta. Qualunque applicazione che offra una scelta di modello deve scrivere le differenze da qualche parte, e il file in cui lo fa è una mappa dell’incompatibilità. Ecco che cosa dichiara un catalogo del genere per un singolo parametro sulle nove fonti di testo supportate:
| intervallo di temperature dichiarato | fonti |
|---|---|
| da 0 a 1 | Anthropic, Google, Meta, Cerebras, PaLM |
| da 0 a 1,5 | Mistral |
| da 0 a 2 | OpenAI, DeepSeek, xAI |
La parola è la stessa; la scala no. Una «temperature di 1» è la distribuzione non modificata su uno e il calore massimo consentito su un altro, e metà del catalogo non può esprimere il valore che l’altra metà tratta come neutro-più-un-po’. Il resto delle manopole è altrettanto disomogeneo: le voci OpenAI, DeepSeek e xAI accettano presence e frequency penalties e nessun topK; le voci Google, Meta, Cerebras e PaLM accettano topK e nessuna penalty; Anthropic accetta topK, topP e stop sequences e nessuna penalty; e esattamente una su nove — Mistral — accetta un seed. Inviare un parametro che un provider non implementa in genere non produce alcun errore: la richiesta riesce, la manopola non fa nulla, e concludi che l’impostazione non ha effetto.
E nota che cosa sia un file del genere: una dichiarazione sull’API di qualcun altro, scritta in un giorno specifico, che poi nulla verifica. Un catalogo che dice da 0 a 1 per un provider che ora accetta da 0 a 2 limiterà silenziosamente ogni richiesta.
Altri due controlli appartengono alla stessa famiglia. logprobs, dove offerto, restituisce le log-probabilities del token scelto e spesso le prime alternative: l’unica finestra che hai sulla distribuzione di cui parla questo capitolo, e la base di ogni euristica di confidenza costruita su un modello chiuso. E maximum tokens più stop sequences terminano la generazione senza alcun riferimento alla probabilità: un limite rigido e una corrispondenza di stringa. Entrambi emergono come finish_reason dal Capitolo 14, dove length significa che la tua risposta è stata tagliata a metà frase da un budget, non conclusa dal modello.
Il seed, e il determinismo che non hai
Link alla sezione: Il seed, e il determinismo che non haiImposta un seed e il sampling diventa riproducibile. Questa parte è reale, ed è facile da verificare:
seed = 1234 " Paris\nWhat is a good geographical qualifier for describing
Paris concerning its location?\nA: Near the Mediterranean Sea"
seed = 1234 " Paris\nWhat is a good geographical qualifier for describing
Paris concerning its location?\nA: Near the Mediterranean Sea"
seed = 7 " Paris is the capital of France. The appellation of Paris is
\"Île de Paris\"."
seed = 7 " Paris is the capital of France. The appellation of Paris is
\"Île de Paris\"."Byte-identiche dentro uno stesso seed, diverse tra seed, esattamente come pubblicizzato. Quindi ciò che il seed fissa è l’estrazione casuale nell’ultima riga di quella funzione sample: quale token viene scelto data una distribuzione.
Ciò che non fissa è la distribuzione. Ed è lì che sta il problema, perché il vettore di logits prodotto dal tuo modello non è un oggetto matematico: è l’output di miliardi di addizioni floating-point, e quelle hanno un ordine.
Il Capitolo 2 aveva lasciato pronto questo esperimento. Gli stessi milioni di numeri float32, sommati in raggruppamenti diversi:
sequential 998.564270020 error vs float64: 6.393e-03
pairwise (numpy) 998.570556641 error vs float64: 1.061e-04
in 4 chunks 998.570495605 error vs float64: 1.672e-04
in 8 chunks 998.570556641 error vs float64: 1.061e-04
in 16 chunks 998.570678711 error vs float64: 1.594e-05
sequential == pairwise? False
4 chunks == 8 chunks? FalseGuarda l’ultima riga. Il numero di chunk cambia la risposta. Non è una curiosità su numpy; è il meccanismo, perché quando un inference server suddivide una riduzione su più o meno unità parallele, sta facendo precisamente questo. E un server suddivide in base a quante richieste sta servendo.
Ecco quell’effetto sul modello stesso. Stesso prompt, stesso forward pass, l’unica differenza è quante altre richieste si trovavano per caso nel batch:
20 identical forward passes, batch of 1: 20 / 20 bit-for-bit identical
the same prompt inside a batch of 2: 147,321 of 151,936 logits differ
the same prompt inside a batch of 4: 146,515 of 151,936 logits differ
the same prompt inside a batch of 8: 146,515 of 151,936 logits differ
the same prompt inside a batch of 16: 147,321 of 151,936 logits differ
largest change to any logit: 2.5e-05Eseguito da solo, il modello è perfettamente deterministico: venti passaggi, identici bit per bit. Metti il prompt identico in un batch con richieste non correlate e il 97 % dei suoi logits cambia. Nulla nella tua richiesta è cambiato. È arrivata la richiesta di qualcun altro.
Ora la parte onesta, perché di solito viene raccontata come se fosse la fine della storia. Una variazione di altera l’output solo se due token candidati erano entro quella distanza l’uno dall’altro. Su 717 passaggi di generazione in dodici prompt, il gap più piccolo tra i primi due logits è stato — cento volte più grande della perturbazione — e nessun passaggio era abbastanza vicino da ribaltarsi. Quindi su questo modello, in float32, su un laptop, il batching ha mosso ogni logit e non ha cambiato nessun token.
Questa è una descrizione di condizioni favorevoli, non una rassicurazione, e basta un cambiamento a quelle condizioni:
same weights, same prompts, greedy decoding, no seed involved
float32 vs bfloat16: 6 of 8 answers diverge
first divergence at step 23, on average
float32: "...it is scattered and dispersed into different colors,
including blue. The blue light is scattered more than other
colors, so it appears to come from the sky."
bfloat16: "...it is scattered and scattered, causing the colors of the
sun to be scattered and scattered, creating the appearance
of a blue color."Sei risposte su otto divergono, e una peggiora molto. La tabella del Capitolo 2 dice perché: bfloat16 conserva 7 bit di mantissa, quindi vicino a una magnitudine di logit di 16 i valori rappresentabili sono separati da 0,125: 16,0, poi 16,125, poi 16,25; e l’arrotondamento può spostare un logit fino a 0,0625. Nel frattempo, il 4,7 % dei passaggi di generazione misurati sopra aveva un gap top-two sotto 0,1. Questa è l’intera differenza tra i due esperimenti: in float32 la perturbazione era cento volte più piccola della decisione più vicina, e in bfloat16 è della stessa dimensione. L’inferenza di produzione gira in 16 bit, su hardware con kernel fusi e ordini di riduzione che nessuno promette di mantenere. Che «il rumore numerico sia trascurabile» è una domanda su precisione e hardware, non sul modello.
Quindi, le quattro cause, catalogate come promesso dal Capitolo 9:
L’addizione floating-point non è associativa
Link alla sezione: L’addizione floating-point non è associativaIl riquadro del Capitolo 2. L’ordine di una somma cambia il suo valore, quindi qualunque cambiamento nel modo in cui una riduzione viene suddivisa cambia i logits. Questo è il substrato; le altre tre sono modi di cambiare l’ordine.
Il dynamic batching raggruppa la tua richiesta con quelle di sconosciuti
Link alla sezione: Il dynamic batching raggruppa la tua richiesta con quelle di sconosciutiIl continuous batching, dal Capitolo 13, è il motivo per cui l’inferenza è economicamente sostenibile, e significa che la forma delle matrici attraversate dai tuoi token dipende dal traffico. Misurato sopra: 147.321 logits si sono mossi perché la dimensione del batch è cambiata.
Il routing mixture-of-experts dipende dal batch
Link alla sezione: Il routing mixture-of-experts dipende dal batchIl riquadro del Capitolo 9 lo aveva già detto. Il router fa una scelta discreta per token e per layer, soggetta a limiti di capacità per expert calcolati sul batch. Un token che da solo sarebbe andato all’expert 7 va all’expert 12 in compagnia. Non è una differenza di arrotondamento; è un insieme diverso di pesi.
Il modello dietro il nome cambia
Link alla sezione: Il modello dietro il nome cambiaUna stringa di versione come -latest è un puntatore, e i puntatori vengono ripuntati. I provider aggiornano anche lo stack di serving sotto un identificatore di versione fisso. Nessuna delle due cose viene annunciata con la granularità che ti permetterebbe di correlarla al cambiamento del tuo output.
Il parametro seed di OpenAI è onesto su questo nell’unico modo possibile: viene fornito insieme a un campo system_fingerprint che identifica la configurazione del backend, e la documentazione afferma che il determinismo è best-effort e che un fingerprint cambiato significa che i risultati possono differire. Leggilo per ciò che è: un provider che ti dice che controlla tutte e quattro le cause sopra, che tu non ne controlli nessuna, e che l’unica cosa che può offrirti è dirti a posteriori che qualcosa si è mosso.
Dove si va ora
Link alla sezione: Dove si va oraTutto qui ha riguardato una manopola e le sue conseguenze. Fai un passo indietro e appare il problema più difficile: l’oggetto che abbiamo regolato è una distribuzione di probabilità, e le distribuzioni di probabilità non hanno un’interfaccia.
Una function call ce l’ha. Una riga di database ce l’ha. Un handler POST che si aspetta un body JSON con tre campi obbligatori ce l’ha, e rifiuterà qualunque altra cosa. Tra il modello e ogni altro componente del tuo sistema c’è un contratto su cui una delle due parti non può fare promesse: il modello produrrà qualcosa, estratto da una distribuzione che hai modellato ma non fissato, e il codice dall’altra parte ha bisogno di un valore di tipo noto o va in errore.
Il ponte tra questi due mondi è costruito con il materiale di questo capitolo, non con parsing e retry. Se un token romperebbe la struttura richiesta, non lo campioni sperando bene: imposti il suo logit a prima ancora che la softmax lo veda. Il constrained decoding è una maschera sullo stesso vettore che abbiamo rimodellato per tutto il capitolo, e trasforma «per favore rispondi in JSON» da richiesta a garanzia.
Il Capitolo 18 è quel contratto: tool calling, JSON Schema, output strutturati, e ciò che serve per rendere un sistema deterministico sicuro da costruire sopra uno probabilistico.
Fonti e metodo
Link alla sezione: Fonti e metodoTutte le misure in questo capitolo arrivano da Qwen/Qwen2.5-0.5B-Instruct su CPU, float32 salvo diversa indicazione, con il sampling implementato come scritto nella sezione facoltativa invece che delegato a una libreria. Sono un modello piccolo, e i valori specifici sono suoi; i meccanismi no. How to generate text with different decoding methods di Von Platen (Hugging Face, 2020) è l’articolo rispetto a cui questo è misurato ed è ancora la migliore introduzione breve allo stesso materiale. Per la sezione sul determinismo: le note di riproducibilità di PyTorch descrivono che cosa un seed fissa e non fissa su una singola macchina, la documentazione OpenAI di seed e system_fingerprint descrive che cosa un provider può e non può promettere, e la discussione del 2025 di Thinking Machines sui kernel batch-invariant è il resoconto pubblico più chiaro del perché risolvere questo problema a livello di inference server sia possibile ma non gratuito.
Riferimenti
Link alla sezione: Riferimenti-
Ackley, D. H., Hinton, G. E. and Sejnowski, T. J. A Learning Algorithm for Boltzmann Machines. Cognitive Science 9(1), pp. 147–169 (1985), dove la temperature in una softmax arriva dalla fisica statistica. Hinton, G., Vinyals, O. and Dean, J., Distilling the Knowledge in a Neural Network, arXiv:1503.02531 (2015), sezione 2, è il punto in cui lo stesso parametro riappare nel deep learning moderno: come modo per esporre l’intera distribuzione di un teacher, cioè le soft labels del Capitolo 13 invece del sampling di questo capitolo. ↩
-
Guo, C., Pleiss, G., Sun, Y. and Weinberger, K. Q. On Calibration of Modern Neural Networks. arXiv:1706.04599 (2017). Non confonderlo con la temperature di questo capitolo. Il temperature scaling adatta un singolo valore su un validation set in modo che la confidenza del modello corrisponda alla sua accuratezza; è un metodo di calibrazione post-hoc applicato agli output di un classificatore. Il temperature sampling è un controllo runtime su come un generatore estrae token. Stessa formula, scopo diverso, e nessun valore condiviso. ↩
-
Holtzman, A., Buys, J., Du, L., Forbes, M. and Choi, Y. The Curious Case of Neural Text Degeneration. arXiv:1904.09751 (2019). Introduce nucleus sampling e la misura secondo cui il decoding basato sulla massimizzazione produce testo il cui profilo di probabilità non assomiglia per nulla al testo umano. ↩ ↩2
-
Fan, A., Lewis, M. and Dauphin, Y. Hierarchical Neural Story Generation. arXiv:1805.04833 (2018). Il paper che ha reso popolare il top-k sampling. ↩
-
Nguyen, M. et al. Turning Up the Heat: Min-p Sampling for Creative and Coherent LLM Outputs. arXiv:2407.01082 (2024). ↩
-
Meister, C., Pimentel, T., Wiher, G. and Cotterell, R. Locally Typical Sampling. arXiv:2202.00666 (2022). ↩
-
Keskar, N. S., McCann, B., Varshney, L. R., Xiong, C. and Socher, R. CTRL: A Conditional Transformer Language Model for Controllable Generation. arXiv:1909.05858 (2019). La sezione 4.1 è la repetition penalty originale: quella che divide. ↩