Przejdź do treści
17/30Rozdział 17 z 30

Temperature, top-p i determinizm, którego nie masz

Temperature dzieli logits przed softmax — i obala mit pokrętła kreatywności. Ten sam greedy call dwa razy, dwa różne wyniki.

Na tej stronie

Oto to samo żądanie wysłane do tego samego modelu pięć razy. Te same wagi, ten sam prompt, ta sama maszyna, ten sam losowy seed. Zmienia się tylko jedna liczba.

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

Nic się nie zepsuło. Każdy token w ostatniej linii został legalnie wylosowany z własnego rozkładu prawdopodobieństwa modelu nad jego słownikiem liczącym 151 936 pozycji. Liczba, która się zmieniła, nazywa się temperature, w większości dokumentacji jest opisywana jako pokrętło kreatywności, a ten opis jest błędny w sposób, który ten rozdział może pokazać, a nie tylko zadeklarować.

To także rozdział, w którym trzeba rozliczyć trzy wcześniejsze obietnice. Rozdział 4 zdefiniował logit i nigdy naprawdę go nie wykorzystał. Ramka o liczbach zmiennoprzecinkowych z rozdziału 2 kończyła się instrukcją — pamiętaj o tym, gdy rozdział 17 zapyta, dlaczego ten sam prompt, model i seed mogą dać różne tokens. A ramka o mixture-of-experts z rozdziału 9 obiecała katalog czterech przyczyn niedeterminizmu. Wszystkie trzy pojawiają się niżej.

Jedna linia, na której wisi cały rozdział

Link do sekcji: Jedna linia, na której wisi cały rozdział

Rozdział 4 wprowadził logit jako nieznormalizowany wynik rzeczywisty, po jednym na klasę. Rozdział 8 sprawił, że model językowy wygenerował po jednym na każdą pozycję słownika. softmax zamienia ten wektor z\mathbf{z} na prawdopodobieństwa:

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

Temperature wchodzi tutaj — nazwa została zapożyczona z fizyki statystycznej, gdzie ten sam parametr kontroluje, jak ostro rozkład Boltzmanna koncentruje się na stanach o niskiej energii1 — i dzieli logits przed funkcją wykładniczą:

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

To umiejscowienie jest całym mechanizmem i warto poświęcić dwie linie algebry, by zobaczyć, dlaczego nie mogłoby być gdzie indziej. Załóżmy, że próbujesz zastosować temperature do prawdopodobieństw — przeskalować je przez 1/T1/T i zrenormalizować. Otrzymasz

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

Stała się skraca. Skalowanie prawdopodobieństw nie robi absolutnie nic; rozkład wraca bez zmian. Temperature działa tylko dlatego, że wpływa na wykładnik, gdzie dzielenie przez TT przed potęgowaniem jest tym samym, co podniesienie każdego prawdopodobieństwa do potęgi 1/T1/T — nieliniowe przekształcenie, które zmienia stosunki między pozycjami, a nie ich wspólną skalę.

Z tego umiejscowienia wynikają oba graniczne przypadki bez żadnej dodatkowej pracy. Gdy T0T \to 0, największy logit ucieka reszcie i pp zapada się do pojedynczego najwyżej ocenionego token: greedy decoding. Gdy TT rośnie, każde zi/Tz_i/T zmierza do zera, każda wartość wykładnicza zmierza do 1, a rozkład spłaszcza się w stronę jednostajnego na całym słowniku. Dokładnie przy T=0T = 0 wzór dzieli przez zero, więc każda implementacja obsługuje to specjalnym przypadkiem jako maksimum arytmetyczne — także widżet poniżej, który przy T0.001T \le 0.001 przełącza się na argmax.

Jedno ostrzeżenie, bo zderzenie nazw powoduje realne zamieszanie. W machine learning istnieje druga, niezwiązana rzecz nazywana temperature: temperature scaling, metoda kalibracji dopasowująca jedną wartość na zbiorze walidacyjnym tak, aby pewność klasyfikatora odpowiadała jego trafności.2 Ten sam wzór, nic wspólnego z generowaniem. Artykuły mówiące „temperature” często mają na myśli właśnie to; ten rozdział nigdy.

Oto ten rozkład, z arytmetyką przed oczami. logits są stałe i wiarygodne, więc liczby w prozie poniżej można sprawdzić względem tego, co widzisz:

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

10 z 10 tokenów przechodzi odcięcie i dzieli prawdopodobieństwo.

Zobacz dane w tabeli
TokenlogitPo zastosowaniu temperaturyPo odcięciu
␣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%
Próbkowanie: temperatura, top-p i top-k

