Tilbage til Insights
TekniskInferensAPI

Determinisme er det forkerte mål: en praktisk guide til pålidelige LLM-resultater

Thomas Vits20. september 202622 min læsetid
Illustration af en maskine med fire drejeknapper. Knappen længst til venstre er dækket af is og drejet helt ned. Uanfægtet spytter maskinen en enorm, ustoppelig strøm af endeløst printerpapir ud, som fylder hele billedet.

Hvorfor temperature=0 ikke gør det, de fleste forventer, og hvordan du alligevel får pålidelige resultater fra en LLM.

En reasoning-model, vi testede, gik i stå. Prompten og parametrene var de samme hver gang, og temperature stod på nul for at give mere "konsistens". Modellen løste opgaven korrekt, men stoppede ikke bagefter: Den blev ved med at remse synonymer for "boat crossing" op, indtil token-grænsen var nået, og det skete ved hver eneste kørsel.

Med temperature på 1.0 og de værdier for top_p og top_k, som modellens udgiver anbefaler, opstod der ikke et eneste loop på 36 kørsler.

Hvis navnene ikke siger dig noget endnu, får du her princippet i få sætninger. En sprogmodel slår ikke et færdigt svar op. Den bygger svaret ord for ord, og ved hvert trin har den en rangliste over mulige fortsættelser foran sig. Temperature, top_p og top_k bestemmer, hvordan den vælger fra listen: om den altid tager førstepladsen, eller om den en gang imellem går lidt længere ned. Mere gør de ikke. De findes i alle LLM-API'er, navnene fortæller intet om, hvad de gør, standardværdierne varierer fra udbyder til udbyder, og de fleste sætter dem ved at kopiere en værdi fra et blogindlæg og rører dem aldrig igen.

Resultatet fra starten modsiger den måde, de fleste forestiller sig, at knapperne virker på. Måske kan du genkende noget af dette:

  • Du har sat temperature: 0, så outputtet kan reproduceres.
  • Du har helt udeladt temperature i forespørgslen i den tro, at platformen vælger en fornuftig værdi.
  • Din evaluering går igennem om mandagen og fejler om torsdagen, uden at du har ændret noget.
  • En model begyndte at gentage sig selv, så du satte frequency_penalty.
  • Du har læst tre forklaringer af top_p og top_k og kan stadig ikke sige, hvornår du skal bruge hvilken.

Ingen af disse reflekser er urimelige. De fleste gør dog ikke det, man forventer, og den første er den mest sikre måde at få en reasoning-model til at fejle på.

Netop temperature=0, den indstilling udviklere griber til, når de vil have konsistens, er den, der oftest skaber problemer. Og den konsistens, den skulle give, fungerer heller ikke, som de fleste tror: Sender du den samme forespørgsel to gange til et hårdt belastet endpoint, kan du få to forskellige svar. Årsagen ligger hverken i forespørgslen eller i parametrene, og et seed kan ikke ændre på det.

Alle målinger, vi markerer som vores egne, er live-kørsler mod vores API, og du kan gentage dem alle. Når et tal stammer fra andres arbejde, står det der.


Kort fortalt

Sprogmodeller giver forskellige svar på det samme spørgsmål af to grunde: Valget af det næste ord indeholder et bevidst element af tilfældighed, og en hårdt belastet server ændrer ubemærket regnestykket nedenunder. Ingen af delene er en fejl, og begge kan håndteres, når man ved, hvilken af dem man har med at gøre.

Hvis du udvikler mod en LLM-API, er det især fem punkter fra denne artikel, du skal tage med:

  • Sæt temperature i hver forespørgsel. Det er ikke et neutralt valg at udelade den. På de fleste modeller her betyder det greedy decoding, som er den mest risikable indstilling overhovedet.
  • Brug den værdi, som modellens udgiver anbefaler. Som regel er det 1.0 og ikke 0. Længere nede finder du en tabel med værdierne for hver model.
  • Med temperature: 0 risikerer du gentagelsesloops. En reasoning-model kan sidde fast og omformulere den samme pointe, indtil den løber tør for tokens. En højere max_tokens gør det kun værre.
  • frequency_penalty og presence_penalty har ingen effekt i denne API. Den parameter, der virker, er repetition_penalty. Send den via extra_body, og hold den på højst 1.2.
  • Skriv aldrig en test, der tjekker det præcise output. Den holder i dag og bryder ved næste modelopgradering, og på et hårdt belastet endpoint holder den måske ikke engang i dag. Tjek i stedet egenskaber: gyldig JSON, det rigtige skema, et tal inden for et interval.

