Spring til indhold
17/30Kapitel 17 af 30

Temperatur, top-p og den determinisme, du ikke har

Temperatur dividerer logits før softmax — og afliver idéen om en kreativitetsknap. Samme greedy-kald to gange kan give to svar.

På denne side

Her er den samme anmodning sendt til den samme model fem gange. Samme vægte, samme prompt, samme maskine, samme tilfældige seed. Det eneste, der ændrer sig, er ét tal.

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 ('$ספטמבר..."

Intet gik i stykker. Hver token i den sidste linje blev trukket legitimt fra modellens egen sandsynlighedsfordeling over dens ordforråd på 151.936 elementer. Tallet, der ændrede sig, kaldes temperatur, det beskrives i den meste dokumentation som en kreativitetsknap, og den beskrivelse er forkert på en måde, dette kapitel kan demonstrere i stedet for blot at påstå.

Det er også kapitlet, hvor tre tidligere løfter forfalder. Kapitel 4 definerede logit og brugte den egentlig aldrig. Kapitel 2s boks om floating-point sluttede med en instruktion — husk det her, når Kapitel 17 spørger, hvorfor samme prompt, model og seed kan producere forskellige tokens. Og Kapitel 9s boks om mixture-of-experts lovede et katalog over fire årsager til ikke-determinisme. Alle tre kommer nedenfor.

Kapitel 4 introducerede logit som en unormaliseret reel score, én pr. klasse. Kapitel 8 fik en sprogmodel til at producere én pr. element i ordforrådet. softmax gør den vektor z\mathbf{z} til sandsynligheder:

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

Temperatur kommer ind her — navnet er lånt fra statistisk fysik, hvor den samme parameter styrer, hvor skarpt en Boltzmann-fordeling koncentrerer sig om sine lavenergitilstande1 — og den dividerer logits før eksponentialfunktionen:

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

Den placering er hele mekanismen, og det er to linjers algebra værd at se, hvorfor den ikke kunne være noget andet sted. Antag, at du i stedet prøvede at anvende temperatur på sandsynlighederne — skalere dem med 1/T1/T og renormalisere. Du ville få

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

Konstanten går ud. At skalere sandsynligheder gør slet ingenting; fordelingen kommer tilbage uændret. Temperatur har kun en effekt, fordi den virker på eksponenten, hvor det at dividere med TT før eksponentiering er det samme som at opløfte hver sandsynlighed til potensen 1/T1/T — en ikke-lineær omformning, der ændrer forholdene mellem elementer i stedet for deres fælles skala.

Fra den placering følger begge grænser uden mere arbejde. Når T0T \to 0, løber den største logit fra resten, og pp kollapser ned på den ene token med højest score: greedy decoding. Når TT vokser, går hver zi/Tz_i/T mod nul, hver eksponentialværdi går mod 1, og fordelingen flader ud mod uniform over hele ordforrådet. Præcis ved T=0T = 0 dividerer formlen med nul, så hver implementation specialbehandler det til det aritmetiske maksimum — inklusive widgetten nedenfor, som skifter til argmax ved T0.001T \le 0.001.

Én advarsel, fordi navnesammenfaldet skaber reel forvirring. Der er en anden, urelateret ting i machine learning, der hedder temperatur: temperature scaling, en kalibreringsmetode, der fitter én værdi på et valideringssæt, så en klassifikators selvtillid matcher dens nøjagtighed.2 Samme formel, intet at gøre med generering. Artikler, der siger "temperatur", mener ofte den; det gør dette kapitel aldrig.

Her er den fordeling, med aritmetikken foran dig. Logits er faste og plausible, så tallene i teksten nedenfor kan tjekkes mod det, du ser:

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

10 af 10 tokens overlever afskæringen og deler sandsynligheden.

Se dataene som en tabel
TokenlogitEfter temperaturEfter afskæringen
␣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: temperatur, top-p og top-k

Ti kandidatfortsættelser af The capital of France is, ved temperatur 1 uden afskæring. ␣Paris holder 96,90 % af massen; ␣banana, nederst med en logit på 2.6-2.6, får 0,00 %. Skub temperaturen til 0, og én token overlever med 100 %. Skub den til 2, og ␣Paris falder til 69,81 %, mens ␣banana stiger til 0,17 % — modellens afviste token, givet reel sandsynlighed af en knap, læseren drejede på.

Tallet ␣banana er hele argumentet i miniature: at hæve temperaturen kan ikke give en model en idé, den ikke havde. Logits er allerede beregnet, rangordningen er allerede fast, og temperatur bevarer den præcist — ingen mængde varme flytter nogensinde en lavere scoret token over en højere scoret token. Det eneste, den gør, er at omfordele masse ned gennem den rangordning, modellen selv producerede. Høj temperatur gør ikke en model mere opfindsom; den gør det mere sandsynligt, at den udsender de tokens, den scorede som dårlige.

På et rigtigt ordforråd holder dette op med at være en kuriositet og bliver grunden til, at output ved høj temperatur er ubrugeligt. Målt på Qwen/Qwen2.5-0.5B-Instruct, ét forward pass, prompten ovenfor, hvor vi tæller, hvor mange tokens det kræver at akkumulere en given andel af sandsynlighedsmassen:

temperaturtop-1-sandsynlighedentropitokens med 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

Læs den nederste række langsomt. Ved T=2T = 2, på et spørgsmål med præcis ét korrekt svar, deler 32.966 forskellige tokens de øverste 90 % af sandsynlighedsmassen. Det er ikke et bredere kreativt rum. Det er en model, der af aritmetik er blevet bedt om at behandle en koreansk partikel og en C++-identifier som levende muligheder for ordet efter A:. Affaldet i åbningsblokken er den direkte konsekvens, og det er ikke en fejl i modellen eller biblioteket — det er det, anmodningen bad om.

Det nyttige interval er smalt og afhænger af opgaven frem for smag. På et faktuelt spørgsmål er svaret én token, og enhver varme over cirka 1,2 injicerer fejl uden gevinst. På et åbent spørgsmål findes der reelt mere end én god fortsættelse, og noget varme køber variation, der stadig er flydende:

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

Ved 1,3 har modellen opfundet et egennavn og en sætning, der ikke kan parses. Båndet mellem "identisk hver gang" og "usammenhængende" er omtrent 0,6 til 1,1 for denne model på denne opgave, og det ærlige råd er, at du finder det ved at måle på din opgave, ikke ved at kopiere et tal fra et blogindlæg.

Hvorfor den mest sandsynlige tekst er dårlig tekst

Link til afsnittet: Hvorfor den mest sandsynlige tekst er dårlig tekst

Der gemmer sig et oplagt spørgsmål under det hele: hvis modellen har en sandsynlighedsfordeling, og én token er mest sandsynlig, hvorfor så ikke altid tage den? Greedy decoding er gratis, reproducerbart og kræver ingen parametre.

Fordi resultatet er dette:

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 %

Otte sætninger, én sætning. Næsten ni ud af ti fire-token-vinduer var allerede dukket op tidligere i samme output. Det er neural text degeneration, navngivet og forklaret af Holtzman et al. i artiklen, der introducerede top-p.3 Modellen er ikke i stykker; maksimering af sekvenssandsynlighed er simpelthen det forkerte mål for åben tekst. Menneskelig skrivning er ikke den mest sandsynlige sekvens af ord — den rummer overraskelse, dens per-token-sandsynlighed vandrer, dykker og kommer sig — mens maksimum-sandsynlighedsstien er et fikspunkt, der, når det først er nået, ikke har nogen grund til at forlade sig selv.

Det er derfor, sampling overhovedet findes. Det er også, og det er den del, der ofte udelades, ikke en universel lov. Kapitel 12 målte 24 ud af 24 korrekte på totrins ordproblemer med almindelig greedy decoding, og sampling ved temperatur 0,8 sænkede det til 81 %; self-consistency brugte derefter seks gange så mange tokens på at klatre tilbage til det sted, hvor greedy allerede var. Begge fakta er sande på én gang:

Åben generering. Der er ikke én enkelt rigtig fortsættelse, så den mest sandsynlige er en fælde — den looper, og 87,6 % af den er kopieret fra sig selv. Sample.

Opgaver med ét rigtigt svar. Der er én korrekt fortsættelse, så at trække noget andet er at trække en fejl. Kapitel 12s 100 % blev til 81 % præcis af den grund. Lad være med at sample.

De fleste produktionsprompts er af den anden slags og bliver konfigureret som den første, fordi temperaturen blev efterladt på det tal, eksempelkoden brugte.

To måder at skære på, og kun én af dem tilpasser sig

Link til afsnittet: To måder at skære på, og kun én af dem tilpasser sig

Sampling fra den fulde fordeling er ikke det, nogen faktisk gør, fordi halen er enorm og fuld af nonsens. Noget må skæres væk. Der er to klassiske svar, og de adskiller sig på ét punkt, der afgør alt.

Top-k beholder et fast antal kandidater. Sortér efter sandsynlighed, behold de første kk, kassér resten, renormalisér.4 Top-p, også kaldet nucleus sampling, beholder en fast mængde masse: tag tokens i faldende rækkefølge, indtil deres kumulative sandsynlighed når pp, og stop.3 Formelt er nucleus det mindste sæt VpV_p med

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

Forskellen lyder kosmetisk, men er det ikke, fordi de to prompts, du sender i samme minut, har helt forskellige fordelingsformer. Begge disse er samme model ved temperatur 1:

Q: What is the capital of France?\nA:Once upon a time,
top-1-sandsynlighed96,01 %25,39 %
tokens med 90 % af massen1467
top-k = 40 beholder99,61 % af massen78,87 % af massen
masse i rang 2 til 403,61 %53,48 %
token på rang 40␣Av, 0,0093 %␣Dr, 0,128 %

Én fast kk, to fejl i modsatte retninger. På den faktuelle prompt lukker k=40k = 40 39 tokens ind, der tilsammen er 3,6 % værd — den lader skrald slippe igennem, inklusive en kandidat på ni tusindedele procent, fordi reglen tæller pladser og ikke evidens. På historieprompten smider den samme k=40k = 40 21 % af den masse væk, som modellen faktisk tildelte, fordi den reelle nucleus dér er 467 tokens bred.

Top-p får præcis ét tal til at gøre begge job. Sæt p=0.9p = 0.9, og den beholder 1 token på den første prompt og 467 på den anden, fordi den stiller et spørgsmål om fordelingen i stedet for at påtvinge den et antal. Se tilpasningen direkte — samme afskæring, fire temperaturer:

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

3 af 10 tokens overlever afskæringen og deler sandsynligheden.

Se dataene som en tabel
TokenlogitEfter temperaturEfter afskæringen
␣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: temperatur, top-p og top-k

Top-p ved 0,90 med temperaturen på 1,5: tre af de ti tokens overlever og deler massen, ␣Paris renormaliseret til 91,10 %. Flyt nu kun temperaturen. Ved 0,7 efterlader den samme 0,90 én overlever — en nucleus så smal er greedy decoding med et andet navn. Ved 2,0 efterlader den fem. Afskæringen flyttede sig aldrig; formen under den gjorde.

Den widget afgør også en misforståelse, der er værd at navngive, fordi den koster folk rigtige penge. På en sikker fordeling er top_p = 0.9 ikke "lidt variation". Det er greedy. Ved temperatur 1 holder den førende token her 96,90 %, hvilket allerede er over 0,9, så nucleus er én token bred, og intet andet kan nogensinde trækkes. Teams sætter top_p til 0,9 i den tro, at de har løsnet noget, og undrer sig så over, hvorfor hvert svar er identisk.

Sæt top-k i stedet, og den modsatte fejl er lige så synlig:

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

5 af 10 tokens overlever afskæringen og deler sandsynligheden.

Se dataene som en tabel
TokenlogitEfter temperaturEfter afskæringen
␣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: temperatur, top-p og top-k

Top-k ved 5, ingen top-p. Fem tokens overlever ved hver temperatur, fordi fem var det, der blev bedt om. Ved temperatur 1 er de fire kandidater under ␣Paris, som vist, 2,79 % værd tilsammen. Sænk til 0,7, og de samme fire er 0,38 % værd — afskæringen er teater, og modellen er reelt greedy. Hæv den til 2,0, og de er 22,54 % værd. Identisk indstilling, identisk antal overlevere, tre helt forskellige adfærdsmønstre, og intet i anmodningen fortæller dig, hvilket du får.

Straffene, med formlerne, fordi de konstant forveksles

Link til afsnittet: Straffene, med formlerne, fordi de konstant forveksles

Tre forskellige mekanismer rejser under lignende navne, de gør forskellige ting, og forskellen kan måles. Lad cic_i være antallet af gange, token ii allerede er dukket op.

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

Træk en konstant fra enhver token, der overhovedet er dukket op. At dukke op én gang og at dukke op fyrre gange straffes identisk. Det er en kontakt, ikke en knap.

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

Træk fra i forhold til antallet. En token brugt fire gange straffes fire gange så hårdt som en token brugt én gang, og presset akkumulerer, efterhånden som teksten vokser.

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}