Dziesięć kandydackich kontynuacji The capital of France is, przy temperature 1 bez przycinania. ␣Paris trzyma 96,90 % masy; ␣banana, na dole z logit równym 2.6-2.6, dostaje 0,00 %. Przesuń temperature do 0 i przeżyje jeden token ze 100 %. Przesuń ją do 2, a ␣Paris spadnie do 69,81 %, podczas gdy ␣banana urośnie do 0,17 % — odrzucony przez model token, któremu realne prawdopodobieństwo dało pokrętło przekręcone przez czytelnika.

Temperature nie jest pokrętłem kreatywności

Link do sekcji: Temperature nie jest pokrętłem kreatywności

Liczba ␣banana to cały argument w miniaturze: podniesienie temperature nie może dać modelowi pomysłu, którego nie miał. logits są już obliczone, ranking jest już ustalony, a temperature zachowuje go dokładnie — żadna ilość ciepła nigdy nie przeniesie niżej ocenionego token ponad wyżej oceniony. Wszystko, co robi, to redystrybucja masy w dół rankingu, który model sam wyprodukował. Wysoka temperature nie czyni modelu bardziej pomysłowym; sprawia, że częściej emituje tokens, które sam ocenił jako złe.

Na prawdziwym słowniku przestaje to być ciekawostką i staje się powodem, dla którego wynik z wysoką temperature jest bezużyteczny. Pomiar na Qwen/Qwen2.5-0.5B-Instruct, jeden forward pass, powyższy prompt, zliczający, ile tokens potrzeba, aby zgromadzić daną część masy prawdopodobieństwa:

temperatureprawdopodobieństwo top-1entropiatokens trzymające 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

Przeczytaj ostatni wiersz powoli. Przy T=2T = 2, w pytaniu z dokładnie jedną poprawną odpowiedzią, 32 966 różnych tokens dzieli górne 90 % masy prawdopodobieństwa. To nie jest szersza przestrzeń twórcza. To model, któremu arytmetyka kazała traktować koreańską partykułę i identyfikator C++ jako żywe opcje dla słowa po A:. Śmieci w bloku otwierającym są bezpośrednią konsekwencją i nie są błędem modelu ani biblioteki — to właśnie zamówiło żądanie.

Użyteczny zakres jest wąski i zależy od zadania, a nie od gustu. W pytaniu faktograficznym odpowiedzią jest jeden token i każde ciepło powyżej około 1.2 wstrzykuje błąd bez zysku. W zadaniu otwartym naprawdę istnieje więcej niż jedna dobra kontynuacja, a trochę ciepła kupuje różnorodność, która pozostaje płynna:

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

Przy 1.3 model wymyślił nazwę własną i zdanie, którego nie da się sparsować. Pasmo między „za każdym razem identycznie” a „niespójnie” wynosi mniej więcej od 0.6 do 1.1 dla tego modelu na tym zadaniu, a uczciwa rada brzmi: znajdź je przez pomiar na swoim zadaniu, nie przez kopiowanie liczby z wpisu na blogu.

Dlaczego najbardziej prawdopodobny tekst jest złym tekstem

Link do sekcji: Dlaczego najbardziej prawdopodobny tekst jest złym tekstem

Pod tym wszystkim kryje się oczywiste pytanie: jeśli model ma rozkład prawdopodobieństwa i jeden token jest najbardziej prawdopodobny, dlaczego nie zawsze go brać? Greedy decoding jest darmowe, powtarzalne i nie wymaga parametrów.

Bo wynik wygląda tak:

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 %

Osiem zdań, jedno zdanie. Prawie dziewięć na dziesięć okien czterech tokens pojawiło się już wcześniej w tym samym wyniku. To neural text degeneration, nazwane i wyjaśnione przez Holtzmana i współautorów w pracy, która wprowadziła top-p.3 Model nie jest zepsuty; maksymalizacja prawdopodobieństwa sekwencji jest po prostu złym celem dla tekstu otwartego. Ludzkie pisanie nie jest najbardziej prawdopodobną sekwencją słów — niesie zaskoczenie, jego prawdopodobieństwo per-token wędruje, spada i się podnosi — podczas gdy ścieżka o maksymalnym prawdopodobieństwie jest punktem stałym, który po wejściu nie ma powodu, by go opuścić.

