پرش به محتوای اصلی
پرش به محتوای مقاله

ساخت مدل GPT-2 از صفر با زبان C و CUDA بدون استفاده از کتابخانه‌های ML

·۸ تیر ۱۴۰۵۸ دقیقه مطالعه
مدل زبانی GPT-2 از صفر در C/CUDA با پس‌انتشار دستی، توکنایزر BPE، FlashAttention، پیش‌آموزش و تنظیم دقیق نظارت‌شده.
مدل زبانی GPT-2 از صفر در C/CUDA با پس‌انتشار دستی، توکنایزر BPE، FlashAttention، پیش‌آموزش و تنظیم دقیق نظارت‌شده.
اشتراک‌گذاری
واقعاً چه چیز جدید است؟

پیاده‌سازی کامل یک خط لوله Training و SFT برای GPT-2 صرفاً با C و CUDA، بدون استفاده از هیچ کتابخانه ML یا Autograd، که تمام مراحل ریاضی را به صورت دست‌نویس و شفاف ارائه می‌دهد.

تصور کنید تمام لایه‌های پنهان مدل‌های زبانی مدرن را کنار بزنید تا با موتور واقعی آن‌ها رو‌به‌رو شوید. اگر برنامه‌نویسی هستید که می‌خواهد بداند در قلب یک ترنسفورمر دقیقاً چه می‌گذرد، پروژه nanoeuler همان نقشه‌ی راهی است که به شما نیاز به کتابخانه‌های حجیم را می‌گیرد. در حالی که یادگیری ماشین مدرن تقریباً به‌طور کامل بر چارچوب‌های سنگین (Frameworks) متکی است، nanoeuler یک کدبیس مدل زبانی در کلاس GPT-2 است که کاملاً این وابستگی‌ها را دور می‌زند. با نوشتن دستی مسیرهای پیش‌رو (Forward Pass) و پس‌رو (Backward Pass) در زبان‌های C و CUDA، این پروژه ثابت می‌کند که یک خط لوله کامل برای پیش‌آموزش و تنظیم دقیق (Fine-tuning) می‌تواند بدون PyTorch یا سیستم‌های اتوماتیک مشتق‌گیری (Autograd) وجود داشته باشد. این انتشار که در ۲۸ ژوئن ۲۰۲۶ در گیت‌هاب مستند شده است، نگاهی شفاف به اجزای مکانیکی معماری ترنسفورمر ارائه می‌دهد.

بسیاری از متخصصان امروز مدل‌های هوش مصنوعی را مانند یک «جعبه سیاه» می‌بینند و نمی‌دانند گرادیان‌ها چگونه در شبکه جریان می‌یابند. این ابهام می‌تواند مخاطراتی ایجاد کند؛ همان‌طور که در تحلیل قبلی ما درباره‌ی خطرات نشت داده‌های محرمانه در چت‌بات‌های عمومی اشاره کردیم، عدم درک مکانیسم داخلی مدل‌ها، ریسک‌های امنیتی را افزایش می‌دهد. پروژه nanoeuler با حذف تمام لایه‌های انتزاعی، فرآیند آموزش را به یک اثر مهندسی ملموس تبدیل کرده است.

هسته مهندسی

