Към съдържанието
17/30Глава 17 от 30

Temperature, Top-p и детерминизмът, който нямате

Temperature дели logits преди softmax — факт, който разбива идеята за „регулатор на креативността“.

На тази страница

Ето една и съща заявка, изпратена към един и същ модел пет пъти. Същите тегла, същият prompt, същата машина, същият случаен seed. Единственото, което се променя, е едно число.

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

Нищо не се е счупило. Всеки token в последния ред е изтеглен напълно легитимно от собственото вероятностно разпределение на модела върху речника му от 151 936 записа. Числото, което се промени, се нарича temperature, в повечето документации се описва като регулатор на креативността и това описание е погрешно по начин, който тази глава може да покаже, а не просто да твърди.

Това е и главата, в която три по-ранни обещания стават дължими. Глава 4 дефинира logit и така и не го използва истински. Кутията за floating-point от Глава 2 завърши с инструкция — помнете това, когато Глава 17 попита защо един и същ prompt, модел и seed могат да произведат различни tokens. А кутията за mixture-of-experts от Глава 9 обеща каталог от четири причини за недетерминизъм. И трите пристигат по-долу.

Единственият ред, от който зависи цялата глава

Връзка към раздела: Единственият ред, от който зависи цялата глава

Глава 4 въведе logit като ненормализирана реална оценка, по една за всеки клас. Глава 8 накара езиков модел да произведе по една такава за всеки запис в речника. softmax превръща този вектор z\mathbf{z} във вероятности:

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

Temperature влиза тук — името е заето от статистическата физика, където същият параметър контролира колко рязко едно разпределение на Болцман се концентрира върху нискоенергийните си състояния1 — и той дели logits преди експонентата:

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

Това място е целият механизъм и си струва два реда алгебра, за да се види защо не би могло да бъде другаде. Да предположим, че се опитате да приложите temperature върху вероятностите вместо това — да ги мащабирате с 1/T1/T и да нормализирате отново. Ще получите

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

Константата се съкращава. Мащабирането на вероятности не прави абсолютно нищо; разпределението се връща непроменено. Temperature има ефект само защото действа върху експонентата, където делението на TT преди експонентиране е същото като повдигане на всяка вероятност на степен 1/T1/T — нелинейно преоформяне, което променя съотношенията между записите, а не общия им мащаб.

От това място и двете граници следват без допълнителна работа. Когато T0T \to 0 най-големият logit се откъсва от останалите и pp колабира върху единствения token с най-висока оценка: greedy decoding. Когато TT расте, всеки zi/Tz_i/T върви към нула, всяка експонента върви към 1 и разпределението се изравнява към uniform върху целия речник. При точно T=0T = 0 формулата дели на нула, така че всяка имплементация го обработва като специален случай до аритметичния максимум — включително widget-ът по-долу, който превключва към argmax при T0.001T \le 0.001.

Едно предупреждение, защото сблъсъкът на имена причинява реално объркване. В machine learning има второ, несвързано нещо, наречено temperature: temperature scaling, метод за калибрация, който напасва една стойност върху validation set, така че увереността на класификатора да съответства на точността му.2 Същата формула, нищо общо с генерирането. Когато статии казват „temperature“, често имат предвид това; тази глава никога няма предвид него.

Ето това разпределение, с аритметиката пред вас. logits са фиксирани и правдоподобни, така че числата в текста по-долу могат да се проверят спрямо това, което виждате:

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

10 от 10 токена минават отсяването и си поделят вероятността.

Виж данните като таблица
ТокенlogitСлед температуратаСлед отсяването
␣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%
Семплиране: температура, top-p и top-k

Десет кандидат-продължения на The capital of France is, при temperature 1 без отрязване. ␣Paris държи 96,90 % от масата; ␣banana, най-долу с logit 2.6-2.6, получава 0,00 %. Преместете temperature на 0 и оцелява един token със 100 %. Преместете го на 2 и ␣Paris пада до 69,81 %, докато ␣banana се качва до 0,17 % — отхвърленият от модела token, получил реална вероятност от копче, което читателят е завъртял.