Dlatego sampling w ogóle istnieje. I dlatego — a to jest część, którą zwykle się pomija — nie jest prawem uniwersalnym. Rozdział 12 zmierzył 24 na 24 poprawne odpowiedzi w dwuetapowych zadaniach słownych przy zwykłym greedy decoding, a sampling przy temperature 0.8 zbił to do 81 %; self-consistency potem wydało sześć razy więcej tokens, by wrócić tam, gdzie greedy już było. Oba fakty są prawdziwe naraz:

Generowanie otwarte. Nie ma jednej właściwej kontynuacji, więc najbardziej prawdopodobna jest pułapką — zapętla się, a 87,6 % tekstu jest skopiowane z samego siebie. Sampluj.

Zadania z jedną właściwą odpowiedzią. Istnieje jedna poprawna kontynuacja, więc wylosowanie czegokolwiek innego jest wylosowaniem błędu. 100 % z rozdziału 12 stało się 81 % dokładnie z tego powodu. Nie sampluj.

Większość produkcyjnych prompts należy do drugiego rodzaju, a jest konfigurowana jak pierwszy, bo temperature zostało na tym, czego używał przykładowy kod.

Dwa sposoby cięcia i tylko jeden z nich się dostosowuje

Link do sekcji: Dwa sposoby cięcia i tylko jeden z nich się dostosowuje

Sampling z pełnego rozkładu nie jest tym, co ktokolwiek naprawdę robi, bo ogon jest ogromny i pełen nonsensu. Coś trzeba odciąć. Istnieją dwie klasyczne odpowiedzi i różnią się jedną rzeczą, która decyduje o wszystkim.

Top-k zachowuje stałą liczbę kandydatów. Posortuj według prawdopodobieństwa, zachowaj pierwsze kk, odrzuć resztę, zrenormalizuj.4 Top-p, nazywane też nucleus sampling, zachowuje stałą ilość masy: bierz tokens w kolejności malejącej, aż ich skumulowane prawdopodobieństwo osiągnie pp, i zatrzymaj się.3 Formalnie nucleus jest najmniejszym zbiorem VpV_p takim, że

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

Różnica brzmi kosmetycznie, ale taka nie jest, bo dwa prompts wysłane w tej samej minucie mają zupełnie różne kształty rozkładu. Oba poniżej to ten sam model przy temperature 1:

Q: What is the capital of France?\nA:Once upon a time,
prawdopodobieństwo top-196.01 %25.39 %
tokens trzymające 90 % masy1467
top-k = 40 zachowuje99.61 % masy78.87 % masy
masa w rangach od 2 do 403.61 %53.48 %
token na randze 40␣Av, 0.0093 %␣Dr, 0.128 %

Jedno stałe kk, dwie porażki w przeciwnych kierunkach. Na faktograficznym prompt k=40k = 40 wpuszcza 39 tokens, które razem są warte 3,6 % — przepuszcza śmieci, w tym kandydata o dziewięciu tysięcznych procenta, bo reguła liczy miejsca, a nie dowody. Na prompt z opowieścią to samo k=40k = 40 odrzuca 21 % masy, którą model naprawdę przypisał, bo rzeczywisty nucleus ma tam szerokość 467 tokens.

Top-p sprawia, że dokładnie jedna liczba robi oba zadania. Ustaw p=0.9p = 0.9, a zachowa 1 token na pierwszym prompt i 467 na drugim, bo zadaje pytanie o rozkład, zamiast narzucać na niego liczebność. Zobacz tę adaptację bezpośrednio — to samo cięcie, cztery temperatures:

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

3 z 10 tokenów przechodzi odcięcie i dzieli prawdopodobieństwo.

Zobacz dane w tabeli
TokenlogitPo zastosowaniu temperaturyPo odcięciu
␣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%
Próbkowanie: temperatura, top-p i top-k

Top-p przy 0.90 z temperature 1.5: przeżywają trzy z dziesięciu tokens i dzielą masę, ␣Paris zrenormalizowane do 91,10 %. Teraz przesuń tylko temperature. Przy 0.7 to samo 0.90 zostawia jednego ocalałego — tak wąski nucleus to greedy decoding pod inną nazwą. Przy 2.0 zostawia pięciu. Cięcie nigdy się nie ruszyło; kształt pod nim tak.

Ten widżet rozstrzyga też nieporozumienie warte nazwania, bo kosztuje ludzi realne pieniądze. Na pewnym rozkładzie top_p = 0.9 to nie „trochę różnorodności”. To greedy. Przy temperature 1 wiodący token ma tutaj 96,90 %, czyli już ponad 0.9, więc nucleus ma szerokość jednego token i nic innego nigdy nie może zostać wylosowane. Zespoły ustawiają top_p na 0.9, wierząc, że coś poluzowały, a potem dziwią się, dlaczego każda odpowiedź jest identyczna.

