تخطَّ إلى المحتوى
17/30الفصل 17 من 30

Temperature وTop-p والحتمية التي لا تملكها

Temperature تقسم logits قبل softmax؛ وهذه الحقيقة وحدها تكسر فكرة مقبض الإبداع. ثم النداء greedy نفسه مرتين بإجابتين.

في هذه الصفحة

إليك الطلب نفسه مُرسلًا إلى النموذج نفسه خمس مرات. الأوزان نفسها، prompt نفسه، الجهاز نفسه، random 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 ولم يستخدمه حقًا. وانتهى صندوق الأعداد العائمة في الفصل 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، ويتسطح التوزيع نحو توزيع منتظم على المفردات كلها. وعند T=0T = 0 تحديدًا تقسم الصيغة على صفر، لذلك تعالج كل implementation هذه الحالة خصوصيًا بالحد الأقصى الحسابي — بما في ذلك الأداة أدناه، التي تنتقل إلى argmax عند T0.001T \le 0.001.

تحذير واحد، لأن تصادم الأسماء يسبب ارتباكًا حقيقيًا. هناك شيء ثانٍ، غير مرتبط، يسمى temperature في machine learning: temperature scaling، وهي طريقة معايرة تلائم قيمة واحدة على مجموعة تحقق كي تطابق ثقة المصنف دقته.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 أدنى درجة فوق token أعلى درجة. كل ما تفعله هو إعادة توزيع الكتلة نزولًا على الترتيب الذي أنتجه النموذج نفسه. temperature المرتفعة لا تجعل النموذج أكثر ابتكارًا؛ بل تجعله أكثر احتمالًا لأن يخرج tokens هو نفسه قيّمها على أنها سيئة.

على مفردات حقيقية، لا يعود هذا فضولًا بل يصبح سبب عدم صلاحية المخرجات ذات temperature العالية. بالقياس على Qwen/Qwen2.5-0.5B-Instruct، مرور أمامي واحد، prompt أعلاه، مع عدّ عدد tokens اللازمة لتجميع حصة معينة من كتلة الاحتمال:

temperatureاحتمال top-1entropytokens التي تحمل 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 token مختلفة أعلى 90 % من كتلة الاحتمال. هذا ليس فضاءً إبداعيًا أوسع. هذا نموذج قيل له، بالحساب، أن يتعامل مع أداة نحوية كورية ومع معرّف C++ كخيارات حيّة للكلمة بعد A:. القمامة في الكتلة الافتتاحية هي النتيجة المباشرة، وليست عيبًا في النموذج أو المكتبة — إنها ما طلبه الطلب.

النطاق المفيد ضيق ويتعلق بالمهمة لا بالذوق. في سؤال واقعي، الجواب 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 لهذا النموذج على هذه المهمة، والنصيحة الصادقة هي أن تجده بالقياس على مهمتك، لا بنسخ رقم من منشور مدونة.

لماذا يكون النص الأكثر احتمالًا نصًا سيئًا

رابط إلى القسم: لماذا يكون النص الأكثر احتمالًا نصًا سيئًا

هناك سؤال واضح مختبئ تحت كل هذا: إذا كان لدى النموذج توزيع احتمالات وكانت 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 %

ثماني جمل، جملة واحدة. ما يقارب تسعة من كل عشرة نوافذ من أربع tokens كانت قد ظهرت سابقًا في المخرج نفسه. هذا هو neural text degeneration، وقد سمّاه وشرحه Holtzman وآخرون في الورقة التي قدّمت top-p.3 النموذج ليس معطّلًا؛ تعظيم احتمال التسلسل هو ببساطة الهدف الخطأ للنص المفتوح. الكتابة البشرية ليست أكثر تسلسل كلمات احتمالًا — فهي تحمل مفاجأة، ويتجول احتمالها لكل token، يهبط ويتعافى — بينما مسار الاحتمال الأقصى نقطة ثابتة لا سبب لديها لمغادرتها بعد دخولها.