به نقل از مستندات این پروژه، سیستم بر پایه معماری ترنسفورمر (Transformer) — ساختاری شبیه به یک سیستم بایگانی هوشمند که تصمیم می‌گیرد کدام بخش از اطلاعات در هر لحظه مهم‌تر است — و به‌صورت «فقط رمزگشا» (Decoder-only) طراحی شده است. برای حفظ کارایی بدون کمک کتابخانه‌های خارجی، از چندین بلوک ساختاری مدرن استفاده شده است:

  • RMSNorm: این لایه به عنوان یک پیش-نرمال‌ساز (Pre-norm) و بدون استفاده از بایاس (Bias) به‌کار رفته است تا پایداری عددی مدل حفظ شود.
  • رمزگذاری موقعیت چرخشی (RoPE): این متد روی پرس‌وجوها (Queries) و کلیدها (Keys) اعمال می‌شود تا مدل آگاهی بهتری از جایگاه نسبی توکن‌ها در متن داشته باشد.
  • SwiGLU Feed-Forward: معماری لایه‌های پیش‌رو از ساختار down(silu(gate(x)) * up(x)) استفاده می‌کند که ترکیبی از توابع فعال‌ساز غیرخطی برای پردازش پیچیده‌تر داده‌هاست.
  • توجه پرس‌وجوی گروه‌بندی‌شده (GQA): در این ساختار، سرهای پرس‌وجو مجموعه‌ی کوچک‌تری از سرهای کلید/مقدار (KV heads) را به اشتراک می‌گذارند تا مصرف حافظه بهینه‌ شود.
  • پیش‌بینی چندتوکنی (MTP): در اینجا K سرِ خروجی، K توکن بعدی را پیش‌بینی می‌کنند. این مکانیسم باعث بهبود نمایش‌های یادگرفته شده می‌شود و امکان استفاده از رمزگشایی گمانه‌زنانه (Speculative Decoding) را فراهم می‌کند. لازم به ذکر است که در مرحله تولید متن (Generation)، تنها از سر شماره ۰ استفاده می‌شود.

جزئیات توکن‌سازی

در بخش توکن‌سازی (Tokenization) — که مانند خرد کردن یک متن طولانی به تکه‌های کوچک برای بلعیدن راحت‌تر توسط مدل است — یک توکنایزر BPE سطح-بایت (Byte-level BPE) به‌صورت دست‌نویس تعبیه شده است. این سیستم از سبک پیش-توکن‌سازی GPT-2 استفاده می‌کند؛ به این معنا که یک فضای خالی (Space) پیش‌رو به کلمه بعدی متصل می‌شود تا فضاهای خالی به عنوان توکن‌های مستقل هدر نروند.

عمل ادغام توکن‌ها (Merges) روی نمونه‌ای از پیکره متنی (Corpus) یاد گرفته می‌شود. برای مدل مبتنی بر GPU، این فرآیند منجر به ایجاد یک واژگان (Vocabulary) با ۴۰۹۶ توکن می‌شود. در بررسی متون انگلیسی، این پیکربندی به کارایی تقریبی ۳.۴ بایت به ازای هر توکن (Token) دست می‌یابد.

سخت‌افزار و عملکرد

بر اساس بررسی فنی، این پروژه دو پیکربندی متمایز را متناسب با محیط‌های سخت‌افزاری مختلف ارائه می‌دهد:

۱. مدل نمایشی کوچک (Small Showcase Model): این مدل شامل حدود ۷۶۰ هزار پارامتر است (در نسخه CPU این مقدار به ۱.۰۵ میلیون پارامتر می‌رسد). این نسخه از بُعد ۱۲۸، ۴ سر پرس‌وجو، ۲ سر KV، ۴ لایه و یک پنجره متنی (Context Window) ۱۲۸ توکنی با واژگان ۵۱۲ توکنی استفاده می‌کند. این مدل روی پردازنده‌های مرکزی (CPU) با استفاده از libm و OpenMP اجرا می‌شود و آموزش آن روی ۱۲ هسته پردازشی تنها چند ساعت زمان می‌برد.

۲. خط لوله GPU: مدلی با ۱۱۶ میلیون پارامتر که به‌طور خاص برای یک کارت گرافیک Nvidia RTX 4070 طراحی شده است. این مدل دارای بُعد ۷۶۸، ۱۲ سر پرس‌وجو، ۴ سر KV، ۱۶ لایه و پنجره متنی ۵۱۲ توکنی با واژگان ۴۰۹۶ توکنی است. اندازه هر سر (Head size) برابر با ۶۴ (حاصل ۷۶۸ تقسیم بر ۱۲) است که دقیقاً برای سازگاری با هسته FlashAttention طراحی شده است.

