Zum Inhalt springen
17/30Kapitel 17 von 30

Temperature, Top-p und der Determinismus, den du nicht hast

Temperature teilt die logits vor dem softmax — und genau das widerlegt die Idee vom Kreativitätsregler. Gleicher greedy Call, zwei Antworten.

Auf dieser Seite

Hier ist dieselbe Anfrage fünfmal an dasselbe Modell gesendet. Gleiche Gewichte, gleicher prompt, gleiche Maschine, gleicher random seed. Das Einzige, was sich ändert, ist eine Zahl.

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

Nichts ist kaputt. Jeder token in der letzten Zeile wurde legitim aus der eigenen Wahrscheinlichkeitsverteilung des Modells über sein Vokabular mit 151.936 Einträgen gezogen. Die Zahl, die sich geändert hat, heißt Temperature, wird in den meisten Dokumentationen als Kreativitätsregler beschrieben, und diese Beschreibung ist auf eine Weise falsch, die dieses Kapitel zeigen statt nur behaupten kann.

Dies ist auch das Kapitel, in dem drei frühere Versprechen fällig werden. Kapitel 4 definierte den logit und hat ihn nie wirklich ausgegeben. Die Floating-Point-Box aus Kapitel 2 endete mit einer Anweisung — merk dir das, wenn Kapitel 17 fragt, warum derselbe prompt, dasselbe Modell und derselbe seed unterschiedliche tokens erzeugen können. Und die Mixture-of-Experts-Box aus Kapitel 9 versprach einen Katalog von vier Ursachen für Nichtdeterminismus. Alle drei kommen unten an.

Die eine Zeile, an der das ganze Kapitel hängt

Link zum Abschnitt: Die eine Zeile, an der das ganze Kapitel hängt

Kapitel 4 führte den logit als unnormalisierten reellwertigen Score ein, einen pro Klasse. Kapitel 8 ließ ein Sprachmodell einen pro Vokabulareintrag erzeugen. Der softmax macht aus diesem Vektor z\mathbf{z} Wahrscheinlichkeiten:

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

Temperature tritt hier ein — der Name ist aus der statistischen Physik entlehnt, wo derselbe Parameter steuert, wie stark sich eine Boltzmann-Verteilung auf ihre Niedrigenergiezustände konzentriert1 — und sie teilt die logits vor der Exponentialfunktion:

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

Diese Position ist der ganze Mechanismus, und zwei Zeilen Algebra zeigen, warum sie nirgendwo anders liegen könnte. Angenommen, du würdest versuchen, Temperature stattdessen auf die Wahrscheinlichkeiten anzuwenden — sie mit 1/T1/T skalieren und neu normalisieren. Du bekämst

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

Die Konstante kürzt sich heraus. Wahrscheinlichkeiten zu skalieren tut überhaupt nichts; die Verteilung kommt unverändert zurück. Temperature hat nur deshalb einen Effekt, weil sie auf den Exponenten wirkt, wo das Teilen durch TT vor dem Exponentieren dasselbe ist wie jede Wahrscheinlichkeit mit 1/T1/T zu potenzieren — eine nichtlineare Umformung, die die Verhältnisse zwischen Einträgen verändert, nicht ihre gemeinsame Skalierung.

Aus dieser Position folgen beide Grenzfälle ohne weitere Arbeit. Wenn T0T \to 0, entfernt sich der größte logit vom Rest und pp kollabiert auf den einzelnen höchstbewerteten token: greedy decoding. Wenn TT wächst, strebt jedes zi/Tz_i/T gegen null, jede Exponentialfunktion gegen 1, und die Verteilung flacht gegen eine Gleichverteilung über das gesamte Vokabular ab. Bei genau T=0T = 0 teilt die Formel durch null, deshalb behandeln alle Implementierungen diesen Fall speziell als arithmetisches Maximum — einschließlich des Widgets unten, das bei T0.001T \le 0.001 auf argmax umschaltet.

Eine Warnung, weil die Namenskollision echte Verwirrung stiftet. Es gibt in Machine Learning eine zweite, unabhängige Sache namens Temperature: temperature scaling, eine Kalibrierungsmethode, die einen Wert auf einem Validierungsset anpasst, damit das Vertrauen eines Klassifikators zu seiner Genauigkeit passt.2 Gleiche Formel, nichts mit Generierung zu tun. Wenn Papers „temperature“ sagen, meinen sie oft diese; dieses Kapitel tut das nie.

Hier ist diese Verteilung, mit der Arithmetik vor dir. Die logits sind fix und plausibel, daher lassen sich die Zahlen im Text unten gegen das prüfen, was du siehst:

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