Den oprindelige, fra CTRL-artiklen.7 Den dividerer i stedet for at trække fra, med fortegnstilfældet nødvendigt, fordi division af en negativ logit ville gøre den større. Dens styrke afhænger derfor af logittens størrelse, hvilket betyder, at samme ρ\rho rammer forskelligt på forskellige steder i samme sætning.

Den samme degenererede fortsættelse fra før, med hver af dem anvendt. "Ændrede trin" tæller, hvor mange af de 120 genereringstrin der valgte en anden token end den ustraffede model ville have gjort. Kørslen er 120 trin her mod 140 i blokken ovenfor, og derfor viser den ustraffede baseline 85,5 % i stedet for 87,6 %:

indstillinggentagne 4-gramsændrede trin
intet85,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

Tre ting falder ud. Presence ved 0,5 ændrede tre beslutninger ud af 120 og skar gentagelsen med en fjerdedel — loopet blev holdt sammen af en håndfuld tokens. Frequency ved 0,5 ændrede fire gange så mange beslutninger med langt større effekt, fordi count-multiplikatoren bliver ved med at vokse, mens presence-konstanten ikke gør. Og CTRL-straffen ved den ofte kopierede værdi 1,2 omskrev 35 af 120 beslutninger, hvilket ikke er et lille puf; det er en anden model.

