Hoppa till innehållet
17/30Kapitel 17 av 30

Temperatur, top-p och determinismen du inte har

Temperatur delar logits före softmax — och därför är det inte ett kreativitetsreglage. Samma greedy-anrop kan ändå ge två svar.

På den här sidan

Här är samma begäran skickad till samma modell fem gånger. Samma vikter, samma prompt, samma maskin, samma slumpfrö. Det enda som ändras är ett 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 ('$ספטמבר..."

Ingenting gick sönder. Varje token i den sista raden drogs legitimt från modellens egen sannolikhetsfördelning över dess vokabulär på 151 936 poster. Talet som ändrades kallas temperatur, det beskrivs i de flesta dokumentationer som ett kreativitetsreglage, och den beskrivningen är fel på ett sätt som det här kapitlet kan demonstrera snarare än bara påstå.

Det här är också kapitlet där tre tidigare löften ska infrias. Kapitel 4 definierade logit och använde det aldrig riktigt. Kapitel 2:s ruta om flyttal slutade med en instruktion — kom ihåg det här när kapitel 17 frågar varför samma prompt, modell och seed kan producera olika tokens. Och kapitel 9:s ruta om mixture-of-experts lovade en katalog över fyra orsaker till icke-determinism. Alla tre kommer nedan.

Kapitel 4 introducerade logit som en onormaliserad realvärd poäng, en per klass. Kapitel 8 fick en språkmodell att producera en per post i vokabulären. softmax gör den vektorn z\mathbf{z} till sannolikheter:

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

Temperatur kommer in här — namnet är lånat från statistisk fysik, där samma parameter styr hur skarpt en Boltzmannfördelning koncentreras på sina lågenergitillstånd1 — och den delar logits före exponentialen:

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

Den placeringen är hela mekanismen, och det räcker med två rader algebra för att se varför den inte kunde sitta någon annanstans. Anta att du försökte applicera temperatur på sannolikheterna i stället — skala dem med 1/T1/T och normalisera om. Du skulle 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 försvinner. Att skala sannolikheter gör ingenting alls; fördelningen kommer tillbaka oförändrad. Temperatur har bara effekt eftersom den verkar på exponenten, där division med TT före exponentiering är detsamma som att upphöja varje sannolikhet till 1/T1/T — en icke-linjär omformning som ändrar kvoterna mellan posterna snarare än deras gemensamma skala.

Från den placeringen följer båda gränsfallen utan mer arbete. När T0T \to 0 rusar den största logit ifrån resten och pp kollapsar till den enda högst rankade token: greedy decoding. När TT växer går varje zi/Tz_i/T mot noll, varje exponential går mot 1, och fördelningen plattas ut mot uniform över hela vokabulären. Vid exakt T=0T = 0 delar formeln med noll, så varje implementation specialhanterar det till det aritmetiska maximumet — inklusive widgeten nedan, som växlar till argmax vid T0.001T \le 0.001.

En varning, eftersom namnkollisionen orsakar verklig förvirring. Det finns en annan, orelaterad sak som kallas temperatur i machine learning: temperature scaling, en kalibreringsmetod som passar in ett värde på en valideringsmängd så att en klassificerares säkerhet matchar dess träffsäkerhet.2 Samma formel, inget med generering att göra. När papers säger ”temperature” menar de ofta den; det gör aldrig det här kapitlet.

Här är den fördelningen, med aritmetiken framför dig. logits är fasta och rimliga, så talen i texten nedan kan kontrolleras mot det du ser:

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

10 av 10 token klarar filtret och delar på sannolikheten.

Se data som tabell
TokenlogitEfter temperaturEfter filtrering
␣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 och top-k

Tio kandidatfortsättningar av The capital of France is, vid temperatur 1 utan beskärning. ␣Paris håller 96,90 % av massan; ␣banana, längst ned med en logit på 2.6-2.6, får 0,00 %. Dra temperaturen till 0 och en token överlever med 100 %. Dra den till 2 och ␣Paris faller till 69,81 % medan ␣banana klättrar till 0,17 % — modellens förkastade token, given verklig sannolikhet av ett reglage som läsaren vred på.

Siffran ␣banana är hela argumentet i miniatyr: att höja temperaturen kan inte ge en modell en idé den inte hade. logits är redan beräknade, rankningen är redan fast, och temperatur bevarar den exakt — ingen mängd värme flyttar någonsin en lägre rankad token över en högre rankad. Allt den gör är att omfördela massa nedåt i den rankning modellen själv producerade. Hög temperatur gör inte en modell mer uppfinningsrik; den gör det mer sannolikt att den avger tokens som den själv rankade som dåliga.

På ett verkligt vokabulär slutar det här vara en kuriositet och blir orsaken till att högtempererad output är oanvändbar. Mätt på Qwen/Qwen2.5-0.5B-Instruct, ett forward pass, prompten ovan, med räkning av hur många tokens som krävs för att samla en given andel av sannolikhetsmassan:

temperaturtop-1-sannolikhetentropitokens som håller 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 nedersta raden långsamt. Vid T=2T = 2, på en fråga med exakt ett korrekt svar, delar 32 966 olika tokens på de översta 90 % av sannolikhetsmassan. Det är inte ett bredare kreativt utrymme. Det är en modell som, av aritmetiken, har fått instruktionen att behandla en koreansk partikel och en C++-identifierare som levande alternativ för ordet efter A:. Skräpet i öppningsblocket är den direkta konsekvensen, och det är inte en bugg i modellen eller biblioteket — det är vad begäran bad om.

Det användbara intervallet är smalt och beror på uppgiften snarare än smak. På en faktabaserad fråga är svaret en token och all värme över ungefär 1,2 injicerar fel utan nytta. På en öppen fråga finns det verkligen mer än en bra fortsättning, och lite värme köper variation som fortfarande är flytande:

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

Vid 1,3 har modellen hittat på ett egennamn och en mening som inte går att tolka. Bandet mellan ”identiskt varje gång” och ”osammanhängande” är ungefär 0,6 till 1,1 för den här modellen på den här uppgiften, och det ärligaste rådet är att du hittar det genom att mäta på din uppgift, inte genom att kopiera ett tal från ett blogginlägg.

Varför den mest sannolika texten är dålig text

Länk till avsnittet: Varför den mest sannolika texten är dålig text

Det finns en uppenbar fråga gömd under allt detta: om modellen har en sannolikhetsfördelning och en token är mest sannolik, varför inte alltid ta den? Greedy decoding är gratis, reproducerbart och behöver inga parametrar.

För att resultatet är det här:

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 %

Åtta meningar, en mening. Nästan nio av tio fyratokenfönster hade redan förekommit tidigare i samma output. Det här är neural text degeneration, namngivet och förklarat av Holtzman et al. i paperet som introducerade top-p.3 Modellen är inte trasig; att maximera sekvenssannolikhet är helt enkelt fel mål för öppen text. Mänskligt skrivande är inte den mest sannolika ordsekvensen — det bär överraskning, dess per-token-sannolikhet vandrar, faller och återhämtar sig — medan vägen med maximal sannolikhet är en fixpunkt som, när den väl har trätts in i, inte har någon anledning att lämna.

Det är därför sampling finns över huvud taget. Det är också, och det här är delen som ofta utelämnas, inte en universell lag. Kapitel 12 mätte 24 av 24 rätt på tvåstegs ordproblem med vanlig greedy decoding, och sampling vid temperatur 0,8 sänkte det till 81 %; self-consistency spenderade sedan sex gånger fler tokens för att klättra tillbaka till där greedy redan var. Båda sakerna är sanna samtidigt:

Öppen generering. Det finns ingen enda rätt fortsättning, så den mest sannolika är en fälla — den loopar, och 87,6 % av den är kopierad från sig själv. Sampla.

Uppgifter med ett rätt svar. Det finns en enda korrekt fortsättning, så att dra något annat är att dra ett fel. Kapitel 12:s 100 % blev 81 % av exakt den anledningen. Sampla inte.

De flesta produktionsprompts är av den andra sorten och konfigureras som den första, eftersom temperaturen lämnades på vad exempelkoden råkade använda.

Två sätt att skära, och bara ett av dem anpassar sig

Länk till avsnittet: Två sätt att skära, och bara ett av dem anpassar sig

Sampling från hela fördelningen är inte vad någon faktiskt gör, eftersom svansen är enorm och full av nonsens. Något måste skäras bort. Det finns två klassiska svar och de skiljer sig åt på en punkt som avgör allt.

Top-k behåller ett fast antal kandidater. Sortera efter sannolikhet, behåll de första kk, kasta resten, normalisera om.4 Top-p, också kallat nucleus sampling, behåller en fast mängd massa: ta tokens i fallande ordning tills deras kumulativa sannolikhet når pp, och stanna.3 Formellt är kärnan den minsta mängden VpV_p med

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

Skillnaden låter kosmetisk och är det inte, eftersom de två prompts du skickar under samma minut har helt olika fördelningsformer. Båda dessa är samma modell vid temperatur 1:

Q: What is the capital of France?\nA:Once upon a time,
top-1-sannolikhet96,01 %25,39 %
tokens som håller 90 % av massan1467
top-k = 40 behåller99,61 % av massan78,87 % av massan
massa i rank 2 till 403,61 %53,48 %
token på rank 40␣Av, 0,0093 %␣Dr, 0,128 %

Ett fast kk, två fel åt motsatta håll. På den faktabaserade prompten släpper k=40k = 40 in 39 tokens som tillsammans är värda 3,6 % — den släpper igenom skräp, inklusive en kandidat på nio tusendels procent, eftersom regeln räknar platser och inte evidens. På berättelseprompten kastar samma k=40k = 40 bort 21 % av massan som modellen verkligen tilldelade, eftersom den verkliga kärnan där är 467 tokens bred.

Top-p får exakt ett tal att göra båda jobben. Sätt p=0.9p = 0.9 och det behåller 1 token på den första prompten och 467 på den andra, eftersom det ställer en fråga om fördelningen i stället för att påtvinga den ett antal. Se den anpassningen direkt — samma snitt, fyra temperaturer:

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

3 av 10 token klarar filtret och delar på sannolikheten.

Se data som tabell
TokenlogitEfter temperaturEfter filtrering
␣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 och top-k

Top-p vid 0,90 med temperaturen på 1,5: tre av de tio tokens överlever och delar på massan, ␣Paris omnormaliserad till 91,10 %. Flytta nu bara temperaturen. Vid 0,7 lämnar samma 0,90 en överlevare — en så smal kärna är greedy decoding under ett annat namn. Vid 2,0 lämnar den fem. Snittet flyttade aldrig; formen under det gjorde det.

Den widgeten avgör också en missuppfattning som är värd att namnge, eftersom den kostar människor riktiga pengar. På en säker fördelning är top_p = 0.9 inte ”lite variation”. Det är greedy. Vid temperatur 1 håller den ledande token här 96,90 %, vilket redan är över 0,9, så kärnan är en token bred och inget annat kan någonsin dras. Team sätter top_p till 0,9 i tron att de har lossat något och undrar sedan varför varje svar är identiskt.

Sätt top-k i stället och det motsatta felet är lika synligt:

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

5 av 10 token klarar filtret och delar på sannolikheten.

Se data som tabell
TokenlogitEfter temperaturEfter filtrering
␣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 och top-k

Top-k vid 5, ingen top-p. Fem tokens överlever vid varje temperatur, eftersom fem var vad som efterfrågades. Vid temperatur 1, som visas, är de fyra kandidaterna under ␣Paris värda 2,79 % tillsammans. Sänk till 0,7 och samma fyra är värda 0,38 % — snittet är teater, och modellen är i praktiken greedy. Höj till 2,0 och de är värda 22,54 %. Identisk inställning, identiskt antal överlevare, tre helt olika beteenden, och inget i begäran berättar vilket du får.

Straffen, med formlerna, eftersom det är endemiskt att blanda ihop dem

Länk till avsnittet: Straffen, med formlerna, eftersom det är endemiskt att blanda ihop dem

Tre olika mekanismer färdas under liknande namn, de gör olika saker, och skillnaden är mätbar. Låt cic_i vara antalet gånger token ii redan har förekommit.

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

Dra av en konstant från varje token som över huvud taget har förekommit. Att förekomma en gång och att förekomma fyrtio gånger straffas identiskt. Det är en brytare, inte ett reglage.

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

Dra av proportionellt mot antalet. En token som använts fyra gånger straffas fyra gånger så hårt som en token som använts en gång, och trycket byggs på när texten växer.

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}