لهذا يوجد sampling أصلًا. وهو أيضًا، وهذا هو الجزء الذي يُترك عادة، ليس قانونًا كونيًا. قاس الفصل 12 24 من 24 صحيحة في مسائل كلمات من خطوتين باستخدام greedy decoding بسيط، وخفّض sampling عند temperature 0.8 ذلك إلى 81 %؛ ثم أنفقت self-consistency ستة أضعاف tokens لتعود إلى حيث كان greedy أصلًا. كلا الأمرين صحيحان في آن واحد:

التوليد المفتوح. لا توجد تتمة صحيحة واحدة، لذلك تكون الأكثر احتمالًا فخًا — تدور في حلقة، و87.6 % منها منسوخ من نفسها. استخدم sampling.

مهام لها جواب صحيح واحد. توجد تتمة صحيحة واحدة، لذا فإن سحب أي شيء آخر هو سحب خطأ. أصبحت 100 % في الفصل 12 مساوية لـ 81 % لهذا السبب بالضبط. لا تستخدم sampling.

معظم production prompts من النوع الثاني وتُضبط مثل النوع الأول، لأن temperature تُركت على أي قيمة استخدمتها الشيفرة المثال.

sampling من التوزيع الكامل ليس ما يفعله أحد فعلًا، لأن الذيل هائل ومليء بالهراء. لا بد من قص شيء ما. هناك جوابان كلاسيكيان، ويختلفان في ناحية واحدة تحسم كل شيء.

Top-k يحتفظ بعدد ثابت من المرشحين. رتّب حسب الاحتمال، احتفظ بأول kk، ارمِ الباقي، وأعد التطبيع.4 Top-p، ويُسمى أيضًا nucleus sampling، يحتفظ بقدر ثابت من الكتلة: خذ tokens بترتيب تنازلي حتى يبلغ احتمالها التراكمي pp، ثم توقف.3 صوريًا، النواة هي أصغر مجموعة VpV_p بحيث

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

يبدو الفرق تجميليًا، لكنه ليس كذلك، لأن promptين ترسلهما في الدقيقة نفسها لهما شكلان مختلفان تمامًا للتوزيع. كلاهما للنموذج نفسه عند temperature 1:

Q: What is the capital of France?\nA:Once upon a time,
احتمال top-196.01 %25.39 %
tokens التي تحمل 90 % من الكتلة1467
top-k = 40 يحتفظ بـ99.61 % من الكتلة78.87 % من الكتلة
الكتلة في الرتب 2 إلى 403.61 %53.48 %
token في الرتبة 40␣Av, 0.0093 %␣Dr, 0.128 %

قيمة kk ثابتة واحدة، وفشلان في اتجاهين متعاكسين. في prompt الواقعي، يسمح k=40k = 40 بدخول 39 token لا تساوي مجتمعة سوى 3.6 % — إنه يمرر الرديء، بما في ذلك مرشح عند تسعة أجزاء من ألف في المئة، لأن القاعدة تعد الخانات لا الدليل. وفي prompt القصة، يرمي k=40k = 40 نفسه 21 % من الكتلة التي أسندها النموذج فعلًا، لأن النواة الحقيقية هناك عرضها 467 token.

Top-p يجعل رقمًا واحدًا يؤدي المهمتين. اضبط p=0.9p = 0.9 فيحتفظ بـ 1 token في prompt الأول و467 في الثاني، لأنه يسأل سؤالًا عن التوزيع بدل فرض عدد عليه. شاهد هذا التكيف مباشرة — القص نفسه، أربع درجات temperature:

  • ␣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 نفسه ناجيًا واحدًا — نواة بهذا الضيق هي greedy decoding باسم مختلف. عند 2.0 تترك خمسة. القص لم يتحرك؛ الشكل تحته هو الذي تغيّر.

تلك الأداة تحسم أيضًا تصورًا خاطئًا يستحق التسمية، لأنه يكلف الناس مالًا حقيقيًا. على توزيع واثق، top_p = 0.9 ليس «قليلًا من التنوع». إنه greedy. عند temperature 1 تحمل token القائدة هنا 96.90 %، وهي فوق 0.9 بالفعل، لذا يكون عرض النواة 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 % — القص مسرحية، والنموذج عمليًا 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 مختلفة عما كان النموذج غير المعاقَب سيختاره. التشغيل هنا 120 خطوة مقابل 140 في الكتلة أعلاه، ولهذا تقرأ baseline غير المعاقبة 85.5 % بدل 87.6 %:

الإعداد4-grams مكررةالخطوات المعدلة
لا شيء85.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 بالقيمة المنسوخة على نطاق واسع 1.2 فأعادت كتابة 35 من 120 قرارًا، وهذا ليس دفعة خفيفة؛ إنه نموذج مختلف.

ذلك الرقم الأخير هو التمهيد للفشل الذي لا يحذر منه أحد.

ماذا تفعل العقوبات بالنص الذي يُفترض أن يكرر

رابط إلى القسم: ماذا تفعل العقوبات بالنص الذي يُفترض أن يكرر

الكود يكرر. الجداول تكرر. القوائم تكرر. المخرجات المنظمة تكرر بحكم التعريف — فهذا هو معنى البنية. لا تستطيع عقوبة أن تفرق بين نموذج عالق في حلقة ونموذج يخرج الصف الرابع من جدول بشكل صحيح، لأن كليهما يبدو مثل token تظهر مرة أخرى.

المهام الثلاث نفسها، مولّدة بثلاث طرق:

المهمةلا شيءfrequency 0.5repetition 1.2
جدول markdown، 6 صفوف0 / 56 خطوة معدلة0 / 562 / 62
دالة Python0 / 930 / 9310 / 110
قائمة نقطية، 1 إلى 120 / 500 / 500 / 50

اتضح أن عقوبة frequency عند 0.5 غير مؤذية في الثلاثة كلها، وهي نتيجة مفيدة ومفاجئة قليلًا، وتقول شيئًا دقيقًا: بما أنه لم يتغير أي قرار، فلا بد أن tokens البنيوية كانت تفوز بمواضعها بأكثر مما طرحته العقوبة، حتى بعد ظهورها خمسًا وست مرات. أما عقوبة CTRL، التي تقسم بدلًا من ذلك، فتزيحها، وهذا ما أنتجته:

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

ينهار الاصطفاف: يتغير مقدار الحشو داخل كل خلية من صف إلى صف، لأن سلسلة المسافات قبل الأنبوب الختامي هي بالضبط نوع التكرار الذي وُجدت العقوبة لكسره. تجميلي، وقد كلف ست 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,

دفعت العقوبة النموذج بعيدًا عن total — التي استُخدمت بالفعل في docstring — إلى total_sum، وحشت المخرج بتعليقات مختلقة لتصرف ميزانيتها على tokens غير مستخدمة، ثم دخلت في range بثلاث وسيطات مع خطوة. يقول التعليق incrementing by 2 each time، وهذا خطأ لمجموع المربعات من 1 إلى nn. أنتجت عقوبة تكرار كودًا غير صحيح من prompt أُجيب عنه بشكل صحيح من دونها.

القاعدة التالية قصيرة: العقوبات للنثر المفتوح، ويجب إيقافها للكود، والمخرجات المنظمة، والبيانات الجدولية، وأي شيء له schema. الفصل 18 يتناول تلك الفئة الثانية تحديدًا.

ترتيب التطبيق، ولماذا يغيّر الجواب

رابط إلى القسم: ترتيب التطبيق، ولماذا يغيّر الجواب

كل implementation حقيقية تطبق هذه المراحل بتسلسل محدد:

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

هذا ليس ترتيبًا إداريًا اعتباطيًا، وتبديل مرحلتين ينتج توزيعات مختلفة فعلًا. قياسان، كلاهما على prompt الواقعي.

القص قبل temperature أو بعدها. تُحسب النواة على أي توزيع يُعطى لها، وtemperature تغيّر ذلك التوزيع جذريًا:

top-p 0.9 بعد temperaturetop-p 0.9 قبل 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 «لا يفعل شيئًا» لدى مزود ويدمر المخرج لدى آخر مع الرقمين نفسيهما، فهذا الجدول جواب معقول.