Det sidste tal er oplægget til den fejl, ingen advarer om.

Hvad straffe gør ved tekst, der skal gentage sig

Link til afsnittet: Hvad straffe gør ved tekst, der skal gentage sig

Kode gentager sig. Tabeller gentager sig. Lister gentager sig. Struktureret output gentager sig pr. definition — det er det, struktur er. En straf kan ikke kende forskel på en model, der sidder fast i et loop, og en model, der korrekt udsender fjerde række i en tabel, fordi begge dele ligner en token, der dukker op igen.

De samme tre opgaver, genereret på tre måder:

opgaveintetfrequency 0,5repetition 1,2
markdown-tabel, 6 rækker0 / 56 trin ændret0 / 562 / 62
Python-funktion0 / 930 / 9310 / 110
punktliste, 1 til 120 / 500 / 500 / 50

Frequency penalty ved 0,5 viste sig at være harmløs på alle tre, hvilket er et nyttigt og lidt overraskende resultat, og det siger noget præcist: eftersom ingen beslutning ændrede sig, må de strukturelle tokens have vundet deres positioner med mere end straffen trak fra, selv efter at være dukket op fem og seks gange. CTRL-straffen, som dividerer i stedet, løsriver dem, og her er hvad den producerede:

TEXT
repetition 1.2, markdown table:
  | n | 2^n |
  | --- | --- |
  | 0 | 1      |
  | 1 | 2       |
  | 2 | 4       |