Числото ␣banana е целият аргумент в миниатюра: повишаването на temperature не може да даде на модела идея, която той не е имал. logits вече са изчислени, подредбата вече е фиксирана и temperature я запазва точно — никаква топлина никога не премества по-ниско оценен token над по-високо оценен. Всичко, което прави, е да преразпределя маса надолу по подредбата, която самият модел е произвел. Висок temperature не прави модела по-изобретателен; прави го по-склонен да изведе tokens, които сам е оценил като лоши.

В реален речник това престава да бъде любопитен детайл и се превръща в причината output-ът при висок temperature да е неизползваем. Измерено върху Qwen/Qwen2.5-0.5B-Instruct, един forward pass, prompt-ът по-горе, с броене колко tokens са нужни, за да се натрупа даден дял от вероятностната маса:

temperaturetop-1 probabilityentropytokens holding 80 %90 %95 %99 %
0.599.98 %0.00 nats1111
0.799.65 %0.03 nats1111
1.096.01 %0.30 nats11114
1.288.20 %0.88 nats1213252
1.562.83 %3.07 nats293532,67226,787
2.016.62 %8.19 nats13,51632,96655,231101,205

Прочетете бавно последния ред. При T=2T = 2, на въпрос с точно един верен отговор, 32 966 различни tokens споделят първите 90 % от вероятностната маса. Това не е по-широко творческо пространство. Това е модел, на който аритметиката е казала да третира корейска частица и C++ идентификатор като живи опции за думата след A:. Боклукът в началния блок е пряката последица и не е bug в модела или библиотеката — това е, което заявката е поискала.

Полезният диапазон е тесен и зависи от задачата, а не от вкуса. При фактологичен въпрос отговорът е един token и всяка топлина над около 1,2 инжектира грешка без полза. При отворена задача наистина има повече от едно добро продължение и малко топлина купува разнообразие, което остава плавно:

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

При 1,3 моделът е измислил собствено име и изречение, което не се парсва. Лентата между „идентично всеки път“ и „несвързано“ е приблизително от 0,6 до 1,1 за този модел върху тази задача, а честният съвет е да я намерите чрез измерване върху вашата задача, не чрез копиране на число от blog пост.

Под всичко това се крие очевиден въпрос: ако моделът има вероятностно разпределение и един token е най-вероятен, защо да не го взимаме винаги? Greedy decoding е безплатен, възпроизводим и не се нуждае от параметри.

Защото резултатът е това:

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 %

Осем изречения, едно изречение. Почти девет от десет четири-token прозореца вече са се появили по-рано в същия output. Това е neural text degeneration, назовано и обяснено от Holtzman et al. в статията, която въведе top-p.3 Моделът не е счупен; максимизирането на вероятността на последователността просто е грешна цел за отворен текст. Човешкото писане не е най-вероятната последователност от думи — то носи изненада, вероятността му на token се лута, спада и се възстановява — докато пътят с максимална вероятност е фиксирана точка, която, веднъж влязла, няма причина да излезе.

Затова sampling изобщо съществува. Това също така — и това е частта, която се пропуска — не е универсален закон. Глава 12 измери 24 от 24 верни отговора на двустъпкови текстови задачи с обикновен greedy decoding, а sampling при temperature 0,8 свали това до 81 %; после self-consistency похарчи шест пъти повече tokens, за да се изкачи обратно до мястото, където greedy вече беше. И двата факта са верни едновременно:

Отворено генериране. Няма едно-единствено правилно продължение, така че най-вероятното е капан — то зацикля, а 87,6 % от него е копирано от самото себе си. Sample.

Задачи с един правилен отговор. Има едно-единствено правилно продължение, така че да изтеглите каквото и да е друго означава да изтеглите грешка. 100 % от Глава 12 станаха 81 % точно по тази причина. Не sample-вайте.

Повечето production prompts са от втория вид и се конфигурират като първия, защото temperature е оставен на каквото е използвал примерният код.

Два начина за отрязване и само един от тях се адаптира

Връзка към раздела: Два начина за отрязване и само един от тях се адаптира

