فاز 19 · درس 34

پیاده‌سازی بلوک ترنسفورمر از صفر در PyTorch

نوع
ساختنی
زمان
حدود 90 دقیقه
زبان‌ها
Python
طرح شماتیک مدولار از اتصال دو بلوک مکعبی دقیق همراه با مسیرهای انشعابی بای‌پس و گیت‌های متوالی
فهرست مطالب
  1. اهداف یادگیری
  2. صورت مسئله
  3. مفاهیم پایه
  4. اتنشن چندسر علّی
  5. لایه‌ی MLP
  6. نقش دوگانه‌ی اتصالات باقیمانده
  7. پیاده‌سازی
  8. ابزارها
  9. الگوهای بهینه‌سازی در محیط عملیاتی
  10. کاربردها
  11. تمرین‌ها
  12. اصطلاحات کلیدی
  13. منابع برای مطالعه‌ی بیشتر

بلوک ترنسفورمر (Transformer Block) کوچک‌ترین واحد ساختاری مدل‌های زبانی امروزی نظیر GPT و LLaMA است. در نگاه اول، سرهم‌کردن لایه‌های اتنشن، شبکه‌ی پیش‌خور و نرمال‌سازی ساده به نظر می‌رسد؛ اما کوچک‌ترین جابه‌جایی در ترتیب لایه‌ها، مثل جایگاه نرمال‌سازی لایه‌ای (LayerNorm) یا نحوه‌ی اعمال ماسک علّی (Causal Mask)، می‌تواند پایداری مدل را در لایه‌های عمیق به‌کلی از بین ببرد و فرایند آموزش را به واگرایی بکشاند.

در این درس یاد می‌گیرید که چطور یک بلوک ترنسفورمر استاندارد را بر پایه‌ی PyTorch و بدون تکیه بر کتابخانه‌های آماده از صفر بسازید. هم‌چنین دو معماری پرکاربرد Pre-LN و Post-LN را در یک پشته‌ی 12 لایه‌ای روبه‌روی هم قرار می‌دهیم تا به‌صورت تجربی ببینید چرا مدل‌های مدرن همگی به سمت Pre-LN رفته‌اند و چگونه جریان گرادیان در این معماری بدون نیاز به تنظیمات پیچیده‌ی پیش‌گرمایش (Warmup) پایدار می‌ماند.

کدها و محاسبات این پیاده‌سازی کاملاً متکی بر پکیج‌های پایه‌ای PyTorch هستند و به‌راحتی روی پردازنده‌های معمولی (CPU) یا شتاب‌دهنده‌های سبک شخصی اجرا می‌شوند و نیازی به سرورها یا GPUهای پرهزینه‌ی ابری نخواهید داشت.

هر بلوک، واحد سازنده‌ی تمام مدل‌های زبانی بزرگ مبتنی بر رمزگشایی (Decoder LLM) امروزی است: نرمال‌سازی لایه‌ای (LayerNorm)، اتنشن چندسر (Multi-Head Attention)، اتصال باقیمانده (Residual Connection)، شبکه‌ی MLP و اتصال باقیمانده‌ی مجدد. نسخه‌ی Pre-LN بدون نیاز به وارم‌آپ (پیش‌گرمایش) با پایداری آموزش می‌بیند. نسخه‌ی Post-LN همان ساختاری است که در مقاله‌ی اصلی ترنسفورمر ارائه شد. در این درس هر دو نسخه را شانه‌به‌شانه می‌سازیم و بررسی می‌کنیم کدام‌یک در یک پشته‌ی 12 لایه‌ای با نرخ‌های یادگیری معمول دوام می‌آورد.