المعاقبة قبل temperature أو بعدها. طرح عقوبة α\alpha ثم القسمة على TT يعطي عقوبة فعالة قدرها α/T\alpha/T؛ والقسمة أولًا ثم الطرح يعطي α\alpha. مع presence penalty قدرها 1.0 مطبقة على token القائدة:

temperatureعاقب، ثم طبّق temperatureطبّق temperature، ثم عاقب
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» ليست مقدار عقوبة محددًا جيدًا إلا إذا عرفت أيضًا أين تُطبّق temperature، ولا توثق أي API هذا.

عرض التفاصيل

اختياري: pipeline كاملة، بالترتيب أعلاه.

ستة عشر سطرًا، وكل شيء في هذا الفصل موجود فيها. إنها العملية نفسها التي تنفذها الأداة، على متجه 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 الحالية، وهذا ما يجعل النواة تشمل token التي تعبر العتبة بدل التوقف قبلها مباشرة. أخطئ ذلك بمقدار واحد وسيصبح top_p = 0.9 بصمت قصًا أضيق قليلًا من كل implementation أخرى.

هذا من المواضع القليلة في النصف الثاني من الدورة حيث تكون Python هي اللغة المناسبة، والسبب بنيوي لا أسلوبي: كل سطر أعلاه يحتاج إلى متجه logits كاملًا في يدك، وعبر HTTP API لا يوجد ذلك المتجه. يمكنك إرسال temperature وtop_p إلى مزود؛ لا يمكنك تنفيذهما بنفسك، ولا يمكنك رؤية ما فعلاه.

كل مزود يقبل مجموعة فرعية مختلفة من هذه الضوابط، بنطاقات مختلفة، ويتجاهل الباقي بصمت. هذه ليست شكوى مجردة. أي تطبيق يتيح اختيار نموذج عليه أن يكتب الاختلافات في مكان ما، والملف الذي يفعل فيه ذلك هو خريطة عدم التوافق. إليك ما يعلنه فهرس كهذا لمعلمة واحدة عبر مصادر النص التسعة التي يدعمها:

نطاق temperature المعلنالمصادر
0 إلى 1Anthropic, Google, Meta, Cerebras, PaLM
0 إلى 1.5Mistral
0 إلى 2OpenAI, DeepSeek, xAI

الكلمة نفسها؛ المقياس ليس كذلك. «temperature قدرها 1» هي التوزيع غير المعدّل عند أحدهم والحرارة القصوى المسموحة عند آخر، ونصف الفهرس لا يستطيع التعبير عن القيمة التي يعاملها النصف الآخر كحيادية-زائد-قليل. بقية المقابض غير متساوية بالقدر نفسه: إدخالات OpenAI وDeepSeek وxAI تقبل عقوبات presence وfrequency ولا تقبل topK؛ وإدخالات Google وMeta وCerebras وPaLM تقبل topK ولا تقبل عقوبات؛ وAnthropic تقبل topK وtopP وتسلسلات stop ولا تقبل عقوبات؛ وواحد فقط من التسعة — Mistral — يقبل seed. إرسال معلمة لا ينفذها مزود لا ينتج عمومًا أي خطأ: ينجح الطلب، ولا يفعل المقبض شيئًا، وتستنتج أن الإعداد لا أثر له.

ولاحظ ما يكونه ملف كهذا: ادعاء عن API شخص آخر، مكتوب في يوم بعينه، ولا شيء يتحقق منه بعد ذلك. فهرس يقول 0 إلى 1 لمزود بات يقبل 0 إلى 2 سيقص كل طلب بصمت.

ينتمي تحكمان آخران إلى العائلة نفسها. logprobs، حيث يُعرض، يعيد log-probabilities الخاصة بـ token المختارة وغالبًا أعلى البدائل القليلة — النافذة الوحيدة التي تحصل عليها على التوزيع الذي يتناوله هذا الفصل، وأساس كل 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، ومختلف بين seeds، تمامًا كما أُعلن. إذن ما تثبته seed هو السحب العشوائي في السطر الأخير من دالة sample تلك — أي token تُختار عند وجود توزيع معين.

