آموزش یک مدل زبانی بزرگ از نقطه صفر نیازمند صدها ساعت محاسبات سنگین و هزینههای سرسامآور پردازشی است که برای بیشتر توسعهدهندگان توجیه اقتصادی ندارد. راهکار عملی و استاندارد در صنعت، استفاده از وزنهای از پیش آموزشدیدهای (Pretrained Weights) است که توسط تیمهای تحقیقاتی در دسترس عموم قرار گرفتهاند؛ اما استفاده از این وزنها چالش فنی بزرگی دارد: ساختار ذخیرهسازی، نامگذاری لایهها و چینش ابعاد ماتریسها در چکپوینتهای عمومی معمولاً با معماری کدهای سفارشی شما همخوانی مستقیم ندارند.
در این درس یاد میگیرید که چطور بدون اتکا به لایههای پنهان و کتابخانههای سطح بالا مانند transformers یا دانلودهای خودکار ابری، یک فایل safetensors با ساختار استاندارد GPT-2 را از حافظه بخوانید. با پیادهسازی نگاشت شفاف نامها، مدیریت ترانهاده کردن ماتریسهای خطی و بررسی ابعاد پیش از انتساب، مدلی پایدار و قابل اعتماد بر پایه وزنهای واقعی تحویل میگیرید.
پیادهسازی این درس کاملاً متکی بر محیط محلی، فایلهای شبیهسازیشده (Stub) و کتابخانه PyTorch است؛ بنابراین برای اجرای کدها و درک عمیق پشتصحنه لودرها نیازی به GPU ابری، ارتباط اینترنتی یا پرداختهای ارزی نخواهید داشت و تستها روی پردازنده معمولی رایانه شما نیز بیدردسر اجرا میشوند.
آموزش یک مدل 124 میلیون پارامتری از صفر تصمیمی وابسته به بودجه است؛ اما بارگذاری یک چکپوینت (Checkpoint) منتشرشده کار هر روز ماست. در این درس، وزنهای از پیش آموزشدیدهی مدل GPT-2 را از یک فایل safetensors مستقیماً درون همان معماری درس 35 بارگذاری میکنیم، نگاشت نام پارامترها را گامبهگام پیش میبریم و با تولید یک متن ادامه، درستی بارگذاری را در عمل میسنجیم. نه از شبکه خبری هست، نه از لودرهای متفرقه و نه از جادوهای سیاه و پشتپرده.
اهداف یادگیری
- خواندن فایل safetensors با کتابخانهی پایتونی
safetensorsو بررسی نام و ابعاد تنسورها (Tensors). - نگاشت نام هر پارامترِ از پیش آموزشدیده روی پارامتر معادلش در مدل GPT درس 35.
- مدیریت دو شیوهی نامگذاری متفاوت میان وزنهای رسمی GPT-2 و مدل این دوره: تفاوت
wte/wpe/h.N.attn.c_attn/c_projوmlp.c_fc/c_projبا نامهای محلیtok_embed/pos_embed/blocks.N.attn.qkv/out_projوmlp.fc1/fc2. - تشخیص و رد عدم تطابق ابعاد (Shape Mismatch) با خطایی شفاف و مشخص، پیش از آنکه وزنی مقداردهی شود.
- تولید یک ادامهی کوتاه برای متن با وزنهای بارگذاریشده و اطمینان از اینکه توکنها (Tokens) حاصل توزیع وزنهای لودشده هستند، نه وزنهای تصادفی اولیه.
صورت مسئله
وزنهایی که بهصورت عمومی منتشر میشوند، اختصاصاً برای معماری شما بستهبندی نشدهاند؛ بلکه همان نامهای پیادهسازی اصلی را یدک میکشند. فایل از پیش آموزشدیده حاوی transformer.h.0.attn.c_attn.weight با ابعاد (2304, 768) است؛ درحالیکه مدل شما انتظار blocks.0.attn.qkv.weight با ابعاد (2304, 768) را دارد (که در واقع همان ماتریس با چینشی متفاوت است) یا اینکه مدل شما از nn.Linear استفاده میکند که ماتریس را بهصورت ترانهاده (transpose) ذخیره میسازد. یک پارامتر یکسان با سه هویت اندکی متفاوت ظاهر میشود (نام، ابعاد، چینش بایتها) و لودر باید این سه مورد را با هم تطبیق دهد.
لودری که دادهها را کورکورانه کپی کند، تنسور درست را در جای نادرست میگذارد و در نتیجه مدلی تحویلتان میدهد که خروجیهای بیمعنی تولید میکند. لودری هم که در صورت عدم تطابق ابعاد از کپی کردن خودداری کند اما لاگی نیندازد، شما را در حدس زدن تنسور خطاداده تنها میگذارد. لودری که در این درس میسازیم کاملاً صریح و شفاف است: هر انتساب لاگ میشود، ابعاد هر تنسور چک میشود و یک LoadReport مواردی چون تطابقها، پارامترهای مفقود و عدم تطابق ابعاد را خلاصه میکند تا دقیقاً ببینید چه اتفاقی افتاده است.
مفاهیم پایه
نگاشت نامها در اصل تابعی ساده از رشته به رشته است. بررسی ابعاد نیز با یک دستور شرطی if انجام میشود. عملیات انتساب وزنها درون بلوک torch.no_grad() صورت میگیرد تا مشتقگیری خودکار (Autograd) بارگذاری را ردیابی نکند. در نهایت، گزارش کار وضعیتِ تکتک نامها را در خود نگه میدارد.
شیوهی نامگذاری در GPT-2
وزنهای منتشرشدهی GPT-2 تحت نامهایی از این دست ذخیره میشوند:
| نام در مدل از پیش آموزشدیده | ابعاد | مفهوم |
|---|---|---|
wte.weight |
(50257, 768) | امبدینگ توکن (Token embedding) |
wpe.weight |
(1024, 768) | امبدینگ موقعیت (Position embedding) |
h.N.ln_1.weight |
(768,) | مقیاس LayerNorm 1 در بلوک N |
h.N.ln_1.bias |
(768,) | بایاس LayerNorm 1 در بلوک N |
h.N.attn.c_attn.weight |
(768, 2304) | وزن لایهی خطی ادغامشدهی QKV |
h.N.attn.c_attn.bias |
(2304,) | بایاس لایهی خطی ادغامشدهی QKV |
h.N.attn.c_proj.weight |
(768, 768) | تصویر کردن (Projection) خروجی اتنشن |
h.N.attn.c_proj.bias |
(768,) | بایاس تصویر کردن خروجی اتنشن |
h.N.ln_2.weight |
(768,) | مقیاس LayerNorm 2 |
h.N.ln_2.bias |
(768,) | بایاس LayerNorm 2 |
h.N.mlp.c_fc.weight |
(768, 3072) | وزن لایهی fc1 در MLP |
h.N.mlp.c_fc.bias |
(3072,) | بایاس لایهی fc1 در MLP |
h.N.mlp.c_proj.weight |
(3072, 768) | وزن لایهی fc2 در MLP |
h.N.mlp.c_proj.bias |
(768,) | بایاس لایهی fc2 در MLP |
ln_f.weight |
(768,) | مقیاس نهایی LayerNorm |
ln_f.bias |
(768,) | بایاس نهایی LayerNorm |
دو نکتهی غیرمنتظره وجود دارد که باید برایشان آماده باشید. لایههای خطی c_attn، c_proj و c_fc با ماتریسی ترانهاده نسبت به آنچه nn.Linear.weight انتظار دارد ذخیره شدهاند؛ بنابراین لودر هنگام انتساب، آنها را ترانهاده میکند. همچنین سر (Head) مدل زبانی اصلاً درون فایل نیست؛ چرا که مدل از اشتراک ماتریس وزنها (Weight Tying) با wte استفاده میکند. پس سر مدل بهمحض بارگذاری wte، از طریق الیاسینگ (Aliasing یا ارجاع به همان حافظه) تنظیم میشود.
شیوهی نامگذاری محلی ما
مدل این دوره از نامهای گویاتری بهره میبرد:
| نام محلی | مفهوم |
|---|---|
tok_embed.weight |
امبدینگ توکن |
pos_embed.weight |
امبدینگ موقعیت |
blocks.N.ln1.scale |
مقیاس LayerNorm 1 در بلوک N |
blocks.N.ln1.shift |
بایاس LayerNorm 1 |
blocks.N.attn.qkv.weight |
لایهی ادغامشدهی QKV |
blocks.N.attn.qkv.bias |
بایاس ادغامشدهی QKV |
blocks.N.attn.out_proj.weight |
تصویر کردن خروجی اتنشن |
blocks.N.attn.out_proj.bias |
بایاس تصویر کردن خروجی |
blocks.N.ln2.scale |
مقیاس LayerNorm 2 |
blocks.N.ln2.shift |
بایاس LayerNorm 2 |
blocks.N.mlp.fc1.weight |
لایهی fc1 در MLP |
blocks.N.mlp.fc1.bias |
بایاس fc1 در MLP |
blocks.N.mlp.fc2.weight |
لایهی fc2 در MLP |
blocks.N.mlp.fc2.bias |
بایاس fc2 در MLP |
final_ln.scale |
مقیاس نهایی LayerNorm |
final_ln.shift |
بایاس نهایی LayerNorm |
این نگاشت، تابعی ثابت است که در این درس در قالب یک دیکشنری ارائه شده و لودر روی آن پیمایش میکند.
فیکسچر ساختگی (The stub fixture)
وزنهای واقعی GPT-2 حدود 0.5 گیگابایت حجم دارند. برای این دمو نیازی به دانلود آنها نیست؛ بلکه در اولین اجرا، یک فایل فیکسچر safetensors کوچک میسازیم که دقیقاً از همان نامگذاری GPT-2 و ابعاد متناسب با یک مدل 12 بلوکی با d_model برابر 192 (بهجای 768) استفاده میکند. ساختار این فیکسچر طوری است که تمام مسیرهای کد لودر را محک میزند. اگر این فیکسچر را با فایل واقعی جایگزین کنید، لودر بدون هیچ تغییری کار خواهد کرد.
پیادهسازی
فایل code/main.py شامل بخشهای زیر است:
- نسخهای کوچک از
GPTModelدرس 35 تا این درس کاملاً خودکفا باشد. - تابع
make_pretrained_to_local(num_layers)که نام پارامترهای هر لایه را بر اساس تعداد لایهها بسط میدهد. - تابع
load_safetensors(model, path)که نامها را پیمایش میکند، آنها را نگاشت میزند، ابعاد را میسنجد، وزنهای سبک conv1d را ترانهاده میکند و تحتtorch.no_grad()انتساب میدهد. خروجی این تابع یکLoadReportاست. - تابع
make_stub_safetensors(path, cfg)که فایل فیکسچری با ساختار نامگذاری دقیق مدل از پیش آموزشدیده تولید میکند. - دمویی که در اولین اجرا
outputs/gpt2-stub.safetensorsرا میسازد، مدلی تازه ایجاد میکند، یک ادامهی متن بر اساس وزنهای تصادفی اولیه تولید میکند، فیکسچر را بارگذاری کرده، ادامهی دیگری تولید میکند، هر دو را چاپ کرده و بررسی میکند که این دو با هم متفاوت باشند (یعنی بارگذاری واقعاً مدل را تغییر داده است).
اجرای برنامه:
python3 code/main.py
خروجی: مسیر فیکسچر، لاگ بارگذاری هر پارامتر، خلاصهی LoadReport، متن تولیدشده قبل از بارگذاری، متن بعد از بارگذاری و یک خطای عدم تطابق ابعاد که عمداً روی یک تنسور معیوب درون فیکسچر تزریق شده تا مسیر مدیریت خطا تست شود.
ابزارها و پیشنیازها
- کتابخانهی
safetensorsبرای قالب ذخیرهسازی روی دیسک و یک خوانندهی استریمینگ (Streaming). - فریمورک
torchبرای معماری مدل و محاسبات انتساب وزنها. - بدون نیاز به
transformers، بدونhuggingface_hubو بدون هیچ ارتباط شبکهای.
الگوهای محیط عملیاتی
سه الگو وجود دارد که به لودر کمک میکند در برخورد با وزنهایی که خودتان نساختهاید دوام بیاورد:
پیش از هر انتسابی، فایل را بهطور کامل اعتبارسنجی کنید. فایل را باز کنید، نام همهی تنسورها را همراه با نوع داده (dtype) و ابعادشان لیست کنید، کل نگاشت و بررسی ابعاد را انجام دهید و تنها در صورت موفقیت، مقداردهی را آغاز کنید. مدلهایی که نیمی از وزنهایشان لود شده، کانون خطاهای پنهان هستند.
هر انتساب را با ذکر نام مبدأ و نام مقصد لاگ کنید. وقتی مشکلی پیش بیاید، لاگ به شما نشان میدهد کدام تنسور در کجا قرار گرفته است؛ در غیر این صورت باید ساعتها خروجیهای هگزادسیمال حافظه را بررسی کنید. کلاس دادهی LoadReport در این درس، لیستهای loaded، missing، unexpected و shape_mismatch را نگهداری کرده و در پایان خلاصهای چاپ میکند.
سر مدل زبانی ارجاعی به وزنهای مشترک است، نه یک کپی مجزا. تنظیم model.lm_head.weight = model.tok_embed.weight پس از بارگذاری tok_embed، الگوی استاندارد این کار است. اگر ماتریس امبدینگ را در یک پارامتر جدید به نام lm_head.weight کپی کنید، قابلیت اشتراک وزنها از بین میرود و تعداد پارامترهای مدل بیسروصدا دو برابر میشود.
کاربردها
- این لودر برای هر فایل safetensors با شیوهی نامگذاری استانداردِ مدلهای از پیش آموزشدیده کار میکند. فایلهای واقعی GPT-2 (در نسخههای small، medium، large و xl) بدون نیاز به تغییر کد و تنها با تغییر تنظیمات کانفیگ مدل کار میکنند.
- همین الگو پس از بهروزرسانی جدول نگاشت نامها، برای وزنهای LLaMA، Mistral و Qwen نیز قابل تعمیم است. منطق اعتبارسنجی ابعاد و گزارش نهایی کاملاً یکسان باقی میماند.
- تولید یک ادامهی کوتاه پس از بارگذاری، محکی سریع برای سنجش سلامت کار است: اگر نمونههای خروجی پس از بارگذاری شبیه به نمونههای قبل از بارگذاری باشند، یعنی بارگذاری تغییری در مدل نداده و نگاشت نامها بیسروصدا همهی تنسورها را جا انداخته است.
تمرینها
- آرگومانی به نام
dtypeبه لودر اضافه کنید که هر تنسور را هنگام انتساب به نوع دادهی هدف (bfloat16،float16،float32) تبدیل کند. مطمئن شوید که یک مدلfloat32میتواند بهbfloat16تبدیل شود و همچنان متن تولید کند. - آرگومانی به نام
expected_layersاضافه کنید تا لودر در صورت عدم تطابق اندیسهایh.Nفایل باnum_layersمدل، از بارگذاری چکپوینت خودداری کند. - لودر را به تابع تولید متن درس 35 متصل کنید و دو نمونه را کنار هم بگیرید: یکی از وزنهای اولیهی تصادفی و دیگری از فیکسچر بارگذاریشده.
- قابلیت ذخیرهسازی را پیاده کنید: وضعیت فعلی مدل را با شیوهی نامگذاری مدلهای از پیش آموزشدیده درون یک فایل safetensors جدید بنویسید. سپس آن را با لودر بخوانید و تأیید کنید که در گزارش هیچ عدم تطابق ابعادی ثبت نمیشود.
- دیکشنری
NAME_MAPرا برای پشتیبانی از نامگذاری LLaMA (بدون بایاس، همراه با RMSNorm و چینش ادغامشدهی qkv) گسترش دهید و لودر را روی یک فیکسچر ساختگی LLaMA که خودتان تولید کردهاید دوباره اجرا کنید.
اصطلاحات کلیدی
| اصطلاح | در گفتوگوهای روزمره | معنای دقیق فنی |
|---|---|---|
| نگاشت نامها (Name map) | بازنگاشت کلیدها (Key remapping) | تابعی برای تبدیل نام تنسورهای از پیش آموزشدیده به نام پارامترهای محلی؛ معمولاً دیکشنری مستقیمی است که برای لایهها درون یک حلقه تکرار و بسط داده میشود |
| عدم تطابق ابعاد (Shape mismatch) | ابعاد نادرست (Bad shape) | تنسور با نام نگاشتشده در فایل وجود دارد اما ابعاد آن با پارامتر معادل در مدل محلی نمیخواند؛ لودر از انتساب خودداری کرده و جفت نامربوط را لاگ میکند |
| ترانهاده در بارگذاری (Transpose-on-load) | چینش Conv1d (Conv1d layout) | مدل رسمی GPT-2 وزنهای مربوط به تصویر کردن اتنشن و MLP را بهصورت ترانهادهی آنچه nn.Linear انتظار دارد ذخیره میکند؛ لودر حین بارگذاری آنها را ترانهاده میکند |
| ارجاع اشتراک ماتریس وزنها (Weight tying alias) | سر مشترک مدل زبانی (Shared LM head) | تنظیم model.lm_head.weight = model.tok_embed.weight به شکلی که سر مدل و امبدینگ از حافظهی مشترک استفاده کنند؛ به همین دلیل وزنهای سر مدل در فایل ذخیره نمیشوند |
| گزارش بارگذاری (Load report) | خلاصهی پوشش (Coverage summary) | کلاس دادهی سادهای برای نگهداری لیستهای loaded، missing، unexpected و shape_mismatch؛ پرینت کردن این گزارش نشان میدهد آیا لود موفق بوده یا خیر |
برای مطالعهی بیشتر
- درس 35 از فاز 19 برای بررسی معماری ترنسفورمر (Transformer) که وزنها را دریافت میکند.
- درس 36 از فاز 19 برای مشاهدهی حلقهی آموزشی که چکپوینتی با همین ابعاد تولید میکند.
- درس 11 از فاز 10 (کوانتیزهسازی یا Quantization) برای اقداماتی که باید روی وزنهای لودشده در شرایط محدودیت حافظه انجام داد.
- درس 13 از فاز 10 (ساخت پایپلاین کامل LLM) برای چرخهی کامل فرایند پیرامون بارگذاری و استنتاج.