10 von 10 Tokens überstehen den Cut und teilen sich die Wahrscheinlichkeit.

Daten als Tabelle anzeigen
TokenlogitNach der TemperaturNach dem Cut
␣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 und top-k

Zehn mögliche Fortsetzungen von The capital of France is, bei Temperature 1 ohne Zuschnitt. ␣Paris hält 96,90 % der Masse; ␣banana, ganz unten mit einem logit von 2.6-2.6, bekommt 0,00 %. Schiebe die Temperature auf 0 und ein token überlebt mit 100 %. Schiebe sie auf 2 und ␣Paris fällt auf 69,81 %, während ␣banana auf 0,17 % steigt — der vom Modell verworfene token, dem ein vom Leser gedrehter Regler echte Wahrscheinlichkeit gibt.

Die Zahl ␣banana ist das ganze Argument im Kleinen: Eine höhere Temperature kann einem Modell keine Idee geben, die es nicht hatte. Die logits sind bereits berechnet, die Rangfolge ist bereits festgelegt, und Temperature erhält sie exakt — keine Menge Hitze verschiebt je einen niedriger bewerteten token über einen höher bewerteten. Alles, was sie tut, ist Masse entlang der Rangfolge umzuverteilen, die das Modell selbst erzeugt hat. Hohe Temperature macht ein Modell nicht erfinderischer; sie macht es wahrscheinlicher, dass es tokens ausgibt, die es als schlecht bewertet hat.

Auf einem echten Vokabular hört das auf, eine Kuriosität zu sein, und wird zum Grund, warum Ausgabe mit hoher Temperature unbrauchbar ist. Gemessen auf Qwen/Qwen2.5-0.5B-Instruct, ein forward pass, der prompt oben, gezählt wurde, wie viele tokens nötig sind, um einen bestimmten Anteil der Wahrscheinlichkeitsmasse zu akkumulieren:

temperaturetop-1 probabilityentropytokens holding 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

Lies die unterste Zeile langsam. Bei T=2T = 2, bei einer Frage mit genau einer richtigen Antwort, teilen sich 32.966 verschiedene tokens die obersten 90 % der Wahrscheinlichkeitsmasse. Das ist kein größerer kreativer Raum. Das ist ein Modell, dem durch Arithmetik gesagt wurde, es solle eine koreanische Partikel und einen C++-Bezeichner als mögliche Optionen für das Wort nach A: behandeln. Der Müll im Anfangsblock ist die direkte Folge, und er ist kein Bug im Modell oder in der Library — er ist das, wonach die Anfrage gefragt hat.

Der nützliche Bereich ist schmal und hängt von der Aufgabe ab, nicht vom Geschmack. Bei einer Faktenfrage ist die Antwort ein token, und jede Hitze über ungefähr 1,2 injiziert nur Fehler. Bei einer offenen Aufgabe gibt es wirklich mehr als eine gute Fortsetzung, und etwas Hitze kauft Vielfalt, die flüssig bleibt:

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

Bei 1,3 hat das Modell einen Eigennamen erfunden und einen Satz, der sich nicht parsen lässt. Das Band zwischen „jedes Mal identisch“ und „inkohärent“ liegt für dieses Modell bei dieser Aufgabe ungefähr zwischen 0,6 und 1,1, und der ehrliche Rat lautet: Finde es durch Messung auf deiner Aufgabe, nicht indem du eine Zahl aus einem Blogpost kopierst.

Warum der wahrscheinlichste Text schlechter Text ist

Link zum Abschnitt: Warum der wahrscheinlichste Text schlechter Text ist

Unter all dem versteckt sich eine naheliegende Frage: Wenn das Modell eine Wahrscheinlichkeitsverteilung hat und ein token am wahrscheinlichsten ist, warum nicht immer diesen nehmen? Greedy decoding ist kostenlos, reproduzierbar und braucht keine Parameter.

Weil das Ergebnis so aussieht:

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 Sätze, ein Satz. Fast neun von zehn Vier-token-Fenstern waren bereits früher in derselben Ausgabe erschienen. Das ist neural text degeneration, benannt und erklärt von Holtzman et al. in dem Paper, das top-p einführte.3 Das Modell ist nicht kaputt; Sequenzwahrscheinlichkeit zu maximieren ist für offenen Text schlicht das falsche Ziel. Menschliches Schreiben ist nicht die wahrscheinlichste Wortfolge — es trägt Überraschung, seine per-token-Wahrscheinlichkeit wandert, fällt und erholt sich — während der Maximum-Probability-Pfad ein Fixpunkt ist, der, einmal betreten, keinen Grund hat, ihn zu verlassen.

