Warum temperature=0 nicht das leistet, was die meisten erwarten, und wie Sie trotzdem zu verlässlichen LLM-Ergebnissen kommen.
Bei einem unserer Tests blieb ein Reasoning-Modell hängen. Prompt und Parameter waren jedes Mal gleich, Temperature stand für mehr "Konsistenz" auf null. Das Modell löste das Rätsel richtig, hörte danach aber nicht auf: Es reihte Synonyme für "boat crossing" aneinander, bis das Token-Limit erreicht war. Und das bei jedem Durchlauf.
Mit Temperature 1.0 und den Werten für top_p und top_k, die der Herausgeber des Modells empfiehlt, sah es anders aus: 36 Durchläufe, keine einzige Schleife.
Falls Ihnen diese Begriffe noch nichts sagen, hier das Prinzip in wenigen Sätzen. Ein Sprachmodell schlägt keine fertige Antwort nach. Es setzt sie Wort für Wort zusammen und hat bei jedem Schritt eine Rangliste möglicher Fortsetzungen vor sich. Temperature, top_p und top_k bestimmen, wie es aus dieser Liste wählt: ob es immer den Spitzenreiter nimmt oder auch einmal weiter unten zugreift. Mehr steuern diese drei Regler nicht. Es gibt sie in jeder LLM-API, ihre Namen verraten nichts über ihre Wirkung, die Standardwerte unterscheiden sich von Anbieter zu Anbieter, und meistens übernimmt man einen Wert aus irgendeinem Blogartikel und fasst ihn danach nie wieder an.
Das Ergebnis vom Anfang widerspricht der verbreiteten Vorstellung davon, wie diese Regler funktionieren. Vielleicht kommt Ihnen das eine oder andere bekannt vor:
- Sie haben
temperature: 0gesetzt, damit die Ausgabe reproduzierbar ist. - Sie lassen
temperatureim Request ganz weg, weil Sie davon ausgehen, dass die Plattform schon einen sinnvollen Wert wählt. - Ihre Evaluierung läuft am Montag fehlerfrei durch und schlägt am Donnerstag fehl, obwohl Sie nichts geändert haben.
- Ein Modell hat sich wiederholt, also haben Sie
frequency_penaltygesetzt. - Sie haben drei Erklärungen zu top_p und top_k gelesen und wissen trotzdem nicht, wann Sie welchen Parameter verwenden sollten.
Keine dieser Annahmen ist abwegig. Die meisten bewirken aber nicht das, was man erwartet, und die erste ist der zuverlässigste Weg, ein Reasoning-Modell scheitern zu lassen.
Ausgerechnet temperature=0, die Einstellung, zu der Entwickler für mehr Konsistenz greifen, richtet am ehesten Schaden an. Und auch die erhoffte Konsistenz funktioniert anders, als die meisten annehmen: Wer denselben Request zweimal an einen stark ausgelasteten Endpoint schickt, kann zwei verschiedene Antworten bekommen. Die Ursache liegt weder im Request noch in den Parametern, und ein Seed ändert daran nichts.
Alle Messungen, die wir als unsere ausweisen, sind Live-Läufe gegen unsere API, und Sie können jede davon selbst nachvollziehen. Zahlen aus fremden Arbeiten sind als solche gekennzeichnet.
Das Wichtigste in Kürze
Sprachmodelle antworten aus zwei Gründen unterschiedlich auf dieselbe Frage. Bei der Wahl des nächsten Worts ist gewollt ein Zufallselement im Spiel, und auf einem ausgelasteten Server verschiebt sich unbemerkt die Arithmetik darunter. Beides ist kein Fehler, und beides lässt sich beherrschen, sobald man die beiden Effekte auseinanderhalten kann.
Wer mit einer LLM-API entwickelt, braucht aus diesem Artikel vor allem fünf Punkte:
- Setzen Sie
temperaturebei jedem Request. Den Parameter wegzulassen ist keine neutrale Entscheidung: Bei den meisten Modellen hier führt das zu Greedy Decoding, der riskantesten Einstellung überhaupt. - Nehmen Sie den Wert, den der Herausgeber des Modells empfiehlt. In der Regel ist das 1.0 und nicht 0. Eine Tabelle mit den Werten je Modell folgt weiter unten.
- Mit
temperature: 0riskieren Sie Wiederholungsschleifen. Ein Reasoning-Modell kann sich darin verfangen und denselben Gedanken immer neu formulieren, bis ihm die Token ausgehen. Ein höheresmax_tokensverschlimmert das nur. frequency_penaltyundpresence_penaltyhaben bei dieser API keine Wirkung. Was funktioniert, istrepetition_penalty. Übergeben Sie den Parameter überextra_bodyund bleiben Sie bei höchstens 1.2.- Prüfen Sie in Tests nie auf die exakte Ausgabe. Ein solcher Test hält bis zum nächsten Modell-Upgrade, und auf einem ausgelasteten Endpoint womöglich nicht einmal heute. Prüfen Sie stattdessen Eigenschaften: gültiges JSON, das richtige Schema, eine Zahl im erwarteten Bereich.
Wie ein Modell das nächste Token auswählt
Ein LLM ruft beim Generieren keine vorformulierten Antworten ab. Für jedes Token, das es ausgibt, durchläuft es drei Schritte:
- Logits berechnen: ein Rohwert für jedes Token im Vokabular
- Softmax anwenden: Aus den Rohwerten werden Wahrscheinlichkeiten
- Sampling: Ein Token wird anhand dieser Wahrscheinlichkeiten ausgewählt
Temperature, top_p und top_k greifen jeweils an einem anderen dieser drei Schritte an.
Schritt 1: Das Modell bewertet jedes Token, das es kennt
Die letzte Schicht des Modells liefert für jedes Token im Vokabular eine Zahl, typischerweise mehr als 100.000 davon. Diese Rohwerte heißen Logits.
Ein Logit ist keine Wahrscheinlichkeit. Es ist ein Wert ohne feste Grenzen, der positiv oder negativ sein kann, etwa -15,3 oder +8,7. Der Begriff stammt aus der Statistik, ist eine Kurzform von "logistic unit" und bezeichnet dort logarithmierte Chancen, die Log-Odds. In neuronalen Netzen meint er die Rohausgabe vor der letzten Aktivierungsfunktion.
Für einen einzelnen Generierungsschritt könnten die Logits zum Beispiel so aussehen:
| Token | Logit |
|---|---|
| "the" | +5,2 |
| "a" | +3,1 |
| "Paris" | +2,8 |
| "quantum" | -4,3 |
Ein Logit von +5,2 heißt nicht, dass die Wahrscheinlichkeit bei 520% liegt. Es heißt nur, dass dieses Token wahrscheinlicher ist als Token mit niedrigeren Werten. Zu echten Wahrscheinlichkeiten werden die Werte erst im nächsten Schritt.
Schritt 2: Aus Rohwerten werden Wahrscheinlichkeiten
Mit den Rohwerten allein lässt sich wenig anfangen. Manche sind negativ, ihre Summe hat keine feste Größe, und aus "+5,2" lässt sich nicht ablesen, um wie viel wahrscheinlicher "the" ist als "a". Das Modell braucht eine echte Wahrscheinlichkeitsverteilung, in der jeder Wert positiv ist und alle zusammen genau 1 ergeben.
Dafür sorgt die Softmax-Funktion in zwei Schritten. Zuerst wird e mit jedem Rohwert potenziert. Dadurch werden alle Zahlen positiv, und die hohen Werte entfernen sich weit von den niedrigen. Danach wird jedes Ergebnis durch die Gesamtsumme geteilt, sodass alle zusammen 1 ergeben.
Für unsere vier Kandidaten ergibt sich folgendes Bild:
| Token | Logit | Wahrscheinlichkeit |
|---|---|---|
| "the" | +5,2 | 82,4% |
| "a" | +3,1 | 10,1% |
| "Paris" | +2,8 | 7,5% |
| "quantum" | -4,3 | 0,006% |
Die Exponentialfunktion vergrößert die Abstände stärker, als man vermuten würde. Bei den Rohwerten liegt "the" nur 2,1 vor "a", was nicht nach einem klaren Vorsprung aussieht. Nach der Exponentialfunktion steht es acht zu eins. Kleine Unterschiede in den Rohwerten werden zu großen Unterschieden in der Wahrscheinlichkeit, und genau hier setzt Temperature an.
Bleibt die letzte Zeile. "quantum" kommt auf -4,3 und wäre als Fortsetzung völlig sinnlos, erhält aber trotzdem eine Wahrscheinlichkeit größer null. Ganz ausgeschlossen ist kein Token, jedes im Vokabular behält eine winzige Chance. Das erklärt, warum ein Modell gelegentlich etwas Seltsames von sich gibt, und warum es einen eigenen Parameter gibt, um diese Ausläufer abzuschneiden.
Schritt 3: Das Modell wählt ein Token aus
Nun liegen Wahrscheinlichkeiten vor. Beim Sampling wird daraus ein Token zufällig gezogen, gewichtet nach diesen Wahrscheinlichkeiten.
Man kann sich das als Strecke von 0 bis 1 vorstellen, auf der jedes Token einen Abschnitt belegt, der so breit ist wie seine Wahrscheinlichkeit:
Gezogen wird eine Zufallszahl zwischen 0 und 1, und es gewinnt das Token, in dessen Abschnitt sie fällt. 0,372 landet bei "the", 0,891 bei "a". Über viele Ziehungen gewinnt "the" in rund 82% der Fälle, doch auch die weniger wahrscheinlichen Token kommen immer wieder zum Zug.
An dieser Stelle kommt der Zufall ins Spiel: Bei gleichen Wahrscheinlichkeiten führt eine andere Zufallszahl zu einem anderen Token.
Wo Temperature ansetzt
Temperature greift genau in diesen exponentiellen Schritt ein. Bevor die Rohwerte potenziert werden, wird jeder von ihnen durch den Temperature-Wert geteilt. Mehr steckt hinter dem Mechanismus nicht.
Bei einem Wert unter 1 werden die Rohwerte größer und rücken weiter auseinander. Der Spitzenreiter setzt sich ab, die Verteilung wird schärfer. Bei einem Wert über 1 rücken sie zusammen, und auch die Außenseiter haben eine echte Chance. Bei genau 1 ändert sich nichts, und deshalb ist 1.0 der Ausgangswert und nicht etwa das Maximum.
| Temperature | Wirkung |
|---|---|
| T = 1.0 | Wahrscheinlichkeiten wie im Training, der Ausgangswert |
| T < 1.0 | Schärfer: Wahrscheinliche Token dominieren stärker |
| T > 1.0 | Flacher: Unwahrscheinliche Token gewinnen an Gewicht |
| T → 0 | Spitze: Es gewinnt immer das wahrscheinlichste Token |
Temperature 1.0 bedeutet also nicht "maximaler Zufall". Es ist der Ausgangswert, bei dem die Verteilung so bleibt, wie das Modell sie im Training gelernt hat. Man kann es mit dem Kontrast eines Fotos vergleichen: T=1 ist das Original, jeder andere Wert eine Bearbeitung.
Bei T=0 schrumpft die Verteilung auf eine einzige Spitze zusammen. Ein Token erhält ~100%, alle anderen ~0%. Dieses Verfahren heißt Greedy Decoding: Das Modell wählt immer das wahrscheinlichste Token, ganz ohne Zufall.
Wo top_p und top_k ansetzen
Beide Parameter sortieren Kandidaten aus, und zwar nach der Softmax-Funktion und vor dem Sampling:
Top-k behält nur die k Token mit der höchsten Wahrscheinlichkeit:
| Token | Vor top_k=2 | Nach top_k=2 |
|---|---|---|
| "the" | 82,4% | 89,1% (renormalisiert) |
| "a" | 10,1% | 10,9% (renormalisiert) |
| "Paris" | 7,5% | entfernt |
| "quantum" | 0,006% | entfernt |
Top-p (Nucleus Sampling) sortiert die Token nach Wahrscheinlichkeit und behält die kleinste Gruppe, die zusammen mindestens p erreicht:
| Token | Vor top_p=0.9 | Nach top_p=0.9 |
|---|---|---|
| "the" | 82,4% (behalten) | 89,1% |
| "a" | 10,1% (behalten) | 10,9% |
| "Paris" | 7,5% (verworfen) | entfernt |
| "quantum" | 0,006% (verworfen) | entfernt |
Allein kommt "the" auf 82,4% und bleibt damit unter 90%. Zusammen mit "a" sind es 92,5%, die Schwelle ist überschritten, und die Gruppe ist vollständig. Alle übrigen Token fallen weg, die verbleibenden werden renormalisiert.
Top-p richtet sich danach, wie sicher sich das Modell ist. Hat ein Token 95% Wahrscheinlichkeit und ist p=0.9, bleibt nur dieses eine übrig. Ist die Wahrscheinlichkeit breit verteilt, bleiben mehr Kandidaten im Rennen.
Naheliegende Frage: Ist T=0 nicht dasselbe wie top_k=1?
Im Ergebnis ja, denn in beiden Fällen wird immer das wahrscheinlichste Token gewählt. Theoretisch wirken sie allerdings an unterschiedlichen Stellen: T=0 verformt die Verteilung zu einer Spitze, top_k=1 lässt nach der Berechnung der Wahrscheinlichkeiten nur einen Kandidaten übrig.
In der Praxis ist die Division eines Logits durch null nicht definiert, deshalb behandelt jede Implementierung diesen Fall gesondert. In der Infercom API ist beides derselbe Mechanismus, und das hat eine Folge:
top_k wirkt erst ab einer temperature von 0.001. Darunter, und auch wenn temperature ganz fehlt, verwirft die API Ihren top_k-Wert, setzt top_k auf 1 und decodiert greedy. Einen Hinweis darauf, dass Ihr Wert ignoriert wurde, enthält die Antwort nicht.
Ein Request mit nur {"top_k": 100} erweitert also gar nichts, sondern liefert argmax.
Der Fehler passiert leicht: Der Request sieht vernünftig aus und wird vollständig akzeptiert, doch einer der beiden Parameter hat keine Wirkung, und der andere steht auf dem riskantesten Wert, den die Skala hergibt.
Zwei weitere Punkte zu top_k in dieser API: Der Parameter gehört nicht zum OpenAI-Standard, und die OpenAI-SDKs lehnen ihn als Argument auf oberster Ebene ab. Übergeben Sie ihn deshalb über extra_body. Außerdem muss der Wert mindestens 1 betragen, denn anders als bei manchen Anbietern lässt sich der Parameter nicht mit -1 oder 0 abschalten. Wer ihn nicht nutzen will, lässt ihn weg.
Mehr dazu in unserem Glossar
Ein Blick in die Praxis
Alle folgenden Blöcke sind Live-Läufe gegen gpt-oss-120b auf der Infercom API, gemessen am 18. September 2026. Der Prompt lautete: "Write a creative one-word name for a cat. Reply with the name only." Jede Variante lief fünfmal, denn drei Läufe reichen nicht, um eine fixierte Ausgabe von einem glücklichen Zufall zu unterscheiden.
Temperature: Konsistenz oder Vielfalt
| Lauf | temperature=0 | temperature=1.0 |
|---|---|---|
| 1 | Quasar | Nebulyn |
| 2 | Quasar | Nebula |
| 3 | Quasar | Mistral |
| 4 | Quasar | Mistral |
| 5 | Quasar | Nimbus |
Bei T=0 liefert das Modell jedes Mal denselben Namen, bei T=1.0 variiert es.
Die Falle: Temperature fehlt
Nun derselbe Prompt ganz ohne temperature-Feld, einmal zusätzlich mit top_k: 100. Dem Wortlaut nach fordert dieser Request einen breiten Kandidatenpool an:
| Lauf | Ohne Temperature | top_k=100, ohne Temperature |
|---|---|---|
| 1 | Quasar | Quasar |
| 2 | Quasar | Quasar |
| 3 | Quasar | Quasar |
| 4 | Quasar | Quasar |
| 5 | Quasar | Quasar |
Beide Varianten laufen greedy und liefern das Ergebnis von T=0. Die Infercom API setzt keinen dienstweiten Standardwert für temperature. Wer den Parameter weglässt, bekommt also nicht etwa den Standardwert des Modells, sondern bei den meisten Modellen temperature 0, und damit wird auch top_k stillschweigend wirkungslos.
Der Vollständigkeit halber: Auch top_k=1 mit temperature=1.0 liefert fünfmal Quasar. Wer auf einen einzigen Kandidaten filtert, landet auf anderem Weg ebenfalls bei Greedy Decoding.
Erst wenn temperature und top_k gemeinsam gesetzt sind, öffnet sich der Pool:
| Lauf | top_k=100, temperature=1.0 |
|---|---|
| 1 | Nebula |
| 2 | Nimbus |
| 3 | Purrcello |
| 4 | Mistral |
| 5 | Luminara |
Seed: fixiert die Ausgabe innerhalb eines Deployments
Derselbe Prompt mit temperature=1.0:
| Lauf | seed=42 | seed=7 | Ohne Seed |
|---|---|---|---|
| 1 | Nebulyn | Zephyrus | Nebulite |
| 2 | Nebulyn | Zephyrus | Nebula |
| 3 | Nebulyn | Zephyrus | Velvetine |
| 4 | Nebulyn | Zephyrus | Nebulous |
| 5 | Nebulyn | Zephyrus | Nimbus |
Mit Seed bleibt die Ausgabe gleich, ein anderer Seed liefert eine andere Ausgabe, und ohne Seed variiert sie. So sieht ein funktionierender Seed aus, und er ist das richtige Werkzeug für wiederholbare Evaluierungsläufe. Er hat allerdings drei Grenzen, denen weiter unten ein eigener Abschnitt gewidmet ist.
Ein zweiter Blick lohnt sich: Drei der sechs Blöcke lieferten fünfmal Quasar, und in zwei davon hatten wir dem Wortlaut nach ausdrücklich um Vielfalt gebeten. In Greedy Decoding gerät man sehr viel leichter hinein als wieder heraus.
Damit zurück zu dem Modell, das nicht aufhören konnte, über Boote zu schreiben.
Das eigentliche Risiko von T=0: Wiederholungsschleifen
Greedy Decoding hat ein eigenes Fehlerbild, und das ist schlimmer als eine uneinheitliche Antwort.
LLMs arbeiten autoregressiv: Jedes Token hängt von allen vorherigen ab, auch von der eigenen Ausgabe des Modells. Gerät das Modell in ein Muster, das sich selbst verstärkt, kann es darin festhängen.
Bei einer Temperature über 0 bietet das Sampling einen Ausweg, weil das Modell auch einmal ein etwas unwahrscheinlicheres Token wählen kann, das in eine andere Richtung führt. Bei Temperature 0 gibt es diesen Ausweg nicht. Das Modell folgt jedes Mal demselben Pfad, und führt dieser in eine Schleife, kommt es nie wieder heraus.
Genau das haben wir bei Reasoning-Modellen beobachtet. Mit temperature=0 geriet ein Modell in einen endlosen Kreislauf aus Synonymen:
... boat crossings. / moves. / steps. / transitions. / state changes.
... boat trips. / crossings. / voyages. / journeys.Es löste die Aufgabe richtig, gab danach aber nie ein Stop-Token aus, sondern reihte fast gleichbedeutende Begriffe aneinander, bis das Token-Limit erreicht war. Bei temperature 0 passierte das in allen drei Läufen. Mit den Einstellungen, die der Herausgeber empfiehlt (temperature=1.0, top_p=0.95, top_k=40), liefen 36 Läufe normal durch.
Die Schleife ist ein Fixpunkt in der Wahrscheinlichkeitsverteilung des Modells: Bei jedem Schritt führt das wahrscheinlichste nächste Token zurück in den Kreislauf. Greedy Decoding folgt diesem Pfad endlos. Mit Sampling wählt das Modell hin und wieder ein etwas weniger wahrscheinliches Token, und das reicht, um auszubrechen.
Die Herausgeber empfehlen für ihre Reasoning-Modelle eine Temperature über 0, und das ist ein wesentlicher Grund dafür. Es geht dabei nicht um Kreativität, sondern darum, nicht hängen zu bleiben.
Ein höheres max_tokens löst das Problem nicht. Eine Schleife bleibt eine Schleife, auch wenn sie mehr Platz bekommt, und jedes einzelne Token davon wird abgerechnet.
Es handelt sich nicht um eine Kuriosität aus dem Labor. Wir sind zuerst in einem Test darauf gestoßen. Inzwischen haben wir erlebt, wie drei Kunden im Produktivbetrieb in dieselbe Falle geraten sind, und alle drei auf demselben Weg: Sie hatten Temperature bewusst und aus gutem Grund gesenkt. Ein Kunde wollte reproduzierbare Benchmark-Ergebnisse, ein anderer einen Voice Agent, der berechenbar klingt. Ausgerechnet der Reflex, der zu einer niedrigen Temperature führt, wird von diesem Fehlerbild bestraft.
Welcher Wiederholungsparameter wirkt und welche zwei nicht
Angenommen, Temperature ist sinnvoll gesetzt, und trotzdem wiederholt sich das Modell noch. Wer von der OpenAI API kommt, greift dann reflexartig zu frequency_penalty oder presence_penalty.
In der Infercom API haben beide Parameter keine Wirkung. Sie werden aus Gründen der Kompatibilität akzeptiert, aber bei keinem Modell angewendet. Sie bekommen HTTP 200 zurück, eine ganz normal aussehende Antwort und keinerlei Hinweis darauf, dass der Wert ignoriert wurde. Hier derselbe Prompt mit temperature 0, gemessen am 18. September 2026:
| Request | Ausgabe | Completion-Token |
|---|---|---|
| Referenz | "Red / Blue / Green" | 83 |
| frequency_penalty = 2.0 | "Red / Blue / Green" | 83 |
| frequency_penalty = 99 | "Red / Blue / Green" | 83 |
Die Ausgaben sind byte-identisch bis hin zur Anzahl der Token, obwohl der Wert weit außerhalb des dokumentierten Bereichs liegt.
Diese Art von Überraschung kostet Zeit, denn ein Parameter, den der Anbieter ignoriert, sieht genauso aus wie einer, der nicht hilft. Beide Erklärungen passen zu dem, was Sie auf dem Bildschirm sehen. Die eine führt Sie in die Dokumentation, die andere dazu, den Wert immer weiter zu erhöhen. Einer unserer Kunden hat eine Woche auf dem zweiten Weg verbracht.
Dahinter steckt eine Regel, die nicht nur für uns gilt: OpenAI-kompatibel heißt nicht OpenAI-gleichwertig. Jeder Anbieter setzt nur einen Teil dieser API um, dieser Teil unterscheidet sich von Anbieter zu Anbieter und ändert sich mit neuen Releases, und die Antwort verrät nie, welche Teile wirken. Bevor Sie einen Sampling-Parameter optimieren, auf welcher Plattform auch immer, sollten Sie deshalb prüfen, ob die Plattform ihn überhaupt auswertet.
Falls Ihre Requests einen der beiden Parameter enthalten, entfernen Sie ihn oder setzen Sie ihn auf 0. Heute bewirkt er nichts, ein kommendes Plattform-Release wird aber jeden anderen Wert mit einer Fehlermeldung ablehnen. Requests ohne diese Parameter oder mit dem Wert 0 sind davon nicht betroffen. Es ist besser, das jetzt zu bereinigen, als beim nächsten Deployment darüber zu stolpern.
Der Parameter, der hier wirkt, heißt repetition_penalty:
| Request | Ausgabe | Completion-Token |
|---|---|---|
| repetition_penalty = 1.2 | "Red / Blue / Green" | 90 |
Die Generierung fällt anders aus, die Antwort bleibt dieselbe. Für den Einsatz gelten fünf Regeln:
- Übergeben Sie ihn über
extra_body. Er gehört nicht zum OpenAI-Standard, und die SDKs lehnen ihn als Argument auf oberster Ebene ab. - Zulässig sind Werte von 1 bis 2, wobei 1.0 keine Penalty bedeutet. Werte außerhalb dieses Bereichs beantwortet die API mit einem
400, und genau das ist erwünscht: Anders als die beiden Penalties oben meldet sich dieser Parameter, wenn etwas nicht stimmt. - Bleiben Sie bei höchstens 1.2. Höhere Werte lassen ein Reasoning-Modell länger nachdenken, bevor es antwortet. Bei
gpt-oss-120bbrauchte eine Antwort aus einem einzigen Satz mit 1.0 genau 104 Completion-Token, mit 1.3 schon 244. Ein knapp bemessenesmax_tokensschneidet dann ab, und bei 1.8 liefert das Modell überhaupt keinen Inhalt mehr. - Er ist kein direkter Ersatz für
frequency_penalty.repetition_penaltybestraft auch Token, die in Ihrem Prompt vorkommen, was die OpenAI-Penalties nie tun. Bei einem langen System-Prompt, etwa für einen Voice Agent, kann das genau die Begriffe unterdrücken, auf die der Prompt angewiesen ist. - Er wirkt nur bei
/v1/chat/completions./v1/responsesund/v1/messagesnehmen den Wert an und verwerfen ihn. Das sollten Sie im Blick behalten, wenn Sie eine Integration auf einen anderen Endpoint umstellen: Der Parameter funktioniert dann nicht schlechter, er wird nur nicht mehr ausgewertet.
Und setzen Sie zuerst temperature. Wiederholungen, die durch Greedy Decoding entstehen, sind ein Temperature-Problem, und das lässt sich mit keiner Penalty beheben.
Warum fremde Requests Ihre Ausgabe verändern
Sie haben eine sinnvolle Temperature gesetzt und einen Seed ergänzt. Die Ausgabe ist jetzt wiederholbar, und naheliegend wäre ein Test, der auf den exakten String prüft.
Davon raten wir ab. Ein solcher Test kann fehlschlagen, ohne dass sich auf Ihrer Seite irgendetwas geändert hat.
In den meisten Fällen liefert ein wiederholter Request mit denselben Parametern tatsächlich dieselben Bytes. Gerade das macht dieses Fehlerbild so tückisch: Alles wirkt stabil, bis es das auf einmal nicht mehr ist, und die Ursache liegt weder in Ihrem Code noch in Ihrem Request noch überhaupt auf Ihrer Seite.
Die übliche Erklärung und warum sie nicht stimmt
Wer fragt, warum LLM-Inferenz nicht reproduzierbar ist, bekommt meist dieselbe Antwort: Gleitkomma-Addition ist nicht assoziativ, (a + b) + c ergibt nicht immer dasselbe wie a + (b + c), und auf GPUs laufen Tausende Threads, die jedes Mal in anderer Reihenfolge fertig werden. Deshalb fallen die Summen unterschiedlich aus, und die Logits schwanken.
Der erste Teil stimmt, der zweite nicht.
Horace He und Thinking Machines Lab haben diese Erklärung in Defeating Nondeterminism in LLM Inference (September 2025) widerlegt. Ihr Befund: Einzelne GPU-Kernel liefern bereits bei jedem Lauf dasselbe Ergebnis. Wer dieselbe Matrixmultiplikation tausendmal auf denselben Daten ausführt, erhält jedes Mal bitgenau identische Werte. Der Forward Pass eines LLM enthält keine einzige atomare Addition, also genau die Operation, auf die sich die gängige Erklärung stützt. Entlang der Batch-Dimension gibt es genügend Parallelität, sodass entlang der Reduktionsdimension keine Threads miteinander konkurrieren müssen.
Das Problem sind also nicht die Kernel, sondern Ihr Request.
Batch-Invarianz, oder: Wer sonst noch gerade anfragt
Ein Endpoint im Produktivbetrieb verarbeitet Ihren Request nicht allein. Er fasst ihn mit allem zusammen, was im selben Zeitfenster eingetroffen ist, und die Größe dieses Batches schwankt mit der Last.
Die Kernel, auf die es hier ankommt, also RMSNorm, Matrixmultiplikation und Attention, sind nicht batch-invariant. Ändert sich die Batch-Größe, wird die Reduktion anders aufgeteilt, die Rundung fällt anders aus, und die Werte für Ihre Zeile ändern sich, obwohl Ihre Eingabe dieselbe geblieben ist.
Ihre Ausgabe hängt also davon ab, wie viele fremde Requests im selben Moment an denselben Endpoint gingen. Das lässt sich nicht einstellen, es steht nicht in Ihrem Request, und auch ein Seed kann es nicht fixieren.
Meistens ändert das nichts, weil das führende Token deutlich vorn liegt und eine Rundungsdifferenz in der sechsten Nachkommastelle nicht ins Gewicht fällt. Kritisch wird es bei knappen Entscheidungen. Nehmen wir zwei Kandidaten, die nur ein Millionstel auseinanderliegen: plus mit 3,700001 und + mit 3,699999. In einem Batch aus vier Requests gewinnt plus. In einem Batch aus 32 Requests fällt die Rundung andersherum aus, und + gewinnt mit demselben unsichtbaren Vorsprung.
Bei temperature 0 lässt sich das nicht mehr korrigieren. Greedy Decoding legt sich fest und zieht die Folgen bis zum Ende der Generierung durch, sodass ein einziges gekipptes Token alles Weitere umschreiben kann.
Bei Qwen3-235B mit temperature 0 lieferten tausend identische Requests achtzig verschiedene Completions. Die häufigste kam 78-mal vor. Die erste Abweichung trat bei Token 103 auf: 992 Läufe schrieben dort "Queens, New York", 8 schrieben "New York City", und von da an gingen die Antworten auseinander.
Das Problem lässt sich lösen, aber nicht umsonst
Thinking Machines hat batch-invariante Versionen der drei Kernel geschrieben und veröffentlicht. Damit lieferte dasselbe Experiment tausendmal dieselbe Completion. Zwei Wochen später hat SGLang deterministische Inferenz nach demselben Prinzip eingeführt.
Der Preis ist Latenz. Im Benchmark der Autoren verarbeitete der Standardpfad von vLLM 1000 Sequenzen in 26 Sekunden. Der batch-invariante Pfad brauchte mit einem verbesserten Attention-Kernel 42 Sekunden, vor dieser Optimierung sogar 55. Das entspricht etwa dem Faktor 1,6, und das bei Kerneln, die noch niemand zu Ende optimiert hat.
Auf den Punkt gebracht: Bitgenaue Ausgaben sind zu haben, bezahlt wird mit Durchsatz. Keine große Inferenz-API aktiviert das standardmäßig, weil den meisten Workloads der Durchsatz wichtiger ist.
Was das für Ihre Anwendungen bedeutet
Der Mechanismus steckt in der Inferenz mit Batches selbst, nicht in einem bestimmten Anbieter oder einem bestimmten Chip. Jede Engine, die Requests bündelt und über eine variable Batch-Dimension reduziert, ist davon betroffen, und über das Bündeln von Requests erreicht jeder Inferenz-Stack im Produktivbetrieb seinen Durchsatz. Thinking Machines hat den Effekt auf GPUs mit vLLM nachgewiesen, weil ihnen dieser Stack zur Verfügung stand, und nicht, weil es sich um ein GPU-Problem handelt.
Byte-Identität kann auch auf einem zweiten Weg verloren gehen, unabhängig von der Last, und diesen erleben die meisten Teams zuerst: Zwei unterschiedlich kompilierte Deployments desselben Modells beantworten denselben Prompt unterschiedlich, und jedes für sich ist dabei völlig stabil. Modellname und Parameter sind gleich, nur der Build ist ein anderer. Genau das passiert bei jedem Modell-Upgrade, und zwar mit Absicht.
Daraus ergibt sich eine klare Regel:
Byte-identische Ausgaben lassen sich beobachten, aber nicht als Grundlage verwenden: nicht über eine Lastspitze hinweg, nicht über ein Deployment hinweg und nicht über eine Modellversion hinweg.
Schreiben Sie deshalb keine Tests, die auf einen exakten String prüfen. Prüfen Sie die Eigenschaften, auf die es Ihnen ankommt: gültiges JSON, das richtige Schema, eine Zahl im erwarteten Bereich, eine Aussage, die durch die Quelle gedeckt ist. Solche Tests überstehen auch einen Tag unter Volllast und ein Modell-Upgrade.
Und was ist mit dem Seed?
Innerhalb eines Deployments lässt sich Wiederholbarkeit allerdings gezielt herstellen, statt sie nur zufällig zu beobachten, und dafür gibt es seed.
Der Seed steuert den Zufallszahlengenerator im dritten Schritt, also dort, wo ein Token gezogen wird. Mit seed=42 erzeugt der Generator (RNG) für denselben Request immer dieselbe Folge von Zufallszahlen. Dieselben Zufallszahlen auf dieselben Wahrscheinlichkeiten angewendet ergeben dieselben Token, und deshalb kam Nebulyn fünfmal hintereinander zurück.
Er wirkt nur dort, wo gesampelt wird. Bei temperature 0 findet kein Sampling statt, der RNG kommt nie zum Einsatz, und der Seed hat keine Wirkung. Die identische Ausgabe bei temperature 0 ist also kein fixierter Seed, sondern argmax, und argmax verschiebt sich, sobald sich die Zahlen darunter verschieben.
Nicht jedes Modell berücksichtigt ihn. Manche Modelle im Katalog akzeptieren einen Seed, ignorieren ihn aber, und die Antwort verrät nicht, bei welchen das der Fall ist. Die Seite zur OpenAI-Kompatibilität listet auf, wo er berücksichtigt wird. Prüfen Sie das, bevor Sie eine Evaluierung auf einem Seed aufbauen.
Er fixiert die Zufallszahlen, nicht die Verteilung. Die Wahrscheinlichkeiten, auf die diese Zahlen angewendet werden, legt ein Seed nicht fest. Vor einer veränderten Batch-Größe oder einem Modell-Upgrade schützt er deshalb nicht. Setzen Sie ihn für eine Debugging-Session oder einen Review-Zyklus ein, aber nicht als dauerhafte Garantie.
Welche Konsistenz möglich ist
Einen String, auf den Ihre Tests die nächsten zwei Jahre prüfen können, bekommen Sie nicht. Die Konsistenz, auf die es in Ihrer Anwendung ankommt, dagegen schon.
Starten Sie mit den Parametern, die der Herausgeber empfiehlt. Diese Werte sind nicht willkürlich gewählt:
| Modell | temperature | top_p | top_k |
|---|---|---|---|
| MiniMax-M2.7 | 1.0 | 0.95 | 40 |
| gpt-oss-120b | 1.0 | 1.0 | - |
| gemma-4-31B-it | 1.0 | 0.95 | 64 |
| DeepSeek-V3.2 | 1.0 | 0.95 | - |
| DeepSeek-V3.1 | - | - | - |
| Meta-Llama-3.3-70B-Instruct | - | - | - |
Ein Strich bedeutet, dass der Herausgeber keinen Wert angibt. Auffällig ist, dass alle T=1.0 verwenden, also den Ausgangswert, bei dem die Verteilung dem Training entspricht.
Setzen Sie Temperature immer explizit. Die Infercom API verwendet keinen dienstweiten Standardwert. Fehlt der Parameter, decodieren gpt-oss-120b, gemma-4-31B-it, DeepSeek-V3.2 und Meta-Llama-3.3-70B-Instruct greedy. Nur MiniMax-M2.7 sampelt auch dann. Ein Client, der nie temperature sendet, arbeitet also bei vier von fünf Modellen unbemerkt mit T=0, und das ist für die oben beschriebene Schleife die riskanteste Einstellung.
Das gilt ebenso für /v1/responses und /v1/messages. Wer Code vom Anthropic SDK portiert, sollte wissen: Die Anthropic API dokumentiert für temperature einen Standardwert von 1.0, wir setzen keinen. Ohne ausdrücklichen Wert wechselt der portierte Code also zu Greedy Decoding.
Probieren Sie die Regler selbst aus. Alle drei finden Sie im Playground unter cloud.infercom.ai/playground (dafür ist ein Infercom-Konto nötig), oben rechts unter Tuning Parameters. Dabei fallen zwei Dinge auf. Der Schalter Sampling ist beim Öffnen ausgeschaltet, und solange er aus ist, sendet der Playground weder temperature noch top_p oder top_k. Das entspricht dem Request ohne Temperature, um den es weiter oben ging. Erst wenn Sie den Schalter aktivieren, erscheinen die drei Regler. Außerdem begrenzt der Playground temperature auf 1.0, höhere Werte lassen sich nur über die API setzen.
Setzen Sie für ein einheitliches Format auf Structured Output. Mit JSON-Modus oder Function Calling legen Sie die Form fest, ohne den Inhalt einzuschränken. Wenn Sie ein bestimmtes Format brauchen, ist das zuverlässiger, als an Temperature zu drehen. Bei einem umfangreichen Schema sollten Sie max_tokens großzügig bemessen.
Verwenden Sie einen Seed für wiederholbare Läufe. Bei Modellen, die ihn berücksichtigen, fixiert ein Seed die Ausgabe. So lassen sich Evaluierungsläufe und Fehlerberichte reproduzieren. Prüfen Sie in der Kompatibilitätstabelle, ob Ihr Modell dazugehört, und erfassen Sie Ihre Referenzwerte nach jedem Modell-Upgrade neu.
Senken Sie Temperature für weniger Streuung, aber nicht auf null. Wenn Sie fokussiertere Ausgaben möchten, probieren Sie T=0.3 bis 0.7. Die Vielfalt nimmt ab, ohne dass Sie sich die Fehlerbilder von Greedy Decoding einhandeln.
Prüfen Sie Eigenschaften, keine Strings. Mit dieser einen Änderung hören Tests auf, sporadisch fehlzuschlagen.
Die Abwägung
In diesem Artikel ging es um zwei verschiedene Dinge, die beide als Varianz auftreten, und die eigentliche Lektion besteht darin, sie auseinanderzuhalten.
Das eine ist das Sampling. Es ist gewollt, es erlaubt dem Modell, eine andere Formulierung auszuprobieren oder aus einer Schleife herauszufinden, und gerade sein Abschalten hat ein Reasoning-Modell tausend Token lang an einem Bootsrätsel festgehalten. Behalten Sie es.
Das andere ist numerisches Rauschen aus einem Batch, den Sie sich nicht ausgesucht haben. Darauf kann jeder verzichten. Es lässt sich auch beseitigen: Die Kernel gibt es, sie funktionieren, und tausend Läufe kamen tausendmal identisch zurück. Der Preis liegt aber bei etwa dem Faktor 1,6 beim Durchsatz, und deshalb stellt Ihnen keine große API das standardmäßig in Rechnung. Wenn Ihr Workload bitgenaue Wiederholbarkeit tatsächlich braucht, sollten Sie diesen Preis kennen. Die meisten Workloads brauchen etwas Günstigeres und Ehrlicheres: Ausgaben, die jedes Mal korrekt sind, statt jedes Mal identisch.
Die Frage lautete also nie "Wie beseitige ich Varianz?", sondern "Welche Varianz habe ich vor mir, und lohnt es sich, für ihre Beseitigung zu bezahlen?"
Das Modell, das am Bootsrätsel hängen blieb, hatte die Lösung längst gefunden. Sie stand drei Zeilen über der Stelle, an der die Schleife begann. Es konnte nur nicht aufhören. Bei Temperature 0 muss sich das Token, das den Satz beendet, bei jedem einzelnen Schritt gegen das Token durchsetzen, das ein weiteres Synonym einleitet, und als es diesen Wettstreit zum ersten Mal verlor, war er endgültig verloren. Ein kleines bisschen Zufall hätte genügt.
Verwenden Sie die empfohlenen Parameter und setzen Sie Temperature explizit. Steuern Sie die Ausgabe über Struktur und über einen Seed, statt das Sampling auf null zu zwingen. Und bevor Sie eine Woche in das Tuning eines Parameters stecken, prüfen Sie in einer Minute, ob er überhaupt etwas bewirkt. Mehr braucht es nicht.
Die Standardwerte der einzelnen Modelle finden Sie unter Recommended sampling parameters, welche Parameter ein Modell berücksichtigt, unter OpenAI compatibility.
Weiterlesen
Quellen
- Horace He und Thinking Machines Lab, Defeating Nondeterminism in LLM Inference, 10. September 2025: Quelle für die Ergebnisse zur Batch-Invarianz, das Experiment mit Qwen3-235B und die oben zitierten Latenzwerte
- thinking-machines-lab/batch_invariant_ops: die batch-invarianten Kernel als Open Source
- LMSYS, Towards Deterministic Inference in SGLang, 22. September 2025
Geschrieben von Thomas Vits, mit Unterstützung von KI.