اهداف یادگیری

  • ساخت یک بلوک ترنسفورمر در PyTorch با استفاده از چهار جزء متحرک: لایه‌ی LayerNorm، اتنشن چندسر علّی (Causal Multi-Head Attention)، اتصالات باقیمانده و MLP مستقل از موقعیت (Position-wise MLP).
  • چیدمان لایه‌های LayerNorm در دو پیکربندی مختلف (Pre-LN و Post-LN) و توضیح این‌که چرا یکی از آن‌ها بدون وارم‌آپ با پایداری آموزش می‌بیند.
  • پیاده‌سازی ماسک علّی (Causal Mask) درون اتنشن چندسر، به‌طوری که توکن i نتواند توکن‌های j > i را ببیند.
  • ردگیری جریان گرادیان در هر دو نسخه روی یک پشته‌ی 12 لایه‌ای و خواندن نتایج بدون ابهام یا ساده‌انگاری.
  • استفاده‌ی مجدد از این بلوک به‌عنوان یک واحد آماده (Drop-in) برای درس بعد، جایی که یک مدل GPT با 124 میلیون پارامتر سرهم می‌شود.

صورت مسئله

یک ترنسفورمر چیزی نیست جز تکرار یک بلوک یکسان. اگر این بلوک را حتی یک بار اشتباه پیاده کنید و دوازده بار تکرارش کنید، مدلی تحویل می‌دهید که در همان دور (Epoch) اول واگرا می‌شود یا مجبور می‌شوید در ادامه‌ی مسیر دست به دامن ترفندهای وارم‌آپ شوید. دو حالت شکستی (Failure modes) که در این درس می‌بینید اصلاً عجیب و غریب نیستند؛ این‌ها اولین خطاهایی هستند که هر تازه‌کاری هنگام چیدن ناآگاهانه‌ی بلوک‌ها روی هم با آن‌ها روبه‌رو می‌شود. یکی از آن‌ها توجه لایه‌ی اتنشن به آینده است، و دیگری قرار گرفتن لایه‌ی LayerNorm در نقطه‌ای که نمی‌تواند سیگنال پسماند را در لایه‌های عمیق مهار کند.

وقتی اصل قضیه را ببینید، چاره‌اش کاملاً مکانیکی و سرراست است. بلوک دقیقاً دو مسیر باقیمانده و دقیقاً دو جایگاه برای نرمال‌سازی دارد. اگر این جایگاه‌ها را درست انتخاب کنید، بقیه‌ی کار پشته فقط حساب‌وکتاب داده‌ها خواهد بود.

مفاهیم پایه

هر بلوک ترنسفورمر صرفاً رمزگشا (Decoder-only)، تابعی است که تنسوری با ابعاد (batch, sequence, embedding) می‌گیرد و تنسوری با همان ابعاد تحویل می‌دهد. درون این بلوک، دو زیرلایه کار اصلی را پیش می‌برند.

امبدینگ ورودیشکل B, T, Dنرمال‌سازی لایه 1اتنشن علیتی چند سرافزودن باقی‌ماندهنرمال‌سازی لایه 2MLP موقعیت‌محورD به 4D به Dافزودن باقی‌ماندهخروجی، با همان شکل

این نسخه‌ی Pre-LN است. لایه‌ی LayerNorm داخل شاخه‌ی (branch) باقیمانده و قبل از زیرلایه می‌نشیند. اتصال باقیمانده، سیگنال نرمال‌نشده را مستقیم به جلو منتقل می‌کند.

در نسخه‌ی Post-LN، لایه‌ی LayerNorm به بعد از جمع باقیمانده منتقل می‌شود.

ورودیاتنشن علیتی چند سرافزودن باقی‌ماندهنرمال‌سازی لایه 1MLP موقعیت‌محورافزودن باقی‌ماندهنرمال‌سازی لایه 2خروجی