Deshalb gibt es sampling überhaupt. Es ist außerdem — und dieser Teil wird oft weggelassen — kein universelles Gesetz. Kapitel 12 maß 24 von 24 korrekte Antworten bei zweistufigen Textaufgaben mit schlichtem greedy decoding, und sampling bei Temperature 0,8 senkte das auf 81 %; self-consistency gab dann sechsmal so viele tokens aus, um wieder dort hinaufzuklettern, wo greedy schon war. Beide Fakten sind gleichzeitig wahr:

Offene Generierung. Es gibt keine einzelne richtige Fortsetzung, also ist die wahrscheinlichste eine Falle — sie loopt, und 87,6 % davon sind von sich selbst kopiert. Sample.

Aufgaben mit einer richtigen Antwort. Es gibt eine einzelne korrekte Fortsetzung, also bedeutet alles andere zu ziehen, einen Fehler zu ziehen. Kapitel 12s 100 % wurden genau deshalb zu 81 %. Nicht samplen.

Die meisten Production-prompts sind vom zweiten Typ und werden wie der erste konfiguriert, weil die Temperature auf dem Wert blieb, den der Beispielcode verwendete.

Zwei Arten zu schneiden, und nur eine passt sich an

Link zum Abschnitt: Zwei Arten zu schneiden, und nur eine passt sich an

Aus der vollen Verteilung zu samplen tut praktisch niemand, weil der Tail enorm ist und voller Unsinn steckt. Irgendetwas muss abgeschnitten werden. Es gibt zwei klassische Antworten, und sie unterscheiden sich in einem Punkt, der alles entscheidet.

Top-k behält eine feste Zahl von Kandidaten. Nach Wahrscheinlichkeit sortieren, die ersten kk behalten, den Rest verwerfen, neu normalisieren.4 Top-p, auch nucleus sampling genannt, behält eine feste Menge an Masse: Nimm tokens in absteigender Reihenfolge, bis ihre kumulative Wahrscheinlichkeit pp erreicht, und stoppe.3 Formal ist der nucleus die kleinste Menge VpV_p mit

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

Der Unterschied klingt kosmetisch, ist es aber nicht, weil zwei prompts, die du in derselben Minute sendest, völlig unterschiedliche Verteilungsformen haben. Beide hier sind dasselbe Modell bei Temperature 1:

Q: What is the capital of France?\nA:Once upon a time,
top-1 probability96.01 %25.39 %
tokens holding 90 % of the mass1467
top-k = 40 keeps99.61 % of the mass78.87 % of the mass
mass in ranks 2 to 403.61 %53.48 %
token at rank 40␣Av, 0.0093 %␣Dr, 0.128 %

Ein festes kk, zwei Fehlschläge in entgegengesetzte Richtungen. Beim Fakten-prompt lässt k=40k = 40 39 tokens zu, die zusammen 3,6 % wert sind — es lässt Müll durch, einschließlich eines Kandidaten bei neun Tausendstel Prozent, weil die Regel Slots zählt und keine Evidenz. Beim Story-prompt wirft dasselbe k=40k = 40 21 % der Masse weg, die das Modell wirklich vergeben hat, weil der echte nucleus dort 467 tokens breit ist.

Top-p lässt exakt eine Zahl beide Jobs erledigen. Setze p=0.9p = 0.9 und es behält 1 token beim ersten prompt und 467 beim zweiten, weil es eine Frage über die Verteilung stellt, statt ihr eine Anzahl aufzuzwingen. Schau dir diese Anpassung direkt an — gleicher Cut, vier Temperaturen:

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

3 von 10 Tokens überstehen den Cut und teilen sich die Wahrscheinlichkeit.

Daten als Tabelle anzeigen
TokenlogitNach der TemperaturNach dem Cut
␣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 und top-k

Top-p bei 0,90 mit Temperature 1,5: drei der zehn tokens überleben und teilen sich die Masse, ␣Paris renormalisiert auf 91,10 %. Bewege jetzt nur die Temperature. Bei 0,7 lässt dasselbe 0,90 einen Überlebenden — ein so schmaler nucleus ist greedy decoding unter anderem Namen. Bei 2,0 lässt es fünf. Der Cut hat sich nie bewegt; die Form darunter schon.

Dieses Widget klärt auch ein Missverständnis, das benannt werden sollte, weil es Menschen echtes Geld kostet. Bei einer sicheren Verteilung ist top_p = 0.9 nicht „ein bisschen Vielfalt“. Es ist greedy. Bei Temperature 1 hält der führende token hier 96,90 %, also bereits mehr als 0,9, damit ist der nucleus ein token breit und nichts anderes kann je gezogen werden. Teams setzen top_p auf 0,9, glauben, sie hätten etwas gelockert, und wundern sich dann, warum jede Antwort identisch ist.

