דלג לתוכן
17/30פרק 17 מתוך 30

טמפרטורה, top-p והדטרמיניזם שאין לכם

Temperature מחלקת את ה-logits לפני ה-softmax — וזה מפרק את רעיון ״חוגת היצירתיות״. וגם: אותה קריאה greedy, פעמיים, שתי תשובות.

בעמוד הזה

הנה אותה בקשה שנשלחה לאותו מודל חמש פעמים. אותם משקלים, אותו 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 ואף פעם לא ממש השתמש בו. תיבת הנקודה הצפה של פרק 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 הנוסחה מחלקת באפס, ולכן כל מימוש מטפל בזה כמקרה מיוחד של המקסימום האריתמטי — כולל הווידג׳ט למטה, שעובר ל-argmax ב-T0.001T \le 0.001.

אזהרה אחת, כי התנגשות השמות גורמת לבלבול אמיתי. יש דבר שני, לא קשור, שנקרא temperature בלמידת מכונה: temperature scaling, שיטת כיול שמתאימה ערך אחד על סט ולידציה כדי שהביטחון של מסווג יתאים לדיוק שלו.2 אותה נוסחה, אין קשר ל-generation. כשמאמרים אומרים ״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 שהמודל דחה, שקיבל הסתברות ממשית מכפתור שהקורא סובב.

Temperature היא לא חוגת יצירתיות

קישור למקטע: Temperature היא לא חוגת יצירתיות

המספר ␣banana הוא כל הטיעון בקטן: העלאת ה-temperature לא יכולה לתת למודל רעיון שלא היה לו. ה-logits כבר חושבו, הדירוג כבר קבוע, ו-temperature משמרת אותו בדיוק — שום כמות של חום לעולם לא תעביר token שקיבל ציון נמוך מעל token שקיבל ציון גבוה. כל מה שהיא עושה הוא לחלק מחדש מסה במורד הדירוג שהמודל עצמו ייצר. Temperature גבוהה לא הופכת מודל ליותר יצירתי; היא מגדילה את הסיכוי שהוא יפלוט את ה-tokens שהוא עצמו דירג כגרועים.

באוצר מילים אמיתי זה מפסיק להיות קוריוז והופך לסיבה שבגללה פלט ב-temperature גבוהה אינו שמיש. נמדד על Qwen/Qwen2.5-0.5B-Instruct, מעבר קדימה אחד, ה-prompt שלמעלה, בספירת כמה tokens צריך כדי לצבור נתח נתון ממסת ההסתברות:

temperatureהסתברות top-1אנטרופיהtokens שמחזיקים 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:. הזבל בבלוק הפתיחה הוא התוצאה הישירה, וזה לא באג במודל או בספרייה — זה מה שהבקשה ביקשה.

הטווח השימושי צר ותלוי במשימה ולא בטעם. בשאלה עובדתית התשובה היא 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 כבר הופיעו קודם באותו פלט. זו ניוון טקסט נוירוני, כפי שנקרא והוסבר בידי Holtzman ואחרים במאמר שהציג את top-p.3 המודל לא שבור; מקסום הסתברות רצף הוא פשוט יעד שגוי לטקסט פתוח. כתיבה אנושית אינה רצף המילים הסביר ביותר — יש בה הפתעה, הסתברות ה-token שלה נודדת, יורדת ומתאוששת — בעוד שהמסלול בעל ההסתברות המרבית הוא נקודת שבת, שברגע שנכנסים אליה אין לה סיבה לצאת.

זו הסיבה ש-sampling קיים בכלל. וזה גם, וזה החלק שבדרך כלל נשמט, לא חוק אוניברסלי. פרק 12 מדד 24 מתוך 24 תשובות נכונות בבעיות מילוליות דו-שלביות עם greedy decoding רגיל, ו-sampling ב-temperature 0.8 הוריד את זה ל-81 %; self-consistency אז הוציא פי שישה tokens כדי לטפס חזרה למקום שבו greedy כבר היה. שני הדברים נכונים בו-זמנית:

