Attention و بلوک transformer، از دلِ یک میانگین
از سادهترین خلاصهٔ context شروع کنید: میانگین؛ ببینید کجا شکست میخورد و فرمول attention را از تعمیر آن به دست آورید.
در این صفحه
شما با یک tokenizer از فصل 7، یک جدول embedding از فصل 8، و objective همراه آنها به اینجا میرسید: با داشتن tokenهای تا اینجا، روی token بعدی یک احتمال بگذارید.
چیزی که کم است میانهٔ کار است. برای پیشبینی token ، model به یک بردار نیاز دارد که همهچیز پیش از آن را خلاصه کند، و هیچکدام از چیزهایی که ساختهاید چنین برداری تولید نمیکند. embedding مربوط به token آن نیست — آن یک bigram model است و نمیتواند بداند جمله با یک پرسش شروع شده است. الحاق همهٔ embeddingهای قبلی هم آن نیست: تعدادشان در هر گام تغییر میکند، و یک ماتریس وزن ثابت نمیتواند ورودی با طول متغیر بگیرد.
پس: یک بردار با اندازهٔ ثابت، که تعداد متغیری از بردارها را خلاصه میکند. کل مسئله همین است، و attention چیزی است که وقتی این مسئله را به تنبلانهترین شکل ممکن حل کنید و بعد دو چیزی را که میشکند تعمیر کنید، به دست میآورید.
پاسخی که این حوزه داشت، و چرا ما آن را نمیسازیم
لینک به بخش: پاسخی که این حوزه داشت، و چرا ما آن را نمیسازیماز 1997 تا حدود 2017، خلاصه یک حالت بازگشتی بود: برداری را نگه دارید و در هر token آن را بهروزرسانی کنید، . اندازهٔ ثابت، ورودی متغیر، دقیقاً شکل درست.
این روش از سه جهت شکست خورد، و معماری این فصل به هر سه پاسخ میدهد. backpropagation از میان گام، ژاکوبین را در هم ضرب میکند، پس gradient محو یا منفجر میشود — همان بیماریای که فصل 5 داخل یک گرهٔ اندازه گرفت. LSTM1 دقیقاً برای مقابله با همین طراحی شد و بازهٔ قابل استفاده را از دهها گام به صدها رساند، بیآنکه این واقعیت را تغییر دهد که اطلاعات token 5 فقط با دوام آوردن در 495 بهروزرسانی ترتیبی به token 500 میرسد. کل منبع باید در یک بردار جا میشد: در ترجمهٔ sequence-to-sequence2، یک encoder ورودی را در حالت نهاییاش فشرده میکند. Bahdanau، Cho و Bengio این گلوگاه را نامگذاری کردند و در 2014، سه سال پیش از transformer، با این کار آن را رفع کردند که decoder بتواند از همهٔ حالتهای encoder یک جمع وزندار بگیرد، با وزنهایی که خودش محاسبه میکرد.3 همهٔ آنچه در ادامه میآید همان ایده است، اعمالشده توسط یک دنباله به خودش، با حذف بازگشت. و بهروزرسانی ذاتاً ترتیبی است: به نیاز دارد، و یک GPU با دههزار هسته نمیتواند کاری با آن بکند. معماری برنده لزوماً باهوشتر نیست؛ همان معماریای است که گام پرهزینهاش ضرب ماتریسی است.
سوگیری استقرایی کلاسیک دیگر، convolution — یک فیلتر کوچک را روی کل ورودی بلغزانید، تا ویژگیای که هرجا تشخیص داده شد همهجا تشخیص داده شود — اینجا هم ساخته نمیشود؛ برای تصویرها تقریباً دقیقاً درست است و به درس بینایی سپرده میشود. نه recurrence و نه convolution پس از این صفحه دوباره ظاهر نمیشوند، و به همین دلیل هیچکدام فصل ندارند: فصل 1 قول داده بود حذفها اعلام شوند، نه اینکه بیسروصدا بگذرند.
ارزانترین خلاصهای که وجود دارد
لینک به بخش: ارزانترین خلاصهای که وجود داردبدیهیترین تابعی که تعداد متغیری از بردارها را میگیرد و یک بردار برمیگرداند، میانگین است:
هر تعداد ورودی، اندازهٔ خروجی ثابت، مشتقپذیر، رایگان. جدول embedding بهعلاوهٔ این میانگین بهعلاوهٔ یک لایهٔ خطی به vocabulary، در پانزده خط یک language model کامل است. و در عین حال وحشتناک است؛ و اینکه چگونه وحشتناک است، کل استنتاج را میسازد.
corpus زیر یک مگابایت از Shakespeare است، 1,115,394 کاراکتر، از طریق یک tokenizer نوع byte-level BPE از همان جنسی که در فصل 7 ساخته شد، با vocabulary برابر 1024: در مجموع 459,760 token با میانگین 2.43 کاراکتر برای هرکدام، با split 90/10. همهٔ modelها پهنای 128 دارند، 128 token میبینند، و برای 3000 گام AdamW با و batch برابر 64 آموزش میبینند. Perplexity روی split نگهداشتهشده است.4
| model | parameters | validation perplexity |
|---|---|---|
| فقط token فعلی، بدون هیچ context | 263,168 | 59.71 |
| بهعلاوهٔ میانگین یکنواختِ همهچیز پیش از آن | 263,168 | 248.07 |
| بهعلاوهٔ position embeddingهای آموختهشده | 279,552 | 245.93 |
| میانگین یکنواخت اضافهشده به token بهجای جایگزینکردن آن | 263,168 | 60.45 |
سطر دوم را دوبار بخوانید. میانگینگرفتن از context کمی کمک نمیکند؛ model را چهار برابر بدتر از نادیدهگرفتن کامل context میکند. دو دلیل دارد، و هر دو قابل اثباتاند نه صرفاً تجربی.
میانگین order را نمیبیند. جمع جابهجاییپذیر است، پس shuffle کردن window خلاصه را بیتغییر میگذارد — نه تقریباً:
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نویز floating-point روی جمعِ بازچیدهشده: دو خلاصه همان بردارند. modelای که تنها نگاهش به context یک میانگین است، نمیتواند the dog bit the man را از the man bit the dog تشخیص دهد. سطر سوم ثابت میکند این با افزودن position به ورودیها درست نمیشود — یک position embedding آموختهشده روی هر token پیش از میانگینگیری فقط 2.14 امتیاز از 188 امتیاز را پس گرفت. positionها وارد جمع میشوند، و جمع آنها را فراموش میکند.
و میانگین، حال را غرق میکند. در position 100، token فعلی یکصدمِ خلاصه است. این یک تعمیر ارزان دارد که از قبل دارید: token را نگه دارید و خلاصه را به آن اضافه کنید — یک اتصال residual، از فصل 6، و سطر چهارم نشان میدهد چه میکند. با تعمیر رقیقشدن، میانگین یکنواخت هیچ چیزی اضافه نمیکند: 60.45 در برابر baseline برابر 59.71. هر token آنجاست، با وزن برابر، و وزندهی برابر همان بیاطلاعی است.
مسئله خودِ میانگینگیری نیست. مسئله وزنها هستند.
میانگین یک ضرب ماتریسی است، و mask یک softmax است
لینک به بخش: میانگین یک ضرب ماتریسی است، و mask یک softmax استمیانگینگیری روی prefix روبهرشد شبیه یک loop است. اما یک ضرب در یک ماتریس پایینمثلثی است که جمع سطرهایش یک میشود — و همچنین، دقیقاً، یک 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 است، که objective آن را تحمیل میکند: اگر position میتوانست position را ببیند، پاسخ داخل ورودی بود — همان نشتیای که فصل 6 به شما گفت audit کنید، فقط این بار داخل معماری. softmax روش پیادهسازی mask است: گذاشتن در ورودیهای ممنوع آنها را دقیقاً به صفر میفرستد و آنچه میماند را نرمال میکند، پس masking و normalizing یک عملیاتاند. (از استفاده کنید، نه -1e9: این همان مقداری است که masking معنا میدهد، در cast به float16 بهصورت دوام میآورد، و شما را از تصمیمگیری دربارهٔ اینکه ثابت انتخابیتان برای بازهای که اتفاقاً در آن هستید بهاندازهٔ کافی بزرگ هست یا نه نجات میدهد — یعنی همان جعبهٔ floating-point در فصل 2 که سوالی میپرسد که لازم نیست جواب دهید.) و scoreها پارامتر آزادند. میانگین یکنواخت چیزی است که وقتی همهٔ scoreهای مجاز یک عدد یکسان باشند به دست میآورید؛ هر عددی آنجا بگذارید و softmax آنها را به وزنهای معتبر تبدیل میکند.
بقیهٔ این فصل یک سوال است: این عددها از کجا میآیند؟
Query, key, value
لینک به بخش: Query, key, valueنمیتوانند پارامترهای ساده باشند. یک ماتریس آموختهشدهٔ برای هر جمله یکسان میبود — میتوانست «چهار token قبل را نگاه کن» را encode کند، اما هرگز «به اسمی نگاه کن که این ضمیر به آن اشاره دارد» را. وزنی که position را به position وصل میکند باید به آنچه در هر دو position است وابسته باشد، چون ارتباط یک رابطه است، نه یک ویژگی: واژهٔ it ذاتاً relevant نیست، نسبت به چیزی relevant است.
ارزانترین تابعی که دو بردار میگیرد و یک عدد برمیگرداند، dot product فصل 1 است. position را برای position بهصورت score کنید و سازوکار کار میکند — بد کار میکند، به دو شکل که بقیهٔ اجزا را تحمیل میکنند. dot product یک بردار با خودش برابر نرمِ بهتواندو آن است، پس هر token عمدتاً به خودش attention میدهد. و رابطه متقارن خواهد بود: اگر it با قدرت به animal attention بدهد، آنگاه animal هم با قدرت به it attention میدهد، که دربارهٔ زبان نادرست است؛ یک صفت بسیار بیشتر به اسمش نیاز دارد تا اسم به صفت.
پس به هر token دو نقش بدهید، بهصورت دو نگاشت خطیِ آموختهشده از آن: اینکه این position دنبال چه میگردد، ، یعنی query؛ و اینکه چه چیزی عرضه میکند تا با آن پیدا شود، ، یعنی key. امتیاز را بگیرید و تقارن از بین میرود، چون : یک token میتواند چیزی را تبلیغ کند و دنبال چیز دیگری بگردد.
یک چیز هنوز غلط است. جمع وزندار روی خودِ ها بود، که اجبار میکند چیزی که کپی میشود همان چیزی باشد که match میشود. Matching ویژگیهایی را میخواهد که یک token را شناسایی میکنند؛ copying ویژگیهایی را میخواهد که downstream مفیدند. پس نگاشت سومی بیاموزید، ، یعنی value، و آنها را جمع کنید.
فرمول حالا حسابداری است:
که در آن causal mask است، صفر روی قطر و زیر آن و بالای آن. در code سی خط است، که بیست خطش shapeها هستند:
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، normalise، mix. بقیه projection است.
تقسیم بر ریشهٔ دوم، و چیزی که در برابرش دفاع میکند
لینک به بخش: تقسیم بر ریشهٔ دوم، و چیزی که در برابرش دفاع میکندتقریباً هر توضیحی دربارهٔ میگوید «برای اینکه softmax اشباع نشود»، که درست است و هیچچیز را توضیح نمیدهد. استدلال دو خط از واریانسِ فصل 2 است. اگر ورودیهای و مستقل، با میانگین صفر و واریانس یک باشند، هر حاصلضرب واریانس یک دارد، و واریانسِ چیزهای مستقل جمع میشود:
پس scoreها انحراف معیار دارند. اندازهگیری روی بیستهزار جفت تصادفی:
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 نسبت به scale حساس است، به شکلی که یک لایهٔ خطی نیست. دو برابر کردن ورودی یک لایهٔ خطی خروجیاش را دو برابر میکند؛ ضربکردن scoreها در ده پیش از softmax یک ترکیب نرم را به یک انتخاب سخت تبدیل میکند. یک سطر از 64 score، با تقسیم و بدون تقسیم:
| بزرگترین وزن، تقسیمنشده | entropy | tokenهای مؤثر | بزرگترین وزن، تقسیمشده | entropy | tokenهای مؤثر | |
|---|---|---|---|---|---|---|
| 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 |
«tokenهای مؤثر» نماییِ entropy است: اینکه سطر واقعاً روی چند position میانگین میگیرد. بدون تقسیم، در ، یک head تازه initialised دقیقاً به یک token از 64 attention میدهد، آن هم با انتخابی که هیچچیز جز نمونهگیری تصادفی تعیینش نکرده است.
این در مسیر forward بد است و در backward بدتر، با شکلی که فصل 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 % |
در ، هفت سطر از ده سطر پیش از شروع آموزش frozen شدهاند، و headای که frozen شروع کند نمیتواند یاد بگیرد به چه چیزی نگاه کند. با تقسیم، این کمیت در همهٔ پهناها روی 0.96 صاف میماند و هیچچیز اشباع نمیشود.
حالا بخشی که هیچکس منتشر نمیکند: آیا perplexity نهایی را تغییر میدهد؟ تقسیم را حذف کنید و در چهار پهنای head آموزش دهید:
| پهنای head | تقسیمنشده | تقسیم بر | تقسیم بر |
|---|---|---|---|
| چهار head، | 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، بدون normalization پیش از projectionها — با هر دو variant در تنظیمات یکسان.
در تقسیم هیچ ارزشی ندارد و اجرای بدون آن اندکی جلوتر است. این مجوزی برای حذفش نیست، چون در 256 بهاندازهٔ 18 امتیاز perplexity ارزش دارد و در 1024 بهاندازهٔ 17. مکانیزم در خود scoreها پیداست:
| انحراف معیار score در init | پس از 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 تقسیمنشده recover نمیکند. فرار میکند: انحراف معیار scoreهایش از 21 در initialisation به 5147 میرسد، entropy مربوط به attention به صفر میافتد، و 99.9 % سطرها بیش از 0.99 وزن خود را روی یک token میگذارند. وقتی یک head به selector سخت تبدیل شود، gradient آن تقریباً صفر است و هیچچیز آن را عقب نمیکشد، پس collapse پایدار است. head تقسیمشده پس از همان آموزش روی انحراف معیار score برابر 3.44 مینشیند، یعنی ترکیبی نرم که هنوز میتوان تغییرش داد.
Vaswani و همکاران دقیقاً همین را میگویند و نه بیشتر — آنها گمان میکنند حاصلضربها برای مقادیر بزرگ «از نظر magnitude بزرگ میشوند» و تقسیم میکنند.5 واژهٔ بزرگ بار اصلی را به دوش میکشد، و جدولها نشان میدهند بزرگی از کجا شروع میشود: هیچچیز در 32، همهچیز تا 256.
بیش از یک نظر، و دو سومی که کسی دربارهاش حرف نمیزند
لینک به بخش: بیش از یک نظر، و دو سومی که کسی دربارهاش حرف نمیزندیک head یعنی یک سطر softmax برای هر position، پس یک پاسخ به «اینجا چه چیزی relevant است» دارد. پیشبینی واژهٔ بعد از the در the animal that crossed the wet street همزمان به جایگاه نحوی، subject و token قبلی نیاز دارد، و یک توزیع احتمال نمیتواند در سه جا متمرکز باشد. پس چند head را موازی اجرا کنید، هرکدام با پهنای ، concatenate کنید، و با یک ماتریس دیگر mix کنید: پهنا را بخشبندی کردهاید، نه اینکه به آن اضافه کرده باشید.
Attention همچنین دقیقاً یک کار میکند — اطلاعات را بین positionها جابهجا میکند. هر عملیات در code بالا در امتداد محور feature خطی است، و فصل 5 ثابت کرد پشتهای از نگاشتهای خطی چیست. پس هر block یک MLP کوچک هم دارد که روی هر position مستقل اعمال میشود، پهنا را چهار برابر میکند و برمیگرداند، با یک GELU در میانه. تقسیم کار ارزش حفظکردن دارد: attention بین positionها mix میکند، شبکهٔ feed-forward داخل یک position محاسبه میکند.
نردبان کامل، هر سطر یک قطعه به سطر بالایی اضافه میکند:
| model | parameters | validation perplexity |
|---|---|---|
| میانگین یکنواخت، اضافهشده | 279,552 | 60.45 |
| یک attention head، جایگزین token | 328,704 | 55.47 |
| یک attention head، اضافهشده | 328,704 | 46.10 |
| چهار head بهجای یکی | 345,216 | 43.21 |
| بهعلاوهٔ شبکهٔ feed-forward | 476,928 | 39.87 |
| بهعلاوهٔ LayerNorm — block کامل | 477,696 | 38.07 |
وزنهای آموختهشده 14 امتیاز perplexity بهتر از وزنهای یکنواختاند، که کل استدلال این فصل در یک سطر است. چهار head با 16,512 پارامتر اضافی، 3 امتیاز دیگر میخرد. و همان head وقتی اضافه شود 9 امتیاز بیشتر از وقتی جایگزین شود ارزش دارد: attention اطلاعات را وارد میکند، تصمیم نمیگیرد یک position چیست.
حالا پارامترها واقعاً کجا نشستهاند؛ چیزی که برای کسانی که فقط diagram را دیدهاند غافلگیرکننده است:
| پهنا | headها | 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 است، در هر scale، چون attention چهار ماتریس دارد و MLP معادل هشتتا. هرچه یک model بداند، بیشتر پارامترهایی که آن را نگه میدارند در MLP per-position هستند.
Residualها و LayerNorm، به ارث رسیده از فصل 6
لینک به بخش: Residualها و LayerNorm، به ارث رسیده از فصل 6LayerNorm در فصل 6 ساخته و اندازهگیری شد، و این فصل همانطور که آنجا باقی ماند از آن استفاده میکند؛ اتصالهای residual آنجا نامگذاری و ablate شدند، و اینجا ساخته میشوند. سطرهای «اضافهشده، نه جایگزین» بالا اتصالهای residual هستند، که برای میانگین 188 امتیاز perplexity و برای یک head 9 امتیاز ارزش دارند. LayerNorm7 هر example را در سراسر featureهایش normalise میکند، و فصل 6 دلیلهایی را داد که چرا این و نه BatchNorm اینجا دوام آورد — بیوابستگی به batch، نبود آمار running، یکسان بودن در training و inference، بیتفاوتی به طول sequence — که هرکدام وقتی یک token در هر لحظه برای یک user تولید میکنید به یک requirement تبدیل میشود؛ همانجایی که فصل 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به محل normalisation نگاه کنید: روی ورودی هر sub-layer، با مسیر residual از ورودی تا خروجی که هرگز normalise نمیشود. این pre-norm است. مقالهٔ 2017 برعکس عمل میکند، x = LayerNorm(x + Att(x)) — یعنی post-norm، که LayerNorm را روی خود مسیر residual میگذارد.
Xiong و همکاران تفاوت را از طریق gradient در initialisation توضیح دادند، که در شبکهٔ post-norm با عمق بهبدی scale میشود — همان دلیلی که transformer اصلی اصلاً برای آموزش به warmup در learning rate نیاز داشت.8 دوازده block، 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 اینجا یک practice خوب عمومی نیست؛ وصلهای برای چیدمان خاصی از normalisation است، و جابهجا کردن LayerNorm نیاز به آن را حذف میکند. به همین دلیل تقریباً هر model از 2019 به بعد pre-norm است، و به همین دلیل diagram سال 2017 باید بهعنوان تاریخ خوانده شود، نه specification.
یک token کجاست؟
لینک به بخش: یک token کجاست؟position embeddingها را حذف کنید و model همچنان train میشود؛ فقط نمیتواند بفهمد چیزی کجاست، و این یک تقارن است نه شکست آموزش. هیچچیز در score مربوط به attention خودِ یا را ذکر نمیکند، پس permute کردن ورودی خروجی را permute میکند: self-attention permutation-equivariant است. این همان نابیناییِ میانگین نسبت به order است در لباسی بهتر — causal mask مقداری order را بازمیگرداند، چون هر position prefix متفاوتی میبیند، اما درون یک prefix همهٔ orderها یکساناند.
چهار راه برای تزریق position، آموزشدیده روی windowهای 64-token و ارزیابیشده در 64، 128 و 256 — فراتر از هر طولی که دیده بودند:
| positionها | perplexity در 64 | در 128 | در 256 |
|---|---|---|---|
| هیچکدام | 48.79 | 52.63 | 57.52 |
| embeddingهای absolute آموختهشده | 38.63 | 108.47 | 181.94 |
| sinusoidهای ثابت | 42.96 | 95.26 | 152.25 |
| RoPE | 44.12 | 50.52 | 84.84 |
| ALiBi | 44.95 | 43.51 | 42.49 |
embeddingهای absolute آموختهشده — یک بردار برای هر position، افزودهشده به token — در طول آموزشدیده برنده میشوند و بعد از پرتگاه میافتند، چون position 100 هرگز در batch نبوده و embedding آن هنوز همان بردار تصادفیِ شروع کار است. Sinusoidها، انتخاب اولیه، بهجای آموختهشدن محاسبه میشوند، از سینوس و کسینوس با فرکانسهای هندسی فاصلهگذاریشده؛ مقالهٔ 2017 امیدوار بود این extrapolate کند، و جدول میگوید نمیکند — تابع در position 200 تعریف شده، اما model هرگز یاد نگرفته آن را آنجا بخواند. RoPE9 چیزی اضافه نمیکند و در عوض query و key را با زاویهای متناسب با position، در برشهای دوبعدی میچرخاند؛ چون چرخاندن هر دو طرف یک dot product به یک اندازه آن را بیتغییر میگذارد، score در نهایت فقط به وابسته میشود، پس position رایگان relative میشود و جدولی وجود ندارد که تمام شود. افت میکند، اما افتکردنش تدریجی است. ALiBi10 سادهترین و عجیبترین نتیجهٔ اینجاست: یک جریمهٔ خطی روی score متناسب با فاصله، با شیب متفاوت برای هر head. Perplexity آن وقتی window از طول آموزشدیده میگذرد بهتر میشود، از 44.95 به 42.49، چون جریمه در هر فاصلهای تعریف شده و هر head همان کاری را ادامه میدهد که برایش آموزش دیده بود.
درس، بیشتر از جدول دوام میآورد: معماریای که نمیتواند چیزی را بازنمایی کند مسئلهای متفاوت از معماریای است که هرگز آن بازه را یاد نگرفته، و دومی همان چیزی است که گاز میگیرد. این همچنین machinery پشت هر اعلامیهٔ «context را به 128K گسترش دادیم» است — آنها تقریباً همیشه rescalingهای یک encoding چرخشیاند، و به همین دلیل فصل 16 میگوید محدودیت context جابهجا میشود، نه اینکه ناپدید شود.
Dropout هم به همان شکل به ارث میرسد: روی وزنهای attention پس از softmax، روی خروجی هر sub-layer پیش از جمع residual، و روی جمع embedding ظاهر میشود، و دقیقاً همان کاری را میکند که فصل 6 توصیف کرد. در pretrainingهای بزرگ اغلب روی صفر تنظیم میشود، چون modelای که هر token را یکبار میبیند در موقعیتی نیست که overfit کند.
هزینهاش چیست
لینک به بخش: هزینهاش چیستدو tensor در لایه شکل دارند، که در آن تعداد tokenهاست: scoreها و وزنهای پس از softmax. همهٔ چیزهای دیگر — هر projection، کل MLP — نسبت به خطی است.
یک لایهٔ attention، پهنای 512، هشت head، batch یک، float32، روی GPU لپتاپ. دو ستون میلیثانیه را فقط برای نسبتهایشان بخوانید: زمان wall clock روی کارت لپتاپ 8 GB است که وقتی داغ میشود از 1,785 MHz به زیر 300 MHz throttling میکند، پس یک اجرای سرد از همین code هفت تا ده برابر سریعتر برمیگردد و اجرای مشغول هنوز کندتر. ستونهای مگابایت شمارش byteهای 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 نسبت به سطر بالایی هستند، و دو برابر کردن برای زمان و memory دقیقاً به 4 همگرا میشود — 3.91 در گام آخر در برابر 4 نظری. ستون projectionها کنترل است: از 4.0 ms در 1024 token تا 40.1 ms در 8192، ضریب ده برای ضریب هشت. خطی، همانطور که وعده داده شد.
بعد سطر آخر. یک لایهٔ attention، یک sequence، بدون هیچ modelی اطراف آن، روی GPU هشتگیگابایتی در 16,384 token از memory خارج میشود — فقط ماتریس score برابر 8 GB خواهد بود، یعنی 8 head ضربدر 16,384 ضربدر 16,384 ضربدر 4 byte. نه model؛ یک tensor میانی در یک لایه.
این واقعیت فیزیکی زیرِ سه فصل بعدی است. دلیل این است که context window اصلاً limit دارد، چیزی که فصل 16 آن را به price تبدیل میکند. دلیل وجود FlashAttention است، که همان نتیجه را در tileها محاسبه میکند بیآنکه هرگز ماتریس را ذخیره کند — پیش از آنکه بهینهسازی سرعت باشد، بهینهسازی memory است.11 و arithmetic پشت قیمت یک prompt طولانی است، که فصل 24 آن را در یک loop مربوط به agent میپردازد — مسئلهای جدا از یافتهٔ دیگر آن فصل، یعنی اینکه model همچنین از context طولانی بدتر استفاده میکند، که آن را اندازه میگیرد و سرزنشش را به گردن این فرمول نمیاندازد.
نمایش جزئیات
دو variant کوچککنندهٔ cache، اینجا نامگذاریشده و در فصل 13 هزینهشان پرداخت میشود.
Generation کلیدها و مقدارهای tokenهای قبلاً پردازششده را cache میکند — یک key و یک value برای هر token، برای هر head در هر layer. Multi-query attention12 تعداد projection مربوط به query را نگه میدارد، اما یک projection یکتای key و value را میان همهٔ headها مشترک میکند، و آن cache را بر تقسیم میکند. Grouped-query attention13 میان اینها interpolates میکند: headها گروهبندی میشوند، هر گروه یک key و value مشترک دارد، پس attention معمولی است و multi-query. تقریباً هر open model از 2023 به بعد از آن با 4 یا 8 گروه استفاده میکند. هیچکدام برای quality وجود ندارد؛ هر دو برای اندازهٔ آن cache وجود دارند، و فصل 13 حسابی را انجام میدهد که آن را به «کدام model در GPU شما جا میشود» تبدیل میکند.
دو شکل، و اندازهٔ یکی
لینک به بخش: دو شکل، و اندازهٔ یکیمقالهٔ 2017 یک encoder-decoder را توصیف میکند: یک stack که منبع را با attention بدون mask میخواند، دومی که هدف را causally تولید میکند، و نوع سومی از attention در میانه که queryهای decoder با keyهای encoder روبهرو میشوند. این برای ترجمه درست است، جایی که input و output دو sequence هستند.
چیزی که برنده شد نیمهٔ decoder-only بود — یک stack، سراسر causal، input و output در همان sequence — و دلیلش elegance نیست. «token بعدی را پیشبینی کن» روی هر متنی اجرا میشود، پس training set اینترنت است نه corpus موازی، و همهچیز همان یک task میشود: ترجمه سندی است که source و سپس target را در خود دارد، پرسش و پاسخش یک سندند، گفتوگویی با tool call در میانه یک سند است. فصل 11 دربارهٔ این است که آخری چگونه ساخته میشود. Encoderها ناپدید نشدند — یکی کل input را یکجا میبیند، که وقتی کار بازنمایی یک متن است نه ادامهدادنش، همان چیزی است که میخواهید، و به همین دلیل embeddingهای retrieval در فصل 19 از encoderها میآیند نه از modelای که chat میکند.
با تعریف block، اندازهٔ model حساب است. برای هر block، با پهنای و expansion چهاربرابر: برای با bias روی هر چهار، همانطور که GPT-2 دارد — جدول بالا bias را از سهتای آنها حذف میکند، بنابراین در برای هر block تعداد 2,304 کمتر دارد؛ برای MLP؛ برای دو LayerNorm — ، بهعلاوهٔ جدول token برابر و، برای positionهای absolute، . برای شکل GPT-2 small — ، 12 block، vocabulary برابر 50,257، context برابر 1024، و output layer که وزنهای embedding را share میکند:
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که همان اندازهٔ منتشرشدهٔ آن model است. فرمول تقریب نیست؛ خود model است. همچنین توجه کنید که تقریباً یکسوم یک model کوچک جدول embedding است، و به همین دلیل اندازهٔ vocabulary یک تصمیم معماری است نه یک تصمیم preprocessing — همان trade-offای که فصل 7 چید.
یک head واقعاً به چه چیزی نگاه میکند
لینک به بخش: یک head واقعاً به چه چیزی نگاه میکندPerplexity عددی دربارهٔ یک corpus است. اینکه یک head چه میکند سوال دیگری است، و modelای که روی یک مگابایت Shakespeare آموزش دیده ابزار غلطی برای آن است: حرف صادقانه دربارهٔ attention map یک model با 500,000 پارامتر این است که عمدتاً قابل تفسیر نیست. پس: زبانی که در آن پرسش پاسخ درست دارد.
تصویر کلاسیک the animal did not cross the street because it was too tired است، که در آن it همان animal است، در برابر …because it was too wet، که یک واژه مرجع را به street منتقل میکند. اینها Winograd schemaها هستند14 — جفتجملههایی که جز در یک واژه یکساناند، و همان واژه تعیین میکند ضمیر به چه چیزی اشاره دارد.
آنها همچنین با تقلب حلشدنیاند، و این همان بخشی است که tutorialها از قلم میاندازند. اگر دو candidate یکی animal و دیگری place باشند، tired و wet مرجع را بر اساس category مشخص میکنند، و modelای که فقط میداند کدام واژهها حاضرند، بیآنکه چیزی دربارهٔ order بداند، درست جواب میدهد. اندازهگیری روی آن نسخهٔ task، با جفتهای animal/place نگهداشتهشده:
uniform causal average held-out referent accuracy 100.0 %
one transformer block held-out referent accuracy 91.7 %Bag of words از transformer بهتر است. هر نمایشی که روی آن جمله ساخته شود هیچچیز دربارهٔ attention ثابت نمیکند.
پس شکاف را ببندید: هر دو candidate را از یک pool شانزدهتایی اسمها بکشید، که هرکدام میتوانند در هر slot ظاهر شوند، و adjectiveها را نه بر اساس category بلکه بر اساس role split کنید — چهار مورد که it را crosser میکنند (tired, scared, slow, weak)، چهار مورد که آن را crossed میکنند (wet, wide, busy, steep).
the {x} did not cross the {y} because it was too {adj} , so the {ref} waited .مثل یک predictor معمولیِ next-token آموزش دهید، یک position را score کنید — واژهٔ پس از so the — و held-out set را از جفتاسمهایی بسازید که order معکوس آنها در training بوده، پس هرچیزی که بداند کدام دو اسم حاضرند اما نداند کدام اول آمده، باید برعکس جواب دهد.
| model | parameters | held-out | اسم دیگر را نام میبرد |
|---|---|---|---|
| فقط token فعلی | 5,796 | 5.2 % | 5.2 % |
| میانگین causal یکنواخت | 5,796 | 27.9 % | 50.0 % |
| یک head از attention آموختهشده | 18,084 | 35.4 % | 64.6 % |
| چهار head | 22,244 | 75.0 % | 15.6 % |
| یک transformer block | 55,716 | 92.7 % | 4.2 % |
| دو transformer block | 105,508 | 100.0 % | 0.0 % |
شانس میان دو اسم حاضر 50 % است. میانگین یکنواخت روی 27.9 % مینشیند و دقیقاً نصف مواقع با اسم غلطِ آن جفت جواب میدهد — امضای چیزی که میداند کدام واژهها آنجا هستند و هیچچیز دربارهٔ orderشان نمیداند، همانطور که test مربوط به shuffle سه بخش پیش پیشبینی کرد.
حالا map: attention در positionای که باید مرجع را نام ببرد، میانگینگرفتهشده روی چهار head هر block، برای دو جملهای که با یک واژه فرق دارند. میانگین یکنواخت روی هرکدام از پانزده token قابل مشاهده 0.067 میگذاشت.
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 روی اسم اول، هرچه adjective باشد. این شکست نیست بلکه اثبات است: در لایهٔ اول، query در یک position تابع token و index خودِ همان position است، و the در position 14 در هر دو جمله همان token است. head لایهٔ اول نمیتواند به واژهای condition شود که هنوز fetch نکرده است. پس block 1 تنها کار مفید در دسترس را انجام میدهد و اسم اول را به جلو میکشد.
Block 2 جایی است که جملهها از هم جدا میشوند، و همان سطر در همهٔ هشت adjective نشان میدهد model چه ruleای پیدا کرده است:
| adjective | block 2 روی animal | روی street | روی adjective | پاسخ |
|---|---|---|---|---|
| tired, scared, slow, weak | 0.000 | 0.000 | 1.000 | animal |
| wet, wide, busy, steep | 0.000 | 0.491 | 0.00–0.03 | street |
برای یک crosser-adjective، block دوم تمام وزنش را روی adjective میگذارد، چون پاسخ از قبل در residual stream است — block 1 آن را آنجا گذاشته — و تنها چیزی که لازم دارد confirmation است. برای یک crossed-adjective، میرود و اسم دیگر را fetch میکند. این یک مدار دو-hop است: یک head یک candidate را جلو میآورد، headای در لایهٔ بعدی tokenی را میخواند که تصمیم میگیرد آن را نگه دارد یا نه. Composition در میان لایهها مکانیزم است، و به همین دلیل یک block به 92.7 % و دو block به 100 % رسیدند.
این همچنین شکل بهترین مدار مستندسازیشده در modelهای واقعی است. Induction headها — یک previous-token head که به headای در لایهٔ بعدی feed میدهد تا pattern [A][B] … [A] → [B] را کامل کند — همان چیزیاند که کار interpretability در Anthropic پشت بخش بزرگی از in-context learning شناسایی میکند، و در لحظهای قابل شناسایی طی pretraining شکل میگیرند. این فصل آن تحلیل را انجام نمیدهد: با هر دو مقاله در references واگذار میشود، چون بیرونکشیدن circuitها از یک model واقعی یک میدان پژوهشی است نه یک بخش.
در نهایت، implementation. همان سی خط بالا، با weightهایی که از خود PyTorch کپی شدهاند:
ours vs nn.MultiheadAttention max |diff| = 1.7881393432617188e-07
ours vs F.scaled_dot_product_attention max |diff| = 1.7881393432617188e-07روی خروجیهایی که میانگین magnitudeشان 0.159 است: همان arithmetic در order متفاوت، با دقت float32.
این از اینجا به کجا میرود
لینک به بخش: این از اینجا به کجا میرودشما معماریای را دارید که هر model در بقیهٔ این دوره بر پایهٔ آن ساخته میشود، و از شهرتش کوچکتر است: یک میانگین وزندار که وزنهایش آموخته میشوند، یک MLP per-position که دو سوم پارامترها را نگه میدارد، دو normalisation و دو addition، stack شده.
چیزی که ندارید modelای است که چیزی بداند، و stack کردن بهتنهایی آن را درست نمیکند. دو block روی این corpus به training perplexity برابر 14.49 و validation perplexity برابر 40.57 میرسند، در برابر 18.77 و 38.07 برای یک block — ظرفیت بیشتر، بهتر روی آنچه دیده، بدتر روی آنچه ندیده، یعنی همان جدول فصل 6 با یک transformer داخل آن. فاصلهٔ این model با modelهایی که فصلهای 14 تا 30 با آنها حرف میزنند معماری نیست. همان block است، که دفعات بیشتری تکرار شده، روی متن بسیار بسیار بیشتر.
پس مسئله به حسابداری تبدیل میشود، و حسابداری عجیبتر از چیزی است که به نظر میرسد. چقدر متن، و اصلاً کسی از کجا آن را میآورد؟ چقدر arithmetic، و چگونه آن را پیش از خرجشدن پول estimate میکنید؟ با یک بودجهٔ ثابت، بهتر است model را بزرگتر کنید یا دادهٔ بیشتری به آن نشان دهید — و آیا پاسخ درستی وجود دارد، یا فقط مد؟ فصل 10 هر سه را با اندازهگیری پاسخ میدهد، و روی ارزانترین شکل مفیدِ پرسش قیمت میگذارد: امروز آموزش modelای مثل GPT-2 از صفر چقدر هزینه دارد؟
منابع و روش
لینک به بخش: منابع و روشسه توضیح از این material در کاری که برایش نوشته شدهاند بهتر از این یکیاند، و این فصل برای خواندن در کنار آنها نوشته شده است. The Illustrated Transformer از Jay Alammar بهترین تصویر از data flow است که تا امروز کشیده شده. The Annotated Transformer از Harvard NLP مقالهٔ 2017 است با code اجرایی که خطبهخط میان متن آمده. Let's build GPT: from scratch, in code, spelled out از Andrej Karpathy همان model را در دو ساعت، زنده و از صفر میسازد، و نردبان ablationهای بالا همان ستون فقرات است که روی corpus دیگری اندازهگیری شده. برای پرسش interpretability که این فصل فقط لمسش میکند، منابع اصلی Elhage و همکاران، A Mathematical Framework for Transformer Circuits (2021) و Olsson و همکاران، In-context Learning and Induction Heads (2022) هستند، هر دو از گروه interpretability در 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 استفاده میکنند، که تنها شرطی است که تحت آن اصلاً میتوان دو perplexity را با هم مقایسه کرد. ↩
-
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 را یک symptom میداند. ↩
-
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). نتیجهٔ extrapolation که بالا بازتولید شد. ↩
-
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 که هر tutorial مربوط به attention از آن استفاده میکند. ↩