Setze stattdessen top-k, und der entgegengesetzte Fehlschlag ist genauso sichtbar:

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

5 von 10 Tokens überstehen den Cut und teilen sich die Wahrscheinlichkeit.

Daten als Tabelle anzeigen
TokenlogitNach der TemperaturNach dem Cut
␣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 und top-k

Top-k bei 5, kein top-p. Fünf tokens überleben bei jeder Temperature, weil fünf verlangt wurde. Bei Temperature 1 sind die vier Kandidaten unter ␣Paris zusammen 2,79 % wert. Senke auf 0,7 und dieselben vier sind 0,38 % wert — der Cut ist Theater, und das Modell ist effektiv greedy. Hebe auf 2,0 und sie sind 22,54 % wert. Identische Einstellung, identische Zahl Überlebender, drei völlig verschiedene Verhaltensweisen, und nichts in der Anfrage sagt dir, welche du bekommst.

Die Penalties, mit Formeln, weil sie ständig verwechselt werden

Link zum Abschnitt: Die Penalties, mit Formeln, weil sie ständig verwechselt werden

Drei verschiedene Mechanismen laufen unter ähnlichen Namen, sie tun Unterschiedliches, und der Unterschied ist messbar. Sei cic_i die Anzahl, wie oft token ii bereits erschienen ist.

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

Ziehe eine Konstante von jedem token ab, der überhaupt erschienen ist. Einmal erscheinen und vierzigmal erscheinen werden identisch bestraft. Es ist ein Schalter, kein Regler.

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

Ziehe proportional zur Anzahl ab. Ein viermal verwendeter token wird viermal so stark bestraft wie ein einmal verwendeter, und der Druck akkumuliert, während der Text wächst.

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}

Das Original aus dem CTRL-Paper.7 Es teilt, statt zu subtrahieren, mit der Fallunterscheidung fürs Vorzeichen, weil das Teilen eines negativen logit ihn größer machen würde. Seine Stärke hängt daher von der Magnitude des logit ab, was bedeutet, dass dasselbe ρ\rho an unterschiedlichen Stellen desselben Satzes unterschiedlich trifft.

Dieselbe degenerierte Fortsetzung von oben, mit jeweils einer angewendet. „Steps altered“ zählt, wie viele der 120 Generierungsschritte einen anderen token wählten, als das unpenalisierte Modell gewählt hätte. Der Lauf hat hier 120 Schritte gegenüber 140 im Block oben, deshalb steht die unpenalisierte Baseline bei 85,5 % statt 87,6 %:

settingrepeated 4-gramssteps altered
nothing85.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

Drei Dinge fallen heraus. Presence bei 0,5 änderte drei Entscheidungen von 120 und senkte Wiederholung um ein Viertel — der Loop wurde von einer Handvoll tokens zusammengehalten. Frequency bei 0,5 änderte viermal so viele Entscheidungen für einen viel größeren Effekt, weil der Zählmultiplikator weiter wächst, während die Presence-Konstante es nicht tut. Und die CTRL-Penalty beim oft kopierten Wert 1,2 schrieb 35 von 120 Entscheidungen um, was kein Stups ist; es ist ein anderes Modell.

Diese letzte Zahl bereitet den Fehler vor, vor dem niemand warnt.

Was Penalties mit Text tun, der sich wiederholen soll

Link zum Abschnitt: Was Penalties mit Text tun, der sich wiederholen soll

Code wiederholt sich. Tabellen wiederholen sich. Listen wiederholen sich. Strukturierte Ausgabe wiederholt sich per Definition — genau das ist Struktur. Eine Penalty kann nicht unterscheiden, ob ein Modell in einem Loop feststeckt oder korrekt die vierte Zeile einer Tabelle ausgibt, weil beides aussieht wie ein token, der erneut erscheint.

Dieselben drei Aufgaben, auf drei Arten generiert:

tasknothingfrequency 0.5repetition 1.2
markdown table, 6 rows0 / 56 steps altered0 / 562 / 62
Python function0 / 930 / 9310 / 110
bulleted list, 1 to 120 / 500 / 500 / 50