Generation פתוח. אין המשך נכון יחיד, ולכן ההמשך הסביר ביותר הוא מלכודת — הוא לולאתי, ו-87.6 % ממנו מועתק מעצמו. בצעו sample.

משימות עם תשובה נכונה אחת. יש המשך נכון יחיד, ולכן משיכה של כל דבר אחר היא משיכת שגיאה. ה-100 % של פרק 12 הפכו ל-81 % בדיוק מהסיבה הזאת. אל תבצעו sample.

רוב ה-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

ההבדל נשמע קוסמטי והוא לא, כי שני ה-prompts שאתם שולחים באותה דקה הם בעלי צורות התפלגות שונות לגמרי. שניהם אותו מודל ב-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 tokens שביחד שווים 3.6 % — הוא נותן לזבל לעבור, כולל מועמד של תשע אלפיות האחוז, כי הכלל סופר מקומות ולא ראיות. ב-prompt הסיפורי, אותו k=40k = 40 זורק 21 % מהמסה שהמודל באמת הקצה, כי הגרעין האמיתי שם הוא ברוחב 467 tokens.

Top-p גורם למספר אחד בדיוק לעשות את שני התפקידים. קבעו p=0.9p = 0.9 והוא משאיר token אחד ב-prompt הראשון ו-467 בשני, כי הוא שואל שאלה על ההתפלגות במקום לכפות עליה ספירה. ראו את ההסתגלות הזאת ישירות — אותו חיתוך, ארבע 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 משאיר שורד אחד — גרעין צר כל כך הוא 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 צעדי ה-generation בחרו token שונה מזה שהמודל ללא קנס היה בוחר. הריצה כאן היא 120 צעדים לעומת 140 בבלוק שלמעלה, ולכן קו הבסיס ללא קנס הוא 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
רשימת bullets, ‏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 בעל שלושה ארגומנטים עם stride. ההערה אומרת incrementing by 2 each time, וזה שגוי עבור סכום ריבועים מ-1 עד nn. Repetition penalty ייצר קוד שגוי מ-prompt שנענה נכון בלעדיו.

הכלל שנובע מכך קצר: קנסות מיועדים לפרוזה פתוחה, והם צריכים להיות כבויים עבור קוד, פלט מובנה, נתונים טבלאיים וכל דבר עם schema. פרק 18 עוסק בדיוק בקטגוריה השנייה הזאת.

סדר היישום, ולמה הוא משנה את התשובה

קישור למקטע: סדר היישום, ולמה הוא משנה את התשובה

כל מימוש אמיתי מפעיל את אלה ברצף מסוים אחד:

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

זה לא ניהול חשבונות שרירותי, והחלפה בין שני שלבים מייצרת התפלגויות שונות באמת. שתי מדידות, שתיהן על ה-prompt העובדתי.

חיתוך לפני או אחרי ה-temperature. הגרעין מחושב על כל התפלגות שניתנת לו, ו-temperature משנה את ההתפלגות הזאת באופן קיצוני:

top-p 0.9 אחרי temperaturetop-p 0.9 לפני temperature
T=1.0T = 1.0token אחדtoken אחד
T=1.5T = 1.5353 tokenstoken אחד
T=2.0T = 2.032,966 tokenstoken אחד

ב-T=2T = 2 אותה הגדרה נומינלית מניבה קבוצת מועמדים של 32,966 או של 1, רק כתלות בשאלה איזה שלב רץ קודם. אם אי פעם תהיתם למה העלאת ה-temperature ״לא עושה כלום״ אצל ספק אחד ומשמידה את הפלט אצל אחר עם אותם שני מספרים, הטבלה הזאת היא תשובה סבירה.

ענישה לפני או אחרי ה-temperature. חיסור קנס α\alpha ואז חלוקה ב-TT נותנים קנס אפקטיבי של α/T\alpha/T; חלוקה קודם ואז חיסור נותנים α\alpha. עם presence penalty של 1.0 שמוחל על ה-token המוביל:

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 הופך בשקט לחיתוך מעט הדוק יותר מכל מימוש אחר.