Justeringen falder fra hinanden: mængden af padding inde i hver celle ændrer sig fra række til række, fordi rækken af mellemrum før den afsluttende pipe præcis er den slags gentagelse, straffen findes for at bryde. Kosmetisk, og det kostede seks ekstra tokens. Python-tilfældet er ikke kosmetisk:

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,

Straffen skubbede modellen væk fra total — allerede brugt i docstringen — over på total_sum, polstrede outputtet med opfundne kommentarer for at bruge sit budget på ubrugte tokens og gik derefter ind i en tre-argumenters range med en stride. Kommentaren siger incrementing by 2 each time, hvilket er forkert for en sum af kvadrater fra 1 til nn. En repetition penalty producerede forkert kode fra en prompt, der blev besvaret korrekt uden den.

Reglen, der følger, er kort: straffe er til åben prosa, og de bør være slået fra for kode, struktureret output, tabeldata og alt med et schema. Kapitel 18 handler præcis om den anden kategori.

Anvendelsesrækkefølgen, og hvorfor den ændrer svaret

Link til afsnittet: Anvendelsesrækkefølgen, og hvorfor den ændrer svaret

Enhver reel implementation anvender disse i én bestemt sekvens:

straffe → temperatur → top-k → top-p → sample

Det er ikke vilkårlig bogføring, og at bytte om på to trin producerer reelt forskellige fordelinger. To målinger, begge på den faktuelle prompt.