Ustaw zamiast tego top-k, a przeciwna porażka jest równie widoczna:

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

5 z 10 tokenów przechodzi odcięcie i dzieli prawdopodobieństwo.

Zobacz dane w tabeli
TokenlogitPo zastosowaniu temperaturyPo odcięciu
␣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%
Próbkowanie: temperatura, top-p i top-k

Top-k przy 5, bez top-p. Pięć tokens przeżywa przy każdej temperature, bo poproszono o pięć. Przy temperature 1, jak pokazano, czterej kandydaci poniżej ␣Paris są razem warci 2,79 %. Zejdź do 0.7, a ta sama czwórka jest warta 0,38 % — cięcie jest teatrem, a model jest w praktyce greedy. Podnieś do 2.0, a są warci 22,54 %. Identyczne ustawienie, identyczna liczba ocalałych, trzy zupełnie różne zachowania i nic w żądaniu nie mówi ci, które dostajesz.

Kary, ze wzorami, bo mylenie ich jest powszechne

Link do sekcji: Kary, ze wzorami, bo mylenie ich jest powszechne

Trzy różne mechanizmy chodzą pod podobnymi nazwami, robią różne rzeczy, a różnica jest mierzalna. Niech cic_i będzie liczbą wystąpień token ii do tej pory.

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

Odejmij stałą od każdego token, który w ogóle już wystąpił. Jedno wystąpienie i czterdzieści wystąpień są karane identycznie. To przełącznik, nie pokrętło.

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

Odejmij proporcjonalnie do liczby wystąpień. Token użyty cztery razy jest karany cztery razy mocniej niż token użyty raz, a presja narasta wraz z tekstem.

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}

Oryginalna, z pracy CTRL.7 Ona dzieli, zamiast odejmować, z przypadkiem znaku potrzebnym dlatego, że dzielenie ujemnego logit uczyniłoby go większym. Jej siła zależy więc od wielkości logit, co oznacza, że to samo ρ\rho uderza inaczej w różnych miejscach tego samego zdania.

Ta sama zdegenerowana kontynuacja co wcześniej, z zastosowaniem każdej kary. „Zmienione kroki” liczą, ile ze 120 kroków generowania wybrało inny token niż wybrałby model bez kary. Ten przebieg ma tutaj 120 kroków wobec 140 w bloku powyżej, dlatego baseline bez kary pokazuje 85,5 %, a nie 87,6 %:

ustawieniepowtórzone 4-gramyzmienione kroki
nic85.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

Wynikają z tego trzy rzeczy. Presence przy 0.5 zmieniła trzy decyzje ze 120 i obcięła powtarzalność o jedną czwartą — pętlę trzymała garść tokens. Frequency przy 0.5 zmieniła cztery razy więcej decyzji i dała dużo większy efekt, bo mnożnik liczby wystąpień wciąż rośnie, a stała presence nie. A kara CTRL przy szeroko kopiowanej wartości 1.2 przepisała 35 ze 120 decyzji, co nie jest muśnięciem; to inny model.

Ta ostatnia liczba przygotowuje porażkę, przed którą nikt nie ostrzega.

Co kary robią tekstowi, który ma się powtarzać

Link do sekcji: Co kary robią tekstowi, który ma się powtarzać

Kod się powtarza. Tabele się powtarzają. Listy się powtarzają. Wynik strukturalny powtarza się z definicji — na tym polega struktura. Kara nie potrafi odróżnić modelu utkniętego w pętli od modelu poprawnie emitującego czwarty wiersz tabeli, bo oba wyglądają jak ponownie pojawiający się token.

Te same trzy zadania, wygenerowane trzema sposobami:

zadanienicfrequency 0.5repetition 1.2
tabela markdown, 6 wierszy0 / 56 zmienionych kroków0 / 562 / 62
funkcja Python0 / 930 / 9310 / 110
lista punktowana, 1 do 120 / 500 / 500 / 50

Frequency penalty przy 0.5 okazała się nieszkodliwa we wszystkich trzech, co jest użytecznym i lekko zaskakującym wynikiem, i mówi coś precyzyjnego: skoro żadna decyzja się nie zmieniła, strukturalne tokens musiały wygrywać swoje pozycje o więcej, niż kara odejmowała, nawet po pięciu i sześciu wystąpieniach. Kara CTRL, która zamiast tego dzieli, wypycha je z pozycji — oto co wyprodukowała:

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

