Attention ובלוק ה-Transformer, נגזרים מממוצע
מתחילים מהממוצע כסיכום הזול ביותר של context, מודדים איפה הוא נכשל, וגוזרים ממנו את נוסחת ה-attention.
בעמוד הזה
אתם מגיעים לכאן עם tokenizer מפרק 7, טבלת embedding מפרק 8, והיעד שמגיע איתם: בהינתן ה-tokens עד כה, לשים הסתברות על ה-token הבא.
מה שחסר הוא האמצע. כדי לחזות את token המודל צריך וקטור אחד שמסכם את כל מה שלפניו, ושום דבר שבניתם לא מייצר אחד כזה. ה-embedding של token אינו זה — זה מודל bigram, והוא לא יכול לדעת שהמשפט התחיל בשאלה. גם שרשור של כל ה-embeddings הקודמים אינו זה: מספרם משתנה בכל צעד, ומטריצת משקלים קבועה לא יכולה לקבל קלט באורך משתנה.
אז: וקטור אחד בגודל קבוע, שמסכם מספר משתנה של וקטורים. זו כל הבעיה, ו-attention הוא מה שמקבלים כשפותרים אותה בדרך העצלנית ביותר האפשרית ואז מתקנים את שני הדברים שנשברים.
התשובה שהייתה לתחום, ולמה אנחנו לא בונים אותה
קישור למקטע: התשובה שהייתה לתחום, ולמה אנחנו לא בונים אותהמ-1997 ועד בערך 2017 הסיכום היה מצב רקורנטי: לשמור וקטור ולעדכן אותו בכל token, . גודל קבוע, קלט משתנה, בדיוק הצורה הנכונה.
הוא נכשל בשלוש דרכים, והארכיטקטורה של הפרק הזה עונה על שלושתן. ביצוע backpropagation דרך צעדים מכפיל יעקוביאנים, ולכן ה-gradient נעלם או מתפוצץ — המחלה שפרק 5 מדד בתוך צומת יחיד. ה-LSTM1 תוכנן בדיוק נגד זה והרחיב את הטווח השימושי מעשרות צעדים למאות, בלי לשנות את העובדה שמידע מ-token 5 מגיע ל-token 500 רק אם שרד 495 עדכונים סדרתיים. כל המקור היה צריך להיכנס לווקטור אחד: בתרגום sequence-to-sequence2 encoder דוחס את הקלט למצב הסופי שלו. Bahdanau, Cho ו-Bengio נתנו שם לצוואר הבקבוק הזה ותיקנו אותו ב-2014, שלוש שנים לפני ה-transformer, בכך שאפשרו ל-decoder לקחת סכום משוקלל של כל מצבי ה-encoder עם משקלים שחישב בעצמו.3 כל מה שמתחת הוא הרעיון הזה, מיושם על ידי רצף על עצמו, כשהרקורסיה נמחקה. וגם העדכון סדרתי מעצם בנייתו: צריך את , ו-GPU עם עשרת אלפים ליבות לא יכול לעשות עם זה כלום. הארכיטקטורה שניצחה אינה בהכרח חכמה יותר; היא זו שהצעד היקר שלה הוא כפל מטריצות.
ההטיה האינדוקטיבית הקלאסית האחרת, קונבולוציה — להחליק מסנן קטן אחד על כל הקלט, כך שתכונה שמזוהה בכל מקום תזוהה בכל מקום — גם היא לא נבנית כאן; היא כמעט בדיוק נכונה לתמונות ומואצלת לקורס ראייה. לא רקורסיה ולא קונבולוציה מופיעות שוב אחרי הדף הזה, ולכן אף אחת מהן לא מקבלת פרק: פרק 1 הבטיח שההשמטות יוצהרו, לא יוסתרו בשקט.
הסיכום הזול ביותר שיש
קישור למקטע: הסיכום הזול ביותר שישהפונקציה הברורה ביותר שמקבלת מספר משתנה של וקטורים ומחזירה וקטור אחד היא הממוצע:
כל מספר קלטים, גודל פלט קבוע, דיפרנציאבילי, חינם. טבלת embedding ועוד הממוצע הזה ועוד שכבה ליניארית אל אוצר המילים הם מודל שפה שלם בחמש-עשרה שורות. הוא גם נוראי, והדרך שבה הוא נוראי היא כל הגזירה.
הקורפוס למטה הוא מגה-בייט אחד של שייקספיר, 1,115,394 תווים, דרך byte-level BPE tokenizer מהסוג שנבנה בפרק 7 עם אוצר מילים של 1024: 459,760 tokens באורך 2.43 תווים כל אחד, בחלוקה 90/10. כל מודל הוא ברוחב 128, רואה 128 tokens, ומתאמן במשך 3000 צעדי AdamW ב- עם batch של 64. Perplexity נמדדת על הפיצול המוחזק בצד.4
| מודל | פרמטרים | validation perplexity |
|---|---|---|
| ה-token הנוכחי בלבד, בלי context כלל | 263,168 | 59.71 |
| בתוספת הממוצע האחיד של כל מה שלפניו | 263,168 | 248.07 |
| בתוספת embeddings מיקום נלמדים | 279,552 | 245.93 |
| ממוצע אחיד נוסף ל-token במקום להחליף אותו | 263,168 | 60.45 |
קראו את השורה השנייה פעמיים. מיצוע ה-context לא עוזר קצת; הוא הופך את המודל לגרוע פי ארבעה מאשר התעלמות מוחלטת מה-context. שתי סיבות, שתיהן ניתנות להוכחה ולא אמפיריות בלבד.
הממוצע לא יכול לראות סדר. חיבור הוא קומוטטיבי, ולכן ערבוב החלון משאיר את הסיכום ללא שינוי — לא בקירוב:
A = torch.tril(torch.ones(T, T))
A = A / A.sum(1, keepdim=True) # rows of the averaging matrix
y = x[torch.randperm(T)] # the same tokens, shuffled
print((A[-1] @ x - A[-1] @ y).abs().max().item())2.9802322387695312e-08רעש נקודה צפה על סכום שסודר מחדש: שני הסיכומים הם אותו וקטור. מודל שהמבט היחיד שלו על ה-context הוא ממוצע לא יכול להבחין בין the dog bit the man לבין the man bit the dog. השורה השלישית מוכיחה שאי אפשר לתקן זאת על ידי הוספת מיקומים לקלטים — embedding מיקום נלמד על כל token לפני המיצוע קנה 2.14 נקודות מתוך 188. המיקומים נכנסים לסכום, והסכום שוכח אותם.
והממוצע מטביע את ההווה. במיקום 100 ה-token הנוכחי הוא מאית מהסיכום. לזה יש תיקון זול שכבר בבעלותכם: לשמור את ה-token ולהוסיף לו את הסיכום — חיבור residual, מפרק 6, והשורה הרביעית מראה מה הוא עושה. אחרי תיקון הדילול, הממוצע האחיד לא תורם שום דבר בכלל: 60.45 מול baseline של 59.71. כל token נמצא שם, במשקל שווה, ומשקל שווה הוא אותו דבר כמו אין מידע.
הבעיה אינה המיצוע. היא המשקלים.
הממוצע הוא כפל מטריצות, וה-mask הוא softmax
קישור למקטע: הממוצע הוא כפל מטריצות, וה-mask הוא softmaxמיצוע על prefix גדל נראה כמו לולאה. הוא כפל אחד במטריצה משולשת-תחתונה שהשורות שלה מסתכמות לאחד — וגם, בדיוק, softmax:
loop = torch.stack([x[:t + 1].mean(0) for t in range(T)]) # the obvious version
A = torch.tril(torch.ones(T, T))
A = A / A.sum(1, keepdim=True)
mat = A @ x # the same thing
S = torch.zeros(T, T).masked_fill(torch.tril(torch.ones(T, T)) == 0, float("-inf"))
soft = F.softmax(S, dim=-1) @ x # and the same thing againloop vs matmul max |diff| = 5.960464477539063e-08
loop vs softmax max |diff| = 5.960464477539063e-08
the averaging matrix A (rows sum to 1, upper triangle is zero):
1.000 0.000 0.000 0.000 0.000 0.000
0.500 0.500 0.000 0.000 0.000 0.000
0.333 0.333 0.333 0.000 0.000 0.000
0.250 0.250 0.250 0.250 0.000 0.000
0.200 0.200 0.200 0.200 0.200 0.000
0.167 0.167 0.167 0.167 0.167 0.167שלושה רכיבים בעלי שם של transformer נמצאים עכשיו על המסך. המשולש הוא ה-causal mask, שנכפה על ידי היעד: אם מיקום היה יכול לראות את מיקום התשובה הייתה בתוך הקלט — הדליפה שפרק 6 אמר לכם לבקר, רק בתוך הארכיטקטורה. ה-softmax הוא הדרך שבה ה-mask מיושם: הצבת ערכים אסורים ל- שולחת אותם בדיוק לאפס ומנרמלת את מה שנשאר, כך ש-masking ונרמול הם פעולה אחת. (השתמשו ב-, לא ב--1e9: זה הערך שה-masking משמעו, הוא שורד המרה ל-float16 כ-, והוא חוסך לכם להחליט אם הקבוע שבחרתם גדול מספיק לטווח שבמקרה אתם נמצאים בו — שזו קופסת הנקודה הצפה של פרק 2 ששואלת שאלה שאין צורך לענות עליה.) וה-scores הם הפרמטר החופשי. הממוצע האחיד הוא מה שמקבלים כשכל score מותר הוא אותו מספר; שימו שם מספרים כלשהם וה-softmax יהפוך אותם למשקלים תקינים.
שאר הפרק הזה הוא שאלה אחת: מאיפה המספרים האלה מגיעים?
Query, key, value
קישור למקטע: Query, key, valueהם לא יכולים להיות פרמטרים רגילים. מטריצת נלמדת תהיה זהה לכל משפט — היא תוכל לקודד "הסתכל ארבעה tokens אחורה" אבל לעולם לא "הסתכל על שם העצם שהכינוי הזה מתייחס אליו". המשקל שמקשר את מיקום למיקום חייב להיות תלוי במה שנמצא בשני המיקומים, כי רלוונטיות היא יחס, לא תכונה: המילה it אינה רלוונטית מעצמה, היא רלוונטית למשהו.
הפונקציה הזולה ביותר שמקבלת שני וקטורים ומחזירה מספר היא המכפלה הסקלרית מפרק 1. תנו score למיקום עבור מיקום כ- והמנגנון עובד — רע, בשתי דרכים שמכריחות את כל השאר. המכפלה הסקלרית של וקטור עם עצמו היא הנורמה בריבוע שלו, ולכן כל token היה עושה attend בעיקר לעצמו. והיחס היה סימטרי: אם it עושה attend חזק ל-animal, אז animal עושה attend חזק ל-it, וזה שקר לגבי שפה, שבה שם תואר צריך את שם העצם שלו הרבה יותר מששם העצם צריך את שם התואר.
לכן תנו לכל token שני תפקידים, כשתי מפות ליניאריות נלמדות שלו: מה המיקום הזה מחפש, , ה-query; ומה הוא מציע כדי שימצאו אותו לפיו, , ה-key. תנו score והסימטריה נעלמת, כי : token יכול לפרסם דבר אחד ולחפש דבר אחר.
דבר אחד עדיין שגוי. הסכום המשוקלל היה על ה- עצמם, מה שמכריח את הדבר שמועתק להיות הדבר שמותאם. התאמה רוצה את התכונות שמזהות token; העתקה רוצה את התכונות שמועילות בהמשך. לכן לומדים מפה שלישית, , ה-value, וסוכמים אותם.
הנוסחה היא עכשיו הנהלת חשבונות:
כאשר הוא ה-causal mask, אפס על האלכסון ומתחתיו ו- מעליו. בקוד זה שלושים שורות, שעשרים מהן הן צורות:
class Head(nn.Module):
"""One head of causal self-attention."""
def __init__(self, d_model, d_head, block):
super().__init__()
self.q = nn.Linear(d_model, d_head, bias=False)
self.k = nn.Linear(d_model, d_head, bias=False)
self.v = nn.Linear(d_model, d_head, bias=False)
self.d_head = d_head
self.register_buffer("mask", torch.tril(torch.ones(block, block)).bool())
def forward(self, x):
T = x.shape[1]
q, k, v = self.q(x), self.k(x), self.v(x)
s = q @ k.transpose(-2, -1) / math.sqrt(self.d_head)
s = s.masked_fill(~self.mask[:T, :T], float("-inf"))
w = F.softmax(s, dim=-1)
return w @ v לתת score, לעשות mask, לנרמל, לערבב. כל השאר הוא projection.
החלוקה בשורש הריבועי, וממה היא מגינה
קישור למקטע: החלוקה בשורש הריבועי, וממה היא מגינהכמעט כל הסבר של אומר "כדי למנוע מה-softmax להגיע לרוויה", וזה נכון ולא מסביר דבר. הטיעון הוא שתי שורות של שונות מפרק 2. אם הערכים של ו- בלתי תלויים עם ממוצע אפס ושונות אחת, לכל מכפלה יש שונות אחת, ושונויות של דברים בלתי תלויים מסתכמות:
אז ל-scores יש סטיית תקן . נמדד על עשרים אלף זוגות אקראיים:
d Var(q.k) std sqrt(d)
4 3.975 1.994 2.000
16 16.071 4.009 4.000
64 64.249 8.016 8.000
256 253.065 15.908 16.000
1024 1015.562 31.868 32.000למה זה משנה: ה-softmax רגיש לסקאלה באופן ששכבה ליניארית אינה. הכפלת הקלט של שכבה ליניארית מכפילה את הפלט שלה; הכפלת scores בעשר לפני softmax הופכת תערובת רכה לבחירה קשיחה. שורה אחת של 64 scores, עם ובלי החלוקה:
| המשקל הגדול ביותר, ללא חלוקה | אנטרופיה | tokens אפקטיביים | המשקל הגדול ביותר, מחולק | אנטרופיה | tokens אפקטיביים | |
|---|---|---|---|---|---|---|
| 4 | 0.205 | 2.944 | 19.0 | 0.081 | 3.758 | 42.9 |
| 16 | 0.438 | 1.692 | 5.4 | 0.075 | 3.849 | 46.9 |
| 64 | 0.489 | 0.874 | 2.4 | 0.085 | 3.673 | 39.4 |
| 256 | 0.9999 | 0.0007 | 1.0 | 0.143 | 3.547 | 34.7 |
| 1024 | 1.0000 | 0.0000 | 1.0 | 0.132 | 3.644 | 38.3 |
"tokens אפקטיביים" הוא האקספוננט של האנטרופיה: על כמה מיקומים השורה באמת ממוצעת. ללא חלוקה, ב-, head שאותחל זה עתה עושה attend בדיוק ל-token אחד מתוך 64, שנבחר רק על ידי ההגרלה האקראית.
זה רע קדימה וגרוע יותר אחורה, בצורה שפרק 5 כבר מדד על . ל-softmax שהתחייב לערך אחד כמעט אין נגזרת: האלכסון של היעקוביאן שלו הוא , אפס בשני הקצוות. על פני אלפיים שורות אקראיות:
| ללא חלוקה | מחולק | שורות ברוויה (המשקל הגדול ביותר מעל 0.99) | |
|---|---|---|---|
| 4 | 0.8427 | 0.9568 | 0.2 % → 0.0 % |
| 64 | 0.2940 | 0.9609 | 17.9 % → 0.0 % |
| 256 | 0.1406 | 0.9609 | 49.1 % → 0.0 % |
| 1024 | 0.0681 | 0.9611 | 70.4 % → 0.0 % |
ב-, שבע שורות מתוך עשר קפואות עוד לפני שהאימון מתחיל, ו-head שמתחיל קפוא לא יכול ללמוד על מה להסתכל. עם חלוקה, הכמות שטוחה ב-0.96 בכל רוחב ושום דבר לא מגיע לרוויה.
עכשיו החלק שאף אחד לא מפרסם: האם זה משנה את ה-perplexity הסופית? מחקו את החלוקה ואמנו, בארבעה רוחבי head:
| רוחב head | ללא חלוקה | מחולק ב- | מחולק ב- |
|---|---|---|---|
| ארבעה heads, | 37.29 | 38.07 | 37.89 |
| head אחד, | 48.51 | 46.10 | 45.99 |
| head אחד, | 65.37 | 47.53 | — |
| head אחד, | 67.06 | 49.15 | — |
| head אחד, | 76.69 | 59.17 | — |
שתי השורות הראשונות מגיעות מתקציב 3000 הצעדים שלמעלה; שלוש האחרונות הן ריצה קצרה יותר — 1500 צעדים, batch של 32, head אחד, בלי נרמול לפני ה-projections — כאשר שתי הגרסאות תחת הגדרות זהות.
ב- החלוקה לא שווה כלום והריצה בלעדיה מקדימה במעט. זו לא רשות לוותר עליה, כי ב-256 היא שווה 18 נקודות perplexity וב-1024 היא שווה 17. המנגנון נראה ב-scores עצמם:
| score std באתחול | אחרי 1500 צעדים, ללא חלוקה | אחרי 1500 צעדים, מחולק | שורות ברוויה, ללא חלוקה | מחולק | |
|---|---|---|---|---|---|
| 256 | 10.49 | 121.67 | 2.13 | 91.9 % | 0.8 % |
| 512 | 15.13 | 836.85 | 2.66 | 98.7 % | 1.3 % |
| 1024 | 21.15 | 5147.46 | 3.44 | 99.9 % | 16.5 % |
ה-head ללא החלוקה לא מתאושש. הוא בורח: סטיית התקן של ה-scores שלו עולה מ-21 באתחול ל-5147, אנטרופיית ה-attention יורדת לאפס, ו-99.9 % מהשורות שמות יותר מ-0.99 ממשקלן על token יחיד. ברגע ש-head הוא בורר קשיח ה-gradient שלו כמעט אפס ושום דבר לא מושך אותו חזרה, ולכן הקריסה יציבה. ה-head המחולק יושב על סטיית תקן score של 3.44 אחרי אותו אימון, תערובת רכה שעדיין אפשר לשנות.
Vaswani et al. אומרים בדיוק את זה ולא יותר — הם חושדים שהמכפלות "grow large in magnitude for large values of " ומחלקים.5 המילה large נושאת משקל, והטבלאות אומרות איפה גדול מתחיל: כלום ב-32, הכול עד 256.
יותר מדעה אחת, ושני השלישים שאף אחד לא מדבר עליהם
קישור למקטע: יותר מדעה אחת, ושני השלישים שאף אחד לא מדבר עליהםhead אחד הוא שורת softmax אחת לכל מיקום, ולכן הוא מחזיק תשובה אחת ל"מה רלוונטי כאן". חיזוי המילה אחרי the ב-the animal that crossed the wet street צריך את המשבצת התחבירית, את הנושא ואת ה-token הקודם בבת אחת, והתפלגות הסתברות אחת לא יכולה להיות מרוכזת בשלושה מקומות. לכן מריצים כמה heads במקביל, כל אחד ברוחב , משרשרים, ומערבבים עם עוד מטריצה : חילקתם את הרוחב, לא הוספתם לו.
Attention גם עושה בדיוק דבר אחד — הוא מעביר מידע בין מיקומים. כל פעולה בקוד למעלה ליניארית לאורך ציר התכונות, ופרק 5 הוכיח מהי ערימה של מפות ליניאריות. לכן כל block נושא גם MLP קטן שמוחל על כל מיקום בנפרד, מרחיב את הרוחב פי ארבע וחוזר, עם GELU באמצע. כדאי לזכור את חלוקת העבודה: attention מערבב בין מיקומים, רשת ה-feed-forward מחשבת בתוך מיקום.
הסולם המלא, כל שורה מוסיפה רכיב אחד לשורה שמעליה:
| מודל | פרמטרים | validation perplexity |
|---|---|---|
| ממוצע אחיד, נוסף | 279,552 | 60.45 |
| attention head אחד, מחליף את ה-token | 328,704 | 55.47 |
| attention head אחד, נוסף | 328,704 | 46.10 |
| ארבעה heads במקום אחד | 345,216 | 43.21 |
| בתוספת רשת feed-forward | 476,928 | 39.87 |
| בתוספת LayerNorm — ה-block המלא | 477,696 | 38.07 |
משקלים נלמדים מנצחים משקלים אחידים ב-14 נקודות perplexity, וזה כל הטיעון של הפרק בשורה אחת. ארבעה heads קונים עוד 3 תמורת 16,512 פרמטרים נוספים. ואותו head שווה עוד 9 נקודות כשהוא נוסף ולא מחליף: attention מכניס מידע פנימה, הוא לא מחליט מהו מיקום.
עכשיו איפה הפרמטרים באמת יושבים, מה שמפתיע אנשים שראו רק את התרשים:
| רוחב | heads | attention | feed-forward | סך הכול ל-block |
|---|---|---|---|---|
| 128 | 4 | 65,664 (33.2 %) | 131,712 (66.6 %) | 197,888 |
| 768 | 12 | 2,360,064 (33.3 %) | 4,722,432 (66.6 %) | 7,085,568 |
| 4096 | 32 | 67,112,960 (33.3 %) | 134,238,208 (66.7 %) | 201,367,552 |
שני שלישים מכל transformer block הם רשת ה-feed-forward, בכל סקאלה, כי ל-attention יש ארבע מטריצות ול-MLP יש שקול של שמונה. כל מה שמודל יודע, רוב הפרמטרים שמחזיקים אותו נמצאים ב-MLP פר-מיקום.
Residuals ו-LayerNorm, בירושה מפרק 6
קישור למקטע: Residuals ו-LayerNorm, בירושה מפרק 6LayerNorm נבנה ונמדד בפרק 6, והפרק הזה משתמש בו כפי שהושאר שם; חיבורי residual נקראו בשם ועברו ablation שם, ונבנים כאן. שורות ה"נוסף, לא מחליף" למעלה הן חיבורי residual, ששווים 188 נקודות perplexity עבור הממוצע ו-9 עבור head אחד. LayerNorm7 מנרמל כל דוגמה על פני התכונות שלה, ופרק 6 נתן את הסיבות לכך שהוא, ולא BatchNorm, שרד כאן — אין תלות ב-batch, אין סטטיסטיקות רצות, זהה באימון וב-inference, אדיש לאורך רצף — וכל אחת מהן הופכת לדרישה כשמייצרים token אחד בכל פעם למשתמש אחד, שזה המקום שאליו פרק 13 מגיע. הוא עולה 768 פרמטרים וקונה 1.8 נקודות perplexity.
class Block(nn.Module):
def forward(self, x):
x = x + self.att(self.ln1(x))
x = x + self.ff(self.ln2(x))
return xהסתכלו איפה הנרמול יושב: על הקלט של כל תת-שכבה, כשנתיב ה-residual מהקלט לפלט לעולם אינו מנורמל. זה pre-norm. המאמר מ-2017 עושה את ההפך, x = LayerNorm(x + Att(x)) — post-norm, שמניח LayerNorm על נתיב ה-residual עצמו.
Xiong et al. הסבירו את ההבדל דרך ה-gradient באתחול, שברשת post-norm מסוקלל רע עם העומק — הסיבה שה-transformer המקורי היה צריך learning-rate warmup כדי להתאמן בכלל.8 שנים-עשר blocks, 1000 צעדים, learning rate :
gradient norm per block at initialisation, before any step
pre-norm block 1 0.0498 ... block 12 0.0657 ratio last/first 1.32
post-norm block 1 0.0977 ... block 12 0.1613 ratio last/first 1.65
pre-norm, no warmup perplexity 37.82
pre-norm, 200-step warmup perplexity 37.62
post-norm, no warmup perplexity 308.05
post-norm, 200-step warmup perplexity 37.88Post-norm בלי warmup גרוע פי שמונה, ו-post-norm עם warmup תואם בדיוק ל-pre-norm. Warmup אינו פרקטיקה טובה כללית כאן; הוא טלאי לסידור ספציפי של הנרמול, והזזת ה-LayerNorm מסירה את הצורך בו. זו הסיבה שכמעט כל מודל מאז 2019 הוא pre-norm, וזו הסיבה שיש לקרוא את התרשים מ-2017 כהיסטוריה ולא כמפרט.
איפה נמצא token?
קישור למקטע: איפה נמצא token?מחקו את embeddings המיקום והמודל עדיין מתאמן; הוא פשוט לא יכול לדעת איפה משהו נמצא, וזו סימטריה ולא כשל אימון. שום דבר ב-attention score לא מזכיר את או עצמם, ולכן פרמוטציה של הקלט מפרמטת את הפלט: self-attention הוא permutation-equivariant. זו עיוורון-הסדר של הממוצע בתחפושת טובה יותר — ה-causal mask משחזר חלק מהסדר, כי כל מיקום רואה prefix אחר, אבל בתוך prefix כל הסידורים דומים.
ארבע דרכים להזריק מיקום, מאומנות על חלונות של 64 tokens ומוערכות ב-64, 128 ו-256 — מעבר לכל אורך שראו:
| מיקומים | perplexity ב-64 | ב-128 | ב-256 |
|---|---|---|---|
| בלי בכלל | 48.79 | 52.63 | 57.52 |
| embeddings אבסולוטיים נלמדים | 38.63 | 108.47 | 181.94 |
| סינוסים קבועים | 42.96 | 95.26 | 152.25 |
| RoPE | 44.12 | 50.52 | 84.84 |
| ALiBi | 44.95 | 43.51 | 42.49 |
Embeddings אבסולוטיים נלמדים — וקטור אחד לכל מיקום, נוסף ל-token — מנצחים באורך שעליו אומנו ואז נופלים מצוק, כי מיקום 100 מעולם לא היה ב-batch וה-embedding שלו עדיין הווקטור האקראי שבו התחיל. סינוסים, הבחירה המקורית, מחושבים ולא נלמדים, מסינוסים וקוסינוסים בתדרים המרווחים גיאומטרית; המאמר מ-2017 קיווה שזה יעשה אקסטרפולציה, והטבלה אומרת שלא — הפונקציה מוגדרת במיקום 200, אבל המודל מעולם לא למד לקרוא אותה שם. RoPE9 לא מוסיף כלום ובמקום זאת מסובב query ו-key בזווית פרופורציונלית למיקום, בפרוסות דו-ממדיות; מכיוון שסיבוב שני הצדדים של מכפלה סקלרית באותה מידה משאיר אותה ללא שינוי, ה-score בסוף תלוי רק ב-, כך שהמיקום הופך יחסי בחינם ואין טבלה שיכולה להיגמר. הוא מתדרדר, אבל הוא מתדרדר. ALiBi10 הוא התוצאה הפשוטה והמוזרה ביותר כאן: קנס ליניארי על ה-score פרופורציונלי למרחק, עם שיפוע שונה לכל head. ה-perplexity שלו משתפרת כשהחלון גדל מעבר לאורך האימון, מ-44.95 ל-42.49, כי הקנס מוגדר בכל מרחק וכל head ממשיך לעשות את מה שאומן לעשות.
הלקח שורד את הטבלה: ארכיטקטורה שלא יכולה לייצג משהו היא בעיה שונה מזו שמעולם לא למדה את הטווח הזה, והשנייה היא זו שנושכת. זו גם המכונה שמאחורי כל הכרזת "הרחבנו את ה-context ל-128K" — כמעט תמיד מדובר בסקיילינג מחדש של קידוד רוטרי, וזו הסיבה שפרק 16 אומר שמגבלת ה-context זזה ולא נעלמת.
Dropout עובר בירושה באותה דרך: הוא מופיע על משקלי ה-attention אחרי ה-softmax, על הפלט של כל תת-שכבה לפני תוספת ה-residual, ועל סכום ה-embedding, ועושה בדיוק את מה שפרק 6 תיאר. בריצות pretraining גדולות הוא לעיתים קרובות מוגדר לאפס, כי מודל שרואה כל token פעם אחת אינו במצב של overfit.
כמה זה עולה
קישור למקטע: כמה זה עולהלשני tensors בשכבה יש צורה , כאשר הוא מספר ה-tokens: ה-scores והמשקלים אחרי ה-softmax. כל השאר — כל projection, כל ה-MLP — ליניארי ב-.
שכבת attention אחת, ברוחב 512, 8 heads, batch של אחד, float32, על GPU של מחשב נייד. קראו את שתי עמודות המילישניות רק לפי היחסים שלהן: הן זמן קיר על כרטיס מחשב נייד של 8 GB שמוריד תדר מ-1,785 MHz לפחות מ-300 MHz כשהוא מתחמם, כך שריצה קרה של אותו קוד חוזרת מהר פי שבעה עד עשרה וריצה עמוסה איטית עוד יותר. עמודות המגה-בייט הן ספירות בייטים של allocator ואינן זזות.
tokens ms total ms x4 ms projections attn matrix MB peak MB MB x4
128 2.246 - 1.324 0.5 14.6 -
256 2.855 1.27 2.113 2.0 19.2 1.31
512 5.761 2.02 3.105 8.0 34.4 1.79
1024 16.414 2.85 4.008 32.0 89.1 2.59
2048 51.573 3.14 9.989 128.0 296.1 3.32
4096 225.432 4.37 20.176 512.0 1100.1 3.72
8192 832.838 3.69 40.106 2048.0 4300.1 3.91
16384 OUT OF MEMORY 8192.0
fitted exponent (log-log slope, last four rows): time ~ n^1.91 memory ~ n^1.87עמודות x4 הן היחס לשורה שמעל, והכפלה של מתכנסת בדיוק ל-4 גם בזמן וגם בזיכרון — 3.91 בצעד האחרון מול 4 תיאורטי. עמודת ה-projections היא הבקרה: 4.0 ms ב-1024 tokens עד 40.1 ms ב-8192, פקטור של עשר עבור פקטור של שמונה. ליניארי, כפי שהובטח.
ואז השורה האחרונה. שכבת attention אחת, רצף אחד, בלי מודל סביבה, נגמר לה הזיכרון על GPU של 8 GB ב-16,384 tokens — מטריצת ה-score לבדה תהיה 8 GB, בהיותה 8 heads כפול 16,384 כפול 16,384 כפול 4 בייטים. לא המודל; tensor ביניים אחד בשכבה אחת.
זו העובדה הפיזית שמתחת לשלושה פרקים מאוחרים יותר. זו הסיבה של-context window יש בכלל מגבלה, שפרק 16 הופך למחיר. זו הסיבה ש-FlashAttention קיים, מחשב את אותה תוצאה באריחים בלי לאחסן את המטריצה לעולם — אופטימיזציית זיכרון לפני שהיא אופטימיזציית מהירות.11 וזה החשבון שמאחורי המחיר של prompt ארוך, שפרק 24 משלם בלולאת agent — עניין נפרד מהממצא האחר של אותו פרק, שמודל גם משתמש ב-context ארוך גרוע יותר, שאותו הוא מודד ומסרב להאשים בנוסחה הזאת.
הצגת פרטים
שתי הגרסאות שמכווצות cache, נקראות כאן ומשולמות בפרק 13.
Generation שומר ב-cache את ה-keys וה-values של ה-tokens שכבר עובדו — key אחד ו-value אחד לכל token, לכל head לכל שכבה. Multi-query attention12 שומר query projections אבל projection יחיד של key ו-value שמשותף לכל ה-heads, ומחלק את ה-cache הזה ב-. Grouped-query attention13 עושה אינטרפולציה: heads מקובצים, כל קבוצה חולקת key ו-value אחד, כך ש- הוא attention רגיל ו- הוא multi-query. כמעט כל מודל פתוח מאז 2023 משתמש בו עם 4 או 8 קבוצות. אף אחד מהם לא קיים בשביל איכות; שניהם קיימים בשביל גודל ה-cache הזה, ופרק 13 עושה את החשבון שהופך אותו ל"איזה מודל נכנס ל-GPU שלכם".
שתי צורות, והגודל של אחת
קישור למקטע: שתי צורות, והגודל של אחתהמאמר מ-2017 מתאר encoder-decoder: stack אחד שקורא את המקור עם attention לא ממוסך, שני שמייצר את היעד בצורה causal, וסוג שלישי של attention באמצע שבו ה-queries של ה-decoder פוגשים את ה-keys של ה-encoder. זה נכון לתרגום, שבו קלט ופלט הם שני רצפים.
מה שניצח היה חצי ה-decoder-only — stack אחד, causal לכל אורכו, קלט ופלט באותו רצף — והסיבה אינה אלגנטיות. "לחזות את ה-token הבא" רץ על כל טקסט, ולכן סט האימון הוא האינטרנט ולא קורפוס מקביל, והכול הופך למשימה האחת הזאת: תרגום הוא מסמך שמכיל מקור ואז יעד, שאלה ותשובתה הן מסמך, שיחה עם tool call באמצע היא מסמך. פרק 11 עוסק באופן שבו האחרון מיוצר. Encoders לא נעלמו — אחד רואה את כל הקלט בבת אחת, וזה מה שרוצים כשהמשימה היא לייצג טקסט ולא להמשיך אותו, וזו הסיבה שה-retrieval embeddings של פרק 19 מגיעים מ-encoders ולא מהמודל שמנהל את הצ'אט.
לאחר שה-block מוגדר, גודל המודל הוא חשבון. לכל block, עם רוחב והרחבה פי ארבע: עבור עם biases על כל הארבעה, כפי שיש ל-GPT-2 — הטבלה למעלה משאירה את ה-bias מחוץ לשלושה מהם, ולכן 2,304 פחות לכל block ב-; עבור ה-MLP; עבור שני LayerNorms — , בתוספת טבלת token של , ועבור מיקומים אבסולוטיים, . עבור הצורה של GPT-2 small — , 12 blocks, אוצר מילים של 50,257, context של 1024, שכבת הפלט חולקת את משקלי ה-embedding:
token embeddings 50,257 x 768 = 38,597,376
position embeddings 1,024 x 768 = 786,432
one block 7,087,872
12 blocks 85,054,464
final LayerNorm 2 x 768 = 1,536
total (weights tied) 124,439,808וזה הגודל שפורסם למודל הזה. הנוסחה אינה קירוב; היא המודל. שימו לב גם שכמעט שליש ממודל קטן הוא טבלת ה-embedding, ולכן גודל אוצר המילים הוא החלטה ארכיטקטונית ולא החלטת preprocessing — ה-trade-off שפרק 7 הציב.
על מה head באמת מסתכל
קישור למקטע: על מה head באמת מסתכלPerplexity היא מספר על קורפוס. מה ש-head אחד עושה זו שאלה אחרת, ומודל שאומן על מגה-בייט של שייקספיר הוא הכלי הלא נכון בשבילה: הדבר ההוגן לומר על מפת ה-attention של מודל בן 500,000 פרמטרים הוא שהיא לרוב אינה ניתנת לפירוש. לכן: שפה שבה לשאלה יש תשובה נכונה.
האיור הקלאסי הוא the animal did not cross the street because it was too tired, שבו it הוא החיה, מול …because it was too wet, שבו מילה אחת מזיזה את הרפרנט לרחוב. אלה Winograd schemas14 — זוגות משפטים זהים חוץ ממילה אחת, שבה המילה הזו קובעת למה כינוי גוף מתייחס.
הם גם ניתנים לפתרון ברמאות, וזה החלק שהמדריכים מדלגים עליו. אם שני המועמדים הם חיה ומקום, tired ו-wet מזהים את הרפרנט לפי קטגוריה, ומודל שיודע רק אילו מילים נוכחות מצליח בלי לדעת דבר על סדר. נמדד על הגרסה הזאת של המשימה, עם זוגות חיה/מקום מוחזקים בצד:
uniform causal average held-out referent accuracy 100.0 %
one transformer block held-out referent accuracy 91.7 %שק המילים מנצח את ה-transformer. כל הדגמה שבנויה על המשפט הזה לא מוכיחה דבר על attention.
אז סוגרים את החור: שולפים את שני המועמדים ממאגר אחד של שישה-עשר שמות עצם, שכל אחד מהם יכול להופיע בכל אחת משתי המשבצות, ומפצלים את שמות התואר לפי תפקיד במקום לפי קטגוריה — ארבעה שהופכים את it לחוצה (tired, scared, slow, weak), ארבעה שהופכים אותו לנחצה (wet, wide, busy, steep).
the {x} did not cross the {y} because it was too {adj} , so the {ref} waited .אמנו כחיזוי רגיל של token הבא, תנו score למיקום אחד — המילה אחרי so the — ובנו את הסט המוחזק בצד מזוגות שמות עצם שסדרם ההפוך היה באימון, כך שכל דבר שיודע אילו שני שמות עצם נוכחים אבל לא מי בא ראשון חייב לענות הפוך.
| מודל | פרמטרים | מוחזק בצד | נותן שם לעצם האחר |
|---|---|---|---|
| ה-token הנוכחי בלבד | 5,796 | 5.2 % | 5.2 % |
| ממוצע causal אחיד | 5,796 | 27.9 % | 50.0 % |
| head אחד של attention נלמד | 18,084 | 35.4 % | 64.6 % |
| ארבעה heads | 22,244 | 75.0 % | 15.6 % |
| transformer block אחד | 55,716 | 92.7 % | 4.2 % |
| שני transformer blocks | 105,508 | 100.0 % | 0.0 % |
ניחוש בין שני שמות העצם הנוכחים הוא 50 %. הממוצע האחיד נוחת על 27.9 % ועונה עם שם העצם הלא נכון בזוג בדיוק חצי מהזמן — החתימה של משהו שיודע אילו מילים שם ולא דבר על הסדר שלהן, כפי שמבחן הערבוב חזה שלושה סעיפים קודם.
עכשיו המפה: ה-attention במיקום שצריך לתת שם לרפרנט, ממוצע על פני ארבעת ה-heads של כל block, עבור שני המשפטים שנבדלים במילה אחת. ממוצע אחיד היה שם 0.067 על כל אחד מחמישה-עשר ה-tokens הנראים.
the animal did not cross the street because it was too tired , so the animal waited .
blk 1 the:0.00 animal:0.70 did:0.00 not:0.00 cross:0.00 the:0.00 street:0.06
because:0.00 it:0.00 was:0.00 too:0.00 tired:0.00 ,:0.05 so:0.00 the:0.19
blk 2 the:0.00 animal:0.00 did:0.00 not:0.00 cross:0.00 the:0.00 street:0.00
because:0.00 it:0.00 was:0.00 too:0.00 tired:1.00 ,:0.00 so:0.00 the:0.00
the animal did not cross the street because it was too wet , so the street waited .
blk 1 the:0.00 animal:0.70 did:0.00 not:0.00 cross:0.00 the:0.00 street:0.06
because:0.00 it:0.00 was:0.00 too:0.00 wet:0.00 ,:0.05 so:0.00 the:0.19
blk 2 the:0.00 animal:0.00 did:0.00 not:0.00 cross:0.03 the:0.00 street:0.49
because:0.00 it:0.00 was:0.00 too:0.20 wet:0.03 ,:0.00 so:0.00 the:0.25Block 1 זהה בשני המשפטים — 0.70 על שם העצם הראשון, בלי קשר לשם התואר. זה לא כישלון אלא הוכחה: בשכבה הראשונה ה-query במיקום הוא פונקציה של ה-token והאינדקס של אותו מיקום עצמו, ו-the במיקום 14 הוא אותו token בשני המשפטים. head בשכבה ראשונה לא יכול להתנות במילה שעדיין לא שלף. לכן block 1 עושה את הדבר המועיל היחיד שזמין לו וגורר את שם העצם הראשון קדימה.
Block 2 הוא המקום שבו המשפטים מתפצלים, ואותה שורה על פני כל שמונת שמות התואר מראה את הכלל שהמודל מצא:
| שם תואר | block 2 על animal | על street | על שם התואר | תשובה |
|---|---|---|---|---|
| tired, scared, slow, weak | 0.000 | 0.000 | 1.000 | animal |
| wet, wide, busy, steep | 0.000 | 0.491 | 0.00–0.03 | street |
עבור שם תואר של חוצה, ה-block השני מבלה את כל משקלו על שם התואר, כי התשובה כבר נמצאת ב-residual stream — block 1 שם אותה שם — וכל מה שהוא צריך הוא אישור. עבור שם תואר של נחצה הוא הולך ושולף את שם העצם האחר במקום. זה מעגל דו-קפיצי: head אחד מזיז מועמד קדימה, head בשכבה מאוחרת יותר קורא token שמחליט אם לשמור אותו. קומפוזיציה על פני שכבות היא המנגנון, וזו הסיבה ש-block אחד הגיע ל-92.7 % ושניים הגיעו ל-100 %.
זו גם הצורה של המעגל המתועד ביותר במודלים אמיתיים. Induction heads — head של token קודם שמזין head בשכבה הבאה שמשלים את התבנית [A][B] … [A] → [B] — הם מה שעבודת הפרשנות של Anthropic מזהה מאחורי חלק גדול מ-in-context learning, והם נוצרים ברגע ניתן לזיהוי במהלך pretraining. הפרק הזה לא מנסה את הניתוח הזה: הוא מואצל, עם שני המאמרים במקורות, כי קריאת מעגלים מתוך מודל אמיתי היא תחום מחקר ולא סעיף.
ולבסוף, המימוש. שלושים השורות למעלה, עם משקליהן מועתקים מ-PyTorch עצמו:
ours vs nn.MultiheadAttention max |diff| = 1.7881393432617188e-07
ours vs F.scaled_dot_product_attention max |diff| = 1.7881393432617188e-07על פלטים שגודל הממוצע שלהם הוא 0.159: אותו חשבון בסדר אחר, בדיוק float32.
לאן זה הולך עכשיו
קישור למקטע: לאן זה הולך עכשיויש לכם את הארכיטקטורה שכל מודל בשאר הקורס בנוי ממנה, והיא קטנה מהמוניטין שלה: ממוצע משוקלל שמשקליו נלמדים, MLP פר-מיקום שמחזיק שני שלישים מהפרמטרים, שני נרמולים ושתי תוספות, בערימה.
מה שאין לכם הוא מודל שיודע משהו, ו-stacking לא יתקן זאת בפני עצמו. שני blocks על הקורפוס הזה מגיעים ל-training perplexity של 14.49 ול-validation perplexity של 40.57, מול 18.77 ו-38.07 של block אחד — יותר קיבולת, טוב יותר על מה שראה, גרוע יותר על מה שלא ראה, הטבלה של פרק 6 עם transformer בתוכה. המרחק בין המודל הזה לבין אלה שפרקים 14 עד 30 מדברים איתם אינו ארכיטקטוני. זה אותו block, חוזר יותר פעמים, על הרבה יותר טקסט.
מה שהופך את זה לבעיית חשבונאות, והחשבונאות מוזרה מכפי שהיא נראית. כמה טקסט, ומאיפה מישהו משיג אותו? כמה חישוב, ואיך מעריכים אותו לפני שהכסף הוצא? בהינתן תקציב קבוע, עדיף להגדיל את המודל או להראות לו יותר נתונים — והאם יש תשובה נכונה, או רק אופנה? פרק 10 עונה על שלוש השאלות במדידה, ומצמיד מחיר לצורה השימושית הזולה ביותר של השאלה: כמה עולה, היום, לאמן מודל כמו GPT-2 מאפס?
מקורות ושיטה
קישור למקטע: מקורות ושיטהשלושה הסברים של החומר הזה טובים מזה במה שהם מיועדים לו, והפרק הזה נכתב כדי להיקרא לצידם. The Illustrated Transformer של Jay Alammar הוא התמונה הטובה ביותר של זרימת הנתונים שצוירה אי פעם. The Annotated Transformer של Harvard NLP הוא המאמר מ-2017 עם קוד רץ משולב שורה אחר שורה. Let's build GPT: from scratch, in code, spelled out של Andrej Karpathy בונה את אותו מודל בשידור חי בשעתיים, וסולם ה-ablations למעלה הוא אותו שלד שנמדד על קורפוס אחר. לשאלת הפרשנות שהפרק הזה רק נוגע בה, המקורות הראשוניים הם Elhage et al., A Mathematical Framework for Transformer Circuits (2021) ו-Olsson et al., In-context Learning and Induction Heads (2022), שניהם מקבוצת הפרשנות של Anthropic.
הפניות
קישור למקטע: הפניות-
Hochreiter, S. and Schmidhuber, J. Long Short-Term Memory. Neural Computation 9(8), pp. 1735–1780 (1997). ↩
-
Sutskever, I., Vinyals, O. and Le, Q. V. Sequence to Sequence Learning with Neural Networks. arXiv:1409.3215 (2014). ה-encoder-decoder שבו וקטור context יחיד הוא צוואר הבקבוק. ↩
-
Bahdanau, D., Cho, K. and Bengio, Y. Neural Machine Translation by Jointly Learning to Align and Translate. arXiv:1409.0473 (2014). Attention, שלוש שנים לפני ה-transformer. ↩
-
Perplexity היא האקספוננט של ה-cross-entropy הממוצע לכל token, מפרק 8. כל מספר כאן משתמש באותו tokenizer ובאותו validation split, וזה התנאי היחיד שבו בכלל מותר להשוות שתי perplexities. ↩
-
Vaswani, A., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L., Gomez, A. N., Kaiser, Ł. and Polosukhin, I. Attention Is All You Need. arXiv:1706.03762 (2017). סעיף 3.2.1 הוא המשפט האחד על שהפרק הזה מקדיש סעיף למדידתו. ↩
-
Shazeer, N., Mirhoseini, A., Maziarz, K., Davis, A., Le, Q., Hinton, G. and Dean, J. Outrageously Large Neural Networks: The Sparsely-Gated Mixture-of-Experts Layer. arXiv:1701.06538 (2017). ↩
-
Ba, J. L., Kiros, J. R. and Hinton, G. E. Layer Normalization. arXiv:1607.06450 (2016). הוצג ונמדד בפרק 6; משמש כאן ללא שינוי. ↩
-
Xiong, R., Yang, Y., He, D., Zheng, K., Zheng, S., Xing, C., Zhang, H., Lan, Y., Wang, L. and Liu, T.-Y. On Layer Normalization in the Transformer Architecture. arXiv:2002.04745 (2020). ניתוח ה-gradient שמאחורי pre-norm, והטיעון ש-warmup הוא תסמין. ↩
-
Su, J., Lu, Y., Pan, S., Murtadha, A., Wen, B. and Liu, Y. RoFormer: Enhanced Transformer with Rotary Position Embedding. arXiv:2104.09864 (2021). ↩
-
Press, O., Smith, N. A. and Lewis, M. Train Short, Test Long: Attention with Linear Biases Enables Input Length Extrapolation. arXiv:2108.12409 (2021). תוצאת האקסטרפולציה ששוחזרה למעלה. ↩
-
Dao, T., Fu, D. Y., Ermon, S., Rudra, A. and Ré, C. FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness. arXiv:2205.14135 (2022). ↩
-
Shazeer, N. Fast Transformer Decoding: One Write-Head is All You Need. arXiv:1911.02150 (2019). ↩
-
Ainslie, J., Lee-Thorp, J., de Jong, M., Zemlyanskiy, Y., Lebrón, F. and Sanghai, S. GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints. arXiv:2305.13245 (2023). ↩
-
Levesque, H. J., Davis, E. and Morgenstern, L. The Winograd Schema Challenge. KR (2012). הבנייה שמאחורי משפט ה-animal / street שכל מדריך attention משתמש בו. ↩