Afskæring før eller efter temperaturen. Nucleus beregnes på den fordeling, den får, og temperatur ændrer den fordeling radikalt:

top-p 0,9 efter temperaturtop-p 0,9 før temperatur
T=1.0T = 1.01 token1 token
T=1.5T = 1.5353 tokens1 token
T=2.0T = 2.032.966 tokens1 token

Ved T=2T = 2 giver den samme nominelle indstilling et kandidatsæt på 32.966 eller på 1, alene afhængigt af hvilket trin der kører først. Hvis du nogensinde har undret dig over, hvorfor en højere temperatur "ikke gør noget" hos én udbyder og ødelægger outputtet hos en anden med de samme to tal, er denne tabel et plausibelt svar.

Straf før eller efter temperaturen. At trække en straf α\alpha fra og derefter dividere med TT giver en effektiv straf på α/T\alpha/T; at dividere først og derefter trække fra giver α\alpha. Med en presence penalty på 1,0 anvendt på den førende token:

temperaturstraf, så temperaturtemperatur, så straf
0,599,858 %99,948 %
1,089,839 %89,839 %
2,010,783 %6,830 %

Identiske ved T=1T = 1, som de skal være. En faktor 1,58 fra hinanden ved T=2T = 2. "Presence penalty 1,0" er ikke en veldefineret mængde straf, medmindre du også ved, hvor temperaturen anvendes, og ingen API dokumenterer det.

Vis detaljer

Valgfrit: hele pipelinen, i rækkefølgen ovenfor.

Seksten linjer, og alt i dette kapitel er i dem. Det er den samme beregning, widgetten udfører, på en rigtig logit-vektor i stedet for ti faste tal.

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)])

cumsum(0) - p i top-p-linjen er den kumulative masse eksklusive den aktuelle token, hvilket er det, der får nucleus til at inkludere den token, der krydser tærsklen, i stedet for at stoppe lige før den. Ram det én forkert, og top_p = 0.9 bliver lydløst en lidt strammere afskæring end alle andre implementationer.

Dette er et af de få steder i kursets anden halvdel, hvor Python er det rigtige sprog, og grunden er strukturel snarere end stilistisk: hver linje ovenfor kræver, at du har hele vektoren af logits i hånden, og over en HTTP API findes den vektor ikke. Du kan sende temperature og top_p til en udbyder; du kan ikke implementere dem, og du kan ikke se, hvad de gjorde.

Hver udbyder tager et forskelligt udsnit af disse kontroller, med forskellige intervaller, og ignorerer resten i stilhed. Det er ikke en abstrakt klage. Enhver applikation, der tilbyder et valg af model, må skrive forskellene ned et sted, og filen, hvor den gør det, er et kort over inkompatibiliteten. Her er, hvad ét sådant katalog erklærer for én parameter på tværs af de ni tekstkilder, det understøtter:

erklæret temperaturintervalkilder
0 til 1Anthropic, Google, Meta, Cerebras, PaLM
0 til 1,5Mistral
0 til 2OpenAI, DeepSeek, xAI