Originalet, från CTRL-paperet.7 Det delar i stället för att subtrahera, med teckenfallet som behövs eftersom division av en negativ logit skulle göra den större. Dess styrka beror därför på storleken på logit, vilket betyder att samma ρ\rho slår olika vid olika punkter i samma mening.

Samma degenererade fortsättning från tidigare, med var och en applicerad. ”Ändrade steg” räknar hur många av de 120 genereringsstegen som valde en annan token än den ostraffade modellen skulle ha gjort. Körningen är 120 steg här mot 140 i blocket ovan, vilket är varför den ostraffade baslinjen visar 85,5 % snarare än 87,6 %:

inställningupprepade 4-gramändrade steg
inget85,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 saker faller ut. Presence vid 0,5 ändrade tre beslut av 120 och minskade repetitionen med en fjärdedel — loopen hölls ihop av en handfull tokens. Frequency vid 0,5 ändrade fyra gånger så många beslut med en mycket större effekt, eftersom antalsmultiplikatorn fortsätter växa medan presence-konstanten inte gör det. Och CTRL-straffet vid det ofta kopierade värdet 1,2 skrev om 35 av 120 beslut, vilket inte är en knuff; det är en annan modell.

Det sista talet är upplägget för felet ingen varnar för.

Kod upprepar. Tabeller upprepar. Listor upprepar. Strukturerad output upprepar per definition — det är vad struktur är. Ett straff kan inte se skillnad på en modell som fastnat i en loop och en modell som korrekt avger den fjärde raden i en tabell, eftersom båda ser ut som en token som förekommer igen.