Sampling от пълното разпределение не е това, което някой реално прави, защото опашката е огромна и пълна с безсмислици. Нещо трябва да се отреже. Има два класически отговора и те се различават по един аспект, който решава всичко.

Top-k запазва фиксиран брой кандидати. Сортирате по вероятност, запазвате първите kk, изхвърляте останалите, нормализирате отново.4 Top-p, наричан още nucleus sampling, запазва фиксирано количество маса: взимате tokens в низходящ ред, докато кумулативната им вероятност достигне pp, и спирате.3 Формално nucleus е най-малкото множество VpV_p с

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

Разликата звучи козметична, но не е, защото двата prompts, които изпращате в една и съща минута, имат напълно различни форми на разпределение. И двете са един и същ модел при temperature 1:

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

Едно фиксирано kk, два провала в противоположни посоки. При фактологичния prompt k=40k = 40 допуска 39 tokens, които заедно струват 3,6 % — пропуска боклук, включително кандидат с девет хилядни от процента, защото правилото брои слотове, а не доказателство. При story prompt същото k=40k = 40 изхвърля 21 % от масата, която моделът наистина е присвоил, защото реалният nucleus там е широк 467 tokens.

Top-p кара точно едно число да върши и двете задачи. Задайте p=0.9p = 0.9 и той запазва 1 token при първия prompt и 467 при втория, защото задава въпрос за разпределението, вместо да му налага брой. Вижте тази адаптация директно — същият cut, четири temperatures:

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

3 от 10 токена минават отсяването и си поделят вероятността.

Виж данните като таблица
ТокенlogitСлед температуратаСлед отсяването
␣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%
Семплиране: температура, top-p и top-k

Top-p при 0,90 с temperature 1,5: три от десетте tokens оцеляват и споделят масата, ␣Paris нормализирано отново до 91,10 %. Сега преместете само temperature. При 0,7 същото 0,90 оставя един оцелял — толкова тесен nucleus е greedy decoding с друго име. При 2,0 оставя пет. Cut-ът никога не се премести; формата под него се промени.

Този widget също изяснява едно погрешно схващане, което струва реални пари. При уверено разпределение top_p = 0.9 не е „малко разнообразие“. То е greedy. При temperature 1 водещият token тук държи 96,90 %, което вече е над 0,9, така че nucleus е широк един token и нищо друго никога не може да бъде изтеглено. Екипи задават top_p на 0,9, вярвайки, че са отпуснали нещо, и после се чудят защо всеки отговор е идентичен.

Задайте top-k вместо това и противоположният провал е също толкова видим:

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

5 от 10 токена минават отсяването и си поделят вероятността.

Виж данните като таблица
ТокенlogitСлед температуратаСлед отсяването
␣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%
Семплиране: температура, top-p и top-k

Top-k при 5, без top-p. Пет tokens оцеляват при всеки temperature, защото пет е поисканото. При temperature 1, както е показано, четирите кандидата под ␣Paris струват общо 2,79 %. Свалете до 0,7 и същите четири струват 0,38 % — cut-ът е театър, а моделът на практика е greedy. Вдигнете до 2,0 и струват 22,54 %. Идентична настройка, идентичен брой оцелели, три напълно различни поведения и нищо в заявката не ви казва кое получавате.

Санкциите, с формулите, защото объркването им е повсеместно

Връзка към раздела: Санкциите, с формулите, защото объркването им е повсеместно

Три различни механизма се движат под сходни имена, правят различни неща и разликата е измерима. Нека cic_i е броят пъти, в които token ii вече се е появил.

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

Изважда константа от всеки token, който изобщо се е появил. Да се появи веднъж и да се появи четиридесет пъти се наказва еднакво. Това е превключвател, не регулатор.

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

Изважда пропорционално на броя. Token, използван четири пъти, се наказва четири пъти по-силно от token, използван веднъж, и натискът се натрупва с растежа на текста.

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}

Оригиналът, от статията CTRL.7 Той дели, вместо да изважда, със случая за знак, нужен защото деленето на отрицателен logit би го направило по-голям. Следователно силата му зависи от големината на logit, което означава, че едно и също ρ\rho удря различно в различни точки на едно и също изречение.

