Temperature، Top-p و قطعیتی که ندارید
temperature پیش از softmax، logitها را تقسیم میکند؛ همین واقعیت ایدهٔ «پیچ خلاقیت» را از بین میبرد.
در این صفحه
این همان درخواست است که پنج بار به همان مدل فرستاده شده. همان وزنها، همان prompt، همان ماشین، همان seed تصادفی. تنها چیزی که عوض میشود یک عدد است.
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 را تعریف کرد و واقعاً خرجش نکرد. جعبهٔ ممیز شناور فصل 2 با یک دستور تمام شد — این را به خاطر بسپارید وقتی فصل 17 میپرسد چرا همان prompt، مدل و seed میتواند tokenهای متفاوت تولید کند. و جعبهٔ mixture-of-experts فصل 9 وعدهٔ فهرستی از چهار علت نادترمینیستی بودن را داد. هر سه پایینتر میرسند.
یک خطی که کل فصل به آن آویزان است
لینک به بخش: یک خطی که کل فصل به آن آویزان استفصل 4 logit را بهعنوان یک امتیاز حقیقی نرمالنشده معرفی کرد، یکی برای هر کلاس. فصل 8 کاری کرد یک مدل زبانی برای هر ورودی واژگان یکی از آنها تولید کند. softmax آن بردار را به احتمالها تبدیل میکند:
temperature همینجا وارد میشود — نامش از فیزیک آماری قرض گرفته شده، جایی که همین پارامتر کنترل میکند یک توزیع بولتزمان با چه شدتی روی حالتهای کمانرژیاش متمرکز شود1 — و logitها را پیش از نماییسازی تقسیم میکند:
همین جایگذاری کل سازوکار است، و ارزش دارد با دو خط جبر ببینیم چرا نمیتوانست جای دیگری باشد. فرض کنید تلاش کنید temperature را بهجای logitها روی احتمالها اعمال کنید — آنها را در مقیاس کنید و دوباره نرمالسازی کنید. به این میرسید:
ثابت حذف میشود. مقیاسکردن احتمالها هیچ کاری نمیکند؛ توزیع بدون تغییر برمیگردد. temperature فقط به این دلیل اثر دارد که روی توان عمل میکند، جایی که تقسیم بر پیش از نماییسازی همان است که هر احتمال را به توان برسانیم — یک بازشکلدهی غیرخطی که بهجای مقیاس مشترک ورودیها، نسبتهای بین آنها را عوض میکند.
از همین جایگذاری، هر دو حد بدون هیچ کار اضافهای نتیجه میشوند. وقتی بزرگترین logit از بقیه جدا میشود و روی تنها token با بالاترین امتیاز فرو میریزد: greedy decoding. وقتی رشد میکند، هر به سمت صفر میرود، هر نمایی به سمت 1 میرود، و توزیع به سمت یکنواختی روی کل واژگان تخت میشود. دقیقاً در فرمول بر صفر تقسیم میکند، بنابراین هر پیادهسازی آن را بهصورت مورد ویژه به بیشینهٔ حسابی تبدیل میکند — از جمله ویجت پایین، که در به argmax سوئیچ میکند.
یک هشدار، چون برخورد نامها واقعاً گیجکننده است. در machine learning چیز دوم و بیربطی هم به نام temperature وجود دارد: temperature scaling، روشی برای کالیبراسیون که یک مقدار را روی مجموعهٔ اعتبارسنجی fit میکند تا اطمینان یک classifier با دقتش جور شود.2 همان فرمول، بیربط به generation. مقالههایی که میگویند «temperature» اغلب منظورشان همان یکی است؛ این فصل هرگز منظورش آن نیست.
این همان توزیع است، با محاسبات جلوی چشم شما. logitها ثابت و باورپذیرند، پس عددهای متن پایین را میتوانید با چیزی که میبینید بسنجید:
temperature پیچ خلاقیت نیست
لینک به بخش: temperature پیچ خلاقیت نیستعدد ␣banana کل استدلال را در اندازهٔ کوچک نشان میدهد: بالا بردن temperature نمیتواند به مدل ایدهای بدهد که نداشته است. logitها از قبل محاسبه شدهاند، رتبهبندی از قبل ثابت شده، و temperature آن را دقیقاً حفظ میکند — هیچ مقدار گرما هرگز token با امتیاز پایینتر را بالاتر از token با امتیاز بالاتر نمیبرد. تنها کاری که میکند بازتوزیع جرم به سمت پایین رتبهبندیای است که خود مدل تولید کرده. temperature بالا مدل را مبتکرتر نمیکند؛ فقط احتمال اینکه tokenهایی را emit کند که خودش بد امتیاز داده بیشتر میکند.
روی یک واژگان واقعی، این دیگر کنجکاوی نیست و به دلیل غیرقابلاستفاده بودن خروجی با temperature بالا تبدیل میشود. اندازهگیریشده روی Qwen/Qwen2.5-0.5B-Instruct، یک forward pass، prompt بالا، با شمارش اینکه چند token لازم است تا سهم مشخصی از جرم احتمال جمع شود:
| temperature | احتمال top-1 | entropy | tokenهای نگهدارندهٔ 80 % | 90 % | 95 % | 99 % |
|---|---|---|---|---|---|---|
| 0.5 | 99.98 % | 0.00 nats | 1 | 1 | 1 | 1 |
| 0.7 | 99.65 % | 0.03 nats | 1 | 1 | 1 | 1 |
| 1.0 | 96.01 % | 0.30 nats | 1 | 1 | 1 | 14 |
| 1.2 | 88.20 % | 0.88 nats | 1 | 2 | 13 | 252 |
| 1.5 | 62.83 % | 3.07 nats | 29 | 353 | 2,672 | 26,787 |
| 2.0 | 16.62 % | 8.19 nats | 13,516 | 32,966 | 55,231 | 101,205 |
ردیف آخر را آهسته بخوانید. در ، روی پرسشی با دقیقاً یک پاسخ درست، 32,966 token مختلف 90 % بالایی جرم احتمال را شریک میشوند. این فضای خلاقانهٔ گستردهتر نیست. این مدلی است که حساب به آن گفته یک پسوند کرهای و یک شناسهٔ C++ را گزینههای زنده برای کلمهٔ بعد از A: بداند. آشغال موجود در بلوک آغازین پیامد مستقیم همین است، و bug مدل یا library نیست — همان چیزی است که درخواست خواسته بود.
بازهٔ مفید باریک است و به task بستگی دارد نه سلیقه. در یک پرسش factual پاسخ یک token است و هر گرمایی بالاتر از حدود 1.2 بیدلیل خطا تزریق میکند. در یک پرسش open-ended واقعاً بیش از یک ادامهٔ خوب وجود دارد، و کمی گرما تنوعی میخرد که هنوز روان میماند:
"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 مدل یک نام خاص و جملهای ساخته که parse نمیشود. نوار بین «هر بار یکسان» و «نامنسجم» برای این مدل روی این task تقریباً 0.6 تا 1.1 است، و توصیهٔ صادقانه این است که آن را با اندازهگیری روی task خودتان پیدا کنید، نه با کپی کردن عددی از یک پست وبلاگ.
چرا محتملترین متن، متن بدی است
لینک به بخش: چرا محتملترین متن، متن بدی استیک پرسش بدیهی زیر همهٔ اینها پنهان است: اگر مدل یک توزیع احتمال دارد و یک token محتملتر است، چرا همیشه همان را نگیریم؟ greedy decoding رایگان، بازتولیدپذیر و بینیاز از پارامتر است.
چون نتیجه این است:
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 %هشت جمله، یک جمله. تقریباً نه از هر ده پنجرهٔ چهارتوکنی قبلاً در همان خروجی ظاهر شده بود. این neural text degeneration است، که Holtzman و همکاران در مقالهای که top-p را معرفی کرد نامگذاری و توضیحش دادند.3 مدل خراب نیست؛ بیشینهسازی احتمال دنباله صرفاً هدف غلطی برای متن open-ended است. نوشتار انسانی محتملترین دنبالهٔ کلمات نیست — غافلگیری دارد، احتمال per-token آن پرسه میزند، افت میکند و برمیگردد — درحالیکه مسیر با بیشینهٔ احتمال یک نقطهٔ ثابت است که وقتی واردش شد، دلیلی برای خروج ندارد.
اصلاً sampling به همین دلیل وجود دارد. و همچنین، بخشی که معمولاً حذف میشود، یک قانون جهانشمول نیست. فصل 12 روی مسائل واژهای دو مرحلهای با greedy decoding ساده 24 از 24 پاسخ درست اندازه گرفت، و sampling در temperature برابر 0.8 آن را به 81 % پایین آورد؛ سپس self-consistency شش برابر token خرج کرد تا دوباره به جایی برسد که greedy از اول آنجا بود. هر دو واقعیت همزمان درستاند:
open-ended generation. ادامهٔ واحدِ درست وجود ندارد، پس محتملترین ادامه یک دام است — loop میشود، و 87.6 % آن از خودش کپی شده. sample کنید.
taskهایی با یک پاسخ درست. یک ادامهٔ درستِ واحد وجود دارد، پس کشیدن هر چیز دیگر یعنی کشیدن خطا. 100 % فصل 12 دقیقاً به همین دلیل 81 % شد. sample نکنید.
بیشتر promptهای production از نوع دوماند و مثل نوع اول پیکربندی میشوند، چون temperature همان مقداری رها شده که کد نمونه استفاده کرده بود.
دو راه برای بریدن، و فقط یکی از آنها سازگار میشود
لینک به بخش: دو راه برای بریدن، و فقط یکی از آنها سازگار میشودsampling از توزیع کامل کاری نیست که کسی واقعاً انجام دهد، چون دُم عظیم و پر از بیمعنایی است. باید چیزی بریده شود. دو پاسخ کلاسیک وجود دارد و آنها در یک جهت تفاوت دارند که همهچیز را تعیین میکند.
Top-k تعداد ثابتی از نامزدها را نگه میدارد. بر اساس احتمال مرتب کنید، اول را نگه دارید، بقیه را دور بریزید، دوباره نرمالسازی کنید.4 Top-p، که nucleus sampling هم نامیده میشود، مقدار ثابتی از جرم را نگه میدارد: tokenها را بهترتیب نزولی بردارید تا احتمال تجمعیشان به برسد، و توقف کنید.3 بهصورت رسمی، nucleus کوچکترین مجموعهٔ است با
تفاوت ظاهراً جزئی است اما نیست، چون دو promptی که در یک دقیقه میفرستید شکلهای کاملاً متفاوتی از توزیع دارند. هر دو، همان مدل در temperature برابر 1 هستند:
Q: What is the capital of France?\nA: | Once upon a time, | |
|---|---|---|
| احتمال top-1 | 96.01 % | 25.39 % |
| tokenهای نگهدارندهٔ 90 % جرم | 1 | 467 |
| top-k = 40 نگه میدارد | 99.61 % جرم | 78.87 % جرم |
| جرم در رتبههای 2 تا 40 | 3.61 % | 53.48 % |
| token در رتبهٔ 40 | ␣Av، 0.0093 % | ␣Dr، 0.128 % |
یک ثابت، دو شکست در جهت مخالف. روی prompt factual، 39 token را راه میدهد که مجموعاً 3.6 % میارزند — آشغال را عبور میدهد، از جمله نامزدی در حد نه هزارم درصد، چون قانون slotها را میشمارد نه شواهد را. روی prompt داستانی، همان ، 21 % جرمی را که مدل واقعاً اختصاص داده دور میاندازد، چون nucleus واقعی آنجا 467 token پهنا دارد.
Top-p با دقیقاً یک عدد هر دو کار را انجام میدهد. را تنظیم کنید و روی prompt اول 1 token و روی prompt دوم 467 token نگه میدارد، چون دربارهٔ توزیع سؤال میپرسد نه اینکه شمارشی را به آن تحمیل کند. این سازگاری را مستقیم ببینید — همان برش، چهار temperature:
آن ویجت همچنین یک برداشت غلط را روشن میکند که نامبردنش میارزد، چون برای افراد پول واقعی هزینه دارد. روی یک توزیع مطمئن، top_p = 0.9 «کمی تنوع» نیست. greedy است. در temperature برابر 1، token پیشتاز اینجا 96.90 % دارد، که از 0.9 بیشتر است، پس nucleus پهنای یک token دارد و هیچ چیز دیگری هرگز نمیتواند کشیده شود. تیمها top_p را روی 0.9 میگذارند با این باور که چیزی را آزادتر کردهاند و بعد تعجب میکنند چرا هر پاسخ یکسان است.
بهجایش top-k را تنظیم کنید و شکست مخالف به همان اندازه پیداست:
جریمهها، با فرمولها، چون اشتباه گرفتنشان همهگیر است
لینک به بخش: جریمهها، با فرمولها، چون اشتباه گرفتنشان همهگیر استسه سازوکار متفاوت با نامهای مشابه جابهجا میشوند، کارهای متفاوتی میکنند، و تفاوتشان قابلاندازهگیری است. بگذارید تعداد دفعاتی باشد که token قبلاً ظاهر شده است.
Presence penalty
لینک به بخش: Presence penaltyاز هر tokenی که اصلاً ظاهر شده یک ثابت کم کنید. یک بار ظاهر شدن و چهل بار ظاهر شدن دقیقاً یکسان جریمه میشوند. این یک سوئیچ است، نه پیچ تنظیم.
Frequency penalty
لینک به بخش: Frequency penaltyبه تناسب تعداد کم کنید. tokenی که چهار بار استفاده شده چهار برابر سختتر از tokenی که یک بار استفاده شده جریمه میشود، و فشار با رشد متن مرکب میشود.
Repetition penalty (CTRL)
لینک به بخش: Repetition penalty (CTRL)نسخهٔ اصلی، از مقالهٔ CTRL.7 بهجای تفریق، تقسیم میکند، با حالت علامت که لازم است چون تقسیم کردن یک logit منفی آن را بزرگتر میکند. بنابراین قدرتش به بزرگی logit بستگی دارد، یعنی همان در نقاط مختلف همان جمله اثر متفاوتی میگذارد.
همان ادامهٔ degenerate قبلی، با اعمال هرکدام. «گامهای تغییرکرده» میشمارد چند تا از 120 گام generation، token متفاوتی نسبت به مدلی که جریمه نشده بود انتخاب کرد. این run اینجا 120 گام است در برابر 140 گام در بلوک بالا، برای همین baseline بدون جریمه 85.5 % خوانده میشود نه 87.6 %:
| تنظیم | 4-gramهای تکراری | گامهای تغییرکرده |
|---|---|---|
| هیچچیز | 85.5 % | 0 / 120 |
| presence 0.5 | 65.0 % | 3 / 120 |
| presence 1.0 | 3.4 % | 11 / 120 |
| frequency 0.5 | 6.0 % | 12 / 120 |
| frequency 1.0 | 0.0 % | 20 / 120 |
| repetition 1.2 (CTRL) | 0.0 % | 35 / 120 |
سه چیز بیرون میافتد. Presence در 0.5 سه تصمیم از 120 را عوض کرد و تکرار را یکچهارم کاهش داد — loop با مشتی token کنار هم نگه داشته شده بود. Frequency در 0.5 چهار برابر تصمیمهای بیشتری را برای اثری بسیار بزرگتر عوض کرد، چون ضریب شمارش همچنان رشد میکند ولی ثابت presence نه. و جریمهٔ CTRL با مقدار بسیار کپیشدهٔ 1.2، 35 تصمیم از 120 را بازنویسی کرد، که تلنگر نیست؛ یک مدل دیگر است.
آن عدد آخر مقدمهٔ شکستی است که کسی دربارهاش هشدار نمیدهد.
جریمهها با متنی که قرار است تکرار شود چه میکنند
لینک به بخش: جریمهها با متنی که قرار است تکرار شود چه میکنندکد تکرار میشود. جدولها تکرار میشوند. فهرستها تکرار میشوند. خروجی ساختاریافته طبق تعریف تکرار میشود — ساختار یعنی همین. جریمه نمیتواند تفاوت بین مدلی گیرکرده در loop و مدلی که ردیف چهارم یک جدول را درست emit میکند تشخیص دهد، چون هر دو مثل ظاهر شدن دوبارهٔ یک token دیده میشوند.
همان سه task، به سه روش generate شده:
| task | هیچچیز | frequency 0.5 | repetition 1.2 |
|---|---|---|---|
| جدول markdown، 6 ردیف | 0 / 56 گام تغییرکرده | 0 / 56 | 2 / 62 |
| تابع Python | 0 / 93 | 0 / 93 | 10 / 110 |
| فهرست bullet، 1 تا 12 | 0 / 50 | 0 / 50 | 0 / 50 |
frequency penalty در 0.5 روی هر سه بیضرر از آب درآمد، که نتیجهای مفید و کمی غافلگیرکننده است، و چیز دقیقی میگوید: چون هیچ تصمیمی عوض نشد، tokenهای ساختاری باید جایگاههایشان را با فاصلهای بیش از مقدار کمشدهٔ جریمه برده باشند، حتی بعد از پنج و شش بار ظاهر شدن. جریمهٔ CTRL، که بهجای تفریق تقسیم میکند، آنها را جابهجا میکند، و این چیزی است که تولید کرد:
repetition 1.2, markdown table:
| n | 2^n |
| --- | --- |
| 0 | 1 |
| 1 | 2 |
| 2 | 4 |تراز از هم میپاشد: مقدار padding داخل هر سلول از ردیفی به ردیف دیگر عوض میشود، چون رشتهٔ فاصلهها پیش از pipe پایانی دقیقاً از همان نوع تکراری است که جریمه برای شکستن آن وجود دارد. ظاهری است، و شش token اضافه هزینه داشت. مورد Python ظاهری نیست:
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,جریمه مدل را از total — که قبلاً در docstring استفاده شده بود — به total_sum هل داد، خروجی را با commentهای ساختگی پُر کرد تا بودجهاش را روی tokenهای استفادهنشده خرج کند، و بعد وارد یک range سهآرگومانی با stride شد. comment میگوید هر بار 2 تا اضافه میکند، که برای جمع مربعها از 1 تا غلط است. یک repetition penalty از promptی که بدون آن درست پاسخ داده شده بود کد نادرست تولید کرد.
قاعدهٔ نتیجه کوتاه است: جریمهها برای نثر open-ended هستند، و برای کد، خروجی ساختاریافته، دادهٔ جدولی و هر چیزی با schema باید خاموش باشند. فصل 18 دقیقاً دربارهٔ همان دستهٔ دوم است.
ترتیب اعمال، و اینکه چرا پاسخ را عوض میکند
لینک به بخش: ترتیب اعمال، و اینکه چرا پاسخ را عوض میکندهر پیادهسازی واقعی اینها را با یک ترتیب مشخص اعمال میکند:
penalties → temperature → top-k → top-p → sample
این bookkeeping دلبخواهی نیست، و جابهجا کردن دو مرحله توزیعهایی واقعاً متفاوت تولید میکند. دو اندازهگیری، هر دو روی prompt factual.
برش قبل یا بعد از temperature. nucleus روی هر توزیعی که به آن داده شود محاسبه میشود، و temperature آن توزیع را بهشدت عوض میکند:
| top-p 0.9 بعد از temperature | top-p 0.9 قبل از temperature | |
|---|---|---|
| 1 token | 1 token | |
| 353 token | 1 token | |
| 32,966 token | 1 token |
در همان تنظیم اسمی، صرفاً بسته به اینکه کدام مرحله اول اجرا شود، یک مجموعهٔ نامزد 32,966تایی یا 1تایی میدهد. اگر تا حالا تعجب کردهاید چرا بالا بردن temperature روی یک provider «هیچ کاری نمیکند» و روی provider دیگر با همان دو عدد خروجی را نابود میکند، این جدول پاسخ محتملی است.
جریمه کردن قبل یا بعد از temperature. کم کردن جریمهٔ و بعد تقسیم بر ، جریمهٔ مؤثر میدهد؛ اول تقسیم کردن و بعد کم کردن، میدهد. با presence penalty برابر 1.0 که روی token پیشتاز اعمال شده:
| temperature | اول جریمه، بعد temper | اول temper، بعد جریمه |
|---|---|---|
| 0.5 | 99.858 % | 99.948 % |
| 1.0 | 89.839 % | 89.839 % |
| 2.0 | 10.783 % | 6.830 % |
در یکساناند، همانطور که باید باشند. در با ضریب 1.58 فاصله دارند. «Presence penalty 1.0» مقدار خوشتعریفی از جریمه نیست مگر اینکه بدانید temperature کجا اعمال میشود، و هیچ API این را مستند نمیکند.
نمایش جزئیات
اختیاری: کل pipeline، با ترتیب بالا.
شانزده خط، و همهچیز این فصل در آنهاست. همان محاسبهای است که ویجت انجام میدهد، روی یک بردار logit واقعی بهجای ده عدد ثابت.
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 بیسروصدا به برشی کمی سفتتر از هر پیادهسازی دیگر تبدیل میشود.
این یکی از معدود جاهای نیمهٔ دوم دوره است که Python زبان درست است، و دلیلش ساختاری است نه سبکی: هر خط بالا لازم دارد کل بردار logitها در دست شما باشد، و روی یک HTTP API چنین برداری وجود ندارد. میتوانید temperature و top_p را به provider بفرستید؛ نمیتوانید آنها را پیادهسازی کنید، و نمیتوانید ببینید چه کردند.
API جهانشمولی برای sampling وجود ندارد
لینک به بخش: API جهانشمولی برای sampling وجود نداردهر provider زیرمجموعهٔ متفاوتی از این کنترلها را میگیرد، با بازههای متفاوت، و بقیه را بیصدا نادیده میگیرد. این شکایت انتزاعی نیست. هر برنامهای که انتخاب مدل ارائه میکند باید تفاوتها را جایی بنویسد، و فایلی که این کار را میکند نقشهٔ ناسازگاری است. این چیزی است که یکی از همین کاتالوگها برای یک پارامتر در نُه منبع متنی که پشتیبانی میکند اعلام میکند:
| بازهٔ اعلامشدهٔ temperature | منابع |
|---|---|
| 0 تا 1 | Anthropic، Google، Meta، Cerebras، PaLM |
| 0 تا 1.5 | Mistral |
| 0 تا 2 | OpenAI، 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 شخص دیگری، نوشتهشده در یک روز خاص، که بعداً هیچچیز آن را راستیآزمایی نمیکند. کاتالوگی که برای providerی که حالا 0 تا 2 میپذیرد میگوید 0 تا 1، بیصدا هر درخواست را سقفگذاری میکند.
دو کنترل دیگر هم به همین خانواده تعلق دارند. logprobs، هرجا ارائه شود، log-probabilityهای token انتخابشده و اغلب چند جایگزین برتر را برمیگرداند — تنها پنجرهای که به توزیعی دارید که این فصل دربارهاش است، و پایهٔ هر heuristic اطمینان ساختهشده روی یک مدل بسته. و maximum tokens بهعلاوهٔ stop sequences بدون هیچ ارجاعی به احتمال generation را تمام میکنند: یک سقف سخت و یک تطابق رشتهای. هر دو بهصورت همان finish_reason از فصل 14 ظاهر میشوند، جایی که length یعنی پاسخ شما بهخاطر بودجه وسط جمله بریده شده، نه اینکه مدل آن را تمام کرده باشد.
seed، و determinismی که ندارید
لینک به بخش: seed، و determinismی که نداریدseed را تنظیم کنید و sampling بازتولیدپذیر میشود. این بخش واقعی است، و تأییدش آسان است:
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، byte-identical؛ بین seedها متفاوت؛ دقیقاً همانطور که تبلیغ شده. پس چیزی که seed ثابت میکند draw تصادفی در خط آخر آن تابع sample است — اینکه با داشتن یک توزیع، کدام token انتخاب شود.
چیزی که ثابت نمیکند خود توزیع است. و دردسر همینجاست، چون بردار logitهایی که مدل شما تولید میکند یک شیء ریاضی نیست؛ خروجی میلیاردها جمع ممیز شناور است، و آن جمعها ترتیب دارند.
فصل 2 این آزمایش را آماده گذاشت. همان یک میلیون عدد float32، جمعشده در گروهبندیهای متفاوت:
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به خط آخر نگاه کنید. تعداد chunkها پاسخ را عوض میکند. این کنجکاویای دربارهٔ numpy نیست؛ خود سازوکار است، چون وقتی یک inference server یک reduction را بین تعداد بیشتر یا کمتری واحد موازی تقسیم میکند، دقیقاً همین کار را میکند. و سرور بسته به تعداد درخواستهایی که سرویس میدهد تقسیم میکند.
این اثر روی خود مدل است. همان prompt، همان forward pass، تنها تفاوت اینکه چند درخواست دیگر اتفاقاً در batch بودهاند:
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تنها اجرا شود، مدل کاملاً deterministic است — بیست pass، bit-identical. همان prompt را در batchی با درخواستهای نامرتبط بگذارید و 97 % از logitهایش تغییر میکنند. هیچچیز دربارهٔ درخواست شما عوض نشده. درخواست شخص دیگری رسیده است.
حالا بخش صادقانه، چون معمولاً طوری روایت میشود انگار پایان داستان است. تغییر فقط وقتی خروجی را عوض میکند که دو token نامزد در همان فاصله از هم بوده باشند. در 717 گام generation روی دوازده prompt، کوچکترین فاصله بین دو logit برتر بود — صد برابر بزرگتر از perturbation — و هیچ گامی آنقدر نزدیک نبود که flip شود. پس روی این مدل، در float32، روی laptop، batching هر logit را جابهجا کرد و هیچ tokenی را عوض نکرد.
این توصیف شرایط مساعد است، نه اطمینانبخشی، و یک تغییر در آن شرایط کافی است:
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 نگه میدارد، پس نزدیک بزرگی logit برابر 16، مقدارهای قابلنمایش 0.125 تا 0.125 فاصله دارند — 16.0، بعد 16.125، بعد 16.25 — و rounding میتواند یک logit را تا 0.0625 جابهجا کند. در همین حال 4.7 % از گامهای generation اندازهگیریشده در بالا فاصلهٔ top-two زیر 0.1 داشتند. کل تفاوت دو آزمایش همین است: در float32، perturbation صد برابر کوچکتر از نزدیکترین تصمیم بود، و در bfloat16 هماندازهٔ آن است. inference تولیدی در 16-bit اجرا میشود، روی سختافزاری با kernelهای fused و ترتیبهای reduction که هیچکس وعده نمیدهد ثابت نگه دارد. اینکه «نویز عددی ناچیز است» پرسشی دربارهٔ precision و سختافزار است، نه دربارهٔ مدل.
پس چهار علت، همانطور که فصل 9 وعده داده بود، فهرستشدهاند:
جمع ممیز شناور associative نیست
لینک به بخش: جمع ممیز شناور associative نیستجعبهٔ فصل 2. ترتیب یک جمع مقدارش را عوض میکند، پس هر تغییری در اینکه یک reduction چگونه تقسیم شود logitها را عوض میکند. این زیرلایه است؛ سه مورد دیگر راههایی برای تغییر ترتیباند.
Dynamic batching درخواست شما را با غریبهها گروهبندی میکند
لینک به بخش: Dynamic batching درخواست شما را با غریبهها گروهبندی میکندContinuous batching، از فصل 13، دلیل مقرونبهصرفه بودن inference است — و یعنی شکل ماتریسهایی که tokenهای شما از آنها عبور میکنند به ترافیک بستگی دارد. اندازهگیری بالا: 147,321 logit بهخاطر تغییر batch size جابهجا شد.
routing در Mixture-of-experts به batch بستگی دارد
لینک به بخش: routing در Mixture-of-experts به batch بستگی داردجعبهٔ فصل 9 قبلاً گفته بود. router برای هر token در هر layer یک انتخاب گسسته انجام میدهد، مشروط به محدودیتهای ظرفیت هر expert که روی batch محاسبه میشوند. tokenی که تنها بود به expert 7 میرفت، در جمع به expert 12 میرود. این تفاوت rounding نیست؛ مجموعهٔ متفاوتی از وزنهاست.
مدل پشت نام عوض میشود
لینک به بخش: مدل پشت نام عوض میشودversion stringی مثل -latest یک pointer است، و pointerها دوباره نشانهگذاری میشوند. providerها همچنین serving stack را زیر یک شناسهٔ نسخهٔ ثابت بهروزرسانی میکنند. هیچکدام با ریزدانگیای اعلام نمیشود که بتوانید آن را با تغییر خروجی خودتان correlate کنید.
پارامتر seed در OpenAI در تنها شکلی که میتواند صادق باشد دربارهٔ این صادق است: همراه با یک فیلد system_fingerprint میآید که backend configuration را شناسایی میکند، و مستندات میگوید determinism در حد best-effort است و fingerprint تغییرکرده یعنی نتایج ممکن است متفاوت شوند. این را همانطور بخوانید که هست — provider به شما میگوید هر چهار علت بالا را او کنترل میکند، شما هیچکدام را کنترل نمیکنید، و تنها چیزی که میتواند ارائه کند این است که بعد از وقوع به شما بگوید چیزی جابهجا شده.
بعد به کجا میرود
لینک به بخش: بعد به کجا میرودهمهٔ اینها دربارهٔ یک پیچ و پیامدهایش بود. یک سطح عقب بروید و مسئلهٔ سختتر ظاهر میشود: شیئی که تنظیم میکردیم یک توزیع احتمال است، و توزیعهای احتمال interface ندارند.
یک function call دارد. یک ردیف database دارد. یک handler POST که انتظار یک JSON body با سه field اجباری دارد، interface دارد، و هر چیز دیگری را رد میکند. بین مدل و هر component دیگر در سیستم شما قراردادی نشسته که یک طرفش نمیتواند وعده بدهد: مدل چیزی تولید خواهد کرد، کشیدهشده از توزیعی که شکل دادهاید اما ثابت نکردهاید، و کد آن طرف به مقداری با نوع معلوم نیاز دارد وگرنه throw میکند.
پل بین این دو جهان از مصالح همین فصل ساخته میشود، نه از parsing و retry. اگر tokenی ساختار لازم را میشکند، آن را sample نمیکنید و امید ببندید — logit آن را پیش از اینکه softmax اصلاً ببیند روی میگذارید. Constrained decoding ماسکی روی همان برداری است که این فصل صرف بازشکلدهیاش کردیم، و «لطفاً در JSON پاسخ بده» را از درخواست به تضمین تبدیل میکند.
فصل 18 همان قرارداد است: tool calling، JSON Schema، structured outputs، و آنچه لازم است تا ساختن روی یک سیستم deterministic بر فراز سیستمی probabilistic امن شود.
منابع و روش
لینک به بخش: منابع و روشهمهٔ اندازهگیریهای این فصل از Qwen/Qwen2.5-0.5B-Instruct روی CPU میآیند، float32 مگر اینکه گفته شده باشد، با samplingی که همانطور که در بخش اختیاری نوشته شد پیادهسازی شده نه delegate شده به library. آنها یک مدل کوچکاند، و مقدارهای خاص متعلق به همان مدلاند؛ سازوکارها نه. مقالهٔ Von Platen با عنوان How to generate text with different decoding methods (Hugging Face, 2020) مقالهای است که این یکی در برابرش اندازهگیری شده و هنوز بهترین مقدمهٔ کوتاه برای همین material است. برای بخش determinism: یادداشتهای reproducibility در PyTorch توضیح میدهند seed روی یک ماشین چه چیزی را fix میکند و چه چیزی را نه، مستندات OpenAI دربارهٔ seed و system_fingerprint توضیح میدهد یک provider چه چیزی را میتواند و نمیتواند وعده بدهد، و بحث Thinking Machines در 2025 دربارهٔ batch-invariant kernels روشنترین روایت عمومی از این است که fix کردن این در سطح inference-server ممکن است اما رایگان نیست.
ارجاعات
لینک به بخش: ارجاعات-
Ackley, D. H., Hinton, G. E. و Sejnowski, T. J. A Learning Algorithm for Boltzmann Machines. Cognitive Science 9(1), pp. 147–169 (1985)، جایی که temperature در softmax از فیزیک آماری میآید. Hinton, G., Vinyals, O. و Dean, J.، Distilling the Knowledge in a Neural Network، arXiv:1503.02531 (2015)، بخش 2، جایی است که همان پارامتر دوباره در deep learning مدرن ظاهر میشود — بهعنوان راهی برای آشکار کردن توزیع کامل teacher، که soft labels فصل 13 است نه sampling این فصل. ↩
-
Guo, C., Pleiss, G., Sun, Y. و Weinberger, K. Q. On Calibration of Modern Neural Networks. arXiv:1706.04599 (2017). این را با temperature این فصل اشتباه نگیرید. Temperature scaling یک مقدار واحد را روی مجموعهٔ اعتبارسنجی fit میکند تا اطمینان مدل با دقتش match شود؛ یک روش کالیبراسیون post-hoc است که روی خروجیهای یک classifier اعمال میشود. Temperature sampling یک کنترل runtime روی این است که generator چگونه tokenها را draw کند. همان فرمول، هدف متفاوت، و بدون مقدار مشترک. ↩
-
Holtzman, A., Buys, J., Du, L., Forbes, M. و Choi, Y. The Curious Case of Neural Text Degeneration. arXiv:1904.09751 (2019). nucleus sampling و اندازهگیریای را معرفی میکند که نشان میدهد decoding مبتنی بر maximisation متنی تولید میکند که profile احتمالش هیچ شباهتی به متن انسانی ندارد. ↩ ↩2
-
Fan, A., Lewis, M. و Dauphin, Y. Hierarchical Neural Story Generation. arXiv:1805.04833 (2018). مقالهای که top-k sampling را محبوب کرد. ↩
-
Nguyen, M. et al. Turning Up the Heat: Min-p Sampling for Creative and Coherent LLM Outputs. arXiv:2407.01082 (2024). ↩
-
Meister, C., Pimentel, T., Wiher, G. و Cotterell, R. Locally Typical Sampling. arXiv:2202.00666 (2022). ↩
-
Keskar, N. S., McCann, B., Varshney, L. R., Xiong, C. و Socher, R. CTRL: A Conditional Transformer Language Model for Controllable Generation. arXiv:1909.05858 (2019). بخش 4.1 همان repetition penalty اصلی است — همان که تقسیم میکند. ↩