Samma tre uppgifter, genererade på tre sätt:

uppgiftingetfrequency 0,5repetition 1,2
markdown-tabell, 6 rader0 / 56 ändrade steg0 / 562 / 62
Python-funktion0 / 930 / 9310 / 110
punktlista, 1 till 120 / 500 / 500 / 50

Frequency-straffet vid 0,5 visade sig vara ofarligt på alla tre, vilket är ett användbart och lite överraskande resultat, och det säger något precist: eftersom inget beslut ändrades måste de strukturella tokens ha vunnit sina positioner med mer än straffet drog av, även efter att ha förekommit fem och sex gånger. CTRL-straffet, som delar i stället, rubbar dem, och här är vad det producerade:

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

Justeringen faller isär: mängden utfyllnad i varje cell ändras från rad till rad, eftersom raden av mellanslag före den avslutande pipen är exakt den sorts repetition som straffet finns för att bryta. Kosmetiskt, och det kostade sex extra tokens. Python-fallet är inte kosmetiskt:

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,

Straffet knuffade modellen bort från total — redan använt i docstringen — till total_sum, fyllde ut output med påhittade kommentarer för att spendera sin budget på oanvända tokens, och gick sedan in i ett trearguments range med ett steg. Kommentaren säger incrementing by 2 each time, vilket är fel för en summa av kvadrater från 1 till nn. Ett repetition penalty producerade felaktig kod från en prompt som besvarades korrekt utan det.