Wyrównanie się rozpada: ilość wypełnienia wewnątrz każdej komórki zmienia się z wiersza na wiersz, bo seria spacji przed zamykającą kreską jest dokładnie tym rodzajem powtórzenia, które kara ma rozbijać. Kosmetyka, a kosztowała sześć dodatkowych tokens. Przypadek Python nie jest kosmetyczny:

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,

Kara zepchnęła model z total — już użytego w docstring — na total_sum, wypchała wynik wymyślonymi komentarzami, by wydać budżet na nieużyte tokens, a potem weszła w trzyargumentowe range z krokiem. Komentarz mówi incrementing by 2 each time, co jest błędne dla sumy kwadratów od 1 do nn. Repetition penalty wyprodukowała niepoprawny kod z prompt, na który bez niej odpowiedź była poprawna.

Wynikająca z tego reguła jest krótka: kary są do otwartej prozy i powinny być wyłączone dla kodu, wyników strukturalnych, danych tabelarycznych i wszystkiego ze schematem. Rozdział 18 jest dokładnie o tej drugiej kategorii.

Kolejność stosowania i dlaczego zmienia odpowiedź

Link do sekcji: Kolejność stosowania i dlaczego zmienia odpowiedź

Każda realna implementacja stosuje te elementy w jednej konkretnej sekwencji:

kary → temperature → top-k → top-p → sample

To nie jest arbitralna księgowość, a zamiana dwóch etapów naprawdę produkuje inne rozkłady. Dwa pomiary, oba na faktograficznym prompt.

Cięcie przed albo po temperature. Nucleus jest liczony na tym rozkładzie, który dostaje, a temperature radykalnie zmienia ten rozkład:

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

Przy T=2T = 2 to samo nominalne ustawienie daje zbiór kandydatów o rozmiarze 32 966 albo 1, wyłącznie w zależności od tego, który etap działa pierwszy. Jeśli kiedykolwiek zastanawiało cię, dlaczego podniesienie temperature „nic nie robi” u jednego dostawcy, a niszczy wynik u innego przy tych samych dwóch liczbach, ta tabela jest wiarygodną odpowiedzią.

Karanie przed albo po temperature. Odjęcie kary α\alpha, a potem podzielenie przez TT daje efektywną karę α/T\alpha/T; najpierw dzielenie, a potem odejmowanie daje α\alpha. Przy presence penalty 1.0 zastosowanej do wiodącego token:

temperaturekara, potem temperaturetemperature, potem kara
0.599.858 %99.948 %
1.089.839 %89.839 %
2.010.783 %6.830 %

Identyczne przy T=1T = 1, bo muszą takie być. Różnią się o czynnik 1,58 przy T=2T = 2. „Presence penalty 1.0” nie jest dobrze zdefiniowaną ilością kary, jeśli nie wiesz też, gdzie stosowana jest temperature, a żadne API tego nie dokumentuje.

Pokaż szczegóły

Opcjonalnie: cały pipeline, w powyższej kolejności.

Szesnaście linii i wszystko z tego rozdziału jest w nich zawarte. To ten sam rachunek, który wykonuje widżet, na prawdziwym wektorze logit zamiast dziesięciu stałych liczb.

sample.pyPYTHON
def sample(logits, counts, presence=0.0, frequency=0.0,
           temperature=1.0, top_k=0, top_p=1.0, generator=None):
    z = logits.clone()

    idx = torch.tensor(list(counts))                       # 1. penalties
    if len(idx):
        z[idx] -= presence
        z[idx] -= frequency * torch.tensor([float(c) for c in counts.values()])

    if temperature <= 0:                                   # 2. temperature
        return int(z.argmax())                             #    T=0 is argmax
    p = torch.softmax(z / temperature, -1)

    p, order = p.sort(descending=True)
    if top_k:                                              # 3. top-k
        p[top_k:] = 0
    p = p * ((p.cumsum(0) - p) < top_p)                     # 4. top-p

    p = p / p.sum()                                        # 5. renormalise
    return int(order[torch.multinomial(p, 1, generator=generator)])

cumsum(0) - p w linii top-p to skumulowana masa bez bieżącego token, dzięki czemu nucleus obejmuje token, który przekracza próg, zamiast zatrzymywać się tuż przed nim. Pomyl to o jeden, a top_p = 0.9 po cichu staje się odrobinę ciaśniejszym cięciem niż w każdej innej implementacji.