Sådan vælger en model det næste token

Når en LLM genererer tekst, henter den ikke færdigskrevne svar frem. For hvert token, den udskriver, gennemløber den tre trin:

  1. Beregn logits: en rå score for hvert token i ordforrådet
  2. Anvend softmax: scorerne omregnes til sandsynligheder
  3. Sampling: ét token vælges ud fra sandsynlighederne

Temperature, top_p og top_k griber hver især ind i et af disse trin.

Trin 1: Modellen giver hvert token, den kender, en score

Modellens sidste lag giver et tal for hvert token i ordforrådet, typisk mere end 100.000 af dem. Disse scorer kaldes logits.

En logit er ikke en sandsynlighed. Det er en score uden faste grænser, som kan være positiv eller negativ, for eksempel -15,3 eller +8,7. Begrebet kommer fra statistikken, hvor det er en forkortelse af "logistic unit" og betegner log-odds. I neurale netværk betyder det det rå output før den sidste aktiveringsfunktion.

Sådan kunne logits se ud for et enkelt generationstrin:

TokenLogit
"the"+5,2
"a"+3,1
"Paris"+2,8
"quantum"-4,3

En logit på +5,2 betyder ikke en sandsynlighed på 520%. Den betyder kun, at tokenet er mere sandsynligt end tokens med lavere scorer. Scorerne bliver først til sandsynligheder i det næste trin.

Trin 2: Fra scorer til sandsynligheder

Rå scorer kan man ikke bruge til meget, som de er. Nogle er negative, deres sum er ikke noget bestemt, og "+5,2" fortæller ikke, hvor meget mere sandsynligt "the" er end "a". Modellen har brug for en rigtig sandsynlighedsfordeling, hvor alle værdier er positive og tilsammen giver præcis 1.

Det klarer softmax-funktionen i to trin. Først opløftes e i hver score, så alle tal bliver positive, og de høje scorer trækkes langt væk fra de lave. Derefter divideres hvert resultat med summen, så det hele giver 1.

For vores fire kandidater ser resultatet sådan ud:

TokenLogitSandsynlighed
"the"+5,282,4%
"a"+3,110,1%
"Paris"+2,87,5%
"quantum"-4,30,006%

Eksponentialfunktionen gør afstandene større, end man skulle tro. På de rå scorer fører "the" kun med 2,1 over "a", hvilket ikke lyder afgørende. Efter eksponentialfunktionen er forholdet otte til en. Små forskelle i score bliver til store forskelle i sandsynlighed, og det er netop den mekanisme, temperature virker på.

Så er der den nederste række. "quantum" får -4,3 og giver ingen mening som fortsættelse, men ender alligevel med en sandsynlighed over nul. Intet token er nogensinde helt udelukket, for hvert token i ordforrådet beholder en lille chance. Derfor siger en model af og til noget mærkeligt, og derfor findes der en parameter, der kan skære halen af.

Trin 3: Modellen vælger et token

Nu er der sandsynligheder. Sampling betyder, at der trækkes et tilfældigt token, vægtet efter sandsynlighederne.

Man kan forestille sig en linje fra 0 til 1, hvor hvert token fylder et stykke, der er lige så bredt som dets sandsynlighed:

Der trækkes et tilfældigt tal mellem 0 og 1, og det token, hvis stykke tallet lander i, vinder. 0,372 lander i "the", 0,891 i "a". Over mange træk vinder "the" i cirka 82% af tilfældene, men også de mindre sandsynlige tokens kommer til en gang imellem.

Det er her, tilfældigheden kommer ind: Med de samme sandsynligheder giver et andet tilfældigt tal et andet token.


Hvor temperature kommer ind