Regeln som följer är kort: straff är för öppen prosa, och de bör vara avstängda för kod, strukturerad output, tabelldata och allt med ett schema. Kapitel 18 handlar om exakt den andra kategorin.

Tillämpningsordningen, och varför den ändrar svaret

Länk till avsnittet: Tillämpningsordningen, och varför den ändrar svaret

Varje verklig implementation applicerar dessa i en specifik sekvens:

straff → temperatur → top-k → top-p → sample

Det här är inte godtycklig bokföring, och att byta plats på två steg producerar genuint olika fördelningar. Två mätningar, båda på den faktabaserade prompten.

Att skära före eller efter temperaturen. Kärnan beräknas på den fördelning den får, och temperatur ändrar den fördelningen radikalt:

top-p 0,9 efter temperaturtop-p 0,9 före temperatur
T=1.0T = 1.01 token1 token
T=1.5T = 1.5353 tokens1 token
T=2.0T = 2.032 966 tokens1 token

Vid T=2T = 2 ger samma nominella inställning en kandidatuppsättning på 32 966 eller 1, helt beroende på vilket steg som körs först. Om du någon gång undrat varför höjd temperatur ”inte gör något” hos en provider och förstör output hos en annan med samma två tal, är den här tabellen ett rimligt svar.

Att straffa före eller efter temperaturen. Att subtrahera ett straff α\alpha och sedan dela med TT ger ett effektivt straff på α/T\alpha/T; att dela först och sedan subtrahera ger α\alpha. Med ett presence penalty på 1,0 applicerat på den ledande token:

temperaturstraffa, temperera sedantemperera, straffa sedan
0,599,858 %99,948 %
1,089,839 %89,839 %
2,010,783 %6,830 %

Identiska vid T=1T = 1, som de måste vara. En faktor 1,58 isär vid T=2T = 2. ”Presence penalty 1,0” är inte en väldefinierad mängd straff om du inte också vet var temperaturen appliceras, och ingen API dokumenterar detta.

Visa detaljer

Valfritt: hela pipeline, i ordningen ovan.

Sexton rader, och allt i det här kapitlet finns i dem. Det är samma beräkning som widgeten utför, på en verklig logit-vektor i stället för tio fasta 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-raden är den kumulativa massan exklusive aktuell token, vilket gör att kärnan inkluderar den token som passerar tröskeln i stället för att stanna precis före den. Få det fel med ett steg och top_p = 0.9 blir tyst ett något snävare snitt än i alla andra implementationer.

Det här är ett av få ställen i kursens andra halva där Python är rätt språk, och skälet är strukturellt snarare än stilistiskt: varje rad ovan kräver hela vektorn av logits i dina händer, och över en HTTP API existerar den vektorn inte. Du kan skicka temperature och top_p till en provider; du kan inte implementera dem, och du kan inte se vad de gjorde.

Varje provider tar en annan delmängd av dessa kontroller, med olika intervall, och ignorerar resten i tystnad. Det här är inget abstrakt klagomål. Varje applikation som erbjuder ett val av modell måste skriva ned skillnaderna någonstans, och filen där den gör det är en karta över inkompatibiliteten. Här är vad en sådan katalog deklarerar för en enda parameter över de nio textkällor den stöder:

deklarerat temperaturintervallkällor
0 till 1Anthropic, Google, Meta, Cerebras, PaLM
0 till 1,5Mistral
0 till 2OpenAI, DeepSeek, xAI

