Température, top-p et le déterminisme que vous n’avez pas
La température divise les logits avant la softmax : fini le mythe du cadran de créativité. Même appel greedy, deux réponses.
Dans cet article
Voici la même requête envoyée cinq fois au même modèle. Mêmes poids, même prompt, même machine, même graine aléatoire. La seule chose qui change est un nombre.
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 ('$ספטמבר..."Rien n’a cassé. Chaque token de la dernière ligne a été tiré légitimement depuis la distribution de probabilité du modèle lui-même sur son vocabulaire de 151 936 entrées. Le nombre qui a changé s’appelle la température ; la plupart des documentations le décrivent comme un cadran de créativité, et cette description est fausse d’une manière que ce chapitre peut démontrer plutôt qu’affirmer.
C’est aussi le chapitre où trois promesses précédentes arrivent à échéance. Le chapitre 4 a défini le logit sans vraiment s’en servir. L’encadré sur les nombres à virgule flottante du chapitre 2 se terminait par une consigne — souvenez-vous-en lorsque le chapitre 17 demandera pourquoi le même prompt, le même modèle et la même graine peuvent produire des tokens différents. Et l’encadré sur les mixture-of-experts du chapitre 9 promettait un catalogue de quatre causes de non-déterminisme. Les trois arrivent ci-dessous.
La ligne dont dépend tout le chapitre
Lien vers la section : La ligne dont dépend tout le chapitreLe chapitre 4 a présenté le logit comme un score réel non normalisé, un par classe. Le chapitre 8 a fait produire à un modèle de langage un logit par entrée du vocabulaire. La softmax transforme ce vecteur en probabilités :
La température intervient ici — le nom vient de la physique statistique, où le même paramètre contrôle la concentration plus ou moins forte d’une distribution de Boltzmann sur ses états de basse énergie1 — et elle divise les logits avant l’exponentielle :
Cet emplacement est tout le mécanisme, et deux lignes d’algèbre suffisent à comprendre pourquoi il ne pouvait pas être ailleurs. Supposons que vous essayiez d’appliquer la température aux probabilités à la place — les multiplier par puis renormaliser. Vous obtiendriez
La constante s’annule. Mettre les probabilités à l’échelle ne change absolument rien ; la distribution revient inchangée. La température n’a d’effet que parce qu’elle agit sur l’exposant, où diviser par avant l’exponentiation revient à élever chaque probabilité à la puissance — un remodelage non linéaire qui change les rapports entre les entrées plutôt que leur échelle commune.
À partir de cet emplacement, les deux limites suivent sans travail supplémentaire. Quand , le plus grand logit s’échappe du reste et s’effondre sur le token ayant le score le plus élevé : greedy decoding. Quand augmente, chaque tend vers zéro, chaque exponentielle tend vers 1, et la distribution s’aplatit vers une distribution uniforme sur tout le vocabulaire. Exactement à , la formule divise par zéro ; toutes les implémentations en font donc un cas particulier et utilisent le maximum arithmétique — y compris le widget ci-dessous, qui bascule vers argmax à .
Un avertissement, car la collision des noms crée une vraie confusion. Il existe en machine learning une deuxième notion sans rapport appelée température : temperature scaling, une méthode de calibration qui ajuste une valeur sur un jeu de validation pour que la confiance d’un classifieur corresponde à sa précision.2 Même formule, rien à voir avec la génération. Quand des articles disent « température », ils parlent souvent de celle-là ; ce chapitre, jamais.
Voici cette distribution, avec le calcul sous vos yeux. Les logits sont fixes et plausibles, donc les nombres du texte ci-dessous peuvent être vérifiés par rapport à ce que vous voyez :
La température n’est pas un cadran de créativité
Lien vers la section : La température n’est pas un cadran de créativitéLe nombre ␣banana résume tout l’argument : augmenter la température ne peut pas donner à un modèle une idée qu’il n’avait pas. Les logits sont déjà calculés, le classement est déjà fixé, et la température le préserve exactement — aucune quantité de chaleur ne fera jamais passer un token moins bien noté au-dessus d’un token mieux noté. Elle ne fait que redistribuer la masse vers le bas du classement que le modèle a lui-même produit. Une température élevée ne rend pas un modèle plus inventif ; elle augmente la probabilité qu’il émette les tokens qu’il a jugés mauvais.
Sur un vrai vocabulaire, cela cesse d’être une curiosité et devient la raison pour laquelle les sorties à haute température sont inutilisables. Mesuré sur Qwen/Qwen2.5-0.5B-Instruct, une forward pass, le prompt ci-dessus, en comptant le nombre de tokens nécessaires pour accumuler une part donnée de la masse de probabilité :
| température | probabilité top-1 | entropie | tokens portant 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 |
Lisez lentement la dernière ligne. À , sur une question qui a exactement une bonne réponse, 32 966 tokens différents se partagent les 90 % supérieurs de la masse de probabilité. Ce n’est pas un espace créatif plus vaste. C’est un modèle auquel l’arithmétique a demandé de traiter une particule coréenne et un identifiant C++ comme des options actives pour le mot après A:. Les déchets du bloc d’ouverture en sont la conséquence directe, et ce n’est pas un bug du modèle ni de la bibliothèque — c’est ce que la requête demandait.
La plage utile est étroite et dépend de la tâche plutôt que du goût. Sur une question factuelle, la réponse est un token, et toute chaleur au-dessus d’environ 1,2 injecte de l’erreur pour rien. Sur une question ouverte, il existe vraiment plusieurs bonnes continuations, et un peu de chaleur apporte une variété qui reste fluide :
"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..."À 1,3, le modèle a inventé un nom propre et une phrase qui ne se parse pas. La bande entre « identique à chaque fois » et « incohérent » est à peu près de 0,6 à 1,1 pour ce modèle sur cette tâche, et le conseil honnête est de la trouver en mesurant sur votre tâche, pas en copiant un nombre depuis un article de blog.
Pourquoi le texte le plus probable est un mauvais texte
Lien vers la section : Pourquoi le texte le plus probable est un mauvais texteUne question évidente se cache sous tout cela : si le modèle possède une distribution de probabilité et qu’un token est le plus probable, pourquoi ne pas toujours le prendre ? Le greedy decoding est gratuit, reproductible et ne demande aucun paramètre.
Parce que le résultat est ceci :
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 %Huit phrases, une phrase. Près de neuf fenêtres de quatre tokens sur dix étaient déjà apparues plus tôt dans la même sortie. C’est la dégénérescence du texte neuronal, nommée et expliquée par Holtzman et al. dans l’article qui a introduit top-p.3 Le modèle n’est pas cassé ; maximiser la probabilité de séquence est simplement le mauvais objectif pour du texte ouvert. L’écriture humaine n’est pas la suite de mots la plus probable — elle contient de la surprise, sa probabilité par token erre, plonge et remonte — tandis que le chemin de probabilité maximale est un point fixe qui, une fois atteint, n’a aucune raison d’en sortir.
C’est pour cela que l’échantillonnage existe. Et c’est aussi — la partie qu’on oublie souvent — pas une loi universelle. Le chapitre 12 a mesuré 24 réponses correctes sur 24 à des problèmes de mots en deux étapes avec un simple greedy decoding, et l’échantillonnage à température 0,8 a fait tomber ce résultat à 81 % ; la self-consistency a ensuite dépensé six fois plus de tokens pour revenir là où le greedy était déjà. Les deux faits sont vrais en même temps :
Génération ouverte. Il n’existe pas de continuation unique correcte, donc la plus probable est un piège — elle boucle, et 87,6 % de celle-ci est copiée depuis elle-même. Échantillonnez.
Tâches avec une seule bonne réponse. Il existe une continuation unique correcte, donc tirer autre chose revient à tirer une erreur. Le 100 % du chapitre 12 est devenu 81 % exactement pour cette raison. N’échantillonnez pas.
La plupart des prompts de production sont du deuxième type et sont configurés comme le premier, parce que la température a été laissée à la valeur utilisée par l’exemple de code.
Deux façons de couper, dont une seule s’adapte
Lien vers la section : Deux façons de couper, dont une seule s’adapteÉchantillonner depuis la distribution complète n’est pas ce que qui que ce soit fait réellement, car la queue est immense et pleine de non-sens. Il faut couper quelque chose. Il existe deux réponses classiques, et elles diffèrent sur un point qui décide de tout.
Top-k conserve un nombre fixe de candidats. Triez par probabilité, gardez les premiers, écartez le reste, renormalisez.4 Top-p, aussi appelé nucleus sampling, conserve une quantité fixe de masse : prenez les tokens par ordre décroissant jusqu’à ce que leur probabilité cumulée atteigne , puis arrêtez.3 Formellement, le noyau est le plus petit ensemble tel que
La différence paraît cosmétique, mais ne l’est pas, parce que les deux prompts que vous envoyez dans la même minute ont des formes de distribution complètement différentes. Les deux utilisent le même modèle à température 1 :
Q: What is the capital of France?\nA: | Once upon a time, | |
|---|---|---|
| probabilité top-1 | 96,01 % | 25,39 % |
| tokens portant 90 % de la masse | 1 | 467 |
| top-k = 40 conserve | 99,61 % de la masse | 78,87 % de la masse |
| masse dans les rangs 2 à 40 | 3,61 % | 53,48 % |
| token au rang 40 | ␣Av, 0,0093 % | ␣Dr, 0,128 % |
Un fixe, deux échecs en sens opposés. Sur le prompt factuel, admet 39 tokens qui valent ensemble 3,6 % — il laisse passer des déchets, y compris un candidat à neuf millièmes de pour cent, parce que la règle compte des emplacements et non des preuves. Sur le prompt d’histoire, le même jette 21 % de la masse que le modèle avait réellement attribuée, parce que le vrai noyau y mesure 467 tokens de large.
Top-p fait accomplir les deux tâches à un seul nombre. Définissez et il conserve 1 token sur le premier prompt et 467 sur le second, parce qu’il pose une question sur la distribution au lieu de lui imposer un nombre. Observez directement cette adaptation — même coupe, quatre températures :
Ce widget règle aussi un malentendu qu’il faut nommer, parce qu’il coûte réellement de l’argent. Sur une distribution confiante, top_p = 0.9 n’est pas « un peu de variété ». C’est du greedy. À température 1, le token de tête détient ici 96,90 %, déjà au-dessus de 0,9 ; le noyau ne fait donc qu’un token de large et rien d’autre ne peut jamais être tiré. Des équipes règlent top_p à 0,9 en pensant avoir desserré quelque chose, puis se demandent pourquoi chaque réponse est identique.
Réglez top-k à la place, et l’échec opposé est tout aussi visible :
Les pénalités, avec les formules, parce que les confondre est endémique
Lien vers la section : Les pénalités, avec les formules, parce que les confondre est endémiqueTrois mécanismes différents circulent sous des noms similaires, ils font des choses différentes, et la différence se mesure. Soit le nombre de fois où le token est déjà apparu.
Presence penalty
Lien vers la section : Presence penaltySoustrait une constante à tout token déjà apparu au moins une fois. Apparaître une fois et apparaître quarante fois sont pénalisés de manière identique. C’est un interrupteur, pas un cadran.
Frequency penalty
Lien vers la section : Frequency penaltySoustrait proportionnellement au compte. Un token utilisé quatre fois est pénalisé quatre fois plus qu’un token utilisé une fois, et la pression se compose à mesure que le texte grandit.
Repetition penalty (CTRL)
Lien vers la section : Repetition penalty (CTRL)L’originale, issue de l’article CTRL.7 Elle divise au lieu de soustraire, avec le cas du signe nécessaire parce que diviser un logit négatif le rendrait plus grand. Sa force dépend donc de la magnitude du logit, ce qui signifie que le même frappe différemment à différents endroits d’une même phrase.
La même continuation dégénérée que plus haut, avec chacune de ces pénalités appliquée. « Étapes modifiées » compte combien des 120 étapes de génération ont choisi un token différent de celui que le modèle non pénalisé aurait choisi. L’exécution fait ici 120 étapes contre 140 dans le bloc ci-dessus, ce qui explique pourquoi la baseline non pénalisée indique 85,5 % plutôt que 87,6 % :
| réglage | 4-grams répétés | étapes modifiées |
|---|---|---|
| rien | 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 |
Trois choses en découlent. Presence à 0,5 a changé trois décisions sur 120 et réduit la répétition d’un quart — la boucle tenait grâce à une poignée de tokens. Frequency à 0,5 a changé quatre fois plus de décisions pour un effet beaucoup plus grand, parce que le multiplicateur du compte continue de croître alors que la constante de presence ne le fait pas. Et la pénalité CTRL à la valeur largement copiée de 1,2 a réécrit 35 décisions sur 120, ce qui n’est pas un coup de pouce ; c’est un autre modèle.
Ce dernier nombre prépare l’échec dont personne ne vous avertit.
Ce que les pénalités font au texte censé se répéter
Lien vers la section : Ce que les pénalités font au texte censé se répéterLe code répète. Les tableaux répètent. Les listes répètent. Les sorties structurées répètent par définition — c’est cela, la structure. Une pénalité ne sait pas faire la différence entre un modèle coincé dans une boucle et un modèle qui émet correctement la quatrième ligne d’un tableau, parce que les deux ressemblent à un token qui réapparaît.
Les trois mêmes tâches, générées de trois façons :
| tâche | rien | frequency 0,5 | repetition 1,2 |
|---|---|---|---|
| tableau markdown, 6 lignes | 0 / 56 étapes modifiées | 0 / 56 | 2 / 62 |
| fonction Python | 0 / 93 | 0 / 93 | 10 / 110 |
| liste à puces, 1 à 12 | 0 / 50 | 0 / 50 | 0 / 50 |
La frequency penalty à 0,5 s’est révélée inoffensive sur les trois, ce qui est un résultat utile et légèrement surprenant, et qui dit quelque chose de précis : puisqu’aucune décision n’a changé, les tokens structurels devaient gagner leurs positions avec une marge supérieure à la pénalité soustraite, même après être apparus cinq ou six fois. La pénalité CTRL, qui divise à la place, les déloge bel et bien, et voici ce qu’elle a produit :
repetition 1.2, markdown table:
| n | 2^n |
| --- | --- |
| 0 | 1 |
| 1 | 2 |
| 2 | 4 |L’alignement s’effondre : la quantité de remplissage à l’intérieur de chaque cellule change d’une ligne à l’autre, parce que la séquence d’espaces avant la barre verticale fermante est exactement le type de répétition que la pénalité est conçue pour casser. Cosmétique, et cela a coûté six tokens supplémentaires. Le cas Python n’est pas cosmétique :
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 pénalité a poussé le modèle hors de total — déjà utilisé dans la docstring — vers total_sum, a rembourré la sortie avec des commentaires inventés pour dépenser son budget sur des tokens inutilisés, puis l’a conduit dans un range à trois arguments avec un pas. Le commentaire dit incrementing by 2 each time, ce qui est faux pour une somme de carrés de 1 à . Une repetition penalty a produit du code incorrect à partir d’un prompt auquel il avait été correctement répondu sans elle.
La règle qui suit est courte : les pénalités sont faites pour de la prose ouverte, et elles doivent être désactivées pour le code, les sorties structurées, les données tabulaires et tout ce qui a un schéma. Le chapitre 18 porte exactement sur cette seconde catégorie.
L’ordre d’application, et pourquoi il change la réponse
Lien vers la section : L’ordre d’application, et pourquoi il change la réponseToute vraie implémentation les applique dans une séquence précise :
pénalités → température → top-k → top-p → échantillonnage
Ce n’est pas de la comptabilité arbitraire, et inverser deux étapes produit réellement des distributions différentes. Deux mesures, toutes deux sur le prompt factuel.
Couper avant ou après la température. Le noyau est calculé sur la distribution qu’on lui donne, et la température change radicalement cette distribution :
| top-p 0,9 après température | top-p 0,9 avant température | |
|---|---|---|
| 1 token | 1 token | |
| 353 tokens | 1 token | |
| 32 966 tokens | 1 token |
À , le même réglage nominal donne un ensemble de candidats de 32 966 ou de 1, uniquement selon l’étape qui s’exécute en premier. Si vous vous êtes déjà demandé pourquoi augmenter la température « ne fait rien » chez un fournisseur et détruit la sortie chez un autre avec les deux mêmes nombres, ce tableau est une réponse plausible.
Pénaliser avant ou après la température. Soustraire une pénalité puis diviser par donne une pénalité effective de ; diviser d’abord puis soustraire donne . Avec une presence penalty de 1,0 appliquée au token de tête :
| température | pénaliser, puis tempérer | tempérer, puis pénaliser |
|---|---|---|
| 0,5 | 99,858 % | 99,948 % |
| 1,0 | 89,839 % | 89,839 % |
| 2,0 | 10,783 % | 6,830 % |
Identiques à , comme elles doivent l’être. Écartées d’un facteur 1,58 à . « Presence penalty 1.0 » n’est pas une quantité de pénalité bien définie à moins de savoir aussi où la température est appliquée, et aucune API ne documente cela.
Afficher les détails
Optionnel : tout le pipeline, dans l’ordre ci-dessus.
Seize lignes, et tout ce chapitre y tient. C’est le même calcul que celui effectué par le widget, sur un vrai vecteur de logits plutôt que sur dix nombres fixes.
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)])Le cumsum(0) - p dans la ligne top-p est la masse cumulée hors token courant, ce qui fait que le noyau inclut le token qui franchit le seuil au lieu de s’arrêter juste avant. Décalez cela d’une unité et top_p = 0.9 devient silencieusement une coupe légèrement plus serrée que toutes les autres implémentations.
C’est l’un des rares endroits dans la seconde moitié du cours où Python est le bon langage, et la raison est structurelle plutôt que stylistique : chaque ligne ci-dessus exige d’avoir le vecteur complet de logits entre les mains, et via une API HTTP, ce vecteur n’existe pas. Vous pouvez envoyer temperature et top_p à un fournisseur ; vous ne pouvez pas les implémenter, et vous ne pouvez pas voir ce qu’ils ont fait.
Il n’existe pas d’API d’échantillonnage universelle
Lien vers la section : Il n’existe pas d’API d’échantillonnage universelleChaque fournisseur accepte un sous-ensemble différent de ces contrôles, avec des plages différentes, et ignore silencieusement le reste. Ce n’est pas une plainte abstraite. Toute application qui propose un choix de modèle doit noter les différences quelque part, et le fichier où elle le fait est une carte de l’incompatibilité. Voici ce que déclare l’un de ces catalogues pour un seul paramètre sur les neuf sources de texte qu’il prend en charge :
| plage de température déclarée | sources |
|---|---|
| 0 à 1 | Anthropic, Google, Meta, Cerebras, PaLM |
| 0 à 1,5 | Mistral |
| 0 à 2 | OpenAI, DeepSeek, xAI |
Le mot est le même ; l’échelle ne l’est pas. Une « température de 1 » est la distribution non modifiée chez l’un et la chaleur maximale autorisée chez l’autre, et la moitié du catalogue ne peut pas exprimer la valeur que l’autre moitié traite comme neutre-plus-un-peu. Les autres boutons sont tout aussi inégaux : les entrées OpenAI, DeepSeek et xAI acceptent des pénalités presence et frequency mais pas topK ; les entrées Google, Meta, Cerebras et PaLM acceptent topK et aucune pénalité ; Anthropic accepte topK, topP et des séquences d’arrêt, mais aucune pénalité ; et exactement une des neuf — Mistral — accepte une graine. Envoyer un paramètre qu’un fournisseur n’implémente pas ne produit généralement aucune erreur : la requête réussit, le bouton ne fait rien, et vous concluez que le réglage n’a aucun effet.
Et remarquez ce qu’est un tel fichier : une affirmation sur l’API de quelqu’un d’autre, écrite un jour précis, que rien ne vérifie ensuite. Un catalogue qui indique 0 à 1 pour un fournisseur acceptant désormais 0 à 2 plafonnera silencieusement chaque requête.
Deux autres contrôles appartiennent à la même famille. logprobs, lorsqu’il est proposé, renvoie les log-probabilités du token choisi et souvent les quelques meilleures alternatives — la seule fenêtre que vous avez sur la distribution dont parle ce chapitre, et la base de toutes les heuristiques de confiance construites sur un modèle fermé. Et les maximum tokens plus les séquences d’arrêt terminent la génération sans aucune référence à la probabilité : une limite dure et une correspondance de chaîne. Les deux apparaissent comme le finish_reason du chapitre 14, où length signifie que votre réponse a été coupée au milieu d’une phrase par un budget, pas terminée par le modèle.
La graine, et le déterminisme que vous n’avez pas
Lien vers la section : La graine, et le déterminisme que vous n’avez pasDéfinissez une graine et l’échantillonnage devient reproductible. Cette partie est réelle, et facile à vérifier :
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\"."Identiques au byte près pour une même graine, différents d’une graine à l’autre, exactement comme annoncé. Ce que la graine fixe, donc, c’est le tirage aléatoire dans la dernière ligne de cette fonction sample — le token choisi étant donnée une distribution.
Ce qu’elle ne fixe pas, c’est la distribution. Et c’est là que les problèmes commencent, parce que le vecteur de logits produit par votre modèle n’est pas un objet mathématique ; c’est le résultat de milliards d’additions en virgule flottante, et ces additions ont un ordre.
Le chapitre 2 avait préparé cette expérience. Le même million de nombres float32, sommés selon des groupements différents :
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? FalseRegardez la dernière ligne. Le nombre de morceaux change la réponse. Ce n’est pas une curiosité à propos de numpy ; c’est le mécanisme, parce que lorsqu’un serveur d’inférence répartit une réduction sur plus ou moins d’unités parallèles, il fait précisément cela. Et un serveur répartit selon le nombre de requêtes qu’il est en train de servir.
Voici cet effet sur le modèle lui-même. Le même prompt, la même forward pass, la seule différence étant le nombre d’autres requêtes qui se trouvaient dans le 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-05Exécuté seul, le modèle est parfaitement déterministe — vingt passages, identiques au bit près. Placez le prompt identique dans un batch avec des requêtes sans rapport et 97 % de ses logits changent. Rien dans votre requête n’a changé. La requête de quelqu’un d’autre est arrivée.
Maintenant, la partie honnête, parce que cela est souvent raconté comme si c’était la fin de l’histoire. Un changement de ne modifie la sortie que si deux tokens candidats étaient à cette distance l’un de l’autre. Sur 717 étapes de génération réparties sur douze prompts, le plus petit écart entre les deux premiers logits était — cent fois plus grand que la perturbation — et aucune étape n’était assez proche pour basculer. Donc sur ce modèle, en float32, sur un ordinateur portable, le batching a déplacé chaque logit et n’a changé aucun token.
C’est la description de conditions favorables, pas une garantie rassurante, et un seul changement de ces conditions suffit :
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."Six réponses sur huit divergent, et l’une se dégrade fortement. Le tableau du chapitre 2 explique pourquoi : bfloat16 conserve 7 bits de mantisse, donc près d’une magnitude de logit de 16, les valeurs représentables sont espacées de 0,125 — 16,0, puis 16,125, puis 16,25 — et l’arrondi peut déplacer un logit jusqu’à 0,0625. Pendant ce temps, 4,7 % des étapes de génération mesurées ci-dessus avaient un écart top-two inférieur à 0,1. Toute la différence entre les deux expériences est là : en float32, la perturbation était cent fois plus petite que la décision la plus proche, et en bfloat16, elle est de même ordre. L’inférence de production s’exécute en 16-bit, sur du matériel avec des kernels fusionnés et des ordres de réduction que personne ne promet de conserver. Savoir si « le bruit numérique est négligeable » est une question de précision et de matériel, pas de modèle.
Donc, les quatre causes, cataloguées comme promis au chapitre 9 :
L’addition en virgule flottante n’est pas associative
Lien vers la section : L’addition en virgule flottante n’est pas associativeL’encadré du chapitre 2. L’ordre d’une somme change sa valeur, donc tout changement dans la façon dont une réduction est découpée change les logits. C’est le substrat ; les trois autres sont des façons de changer l’ordre.
Le batching dynamique regroupe votre requête avec celles d’inconnus
Lien vers la section : Le batching dynamique regroupe votre requête avec celles d’inconnusLe continuous batching, vu au chapitre 13, est ce qui rend l’inférence abordable — et il signifie que la forme des matrices traversées par vos tokens dépend du trafic. Mesuré ci-dessus : 147 321 logits ont bougé parce que la taille du batch a changé.
Le routage mixture-of-experts dépend du batch
Lien vers la section : Le routage mixture-of-experts dépend du batchL’encadré du chapitre 9 le disait déjà. Le router fait un choix discret par token et par couche, soumis à des limites de capacité par expert calculées sur le batch. Un token qui serait allé seul vers l’expert 7 va vers l’expert 12 en compagnie. Ce n’est pas une différence d’arrondi ; c’est un ensemble de poids différent.
Le modèle derrière le nom change
Lien vers la section : Le modèle derrière le nom changeUne chaîne de version comme -latest est un pointeur, et les pointeurs sont repointés. Les fournisseurs mettent aussi à jour la pile de service sous un identifiant de version fixe. Aucun de ces changements n’est annoncé à une granularité qui vous permettrait de le corréler à vos propres sorties qui changent.
Le paramètre seed d’OpenAI est honnête à ce sujet de la seule manière possible : il arrive avec un champ system_fingerprint qui identifie la configuration backend, et la documentation indique que le déterminisme est best-effort et qu’un fingerprint modifié signifie que les résultats peuvent différer. Lisez cela pour ce que c’est — un fournisseur qui vous dit qu’il contrôle les quatre causes ci-dessus, que vous n’en contrôlez aucune, et que la seule chose qu’il peut offrir est de vous dire après coup que quelque chose a bougé.
Où cela mène ensuite
Lien vers la section : Où cela mène ensuiteTout ce qui précède portait sur un bouton et ses conséquences. Reculez d’un niveau et le problème plus difficile apparaît : l’objet que nous réglions est une distribution de probabilité, et les distributions de probabilité n’ont pas d’interface.
Un function call en a une. Une ligne de base de données en a une. Un handler POST attendant un corps JSON avec trois champs obligatoires en a une, et il rejettera tout le reste. Entre le modèle et chaque autre composant de votre système se trouve un contrat dont l’une des parties ne peut pas faire de promesses : le modèle produira quelque chose, tiré d’une distribution que vous avez façonnée mais pas fixée, et le code de l’autre côté a besoin d’une valeur d’un type connu, sinon il lève une erreur.
Le pont entre ces deux mondes se construit avec la matière de ce chapitre plutôt qu’avec du parsing et des retries. Si un token casserait la structure requise, vous ne l’échantillonnez pas en espérant — vous réglez son logit à avant même que la softmax ne le voie. Le constrained decoding est un masque sur le même vecteur que nous avons passé ce chapitre à remodeler, et il transforme « veuillez répondre en JSON » d’une requête en garantie.
Le chapitre 18 est ce contrat : tool calling, JSON Schema, sorties structurées, et ce qu’il faut pour rendre un système déterministe sûr à construire au-dessus d’un système probabiliste.
Sources et méthode
Lien vers la section : Sources et méthodeToutes les mesures de ce chapitre viennent de Qwen/Qwen2.5-0.5B-Instruct sur CPU, en float32 sauf indication contraire, avec l’échantillonnage implémenté comme écrit dans la section optionnelle plutôt que délégué à une bibliothèque. C’est un petit modèle, et les valeurs spécifiques sont les siennes ; les mécanismes, non. How to generate text with different decoding methods de Von Platen (Hugging Face, 2020) est l’article par rapport auquel celui-ci est mesuré, et reste la meilleure courte introduction au même sujet. Pour la section sur le déterminisme : les notes de reproductibilité de PyTorch décrivent ce qu’une graine fixe et ne fixe pas sur une seule machine, la documentation OpenAI de seed et system_fingerprint décrit ce qu’un fournisseur peut et ne peut pas promettre, et la discussion de Thinking Machines en 2025 sur les kernels invariants au batch est l’explication publique la plus claire de la raison pour laquelle corriger cela au niveau du serveur d’inférence est possible, mais pas gratuit.
Références
Lien vers la section : Références-
Ackley, D. H., Hinton, G. E. and Sejnowski, T. J. A Learning Algorithm for Boltzmann Machines. Cognitive Science 9(1), pp. 147–169 (1985), où la température dans une softmax vient de la physique statistique. Hinton, G., Vinyals, O. and Dean, J., Distilling the Knowledge in a Neural Network, arXiv:1503.02531 (2015), section 2, est l’endroit où le même paramètre réapparaît dans le deep learning moderne — comme moyen d’exposer toute la distribution d’un enseignant, ce qui correspond aux soft labels du chapitre 13 plutôt qu’à l’échantillonnage de ce chapitre. ↩
-
Guo, C., Pleiss, G., Sun, Y. and Weinberger, K. Q. On Calibration of Modern Neural Networks. arXiv:1706.04599 (2017). Ne confondez pas cela avec la température de ce chapitre. Le temperature scaling ajuste une seule valeur sur un jeu de validation pour que la confiance du modèle corresponde à sa précision ; c’est une méthode de calibration post-hoc appliquée aux sorties d’un classifieur. Le temperature sampling est un contrôle à l’exécution sur la façon dont un générateur tire des tokens. Même formule, objectif différent, et aucune valeur commune. ↩
-
Holtzman, A., Buys, J., Du, L., Forbes, M. and Choi, Y. The Curious Case of Neural Text Degeneration. arXiv:1904.09751 (2019). Introduit le nucleus sampling et la mesure montrant que le décodage fondé sur la maximisation produit un texte dont le profil de probabilité n’a rien à voir avec le texte humain. ↩ ↩2
-
Fan, A., Lewis, M. and Dauphin, Y. Hierarchical Neural Story Generation. arXiv:1805.04833 (2018). L’article qui a popularisé le 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 section 4.1 présente la repetition penalty originale — celle qui divise. ↩