ما لا تثبته هو التوزيع. وهنا تبدأ المشكلة، لأن متجه logits الذي ينتجه نموذجك ليس كائنًا رياضيًا؛ إنه ناتج مليارات عمليات جمع بأعداد عائمة، ولهذه العمليات ترتيب.

ترك الفصل 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 عندما يقسم اختزالًا على وحدات متوازية أكثر أو أقل، يفعل هذا بالضبط. والخادم يقسم بحسب عدد الطلبات التي يخدمها.

إليك ذلك الأثر على النموذج نفسه. prompt نفسه، المرور الأمامي نفسه، والفرق الوحيد هو عدد الطلبات الأخرى التي صادف وجودها في 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

عند تشغيله وحده، يكون النموذج حتميًا تمامًا — عشرون مرورًا، متطابقة حتى البت. ضع prompt المطابق في batch مع طلبات غير ذات صلة وتتغير 97 % من logits الخاصة به. لم يتغير شيء في طلبك. وصل طلب شخص آخر.

والآن الجزء الصادق، لأن هذا يُروى عادة كما لو كان نهاية القصة. تغير قدره 2.5×1052.5 \times 10^{-5} لا يغير المخرج إلا إذا كانت tokenان مرشحتان ضمن هذا الفرق من بعضهما. على 717 خطوة توليد عبر اثني عشر prompt، كان أصغر فرق بين أعلى logits اثنين 2.5×1032.5 \times 10^{-3} — أكبر بمئة مرة من الاضطراب — ولم تكن أي خطوة قريبة بما يكفي لتنقلب. لذلك في هذا النموذج، بـ 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، لذا قرب مقدار logit يساوي 16 تكون القيم القابلة للتمثيل متباعدة بـ 0.125 — 16.0، ثم 16.125، ثم 16.25 — ويمكن أن يحرك التقريب logit بما يصل إلى 0.0625. وفي الوقت نفسه كان لدى 4.7 % من خطوات التوليد المقاسة أعلاه فرق بين الأعلىين أقل من 0.1. هذا هو الفرق كله بين التجربتين: في float32 كان الاضطراب أصغر بمئة مرة من أقرب قرار، وفي bfloat16 هو بالحجم نفسه. يعمل production inference بدقة 16-bit، على عتاد فيه kernels مدمجة وترتيبات اختزال لا يعد أحد بالحفاظ عليها. هل «الضجيج العددي مهمل» سؤال عن الدقة والعتاد، لا عن النموذج.

إذن، الأسباب الأربعة، مفهرسة كما وعد الفصل 9:

جمع الأعداد العائمة ليس ترابطيًا

رابط إلى القسم: جمع الأعداد العائمة ليس ترابطيًا

صندوق الفصل 2. يغيّر ترتيب الجمع قيمته، لذا فإن أي تغيير في كيفية تقسيم الاختزال يغيّر logits. هذه هي الطبقة التحتية؛ الثلاثة الأخرى طرق لتغيير الترتيب.

Dynamic batching يجمّع طلبك مع طلبات غرباء

رابط إلى القسم: Dynamic batching يجمّع طلبك مع طلبات غرباء

Continuous batching، من الفصل 13، هو السبب في أن inference ميسور الكلفة — ويعني أن شكل المصفوفات التي تمر tokens عبرها يعتمد على حركة المرور. كما قيس أعلاه: تحركت 147,321 logits لأن حجم batch تغيّر.

قال صندوق الفصل 9 ذلك بالفعل. يتخذ router اختيارًا منفصلًا لكل token في كل طبقة، خاضعًا لحدود سعة لكل expert محسوبة على مستوى batch. token كانت ستذهب إلى expert 7 وحدها تذهب إلى expert 12 بصحبة غيرها. هذا ليس فرق تقريب؛ إنها مجموعة أوزان مختلفة.

