Naar inhoud springen
17/30Hoofdstuk 17 van 30

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.

TEXT
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 hangt

Hoofdstuk 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 z\mathbf{z} om in kansen:

pi=ezijezjp_i = \frac{e^{z_i}}{\sum_j e^{z_j}}

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:

pi(T)=ezi/Tjezj/Tp_i(T) = \frac{e^{z_i/T}}{\sum_j e^{z_j/T}}

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 1/T1/T en opnieuw normaliseren. Dan krijg je

pi/Tjpj/T=pijpj=pi\frac{p_i/T}{\sum_j p_j/T} = \frac{p_i}{\sum_j p_j} = p_i

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 TT vóór het exponentiëren hetzelfde is als elke kans verheffen tot de macht 1/T1/T — 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 T0T \to 0 loopt de grootste logit weg van de rest en klapt pp in tot de ene hoogst scorende token: greedy decoding. Als TT groeit, gaat elke zi/Tz_i/T richting nul, gaat elke exponentiële waarde richting 1, en vlakt de verdeling af naar uniform over het hele vocabulaire. Precies bij T=0T = 0 deelt de formule door nul, dus elke implementatie behandelt dat als speciaal geval met het rekenkundige maximum — ook de widget hieronder, die bij T0.001T \le 0.001 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:

  • ␣Paris96.9%
  • ␣the1.3%
  • ␣located0.8%
  • ␣a0.5%
  • ␣Lyon0.2%
  • ␣called0.1%
  • ␣home0.1%
  • ␣Marseille0.0%
  • ␣not0.0%
  • ␣banana0.0%

10 van 10 tokens blijven over na de selectie en delen de kans.

Bekijk de gegevens als tabel
TokenlogitNa temperatuurNa de selectie
␣Paris⁨9.4⁩96.90%96.90%
␣the⁨5.1⁩1.31%1.31%
␣located⁨4.6⁩0.80%0.80%
␣a⁨4.1⁩0.48%0.48%
␣Lyon⁨3.2⁩0.20%0.20%
␣called⁨2.9⁩0.15%0.15%
␣home⁨2.4⁩0.09%0.09%
␣Marseille⁨1.8⁩0.05%0.05%
␣not⁨1.1⁩0.02%0.02%
␣banana⁨-2.6⁩0.00%0.00%
Sampling: temperatuur, top-p en top-k

Tien mogelijke vervolgen van The capital of France is, bij temperature 1 zonder cut. ␣Paris bevat 96,90 % van de massa; ␣banana, onderaan met een logit van 2.6-2.6, krijgt 0,00 %. Schuif de temperature naar 0 en één token overleeft met 100 %. Schuif hem naar 2 en ␣Paris daalt naar 69,81 %, terwijl ␣banana stijgt naar 0,17 % — de afgewezen token van het model, echte kans gegeven door een knop die de lezer omdraaide.

Het 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:

temperaturetop-1-kansentropytokens met 80 %90 %95 %99 %
0,599,98 %0,00 nats1111
0,799,65 %0,03 nats1111
1,096,01 %0,30 nats11114
1,288,20 %0,88 nats1213252
1,562,83 %3,07 nats293532.67226.787
2,016,62 %8,19 nats13.51632.96655.231101.205

Lees de onderste rij langzaam. Bij T=2T = 2, 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:

TEXT
"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 is

Onder 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:

TEXT
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 aan

Sampling 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 kk, 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 pp bereikt, en stop.3 Formeel is de nucleus de kleinste set VpV_p met

iVppip\sum_{i \in V_p} p_i \ge p

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-kans96,01 %25,39 %
tokens met 90 % van de massa1467
top-k = 40 bewaart99,61 % van de massa78,87 % van de massa
massa in rangen 2 tot 403,61 %53,48 %
token op rang 40␣Av, 0,0093 %␣Dr, 0,128 %

Eén vaste kk, twee fouten in tegengestelde richtingen. Op de feitelijke prompt laat k=40k = 40 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 k=40k = 40 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 p=0.9p = 0.9 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:

  • ␣Paris91.1%
  • ␣the5.2%
  • ␣located3.7%
  • ␣a0.0%
  • ␣Lyon0.0%
  • ␣called0.0%
  • ␣home0.0%
  • ␣Marseille0.0%
  • ␣not0.0%
  • ␣banana0.0%

3 van 10 tokens blijven over na de selectie en delen de kans.

Bekijk de gegevens als tabel
TokenlogitNa temperatuurNa de selectie
␣Paris⁨9.4⁩85.03%91.10%
␣the⁨5.1⁩4.84%5.18%
␣located⁨4.6⁩3.47%3.71%
␣a⁨4.1⁩2.48%
␣Lyon⁨3.2⁩1.36%
␣called⁨2.9⁩1.12%
␣home⁨2.4⁩0.80%
␣Marseille⁨1.8⁩0.54%
␣not⁨1.1⁩0.34%
␣banana⁨-2.6⁩0.03%
Sampling: temperatuur, top-p en top-k