Die frequency penalty bei 0,5 erwies sich bei allen dreien als harmlos, ein nützliches und leicht überraschendes Ergebnis, und es sagt etwas Präzises: Da keine Entscheidung geändert wurde, müssen die strukturellen tokens ihre Positionen um mehr gewonnen haben, als die Penalty abzog, selbst nachdem sie fünf- und sechsmal erschienen waren. Die CTRL-Penalty, die stattdessen teilt, verdrängt sie, und hier ist, was sie produzierte:

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

Die Ausrichtung fällt auseinander: Die Menge an Padding in jeder Zelle ändert sich von Zeile zu Zeile, weil die Folge von Leerzeichen vor der schließenden Pipe genau die Art Wiederholung ist, die die Penalty brechen soll. Kosmetisch, und es kostete sechs zusätzliche tokens. Der Python-Fall ist nicht kosmetisch:

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,

Die Penalty drückte das Modell von total — bereits im Docstring verwendet — zu total_sum, polsterte die Ausgabe mit erfundenen Kommentaren auf, um sein Budget für unbenutzte tokens auszugeben, und lief dann in ein range mit drei Argumenten und Schrittweite. Der Kommentar sagt incrementing by 2 each time, was für eine Summe von Quadraten von 1 bis nn falsch ist. Eine repetition penalty erzeugte falschen Code aus einem prompt, der ohne sie korrekt beantwortet wurde.

Die daraus folgende Regel ist kurz: Penalties sind für offene Prosa, und sie sollten für Code, strukturierte Ausgabe, tabellarische Daten und alles mit Schema aus sein. Kapitel 18 handelt genau von dieser zweiten Kategorie.

Die Reihenfolge der Anwendung, und warum sie die Antwort ändert

Link zum Abschnitt: Die Reihenfolge der Anwendung, und warum sie die Antwort ändert

Jede reale Implementierung wendet diese Schritte in einer bestimmten Reihenfolge an:

penalties → temperature → top-k → top-p → sample

Das ist keine willkürliche Buchhaltung, und zwei Stufen zu vertauschen erzeugt tatsächlich andere Verteilungen. Zwei Messungen, beide am Fakten-prompt.

Schneiden vor oder nach der Temperature. Der nucleus wird auf der Verteilung berechnet, die er bekommt, und Temperature verändert diese Verteilung radikal:

top-p 0.9 after temperaturetop-p 0.9 before temperature
T=1.0T = 1.01 token1 token
T=1.5T = 1.5353 tokens1 token
T=2.0T = 2.032,966 tokens1 token

Bei T=2T = 2 ergibt dieselbe nominale Einstellung eine Kandidatenmenge von 32.966 oder von 1, allein abhängig davon, welche Stufe zuerst läuft. Wenn du dich je gefragt hast, warum eine höhere Temperature bei einem provider „nichts tut“ und bei einem anderen mit denselben zwei Zahlen die Ausgabe zerstört, ist diese Tabelle eine plausible Antwort.

Penalisieren vor oder nach der Temperature. Eine Penalty α\alpha abzuziehen und dann durch TT zu teilen ergibt eine effektive Penalty von α/T\alpha/T; zuerst zu teilen und dann abzuziehen ergibt α\alpha. Mit einer presence penalty von 1,0 auf den führenden token:

temperaturepenalise, then tempertemper, then penalise
0.599.858 %99.948 %
1.089.839 %89.839 %
2.010.783 %6.830 %

Identisch bei T=1T = 1, wie es sein muss. Um Faktor 1,58 auseinander bei T=2T = 2. „Presence penalty 1.0“ ist keine wohldefinierte Menge Penalty, wenn du nicht auch weißt, wo die Temperature angewendet wird, und keine API dokumentiert das.

Details anzeigen

Optional: die ganze Pipeline, in der Reihenfolge oben.

Sechzehn Zeilen, und alles in diesem Kapitel steckt darin. Es ist dieselbe Berechnung, die das Widget ausführt, auf einem echten logit-Vektor statt auf zehn festen Zahlen.

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

Das cumsum(0) - p in der top-p-Zeile ist die kumulative Masse ohne den aktuellen token, wodurch der nucleus den token einschließt, der den Schwellenwert überschreitet, statt direkt davor zu stoppen. Verfehle das um eins und top_p = 0.9 wird stillschweigend ein etwas engerer Cut als in jeder anderen Implementierung.

Dies ist eine der wenigen Stellen in der zweiten Hälfte des Kurses, an der Python die richtige Sprache ist, und der Grund ist strukturell, nicht stilistisch: Jede Zeile oben braucht den vollständigen Vektor von logits in deiner Hand, und über eine HTTP API existiert dieser Vektor nicht. Du kannst temperature und top_p an einen provider senden; du kannst sie nicht implementieren, und du kannst nicht sehen, was sie getan haben.