برای رسیدن به سرعت‌های عملیاتی، نویسنده یک هسته توجه برق‌آسا (FlashAttention) دست‌نویس پیاده کرده است. این رویکرد که بر اساس کاشی‌بندی (Tiling) و softmax آنلاین است، از ذخیره ماتریس کامل T×T در حافظه جلوگیری می‌کند و در نتیجه سرعت آموزش را تقریباً ۳ برابر افزایش می‌دهد. عملیات ضرب ماتریسی نیز با استفاده از TF32 tensor cores به cuBLAS واگذار شده است.

خط لوله آموزش

مسیر آموزش در یک فرآیند سنتی دو مرحله‌ای طی می‌شود. ابتدا مدل پایه در مرحله پیش‌آموزش (Pretraining) روی ترکیبی از کتاب‌ها و داده‌های وب با استفاده از دستور nanoeuler_cuda t آموزش می‌بیند. برای ادامه آموزش از نقاط ذخیره شده (Checkpoints) که هر ۵۰۰۰ گام یک‌بار ذخیره می‌شوند، از دستور tr استفاده می‌شود.

منابع داده‌ها:

  • کتاب‌ها: اسکریپت get_gutenberg.sh حدود ۹۵ اثر کلاسیک در مالکیت عمومی، شامل آثار جین Austen، دیکنز، داستایوفسکی، تولستوی، ملویل و مجموعه کامل شکسپیر را دانلود می‌کند. سیستم به‌طور خودکار سربرگ‌ها و پانویس‌های لایسنس Project Gutenberg را حذف کرده و فقط متون بین نشانگرهای *** START و *** END را نگه می‌دارد.
  • وب: اسکریپت get_web.sh یک برش باکیفیت آموزشی از FineWeb-Edu را استخراج می‌کند. این کار از طریق CLI مربوط به DuckDB انجام شده تا وابستگی به پایتون کاملاً حذف شود و در حالت پیش‌فرض یک پیکره متنی حدود ۱ گیگابایتی ایجاد شود.

پس از اتمام پیش‌آموزش، مدل وارد مرحله تنظیم نظارت‌شده (SFT) می‌شود — مشابه وقتی که به یک پزشک عمومی تخصص پوست می‌دهیم تا در یک حوزه دقیق شود. با استفاده از مجموعه داده‌های دستورالبی Alpaca، آموزش‌دهنده (nanoeuler_cuda s) مقدار Loss را برای موقعیت‌های مربوط به پرامپت و Padding ماسک می‌کند (با اختصاص مقدار -۱). هسته cross-entropy این مقدار را به گرادیان صفر تبدیل می‌کند. این تضمین می‌کند که مدل به‌طور خاص یاد بگیرد چگونه «پاسخ» تولید کند، نه اینکه خودِ «پرامپت» را بازتولید نماید. در این راستا، انتخاب میان کنترل‌های دستی و تنظیمات آماده همیشه یک چالش است؛ برای مثال در بررسی تنظیمات JSON در مقابل کدنویسی دستی در AutoFit2، تأثیر این انتخاب‌ها بر دقت مدل‌های طبقه‌بندی متن بررسی شده است.

نتیجه، مدلی است که فرمت «دستور-پاسخ» را دنبال می‌کند و تولید متن را در نشانگر پایان مناسب </s> متوقف می‌سازد. حالت چت تعاملی (nanoeuler_cuda c) ورودی کاربر را در همان قالب دستورالبی که در SFT استفاده شده، قرار می‌دهد.

تاییدیه و یکپارچگی

از آنجایی که پیاده‌سازی دستی پس‌انتشار (Backpropagation) مستعد خطاهای ظریف است، nanoeuler شامل یک بررسی سخت‌گیرانه گرادیان است. هر گرادیان تحلیلی با یک تفاضل مرکزی محدود (Central Finite Difference) مقایسه می‌شود. برای جلوگیری از حذف اثرات اعشاری (Floating-point cancellation) که ممکن است خطاها را پنهان کند، این بررسی در دقت مضاعف (Double Precision) اجرا می‌شود.