ابعاد خروجی کاملاً یکسان است، اما رفتار هنگام آموزش خیر. در Post-LN، گرادیانی که در مسیر باقیمانده به عقب برمی‌گردد باید از لایه‌ی LayerNorm عبور کند. در عمق دوازده لایه و با نرخ یادگیری 3e-4، این گرادیان آن‌قدر سریع تضعیف می‌شود که چاره‌ای جز استفاده از برنامه‌ی زمان‌بندی وارم‌آپ باقی نمی‌ماند. اما Pre-LN مسیر باقیمانده را بدون نرمال‌سازی رها می‌کند؛ در نتیجه گرادیان‌ها تر‌وتمیز به لایه‌ی امبدینگ می‌رسند. دقیقاً به همین دلیل است که از GPT-2 به بعد، همه‌ی مدل‌ها با ساختار Pre-LN عرضه می‌شوند.

اتنشن چندسر علّی

زیرلایه‌ی اتنشن، ورودی را به سه شکل روی تنسورهای پرسش (Query)، کلید (Key) و مقدار (Value) تصویر می‌کند (Projection). هر کدام از این تنسورها از شکل (B, T, D) به شکل (B, H, T, D/H) تغییر بعد می‌دهند که در آن H تعداد سرها است. اتنشن ضرب داخلی مقیاس‌شده (Scaled Dot-Product Attention) مقدار softmax(Q K^T / sqrt(d_k)) را برای هر سر محاسبه می‌کند، مثلث بالایی را با بی‌نهایت منفی ماسک می‌زند، ماسک را از طریق سافت‌مکس اعمال می‌کند و سپس حاصل را در V ضرب می‌کند. سرها دوباره در قالب یک تنسور واحد (B, T, D) به هم متصل می‌شوند و یک بار دیگر تصویر می‌شوند. این ماسک تنها قطعه‌ای است که مدل را علّی نگه می‌دارد. اگر ماسک را فراموش کنید، مدلی آموزش می‌دهید که تقلب می‌کند.

لایه‌ی MLP

لایه‌ی MLP مستقل از موقعیت، یک شبکه‌ی دولایه‌ای یکسان را به‌طور مستقل روی تک‌تک توکن‌ها اعمال می‌کند. عرض لایه‌ی پنهان چهار برابر عرض لایه‌ی امبدینگ است، تابع فعال‌سازی GELU بوده و بعد از لایه‌ی خطی دوم یک دراپ‌اوت قرار می‌گیرد. هیچ توکنی درون MLP با توکن دیگر تبادل اطلاعات نمی‌کند؛ همه‌ی آمیختگی توکن‌ها در اتنشن اتفاق می‌افتد.

نقش دوگانه‌ی اتصالات باقیمانده

اتصالات باقیمانده مسیر گرادیان را در طول عمق شبکه جمع‌پذیر (Additive) می‌کنند، که باعث می‌شود نرم گرادیان در تمام دوازده لایه در مقیاس درستی حفظ شود. از طرفی به هر بلوک این امکان را می‌دهند که به‌جای بازنویسی کامل بازنمایی فعلی، یک به‌روزرسانی جمع‌پذیر به آن اضافه کند. هر دوی این ویژگی‌ها دلایل اصلی مقیاس‌پذیری بلوک هستند.

بلاک ترنسفورمردو مسیر میان‌بر باقیمانده
TRANSFORMER BLOCKThe block has exactly two sublayers and two residual paths. The input forks: one copy flows through LayerNorm then attention, the other skips straight to the add. The same fork repeats around the MLP. The clean bypass is what lets gradients reach the bottom of a deep stack, and placing the norm inside the bypass (pre-LN) is why the stack trains without a warmup crutch.ورودی (B, T, D)LayerNorm 1اتنشنعلّی · چندسرجمع باقیماندهLayerNorm 2MLP D→4D→Dباقیماندهخروجی (B, T, D)pre-LN:نرمال‌سازیدرون میان‌براست، پسمسیر باقیماندهتا عمق، پاکباقی می‌ماند
این بلاک دقیقاً دو زیرلایه و دو مسیر باقیمانده دارد. ورودی fork می‌شود: یک نسخه از LayerNorm و سپس اتنشن عبور می‌کند، نسخه‌ی دیگر مستقیماً به جمع جهش می‌کند. همین fork دور MLP تکرار می‌شود. میان‌بر دست‌نخورده همان چیزی است که به گرادیان‌ها امکان می‌دهد به انتهای یک استک عمیق برسند، و قرار دادن نرمال‌سازی درون میان‌بر (pre-LN) علتی است که استک بدون نیاز به تکیه‌گاه وارم‌آپ (پیش‌گرمایش) آموزش می‌بیند.

