Temperatura, top-p e o determinismo que você não tem
A temperatura divide os logits antes da softmax — e isso derruba a ideia de controle de criatividade. Mesmo em greedy, duas chamadas podem divergir.
Nesta página
Aqui está a mesma solicitação enviada ao mesmo modelo cinco vezes. Mesmos pesos, mesmo prompt, mesma máquina, mesmo seed aleatório. A única coisa que muda é um 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 ('$ספטמבר..."Nada quebrou. Cada token na última linha foi extraído legitimamente da própria distribuição de probabilidade do modelo sobre seu vocabulário de 151.936 entradas. O número que mudou se chama temperatura, é descrito na maioria das documentações como um controle de criatividade, e essa descrição está errada de um jeito que este capítulo pode demonstrar, não apenas afirmar.
Este também é o capítulo em que três promessas anteriores vencem. O Capítulo 4 definiu o logit e nunca chegou a gastá-lo de verdade. A caixa de ponto flutuante do Capítulo 2 terminou com uma instrução — lembre-se disso quando o Capítulo 17 perguntar por que o mesmo prompt, modelo e seed podem produzir tokens diferentes. E a caixa de mixture-of-experts do Capítulo 9 prometeu um catálogo de quatro causas de não determinismo. As três aparecem abaixo.
A única linha da qual o capítulo inteiro depende
Link para a seção: A única linha da qual o capítulo inteiro dependeO Capítulo 4 apresentou o logit como uma pontuação real não normalizada, uma por classe. O Capítulo 8 fez um modelo de linguagem produzir uma por entrada do vocabulário. A softmax transforma esse vetor em probabilidades:
A temperatura entra aqui — o nome vem da física estatística, onde o mesmo parâmetro controla o quão fortemente uma distribuição de Boltzmann se concentra em seus estados de baixa energia1 — e ela divide os logits antes da exponencial:
Essa posição é o mecanismo inteiro, e vale duas linhas de álgebra para ver por que ela não poderia estar em nenhum outro lugar. Suponha que você tentasse aplicar a temperatura às probabilidades em vez disso — escalá-las por e renormalizar. Você obteria
A constante se cancela. Escalar probabilidades não faz absolutamente nada; a distribuição volta inalterada. A temperatura só tem efeito porque atua no expoente, onde dividir por antes de exponenciar é o mesmo que elevar cada probabilidade à potência — uma remodelagem não linear que altera as razões entre as entradas, não sua escala comum.
A partir dessa posição, os dois limites seguem sem nenhum trabalho adicional. Quando , o maior logit dispara em relação ao restante e colapsa no único token de maior pontuação: greedy decoding. Quando cresce, todo tende a zero, toda exponencial tende a 1, e a distribuição se achata rumo a uma distribuição uniforme sobre todo o vocabulário. Exatamente em a fórmula divide por zero, então toda implementação trata esse caso separadamente como o máximo aritmético — inclusive o widget abaixo, que muda para argmax em .
Um aviso, porque a colisão de nomes causa confusão real. Existe uma segunda coisa, sem relação, chamada temperatura em machine learning: temperature scaling, um método de calibração que ajusta um valor em um conjunto de validação para que a confiança de um classificador corresponda à sua precisão.2 Mesma fórmula, nada a ver com geração. Artigos que dizem "temperatura" muitas vezes querem dizer isso; este capítulo nunca quer.
Aqui está essa distribuição, com a aritmética na sua frente. Os logits são fixos e plausíveis, então os números no texto abaixo podem ser conferidos com o que você vê:
Temperatura não é um controle de criatividade
Link para a seção: Temperatura não é um controle de criatividadeO número ␣banana é o argumento inteiro em miniatura: aumentar a temperatura não pode dar a um modelo uma ideia que ele não teve. Os logits já foram calculados, o ranking já está fixo, e a temperatura o preserva exatamente — nenhum calor jamais move um token com pontuação menor acima de outro com pontuação maior. Tudo que ela faz é redistribuir massa para baixo no ranking que o próprio modelo produziu. Temperatura alta não deixa um modelo mais inventivo; ela o torna mais propenso a emitir os tokens que ele pontuou como ruins.
Em um vocabulário real, isso deixa de ser curiosidade e vira o motivo pelo qual saídas em alta temperatura são inutilizáveis. Medido em Qwen/Qwen2.5-0.5B-Instruct, uma forward pass, o prompt acima, contando quantos tokens são necessários para acumular uma determinada parcela da massa de probabilidade:
| temperatura | probabilidade top-1 | entropia | tokens com 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 |
Leia a última linha devagar. Em , numa pergunta com exatamente uma resposta correta, 32.966 tokens diferentes dividem os 90 % superiores da massa de probabilidade. Isso não é um espaço criativo mais amplo. É um modelo que recebeu uma instrução aritmética para tratar uma partícula coreana e um identificador C++ como opções vivas para a palavra depois de A:. O lixo no bloco de abertura é a consequência direta, e não é um bug no modelo nem na biblioteca — é o que a solicitação pediu.
A faixa útil é estreita e depende da tarefa, não do gosto. Em uma pergunta factual, a resposta é um token, e qualquer calor acima de cerca de 1.2 injeta erro sem benefício. Em uma pergunta aberta, realmente há mais de uma boa continuação, e algum calor compra variedade que ainda permanece fluente:
"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..."Em 1.3, o modelo inventou um nome próprio e uma frase que não faz parse. A faixa entre "idêntico toda vez" e "incoerente" fica aproximadamente de 0.6 a 1.1 para este modelo nesta tarefa, e o conselho honesto é que você a encontre medindo na sua tarefa, não copiando um número de um post de blog.
Por que o texto mais provável é um texto ruim
Link para a seção: Por que o texto mais provável é um texto ruimHá uma pergunta óbvia escondida em tudo isso: se o modelo tem uma distribuição de probabilidade e um token é o mais provável, por que não escolhê-lo sempre? Greedy decoding é gratuito, reproduzível e não precisa de 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, uma frase. Quase nove em cada dez janelas de quatro tokens já tinham aparecido antes na mesma saída. Isso é degeneração de texto neural, nomeada e explicada por Holtzman et al. no artigo que introduziu o top-p.3 O modelo não está quebrado; maximizar a probabilidade da sequência é simplesmente o objetivo errado para texto aberto. A escrita humana não é a sequência de palavras mais provável — ela carrega surpresa, sua probabilidade por token oscila, cai e se recupera — enquanto o caminho de máxima probabilidade é um ponto fixo que, uma vez alcançado, não tem motivo para sair.
É por isso que a amostragem existe. E também, esta é a parte que costuma ficar de fora, não é uma lei universal. O Capítulo 12 mediu 24 de 24 acertos em problemas de palavras de duas etapas com greedy decoding simples, e a amostragem em temperatura 0.8 derrubou isso para 81 %; self-consistency então gastou seis vezes mais tokens para voltar aonde greedy já estava. As duas coisas são verdadeiras ao mesmo tempo:
Geração aberta. Não há uma única continuação correta, então a mais provável é uma armadilha — ela entra em loop, e 87,6 % dela é copiada de si mesma. Faça amostragem.
Tarefas com uma resposta correta. Há uma única continuação correta, então escolher qualquer outra coisa é escolher um erro. Os 100 % do Capítulo 12 viraram 81 % exatamente por esse motivo. Não faça amostragem.
A maioria dos prompts de produção é do segundo tipo e é configurada como o primeiro, porque a temperatura ficou no valor usado pelo código de exemplo.
Duas formas de cortar, e só uma delas se adapta
Link para a seção: Duas formas de cortar, e só uma delas se adaptaAmostragem da distribuição completa não é o que alguém realmente faz, porque a cauda é enorme e cheia de absurdo. Algo precisa ser cortado. Há duas respostas clássicas, e elas diferem em um aspecto que decide tudo.
Top-k mantém um número fixo de candidatos. Ordene por probabilidade, mantenha os primeiros , descarte o restante, renormalize.4 Top-p, também chamado de nucleus sampling, mantém uma quantidade fixa de massa: pegue tokens em ordem decrescente até que sua probabilidade acumulada atinja , e pare.3 Formalmente, o núcleo é o menor conjunto com
A diferença parece cosmética, mas não é, porque os dois prompts que você envia no mesmo minuto têm formatos de distribuição completamente diferentes. Ambos abaixo são o mesmo modelo em temperatura 1:
Q: What is the capital of France?\nA: | Once upon a time, | |
|---|---|---|
| probabilidade top-1 | 96,01 % | 25,39 % |
| tokens com 90 % da massa | 1 | 467 |
| top-k = 40 mantém | 99,61 % da massa | 78,87 % da massa |
| massa nas posições 2 a 40 | 3,61 % | 53,48 % |
| token na posição 40 | ␣Av, 0,0093 % | ␣Dr, 0,128 % |
Um fixo, duas falhas em direções opostas. No prompt factual, admite 39 tokens que juntos valem 3,6 % — deixa passar lixo, incluindo um candidato em nove milésimos de por cento, porque a regra conta vagas, não evidência. No prompt de história, o mesmo joga fora 21 % da massa que o modelo genuinamente atribuiu, porque o núcleo real ali tem 467 tokens de largura.
Top-p faz exatamente um número cumprir os dois trabalhos. Defina e ele mantém 1 token no primeiro prompt e 467 no segundo, porque está fazendo uma pergunta sobre a distribuição em vez de impor uma contagem a ela. Veja essa adaptação diretamente — mesmo corte, quatro temperaturas:
Esse widget também resolve uma concepção equivocada que vale nomear, porque ela custa dinheiro real. Em uma distribuição confiante, top_p = 0.9 não é "um pouco de variedade". É greedy. Em temperatura 1, o token líder aqui detém 96,90 %, que já passa de 0.9, então o núcleo tem um token de largura e nada mais poderá ser escolhido. Equipes definem top_p como 0.9 acreditando que afrouxaram alguma coisa e depois se perguntam por que todas as respostas são idênticas.
Defina top-k em vez disso e a falha oposta fica igualmente visível:
As penalidades, com as fórmulas, porque confundi-las é endêmico
Link para a seção: As penalidades, com as fórmulas, porque confundi-las é endêmicoTrês mecanismos diferentes circulam sob nomes parecidos, fazem coisas diferentes, e a diferença é mensurável. Seja o número de vezes que o token já apareceu.
Presence penalty
Link para a seção: Presence penaltySubtraia uma constante de qualquer token que tenha aparecido. Aparecer uma vez e aparecer quarenta vezes são penalizados de modo idêntico. É um interruptor, não um botão graduado.
Frequency penalty
Link para a seção: Frequency penaltySubtraia proporcionalmente à contagem. Um token usado quatro vezes é penalizado quatro vezes mais forte que um token usado uma vez, e a pressão se acumula conforme o texto cresce.
Repetition penalty (CTRL)
Link para a seção: Repetition penalty (CTRL)A original, do artigo CTRL.7 Ela divide em vez de subtrair, com o caso de sinal necessário porque dividir um logit negativo o tornaria maior. Sua força, portanto, depende da magnitude do logit, o que significa que o mesmo atinge de modo diferente pontos diferentes da mesma frase.
A mesma continuação degenerada de antes, com cada uma aplicada. "Etapas alteradas" conta quantas das 120 etapas de geração escolheram um token diferente daquele que o modelo sem penalidade teria escolhido. A execução aqui tem 120 etapas contra 140 no bloco acima, por isso a linha de base sem penalidade mostra 85,5 % em vez de 87,6 %:
| configuração | 4-grams repetidos | etapas alteradas |
|---|---|---|
| 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 |
Três coisas aparecem. Presence em 0.5 mudou três decisões em 120 e cortou a repetição em um quarto — o loop era sustentado por um punhado de tokens. Frequency em 0.5 mudou quatro vezes mais decisões e teve um efeito muito maior, porque o multiplicador da contagem continua crescendo enquanto a constante de presence não cresce. E a penalidade CTRL no valor amplamente copiado de 1.2 reescreveu 35 de 120 decisões, o que não é um empurrão; é outro modelo.
Esse último número prepara a falha sobre a qual ninguém avisa.
O que as penalidades fazem com texto que deveria se repetir
Link para a seção: O que as penalidades fazem com texto que deveria se repetirCódigo repete. Tabelas repetem. Listas repetem. Saída estruturada repete por definição — isso é a estrutura. Uma penalidade não consegue distinguir um modelo preso em loop de um modelo emitindo corretamente a quarta linha de uma tabela, porque ambos parecem um token aparecendo de novo.
As mesmas três tarefas, geradas de três formas:
| tarefa | nada | frequency 0.5 | repetition 1.2 |
|---|---|---|---|
| tabela markdown, 6 linhas | 0 / 56 etapas alteradas | 0 / 56 | 2 / 62 |
| função Python | 0 / 93 | 0 / 93 | 10 / 110 |
| lista com marcadores, 1 a 12 | 0 / 50 | 0 / 50 | 0 / 50 |
A frequency penalty em 0.5 acabou sendo inofensiva nas três, o que é um resultado útil e levemente surpreendente, e diz algo preciso: como nenhuma decisão mudou, os tokens estruturais devem ter vencido suas posições por mais do que a penalidade subtraiu, mesmo depois de aparecerem cinco e seis vezes. A penalidade CTRL, que divide, de fato os desloca, e aqui está o que ela produziu:
repetition 1.2, markdown table:
| n | 2^n |
| --- | --- |
| 0 | 1 |
| 1 | 2 |
| 2 | 4 |O alinhamento desmorona: a quantidade de preenchimento dentro de cada célula muda de linha para linha, porque a sequência de espaços antes da barra vertical de fechamento é exatamente o tipo de repetição que a penalidade existe para quebrar. Cosmético, e custou seis tokens extras. O caso Python não é 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 penalidade empurrou o modelo para fora de total — já usado na docstring — para total_sum, preencheu a saída com comentários inventados para gastar seu orçamento em tokens não usados, e então entrou em um range de três argumentos com um passo. O comentário diz incrementing by 2 each time, o que está errado para uma soma de quadrados de 1 a . Uma repetition penalty produziu código incorreto a partir de um prompt que era respondido corretamente sem ela.
A regra que segue é curta: penalidades são para prosa aberta, e devem ficar desligadas para código, saída estruturada, dados tabulares e qualquer coisa com schema. O Capítulo 18 é exatamente sobre essa segunda categoria.
A ordem de aplicação, e por que ela muda a resposta
Link para a seção: A ordem de aplicação, e por que ela muda a respostaToda implementação real aplica isso em uma sequência específica:
penalidades → temperatura → top-k → top-p → amostragem
Isso não é contabilidade arbitrária, e trocar duas etapas produz distribuições genuinamente diferentes. Duas medições, ambas no prompt factual.
Cortar antes ou depois da temperatura. O núcleo é calculado sobre qualquer distribuição que recebe, e a temperatura muda radicalmente essa distribuição:
| top-p 0.9 depois da temperatura | top-p 0.9 antes da temperatura | |
|---|---|---|
| 1 token | 1 token | |
| 353 tokens | 1 token | |
| 32.966 tokens | 1 token |
Em , a mesma configuração nominal produz um conjunto candidato de 32.966 ou de 1, dependendo puramente de qual etapa roda primeiro. Se você já se perguntou por que aumentar a temperatura "não faz nada" em um provedor e destrói a saída em outro com os mesmos dois números, esta tabela é uma resposta plausível.
Penalizar antes ou depois da temperatura. Subtrair uma penalidade e depois dividir por dá uma penalidade efetiva de ; dividir primeiro e depois subtrair dá . Com uma presence penalty de 1.0 aplicada ao token líder:
| temperatura | penaliza, depois tempera | tempera, depois penaliza |
|---|---|---|
| 0.5 | 99,858 % | 99,948 % |
| 1.0 | 89,839 % | 89,839 % |
| 2.0 | 10,783 % | 6,830 % |
Idênticas em , como devem ser. Separadas por um fator de 1,58 em . "Presence penalty 1.0" não é uma quantidade bem definida de penalidade a menos que você também saiba onde a temperatura é aplicada, e nenhuma API documenta isso.
Mostrar detalhes
Opcional: o pipeline inteiro, na ordem acima.
Dezesseis linhas, e tudo neste capítulo está nelas. É o mesmo cálculo que o widget realiza, em um vetor real de logits em 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 linha de top-p é a massa acumulada excluindo o token atual, que é o que faz o núcleo incluir o token que cruza o limiar em vez de parar imediatamente antes dele. Erre isso por um e top_p = 0.9 vira silenciosamente um corte um pouco mais apertado que o de todas as outras implementações.
Este é um dos poucos lugares na segunda metade do curso em que Python é a linguagem certa, e o motivo é estrutural, não estilístico: cada linha acima precisa do vetor completo de logits nas suas mãos, e por uma API HTTP esse vetor não existe. Você pode enviar temperature e top_p para um provedor; você não pode implementá-los, e não pode ver o que eles fizeram.
Não existe uma API universal de amostragem
Link para a seção: Não existe uma API universal de amostragemCada provedor aceita um subconjunto diferente desses controles, com faixas diferentes, e ignora o resto em silêncio. Isso não é uma reclamação abstrata. Qualquer aplicação que oferece escolha de modelo precisa escrever as diferenças em algum lugar, e o arquivo onde faz isso é um mapa da incompatibilidade. Aqui está o que um catálogo desse tipo declara para um único parâmetro nas nove fontes de texto que ele suporta:
| faixa de temperatura declarada | fontes |
|---|---|
| 0 a 1 | Anthropic, Google, Meta, Cerebras, PaLM |
| 0 a 1.5 | Mistral |
| 0 a 2 | OpenAI, DeepSeek, xAI |
A palavra é a mesma; a escala não. Uma "temperatura de 1" é a distribuição não modificada em um provedor e o máximo de calor permitido em outro, e metade do catálogo não consegue expressar o valor que a outra metade trata como neutro-mais-um-pouco. Os outros botões são igualmente desiguais: as entradas OpenAI, DeepSeek e xAI aceitam presence e frequency penalties e nenhum topK; as entradas Google, Meta, Cerebras e PaLM aceitam topK e nenhuma penalidade; Anthropic aceita topK, topP e sequências de parada, e nenhuma penalidade; e exatamente uma das nove — Mistral — aceita um seed. Enviar um parâmetro que um provedor não implementa geralmente não produz erro nenhum: a solicitação é bem-sucedida, o botão não faz nada, e você conclui que a configuração não tem efeito.
E repare no que esse arquivo é: uma alegação sobre a API de outra pessoa, escrita em um dia específico, que nada verifica depois. Um catálogo que diz 0 a 1 para um provedor que agora aceita 0 a 2 vai limitar silenciosamente toda solicitação.
Mais dois controles pertencem à mesma família. logprobs, quando oferecido, retorna as log-probabilidades do token escolhido e muitas vezes as principais alternativas — a única janela que você tem para a distribuição de que este capítulo trata, e a base de toda heurística de confiança construída sobre um modelo fechado. E maximum tokens mais sequências de parada encerram a geração sem referência à probabilidade: um limite rígido e uma correspondência de string. Ambos aparecem como o finish_reason do Capítulo 14, onde length significa que sua resposta foi cortada no meio da frase por um orçamento, não finalizada pelo modelo.
O seed e o determinismo que você não tem
Link para a seção: O seed e o determinismo que você não temDefina um seed e a amostragem se torna reproduzível. Essa parte é real, e é fácil 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êntico byte a byte dentro de um seed, diferente entre seeds, exatamente como anunciado. Então o que o seed fixa é o sorteio aleatório na última linha daquela função sample — qual token é escolhido dada uma distribuição.
O que ele não fixa é a distribuição. E é aí que está o problema, porque o vetor de logits que seu modelo produz não é um objeto matemático; é o resultado de bilhões de somas em ponto flutuante, e essas somas têm uma ordem.
O Capítulo 2 deixou este experimento pronto. O mesmo milhão de números float32, somados em agrupamentos diferentes:
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? FalseVeja a última linha. O número de chunks muda a resposta. Isso não é uma curiosidade sobre numpy; é o mecanismo, porque quando um servidor de inferência divide uma redução por mais ou menos unidades paralelas, ele está fazendo precisamente isso. E um servidor divide de acordo com quantas solicitações está atendendo.
Aqui está esse efeito no próprio modelo. O mesmo prompt, a mesma forward pass, a única diferença sendo quantas outras solicitações por acaso estavam 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-05Rodando sozinho, o modelo é perfeitamente determinístico — vinte passes, idênticos até o bit. Coloque o prompt idêntico em um batch com solicitações sem relação e 97 % dos seus logits mudam. Nada na sua solicitação mudou. A solicitação de outra pessoa chegou.
Agora a parte honesta, porque isso costuma ser contado como se fosse o fim da história. Uma mudança de só altera a saída se dois tokens candidatos estiverem dentro dessa distância um do outro. Em 717 etapas de geração ao longo de doze prompts, a menor lacuna entre os dois maiores logits foi — cem vezes maior que a perturbação — e nenhuma etapa ficou perto o suficiente para virar. Então, neste modelo, em float32, em um laptop, o batching moveu todo logit e não mudou nenhum token.
Isso é uma descrição de condições favoráveis, não uma garantia, e uma mudança nessas condições basta:
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 divergem, e uma delas degrada bastante. A tabela do Capítulo 2 diz por quê: bfloat16 mantém 7 bits de mantissa, então perto de uma magnitude de logit de 16 os valores representáveis ficam separados por 0,125 — 16,0, depois 16,125, depois 16,25 — e o arredondamento pode mover um logit em até 0,0625. Enquanto isso, 4,7 % das etapas de geração medidas acima tinham uma lacuna entre os dois primeiros abaixo de 0,1. Essa é toda a diferença entre os dois experimentos: em float32 a perturbação era cem vezes menor que a decisão mais próxima, e em bfloat16 ela tem o mesmo tamanho. Inferência em produção roda em 16 bits, em hardware com kernels fundidos e ordens de redução que ninguém promete manter. Se "o ruído numérico é desprezível" é uma pergunta sobre precisão e hardware, não sobre o modelo.
Então, as quatro causas, catalogadas como prometeu o Capítulo 9:
A adição em ponto flutuante não é associativa
Link para a seção: A adição em ponto flutuante não é associativaA caixa do Capítulo 2. A ordem de uma soma muda seu valor, então qualquer mudança em como uma redução é dividida muda os logits. Este é o substrato; as outras três são formas de mudar a ordem.
Dynamic batching agrupa sua solicitação com as de estranhos
Link para a seção: Dynamic batching agrupa sua solicitação com as de estranhosContinuous batching, do Capítulo 13, é o motivo pelo qual a inferência é acessível — e significa que o formato das matrizes pelas quais seus tokens fluem depende do tráfego. Medido acima: 147.321 logits se moveram porque o tamanho do batch mudou.
Mixture-of-experts routing depende do batch
Link para a seção: Mixture-of-experts routing depende do batchA caixa do Capítulo 9 já disse isso. O router faz uma escolha discreta por token por camada, sujeita a limites de capacidade por expert calculados sobre o batch. Um token que sozinho teria ido para o expert 7 vai para o expert 12 acompanhado. Isso não é diferença de arredondamento; é um conjunto diferente de pesos.
O modelo por trás do nome muda
Link para a seção: O modelo por trás do nome mudaUma string de versão como -latest é um ponteiro, e ponteiros são redirecionados. Provedores também atualizam a pilha de serving sob um identificador de versão fixo. Nenhum dos dois é anunciado na granularidade que permitiria correlacionar isso com a mudança da sua própria saída.
O parâmetro seed da OpenAI é honesto sobre isso da única forma possível: ele vem junto com um campo system_fingerprint que identifica a configuração de backend, e a documentação afirma que o determinismo é best-effort e que uma fingerprint alterada significa que os resultados podem diferir. Leia isso como o que é — um provedor dizendo que controla as quatro causas acima, que você não controla nenhuma delas, e que a única coisa que ele pode oferecer é dizer depois do fato que algo se moveu.
Para onde isso vai agora
Link para a seção: Para onde isso vai agoraTudo aqui foi sobre um botão e suas consequências. Dê um passo para trás e o problema mais difícil aparece: o objeto que estivemos ajustando é uma distribuição de probabilidade, e distribuições de probabilidade não têm uma interface.
Uma function call tem. Uma linha de banco de dados tem. Um handler POST que espera um corpo JSON com três campos obrigatórios tem uma, e rejeitará qualquer outra coisa. Entre o modelo e todo outro componente do seu sistema existe um contrato sobre o qual um dos lados não consegue fazer promessas: o modelo produzirá algo, extraído de uma distribuição que você moldou, mas não fixou, e o código do outro lado precisa de um valor de tipo conhecido ou lança erro.
A ponte entre esses dois mundos é construída com o material deste capítulo, não com parsing e retries. Se um token quebraria a estrutura exigida, você não o amostra torcendo para dar certo — você define seu logit como antes que a softmax sequer o veja. Constrained decoding é uma máscara sobre o mesmo vetor que passamos este capítulo remodelando, e transforma "responda em JSON, por favor" de uma solicitação em uma garantia.
O Capítulo 18 é esse contrato: tool calling, JSON Schema, saídas estruturadas, e o que é necessário para tornar um sistema determinístico seguro para construir sobre um probabilístico.
Fontes e método
Link para a seção: Fontes e métodoTodas as medições deste capítulo vêm de Qwen/Qwen2.5-0.5B-Instruct em CPU, float32 salvo indicação contrária, com a amostragem implementada como escrito na seção opcional, não delegada a uma biblioteca. Elas vêm de um modelo pequeno, e os valores específicos são dele; os mecanismos não. How to generate text with different decoding methods, de Von Platen (Hugging Face, 2020), é o artigo contra o qual este é medido e continua sendo a melhor introdução curta ao mesmo material. Para a seção de determinismo: as notas de reprodutibilidade do PyTorch descrevem o que um seed fixa e não fixa em uma única máquina, a documentação da OpenAI sobre seed e system_fingerprint descreve o que um provedor pode e não pode prometer, e a discussão de 2025 da Thinking Machines sobre kernels invariantes a batch é o relato público mais claro de por que corrigir isso no nível do servidor de inferência é possível, mas não gratuito.
Referências
Link para a seção: Referências-
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 em uma softmax vem da física estatística. Hinton, G., Vinyals, O. e Dean, J., Distilling the Knowledge in a Neural Network, arXiv:1503.02531 (2015), seção 2, é onde o mesmo parâmetro reaparece em deep learning moderno — como uma forma de expor a distribuição completa de um professor, que são os soft labels do Capítulo 13, não a amostragem deste capítulo. ↩
-
Guo, C., Pleiss, G., Sun, Y. e Weinberger, K. Q. On Calibration of Modern Neural Networks. arXiv:1706.04599 (2017). Não confunda isso com a temperatura deste capítulo. Temperature scaling ajusta um único valor em um conjunto de validação para que a confiança do modelo corresponda à sua precisão; é um método de calibração post-hoc aplicado às saídas de um classificador. Temperature sampling é um controle em tempo de execução sobre como um gerador escolhe tokens. Mesma fórmula, finalidade diferente, e nenhum valor compartilhado. ↩
-
Holtzman, A., Buys, J., Du, L., Forbes, M. e Choi, Y. The Curious Case of Neural Text Degeneration. arXiv:1904.09751 (2019). Introduz nucleus sampling e a medição de que decoding baseado em maximização produz texto cujo perfil de probabilidade não se parece em nada com texto humano. ↩ ↩2
-
Fan, A., Lewis, M. e Dauphin, Y. Hierarchical Neural Story Generation. arXiv:1805.04833 (2018). O artigo que popularizou a amostragem top-k. ↩
-
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 seção 4.1 é a repetition penalty original — a que divide. ↩