Ordet är detsamma; skalan är det inte. En ”temperatur på 1” är den omodifierade fördelningen hos en och högsta tillåtna värme hos en annan, och halva katalogen kan inte uttrycka värdet som den andra halvan behandlar som neutralt-plus-lite. Resten av reglagen är lika ojämna: posterna för OpenAI, DeepSeek och xAI tar presence och frequency penalties och ingen topK; posterna för Google, Meta, Cerebras och PaLM tar topK och inga straff; Anthropic tar topK, topP och stop sequences och inga straff; och exakt en av de nio — Mistral — tar en seed. Att skicka en parameter som en provider inte implementerar ger i allmänhet inget fel alls: begäran lyckas, reglaget gör ingenting, och du drar slutsatsen att inställningen saknar effekt.

Och lägg märke till vad en sådan fil är: ett påstående om någon annans API, skrivet en viss dag, som ingenting verifierar efteråt. En katalog som säger 0 till 1 för en provider som nu accepterar 0 till 2 kommer tyst att kapa varje begäran.

Två kontroller till hör till samma familj. logprobs, där det erbjuds, returnerar log-sannolikheterna för vald token och ofta de främsta alternativen — det enda fönstret du får mot fördelningen det här kapitlet handlar om, och grunden för varje confidence heuristic byggd på en sluten modell. Och maximum tokens plus stop sequences avslutar generering utan hänsyn till sannolikhet alls: ett hårt tak och en strängmatchning. Båda syns som finish_reason från kapitel 14, där length betyder att ditt svar kapades mitt i meningen av en budget, inte avslutades av modellen.

Sätt en seed och sampling blir reproducerbart. Den delen är verklig, och den är lätt att verifiera:

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-identiskt inom en seed, olika mellan seeds, precis som utlovat. Så det seed fixerar är slumpdragningen i den sista raden av sample-funktionen — vilken token som väljs givet en fördelning.

Det den inte fixerar är fördelningen. Och det är där problemet finns, eftersom vektorn av logits som din modell producerar inte är ett matematiskt objekt; den är resultatet av miljarder flyttalsadditioner, och de har en ordning.

Kapitel 2 lämnade det här experimentet redo. Samma miljon float32-tal, summerade i olika grupperingar:

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

Titta på sista raden. Antalet chunks ändrar svaret. Det är inte en kuriositet om numpy; det är mekanismen, för när en inference server delar en reduktion över fler eller färre parallella enheter gör den precis detta. Och en server delar upp utifrån hur många begäranden den hanterar.

Här är den effekten på själva modellen. Samma prompt, samma forward pass, enda skillnaden är hur många andra begäranden som råkade finnas 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örd ensam är modellen perfekt deterministisk — tjugo pass, identiska bit för bit. Lägg samma prompt i en batch med orelaterade begäranden och 97 % av dess logits ändras. Ingenting i din begäran ändrades. Någon annans begäran kom in.

Nu den ärliga delen, eftersom detta vanligtvis berättas som om det vore slutet på historien. En ändring på 2.5×1052.5 \times 10^{-5} ändrar bara output om två kandidat-tokens låg inom det avståndet från varandra. Över 717 genereringssteg över tolv prompts var det minsta gapet mellan de två högsta logits 2.5×1032.5 \times 10^{-3} — hundra gånger större än perturbationen — och inget steg var tillräckligt nära för att flippa. Så på den här modellen, i float32, på en laptop, flyttade batching varje logit och ändrade ingen token.

Det är en beskrivning av gynnsamma förhållanden, inte ett lugnande besked, och en enda ändring av de förhållandena räcker:

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

Sex av åtta svar divergerar, och ett av dem försämras rejält. Kapitel 2:s tabell säger varför: bfloat16 behåller 7 mantissabitar, så nära en logit-storlek på 16 ligger de representerbara värdena 0,125 isär — 16,0, sedan 16,125, sedan 16,25 — och avrundning kan flytta en logit med upp till 0,0625. Samtidigt hade 4,7 % av genereringsstegen ovan ett topp-två-gap under 0,1. Det är hela skillnaden mellan de två experimenten: i float32 var perturbationen hundra gånger mindre än det närmaste beslutet, och i bfloat16 är den av samma storlek. Produktionens inference körs i 16-bit, på hårdvara med fused kernels och reduktionsordningar som ingen lovar att behålla. Om ”det numeriska bruset är försumbart” är en fråga om precision och hårdvara, inte om modellen.