זה אחד המקומות הבודדים במחצית השנייה של הקורס שבהם 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 penalties וללא topK; הרשומות של Google, Meta, Cerebras ו-PaLM מקבלות topK וללא קנסות; Anthropic מקבלת topK, topP ו-stop sequences וללא קנסות; ו-בדיוק אחד מתוך התשעה — Mistral — מקבל seed. שליחת פרמטר שספק לא מממש בדרך כלל לא מייצרת שגיאה בכלל: הבקשה מצליחה, הכפתור לא עושה כלום, ואתם מסיקים שלהגדרה אין השפעה.

ושימו לב מה קובץ כזה הוא: טענה על API של מישהו אחר, שנכתבה ביום מסוים, ושום דבר לא מאמת אחר כך. קטלוג שאומר 0 עד 1 עבור ספק שכעת מקבל 0 עד 2 יגביל כל בקשה בשקט.

עוד שתי בקרות שייכות לאותה משפחה. logprobs, כשהיא מוצעת, מחזירה את log-probabilities של ה-token שנבחר ולעיתים את כמה החלופות העליונות — החלון היחיד שאתם מקבלים אל ההתפלגות שעליה הפרק הזה מדבר, והבסיס לכל heuristics של ביטחון שנבנים על מודל סגור. ו-maximum tokens יחד עם stop sequences מסיימים generation ללא שום התייחסות להסתברות: תקרה קשיחה והתאמת מחרוזת. שניהם צפים כ-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\"."

זהה ברמת bytes בתוך 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 מפצל reduction על פני יותר או פחות יחידות מקביליות, הוא עושה בדיוק את זה. ושרת מפצל לפי מספר הבקשות שהוא משרת.

הנה ההשפעה הזאת על המודל עצמו. אותו 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} משנה את הפלט רק אם שני tokens מועמדים היו במרחק כזה זה מזה. על פני 717 צעדי generation ב-12 prompts, הפער הקטן ביותר בין שני ה-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 % מצעדי ה-generation שנמדדו למעלה היו בעלי פער בין שני העליונים מתחת ל-0.1. זה כל ההבדל בין שני הניסויים: ב-float32 ההפרעה הייתה קטנה פי מאה מההחלטה הקרובה ביותר, וב-bfloat16 היא באותו גודל. Production inference רץ ב-16-bit, על חומרה עם kernels מאוחדים וסדרי reduction שאף אחד לא מבטיח לשמור. האם ״הרעש המספרי זניח״ היא שאלה על דיוק וחומרה, לא על המודל.

אז אלה ארבע הסיבות, מקוטלגות כפי שפרק 9 הבטיח:

חיבור בנקודה צפה אינו אסוציאטיבי

קישור למקטע: חיבור בנקודה צפה אינו אסוציאטיבי

התיבה של פרק 2. סדר הסכימה משנה את הערך שלה, ולכן כל שינוי באופן שבו reduction מפוצל משנה את ה-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 היא מצביע, ומצביעים מופנים מחדש. ספקים גם מעדכנים את שכבת ההגשה תחת מזהה גרסה קבוע. אף אחד מהם לא מוכרז ברזולוציה שתאפשר לכם לקשר אותו לשינוי בפלט שלכם.

פרמטר seed של OpenAI כן בעניין הזה באופן היחיד שבו הוא יכול להיות: הוא נשלח לצד שדה system_fingerprint שמזהה את תצורת ה-backend, והתיעוד מציין שדטרמיניזם הוא best-effort וש-fingerprint שהשתנה אומר שהתוצאות עשויות להיות שונות. קראו את זה כפי שזה — ספק שאומר לכם שהוא שולט בכל ארבע הסיבות שלמעלה, שאתם לא שולטים באף אחת מהן, ושהדבר היחיד שהוא יכול להציע הוא לומר לכם בדיעבד שמשהו זז.