Temperature virker netop i det eksponentielle trin. Før scorerne opløftes, divideres hver af dem med værdien af temperature. Mere ligger der ikke i mekanismen.

Divideres der med en værdi under 1, bliver scorerne større og rykker længere fra hinanden, så favoritten trækker fra, og fordelingen bliver skarpere. Divideres der med en værdi over 1, rykker de sammen, og også outsiderne får en reel chance. Divideres der med præcis 1, ændres intet, og derfor er 1.0 baseline og ikke maksimum.

TemperatureEffekt
T = 1.0Sandsynligheder som trænet: baseline
T < 1.0Skarpere: tokens med høj sandsynlighed dominerer mere
T > 1.0Fladere: tokens med lav sandsynlighed vinder frem
T → 0Spids: det mest sandsynlige token vinder altid

Temperature 1.0 betyder altså ikke "maksimal tilfældighed". Det er baseline, hvor fordelingen er, som modellen lærte den under træningen. Man kan sammenligne det med kontrasten på et foto: T=1 er originalen, og alle andre værdier er en justering.

Ved T=0 falder fordelingen sammen til en enkelt spids. Ét token får ~100%, alle andre ~0%. Det kaldes greedy decoding: Modellen vælger altid det mest sandsynlige token, helt uden tilfældighed.


Hvor top_p og top_k kommer ind

Begge parametre sorterer kandidater fra efter softmax og før sampling:

Top-k beholder kun de k tokens med den højeste sandsynlighed:

TokenFør top_k=2Efter top_k=2
"the"82,4%89,1% (renormaliseret)
"a"10,1%10,9% (renormaliseret)
"Paris"7,5%fjernet
"quantum"0,006%fjernet

Top-p (nucleus sampling) sorterer tokens efter sandsynlighed og beholder den mindste gruppe, der tilsammen når op på p:

TokenFør top_p=0.9Efter top_p=0.9
"the"82,4% (beholdt)89,1%
"a"10,1% (beholdt)10,9%
"Paris"7,5% (frasorteret)fjernet
"quantum"0,006% (frasorteret)fjernet

Alene når "the" op på 82,4% og ligger dermed under 90%. Med "a" kommer man op på 92,5%, grænsen er overskredet, og gruppen er komplet. Alt det øvrige sorteres fra, og de tilbageværende tokens renormaliseres.

Top-p tilpasser sig, hvor sikker modellen er: Har ét token 95% sandsynlighed, og er p=0.9, er det kun det token, der bliver tilbage. Er sandsynlighederne spredt ud, bliver flere kandidater tilbage.

Det oplagte spørgsmål: Er T=0 ikke det samme som top_k=1?

I resultatet ja, for i begge tilfælde vælges altid det mest sandsynlige token. Teoretisk virker de dog forskellige steder: T=0 omformer fordelingen til en spids, mens top_k=1 kun lader én kandidat tilbage, efter at sandsynlighederne er beregnet.

I praksis er division af en logit med nul ikke defineret, så enhver implementering håndterer det tilfælde særskilt. I Infercoms API er de to én og samme mekanisme, og det har en konsekvens:

top_k virker først, når temperature er mindst 0.001. Under den grænse, også når du helt udelader temperature, bliver din top_k-værdi kasseret, top_k sættes til 1, og der dekodes greedy. Intet i svaret fortæller dig, at din værdi blev ignoreret.

{"top_k": 100} alene gør altså ikke noget bredere. Det returnerer argmax.

Fejlen er nem at begå: en fornuftig forespørgsel, der accepteres fuldt ud, hvor den ene af de to parametre ingen effekt har, og den anden står på den mest risikable værdi på skalaen.

Der er to ting mere at vide om top_k i denne API. Parameteren er ikke en del af OpenAI-standarden, så SDK'erne fra OpenAI afviser den som argument på øverste niveau. Send den derfor via extra_body. Desuden skal værdien være mindst 1, for i modsætning til hos nogle udbydere kan den ikke slås fra med -1 eller 0. Hvis du ikke vil bruge den, så udelad den.


Sådan ser det ud i praksis