To jedno z niewielu miejsc w drugiej połowie kursu, gdzie Python jest właściwym językiem, a powód jest strukturalny, nie stylistyczny: każda linia powyżej wymaga pełnego wektora logits w twoich rękach, a przez HTTP API taki wektor nie istnieje. Możesz wysłać temperature i top_p do dostawcy; nie możesz ich zaimplementować i nie możesz zobaczyć, co zrobiły.

Każdy dostawca przyjmuje inny podzbiór tych kontrolek, z innymi zakresami, i resztę ignoruje po cichu. To nie jest abstrakcyjna skarga. Każda aplikacja oferująca wybór modelu musi gdzieś spisać różnice, a plik, w którym to robi, jest mapą niekompatybilności. Oto co jeden taki katalog deklaruje dla jednego parametru w dziewięciu obsługiwanych źródłach tekstu:

deklarowany zakres temperatureźródła
0 do 1Anthropic, Google, Meta, Cerebras, PaLM
0 do 1.5Mistral
0 do 2OpenAI, DeepSeek, xAI

Słowo jest to samo; skala nie. „Temperature 1” jest niezmodyfikowanym rozkładem u jednego i maksymalnie dozwolonym ciepłem u innego, a połowa katalogu nie potrafi wyrazić wartości, którą druga połowa traktuje jako neutralną-plus-trochę. Reszta pokręteł jest równie nierówna: wpisy OpenAI, DeepSeek i xAI przyjmują presence i frequency penalties, ale nie topK; wpisy Google, Meta, Cerebras i PaLM przyjmują topK i żadnych kar; Anthropic przyjmuje topK, topP i sekwencje stop, ale żadnych kar; i dokładnie jeden z dziewięciu — Mistral — przyjmuje seed. Wysłanie parametru, którego dostawca nie implementuje, zwykle nie powoduje żadnego błędu: żądanie się udaje, pokrętło nic nie robi, a ty dochodzisz do wniosku, że ustawienie nie ma wpływu.

Zauważ też, czym jest taki plik: twierdzeniem o cudzym API, napisanym konkretnego dnia, którego później nic nie weryfikuje. Katalog mówiący 0 do 1 dla dostawcy, który teraz akceptuje 0 do 2, po cichu przytnie każde żądanie.

Do tej samej rodziny należą jeszcze dwie kontrolki. logprobs, tam gdzie jest oferowane, zwraca log-prawdopodobieństwa wybranego token i często kilka najlepszych alternatyw — jedyne okno na rozkład, o którym jest ten rozdział, oraz podstawę każdej heurystyki pewności zbudowanej na zamkniętym modelu. A maximum tokens plus sekwencje stop kończą generowanie bez żadnego odniesienia do prawdopodobieństwa: twardy limit i dopasowanie stringa. Oba wychodzą na powierzchnię jako finish_reason z rozdziału 14, gdzie length oznacza, że twoja odpowiedź została ucięta w połowie zdania przez budżet, a nie zakończona przez model.

Seed i determinizm, którego nie masz

Link do sekcji: Seed i determinizm, którego nie masz

Ustaw seed, a sampling staje się powtarzalny. Ta część jest prawdziwa i łatwo ją zweryfikować:

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

Bajtowo identyczne w ramach seed, różne między seeds, dokładnie jak obiecano. Więc seed naprawia losowanie w ostatniej linii funkcji sample — czyli który token zostanie wybrany przy danym rozkładzie.

Nie naprawia rozkładu. I tu zaczyna się problem, bo wektor logits produkowany przez twój model nie jest obiektem matematycznym; jest wynikiem miliardów dodawań zmiennoprzecinkowych, a one mają kolejność.

Rozdział 2 zostawił ten eksperyment gotowy. Ten sam milion liczb float32, sumowany w różnych grupowaniach:

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

Spójrz na ostatnią linię. Liczba chunks zmienia odpowiedź. To nie ciekawostka o numpy; to mechanizm, bo gdy serwer inferencji rozdziela redukcję na mniej lub więcej jednostek równoległych, robi dokładnie to. A serwer rozdziela w zależności od tego, ile żądań obsługuje.

Oto ten efekt na samym modelu. Ten sam prompt, ten sam forward pass, jedyna różnica to liczba innych żądań, które akurat znalazły się w batch:

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

Uruchomiony sam model jest idealnie deterministyczny — dwadzieścia przebiegów, identyczne co do bitu. Umieść identyczny prompt w batch z niepowiązanymi żądaniami, a 97 % jego logits się zmienia. Nic w twoim żądaniu się nie zmieniło. Przyszło cudze żądanie.