Същото дегенерирало продължение от по-рано, с приложен всеки един от тях. „Променени стъпки“ брои колко от 120-те стъпки на генериране са избрали различен token от този, който моделът без penalty би избрал. Run-ът тук е 120 стъпки срещу 140 в блока по-горе, затова базовата линия без penalty показва 85,5 %, а не 87,6 %:

settingrepeated 4-gramssteps altered
nothing85.5 %0 / 120
presence 0.565.0 %3 / 120
presence 1.03.4 %11 / 120
frequency 0.56.0 %12 / 120
frequency 1.00.0 %20 / 120
repetition 1.2 (CTRL)0.0 %35 / 120

Оттук следват три неща. Presence при 0,5 промени три решения от 120 и намали повторението с една четвърт — цикълът се държеше от шепа tokens. Frequency при 0,5 промени четири пъти повече решения за далеч по-голям ефект, защото множителят по брой продължава да расте, а presence константата — не. А CTRL penalty при широко копираната стойност 1,2 пренаписа 35 от 120 решения, което не е леко побутване; това е различен модел.

Това последно число подготвя провала, за който никой не предупреждава.

Какво правят penalties с текст, който трябва да се повтаря

Връзка към раздела: Какво правят penalties с текст, който трябва да се повтаря

Кодът се повтаря. Таблиците се повтарят. Списъците се повтарят. Структурираният output се повтаря по дефиниция — това е структурата. Penalty не може да различи модел, заседнал в цикъл, от модел, който правилно извежда четвъртия ред на таблица, защото и двете изглеждат като token, който се появява отново.

Същите три задачи, генерирани по три начина:

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

Frequency penalty при 0,5 се оказа безвреден и при трите, което е полезен и леко изненадващ резултат, и казва нещо точно: щом нито едно решение не се е променило, структурните tokens трябва да са печелили позициите си с повече от изваденото от penalty, дори след като са се появили пет и шест пъти. CTRL penalty, който дели, вместо това ги измества, а ето какво произведе:

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

Подравняването се разпада: количеството padding във всяка клетка се променя от ред на ред, защото поредицата от интервали преди затварящата вертикална черта е точно видът повторение, което penalty е създаден да разбие. Козметично е, и струва шест допълнителни tokens. Случаят с Python не е козметичен:

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,

Penalty избута модела от total — вече използвано в docstring — към total_sum, напълни output-а с измислени коментари, за да похарчи бюджета си за неизползвани tokens, и после влезе в триаргументен range със stride. Коментарът казва incrementing by 2 each time, което е грешно за сума от квадрати от 1 до nn. Repetition penalty произведе неправилен код от prompt, на който без него беше отговорено правилно.

Правилото, което следва, е кратко: penalties са за отворена проза и трябва да са изключени за код, структуриран output, таблични данни и всичко със schema. Глава 18 е точно за тази втора категория.

Редът на прилагане и защо променя отговора

Връзка към раздела: Редът на прилагане и защо променя отговора

Всяка реална имплементация прилага тези неща в една конкретна последователност:

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

Това не е произволно счетоводство и размяната на два етапа произвежда наистина различни разпределения. Две измервания, и двете върху фактологичния prompt.

Cut преди или след temperature. Nucleus се изчислява върху разпределението, което му е подадено, а temperature променя това разпределение радикално:

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

При T=2T = 2 същата номинална настройка дава кандидатно множество от 32 966 или от 1, само според това кой етап върви първи. Ако някога сте се чудили защо повишаването на temperature „не прави нищо“ при един provider и унищожава output-а при друг със същите две числа, тази таблица е правдоподобен отговор.

Penalizing преди или след temperature. Да извадите penalty α\alpha и после да разделите на TT дава ефективен penalty α/T\alpha/T; да разделите първо и после да извадите дава α\alpha. С presence penalty 1,0, приложен към водещия token:

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

Идентични при T=1T = 1, както трябва да бъдат. Различни с фактор 1,58 при T=2T = 2. „Presence penalty 1,0“ не е добре дефинирано количество penalty, освен ако не знаете и къде се прилага temperature, а никое API не документира това.