Alle blokkene herunder er live-kørsler mod gpt-oss-120b på Infercoms API, målt den 18. september 2026. Prompten var: "Write a creative one-word name for a cat. Reply with the name only." Hver variant blev kørt fem gange, for tre kørsler er ikke nok til at skelne et fastlåst output fra et heldigt tilfælde.

Temperature: konsistens eller variation

Kørseltemperature=0temperature=1.0
1QuasarNebulyn
2QuasarNebula
3QuasarMistral
4QuasarMistral
5QuasarNimbus

Ved T=0 returnerer modellen det samme navn hver gang, ved T=1.0 varierer den.

Fælden: temperature mangler

Her er den samme prompt helt uden et temperature-felt, den ene gang også med top_k: 100. Umiddelbart beder den forespørgsel om en bred pulje af kandidater:

KørselUden temperaturetop_k=100, uden temperature
1QuasarQuasar
2QuasarQuasar
3QuasarQuasar
4QuasarQuasar
5QuasarQuasar

Begge varianter kører greedy og returnerer resultatet fra T=0. Infercoms API anvender ingen tjenesteomfattende standardværdi for temperature. At udelade parameteren betyder altså ikke "brug modellens standardværdi". På de fleste modeller betyder det temperature 0, og det gør samtidig din top_k virkningsløs, uden at du får besked.

For fuldstændighedens skyld: top_k=1 med temperature=1.0 returnerer også Quasar fem ud af fem gange. At filtrere ned til én kandidat er greedy decoding ad en anden vej.

Først når temperature og top_k sættes sammen, åbner puljen sig:

Kørseltop_k=100, temperature=1.0
1Nebula
2Nimbus
3Purrcello
4Mistral
5Luminara

Seed: låser outputtet inden for ét deployment

Samme prompt med temperature=1.0:

Kørselseed=42seed=7Uden seed
1NebulynZephyrusNebulite
2NebulynZephyrusNebula
3NebulynZephyrusVelvetine
4NebulynZephyrusNebulous
5NebulynZephyrusNimbus

Med et seed forbliver outputtet det samme, et andet seed giver et andet output, og uden seed varierer det. Sådan ser et seed ud, der virker, og det er det rigtige værktøj, når du har brug for en evalueringskørsel, der kan gentages. Det har dog tre begrænsninger, som får deres eget afsnit længere nede.

Læg mærke til, hvad der skete. Tre af de seks blokke returnerede Quasar fem ud af fem gange, og i to af dem havde vi umiddelbart bedt om variation. Det er langt lettere at havne i greedy decoding end at komme ud af det igen.

Og det bringer os tilbage til modellen, der ikke kunne holde op med at tale om både.


Den reelle risiko ved T=0: gentagelsesloops

Greedy decoding har sin egen måde at fejle på, og den er værre end et inkonsistent svar.

LLM'er er autoregressive: Hvert token afhænger af alle de foregående tokens, også af modellens eget output. Hvis modellen havner i et mønster, der forstærker sig selv, kan den sidde fast i det.

Når temperature er over 0, giver sampling en vej ud, fordi modellen kan vælge et lidt mindre sandsynligt token, der fører et andet sted hen. Ved temperature 0 findes den vej ud ikke. Modellen følger den samme sti hver gang, og fører stien ind i et loop, bliver den der for altid.

Det har vi set direkte med reasoning-modeller. Ved temperature=0 endte en model i en uendelig cyklus af synonymer:

... boat crossings. / moves. / steps. / transitions. / state changes.
... boat trips. / crossings. / voyages. / journeys.

Den løste opgaven korrekt, men udsendte aldrig et stop-token og blev i stedet ved med at remse næsten-synonymer op, indtil token-grænsen var nået. Ved temperature 0 gik den i loop i alle tre kørsler. Med de indstillinger, som modellens udgiver anbefaler (temperature=1.0, top_p=0.95, top_k=40), gennemførte 36 kørsler normalt.

Loopet er et fikspunkt i modellens sandsynlighedsfordeling: Ved hvert trin fører det mest sandsynlige næste token tilbage ind i cyklussen. Greedy decoding følger den sti i det uendelige. Med sampling vælger modellen en gang imellem et lidt mindre sandsynligt token, og det er nok til at bryde ud.

