Temperature, top-p en het determinisme dat je niet hebt
Temperature deelt de logits vóór de softmax. Dat haalt het idee van een creativiteitsknop onderuit — zelfs greedy calls kunnen verschillen.
Op deze pagina
Hier is hetzelfde verzoek vijf keer naar hetzelfde model gestuurd. Dezelfde gewichten, dezelfde prompt, dezelfde machine, dezelfde random seed. Het enige dat verandert is één getal.
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 ('$ספטמבר..."Er is niets kapot. Elke token in de laatste regel is legitiem getrokken uit de eigen kansverdeling van het model over zijn vocabulaire van 151.936 items. Het getal dat veranderde heet temperature, wordt in de meeste documentatie beschreven als een creativiteitsknop, en die beschrijving is aantoonbaar verkeerd — dit hoofdstuk laat dat zien in plaats van het alleen te beweren.
Dit is ook het hoofdstuk waarin drie eerdere beloften worden ingelost. Hoofdstuk 4 definieerde de logit en deed er daarna weinig mee. Het floating-point-kader uit hoofdstuk 2 eindigde met een instructie — onthoud dit wanneer hoofdstuk 17 vraagt waarom dezelfde prompt, hetzelfde model en dezelfde seed verschillende tokens kunnen produceren. En het mixture-of-experts-kader uit hoofdstuk 9 beloofde een catalogus van vier oorzaken van non-determinisme. Alle drie komen hieronder terug.
De ene regel waar het hele hoofdstuk aan hangt
Link naar de sectie: De ene regel waar het hele hoofdstuk aan hangtHoofdstuk 4 introduceerde de logit als een ongenormaliseerde reële score, één per klasse. Hoofdstuk 8 liet een taalmodel er één produceren per item in het vocabulaire. De softmax zet die vector om in kansen:
Temperature komt hier binnen — de naam is geleend uit de statistische fysica, waar dezelfde parameter bepaalt hoe scherp een Boltzmann-verdeling zich concentreert op toestanden met lage energie1 — en deelt de logits vóór de exponentiële functie:
Die positie is het hele mechanisme, en twee regels algebra laten zien waarom het nergens anders kan zitten. Stel dat je temperature in plaats daarvan op de kansen probeert toe te passen — ze schalen met en opnieuw normaliseren. Dan krijg je
De constante valt weg. Kansen schalen doet helemaal niets; de verdeling komt onveranderd terug. Temperature heeft alleen effect omdat het op de exponent werkt, waar delen door vóór het exponentiëren hetzelfde is als elke kans verheffen tot de macht — een niet-lineaire hervorming die de verhoudingen tussen items verandert in plaats van hun gezamenlijke schaal.
Vanuit die positie volgen beide limieten zonder verder werk. Als loopt de grootste logit weg van de rest en klapt in tot de ene hoogst scorende token: greedy decoding. Als groeit, gaat elke richting nul, gaat elke exponentiële waarde richting 1, en vlakt de verdeling af naar uniform over het hele vocabulaire. Precies bij deelt de formule door nul, dus elke implementatie behandelt dat als speciaal geval met het rekenkundige maximum — ook de widget hieronder, die bij overschakelt naar argmax.
Eén waarschuwing, omdat de botsing van namen echt verwarring veroorzaakt. Er bestaat in machine learning een tweede, niet-gerelateerd ding dat temperature heet: temperature scaling, een kalibratiemethode die één waarde fit op een validatieset zodat de betrouwbaarheid van een classifier overeenkomt met zijn accuratesse.2 Dezelfde formule, niets met generatie te maken. Papers die “temperature” zeggen bedoelen vaak die versie; dit hoofdstuk nooit.
Hier is die verdeling, met de rekenstappen voor je. De logits zijn vast en plausibel, dus de getallen in de tekst hieronder kun je controleren aan wat je ziet:
Temperature is geen creativiteitsknop
Link naar de sectie: Temperature is geen creativiteitsknopHet getal ␣banana is het hele argument in het klein: temperature verhogen kan een model geen idee geven dat het niet had. De logits zijn al berekend, de rangorde staat al vast, en temperature bewaart die exact — geen enkele hoeveelheid hitte zet ooit een lager gescoorde token boven een hoger gescoorde token. Het enige wat gebeurt is massa herverdelen naar beneden in de rangorde die het model zelf produceerde. Hoge temperature maakt een model niet inventiever; het maakt waarschijnlijker dat het tokens uitstoot die het zelf als slecht scoorde.
Op een echt vocabulaire houdt dit op een curiositeit te zijn en wordt het de reden waarom output met hoge temperature onbruikbaar is. Gemeten op Qwen/Qwen2.5-0.5B-Instruct, één forward pass, met de prompt hierboven, door te tellen hoeveel tokens nodig zijn om een bepaald aandeel van de kansmassa te verzamelen:
| temperature | top-1-kans | entropy | tokens met 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 |
Lees de onderste rij langzaam. Bij , op een vraag met precies één correct antwoord, delen 32.966 verschillende tokens de bovenste 90 % van de kansmassa. Dat is geen ruimere creatieve ruimte. Dat is een model dat door rekenkunde te horen heeft gekregen dat een Koreaanse partikel en een C++-identifier levende opties zijn voor het woord na A:. De rommel in het openingsblok is het directe gevolg, en het is geen bug in het model of de library — het is waar het verzoek om vroeg.
Het bruikbare bereik is smal en hangt af van de taak, niet van smaak. Bij een feitelijke vraag is het antwoord één token en elke hitte boven ongeveer 1,2 injecteert fout zonder opbrengst. Bij een open vraag is er echt meer dan één goed vervolg, en wat hitte koopt variatie die vloeiend blijft:
"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..."Bij 1,3 heeft het model een eigennaam verzonnen en een zin gemaakt die syntactisch niet klopt. De band tussen “elke keer identiek” en “incoherent” ligt voor dit model op deze taak grofweg tussen 0,6 en 1,1, en het eerlijke advies is dat je die vindt door te meten op jouw taak, niet door een getal uit een blogpost te kopiëren.
Waarom de meest waarschijnlijke tekst slechte tekst is
Link naar de sectie: Waarom de meest waarschijnlijke tekst slechte tekst isOnder dit alles zit een voor de hand liggende vraag: als het model een kansverdeling heeft en één token het waarschijnlijkst is, waarom neem je die dan niet altijd? Greedy decoding is gratis, reproduceerbaar en heeft geen parameters nodig.
Omdat het resultaat dit is:
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 %Acht zinnen, één zin. Bijna negen van de tien vensters van vier tokens waren eerder al in dezelfde output verschenen. Dit is neural text degeneration, benoemd en uitgelegd door Holtzman et al. in de paper die top-p introduceerde.3 Het model is niet kapot; sequentiekans maximaliseren is simpelweg de verkeerde doelstelling voor open tekst. Menselijk schrijven is niet de meest waarschijnlijke reeks woorden — het bevat verrassing, met per-token-kans die zwerft, daalt en herstelt — terwijl het pad met maximale kans een vast punt is dat, zodra het is betreden, geen reden heeft om te vertrekken.
Daarom bestaat sampling überhaupt. Het is ook — en dit deel wordt vaak weggelaten — geen universele wet. Hoofdstuk 12 mat 24 van de 24 correct op woordproblemen met twee stappen met gewone greedy decoding, en sampling bij temperature 0,8 liet dat dalen naar 81 %; self-consistency besteedde daarna zes keer zoveel tokens om terug te klimmen naar waar greedy al was. Beide feiten zijn tegelijk waar:
Open generatie. Er is geen enkel juist vervolg, dus het meest waarschijnlijke is een val — het loopt vast in een loop, en 87,6 % ervan is van zichzelf gekopieerd. Sample.
Taken met één juist antwoord. Er is één correct vervolg, dus iets anders trekken is een fout trekken. De 100 % uit hoofdstuk 12 werd om precies deze reden 81 %. Niet samplen.
De meeste production prompts zijn van het tweede type en worden geconfigureerd als het eerste, omdat de temperature op de waarde bleef staan die de voorbeeldcode gebruikte.
Twee manieren om te cutten, en slechts één past zich aan
Link naar de sectie: Twee manieren om te cutten, en slechts één past zich aanSampling uit de volledige verdeling is niet wat iemand werkelijk doet, omdat de staart enorm is en vol onzin zit. Er moet iets worden weggeknipt. Er zijn twee klassieke antwoorden en ze verschillen op één punt dat alles bepaalt.
Top-k bewaart een vast aantal kandidaten. Sorteer op kans, bewaar de eerste , gooi de rest weg, normaliseer opnieuw.4 Top-p, ook nucleus sampling genoemd, bewaart een vaste hoeveelheid massa: neem tokens in dalende volgorde totdat hun cumulatieve kans bereikt, en stop.3 Formeel is de nucleus de kleinste set met
Het verschil klinkt cosmetisch, maar is dat niet, omdat de twee prompts die je in dezelfde minuut stuurt totaal verschillende vormen van verdeling hebben. Beide zijn hetzelfde model bij temperature 1:
Q: What is the capital of France?\nA: | Once upon a time, | |
|---|---|---|
| top-1-kans | 96,01 % | 25,39 % |
| tokens met 90 % van de massa | 1 | 467 |
| top-k = 40 bewaart | 99,61 % van de massa | 78,87 % van de massa |
| massa in rangen 2 tot 40 | 3,61 % | 53,48 % |
| token op rang 40 | ␣Av, 0,0093 % | ␣Dr, 0,128 % |
Eén vaste , twee fouten in tegengestelde richtingen. Op de feitelijke prompt laat 39 tokens toe die samen 3,6 % waard zijn — het laat rommel door, waaronder een kandidaat van negen duizendste procent, omdat de regel slots telt en geen bewijs. Op de verhaalprompt gooit dezelfde 21 % van de massa weg die het model oprecht toekende, omdat de echte nucleus daar 467 tokens breed is.
Top-p laat precies één getal beide taken doen. Zet en het bewaart 1 token op de eerste prompt en 467 op de tweede, omdat het een vraag stelt over de verdeling in plaats van er een aantal aan op te leggen. Bekijk die aanpassing direct — dezelfde cut, vier temperatures:
Die widget rekent ook af met een misvatting die het benoemen waard is, omdat ze mensen echt geld kost. Op een zelfverzekerde verdeling is top_p = 0.9 niet “een beetje variatie”. Het is greedy. Bij temperature 1 bevat de leidende token hier 96,90 %, wat al boven 0,9 ligt, dus de nucleus is één token breed en er kan nooit iets anders worden getrokken. Teams zetten top_p op 0,9 in de veronderstelling dat ze iets losser maken en vragen zich daarna af waarom elke response identiek is.
Zet in plaats daarvan top-k en de omgekeerde fout is net zo zichtbaar:
De penalties, met de formules, omdat ze voortdurend worden verward
Link naar de sectie: De penalties, met de formules, omdat ze voortdurend worden verwardDrie verschillende mechanismen reizen onder vergelijkbare namen, ze doen verschillende dingen, en het verschil is meetbaar. Laat het aantal keren zijn dat token al is verschenen.
Presence penalty
Link naar de sectie: Presence penaltyTrek een constante af van elke token die überhaupt is verschenen. Eén keer verschijnen en veertig keer verschijnen worden identiek bestraft. Het is een schakelaar, geen draaiknop.
Frequency penalty
Link naar de sectie: Frequency penaltyTrek af naar verhouding van de telling. Een token die vier keer is gebruikt wordt vier keer zo zwaar bestraft als een token die één keer is gebruikt, en de druk stapelt zich op naarmate de tekst groeit.
Repetition penalty (CTRL)
Link naar de sectie: Repetition penalty (CTRL)De oorspronkelijke, uit de CTRL-paper.7 Die deelt in plaats van af te trekken, met de teken-casus die nodig is omdat delen van een negatieve logit hem groter zou maken. De sterkte hangt daardoor af van de grootte van de logit, wat betekent dat dezelfde op verschillende plekken in dezelfde zin anders uitpakt.
Hetzelfde gedegenereerde vervolg van eerder, met elk mechanisme toegepast. “Gewijzigde stappen” telt hoeveel van de 120 generatiestappen een andere token kozen dan het onbestrafte model zou hebben gedaan. De run is hier 120 stappen tegenover 140 in het blok hierboven, en daarom is de onbestrafte baseline 85,5 % in plaats van 87,6 %:
| instelling | herhaalde 4-grams | gewijzigde stappen |
|---|---|---|
| niets | 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 |
Daar vallen drie dingen uit af te leiden. Presence op 0,5 veranderde drie beslissingen op 120 en verminderde herhaling met een kwart — de loop werd bijeengehouden door een handvol tokens. Frequency op 0,5 veranderde vier keer zoveel beslissingen met een veel groter effect, omdat de telmultiplier blijft groeien terwijl de presence-constante dat niet doet. En de CTRL-penalty op de veel gekopieerde waarde 1,2 herschreef 35 van de 120 beslissingen, wat geen duwtje is; het is een ander model.
Dat laatste getal is de aanloop naar de fout waar niemand voor waarschuwt.
Wat penalties doen met tekst die hoort te herhalen
Link naar de sectie: Wat penalties doen met tekst die hoort te herhalenCode herhaalt. Tabellen herhalen. Lijsten herhalen. Gestructureerde output herhaalt per definitie — dat is wat structuur is. Een penalty kan het verschil niet zien tussen een model dat vastzit in een loop en een model dat correct de vierde rij van een tabel uitstoot, omdat beide eruitzien als een token die opnieuw verschijnt.
Dezelfde drie taken, op drie manieren gegenereerd:
| taak | niets | frequency 0,5 | repetition 1,2 |
|---|---|---|---|
| markdown-tabel, 6 rijen | 0 / 56 stappen gewijzigd | 0 / 56 | 2 / 62 |
| Python-functie | 0 / 93 | 0 / 93 | 10 / 110 |
| lijst met bullets, 1 tot 12 | 0 / 50 | 0 / 50 | 0 / 50 |
De frequency penalty op 0,5 bleek onschadelijk op alle drie, wat een nuttig en licht verrassend resultaat is, en het zegt iets precies: omdat geen beslissing veranderde, moeten de structurele tokens hun posities met meer hebben gewonnen dan de penalty aftrok, zelfs nadat ze vijf en zes keer waren verschenen. De CTRL-penalty, die in plaats daarvan deelt, verdringt ze wel, en dit is wat hij produceerde:
repetition 1.2, markdown table:
| n | 2^n |
| --- | --- |
| 0 | 1 |
| 1 | 2 |
| 2 | 4 |De uitlijning valt uit elkaar: de hoeveelheid padding binnen elke cel verandert van rij tot rij, omdat de reeks spaties vóór de afsluitende pipe precies het soort herhaling is dat de penalty hoort te breken. Cosmetisch, en het kostte zes extra tokens. De Python-casus is niet cosmetisch:
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,De penalty duwde het model weg van total — al gebruikt in de docstring — naar total_sum, vulde de output op met verzonnen comments om zijn budget aan ongebruikte tokens te besteden, en liep daarna een drie-argumenten-range met een stride in. De comment zegt incrementing by 2 each time, wat fout is voor een som van kwadraten van 1 tot . Een repetition penalty produceerde incorrecte code uit een prompt die zonder die penalty correct werd beantwoord.
De regel die volgt is kort: penalties zijn voor open proza, en ze moeten uit staan voor code, gestructureerde output, tabeldata en alles met een schema. Hoofdstuk 18 gaat precies over die tweede categorie.
De volgorde van toepassing, en waarom die het antwoord verandert
Link naar de sectie: De volgorde van toepassing, en waarom die het antwoord verandertElke echte implementatie past dit toe in één specifieke volgorde:
penalties → temperature → top-k → top-p → sample
Dit is geen willekeurige administratie, en twee stappen omwisselen levert echt andere verdelingen op. Twee metingen, beide op de feitelijke prompt.
Cutten vóór of na de temperature. De nucleus wordt berekend op de verdeling die hij krijgt, en temperature verandert die verdeling radicaal:
| top-p 0,9 na temperature | top-p 0,9 vóór temperature | |
|---|---|---|
| 1 token | 1 token | |
| 353 tokens | 1 token | |
| 32.966 tokens | 1 token |
Bij levert dezelfde nominale instelling een kandidaatset van 32.966 of van 1 op, puur afhankelijk van welke stap eerst draait. Als je je ooit hebt afgevraagd waarom temperature verhogen bij de ene provider “niets doet” en bij een andere provider met dezelfde twee getallen de output sloopt, is deze tabel een plausibel antwoord.
Penalizen vóór of na de temperature. Een penalty aftrekken en daarna delen door geeft een effectieve penalty van ; eerst delen en daarna aftrekken geeft . Met een presence penalty van 1,0 toegepast op de leidende token:
| temperature | penalize, dan temper | temper, dan penalize |
|---|---|---|
| 0,5 | 99,858 % | 99,948 % |
| 1,0 | 89,839 % | 89,839 % |
| 2,0 | 10,783 % | 6,830 % |
Identiek bij , zoals het moet. Een factor 1,58 uit elkaar bij . “Presence penalty 1,0” is geen goed gedefinieerde hoeveelheid penalty tenzij je ook weet waar de temperature wordt toegepast, en geen enkele API documenteert dit.
Details tonen
Optioneel: de hele pipeline, in de volgorde hierboven.
Zestien regels, en alles in dit hoofdstuk zit erin. Het is dezelfde berekening die de widget uitvoert, op een echte logit-vector in plaats van tien vaste getallen.
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)])De cumsum(0) - p in de top-p-regel is de cumulatieve massa zonder de huidige token, waardoor de nucleus de token bevat die de drempel overschrijdt in plaats van net ervoor te stoppen. Maak daar een off-by-one-fout en top_p = 0.9 wordt stilletjes een iets strakkere cut dan elke andere implementatie.
Dit is een van de weinige plekken in de tweede helft van de cursus waar Python de juiste taal is, en de reden is structureel in plaats van stilistisch: elke regel hierboven heeft de volledige vector van logits in je handen nodig, en via een HTTP API bestaat die vector niet. Je kunt temperature en top_p naar een provider sturen; je kunt ze niet zelf implementeren, en je kunt niet zien wat ze deden.
Er is geen universele sampling-API
Link naar de sectie: Er is geen universele sampling-APIElke provider accepteert een andere subset van deze controles, met verschillende bereiken, en negeert de rest stilletjes. Dit is geen abstracte klacht. Elke applicatie die een modelkeuze aanbiedt moet de verschillen ergens opschrijven, en het bestand waarin dat gebeurt is een kaart van de incompatibiliteit. Dit is wat zo’n catalogus declareert voor één parameter over de negen tekstbronnen die hij ondersteunt:
| gedeclareerd temperature-bereik | bronnen |
|---|---|
| 0 tot 1 | Anthropic, Google, Meta, Cerebras, PaLM |
| 0 tot 1,5 | Mistral |
| 0 tot 2 | OpenAI, DeepSeek, xAI |
Het woord is hetzelfde; de schaal niet. Een “temperature van 1” is bij de ene de ongewijzigde verdeling en bij de andere de maximaal toegestane hitte, en de helft van de catalogus kan de waarde niet uitdrukken die de andere helft behandelt als neutraal-plus-een-beetje. De rest van de knoppen is net zo ongelijk: de OpenAI-, DeepSeek- en xAI-items accepteren presence en frequency penalties en geen topK; de Google-, Meta-, Cerebras- en PaLM-items accepteren topK en geen penalties; Anthropic accepteert topK, topP en stop sequences en geen penalties; en precies één van de negen — Mistral — accepteert een seed. Een parameter sturen die een provider niet implementeert levert meestal helemaal geen fout op: het verzoek slaagt, de knop doet niets, en jij concludeert dat de instelling geen effect heeft.
En merk op wat zo’n bestand is: een claim over iemand anders’ API, geschreven op één specifieke dag, die daarna door niets wordt geverifieerd. Een catalogus die 0 tot 1 zegt voor een provider die inmiddels 0 tot 2 accepteert, kapt elk verzoek stilletjes af.
Twee extra controles horen bij dezelfde familie. logprobs, waar beschikbaar, geeft de log-kansen van de gekozen token terug en vaak de paar beste alternatieven — het enige venster dat je krijgt op de verdeling waar dit hoofdstuk over gaat, en de basis van elke confidence-heuristiek op een gesloten model. En maximum tokens plus stop sequences beëindigen generatie zonder enige verwijzing naar kans: een harde limiet en een stringmatch. Beide verschijnen als de finish_reason uit hoofdstuk 14, waar length betekent dat je antwoord midden in een zin door een budget is afgekapt, niet door het model is afgerond.
De seed, en het determinisme dat je niet hebt
Link naar de sectie: De seed, en het determinisme dat je niet hebtZet een seed en sampling wordt reproduceerbaar. Dat deel is echt, en makkelijk te verifiëren:
seed = 1234 " Paris\nWhat is a good geographical qualifier for describing
Paris concerning its location?\nA: Near the Mediterranean Sea"
seed = 1234 " Paris\nWhat is a good geographical qualifier for describing
Paris concerning its location?\nA: Near the Mediterranean Sea"
seed = 7 " Paris is the capital of France. The appellation of Paris is
\"Île de Paris\"."
seed = 7 " Paris is the capital of France. The appellation of Paris is
\"Île de Paris\"."Byte-identiek binnen één seed, verschillend tussen seeds, precies zoals beloofd. Wat de seed dus vastzet is de willekeurige trekking in de laatste regel van die sample-functie — welke token wordt gekozen gegeven een verdeling.
Wat hij niet vastzet is de verdeling. En daar zit het probleem, omdat de vector van logits die je model produceert geen wiskundig object is; het is de output van miljarden floating-point-optellingen, en die hebben een volgorde.
Hoofdstuk 2 liet dit experiment klaarstaan. Dezelfde miljoen float32-getallen, opgeteld in verschillende groeperingen:
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? FalseKijk naar de laatste regel. Het aantal chunks verandert het antwoord. Dat is geen curiositeit over numpy; het is het mechanisme, want wanneer een inference-server een reductie splitst over meer of minder parallelle units, doet hij precies dit. En een server splitst op basis van hoeveel verzoeken hij bedient.
Hier is dat effect op het model zelf. Dezelfde prompt, dezelfde forward pass, met als enige verschil hoeveel andere verzoeken toevallig in de batch zaten:
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-05Alleen gedraaid is het model perfect deterministisch — twintig passes, bit-identiek. Zet dezelfde prompt in een batch met niet-gerelateerde verzoeken en 97 % van zijn logits verandert. Er veranderde niets aan jouw verzoek. Iemand anders’ verzoek kwam binnen.
Nu het eerlijke deel, omdat dit meestal wordt verteld alsof het het einde van het verhaal is. Een verandering van verandert de output alleen als twee kandidaat-tokens binnen die marge van elkaar lagen. Over 717 generatiestappen over twaalf prompts was de kleinste kloof tussen de top twee logits — honderd keer groter dan de verstoring — en geen enkele stap zat dichtbij genoeg om te flippen. Dus op dit model, in float32, op een laptop, verplaatste batching elke logit en veranderde geen enkele token.
Dat is een beschrijving van gunstige omstandigheden, geen geruststelling, en één wijziging aan die omstandigheden is genoeg:
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."Zes van de acht antwoorden lopen uiteen, en één ervan degradeert sterk. De tabel uit hoofdstuk 2 zegt waarom: bfloat16 bewaart 7 mantissebits, dus bij een logit-grootte rond 16 liggen de representeerbare waarden 0,125 uit elkaar — 16,0, dan 16,125, dan 16,25 — en afronding kan een logit tot 0,0625 verplaatsen. Ondertussen had 4,7 % van de hierboven gemeten generatiestappen een top-twee-kloof onder 0,1. Dat is het volledige verschil tussen de twee experimenten: in float32 was de verstoring honderd keer kleiner dan de dichtstbijzijnde beslissing, en in bfloat16 is ze even groot. Production inference draait in 16-bit, op hardware met fused kernels en reductievolgordes waarvan niemand belooft ze gelijk te houden. Of “de numerieke ruis verwaarloosbaar is” is een vraag over precisie en hardware, niet over het model.
Dus, de vier oorzaken, gecatalogiseerd zoals hoofdstuk 9 beloofde:
Floating-point-optelling is niet associatief
Link naar de sectie: Floating-point-optelling is niet associatiefHet kader uit hoofdstuk 2. De volgorde van een som verandert de waarde, dus elke verandering in hoe een reductie wordt gesplitst verandert de logits. Dit is het substraat; de andere drie zijn manieren om de volgorde te veranderen.
Dynamic batching groepeert je verzoek met die van vreemden
Link naar de sectie: Dynamic batching groepeert je verzoek met die van vreemdenContinuous batching, uit hoofdstuk 13, is waarom inference betaalbaar is — en het betekent dat de vorm van de matrices waar je tokens doorheen stromen afhangt van verkeer. Hierboven gemeten: 147.321 logits bewogen doordat de batchgrootte veranderde.
Mixture-of-experts routing hangt af van de batch
Link naar de sectie: Mixture-of-experts routing hangt af van de batchHet kader uit hoofdstuk 9 zei het al. De router maakt een discrete keuze per token per laag, onderworpen aan capaciteitslimieten per expert die over de batch worden berekend. Een token die alleen naar expert 7 was gegaan, gaat in gezelschap naar expert 12. Dit is geen afrondingsverschil; het is een andere set gewichten.
Het model achter de naam verandert
Link naar de sectie: Het model achter de naam verandertEen versiestring zoals -latest is een pointer, en pointers worden omgezet. Providers updaten ook de serving stack onder een vaste versie-identifier. Geen van beide wordt aangekondigd met een granulariteit waarmee je het zou kunnen correleren met verandering in je eigen output.
OpenAI’s parameter seed is hier eerlijk over op de enige manier waarop dat kan: hij wordt geleverd naast een veld system_fingerprint dat de backendconfiguratie identificeert, en de documentatie zegt dat determinisme best-effort is en dat een gewijzigde fingerprint betekent dat resultaten kunnen verschillen. Lees dat voor wat het is — een provider die je vertelt dat hij alle vier oorzaken hierboven beheerst, dat jij er geen enkele beheerst, en dat het enige wat hij kan bieden is je achteraf vertellen dat er iets bewoog.
Waar dit hierna heen gaat
Link naar de sectie: Waar dit hierna heen gaatAlles hier ging over een knop en de gevolgen ervan. Doe één stap terug en het moeilijkere probleem verschijnt: het object dat we hebben afgestemd is een kansverdeling, en kansverdelingen hebben geen interface.
Een function call heeft die wel. Een databaserij heeft die wel. Een POST-handler die een JSON-body met drie verplichte velden verwacht heeft die wel, en wijst alles anders af. Tussen het model en elk ander component in je systeem zit een contract waar één kant geen beloften over kan doen: het model produceert iets, getrokken uit een verdeling die je hebt gevormd maar niet vastgezet, en de code aan de andere kant heeft een waarde van een bekend type nodig of gooit een fout.
De brug tussen die twee werelden wordt gebouwd uit het materiaal van dit hoofdstuk, niet uit parsing en retries. Als een token de vereiste structuur zou breken, sample je hem niet in de hoop dat het goed gaat — je zet zijn logit op voordat de softmax hem ooit ziet. Constrained decoding is een mask over dezelfde vector die we dit hele hoofdstuk hebben hervormd, en het verandert “antwoord alsjeblieft in JSON” van een verzoek in een garantie.
Hoofdstuk 18 is dat contract: tool calling, JSON Schema, structured outputs, en wat er nodig is om een deterministisch systeem veilig te bouwen bovenop een probabilistisch systeem.
Bronnen en methode
Link naar de sectie: Bronnen en methodeAlle metingen in dit hoofdstuk komen van Qwen/Qwen2.5-0.5B-Instruct op CPU, float32 tenzij anders vermeld, met sampling geïmplementeerd zoals geschreven in de optionele sectie in plaats van uitbesteed aan een library. Het is een klein model, en de specifieke waarden zijn van dat model; de mechanismen niet. Von Platens How to generate text with different decoding methods (Hugging Face, 2020) is het artikel waar dit mee is vergeleken en nog steeds de beste korte introductie tot hetzelfde materiaal. Voor de sectie over determinisme: PyTorchs reproducibility notes beschrijven wat een seed wel en niet vastzet op één machine, OpenAI’s documentatie van seed en system_fingerprint beschrijft wat een provider wel en niet kan beloven, en Thinking Machines’ discussie uit 2025 over batch-invariant kernels is het duidelijkste publieke verslag van waarom dit oplossen op inference-server-niveau mogelijk is maar niet gratis.
Referenties
Link naar de sectie: Referenties-
Ackley, D. H., Hinton, G. E. en Sejnowski, T. J. A Learning Algorithm for Boltzmann Machines. Cognitive Science 9(1), pp. 147–169 (1985), waar de temperature in een softmax uit de statistische fysica komt. Hinton, G., Vinyals, O. en Dean, J., Distilling the Knowledge in a Neural Network, arXiv:1503.02531 (2015), sectie 2, is waar dezelfde parameter opnieuw verschijnt in moderne deep learning — als een manier om de volledige verdeling van een teacher bloot te leggen, wat de soft labels uit hoofdstuk 13 zijn in plaats van de sampling van dit hoofdstuk. ↩
-
Guo, C., Pleiss, G., Sun, Y. en Weinberger, K. Q. On Calibration of Modern Neural Networks. arXiv:1706.04599 (2017). Verwar dit niet met de temperature in dit hoofdstuk. Temperature scaling fit één waarde op een validatieset zodat de confidence van het model overeenkomt met zijn accuratesse; het is een post-hoc-kalibratiemethode toegepast op de outputs van een classifier. Temperature sampling is een runtime-control over hoe een generator tokens trekt. Dezelfde formule, een ander doel, en geen gedeelde waarde. ↩
-
Holtzman, A., Buys, J., Du, L., Forbes, M. en Choi, Y. The Curious Case of Neural Text Degeneration. arXiv:1904.09751 (2019). Introduceert nucleus sampling en de meting dat op maximalisatie gebaseerde decoding tekst produceert waarvan het kansprofiel totaal niet op menselijke tekst lijkt. ↩ ↩2
-
Fan, A., Lewis, M. en Dauphin, Y. Hierarchical Neural Story Generation. arXiv:1805.04833 (2018). De paper die top-k sampling populair maakte. ↩
-
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. en Cotterell, R. Locally Typical Sampling. arXiv:2202.00666 (2022). ↩
-
Keskar, N. S., McCann, B., Varshney, L. R., Xiong, C. en Socher, R. CTRL: A Conditional Transformer Language Model for Controllable Generation. arXiv:1909.05858 (2019). Sectie 4.1 is de oorspronkelijke repetition penalty — degene die deelt. ↩