پیاده‌سازی

فایل code/main.py موارد زیر را پیاده‌سازی می‌کند:

  • کلاس LayerNorm همراه با مقیاس و جابه‌جایی یادگرفتنی، پارامتر eps اریب، که روی بردار هر توکن اعمال می‌شود.
  • کلاس MultiHeadAttention با پارامترهای num_heads، بعد سرها به‌صورت head_dim = d_model // num_heads، تصویرسازی ادغام‌شده‌ی QKV، بافر ثبت‌شده‌ی ماسک علّی، و دراپ‌اوت برای اتنشن و اتصال باقیمانده.
  • کلاس FeedForward با دو لایه‌ی خطی، تابع فعال‌سازی GELU و دراپ‌اوت.
  • کلاس TransformerBlock همراه با پرچم pre_ln برای جابه‌جایی بین دو نسخه.
  • یک دمو که دو پشته‌ی 6 لایه‌ای، یکی Pre-LN و دیگری Post-LN، با ورودی‌های کاملاً یکسان می‌سازد و موارد زیر را چاپ می‌کند: (الف) ابعاد خروجی، (ب) نرم گرادیان در لایه‌ی امبدینگ پس از یک گام رو به عقب.

اجرای کد:

python3 code/main.py

خروجی: بررسی ابعاد در هر دو پشته و مقایسه‌ی شانه‌به‌شانه‌ی نرم گرادیان‌ها. نرم گرادیان امبدینگ در پشته‌ی Pre-LN یک مرتبه بزرگی (Order of magnitude) بیشتر از پشته‌ی Post-LN در همان نرخ یادگیری است؛ این همان نشانه‌ی تجربی است که ثابت می‌کند Pre-LN بدون نیاز به وارم‌آپ آموزش می‌بیند.

ابزارها

  • پکیج torch برای محاسبات تنسور، مشتق‌گیری خودکار (Autograd) و اتصالات لایه‌های nn.Module.
  • بدون transformers و بدون وزن‌های از پیش‌آموزش‌دیده؛ این بلوک کاملاً از اجزای سازنده (Primitives) پیاده‌سازی شده است.

الگوهای بهینه‌سازی در محیط عملیاتی

سه الگو وجود دارد که بلوک متنی کتاب‌ها را به چیزی تبدیل می‌کند که واقعاً بشود در محیط عملیاتی (پروداکشن) استفاده کرد.

تصویرسازی ادغام‌شده‌ی QKV (Fused QKV projection). سه لایه‌ی خطی جداگانه به معنای سه بار اجرای کرنل (Kernel launch) و سه ضرب ماتریسی است. یک لایه‌ی خطی با عرض 3 * d_model همین کار را در یک‌بار اجرای کرنل انجام می‌دهد و بعد خروجی را در امتداد بعد آخر می‌شکند. مسیر ادغام‌شده روی تمام شتاب‌دهنده‌ها سریع‌تر است و با آن‌چه پیاده‌سازی‌های مرجع GPT-2، LLaMA و Mistral استفاده می‌کنند مطابقت دارد.