Udgiverne anbefaler en temperature over 0 til deres reasoning-modeller, og det er en væsentlig del af grunden. Det handler ikke om kreativitet, men om ikke at gå i stå.

En højere max_tokens løser det ikke. Et loop med mere plads er stadig et loop, og du betaler for hvert eneste token af det.

Det er ikke en kuriositet fra laboratoriet. Vi stødte første gang på det i en test. Siden har vi set tre kunder havne i det samme i produktion, og alle tre kom dertil ad samme vej: De havde sænket temperature med vilje og af en god grund. Én ville have reproducerbare benchmarktal, en anden ville have en stemmeagent, der lyder forudsigelig. Netop den refleks, der får en til at sænke temperature, er den, som denne fejltype straffer.


Den gentagelsesparameter, der virker, og de to, der ikke gør

Lad os sige, at temperature er sat fornuftigt, og at der stadig er lidt gentagelse tilbage. Kommer du fra OpenAI's API, er refleksen at gribe til frequency_penalty eller presence_penalty.

I Infercoms API har ingen af dem nogen effekt. De accepteres af hensyn til kompatibiliteten, men anvendes ikke på nogen model. Du får et HTTP 200, et svar, der ser helt normalt ud, og ingen tegn på, at værdien blev ignoreret. Her er den samme prompt ved temperature 0, målt den 18. september 2026:

ForespørgselOutputCompletion-tokens
baseline"Red / Blue / Green"83
frequency_penalty = 2.0"Red / Blue / Green"83
frequency_penalty = 99"Red / Blue / Green"83

Outputtet er identisk ned til den sidste byte og det samme antal tokens, selvom værdien ligger langt uden for det dokumenterede interval.

Det er den dyre slags overraskelse, for en parameter, som din udbyder ignorerer, ser præcis ud som en parameter, der ikke hjælper. Begge forklaringer passer med det, du ser på skærmen. Den ene sender dig til dokumentationen, den anden får dig til at skrue endnu mere på værdien. En af vores kunder brugte en uge på den anden vej.

Den generelle regel gælder ikke kun for os: OpenAI-kompatibel betyder ikke OpenAI-ækvivalent. Alle udbydere implementerer kun en del af det API, delen er forskellig fra udbyder til udbyder, den ændrer sig mellem releases, og svaret fortæller dig aldrig, hvilke dele du fik. Før du tuner en sampling-parameter, uanset platform, bør du derfor tjekke, at platformen overhovedet læser den.

Hvis dine forespørgsler i dag indeholder en af de to parametre, så fjern den eller sæt den til 0. I dag gør den ingenting, men en kommende platform-release vil afvise alle andre værdier med en fejl. Forespørgsler uden parametrene, eller med værdien 0, bliver ikke berørt. Det er bedre at rydde op nu end at opdage det midt i et deploy.

Den parameter, der virker her, er repetition_penalty:

ForespørgselOutputCompletion-tokens
repetition_penalty = 1.2"Red / Blue / Green"90

Genereringen bliver en anden, men svaret er det samme. Der gælder fem regler for brugen:

  1. Send den via extra_body. Den er ikke en del af OpenAI-standarden, så SDK'erne afviser den som argument på øverste niveau.
  2. Det tilladte interval er 1 til 2, og 1.0 betyder, at der ikke gives nogen penalty. Værdier uden for intervallet giver en 400, og det er netop den adfærd, du vil have: I modsætning til de to penalties ovenfor siger denne parameter fra, når noget er galt.
  3. Hold dig på højst 1.2. Højere værdier får en reasoning-model til at tænke længere, før den svarer. På gpt-oss-120b brugte et svar på én sætning 104 completion-tokens ved 1.0 og 244 ved 1.3, så en stram max_tokens begynder at skære svaret af. Ved 1.8 returnerer modellen slet intet indhold.
  4. Den er ikke en direkte erstatning for frequency_penalty. repetition_penalty straffer også tokens, der forekommer i din prompt, hvilket OpenAI's penalties aldrig gør. Med en lang systemprompt, for eksempel til en stemmeagent, kan det undertrykke netop det ordforråd, prompten bygger på.
  5. Den virker kun på /v1/chat/completions. /v1/responses og /v1/messages accepterer værdien og kasserer den. Husk det, hvis du flytter en integration mellem endpoints: Parameteren er ikke holdt op med at virke, den bliver ikke længere læst.