Ordet er det samme; skalaen er det ikke. En "temperatur på 1" er den uændrede fordeling hos én og den maksimalt tilladte varme hos en anden, og halvdelen af kataloget kan ikke udtrykke den værdi, den anden halvdel behandler som neutral-plus-lidt. Resten af knapperne er lige så ujævne: OpenAI-, DeepSeek- og xAI-posterne tager presence og frequency penalties og ingen topK; Google-, Meta-, Cerebras- og PaLM-posterne tager topK og ingen straffe; Anthropic tager topK, topP og stopsekvenser og ingen straffe; og præcis én af de ni — Mistral — tager en seed. At sende en parameter, en udbyder ikke implementerer, giver typisk slet ingen fejl: anmodningen lykkes, knappen gør ingenting, og du konkluderer, at indstillingen ingen effekt har.

Og læg mærke til, hvad sådan en fil er: en påstand om en andens API, skrevet på én bestemt dag, som intet verificerer bagefter. Et katalog, der siger 0 til 1 for en udbyder, som nu accepterer 0 til 2, vil lydløst cappe hver anmodning.

To kontroller mere hører til samme familie. logprobs, hvor det tilbydes, returnerer log-sandsynlighederne for den valgte token og ofte de få bedste alternativer — det eneste vindue, du får ind til den fordeling, dette kapitel handler om, og grundlaget for enhver confidence-heuristik bygget på en lukket model. Og maximum tokens plus stopsekvenser afslutter generering helt uden reference til sandsynlighed: en hård grænse og et string-match. Begge dukker op som finish_reason fra Kapitel 14, hvor length betyder, at dit svar blev skåret midt i en sætning af et budget, ikke afsluttet af modellen.

Sæt en seed, og sampling bliver reproducerbar. Den del er reel, og den er nem at verificere:

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-identisk inden for en seed, forskellig på tværs af seeds, præcis som annonceret. Så det, seeden fastlåser, er det tilfældige træk i den sidste linje af sample-funktionen — hvilken token der bliver valgt, givet en fordeling.

Det, den ikke fastlåser, er fordelingen. Og det er dér, problemet ligger, fordi vektoren af logits, som din model producerer, ikke er et matematisk objekt; den er outputtet af milliarder af floating-point-additioner, og de har en rækkefølge.

Kapitel 2 efterlod dette eksperiment klar. De samme million float32-tal, summeret i forskellige grupperinger:

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

Se på den sidste linje. Antallet af chunks ændrer svaret. Det er ikke en kuriositet ved numpy; det er mekanismen, for når en inference-server splitter en reduktion over flere eller færre parallelle enheder, gør den præcis dette. Og en server splitter efter, hvor mange anmodninger den betjener.

Her er den effekt på selve modellen. Samme prompt, samme forward pass, den eneste forskel er, hvor mange andre anmodninger der tilfældigvis var i batchen:

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

Kørt alene er modellen perfekt deterministisk — tyve pass, identiske ned til bitten. Læg den identiske prompt i en batch med urelaterede anmodninger, og 97 % af dens logits ændrer sig. Intet ved din anmodning ændrede sig. En andens anmodning ankom.

Nu den ærlige del, fordi dette normalt fortælles, som om det var slutningen på historien. En ændring på 2.5×1052.5 \times 10^{-5} ændrer kun outputtet, hvis to kandidat-tokens lå inden for den afstand af hinanden. Over 717 genereringstrin på tværs af tolv prompts var det mindste gab mellem de to øverste logits 2.5×1032.5 \times 10^{-3} — hundrede gange større end perturbationen — og intet trin var tæt nok på at flippe. Så på denne model, i float32, på en laptop, flyttede batching hver logit og ændrede ingen token.

Det er en beskrivelse af gunstige forhold, ikke en beroligelse, og én ændring af de forhold er nok:

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

Seks af otte svar divergerer, og ét af dem forringes voldsomt. Kapitel 2s tabel siger hvorfor: bfloat16 beholder 7 mantissebits, så nær en logit-størrelse på 16 ligger de repræsenterbare værdier 0,125 fra hinanden — 16,0, så 16,125, så 16,25 — og afrunding kan flytte en logit med op til 0,0625. Imens havde 4,7 % af genereringstrinnene målt ovenfor et top-to-gab under 0,1. Det er hele forskellen mellem de to eksperimenter: i float32 var perturbationen hundrede gange mindre end den nærmeste beslutning, og i bfloat16 er den samme størrelse. Produktions-inference kører i 16-bit, på hardware med fused kernels og reduktionsrækkefølger, ingen lover at holde stabile. Om "den numeriske støj er ubetydelig", er et spørgsmål om præcision og hardware, ikke om modellen.