سلسلة إصدار مثل -latest مؤشر، والمؤشرات يعاد توجيهها. كما يحدّث المزودون serving stack تحت معرف إصدار ثابت. لا يُعلن أي منهما بدقة تسمح لك بربطه بتغير مخرجك.

معلمة seed لدى OpenAI صادقة بشأن هذا بالطريقة الوحيدة الممكنة: تأتي إلى جانب حقل system_fingerprint يعرّف إعداد backend، وتذكر الوثائق أن الحتمية best-effort وأن تغير fingerprint يعني أن النتائج قد تختلف. اقرأ ذلك كما هو — مزود يخبرك أنه يتحكم في الأسباب الأربعة أعلاه كلها، وأنك لا تتحكم في أي منها، وأن الشيء الوحيد الذي يستطيع عرضه هو أن يخبرك بعد الواقعة بأن شيئًا تحرك.

كان كل ما سبق عن مقبض وعواقبه. ابتعد مستوى واحدًا فتظهر المشكلة الأصعب: الكائن الذي كنا نضبطه هو توزيع احتمالات، وتوزيعات الاحتمالات لا تملك واجهة.

function call تملك واحدة. صف قاعدة بيانات يملك واحدة. handler من نوع POST يتوقع JSON body بثلاثة حقول مطلوبة يملك واحدة، وسيرفض أي شيء آخر. بين النموذج وكل مكوّن آخر في نظامك يوجد عقد لا يستطيع أحد طرفيه تقديم وعود بشأنه: سيُنتج النموذج شيئًا ما، مسحوبًا من توزيع شكّلته ولم تثبته، والكود في الطرف الآخر يحتاج إلى قيمة من نوع معروف وإلا سيرمي خطأ.

الجسر بين هذين العالمين يُبنى من مادة هذا الفصل لا من parsing وإعادة المحاولة. إذا كانت token ستكسر البنية المطلوبة، فلا تأخذ sample منها وتأمل — بل تضبط logit الخاص بها على -\infty قبل أن يراها softmax أصلًا. Constrained decoding هو قناع فوق المتجه نفسه الذي قضينا هذا الفصل نعيد تشكيله، ويحوّل «من فضلك أجب بصيغة JSON» من طلب إلى ضمان.

الفصل 18 هو ذلك العقد: tool calling وJSON Schema وstructured outputs، وما يلزم لجعل نظام حتمي آمنًا للبناء فوق نظام احتمالي.


كل القياسات في هذا الفصل تأتي من Qwen/Qwen2.5-0.5B-Instruct على CPU، وبـ float32 ما لم يُذكر خلاف ذلك، مع تنفيذ sampling كما هو مكتوب في القسم الاختياري بدل تفويضه إلى مكتبة. إنها نموذج صغير، والقيم المحددة قيمه؛ أما الآليات فليست كذلك. مقال Von Platen How to generate text with different decoding methods (Hugging Face, 2020) هو المقال الذي يُقاس هذا المقال مقابله، وما زال أفضل مقدمة قصيرة للمادة نفسها. وبالنسبة لقسم الحتمية: ملاحظات PyTorch عن قابلية التكرار تصف ما تثبته seed وما لا تثبته على جهاز واحد، وتوثيق OpenAI لـ seed وsystem_fingerprint يصف ما يستطيع المزود وعدك به وما لا يستطيع، ونقاش Thinking Machines عام 2025 عن batch-invariant kernels هو أوضح حساب عام لسبب أن إصلاح هذا على مستوى خادم inference ممكن لكنه ليس مجانيًا.

  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 الحديث — كطريقة لكشف التوزيع الكامل للمعلّم، أي 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 تلائم قيمة واحدة على مجموعة تحقق بحيث تطابق ثقة النموذج دقته؛ إنها طريقة معايرة لاحقة تطبق على مخرجات مصنف. Temperature sampling تحكم وقت التشغيل في كيفية سحب المولّد 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 هو عقوبة التكرار الأصلية — تلك التي تقسم.

هل أنت مستعد لتترك الاختيار لـ LIA؟

ابنِ بكل نماذج الذكاء الاصطناعي في مكان واحد — ابدأ مجانًا اليوم.