Покажи подробности

По избор: целият pipeline, в горния ред.

Шестнадесет реда и всичко в тази глава е в тях. Това е същото изчисление, което widget-ът изпълнява, върху реален logit вектор вместо върху десет фиксирани числа.

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 в реда за top-p е кумулативната маса без текущия token, което кара nucleus да включи token, който пресича прага, вместо да спре точно преди него. Сбъркайте това с едно и top_p = 0.9 тихо се превръща в малко по-строг cut от всяка друга имплементация.

Това е едно от малкото места във втората половина на курса, където Python е правилният език, и причината е структурна, не стилова: всеки ред по-горе се нуждае от пълния вектор logits във вашите ръце, а през HTTP API този вектор не съществува. Можете да изпратите temperature и top_p към provider; не можете да ги имплементирате и не можете да видите какво са направили.

Всеки provider приема различно подмножество от тези контроли, с различни диапазони, и игнорира останалите мълчаливо. Това не е абстрактно оплакване. Всяко приложение, което предлага избор на модел, трябва да запише разликите някъде, а файлът, в който го прави, е карта на несъвместимостта. Ето какво декларира един такъв каталог за един параметър при деветте text sources, които поддържа:

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

Думата е същата; скалата не е. „Temperature 1“ е непромененото разпределение при един и максимално позволената топлина при друг, а половината каталог не може да изрази стойността, която другата половина третира като неутрално-плюс-малко. Останалите копчета са също толкова неравни: записите за OpenAI, DeepSeek и xAI приемат presence и frequency penalties и никакъв topK; записите за Google, Meta, Cerebras и PaLM приемат topK и никакви penalties; Anthropic приема topK, topP и stop sequences и никакви penalties; и точно един от деветте — Mistral — приема seed. Изпращането на параметър, който provider не имплементира, обикновено не произвежда никаква грешка: заявката успява, копчето не прави нищо и вие заключавате, че настройката няма ефект.

И забележете какво е такъв файл: твърдение за чуждо API, написано в един конкретен ден, което после не се проверява от нищо. Каталог, който казва 0 до 1 за provider, който вече приема 0 до 2, тихо ще ограничава всяка заявка.

Още два контрола принадлежат към същото семейство. logprobs, когато се предлага, връща log-probabilities на избрания token и често първите няколко алтернативи — единственият прозорец, който получавате към разпределението, за което е тази глава, и основата на всяка confidence heuristic, изградена върху затворен модел. А maximum tokens плюс stop sequences прекратяват генерирането без никаква връзка с вероятността: твърд лимит и съвпадение на низ. И двете се появяват като finish_reason от Глава 14, където length означава, че отговорът ви е отрязан по средата на изречение от бюджет, не завършен от модела.

Задайте seed и sampling става възпроизводим. Тази част е реална и е лесна за проверка:

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

Идентични байт по байт в рамките на seed, различни между seed-ове, точно както е рекламирано. Значи seed фиксира случайното теглене в последния ред на функцията sample — кой token се избира при дадено разпределение.

Това, което не фиксира, е разпределението. И там е проблемът, защото векторът logits, който моделът ви произвежда, не е математически обект; той е output от милиарди floating-point събирания, а те имат ред.

Глава 2 остави този експеримент готов. Същият милион float32 числа, сумирани в различни групирания:

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

Вижте последния ред. Броят chunks променя отговора. Това не е любопитен детайл за numpy; това е механизмът, защото когато inference server разделя reduction върху повече или по-малко паралелни единици, той прави точно това. А server-ът разделя според това колко заявки обслужва.

Ето този ефект върху самия модел. Същият prompt, същият forward pass, единствената разлика е колко други заявки случайно са били в 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

Пуснат сам, моделът е напълно детерминистичен — двадесет pass-а, идентични до bit. Поставете идентичния prompt в batch с несвързани заявки и 97 % от неговите logits се променят. Нищо във вашата заявка не се промени. Пристигна чужда заявка.