Og sæt temperature først. Gentagelser, der kommer fra greedy decoding, er et temperature-problem, og det løser ingen penalty.


Hvorfor andres forespørgsler ændrer dit output

Du har sat en fornuftig temperature og tilføjet et seed. Nu kan outputtet gentages, og det oplagte næste skridt er en test, der tjekker den præcise streng.

Det bør du lade være med. Sådan en test kan fejle, uden at der er ændret noget som helst på din side.

I de fleste tilfælde giver en gentaget forespørgsel med de samme parametre også de samme bytes tilbage. Netop det gør fejltypen lumsk: Alt ser stabilt ud, indtil det pludselig ikke gør, og årsagen ligger hverken i din kode, i din forespørgsel eller overhovedet på din side.

Den forklaring, alle giver, og hvorfor den er forkert

Spørger man, hvorfor LLM-inferens ikke kan reproduceres, får man et standardsvar: Addition af flydende kommatal er ikke associativ, (a + b) + c er ikke altid lig med a + (b + c), og GPU'er kører tusindvis af tråde, der bliver færdige i en ny rækkefølge hver gang. Derfor bliver summerne forskellige, og logits svinger.

Den første halvdel er rigtig, den anden er ikke.

Horace He og Thinking Machines Lab gjorde op med den forklaring i Defeating Nondeterminism in LLM Inference (september 2025), og deres resultat er, at de enkelte GPU-kernels allerede er deterministiske fra kørsel til kørsel. Kører man den samme matrixmultiplikation tusind gange på de samme data, får man bit for bit identiske resultater hver gang. En LLM's forward pass indeholder ikke en eneste atomisk addition, og det er netop den operation, den gængse forklaring hviler på. Langs batch-dimensionen er der parallelisme nok til, at ingen behøver lade tråde konkurrere langs reduktionsdimensionen.

Problemet er altså ikke GPU-kernels, men din forespørgsel.

Batch-invarians, eller: hvem der ellers sendte forespørgsler

Et endpoint i produktion behandler ikke din forespørgsel alene. Det samler den i et batch med alt det andet, der kom ind i samme tidsvindue, og batch-størrelsen svinger med belastningen.

De kernels, der betyder noget her, altså RMSNorm, matrixmultiplikation og attention, er ikke batch-invariante. Ændrer batch-størrelsen sig, deles reduktionen anderledes op, afrundingen bliver en anden, og tallene for din række ændrer sig, selvom dit input er det samme.

Dit output afhænger af, hvor mange andre der sendte forespørgsler til det samme endpoint i samme øjeblik. Det er ikke noget, du kan indstille. Det står ikke i din forespørgsel, og et seed kan ikke låse det fast.

For det meste ændrer det intet, fordi det førende token vinder med god margin, og en afrundingsforskel på sjette decimal når ikke op til det. Problemet er de næsten uafgjorte tilfælde. Forestil dig to kandidater, der kun ligger en milliontedel fra hinanden: plus på 3,700001 og + på 3,699999. I et batch med fire forespørgsler vinder plus. I et batch med 32 falder afrundingen den anden vej, og + vinder med den samme usynlige margin.

Ved temperature 0 er der ingen vej tilbage fra det. Greedy decoding lægger sig fast og følger konsekvenserne til slutningen af genereringen, så ét vendt token kan omskrive alt, hvad der kommer efter.

På Qwen3-235B ved temperature 0 gav tusind identiske forespørgsler firs forskellige completions. Den hyppigste optrådte 78 gange. Den første afvigelse kom ved token 103, hvor 992 kørsler skrev "Queens, New York" og 8 skrev "New York City", og derfra gik svarene hver sin vej.

Det kan løses, men det koster

Thinking Machines skrev batch-invariante versioner af de tre kernels og udgav dem. Med dem gav det samme eksperiment tusind identiske completions ud af tusind. To uger senere lancerede SGLang deterministisk inferens efter samme princip.