Teraz uczciwa część, bo zwykle opowiada się to tak, jakby był to koniec historii. Zmiana 2.5×1052.5 \times 10^{-5} zmienia wynik tylko wtedy, gdy dwa kandydackie tokens były od siebie w takiej odległości. W 717 krokach generowania przez dwanaście prompts najmniejsza luka między dwoma najwyższymi logits wyniosła 2.5×1032.5 \times 10^{-3} — sto razy więcej niż perturbacja — i żaden krok nie był na tyle blisko, by się odwrócić. Zatem na tym modelu, w float32, na laptopie, batching poruszył każdy logit i nie zmienił żadnego token.

To opis sprzyjających warunków, nie uspokojenie, a wystarczy jedna zmiana tych warunków:

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

Sześć z ośmiu odpowiedzi się rozjeżdża, a jedna mocno się pogarsza. Tabela z rozdziału 2 mówi dlaczego: bfloat16 zachowuje 7 bitów mantysy, więc przy wielkości logit około 16 reprezentowalne wartości są oddalone o 0.125 — 16.0, potem 16.125, potem 16.25 — a zaokrąglenie może przesunąć logit nawet o 0.0625. Tymczasem 4,7 % zmierzonych wyżej kroków generowania miało lukę top-two poniżej 0.1. To cała różnica między dwoma eksperymentami: w float32 perturbacja była sto razy mniejsza niż najbliższa decyzja, a w bfloat16 jest tego samego rozmiaru. Produkcyjna inferencja działa w 16 bitach, na sprzęcie z fused kernels i kolejnościami redukcji, których nikt nie obiecuje utrzymać. To, czy „szum numeryczny jest pomijalny”, jest pytaniem o precyzję i sprzęt, nie o model.

Zatem cztery przyczyny, skatalogowane zgodnie z obietnicą z rozdziału 9:

Dodawanie zmiennoprzecinkowe nie jest łączne

Link do sekcji: Dodawanie zmiennoprzecinkowe nie jest łączne

Ramka z rozdziału 2. Kolejność sumy zmienia jej wartość, więc każda zmiana sposobu podziału redukcji zmienia logits. To podłoże; pozostałe trzy to sposoby zmiany kolejności.

Dynamic batching grupuje twoje żądanie z cudzymi

Link do sekcji: Dynamic batching grupuje twoje żądanie z cudzymi

Continuous batching z rozdziału 13 jest powodem, dla którego inferencja jest tania — i oznacza, że kształt macierzy, przez które przepływają twoje tokens, zależy od ruchu. Pomiar powyżej: 147 321 logits ruszyło się, bo zmienił się rozmiar batch.

Routing mixture-of-experts zależy od batch

Link do sekcji: Routing mixture-of-experts zależy od batch

Ramka z rozdziału 9 już to mówiła. Router dokonuje dyskretnego wyboru per token per layer, z ograniczeniami pojemności per expert liczonymi dla całego batch. Token, który sam trafiłby do expert 7, w towarzystwie trafia do expert 12. To nie jest różnica zaokrąglenia; to inny zestaw wag.

String wersji taki jak -latest jest wskaźnikiem, a wskaźniki są przepinane. Dostawcy aktualizują też stack serwowania pod stałym identyfikatorem wersji. Żadne z tego nie jest ogłaszane z taką granularnością, która pozwoliłaby skorelować to ze zmianą twojego wyniku.

Parametr seed OpenAI jest co do tego uczciwy w jedyny możliwy sposób: przychodzi razem z polem system_fingerprint identyfikującym konfigurację backendu, a dokumentacja stwierdza, że determinizm jest best-effort i że zmieniony fingerprint oznacza, iż wyniki mogą się różnić. Czytaj to jako to, czym jest — dostawca mówi ci, że kontroluje wszystkie cztery powyższe przyczyny, że ty nie kontrolujesz żadnej, i że jedyne, co może zaoferować, to powiedzieć ci po fakcie, że coś się ruszyło.

Wszystko tutaj dotyczyło pokrętła i jego konsekwencji. Cofnij się o poziom, a pojawi się trudniejszy problem: obiekt, który stroiliśmy, jest rozkładem prawdopodobieństwa, a rozkłady prawdopodobieństwa nie mają interfejsu.

Function call go ma. Wiersz bazy danych go ma. Handler POST oczekujący ciała JSON z trzema wymaganymi polami go ma i odrzuci wszystko inne. Między modelem a każdym innym komponentem twojego systemu siedzi kontrakt, o którym jedna strona nie może składać obietnic: model wyprodukuje coś, wylosowane z rozkładu, który ukształtowałeś, ale którego nie ustaliłeś, a kod po drugiej stronie potrzebuje wartości znanego typu albo rzuca wyjątek.