Alltså, de fyra orsakerna, katalogiserade som kapitel 9 lovade:

Kapitel 2:s ruta. Ordningen på en summa ändrar dess värde, så varje ändring i hur en reduktion delas upp ändrar logits. Detta är substratet; de andra tre är sätt att ändra ordningen.

Dynamic batching grupperar din begäran med främlingars

Länk till avsnittet: Dynamic batching grupperar din begäran med främlingars

Continuous batching, från kapitel 13, är varför inference är överkomligt — och det betyder att formen på matriserna som dina tokens flyter igenom beror på trafiken. Mätt ovan: 147 321 logits flyttades eftersom batchstorleken ändrades.

Kapitel 9:s ruta sade det redan. Routern gör ett diskret val per token per lager, föremål för kapacitetsgränser per expert som beräknas över batchen. En token som ensam skulle ha gått till expert 7 går till expert 12 i sällskap. Det här är ingen avrundningsskillnad; det är en annan uppsättning vikter.

En versionssträng som -latest är en pekare, och pekare pekas om. Providers uppdaterar också serving-stacken under en fast versionsidentifierare. Inget av detta annonseras på den granularitet som skulle låta dig korrelera det med att din egen output ändras.

OpenAI:s seed-parameter är ärlig om detta på det enda sätt den kan vara: den levereras tillsammans med ett system_fingerprint-fält som identifierar backendkonfigurationen, och dokumentationen säger att determinism är best-effort och att ett ändrat fingerprint betyder att resultaten kan skilja sig. Läs det för vad det är — en provider som säger att den kontrollerar alla fyra orsakerna ovan, att du inte kontrollerar någon av dem, och att det enda den kan erbjuda är att säga i efterhand att något flyttade sig.

Allt här har handlat om ett reglage och dess konsekvenser. Ta ett steg tillbaka och det svårare problemet framträder: objektet vi har stämt in är en sannolikhetsfördelning, och sannolikhetsfördelningar har inget interface.

Ett function call har ett. En databasrad har ett. En POST-handler som förväntar sig en JSON body med tre obligatoriska fält har ett, och den avvisar allt annat. Mellan modellen och varje annan komponent i ditt system sitter ett kontrakt som ena sidan inte kan ge löften om: modellen kommer att producera något, draget från en fördelning som du har format men inte fixerat, och koden på andra sidan behöver ett värde av en känd typ, annars kastar den.

Bron mellan de två världarna byggs av materialet i det här kapitlet snarare än av parsing och retries. Om en token skulle bryta den krävda strukturen samplar du den inte och hoppas — du sätter dess logit till -\infty innan softmax någonsin ser den. Constrained decoding är en mask över samma vektor som vi har ägnat kapitlet åt att forma om, och den gör ”svara i JSON, tack” från en begäran till en garanti.

Kapitel 18 är det kontraktet: tool calling, JSON Schema, structured outputs, och vad som krävs för att göra ett deterministiskt system säkert att bygga ovanpå ett probabilistiskt.