כל מה שהיה כאן עסק בכפתור ובהשלכות שלו. צעד אחד אחורה חושף את הבעיה הקשה יותר: האובייקט שכיווננו הוא התפלגות הסתברות, ולהתפלגויות הסתברות אין ממשק.

ל-function call יש אחד. לשורת מסד נתונים יש אחד. ל-handler של POST שמצפה לגוף JSON עם שלושה שדות חובה יש אחד, והוא ידחה כל דבר אחר. בין המודל לבין כל רכיב אחר במערכת שלכם יושב חוזה שצד אחד לא יכול להבטיח לגביו: המודל יפיק משהו, שנמשך מהתפלגות שעיצבתם אבל לא קיבעתם, והקוד בצד השני צריך ערך מסוג ידוע או שהוא זורק שגיאה.

הגשר בין שני העולמות האלה נבנה מהחומר של הפרק הזה ולא מ-parsing וניסיונות חוזרים. אם token ישבור את המבנה הנדרש, לא עושים לו sample ומקווים — קובעים את ה-logit שלו ל--\infty לפני שה-softmax בכלל רואה אותו. Constrained decoding הוא mask מעל אותו וקטור שבילינו את הפרק הזה בעיצובו מחדש, והוא הופך את ״אנא ענה ב-JSON״ מבקשה להבטחה.

פרק 18 הוא החוזה הזה: tool calling, JSON Schema, פלטים מובנים, ומה נדרש כדי להפוך מערכת דטרמיניסטית לבטוחה לבנייה מעל מערכת הסתברותית.


כל המדידות בפרק הזה מגיעות מ-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 על kernels שאינם תלויי batch הוא ההסבר הציבורי הברור ביותר לכך שתיקון הבעיה ברמת שרת ה-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 מודרני — כדרך לחשוף את ההתפלגות המלאה של 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 מתאים ערך יחיד על סט ולידציה כדי שהביטחון של המודל יתאים לדיוק שלו; זו שיטת כיול בדיעבד שמוחלת על פלטי מסווג. Temperature sampling היא בקרת runtime על האופן שבו 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, שם, כסמנכ״ל טכנולוגיות, בניתי את האפליקציה שמיליוני אנשים השתמשו בה כדי לאכול בריא יותר, והקמתי את NeuraLIA Labs, שם אני בונה מוצרי בינה מלאכותית. כאן אני כותב על מה שהייתי צריך להבין לאורך הדרך, כפי שהייתי רוצה שמישהו היה מסביר לי בזמנו.

עוד על המחבר

פורסם על ידי NeuraLIA Labs.

פוסטים חדשים ישירות לתיבת הדואר

חדשות AI, מדריכים ועדכוני מוצר — מייל קצר כשאנחנו מפרסמים משהו ששווה את הזמן שלך.

תוכן הקורס

Abstract software decision engine with branching paths, probability nodes, and glowing gates.
jev10 דקות קריאה

מודל ה-AI Jev נבנה להחלטות, לא לפרוזה

Jev של TypeSafe AI מושך תשומת לב כי הוא מתייחס לאינטליגנציית תוכנה כאל בעיית הסתברות: לבחור את ההסתעפות הנכונה, להצמיד ביטחון, ולהימנע מתשלום ל-LLM כדי שיכתוב טקסט כשהקוד צריך החלטה.

Abstract agent runtime sorting documents, memory blocks and pointer nodes inside a bounded context frame.
context-engineering10 דקות קריאה

הנדסת הקשר לסוכני AI ארוכי־טווח

סוכנים שרצים לאורך זמן לא נכשלים רק כי החלון קטן. הם נכשלים כשקבצים, פלטי כלים והיסטוריה מיושנת דוחקים החוצה את המשימה שהסוכן היה אמור להשלים.

מוכנים לתת ל-LIA לבחור?

בנו עם כל מודלי ה-AI במקום אחד — התחילו בחינם עוד היום.