Temperatura, top-p e o determinismo que non tes
A temperatura divide os logits antes da softmax: iso desmonta a idea do selector de creatividade. Mesmo greedy call, dúas respostas.
Nesta páxina
Aquí vai a mesma solicitude enviada ao mesmo modelo cinco veces. Mesmos pesos, mesmo prompt, mesma máquina, mesma semente aleatoria. O único que cambia é un número.
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 se rompeu nada. Cada token da última liña foi extraído lexitimamente da propia distribución de probabilidade do modelo sobre o seu vocabulario de 151.936 entradas. O número que cambiou chámase temperatura, descríbese na maioría da documentación como un selector de creatividade, e esa descrición é errónea dun xeito que este capítulo pode demostrar en vez de simplemente afirmar.
Este tamén é o capítulo no que se cumpren tres promesas anteriores. O capítulo 4 definiu o logit e nunca chegou a gastalo de verdade. A caixa de coma flotante do capítulo 2 rematou cunha instrución: lembra isto cando o capítulo 17 pregunte por que o mesmo prompt, modelo e semente poden producir tokens diferentes. E a caixa de mixture-of-experts do capítulo 9 prometeu un catálogo de catro causas de non determinismo. As tres chegan a continuación.
A liña única da que colga todo o capítulo
Ligazón á sección: A liña única da que colga todo o capítuloO capítulo 4 presentou o logit como unha puntuación real non normalizada, unha por clase. O capítulo 8 fixo que un modelo de linguaxe producise unha por cada entrada do vocabulario. A softmax converte ese vector en probabilidades:
A temperatura entra aquí —o nome vén da física estatística, onde o mesmo parámetro controla con que nitidez unha distribución de Boltzmann se concentra nos seus estados de baixa enerxía1— e divide os logits antes da exponencial:
Esa colocación é todo o mecanismo, e paga a pena velo con dúas liñas de álxebra para entender por que non podería estar noutro sitio. Supón que tentases aplicar a temperatura ás probabilidades no seu lugar: escalalas por e renormalizar. Obterías
A constante cancélase. Escalar probabilidades non fai absolutamente nada; a distribución volve sen cambios. A temperatura só ten efecto porque actúa sobre o expoñente, onde dividir por antes de expoñenciar é o mesmo que elevar cada probabilidade á potencia : unha remodelación non lineal que cambia as proporcións entre entradas, non a súa escala común.
A partir desa colocación, os dous límites saen sen máis traballo. Cando , o maior logit afástase do resto e colapsa no único token coa puntuación máis alta: greedy decoding. Cando medra, cada tende a cero, cada exponencial tende a 1, e a distribución aplánase cara a unha uniforme sobre todo o vocabulario. Exactamente en a fórmula divide por cero, así que toda implementación o trata como un caso especial e usa o máximo aritmético, incluído o widget de abaixo, que cambia a argmax en .
Un aviso, porque a colisión de nomes causa confusión real. Hai unha segunda cousa, non relacionada, chamada temperatura en machine learning: temperature scaling, un método de calibración que axusta un valor nun conxunto de validación para que a confianza dun clasificador coincida coa súa exactitude.2 Mesma fórmula, nada que ver coa xeración. Cando os papers din «temperature», moitas veces queren dicir iso; este capítulo nunca.
Aquí tes esa distribución, coa aritmética diante. Os logits son fixos e plausibles, así que os números da explicación de abaixo pódense comprobar contra o que ves:
A temperatura non é un selector de creatividade
Ligazón á sección: A temperatura non é un selector de creatividadeO número de ␣banana é todo o argumento en miniatura: subir a temperatura non pode darlle a un modelo unha idea que non tiña. Os logits xa están calculados, o ranking xa está fixado, e a temperatura consérvao exactamente: ningunha cantidade de calor move un token con menor puntuación por riba doutro con maior puntuación. O único que fai é redistribuír masa cara abaixo no ranking que produciu o propio modelo. Unha temperatura alta non fai un modelo máis inventivo; fai máis probable que emita os tokens que puntuou como malos.
Nun vocabulario real isto deixa de ser unha curiosidade e convértese na razón pola que a saída con temperatura alta é inutilizable. Medido en Qwen/Qwen2.5-0.5B-Instruct, unha forward pass, o prompt anterior, contando cantos tokens fan falta para acumular unha determinada parte da masa de probabilidade:
| temperatura | probabilidade top-1 | entropía | tokens que conteñen o 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 |
Le a fila inferior con calma. En , nunha pregunta cunha única resposta correcta, 32.966 tokens diferentes comparten o 90 % superior da masa de probabilidade. Iso non é un espazo creativo máis amplo. Iso é un modelo ao que a aritmética lle dixo que trate unha partícula coreana e un identificador de C++ como opcións vivas para a palabra despois de A:. O lixo do bloque inicial é a consecuencia directa, e non é un bug do modelo nin da biblioteca: é o que pedía a solicitude.
O intervalo útil é estreito e depende da tarefa, non do gusto. Nunha pregunta factual a resposta é un token e calquera calor por riba de arredor de 1,2 inxecta erro a cambio de nada. Nunha aberta si hai máis dunha boa continuación, e algo de calor compra variedade que segue sendo fluída:
"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..."En 1,3 o modelo inventou un nome propio e unha frase que non se analiza ben. A franxa entre «idéntico cada vez» e «incoherente» é aproximadamente de 0,6 a 1,1 para este modelo nesta tarefa, e o consello honesto é que a atopes medindo na túa tarefa, non copiando un número dun blog post.
Por que o texto máis probable é mal texto
Ligazón á sección: Por que o texto máis probable é mal textoHai unha pregunta obvia agochada baixo todo isto: se o modelo ten unha distribución de probabilidade e un token é o máis probable, por que non collelo sempre? Greedy decoding é gratis, reproducible e non necesita parámetros.
Porque o resultado é isto:
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 %Oito frases, unha frase. Case nove de cada dez ventás de catro tokens xa apareceran antes na mesma saída. Isto é dexeneración de texto neural, nomeada e explicada por Holtzman et al. no paper que introduciu top-p.3 O modelo non está roto; maximizar a probabilidade da secuencia é simplemente o obxectivo equivocado para texto aberto. A escrita humana non é a secuencia de palabras máis probable: leva sorpresa, a súa probabilidade por token deambula, baixa e recupérase, mentres que a ruta de máxima probabilidade é un punto fixo que, unha vez dentro, non ten motivo para saír.
Por iso existe a mostraxe. Tamén, e esta é a parte que adoita quedar fóra, non é unha lei universal. O capítulo 12 mediu 24 de 24 correctas en problemas de palabras de dous pasos con greedy decoding simple, e a mostraxe a temperatura 0,8 baixou iso ao 81 %; self-consistency gastou logo seis veces máis tokens para volver subir ata onde greedy xa estaba. As dúas cousas son certas á vez:
Xeración aberta. Non hai unha continuación correcta única, así que a máis probable é unha trampa: fai bucles, e o 87,6 % dela está copiado de si mesma. Mostrea.
Tarefas cunha única resposta correcta. Si hai unha continuación correcta única, así que extraer calquera outra cousa é extraer un erro. O 100 % do capítulo 12 converteuse en 81 % exactamente por isto. Non mostrees.
A maioría dos prompts de produción son do segundo tipo e configúranse como os do primeiro, porque a temperatura quedou no que usaba o código de exemplo.
Dúas formas de cortar, e só unha delas se adapta
Ligazón á sección: Dúas formas de cortar, e só unha delas se adaptaMostrear desde a distribución completa non é o que fai ninguén na práctica, porque a cola é enorme e está chea de sen sentido. Hai que cortar algo. Hai dúas respostas clásicas e difiren nun aspecto que o decide todo.
Top-k conserva un número fixo de candidatos. Ordena por probabilidade, queda cos primeiros , descarta o resto e renormaliza.4 Top-p, tamén chamado nucleus sampling, conserva unha cantidade fixa de masa: toma tokens en orde descendente ata que a súa probabilidade acumulada chega a , e para.3 Formalmente, o núcleo é o conxunto máis pequeno con
A diferenza soa cosmética e non o é, porque os dous prompts que envías no mesmo minuto teñen formas de distribución completamente distintas. Estes dous son o mesmo modelo a temperatura 1:
Q: What is the capital of France?\nA: | Once upon a time, | |
|---|---|---|
| probabilidade top-1 | 96,01 % | 25,39 % |
| tokens que conteñen o 90 % da masa | 1 | 467 |
| top-k = 40 conserva | 99,61 % da masa | 78,87 % da masa |
| masa nos postos 2 a 40 | 3,61 % | 53,48 % |
| token no posto 40 | ␣Av, 0,0093 % | ␣Dr, 0,128 % |
Un fixo, dous fallos en direccións opostas. No prompt factual, admite 39 tokens que entre todos valen 3,6 %: está deixando pasar lixo, incluído un candidato de nove milésimas por cento, porque a regra conta ocos e non evidencia. No prompt de historia, o mesmo tira fóra o 21 % da masa que o modelo asignou de verdade, porque o núcleo real aí ten 467 tokens de ancho.
Top-p fai que exactamente un número faga os dous traballos. Define e conserva 1 token no primeiro prompt e 467 no segundo, porque está facendo unha pregunta sobre a distribución en vez de imporlle un reconto. Mira esa adaptación directamente: mesmo corte, catro temperaturas:
Ese widget tamén resolve unha idea equivocada que convén nomear, porque lle custa diñeiro real á xente. Nunha distribución confiada, top_p = 0.9 non é «un pouco de variedade». É greedy. A temperatura 1, o token líder aquí contén o 96,90 %, que xa está por riba de 0,9, así que o núcleo ten un só token de ancho e nunca se pode extraer nada máis. Os equipos poñen top_p a 0,9 crendo que afrouxaron algo e logo pregúntanse por que cada resposta é idéntica.
Define top-k no seu lugar e o fallo contrario vese igual de claro:
As penalizacións, coas fórmulas, porque confundilas é endémico
Ligazón á sección: As penalizacións, coas fórmulas, porque confundilas é endémicoTres mecanismos diferentes viaxan baixo nomes parecidos, fan cousas distintas, e a diferenza é medible. Sexa o número de veces que o token xa apareceu.
Presence penalty
Ligazón á sección: Presence penaltyResta unha constante de calquera token que xa aparecese. Aparecer unha vez e aparecer corenta veces penalízanse igual. É un interruptor, non un mando.
Frequency penalty
Ligazón á sección: Frequency penaltyResta en proporción ao reconto. Un token usado catro veces penalízase catro veces máis ca un token usado unha vez, e a presión acumúlase a medida que medra o texto.
Repetition penalty (CTRL)
Ligazón á sección: Repetition penalty (CTRL)A orixinal, do paper de CTRL.7 Divide en vez de restar, co caso do signo necesario porque dividir un logit negativo o faría maior. A súa forza depende polo tanto da magnitude do logit, o que significa que o mesmo golpea de forma distinta en puntos distintos da mesma frase.
A mesma continuación dexenerada de antes, con cada unha aplicada. «Pasos alterados» conta cantos dos 120 pasos de xeración escolleron un token diferente ao que escollería o modelo sen penalización. A execución aquí é de 120 pasos fronte aos 140 do bloque anterior, por iso a liña base sen penalización marca 85,5 % en vez de 87,6 %:
| configuración | 4-grams repetidos | pasos alterados |
|---|---|---|
| nada | 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 |
Saen tres cousas. Presence en 0,5 cambiou tres decisións de 120 e reduciu a repetición nun cuarto: o bucle estaba sostido por un pequeno feixe de tokens. Frequency en 0,5 cambiou catro veces máis decisións para un efecto moito maior, porque o multiplicador do reconto segue medrando e a constante de presence non. E a penalización CTRL no valor tan copiado de 1,2 reescribiu 35 de 120 decisións, o que non é un empurrón; é outro modelo.
Ese último número prepara o fallo do que ninguén avisa.
Que lles fan as penalizacións aos textos que deben repetirse
Ligazón á sección: Que lles fan as penalizacións aos textos que deben repetirseO código repite. As táboas repiten. As listas repiten. A saída estruturada repite por definición: iso é a estrutura. Unha penalización non pode distinguir entre un modelo atrapado nun bucle e un modelo emitindo correctamente a cuarta fila dunha táboa, porque ambas as dúas cousas parecen un token que aparece outra vez.
As mesmas tres tarefas, xeradas de tres maneiras:
| tarefa | nada | frequency 0,5 | repetition 1,2 |
|---|---|---|---|
| táboa markdown, 6 filas | 0 / 56 pasos alterados | 0 / 56 | 2 / 62 |
| función Python | 0 / 93 | 0 / 93 | 10 / 110 |
| lista con viñetas, 1 a 12 | 0 / 50 | 0 / 50 | 0 / 50 |
A frequency penalty en 0,5 resultou ser inofensiva nas tres, que é un resultado útil e algo sorprendente, e di algo preciso: como non cambiou ningunha decisión, os tokens estruturais debían estar gañando as súas posicións por máis do que restaba a penalización, mesmo despois de aparecer cinco e seis veces. A penalización CTRL, que divide, si os despraza, e isto é o que produciu:
repetition 1.2, markdown table:
| n | 2^n |
| --- | --- |
| 0 | 1 |
| 1 | 2 |
| 2 | 4 |A aliñación desfáise: a cantidade de recheo dentro de cada cela cambia de fila a fila, porque a serie de espazos antes do pipe de peche é exactamente o tipo de repetición que a penalización existe para romper. Cosmético, e custou seis tokens extra. O caso de Python non é cosmético:
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,A penalización sacou o modelo de total —xa usado na docstring— e levouno a total_sum, recheou a saída con comentarios inventados para gastar o seu orzamento en tokens non usados, e logo entrou nun range de tres argumentos cun stride. O comentario di incrementing by 2 each time, que é incorrecto para unha suma de cadrados de 1 a . Unha repetition penalty produciu código incorrecto a partir dun prompt que se respondía correctamente sen ela.
A regra que segue é curta: as penalizacións son para prosa aberta, e deben estar desactivadas para código, saída estruturada, datos tabulares e calquera cousa cun esquema. O capítulo 18 trata exactamente desa segunda categoría.
A orde de aplicación, e por que cambia a resposta
Ligazón á sección: A orde de aplicación, e por que cambia a respostaToda implementación real aplica isto nunha secuencia concreta:
penalizacións → temperatura → top-k → top-p → mostraxe
Non é contabilidade arbitraria, e intercambiar dúas etapas produce distribucións realmente distintas. Dúas medicións, ambas no prompt factual.
Cortar antes ou despois da temperatura. O núcleo calcúlase sobre a distribución que recibe, e a temperatura cambia esa distribución de forma radical:
| top-p 0,9 despois da temperatura | top-p 0,9 antes da temperatura | |
|---|---|---|
| 1 token | 1 token | |
| 353 tokens | 1 token | |
| 32.966 tokens | 1 token |
En , a mesma configuración nominal dá un conxunto de candidatos de 32.966 ou de 1, só dependendo de que etapa se execute primeiro. Se algunha vez te preguntaches por que subir a temperatura «non fai nada» nun provider e destrúe a saída noutro cos mesmos dous números, esta táboa é unha resposta plausible.
Penalizar antes ou despois da temperatura. Restar unha penalización e logo dividir por dá unha penalización efectiva de ; dividir primeiro e logo restar dá . Cunha presence penalty de 1,0 aplicada ao token líder:
| temperatura | penalizar, logo temperar | temperar, logo penalizar |
|---|---|---|
| 0,5 | 99,858 % | 99,948 % |
| 1,0 | 89,839 % | 89,839 % |
| 2,0 | 10,783 % | 6,830 % |
Idénticas en , como deben ser. Separadas por un factor de 1,58 en . «Presence penalty 1.0» non é unha cantidade de penalización ben definida salvo que tamén saibas onde se aplica a temperatura, e ningunha API documenta isto.
Mostrar detalles
Opcional: todo o pipeline, na orde anterior.
Dezaseis liñas, e todo este capítulo está nelas. É o mesmo cálculo que fai o widget, nun vector de logit real en vez de dez números fixos.
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)])O cumsum(0) - p na liña de top-p é a masa acumulada excluíndo o token actual, que é o que fai que o núcleo inclúa o token que cruza o limiar en vez de parar xusto antes. Erra iso por un e top_p = 0.9 convértese silenciosamente nun corte algo máis estrito ca o de calquera outra implementación.
Este é un dos poucos lugares na segunda metade do curso nos que Python é a linguaxe axeitada, e a razón é estrutural, non estilística: cada liña anterior necesita que teñas nas mans o vector completo de logits, e sobre unha API HTTP ese vector non existe. Podes enviar temperature e top_p a un provider; non podes implementalos, e non podes ver que fixeron.
Non hai unha API de mostraxe universal
Ligazón á sección: Non hai unha API de mostraxe universalCada provider acepta un subconxunto distinto destes controis, con rangos distintos, e ignora o resto en silencio. Isto non é unha queixa abstracta. Calquera aplicación que ofrece escolla de modelo ten que escribir nalgún sitio as diferenzas, e o ficheiro onde o fai é un mapa da incompatibilidade. Isto é o que un catálogo deste tipo declara para un único parámetro nas nove fontes de texto que admite:
| rango de temperatura declarado | fontes |
|---|---|
| 0 a 1 | Anthropic, Google, Meta, Cerebras, PaLM |
| 0 a 1,5 | Mistral |
| 0 a 2 | OpenAI, DeepSeek, xAI |
A palabra é a mesma; a escala non. Unha «temperatura de 1» é a distribución sen modificar nun e a calor máxima permitida noutro, e metade do catálogo non pode expresar o valor que a outra metade trata como neutral-máis-un-pouco. O resto dos mandos son igual de desiguais: as entradas de OpenAI, DeepSeek e xAI aceptan presence e frequency penalties e ningún topK; as de Google, Meta, Cerebras e PaLM aceptan topK e ningunha penalización; Anthropic acepta topK, topP e secuencias de parada e ningunha penalización; e exactamente unha das nove —Mistral— acepta unha semente. Enviar un parámetro que un provider non implementa xeralmente non produce erro ningún: a solicitude ten éxito, o mando non fai nada, e ti conclúes que a configuración non ten efecto.
E observa que é un ficheiro así: unha afirmación sobre a API doutra persoa, escrita nun día concreto, que nada verifica despois. Un catálogo que di 0 a 1 para un provider que agora acepta 0 a 2 limitará silenciosamente cada solicitude.
Dous controis máis pertencen á mesma familia. logprobs, cando existe, devolve as log-probabilities do token escollido e a miúdo as poucas alternativas superiores: a única xanela que tes cara á distribución da que trata este capítulo, e a base de toda heurística de confianza construída sobre un modelo pechado. E maximum tokens máis secuencias de parada rematan a xeración sen referencia ningunha á probabilidade: un límite duro e unha coincidencia de cadea. Ambos aparecen como o finish_reason do capítulo 14, onde length significa que a túa resposta foi cortada a media frase por un orzamento, non rematada polo modelo.
A semente, e o determinismo que non tes
Ligazón á sección: A semente, e o determinismo que non tesDefine unha semente e a mostraxe vólvese reproducible. Esa parte é real, e é doada de verificar:
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\"."Idénticas byte a byte dentro dunha semente, distintas entre sementes, exactamente como se anuncia. Así que o que fixa a semente é a extracción aleatoria na última liña desa función sample: que token se escolle dada unha distribución.
O que non fixa é a distribución. E aí está o problema, porque o vector de logits que produce o teu modelo non é un obxecto matemático; é a saída de miles de millóns de sumas en coma flotante, e esas sumas teñen unha orde.
O capítulo 2 deixou este experimento preparado. O mesmo millón de números float32, sumados con agrupacións distintas:
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? FalseMira a última liña. O número de chunks cambia a resposta. Non é unha curiosidade de numpy; é o mecanismo, porque cando un servidor de inferencia divide unha redución entre máis ou menos unidades paralelas está facendo exactamente isto. E un servidor divide segundo cantas solicitudes estea atendendo.
Aquí está ese efecto no propio modelo. O mesmo prompt, a mesma forward pass, coa única diferenza de cantas outras solicitudes cadraron no 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-05Executado só, o modelo é perfectamente determinista: vinte pasadas, idénticas ata o bit. Pon o prompt idéntico nun batch con solicitudes alleas e cambian o 97 % dos seus logits. Nada da túa solicitude cambiou. Chegou a solicitude doutra persoa.
Agora a parte honesta, porque isto adoita contarse coma se fose o final da historia. Un cambio de só altera a saída se dous tokens candidatos estaban a esa distancia entre si. En 717 pasos de xeración ao longo de doce prompts, a menor separación entre os dous logits superiores foi —cen veces maior ca a perturbación— e ningún paso estivo o bastante preto para virar. Así que neste modelo, en float32, nun portátil, o batching moveu todos os logit e non cambiou ningún token.
Iso é unha descrición de condicións favorables, non unha tranquilización, e abonda con cambiar unha desas condicións:
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."Seis de oito respostas diverxen, e unha delas degrádase moito. A táboa do capítulo 2 di por que: bfloat16 conserva 7 bits de mantisa, así que preto dunha magnitude de logit de 16 os valores representables están separados por 0,125: 16,0, logo 16,125, logo 16,25, e o redondeo pode mover un logit ata 0,0625. Mentres tanto, o 4,7 % dos pasos de xeración medidos arriba tiñan unha separación top-two por baixo de 0,1. Esa é toda a diferenza entre os dous experimentos: en float32 a perturbación era cen veces menor ca a decisión máis próxima, e en bfloat16 ten o mesmo tamaño. A inferencia de produción execútase en 16 bits, en hardware con kernels fusionados e ordes de redución que ninguén promete conservar. Que «o ruído numérico é desprezable» é unha pregunta sobre precisión e hardware, non sobre o modelo.
Así que, as catro causas, catalogadas como prometeu o capítulo 9:
A suma en coma flotante non é asociativa
Ligazón á sección: A suma en coma flotante non é asociativaA caixa do capítulo 2. A orde dunha suma cambia o seu valor, así que calquera cambio en como se divide unha redución cambia os logits. Este é o substrato; as outras tres son formas de cambiar a orde.
Dynamic batching agrupa a túa solicitude coas de descoñecidos
Ligazón á sección: Dynamic batching agrupa a túa solicitude coas de descoñecidosO continuous batching, do capítulo 13, é a razón pola que a inferencia é asumible, e significa que a forma das matrices polas que flúen os teus tokens depende do tráfico. Medido arriba: 147.321 logits movéronse porque cambiou o tamaño do batch.
O routing de mixture-of-experts depende do batch
Ligazón á sección: O routing de mixture-of-experts depende do batchA caixa do capítulo 9 xa o dicía. O router fai unha escolla discreta por token e por capa, suxeita a límites de capacidade por experto calculados sobre o batch. Un token que iría ao experto 7 estando só vai ao experto 12 en compañía. Isto non é unha diferenza de redondeo; é un conxunto de pesos distinto.
O modelo detrás do nome cambia
Ligazón á sección: O modelo detrás do nome cambiaUnha cadea de versión como -latest é un punteiro, e os punteiros repúntanse. Os providers tamén actualizan a pila de servizo baixo un identificador de versión fixo. Ningunha das dúas cousas se anuncia coa granularidade que che permitiría correlacionala co cambio da túa propia saída.
O parámetro seed de OpenAI é honesto sobre isto do único xeito que pode: envíase xunto cun campo system_fingerprint que identifica a configuración do backend, e a documentación afirma que o determinismo é best-effort e que unha fingerprint cambiada significa que os resultados poden diferir. Le iso como o que é: un provider dicíndoche que controla as catro causas anteriores, que ti non controlas ningunha, e que o único que pode ofrecer é dicirche despois do feito que algo se moveu.
A onde imos despois
Ligazón á sección: A onde imos despoisTodo isto tratou dun mando e das súas consecuencias. Retrocede un nivel e aparece o problema máis difícil: o obxecto que estivemos axustando é unha distribución de probabilidade, e as distribucións de probabilidade non teñen interface.
Unha function call tena. Unha fila de base de datos tena. Un handler POST que espera un corpo JSON con tres campos obrigatorios tena, e rexeitará calquera outra cousa. Entre o modelo e cada outro compoñente do teu sistema hai un contrato sobre o que unha das partes non pode facer promesas: o modelo producirá algo, extraído dunha distribución que moldeaches pero non fixaches, e o código do outro lado necesita un valor dun tipo coñecido ou lanza un erro.
A ponte entre eses dous mundos constrúese co material deste capítulo, non con parsing e reintentos. Se un token rompería a estrutura requirida, non o mostreas e esperas: poñes o seu logit en antes de que a softmax chegue a velo. Constrained decoding é unha máscara sobre o mesmo vector que pasamos todo este capítulo remodelando, e converte «responde en JSON, por favor» dunha petición nunha garantía.
O capítulo 18 é ese contrato: tool calling, JSON Schema, structured outputs, e o que fai falta para que un sistema determinista sexa seguro de construír enriba dun probabilístico.
Fontes e método
Ligazón á sección: Fontes e métodoTodas as medicións deste capítulo veñen de Qwen/Qwen2.5-0.5B-Instruct en CPU, float32 salvo indicación contraria, coa mostraxe implementada como se escribiu na sección opcional en vez de delegada nunha biblioteca. É un modelo pequeno, e os valores concretos son seus; os mecanismos non. O artigo de Von Platen How to generate text with different decoding methods (Hugging Face, 2020) é o texto contra o que se mide este, e segue sendo a mellor introdución curta ao mesmo material. Para a sección de determinismo: as notas de reproducibilidade de PyTorch describen que fixa e que non fixa unha semente nunha soa máquina, a documentación de OpenAI sobre seed e system_fingerprint describe que pode e non pode prometer un provider, e a discusión de Thinking Machines de 2025 sobre kernels invariantes ao batch é a explicación pública máis clara de por que arranxar isto no nivel do servidor de inferencia é posible pero non gratis.
Referencias
Ligazón á sección: Referencias-
Ackley, D. H., Hinton, G. E. e Sejnowski, T. J. A Learning Algorithm for Boltzmann Machines. Cognitive Science 9(1), pp. 147–169 (1985), onde a temperatura nunha softmax vén da física estatística. Hinton, G., Vinyals, O. e Dean, J., Distilling the Knowledge in a Neural Network, arXiv:1503.02531 (2015), sección 2, é onde o mesmo parámetro reaparece no deep learning moderno: como unha forma de expor a distribución completa dun profesor, que son as soft labels do capítulo 13 e non a mostraxe deste capítulo. ↩
-
Guo, C., Pleiss, G., Sun, Y. e Weinberger, K. Q. On Calibration of Modern Neural Networks. arXiv:1706.04599 (2017). Non confundas isto coa temperatura deste capítulo. Temperature scaling axusta un único valor nun conxunto de validación para que a confianza do modelo coincida coa súa exactitude; é un método de calibración post-hoc aplicado ás saídas dun clasificador. Temperature sampling é un control en tempo de execución sobre como un xerador extrae tokens. Mesma fórmula, propósito distinto, e ningún valor compartido. ↩
-
Holtzman, A., Buys, J., Du, L., Forbes, M. e Choi, Y. The Curious Case of Neural Text Degeneration. arXiv:1904.09751 (2019). Introduce nucleus sampling e a medición de que a decodificación baseada en maximización produce texto cuxo perfil de probabilidade non se parece en nada ao texto humano. ↩ ↩2
-
Fan, A., Lewis, M. e Dauphin, Y. Hierarchical Neural Story Generation. arXiv:1805.04833 (2018). O paper que popularizou 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. e Cotterell, R. Locally Typical Sampling. arXiv:2202.00666 (2022). ↩
-
Keskar, N. S., McCann, B., Varshney, L. R., Xiong, C. e Socher, R. CTRL: A Conditional Transformer Language Model for Controllable Generation. arXiv:1909.05858 (2019). A sección 4.1 é a repetition penalty orixinal: a que divide. ↩