بافر ثبت‌شده برای ماسک علّی. این ماسک فقط به حداکثر طول زمینه (Context length) وابسته است. کافی است آن را یک بار هنگام ساخت شیء با register_buffer تخصیص دهید، پنجره‌ی فعال را در هر گذر رو به جلو برش بزنید و از تخصیص حافظه‌ی تکراری در هر فراخوانی پرهیز کنید. فراموش کردن این نکته، ماسک را در طول زمینه‌های طولانی به یک گلوگاه تخصیص حافظه تبدیل می‌کند.

دراپ‌اوت در دو نقطه، نه سه نقطه. دراپ‌اوت باید بعد از سافت‌مکس اتنشن (دراپ‌اوت اتنشن) و بعد از لایه‌ی خطی دوم MLP (دراپ‌اوت باقیمانده) بنشیند. قرار دادن دراپ‌اوت روی خود اتصال باقیمانده، خاصیت همانیِ جمع‌پذیری را که ضامن عبور گرادیان در لایه‌های عمیق است از بین می‌برد. برخی پیاده‌سازی‌های اولیه این اشتباه را مرتکب شدند و تاوانش را با آموزش‌های شکننده و ناپایدار پس دادند.

کاربردها

  • بلوک این درس مستقیماً و بدون تغییر در سرهم‌بندی GPT در درس 35 قرار می‌گیرد.
  • نسخه‌ی Pre-LN همان ساختاری است که تمام مدل‌های متن‌باز امروزی به کار می‌برند. نسخه‌ی Post-LN ساختار مقاله‌ی اتنشن سال 2017 است. تسلط بر هر دو نسخه برای فهم هر معماری مبتنی بر رمزگشایی که با آن مواجه می‌شوید کافی است.
  • اگر GELU را با SiLU عوض کنید، به تابع فعال‌سازی خانواده‌ی LLaMA می‌رسید. اگر LayerNorm را با RMSNorm جایگزین کنید، ساختار نرمال‌سازی خانواده‌ی LLaMA را خواهید داشت؛ اسکلت اصلی یکی است.

تمرین‌ها

  1. پرچم bias=False را به تمام لایه‌های خطی بلوک اضافه کنید. مدل‌های زبانی متن‌باز امروزی لایه‌های خطی را بدون بایاس به کار می‌برند. حساب کنید در یک مدل 12 لایه‌ای با بعد 768، چه تعداد پارامتر صرفه‌جویی می‌شود.
  2. لایه‌ی nn.LayerNorm را با یک لایه‌ی RMSNorm دست‌نویس جایگزین کنید و مطمئن شوید که ابعاد خروجی دست‌نخورده باقی می‌ماند.
  3. پرچمی اضافه کنید که وزن‌های اتنشن را برای سر اول به‌صورت تنسور (B, T, T) برگرداند. مثلث بالایی آن را رسم کنید تا مطمئن شوید بعد از سافت‌مکس مقدارش صفر است.
  4. یک آزمون اعتبارسنجی بسازید که تنسوری با ابعاد (2, 16, 384) و H=6 را به هر دو نسخه بفرستد و بررسی کند که وقتی وزن‌ها کاملاً یکسان مقداردهی اولیه شده‌اند و دراپ‌اوت صفر است، خروجی‌های گذر رو به جلو با هم تفاوت دارند (مثلاً با not torch.allclose).

اصطلاحات کلیدی

اصطلاح در گفتار رایج معنای فنی و دقیق
Pre-LN «پری‌نرم» لایه‌ی LayerNorm داخل شاخه‌ی باقیمانده و قبل از هر زیرلایه قرار دارد؛ اتصال باقیمانده سیگنال نرمال‌نشده را منتقل می‌کند
Post-LN «پست‌نرم» لایه‌ی LayerNorm بعد از جمع باقیمانده قرار می‌گیرد؛ ساختاری که در مقاله‌ی 2017 عرضه شد و به وارم‌آپ نیاز دارد
Causal mask «ماسک مثلثی» تبدیل مثلث بالایی لاجیت‌های اتنشن به بی‌نهایت منفی تا توکن i نتواند توکن j را وقتی j بزرگتر از i است بخواند
Fused QKV «تصویرسازی ترکیبی» استفاده از یک لایه‌ی خطی با عرض 3D به‌جای سه لایه‌ی خطی با عرض D؛ یک کرنل، یک ضرب ماتریسی
Residual stream «اتصال جهشی» تنسور نرمال‌نشده‌ای که از بالا تا پایین درون تمام بلوک‌ها جریان دارد؛ مقداری که هر بلوک سهم خود را به آن اضافه می‌کند