نتایج تجربی حداکثر خطاهای نسبی زیر را نشان می‌دهند:

  • tok: 1.02e-04
  • qkvw: 7.20e-07
  • gatew: 6.86e-08

این نتایج تایید می‌کند که مسیر پس‌رو در تمام پارامترها، از جمله مسیرهای پیچیده برای RoPE، SwiGLU، GQA و MTP، از نظر ریاضی صحیح است (خطا کمتر از 1e-2). برای موتور GPU، هسته‌ها در برابر یک مرجع CPU در دستگاه تست شده‌اند و گرادیان‌های GPU با گرادیان‌های CPU تا دقت ~1e-6 مطابقت دارند.

فلسفه «اویلر»

نام این پروژه از یک تفسیر ریاضی خاص از بلوک‌های باقی‌مانده (Residual Blocks) گرفته شده است. در nanoeuler، یک اتصال باقی‌مانده مانند x = x + f(x) به عنوان یک گام واحد از روش «اویلر پیشرو» برای حل معادلات دیفرانسیل معمولی (ODEs) دیده می‌شود.

به‌طور مشخص، روش اویلر پیشرو یک معادله ODE را به صورت dx/dt = f(x) و با گام x(t+Δt) = x(t) + Δt · f(x(t)) پیش می‌برد. با اندازه گام Δt = 1 و جایگذاری در فرمول، دقیقاً همان به‌روزرسانی باقی‌مانده در شبکه‌های عصبی به دست می‌آید. این دیدگاه، عمق مدل را با زمان انتگرال‌گیری همسو می‌کند و اساساً شبکه باقی‌مانده عمیق را به عنوان یک ODE گسسته‌سازی شده در نظر می‌گیرد. این رویکرد بازتابی از تئوری Neural ODEs است که در آن یک ResNet، گسسته‌سازی اویلر از یک جریان پیوسته است. این پروژه به افتخار لئونارد اویلر، ریاضی‌دانی که این روش انتگرال‌گیری را توسعه داد، نام‌گذاری شده است.

محدودیت‌های فعلی

نویسنده درباره توانایی‌های مدل شفاف است. مدل ۱۱۶ میلیون پارامتری که روی یک GPU آموزش دیده، در زبان انگلیسی «تا حدی روان» (fluent-ish) است اما فاقد دانش واقعی از جهان است. برای مثال، در پاسخ به یک پرامپت نمونه مانند "Alessandro eat a"، مدل عباراتی چون "icing textile" و وکلاهای "sedentary" را تولید کرد؛ این نشان می‌دهد که گرامر و لحن متون وب درست است، اما محتوا فاقد معنای عمیق است.

این سیستم بیشتر به عنوان یک اثر آموزشی طراحی شده تا یک دستیار توانمند. نویسنده اشاره می‌کند که یک مدل مکالمه‌ای کاربردی به داده‌هایی با چندین مرتبه بزرگی بیشتر نیاز دارد؛ یک مدل ۱۳۵ میلیون پارامتری معمولاً به حدود ۶۰۰ میلیارد توکن آموزش نیاز دارد تا به یک دستیار ابتدایی تبدیل شود. مدل چت فعلی ثابت می‌کند که خط لوله (Pipeline) کار می‌کند، اما محتوا سطحی باقی می‌ماند زیرا SFT تنها «چگونگی» پاسخ دادن را می‌آموزد، نه «آنچه» باید دانست را.

ساخت فنی و اجرا

این پروژه با استفاده از gcc 13 در سیستم‌عامل لینوکس و با پرچم‌های -O3 -march=native -ffast-math -fopenmp ساخته شده است. برای موتور CUDA، نویسنده از nvcc -O3 -arch=sm_89 استفاده کرده و پرچم‌های خاص کامپایلر میزبان (-fno-tree-reassoc,-fno-tree-copy-prop) را اضافه نموده است تا از خطای داخلی کامپایلر gcc (ICE) در فایل‌های منبع بزرگ جلوگیری شود.

نقشه راه آینده