Top-p op 0,90 met temperature op 1,5: drie van de tien tokens overleven en delen de massa, ␣Paris opnieuw genormaliseerd naar 91,10 %. Verplaats nu alleen de temperature. Bij 0,7 laat dezelfde 0,90 één overlevende over — zo’n smalle nucleus is greedy decoding onder een andere naam. Bij 2,0 laat hij er vijf over. De cut bewoog niet; de vorm eronder wel.

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:

  • ␣Paris97.2%
  • ␣the1.3%
  • ␣located0.8%
  • ␣a0.5%
  • ␣Lyon0.2%
  • ␣called0.0%
  • ␣home0.0%
  • ␣Marseille0.0%
  • ␣not0.0%
  • ␣banana0.0%

5 van 10 tokens blijven over na de selectie en delen de kans.

Bekijk de gegevens als tabel
TokenlogitNa temperatuurNa de selectie
␣Paris⁨9.4⁩96.90%97.20%
␣the⁨5.1⁩1.31%1.32%
␣located⁨4.6⁩0.80%0.80%
␣a⁨4.1⁩0.48%0.49%
␣Lyon⁨3.2⁩0.20%0.20%
␣called⁨2.9⁩0.15%
␣home⁨2.4⁩0.09%
␣Marseille⁨1.8⁩0.05%
␣not⁨1.1⁩0.02%
␣banana⁨-2.6⁩0.00%
Sampling: temperatuur, top-p en top-k

Top-k op 5, geen top-p. Vijf tokens overleven bij elke temperature, omdat vijf is waar om gevraagd werd. Bij temperature 1, zoals getoond, zijn de vier kandidaten onder ␣Paris samen 2,79 % waard. Verlaag naar 0,7 en dezelfde vier zijn 0,38 % waard — de cut is theater, en het model is effectief greedy. Verhoog naar 2,0 en ze zijn 22,54 % waard. Identieke instelling, identiek aantal overlevenden, drie compleet verschillende gedragingen, en niets in het verzoek vertelt je welke je krijgt.

De penalties, met de formules, omdat ze voortdurend worden verward

Link naar de sectie: De penalties, met de formules, omdat ze voortdurend worden verward

Drie verschillende mechanismen reizen onder vergelijkbare namen, ze doen verschillende dingen, en het verschil is meetbaar. Laat cic_i het aantal keren zijn dat token ii al is verschenen.

ziziα1[ci>0]z_i \leftarrow z_i - \alpha \cdot \mathbb{1}[c_i > 0]

Trek 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.

ziziβciz_i \leftarrow z_i - \beta \, c_i

Trek 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.

zi{zi/ρif zi>0ziρif zi0z_i \leftarrow \begin{cases} z_i / \rho & \text{if } z_i > 0 \\ z_i \cdot \rho & \text{if } z_i \le 0 \end{cases}

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 ρ\rho 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 %:

instellingherhaalde 4-gramsgewijzigde stappen
niets85,5 %0 / 120
presence 0,565,0 %3 / 120
presence 1,03,4 %11 / 120
frequency 0,56,0 %12 / 120
frequency 1,00,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 herhalen

Code 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:

taaknietsfrequency 0,5repetition 1,2
markdown-tabel, 6 rijen0 / 56 stappen gewijzigd0 / 562 / 62
Python-functie0 / 930 / 9310 / 110
lijst met bullets, 1 tot 120 / 500 / 500 / 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:

TEXT
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:

TEXT
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 nn. 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 verandert

Elke 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 temperaturetop-p 0,9 vóór temperature
T=1.0T = 1.01 token1 token
T=1.5T = 1.5353 tokens1 token
T=2.0T = 2.032.966 tokens1 token

Bij T=2T = 2 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 α\alpha aftrekken en daarna delen door TT geeft een effectieve penalty van α/T\alpha/T; eerst delen en daarna aftrekken geeft α\alpha. Met een presence penalty van 1,0 toegepast op de leidende token:

temperaturepenalize, dan tempertemper, dan penalize
0,599,858 %99,948 %
1,089,839 %89,839 %
2,010,783 %6,830 %

Identiek bij T=1T = 1, zoals het moet. Een factor 1,58 uit elkaar bij T=2T = 2. “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.

sample.pyPYTHON
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.

Elke 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-bereikbronnen
0 tot 1Anthropic, Google, Meta, Cerebras, PaLM
0 tot 1,5Mistral
0 tot 2OpenAI, 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 hebt

Zet een seed en sampling wordt reproduceerbaar. Dat deel is echt, en makkelijk te verifiëren:

TEXT
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:

TEXT
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?    False

Kijk 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:

TEXT
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-05

Alleen 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 2.5×1052.5 \times 10^{-5} 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 2.5×1032.5 \times 10^{-3} — 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:

TEXT
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 associatief

Het 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 vreemden

Continuous 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 batch

Het 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.

Een 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.

Alles 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 -\infty 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.


Alle 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.

  1. 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.

  2. 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.

  3. 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

  4. Fan, A., Lewis, M. en Dauphin, Y. Hierarchical Neural Story Generation. arXiv:1805.04833 (2018). De paper die top-k sampling populair maakte.

  5. Nguyen, M. et al. Turning Up the Heat: Min-p Sampling for Creative and Coherent LLM Outputs. arXiv:2407.01082 (2024).

  6. Meister, C., Pimentel, T., Wiher, G. en Cotterell, R. Locally Typical Sampling. arXiv:2202.00666 (2022).

  7. 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.

Klaar om LIA te laten kiezen?

Bouw met elk AI-model op één plek — begin vandaag nog gratis.