Regningen kommer som latens. I deres benchmark behandlede vLLM's standardsti 1000 sekvenser på 26 sekunder, mens den batch-invariante sti brugte 42 sekunder med en forbedret attention-kernel og 55 sekunder før den optimering. Det svarer til cirka 1,6 gange, på kernels, som ingen er færdig med at optimere.

Handlen kan siges på én linje: Bit for bit identisk output kan købes, og betalingen er throughput. Ingen stor inferens-API slår det til som standard, fordi de fleste workloads hellere vil beholde deres throughput.

Hvad det betyder for det, du bygger

Mekanismen hører til inferens i batches, ikke til en bestemt udbyder eller en bestemt type chip. Enhver motor, der samler forespørgsler og reducerer over en variabel batch-dimension, er udsat for den, og det er netop ved at samle forespørgsler, at enhver inferensstack i produktion opnår sit throughput. Thinking Machines viste det på GPU'er med vLLM, fordi det var den stack, de havde ved hånden, og ikke fordi det er et GPU-problem.

Der er også en anden måde at miste byte-identiteten på, uafhængigt af belastningen, og den møder de fleste teams først: To forskelligt kompilerede deployments af den samme model giver to forskellige svar på den samme prompt, og hvert af dem er helt stabilt i sig selv. Modelnavnet og parametrene er de samme, kun buildet er et andet. Det sker ved enhver modelopgradering, med vilje og hver gang.

Heraf følger en klar regel:

Byte-identisk output kan du observere, men du kan ikke bygge på det, hverken hen over en belastningstop, et deployment eller en modelversion.

Skriv derfor ikke tests, der tjekker en præcis streng. Tjek de egenskaber, du har brug for: gyldig JSON, det rigtige skema, et tal inden for et interval, en påstand, som kilden understøtter. Sådanne tests overlever både en travl tirsdag og en modelopgradering.


Hvad med seed?

Inden for ét deployment kan du dog få gentagelighed med vilje i stedet for ved et tilfælde, og det er det, seed er til.

Et seed styrer tilfældighedsgeneratoren i det tredje trin, dér hvor et token trækkes. Sætter du seed=42, producerer generatoren (RNG) den samme række af træk for den samme forespørgsel. De samme træk på de samme sandsynligheder giver de samme tokens, og derfor kom Nebulyn tilbage fem ud af fem gange.

Det virker kun dér, hvor der samples. Ved temperature 0 foregår der ingen sampling, så RNG'en kommer aldrig i brug, og seedet har ingen effekt. Det identiske output, du får ved temperature 0, er ikke et fastlåst seed. Det er argmax, og argmax flytter sig, når tallene nedenunder flytter sig.

Det respekteres ikke af alle modeller. Nogle modeller i kataloget accepterer et seed, men ignorerer det, og intet i svaret fortæller dig hvilke. Siden om OpenAI-kompatibilitet viser, hvor det respekteres. Tjek den, før du bygger en evaluering op omkring et seed.

Det låser trækkene, ikke fordelingen. Et seed fastlåser de tilfældige tal, men ikke de sandsynligheder, tallene anvendes på. Derfor kan det ikke beskytte dig mod en ændret batch-størrelse eller en modelopgradering. Brug et seed til en debug-session eller en review-runde, ikke som en langsigtet garanti.


Den konsistens, du kan få

En streng, som dine tests kan regne med de næste to år, får du ikke. Men den konsistens, din applikation har brug for, kan du godt få.

Start med de parametre, som modellens udgiver anbefaler. De er ikke valgt tilfældigt:

Modeltemperaturetop_ptop_k
MiniMax-M2.71.00.9540
gpt-oss-120b1.01.0-
gemma-4-31B-it1.00.9564
DeepSeek-V3.21.00.95-
DeepSeek-V3.1---
Meta-Llama-3.3-70B-Instruct---

En streg betyder, at udgiveren ikke angiver en værdi. Læg mærke til, at de alle bruger T=1.0, altså baseline, hvor fordelingen svarer til den fra træningen.