Jeder provider akzeptiert eine andere Teilmenge dieser Controls, mit anderen Bereichen, und ignoriert den Rest stillschweigend. Das ist keine abstrakte Beschwerde. Jede Anwendung, die eine Modellwahl anbietet, muss die Unterschiede irgendwo aufschreiben, und die Datei, in der sie das tut, ist eine Karte der Inkompatibilität. Hier ist, was ein solcher Katalog für einen einzelnen Parameter über die neun Textquellen deklariert, die er unterstützt:

declared temperature rangesources
0 to 1Anthropic, Google, Meta, Cerebras, PaLM
0 to 1.5Mistral
0 to 2OpenAI, DeepSeek, xAI

Das Wort ist dasselbe; die Skala ist es nicht. Eine „Temperature von 1“ ist bei einem die unveränderte Verteilung und bei einem anderen die maximal erlaubte Hitze, und die Hälfte des Katalogs kann den Wert nicht ausdrücken, den die andere Hälfte als neutral-plus-ein-bisschen behandelt. Die übrigen Regler sind genauso ungleich: Die Einträge für OpenAI, DeepSeek und xAI akzeptieren presence und frequency penalties und kein topK; die Einträge für Google, Meta, Cerebras und PaLM akzeptieren topK und keine Penalties; Anthropic akzeptiert topK, topP und stop sequences und keine Penalties; und genau einer von neun — Mistral — akzeptiert einen seed. Einen Parameter zu senden, den ein provider nicht implementiert, erzeugt im Allgemeinen überhaupt keinen Fehler: Die Anfrage ist erfolgreich, der Regler tut nichts, und du schließt, dass die Einstellung keinen Effekt hat.

Und beachte, was so eine Datei ist: eine Behauptung über die API eines anderen, geschrieben an einem bestimmten Tag, die danach durch nichts verifiziert wird. Ein Katalog, der 0 bis 1 für einen provider sagt, der jetzt 0 bis 2 akzeptiert, wird jede Anfrage stillschweigend kappen.

Zwei weitere Controls gehören zur selben Familie. logprobs liefert, wo angeboten, die Log-Wahrscheinlichkeiten des gewählten token und oft die Top-Alternativen — das einzige Fenster auf die Verteilung, um die es in diesem Kapitel geht, und die Grundlage jeder Confidence-Heuristik auf einem geschlossenen Modell. Und maximum tokens plus stop sequences beenden Generierung ohne Bezug auf Wahrscheinlichkeit: eine harte Obergrenze und ein String-Match. Beides erscheint als finish_reason aus Kapitel 14, wo length bedeutet, dass deine Antwort mitten im Satz durch ein Budget abgeschnitten wurde, nicht vom Modell beendet.

Der seed und der Determinismus, den du nicht hast

Link zum Abschnitt: Der seed und der Determinismus, den du nicht hast

Setze einen seed, und sampling wird reproduzierbar. Dieser Teil ist echt und leicht zu überprüfen:

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-identisch innerhalb eines seed, verschieden über seeds hinweg, genau wie beworben. Was der seed also fixiert, ist die Zufallsziehung in der letzten Zeile dieser sample-Funktion — welcher token bei gegebener Verteilung gewählt wird.

Was er nicht fixiert, ist die Verteilung. Und dort liegt das Problem, denn der Vektor von logits, den dein Modell erzeugt, ist kein mathematisches Objekt; er ist das Ergebnis von Milliarden Floating-Point-Additionen, und diese haben eine Reihenfolge.

Kapitel 2 ließ dieses Experiment bereitstehen. Dieselben eine Million float32-Zahlen, in verschiedenen Gruppierungen summiert:

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

Sieh dir die letzte Zeile an. Die Anzahl der Chunks ändert die Antwort. Das ist keine Kuriosität über numpy; es ist der Mechanismus, denn wenn ein Inferenzserver eine Reduktion über mehr oder weniger parallele Einheiten verteilt, tut er genau das. Und ein Server verteilt abhängig davon, wie viele Anfragen er bedient.

Hier ist dieser Effekt am Modell selbst. Derselbe prompt, derselbe forward pass, der einzige Unterschied ist, wie viele andere Anfragen zufällig im Batch waren:

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

Allein ausgeführt ist das Modell perfekt deterministisch — zwanzig Durchläufe, bitidentisch. Pack denselben prompt in einen Batch mit fremden Anfragen und 97 % seiner logits ändern sich. An deiner Anfrage hat sich nichts geändert. Die Anfrage von jemand anderem ist angekommen.