Så her er de fire årsager, katalogiseret som Kapitel 9 lovede:

Floating-point-addition er ikke associativ

Link til afsnittet: Floating-point-addition er ikke associativ

Kapitel 2s boks. Rækkefølgen af en sum ændrer dens værdi, så enhver ændring i, hvordan en reduktion splittes, ændrer logits. Det er substratet; de tre andre er måder at ændre rækkefølgen på.

Dynamic batching grupperer din anmodning med fremmedes

Link til afsnittet: Dynamic batching grupperer din anmodning med fremmedes

Continuous batching, fra Kapitel 13, er grunden til, at inference er økonomisk muligt — og det betyder, at formen på de matricer, dine tokens flyder igennem, afhænger af trafikken. Målt ovenfor: 147.321 logits flyttede sig, fordi batchstørrelsen ændrede sig.

Mixture-of-experts routing afhænger af batchen

Link til afsnittet: Mixture-of-experts routing afhænger af batchen

Kapitel 9s boks sagde det allerede. Routeren træffer et diskret valg pr. token pr. lag, underlagt kapacitetsgrænser pr. expert beregnet over batchen. En token, der alene ville være gået til expert 7, går i selskab til expert 12. Det er ikke en afrundingsforskel; det er et andet sæt vægte.

En versionsstreng som -latest er en pointer, og pointers bliver peget om. Udbydere opdaterer også serving-stacken under en fast versionsidentifier. Ingen af delene annonceres med en granularitet, der ville lade dig korrelere det med, at dit eget output ændrede sig.

OpenAIs seed-parameter er ærlig om dette på den eneste måde, den kan være: den sendes sammen med et system_fingerprint-felt, der identificerer backend-konfigurationen, og dokumentationen siger, at determinisme er best-effort, og at et ændret fingerprint betyder, at resultater kan afvige. Læs det som det, det er — en udbyder, der fortæller dig, at den kontrollerer alle fire årsager ovenfor, at du ikke kontrollerer nogen af dem, og at det eneste, den kan tilbyde, er at fortælle dig bagefter, at noget flyttede sig.

Alt her har handlet om en knap og dens konsekvenser. Træd ét niveau tilbage, og det sværere problem viser sig: objektet, vi har tunet, er en sandsynlighedsfordeling, og sandsynlighedsfordelinger har ikke et interface.

Et function call har ét. En databaserække har ét. En POST-handler, der forventer en JSON-body med tre påkrævede felter, har ét, og den afviser alt andet. Mellem modellen og hver anden komponent i dit system sidder en kontrakt, som den ene side ikke kan give løfter om: modellen vil producere noget, trukket fra en fordeling, du har formet, men ikke fastlåst, og koden på den anden side kræver en værdi af en kendt type, ellers fejler den.

Broen mellem de to verdener bygges af dette kapitels materiale snarere end af parsing og retries. Hvis en token ville bryde den krævede struktur, sampler du den ikke og håber — du sætter dens logit til -\infty, før softmax nogensinde ser den. Constrained decoding er en maske over den samme vektor, vi har brugt dette kapitel på at omforme, og den gør "svar venligst i JSON" fra en anmodning til en garanti.

Kapitel 18 er den kontrakt: tool calling, JSON Schema, strukturerede outputs, og hvad der skal til for at gøre et deterministisk system sikkert at bygge oven på et probabilistisk.