پروژه nanoeuler ایستا نیست. توسعه‌دهنده قصد دارد بهینه‌سازی ترجیح مستقیم (DPO) را برای مدیریت مرحله تراز کردن (Alignment) پیاده‌سازی کند. همچنین برنامه‌هایی برای مقیاس‌بندی مدل و مجموعه داده‌ها به سمت ۲۷۰ میلیون پارامتر و انتشار نقاط ذخیره (Checkpoints) آموزش‌دیده برای تست جامعه توسعه‌دهندگان وجود دارد.

برای برنامه‌نویسان، این پروژه بازگشتی به مهندسی بر پایه اصول اولیه (First-principles engineering) است. این رویکرد، راحتیِ دستور loss.backward() را با درک عمیقی از نحوه به‌روزرسانی تک‌تک پارامترها جایگزین می‌کند. با مالکیت توکنایزر، هسته‌ها و گرادیان‌ها، این پروژه «بدهی وابستگی» (Dependency Debt) مرتبط با پشته‌های مدرن هوش مصنوعی را حذف می‌کند.

گام بعدی شما

  • کدبیس nanoeuler را در گیت‌هاب بررسی کنید تا جریان واقعی گرادیان‌ها را بدون لایه‌های PyTorch ببینید.
  • اگر به دنبال یادگیری عمیق هستید، سعی کنید یکی از لایه‌های مدل را تغییر داده و اثر آن را بر صحت گرادیان‌ها بسنجید.
  • برای درک چگونگی مقیاس‌پذیری، مقادیر Hyperparameter مدل کوچک را با مدل GPU مقایسه کنید.

اما درک این مکانیسم‌ها تنها نیمی از مسیر است؛ برای دیدن اینکه چگونه این مفاهیم در مقیاس صنعتی پیاده می‌شوند، به تحلیل ما درباره تراشه‌های Blackwell مراجعه کنید.

چرا این موضوع مهم است؟

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

تأثیر برای ایران

این پروژه برای پژوهشگران ایرانی که با محدودیت‌های سخت‌افزاری یا دسترسی به GPUهای ابری دست‌وپنجه نرم می‌کنند، الگویی برای بهینه‌سازی حداکثری مدل‌ها روی سخت‌افزارهای محدود است.

·نگاه ما
تحریریه دات‌هوش

بازگشت به مهندسی اصول اولیه (First-principles engineering) در عصر ابزارهای آماده، یک واکنش ضروری به «بحران انتزاع» در AI است. وقتی توسعه‌دهندگان دیگر نمی‌دانند `loss.backward()` در پس‌زمینه چه می‌کند، ریسک شکست سیستمی بدون دلیل مشخص بالا می‌رود. این پروژه ثابت می‌کند که مالکیت کامل بر توکنایزر و هسته‌ها، تنها راه رسیدن به بهینه‌سازی‌های افراطی در سطح سخت‌افزار است.

منابع

این گزارش با خط‌لولهٔ خودکار دات‌هوش از منابع معتبر جهانی تدوین و زیر نظر تحریریه منتشر شده است. روش کار ما

گفتگو

پنج‌شنبه‌های هوش‌محور

بسته‌ی هفتگی دات‌هوش

۵ خبر، ۲ ابزار، ۱ پرامپت در هر شماره. به‌زودی راه‌اندازی می‌شود — هر پنج‌شنبه صبح.

خبر کلیدی
ابزار کاربردی
پرامپت حرفه‌ای
تحلیل پژوهش
به‌زودی
زاویه‌ی ایرانی
به‌زودی
تمرین این هفته
به‌زودی

راهنماهای دات‌هوش

راهنماهای کاربردیِ دات‌هوش برای کار با هوش مصنوعی — از همین‌جا شروع کنید:

دات‌هوش

راهنمای فارسی هوش مصنوعی — با نگاه به ایران

اخبار روزانه، معرفی ابزارها و مدل‌ها، و آموزشِ کار با هوش مصنوعی؛ همیشه با این پرسش که از ایران چه چیزی کار می‌کند و چه چیزی نه.