Сега честната част, защото това обикновено се разказва сякаш е краят на историята. Промяна от 2.5×1052.5 \times 10^{-5} променя output-а само ако два кандидат-tokens са били на такова разстояние един от друг. В 717 стъпки на генериране върху дванадесет prompts най-малката разлика между първите два logits беше 2.5×1032.5 \times 10^{-3} — сто пъти по-голяма от perturbation — и нито една стъпка не беше достатъчно близо, за да се обърне. Така че при този модел, във float32, на лаптоп, batching премести всеки logit и не промени нито един token.

Това е описание на благоприятни условия, не успокоение, и една промяна в тези условия е достатъчна:

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

Шест от осем отговора се разминават, а един от тях деградира сериозно. Таблицата от Глава 2 казва защо: bfloat16 пази 7 mantissa bits, така че близо до logit magnitude 16 представимите стойности са на 0,125 една от друга — 16,0, после 16,125, после 16,25 — и rounding може да премести logit с до 0,0625. Междувременно 4,7 % от измерените по-горе стъпки на генериране имаха top-two gap под 0,1. Това е цялата разлика между двата експеримента: във float32 perturbation беше сто пъти по-малка от най-близкото решение, а в bfloat16 е със същия размер. Production inference върви в 16-bit, на хардуер с fused kernels и reduction orders, които никой не обещава да запази. Дали „численият шум е пренебрежим“ е въпрос за precision и hardware, не за модела.

И така, четирите причини, каталогизирани както обеща Глава 9:

Кутията от Глава 2. Редът на една сума променя стойността ѝ, така че всяка промяна в начина, по който reduction се разделя, променя logits. Това е субстратът; останалите три са начини да се промени редът.

Continuous batching, от Глава 13, е причината inference да е достъпен — и означава, че формата на матриците, през които минават вашите tokens, зависи от трафика. Измерено по-горе: 147 321 logits се преместиха, защото batch size се промени.

Кутията от Глава 9 вече го каза. Router-ът прави дискретен избор за token на layer, подчинен на лимити за капацитет по expert, изчислени върху batch-а. Token, който сам би отишъл при expert 7, отива при expert 12 в компания. Това не е rounding разлика; това е различен набор от тегла.

Version string като -latest е pointer, а pointers се пренасочват. Providers също обновяват serving stack под фиксиран version identifier. Нито едното не се обявява с granularност, която би ви позволила да го свържете с промяна във вашия output.

Параметърът seed на OpenAI е честен за това по единствения възможен начин: идва заедно с поле system_fingerprint, което идентифицира backend configuration, а документацията казва, че детерминизмът е best-effort и че променен fingerprint означава, че резултатите може да се различават. Прочетете това като каквото е — provider ви казва, че контролира и четирите причини по-горе, че вие не контролирате нито една от тях и че единственото, което може да предложи, е да ви каже след факта, че нещо се е преместило.

Всичко тук беше за едно копче и последствията му. Отстъпете едно ниво назад и се появява по-трудният проблем: обектът, който настройвахме, е вероятностно разпределение, а вероятностните разпределения нямат интерфейс.

Function call има такъв. Ред в база данни има такъв. Handler POST, който очаква JSON body с три задължителни полета, има такъв и ще отхвърли всичко друго. Между модела и всеки друг компонент във вашата система стои договор, за който едната страна не може да дава обещания: моделът ще произведе нещо, изтеглено от разпределение, което сте оформили, но не сте фиксирали, а кодът от другата страна се нуждае от стойност с познат тип или хвърля грешка.

Мостът между тези два свята се строи от материала на тази глава, не от parsing и retries. Ако даден token би счупил изискваната структура, не го sample-вате с надежда — задавате logit му на -\infty, преди softmax изобщо да го види. Constrained decoding е маска върху същия вектор, който цяла глава преоформяхме, и превръща „моля, отговори в JSON“ от заявка в гаранция.

Глава 18 е този договор: tool calling, JSON Schema, structured outputs и какво е нужно, за да направите детерминистична система безопасна за изграждане върху вероятностна.