Most między tymi dwoma światami buduje się z materiału tego rozdziału, a nie z parsowania i ponawiania prób. Jeśli token złamałby wymaganą strukturę, nie samplujesz go z nadzieją — ustawiasz jego logit na -\infty, zanim softmax w ogóle go zobaczy. Constrained decoding jest maską na tym samym wektorze, który przez cały rozdział przekształcaliśmy, i zamienia „odpowiedz proszę w JSON” z prośby w gwarancję.

Rozdział 18 jest tym kontraktem: tool calling, JSON Schema, structured outputs i to, czego potrzeba, by deterministyczny system dało się bezpiecznie zbudować na probabilistycznym.


Wszystkie pomiary w tym rozdziale pochodzą z Qwen/Qwen2.5-0.5B-Instruct na CPU, float32 jeśli nie zaznaczono inaczej, z sampling zaimplementowanym tak, jak w sekcji opcjonalnej, a nie delegowanym do biblioteki. To mały model i konkretne wartości są jego; mechanizmy nie. Artykuł von Platena How to generate text with different decoding methods (Hugging Face, 2020) jest tekstem, względem którego mierzony jest ten rozdział, i nadal najlepszym krótkim wprowadzeniem do tego samego materiału. Dla sekcji o determinizmie: notatki PyTorch o reprodukowalności opisują, co seed naprawia, a czego nie naprawia na jednej maszynie; dokumentacja OpenAI dla seed i system_fingerprint opisuje, co dostawca może, a czego nie może obiecać; a dyskusja Thinking Machines z 2025 roku o batch-invariant kernels jest najklarowniejszym publicznym opisem tego, dlaczego naprawienie tego na poziomie serwera inferencji jest możliwe, ale nie darmowe.

  1. Ackley, D. H., Hinton, G. E. and Sejnowski, T. J. A Learning Algorithm for Boltzmann Machines. Cognitive Science 9(1), pp. 147–169 (1985), gdzie temperature w softmax pochodzi z fizyki statystycznej. Hinton, G., Vinyals, O. and Dean, J., Distilling the Knowledge in a Neural Network, arXiv:1503.02531 (2015), sekcja 2, to miejsce, w którym ten sam parametr wraca we współczesnym deep learning — jako sposób ujawnienia pełnego rozkładu nauczyciela, czyli soft labels z rozdziału 13, a nie sampling z tego rozdziału.

  2. Guo, C., Pleiss, G., Sun, Y. and Weinberger, K. Q. On Calibration of Modern Neural Networks. arXiv:1706.04599 (2017). Nie myl tego z temperature w tym rozdziale. Temperature scaling dopasowuje pojedynczą wartość na zbiorze walidacyjnym tak, aby pewność modelu odpowiadała jego trafności; jest to post-hoc metoda kalibracji stosowana do wyników klasyfikatora. Temperature sampling jest runtime control nad tym, jak generator losuje tokens. Ten sam wzór, inny cel i żadnej wspólnej wartości.

  3. Holtzman, A., Buys, J., Du, L., Forbes, M. and Choi, Y. The Curious Case of Neural Text Degeneration. arXiv:1904.09751 (2019). Wprowadza nucleus sampling i pomiar pokazujący, że decoding oparte na maksymalizacji produkuje tekst, którego profil prawdopodobieństwa nie przypomina tekstu ludzkiego. 2

  4. Fan, A., Lewis, M. and Dauphin, Y. Hierarchical Neural Story Generation. arXiv:1805.04833 (2018). Praca, która spopularyzowała top-k sampling.

  5. Nguyen, M. et al. Turning Up the Heat: Min-p Sampling for Creative and Coherent LLM Outputs. arXiv:2407.01082 (2024).

  6. Meister, C., Pimentel, T., Wiher, G. and Cotterell, R. Locally Typical Sampling. arXiv:2202.00666 (2022).

  7. Keskar, N. S., McCann, B., Varshney, L. R., Xiong, C. and Socher, R. CTRL: A Conditional Transformer Language Model for Controllable Generation. arXiv:1909.05858 (2019). Sekcja 4.1 to oryginalna repetition penalty — ta, która dzieli.

Gotowy, żeby to LIA wybierała za Ciebie?

Twórz ze wszystkimi modelami AI w jednym miejscu — zacznij dziś za darmo.