Alle målinger i dette kapitel kommer fra Qwen/Qwen2.5-0.5B-Instruct på CPU, float32 medmindre andet er angivet, med sampling implementeret som skrevet i det valgfrie afsnit i stedet for delegeret til et bibliotek. Det er en lille model, og de konkrete værdier er dens; mekanismerne er ikke. Von Platens How to generate text with different decoding methods (Hugging Face, 2020) er artiklen, denne måles op imod, og er stadig den bedste korte introduktion til det samme materiale. For afsnittet om determinisme: PyTorchs noter om reproducerbarhed beskriver, hvad en seed gør og ikke gør på én maskine, OpenAIs dokumentation af seed og system_fingerprint beskriver, hvad en udbyder kan og ikke kan love, og Thinking Machines' diskussion fra 2025 af batch-invariant kernels er den klareste offentlige forklaring på, hvorfor det er muligt, men ikke gratis, at fikse dette på inference-serverniveau.

  1. Ackley, D. H., Hinton, G. E. og Sejnowski, T. J. A Learning Algorithm for Boltzmann Machines. Cognitive Science 9(1), s. 147–169 (1985), hvor temperaturen i en softmax kommer fra statistisk fysik. Hinton, G., Vinyals, O. og Dean, J., Distilling the Knowledge in a Neural Network, arXiv:1503.02531 (2015), afsnit 2, er der, hvor den samme parameter dukker op igen i moderne deep learning — som en måde at eksponere en lærers fulde fordeling på, hvilket er Kapitel 13s soft labels snarere end dette kapitels sampling.

  2. Guo, C., Pleiss, G., Sun, Y. og Weinberger, K. Q. On Calibration of Modern Neural Networks. arXiv:1706.04599 (2017). Forveksl ikke dette med temperaturen i dette kapitel. Temperature scaling fitter én enkelt værdi på et valideringssæt, så modellens selvtillid matcher dens nøjagtighed; det er en post-hoc-kalibreringsmetode anvendt på en klassifikators outputs. Temperature sampling er en runtime-kontrol over, hvordan en generator trækker tokens. Samme formel, forskelligt formål, og ingen fælles værdi.

  3. Holtzman, A., Buys, J., Du, L., Forbes, M. og Choi, Y. The Curious Case of Neural Text Degeneration. arXiv:1904.09751 (2019). Introducerer nucleus sampling og målingen af, at maksimeringsbaseret decoding producerer tekst, hvis sandsynlighedsprofil slet ikke ligner menneskelig tekst. 2

  4. Fan, A., Lewis, M. og Dauphin, Y. Hierarchical Neural Story Generation. arXiv:1805.04833 (2018). Artiklen, der populariserede top-k sampling.

  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. og Cotterell, R. Locally Typical Sampling. arXiv:2202.00666 (2022).

  7. Keskar, N. S., McCann, B., Varshney, L. R., Xiong, C. og Socher, R. CTRL: A Conditional Transformer Language Model for Controllable Generation. arXiv:1909.05858 (2019). Afsnit 4.1 er den oprindelige repetition penalty — den, der dividerer.


Skabt af

David Vicente Campos

Grundlægger af NeuraLIA Labs og medstifter af MyRealFood

Jeg er dataingeniør fra Universitetet i León. Jeg var med til at stifte MyRealFood, hvor jeg som CTO byggede den app, som millioner af mennesker har brugt til at spise bedre, og jeg grundlagde NeuraLIA Labs, hvor jeg bygger AI-produkter. Her skriver jeg om det, jeg har måttet forstå undervejs, sådan som jeg ville ønske, nogen havde forklaret det for mig.

Mere om forfatteren

Udgivet af NeuraLIA Labs.

Få nye indlæg i din indbakke

AI-nyheder, guides og produktopdateringer — en kort mail, når vi udgiver noget, der er værd at bruge tid på.

Vil du hellere have beskeder? De samme indlæg, her:WhatsApp-fællesskab (åbnes i en ny fane)Telegram-kanal (åbnes i en ny fane)

Kursusindeks

Abstract software decision engine with branching paths, probability nodes, and glowing gates.
jev11 min læsning

Jev AI-modellen er bygget til beslutninger, ikke prosa

TypeSafe AI’s Jev får opmærksomhed, fordi den behandler softwareintelligens som et sandsynlighedsproblem: vælg den rigtige gren, tilføj tillid, og undgå at betale en LLM for at skrive tekst, når kode har brug for en beslutning.

Abstract agent runtime sorting documents, memory blocks and pointer nodes inside a bounded context frame.
context-engineering11 min læsning

Kontekstteknik til langsigtede AI-agenter

Langvarige agenter fejler ikke kun, fordi vinduet er lille. De fejler, når filer, tool-outputs og forældet historik fortrænger den opgave, agenten skulle færdiggøre.

Klar til at lade LIA vælge for dig?

Byg med alle AI-modeller ét sted — kom gratis i gang i dag.