منابع برای مطالعه‌ی بیشتر

  • بخش 7، درس 02 (خوداتنشنی از صفر) برای درک ریاضیات اتنشنِ زیربنای این بلوک.
  • بخش 7، درس 05 (ترنسفورمر کامل) برای مشاهده‌ی نسخه‌ی رمزگذار-رمزگشا (Encoder-Decoder) از همین ساختار.
  • بخش 10، درس 04 (پیش‌آموزش مینی‌جی‌پی‌تی) برای یادگیری فرایند آموزشی که این بلوک درون آن قرار می‌گیرد.
  • بخش 19، درس 35 (همین مسیر آموزشی) که دوازده تا از این بلوک‌ها را در قالب یک مدل GPT سرهم می‌کند.

خودآزمایی

به سؤال‌ها جواب بدهید تا ببینید چقدر از درس در ذهنتان مانده است.

  1. پیش از درسکدام دو زیرلایه درون یک بلاک رمزگشایی (Decode) ترنسفورمر قرار دارند؟
  2. حین درستفاوت کلیدی بین pre-LN و post-LN در عمق زیاد چیست؟
  3. حین درسماسک علّی در اتنشن چندسر چه کاری انجام می‌دهد؟
  4. حین درسچرا به جای سه لایه خطی جداگانه از یک تصویر کردن (Projection) فیوزشده‌ی QKV استفاده می‌شود؟
  5. پس از درسدراپ‌اوت نباید در کدام بخش از یک بلاک ترنسفورمر قرار گیرد؟
  6. پس از درسچرا ماسک علّی به‌جای تخصیص حافظه در هر بار فراخوانی forward، به عنوان یک بافر ثبت می‌شود؟

سؤالات متداول

تفاوت اصلی معماری Pre-LN و Post-LN در بلوک ترنسفورمر چیست؟

در ساختار Pre-LN، نرمال‌سازی لایه‌ای قبل از زیرلایه‌های اتنشن و MLP و داخل شاخه‌ی باقیمانده قرار می‌گیرد تا سیگنال جمع‌شونده بدون دستکاری جلو برود؛ اما در Post-LN نرمال‌سازی بعد از جمع باقیمانده اعمال می‌شود که در شبکه‌های عمیق گرادیان را تضعیف کرده و به وارم‌آپ نیاز دارد.

چرا در اتنشن چندسر از تصویرسازی ادغام‌شده (Fused QKV) استفاده می‌شود؟

استفاده از یک لایه‌ی خطی با عرض 3D به‌جای سه لایه‌ی جداگانه با عرض D باعث می‌شود محاسبات ضرب ماتریسی در قالب یک‌بار اجرای کرنل انجام شود که سرعت اجرا روی تمام شتاب‌دهنده‌ها را بهینه‌تر می‌کند.

وظیفه ماسک علّی (Causal Mask) در بلوک ترنسفورمر دیکودر چیست؟

این ماسک با تبدیل مقادیر مثلث بالایی ماتریس اتنشن به بی‌نهایت منفی مانع از این می‌شود که هر توکن بتواند به اطلاعات توکن‌های بعدی یا آینده دسترسی پیدا کند.

ما رو توی گوگل بیشتر ببین

با افزودن نکست ادمین به «منابع ترجیحی» گوگل، درس‌ها و مطالب ما را در بخش اخبار برتر جستجو بیشتر می‌بینید.