Sæt altid temperature eksplicit. Infercoms API anvender ingen tjenesteomfattende standardværdi. Mangler parameteren, dekoder gpt-oss-120b, gemma-4-31B-it, DeepSeek-V3.2 og Meta-Llama-3.3-70B-Instruct alle greedy. MiniMax-M2.7 er undtagelsen og sampler. En klient, der aldrig sender temperature, kører altså med T=0 på fire ud af fem modeller uden at vide det, og det er den mest risikable indstilling for det loop, vi beskrev ovenfor.

Det gælder også for /v1/responses og /v1/messages. Hvis du porterer kode fra Anthropics SDK, så vær opmærksom på, at Anthropics API dokumenterer en standardværdi for temperature på 1.0, mens vi ikke anvender nogen. Uden en eksplicit værdi skifter din porterede kode til greedy decoding.

Prøv knapperne i playground. Du finder alle tre på cloud.infercom.ai/playground (det kræver en Infercom-konto) under Tuning Parameters øverst til højre. Her er der to ting at lægge mærke til. Kontakten Sampling er slået fra, når du åbner siden, og så længe den er slået fra, sendes hverken temperature, top_p eller top_k. Det svarer til forespørgslen uden temperature, som vi så tidligere i artiklen. Slår du kontakten til, kommer de tre knapper frem. Playground begrænser temperature til 1.0, så værdier over baseline kan kun sættes via API'et.

Brug structured output til et ensartet format. JSON-tilstand eller function calling fastlægger formen uden at begrænse indholdet. Har du brug for et bestemt format, er det mere pålideligt end at skrue på temperature. Sæt max_tokens rundeligt, hvis dit skema er stort.

Brug et seed til kørsler, der skal kunne gentages. På de modeller, der respekterer det, låser et seed outputtet fast, så evalueringskørsler og fejlrapporter kan reproduceres. Tjek kompatibilitetstabellen for din model, og opdater dine referenceværdier efter en modelopgradering.

Sænk temperature for mindre variation, men ikke til nul. Vil du have mere fokuserede outputs? Prøv T=0.3 til 0.7. Du får mindre variation uden de fejltyper, greedy decoding giver.

Tjek egenskaber, ikke strenge. Det er den ene ændring, der får en testsuite til at holde op med at fejle i ny og næ.


Afvejningen

To forskellige ting har her gået under navnet variation, og hele pointen er at holde dem adskilt.

Den ene er sampling. Den har du selv bedt om, den lader modellen prøve en anden formulering eller komme ud af et loop, og det var netop det at slå den fra, der holdt en reasoning-model fast i en bådgåde i tusind tokens. Behold den.

Den anden er numerisk støj fra et batch, du ikke selv har valgt. Den vil ingen have. Den kan fjernes, for de nødvendige kernels findes, de virker, og tusind kørsler kom tilbage tusind gange identiske. Men prisen er cirka 1,6 gange i throughput, og derfor sender ingen stor API dig regningen for det som standard. Har din workload brug for bit for bit gentagelighed, er det en pris, du bør kende. De fleste workloads har brug for noget billigere og mere ærligt: output, der er korrekt hver gang, snarere end identisk hver gang.

Spørgsmålet var altså aldrig "hvordan fjerner jeg variation?", men "hvilken variation ser jeg på, og er den værd at betale for at fjerne?"

Modellen, der sad fast i bådgåden, havde allerede løst den. Svaret stod i dens ræsonnering, tre linjer over det sted, hvor loopet begyndte. Det, den ikke kunne, var at stoppe. Ved temperature 0 skal det token, der afslutter sætningen, ved hvert eneste trin slå det token, der indleder endnu et synonym, og første gang det tabte den kamp, var den tabt for altid. Lidt tilfældighed havde været nok.

Brug de anbefalede parametre, og sæt temperature eksplicit. Styr outputtet med struktur og med et seed i stedet for at tvinge sampling ned på nul. Og før du bruger en uge på at tune en parameter, så brug et minut på at tjekke, om den overhovedet gør noget. Mere skal der ikke til.


Standardværdierne for hver model finder du under Recommended sampling parameters, og hvilke parametre hver model respekterer, står under OpenAI compatibility.

Kilder

Skrevet af Thomas Vits, med assistance fra AI.

Klar til at bygge fremtidens AI i Europa?

Slut dig til virksomheder, der bruger suveræn AI med performance i topklasse