Alla mätningar i det här kapitlet kommer från Qwen/Qwen2.5-0.5B-Instruct på CPU, float32 om inget annat anges, med sampling implementerad som skrivet i det valfria avsnittet snarare än delegerad till ett bibliotek. De är en liten modell, och de specifika värdena är dess; mekanismerna är det inte. Von Platens How to generate text with different decoding methods (Hugging Face, 2020) är artikeln som den här mäts mot och är fortfarande den bästa korta introduktionen till samma material. För determinismavsnittet: PyTorchs reproducibility notes beskriver vad en seed gör och inte fixerar på en enda maskin, OpenAI:s dokumentation av seed och system_fingerprint beskriver vad en provider kan och inte kan lova, och Thinking Machines diskussion från 2025 om batch-invariant kernels är den tydligaste offentliga redogörelsen för varför det är möjligt men inte gratis att fixa detta på inference-servernivå.

  1. Ackley, D. H., Hinton, G. E. och Sejnowski, T. J. A Learning Algorithm for Boltzmann Machines. Cognitive Science 9(1), s. 147–169 (1985), där temperaturen i en softmax kommer från statistisk fysik. Hinton, G., Vinyals, O. och Dean, J., Distilling the Knowledge in a Neural Network, arXiv:1503.02531 (2015), avsnitt 2, är där samma parameter återkommer i modern deep learning — som ett sätt att exponera en lärares fulla fördelning, vilket är kapitel 13:s soft labels snarare än det här kapitlets sampling.

  2. Guo, C., Pleiss, G., Sun, Y. och Weinberger, K. Q. On Calibration of Modern Neural Networks. arXiv:1706.04599 (2017). Blanda inte ihop detta med temperaturen i det här kapitlet. Temperature scaling passar in ett enda värde på en valideringsmängd så att modellens säkerhet matchar dess träffsäkerhet; det är en post-hoc-kalibreringsmetod som appliceras på en klassificerares outputs. Temperature sampling är en runtime-kontroll över hur en generator drar tokens. Samma formel, olika syfte, och inget gemensamt värde.

  3. Holtzman, A., Buys, J., Du, L., Forbes, M. och Choi, Y. The Curious Case of Neural Text Degeneration. arXiv:1904.09751 (2019). Introducerar nucleus sampling och mätningen som visar att maximiseringsbaserad decoding producerar text vars sannolikhetsprofil inte alls liknar mänsklig text. 2

  4. Fan, A., Lewis, M. och Dauphin, Y. Hierarchical Neural Story Generation. arXiv:1805.04833 (2018). Paperet som populariserade 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. och Cotterell, R. Locally Typical Sampling. arXiv:2202.00666 (2022).

  7. Keskar, N. S., McCann, B., Varshney, L. R., Xiong, C. och Socher, R. CTRL: A Conditional Transformer Language Model for Controllable Generation. arXiv:1909.05858 (2019). Avsnitt 4.1 är det ursprungliga repetition penalty — det som delar.


Skapad av

David Vicente Campos

Grundare av NeuraLIA Labs och medgrundare av MyRealFood

Jag är dataingenjör från Universitetet i León. Jag var med och grundade MyRealFood, där jag som CTO byggde appen som miljontals människor har använt för att äta bättre, och jag grundade NeuraLIA Labs, där jag bygger AI-produkter. Här skriver jag om det jag har behövt förstå längs vägen, så som jag önskar att någon hade förklarat det för mig.

Mer om författaren

Publicerad av NeuraLIA Labs.

Få nya inlägg i din inkorg

AI-nyheter, guider och produktuppdateringar — ett kort mejl när vi publicerar något som är värt din tid.

Kursindex

Abstract software decision engine with branching paths, probability nodes, and glowing gates.
jevLästid 11 min

Jevs AI-modell är byggd för beslut, inte prosa

TypeSafe AI:s Jev väcker uppmärksamhet eftersom den behandlar mjukvaruintelligens som ett sannolikhetsproblem: välj rätt gren, lägg till konfidens och undvik att betala en LLM för att skriva text när koden behöver ett beslut.

Abstract agent runtime sorting documents, memory blocks and pointer nodes inside a bounded context frame.
context-engineeringLästid 11 min

Kontextteknik för AI-agenter med lång horisont

Långkörande agenter misslyckas inte bara för att fönstret är litet. De misslyckas när filer, verktygsutdata och gammal historik tränger undan uppgiften agenten skulle slutföra.

Redo att låta LIA välja åt dig?

Bygg med alla AI-modeller på ett ställe – kom igång gratis i dag.