Nun der ehrliche Teil, weil das meist erzählt wird, als wäre es das Ende der Geschichte. Eine Änderung von 2.5×1052.5 \times 10^{-5} verändert die Ausgabe nur, wenn zwei Kandidaten-tokens innerhalb dieses Abstands lagen. Über 717 Generierungsschritte in zwölf prompts betrug die kleinste Lücke zwischen den Top-2-logits 2.5×1032.5 \times 10^{-3} — hundertmal größer als die Störung — und kein Schritt war nah genug zum Kippen. Auf diesem Modell, in float32, auf einem Laptop, bewegte batching also jedes logit und änderte keinen token.

Das ist eine Beschreibung günstiger Bedingungen, keine Beruhigung, und eine Änderung dieser Bedingungen reicht:

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

Sechs von acht Antworten divergieren, und eine davon wird deutlich schlechter. Die Tabelle aus Kapitel 2 sagt warum: bfloat16 behält 7 Mantissenbits, sodass nahe einer logit-Magnitude von 16 die darstellbaren Werte 0,125 auseinanderliegen — 16,0, dann 16,125, dann 16,25 — und Rundung einen logit um bis zu 0,0625 verschieben kann. Gleichzeitig hatten 4,7 % der oben gemessenen Generierungsschritte eine Top-2-Lücke unter 0,1. Das ist der ganze Unterschied zwischen den zwei Experimenten: In float32 war die Störung hundertmal kleiner als die knappste Entscheidung, und in bfloat16 ist sie gleich groß. Production inference läuft in 16-bit, auf Hardware mit fused kernels und Reduktionsreihenfolgen, deren Stabilität niemand verspricht. Ob „das numerische Rauschen vernachlässigbar ist“, ist eine Frage von Präzision und Hardware, nicht des Modells.

Also die vier Ursachen, katalogisiert wie in Kapitel 9 versprochen:

Floating-Point-Addition ist nicht assoziativ

Link zum Abschnitt: Floating-Point-Addition ist nicht assoziativ

Die Box aus Kapitel 2. Die Reihenfolge einer Summe ändert ihren Wert, also ändert jede Veränderung darin, wie eine Reduktion aufgeteilt wird, die logits. Das ist das Substrat; die anderen drei sind Wege, die Reihenfolge zu ändern.

Dynamic batching gruppiert deine Anfrage mit fremden

Link zum Abschnitt: Dynamic batching gruppiert deine Anfrage mit fremden

Continuous batching aus Kapitel 13 ist der Grund, warum Inferenz bezahlbar ist — und es bedeutet, dass die Form der Matrizen, durch die deine tokens fließen, vom Traffic abhängt. Oben gemessen: 147.321 logits bewegten sich, weil sich die Batchgröße änderte.

Mixture-of-experts routing hängt vom Batch ab

Link zum Abschnitt: Mixture-of-experts routing hängt vom Batch ab

Die Box aus Kapitel 9 sagte es bereits. Der router trifft pro token und Schicht eine diskrete Wahl, abhängig von pro-Expert-Kapazitätsgrenzen, die über den Batch berechnet werden. Ein token, der allein zu Experte 7 gegangen wäre, geht in Gesellschaft zu Experte 12. Das ist kein Rundungsunterschied; es ist ein anderer Satz Gewichte.

Ein Versionsstring wie -latest ist ein Pointer, und Pointer werden umgehängt. Provider aktualisieren außerdem den Serving-Stack unter einer festen Versionskennung. Beides wird nicht in einer Granularität angekündigt, die es dir erlauben würde, es mit Änderungen deiner eigenen Ausgabe zu korrelieren.

OpenAIs Parameter seed ist auf die einzige Weise ehrlich, wie er es sein kann: Er kommt zusammen mit einem Feld system_fingerprint, das die Backend-Konfiguration identifiziert, und die Dokumentation sagt, dass Determinismus best-effort ist und ein geänderter Fingerprint bedeutet, dass Ergebnisse abweichen können. Lies das als das, was es ist — ein provider sagt dir, dass er alle vier Ursachen oben kontrolliert, dass du keine davon kontrollierst, und dass das eine, was er anbieten kann, ist, dir im Nachhinein zu sagen, dass sich etwas bewegt hat.

Alles hier handelte von einem Regler und seinen Folgen. Tritt eine Ebene zurück, und das härtere Problem erscheint: Das Objekt, das wir getuned haben, ist eine Wahrscheinlichkeitsverteilung, und Wahrscheinlichkeitsverteilungen haben kein Interface.