Всички измервания в тази глава идват от Qwen/Qwen2.5-0.5B-Instruct на CPU, float32 освен ако не е посочено друго, със sampling, имплементиран както е написан в секцията по избор, вместо делегиран на библиотека. Това е малък модел и конкретните стойности са негови; механизмите не са. How to generate text with different decoding methods на Von Platen (Hugging Face, 2020) е статията, спрямо която тази е измерена, и все още е най-доброто кратко въведение в същия материал. За секцията за детерминизъм: бележките на PyTorch за reproducibility описват какво seed фиксира и не фиксира на една машина, документацията на OpenAI за seed и system_fingerprint описва какво provider може и не може да обещае, а дискусията на Thinking Machines от 2025 г. за batch-invariant kernels е най-ясният публичен разказ защо поправянето на това на ниво inference server е възможно, но не е безплатно.

  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), където temperature в softmax идва от статистическата физика. Hinton, G., Vinyals, O. and Dean, J., Distilling the Knowledge in a Neural Network, arXiv:1503.02531 (2015), раздел 2, е мястото, където същият параметър се появява отново в съвременния deep learning — като начин да се покаже пълното разпределение на teacher-а, което е soft labels от Глава 13, а не sampling от тази глава.

  2. Guo, C., Pleiss, G., Sun, Y. and Weinberger, K. Q. On Calibration of Modern Neural Networks. arXiv:1706.04599 (2017). Не бъркайте това с temperature в тази глава. Temperature scaling напасва една стойност върху validation set, така че увереността на модела да съответства на точността му; това е post-hoc calibration method, приложен към output-а на класификатор. Temperature sampling е runtime control върху начина, по който generator тегли tokens. Същата формула, различна цел и никаква споделена стойност.

  3. Holtzman, A., Buys, J., Du, L., Forbes, M. and Choi, Y. The Curious Case of Neural Text Degeneration. arXiv:1904.09751 (2019). Въвежда nucleus sampling и измерването, че decoding, базиран на максимизация, произвежда текст, чийто вероятностен профил изобщо не прилича на човешки текст. 2

  4. Fan, A., Lewis, M. and Dauphin, Y. Hierarchical Neural Story Generation. arXiv:1805.04833 (2018). Статията, която популяризира 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). Раздел 4.1 е оригиналният repetition penalty — този, който дели.


Създадено от

David Vicente Campos

Основател на NeuraLIA Labs и съосновател на MyRealFood

Компютърен инженер съм, завършил Университета в Леон. Съосновах MyRealFood, където като CTO създадох приложението, което милиони хора са използвали, за да се хранят по-здравословно, и основах NeuraLIA Labs, където изграждам AI продукти. Тук пиша за това, което трябваше да разбера по пътя, така, както ми се иска някой да ми го беше обяснил.

Още за автора

Публикувано от NeuraLIA Labs.

Получавайте нови публикации във входящата си поща

Новини за AI, ръководства и продуктови обновления — кратък имейл, когато публикуваме нещо, което си заслужава.

Индекс на курса

Abstract software decision engine with branching paths, probability nodes, and glowing gates.
jev12 мин четене

AI моделът Jev е създаден за решения, не за проза

Jev на TypeSafe AI привлича внимание, защото разглежда софтуерната интелигентност като проблем на вероятностите: изберете правилния клон, добавете увереност и не плащайте на LLM да пише текст, когато кодът има нужда от решение.

Abstract legal research workspace with documents, search nodes and governance controls.
openai11 мин четене

Astra for Law на OpenAI е правна AI система, не нов модел

Правният старт на OpenAI е не толкова за нов базов модел, колкото за системата около него: домейн извличане, надеждни инструменти, права, бенчмаркове и пътища за преглед.

Abstract agent runtime sorting documents, memory blocks and pointer nodes inside a bounded context frame.
context-engineering12 мин четене

Инженеринг на контекста за AI агенти с дълъг хоризонт

Дълго работещите агенти не се провалят само защото прозорецът е малък. Те се провалят, когато файлове, изходи от инструменти и остаряла история изтласкат задачата, която агентът е трябвало да завърши.

Готови ли сте LIA да избира вместо вас?

Създавайте с всички AI модели на едно място — започнете безплатно още днес.