فاز 19 · درس 37

بارگذاری وزن‌های از پیش آموزش‌دیده GPT-2 با Safetensors

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

آموزش یک مدل زبانی بزرگ از نقطه صفر نیازمند صدها ساعت محاسبات سنگین و هزینه‌های سرسام‌آور پردازشی است که برای بیشتر توسعه‌دهندگان توجیه اقتصادی ندارد. راهکار عملی و استاندارد در صنعت، استفاده از وزن‌های از پیش آموزش‌دیده‌ای (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 مواردی چون تطابق‌ها، پارامترهای مفقود و عدم تطابق ابعاد را خلاصه می‌کند تا دقیقاً ببینید چه اتفاقی افتاده است.

مفاهیم پایه

تطابقعدم تطابقفایل safetensorsgpt2-stub.safetensorsخوانندهsafe_openتکرارگر نام پارامترنگاشت نامپیش‌آموزش‌دیده -> محلیبررسی ابعادانتساب تنسورتحت torch.no_gradثبت عدم تطابقعدم انتساب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) استفاده می‌کند. ساختار این فیکسچر طوری است که تمام مسیرهای کد لودر را محک می‌زند. اگر این فیکسچر را با فایل واقعی جایگزین کنید، لودر بدون هیچ تغییری کار خواهد کرد.

نگاشت مجدد وزن‌هاتغییر نام · بررسی ابعاد · انتساب
WEIGHT REMAPA published checkpoint carries the original implementation's parameter names, not yours. The loader is a string-to-string mapper feeding a single shape check: matching tensors copy under no_grad into the local name, mismatches are logged and refused rather than copied blindly. Every assignment is recorded so a misload shows up in the report instead of as silent gibberish at generation time.نام‌های از‌پیش‌آموزش‌دیدهمدل محلیwtetok_embedh.0.attn.c_attnblocks.0.attn.qkvh.0.mlp.c_fcblocks.0.mlp.fc1ابعادبررسی✗ عدم تطابق ثبت شد
یک چک‌پوینت منتشرشده نام پارامترهای پیاده‌سازی اصلی را به همراه دارد، نه نام‌های شما را. لودر یک نگاشت رشته‌به‌رشته است که ورودی را به یک بررسی ابعاد می‌فرستد: تنسورهای منطبق تحت no_grad در نام‌های محلی کپی می‌شوند و موارد نامنطبق به جای کپی کورکورانه، ثبت و رد می‌شوند. هر انتساب ثبت می‌شود تا بارگذاری اشتباه به جای تولید خروجی بی‌معنی و نامحسوس در زمان تولید متن، در گزارش مشخص شود.

پیاده‌سازی

فایل 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 نیز قابل تعمیم است. منطق اعتبارسنجی ابعاد و گزارش نهایی کاملاً یکسان باقی می‌ماند.
  • تولید یک ادامه‌ی کوتاه پس از بارگذاری، محکی سریع برای سنجش سلامت کار است: اگر نمونه‌های خروجی پس از بارگذاری شبیه به نمونه‌های قبل از بارگذاری باشند، یعنی بارگذاری تغییری در مدل نداده و نگاشت نام‌ها بی‌سر‌و‌صدا همه‌ی تنسورها را جا انداخته است.

تمرین‌ها

  1. آرگومانی به نام dtype به لودر اضافه کنید که هر تنسور را هنگام انتساب به نوع داده‌ی هدف (bfloat16، float16، float32) تبدیل کند. مطمئن شوید که یک مدل float32 می‌تواند به bfloat16 تبدیل شود و همچنان متن تولید کند.
  2. آرگومانی به نام expected_layers اضافه کنید تا لودر در صورت عدم تطابق اندیس‌های h.N فایل با num_layers مدل، از بارگذاری چک‌پوینت خودداری کند.
  3. لودر را به تابع تولید متن درس 35 متصل کنید و دو نمونه را کنار هم بگیرید: یکی از وزن‌های اولیه‌ی تصادفی و دیگری از فیکسچر بارگذاری‌شده.
  4. قابلیت ذخیره‌سازی را پیاده کنید: وضعیت فعلی مدل را با شیوه‌ی نام‌گذاری مدل‌های از پیش آموزش‌دیده درون یک فایل safetensors جدید بنویسید. سپس آن را با لودر بخوانید و تأیید کنید که در گزارش هیچ عدم تطابق ابعادی ثبت نمی‌شود.
  5. دیکشنری 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) برای چرخه‌ی کامل فرایند پیرامون بارگذاری و استنتاج.

خودآزمایی

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

  1. پیش از درسچرا لودر به یک نگاشت نام (name map) نیاز دارد؟
  2. حین درسترانهاده conv1d چیست و کدام تنسورها به آن نیاز دارند؟
  3. حین درسچرا سر مدل زبانی (LM head) در فایل safetensors وجود ندارد؟
  4. حین درسلودر در صورت عدم تطابق ابعاد چه کاری انجام می‌دهد؟
  5. پس از درسچرا داده تستی (stub) دمو یک دانلود واقعی GPT-2 نیست؟
  6. پس از درسچرا قبل و بعد از بارگذاری یک نمونه تولید می‌شود؟

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

چرا وزن‌های GPT-2 در لایه‌های خطی هنگام بارگذاری باید ترانهاده (Transpose) شوند؟

چک‌پوینت رسمی GPT-2 از پیاده‌سازی لایه‌های بر پایه Conv1D استفاده می‌کند که ماتریس را به صورت معکوس نسبت به لایه پیش‌فرض nn.Linear در PyTorch ذخیره می‌سازد؛ به همین دلیل ماتریس‌های c_attn و c_proj و c_fc باید حین بارگذاری ترانهاده شوند.

چرا سر مدل زبانی (LM head) درون فایل safetensors ذخیره نشده است؟

مدل GPT-2 از قابلیت اشتراک وزن‌ها (Weight Tying) میان ماتریس امبدینگ توکن‌ها و سر خروجی استفاده می‌کند. سر مدل به جای فایل جداگانه، حافظه مشترکی با پارامتر tok_embed دارد و از طریق الیاسینگ مقداردهی می‌شود.

چگونه می‌توان مطمئن شد که بارگذاری وزن‌ها به شکل خاموش شکست نخورده است؟

با اعتبارسنجی ابعاد پیش از انتساب، ساخت گزارش LoadReport از پارامترهای جاافتاده یا ناسازگار، و در نهایت تولید یک متن تستی کوتاه قبل و بعد از بارگذاری تا تفاوت توزیع خروجی با وزن‌های اولیه تصادفی اثبات شود.

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

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