Ein function call hat eins. Eine Datenbankzeile hat eins. Ein POST-Handler, der einen JSON-Body mit drei Pflichtfeldern erwartet, hat eins, und er weist alles andere zurück. Zwischen dem Modell und jeder anderen Komponente in deinem System sitzt ein Vertrag, über den eine Seite keine Versprechen machen kann: Das Modell wird etwas erzeugen, gezogen aus einer Verteilung, die du geformt, aber nicht fixiert hast, und der Code auf der anderen Seite braucht einen Wert eines bekannten Typs, sonst wirft er.

Die Brücke zwischen diesen zwei Welten wird aus dem Material dieses Kapitels gebaut, nicht aus Parsing und Retries. Wenn ein token die erforderliche Struktur brechen würde, samplest du ihn nicht und hoffst — du setzt seinen logit auf -\infty, bevor der softmax ihn je sieht. Constrained decoding ist eine Maske über demselben Vektor, den wir in diesem Kapitel umgeformt haben, und es macht aus „bitte antworte in JSON“ eine Garantie statt einer Bitte.

Kapitel 18 ist dieser Vertrag: tool calling, JSON Schema, strukturierte Ausgaben, und was nötig ist, um ein deterministisches System sicher auf ein probabilistisches aufzubauen.


Alle Messungen in diesem Kapitel stammen von Qwen/Qwen2.5-0.5B-Instruct auf CPU, float32 sofern nicht anders angegeben, mit sampling wie im optionalen Abschnitt implementiert statt an eine Library delegiert. Es ist ein kleines Modell, und die konkreten Werte gehören zu ihm; die Mechanismen nicht. Von Platens How to generate text with different decoding methods (Hugging Face, 2020) ist der Artikel, an dem dieser hier gemessen wird, und weiterhin die beste kurze Einführung in dasselbe Material. Für den Determinismus-Abschnitt: PyTorchs Reproduzierbarkeitsnotizen beschreiben, was ein seed auf einer einzelnen Maschine fixiert und was nicht, OpenAIs Dokumentation zu seed und system_fingerprint beschreibt, was ein provider versprechen kann und was nicht, und Thinking Machines’ Diskussion von 2025 zu batch-invariant kernels ist die klarste öffentliche Darstellung, warum eine Lösung auf Inferenzserver-Ebene möglich, aber nicht kostenlos ist.

  1. Ackley, D. H., Hinton, G. E. und Sejnowski, T. J. A Learning Algorithm for Boltzmann Machines. Cognitive Science 9(1), S. 147–169 (1985), wo die Temperature in einem softmax aus der statistischen Physik stammt. Hinton, G., Vinyals, O. und Dean, J., Distilling the Knowledge in a Neural Network, arXiv:1503.02531 (2015), Abschnitt 2, ist die Stelle, an der derselbe Parameter im modernen Deep Learning wieder auftaucht — als Möglichkeit, die volle Verteilung eines Teachers offenzulegen, was den soft labels aus Kapitel 13 entspricht, nicht dem sampling dieses Kapitels.

  2. Guo, C., Pleiss, G., Sun, Y. und Weinberger, K. Q. On Calibration of Modern Neural Networks. arXiv:1706.04599 (2017). Verwechsle das nicht mit der Temperature in diesem Kapitel. Temperature scaling passt einen einzelnen Wert auf einem Validierungsset an, damit das Vertrauen des Modells zu seiner Genauigkeit passt; es ist eine post-hoc-Kalibrierungsmethode auf den Ausgaben eines Klassifikators. Temperature sampling ist ein Runtime-Control darüber, wie ein Generator tokens zieht. Gleiche Formel, anderer Zweck, kein gemeinsamer Wert.

  3. Holtzman, A., Buys, J., Du, L., Forbes, M. und Choi, Y. The Curious Case of Neural Text Degeneration. arXiv:1904.09751 (2019). Führt nucleus sampling ein und die Messung, dass maximierungsbasiertes decoding Text erzeugt, dessen Wahrscheinlichkeitsprofil menschlichem Text überhaupt nicht ähnelt. 2

  4. Fan, A., Lewis, M. und Dauphin, Y. Hierarchical Neural Story Generation. arXiv:1805.04833 (2018). Das Paper, das top-k sampling populär gemacht hat.

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

  7. Keskar, N. S., McCann, B., Varshney, L. R., Xiong, C. und Socher, R. CTRL: A Conditional Transformer Language Model for Controllable Generation. arXiv:1909.05858 (2019). Abschnitt 4.1 ist die ursprüngliche repetition penalty — diejenige, die teilt.

Bereit, LIA die Wahl zu überlassen?

Bau mit jedem KI-Modell an einem Ort — starte heute kostenlos.