اگر از مکهای سری M برای تولید تصویر استفاده میکنید، زمان انتظار شما برای هر خروجی بهطور محسوسی کوتاه میشود. طبق گزارش therad.ninja در ۱۲ اوت ۲۰۲۶، یک نسخه بهینهشده یا Metal Fork از Automatic1111 توانسته است زمان تولید تصویر در یک Mac mini M1 را از ۱۲.۸ ثانیه به ۸.۷ ثانیه کاهش دهد.
این کاهش ۳۲ درصدی در تأخیر (Latency) — که در واقع همان فاصله زمانی بین زدن دکمه تولید تا دیدن تصویر نهایی است — نشان میدهد که فاصله بین پیادهسازیهای عمومی PyTorch و نرمافزارهای بومی اپل کمتر از آن چیزی است که تصور میشد. برای سالها، کاربران اپل سیلیکون احساس میکردند Automatic1111 در برابر برنامههای بومی مثل Draw Things کندتر است. در حالی که Draw Things مالک کامل محیط اجراست, Automatic1111 یک اپلیکیشن پایتونی پویا است که برای انعطافپذیری طراحی شده است. این موضوع تضادی میان تمایل به داشتن یک اکوسیستم غنی از افزونهها و نیاز به سرعت خام سختافزاری ایجاد میکند.
تفاوت این دو حالت را میتوان به تفاوت یک خودرویی که در هر تقاطع ترمز میکند با خودرویی که در یک مسیر سبز و بدون توقف حرکت میکند تشبیه کرد. اکثر ابزارهای هوش مصنوعی روی مک در مرز بین پایتون، PyTorch و واحد پردازش گرافیکی (GPU) متوقف میشوند؛ اما Metal Fork هدفش حذف این توقفهاست. همانطور که در تحلیلهای قبلی ما درباره بهینهسازی مدلهای محلی اشاره کردیم، حذف لایههای واسط همیشه کلید رسیدن به حداکثر توان سختافزار است.
جزئیات بار کاری و بنچمارکها
توسعهدهنده این پروژه بهجای دنبال کردن بنچمارکهای کلی، روی یک بار کاری بسیار خاص تمرکز کرد. هدف، مدل Stable Diffusion 1.x با استفاده از DPM++ SDE Karras در ۵ گام و CFG حدود ۱.۱۵ بود. رزولوشنهای هدف ۵۱۲×۵۱۲ و ۳۸۴×۶۴۰ بودند که از FP16 UNet روی MPS و FP32 VAE بهصورت پیشفرض استفاده میکردند.
این دقت در انتخاب متدها عمدی بود. برای مثال، اگرچه DPM++ 2M در حالت ایزوله سریعتر بود، اما نتایج مطلوب را در گامهای کوتاه تولید نمیکرد. قانون پروژه ساده بود: هر بهینهسازی که در حالت تکی عالی بود اما سرعت تولید واقعی را بدون حفظ کیفیت افزایش نمیداد، رد میشد.
بر اساس دادههای گزارششده، در یک M3 Pro، تولید تصویری که در نسخه استاندارد ۸ تا ۱۰ ثانیه زمان میبرد، اکنون بین ۳ تا ۷ ثانیه به پایان میرسد. در Mac Mini M1 نیز این بازه از ۱۳ تا ۲۰ ثانیه به ۸ تا ۱۰ ثانیه کاهش یافته است. باید توجه داشت که اینها بازههای مشاهدهشده در بارهای کاری فعلی هستند و نه بنچمارکهای کنترلشدهای که ادعای بهبود ۲ برابری جهانی را داشته باشند.
معماری سرعت: جراحی در لایههای پایین
افزایش سرعت حاصل یک تغییر ساده نبود، بلکه نتیجه مجموعهای از بهینهسازیهای جراحیگونه بود که اشکال خاص بارهای کاری Stable Diffusion 1.x را هدف قرار داده بود:
- توجه برقآسا (Flash Attention) گزینشی: بهجای جایگزینی کامل تمام عملیات Attention با Metal، یک مسیریاب (Router) طراحی شده است. این سیستم تنها زمانی Metal Flash Attention را فعال میکند که شرایط خاصی برقرار باشد: استنتاج فعال باشد، از fp16_mps استفاده شود، تعداد توکنهای پرسوجو (Query Tokens) برابر یا بیشتر از ۱۹۲ باشد و بعدِ سر (Head Dimension) برابر با ۴۰، ۸۰ یا ۱۶۰ باشد. منطق مسیریاب به این صورت است:
if inference and fp16_mps and query_tokens >= 192 and head_dim in (40, 80, 160): return metal_flash_attention(q, k, v) return pytorch_sdpa(q, k, v). هر مورد دیگری برای حفظ پایداری در برابر ماسکهای مختلف، چیدمان تنسورها، پیکربندیهای Grouped-query attention، حالتهای آموزش و تنظیمات Dropout، به PyTorch SDPA بازمیگردد. - ارسال بهتافته بافر دستورات: یک کشف حیاتی این بود که افزونههای بومی بعد از هر فراخوانی Attention، بافر دستورات MPS را ارسال (Commit) میکردند. چون Stable Diffusion در هر ارزیابی UNet بارها این کار را تکرار میکند، ارسال تکههای کوچک کار، بخش بزرگی از زمان اجرا را میبلعید. Metal Fork با ادغام هسته در جریان (Stream) فعلی PyTorch، این سربار را حذف کرد. این افزونه ابتدا تجمیع هسته پایتورچ را به پایان میرساند، عملیات Metal Flash Attention را در بافر دستورات فعلی کدگذاری میکند و سپس اجازه میدهد بقیه کارهای PyTorch MPS ادامه یابند. این تغییر ثابت میکند که حتی سریعترین هسته (Kernel) اگر بعد از هر فراخوانی بافر دستورات ارسال شود، بازنده است.
- مسیریابی حافظه یکپارچه: با توجه به اینکه اپل سیلیکون حافظه مشترکی بین CPU و GPU دارد، این فورک هزینه توجه بومی را در برابر حافظه در دسترس سیستم تخمین میزند تا از پدیده Thrashing یا Swap در زمان فشار سیستم جلوگیری کند. سیستم مقدار
attention_bytes = batch × heads × query_tokens × key_tokens × element_sizeرا محاسبه کرده و اوج مصرف را ۲.۵ برابر این مقدار تخمین میزند. اگر این مقدار از بودجه تعیینشده (حداقلِ ۱۰٪ کل حافظه، ۲۰٪ حافظه در دسترس یا ۱.۵ گیگابایت) بیشتر شود، سیستم به یک مسیر زیر-کوادراتیک (Sub-quadratic) محدود به حافظه تغییر وضعیت میدهد. این تضمین میکند که عملکرد در مکهای ۸ گیگابایتی و ۳۲ گیگابایتی، یا در زمان باز بودن برنامههایی مثل Chrome و Xcode، پیشبینیپذیر بماند.

بهینهسازیهای حافظه و هسته
برای کاهش بیشتر ردپای حافظه، یک Streaming Online Softmax پیادهسازی شده است. در روش قبلی، نتایج جزئی محاسبه میشد و صورت کسر، وزن نرمالسازی و مقدار حداکثری برای هر تکه ذخیره میشد تا در انتها روی هم انباشته شوند. این کار غیرضروری بود.
در روش جدید، یک مقدار حداکثری جاری، مجموع نرمالسازی و خروجی وزندار نگه داشته میشود. هر تکه جدید از K/V در این وضعیت جاری ادغام شده و سپس دور انداخته میشود. رابطه بازگشتی به این صورت است: new_max = max(running_max, chunk_max)، running_scale = exp(running_max - new_max)، chunk_scale = exp(chunk_max - new_max)، running_values = running_values × running_scale + chunk_values × chunk_scale و running_weights = running_weights × running_scale + chunk_weights × chunk_scale. این یعنی حافظه بر اساس تکه فعلی مقیاس میگیرد، نه اینکه تا پایان صبر کند. توسعهدهنده این نتایج را در برابر PyTorch SDPA اعتبارسنجی کرد و گرادینتها را در float64 تست کرد تا از ایمن بودن این جایگزینی مطمئن شود.
همچنین عملیاتهای تکراری UNet هدف قرار گرفتند. ترکیب GroupNorm و SiLU در SD 1.x بسیار رایج است. در حالت عادی، اینها دو عملیات جداگانه در PyTorch هستند که نیاز به ارسالهای جداگانه و یک فعالساز میانی دارند که باید در حافظه نوشته شده و بلافاصله بازخوانی شود.
Metal Fork این دو را با یک هسته ادغامشده (Fused Kernel) جایگزین کرد. برای تنسورهای استنتاج FP16 سازگار، یک گروه رشتهای Metal با ۲۵۶ رشته، هر جفت batch/group را مدیریت میکند. هسته، مجموع و مجموع مربعات را در FP32 انباشته میکند، آنها را به میانگین و واریانس تبدیل کرده، پارامترهای نرمالسازی و affine را اعمال میکند، SiLU را اجرا کرده و نتیجه FP16 را در یک Dispatch مینویسد. اگر تنسور ناسازگار باشد، آموزش فعال باشد، گرادینتها فعال باشند یا نوع داده (dtype) اشتباه باشد، سیستم به F.silu(norm(input_tensor)) بازمیگردد.
پاکسازی بدهیهای فنی
بخشی از سرعت حاصل از حذف کدهای قدیمی بود. فورک مذکور، دور زدنهای (Workarounds) منسوخ MPS را حذف کرد — مواردی مثل کلون کردن نتایج torch.narrow() و عبور دادن LayerNorm از طریق FP32 — که در نسخههای قدیمی PyTorch لازم بود اما اکنون فقط باعث کپیهای غیرضروری، تخصیصهای حافظه و ترافیک داده میشد. این رویکرد برای مدیریت بهینه کدها ضروری است، چرا که استفاده از قراردادهای معماری میتواند از انباشت بدهیهای فنی در پروژههای پیچیده هوش مصنوعی جلوگیری کند. این رفتارها اکنون بر اساس نسخه زمان اجرا کنترل میشوند، هرچند یک راه فرار با متغیر A1111_MPS_FORCE_LEGACY_OPS=1 برای کسانی که به آن نیاز دارند باقی مانده است.
سایر اصلاحات عبارتند از:
- فعالسازی
PYTORCH_MPS_PREFER_METAL=1زیرا ضرب ماتریسی مستقیم Metal برای اندازههای Projection در SD 1.x نتایج بهتری داشت. - حذف Upcastهای پیشفرض در نمونهگیری برای نگه داشتن مسیر در FP16، هرچند این یک موازنه است که میتواند بر خروجیهای با Seed یکسان تأثیر بگذارد.
- پیادهسازی NGMS (حداقل سیگما برای هدایت منفی) برای حذف محاسبات هدایت غیرشرطی در بخشهای واجد شرایط نمونهگیری. برای یک CFG پایین مثل ۱.۱۵ در یک برنامه ۵ گام، این کار حجم قابل توجهی از محاسبات را حذف میکند. در این فورک، NGMS بهصورت پیشفرض روی ۱.۰ با فعالسازی رفتار تمام-گامها تنظیم شده است. نکته مهم این است که NGMS یک دسته متفاوت از بهینهسازی است؛ زیرا محاسبات حذف نویز (denoising) را تغییر میدهد و میتواند بر ترکیببندی و جزئیات اثر بگذارد که این مورد در متادیتای PNG ثبت میشود.

هزینه کدهای «درست»
جالب است که برخی جسورانهترین آزمایشها شکست خوردند. توسعهدهنده سعی کرد کل بلوکهای باقیمانده (Residual Blocks) را به MPSGraph منتقل کند. ایده این بود که GroupNorm، SiLU، کانولوشنهای ۳×۳، جاسازی گام زمانی (timestep embedding)، جمع باقیمانده و کانولوشنهای skip را در یک گراف جای دهد تا از ارسالهای تکی جلوگیری کند. بافرهای موجود PyTorch MPS میتوانستند مستقیماً متصل شوند و گرافهای کامپایلشده بر اساس شکل (shape) کش شوند.
میکرو-بنچمارکها امیدوارکننده بودند و برخی بلوکهای رزولوشن متوسط و پایین ۱ تا ۵ درصد و کوچکترین بلوکها حدود ۹ درصد بهبود یافتند. اما در تولید تصویر کامل، نسخه MPSGraph در واقع ۱.۰۲٪ کندتر از مسیر فعلی بود (۹.۶۵۳۳ ثانیه در مقابل ۹.۵۵۵۶ ثانیه).
این آزمایش حتی باعث یک کرش قابل توجه شد: اولین پیادهسازی بهصورت همزمان روی صف Metal پایتورچ ارسال میشد و سپس یک تابع MPSGraph را فراخوانی میکرد که وارد همان صف میشد و باعث میشد macOS برنامه را با خطای dispatch_sync called on queue already owned by current thread ببندد. حتی پس از رفع مشکل ارسال تو در تو، گراف کندتر باقی ماند زیرا سربار مدیریت آن، سود حاصل از کاهش ارسالها را میبلعید. کانولوشنهای تکی MPS در پایتورچ از قبل بهینه هستند و سربار گراف بهویژه در بزرگترین بلوکهای مکانی که بیشترین کار در آنها انجام میشود، اثر مثبت را خنثی کرد.
آزمایش شکستخورده دیگر مربوط به Packed QKV Projections بود. در حالی که تصویر نهایی در تستها کاملاً یکسان بود، عملکرد از ۸.۹۸۸ ثانیه به ۹.۰۱۱ ثانیه کاهش یافت (کندی ۰.۲۶٪) و منجر به حذف آن شد. این موضوع فلسفه پروژه را نشان میدهد: میکرو-بنچمارکها تغییرات را پیشنهاد میدهند، اما تولیدات کامل آنها را انتخاب میکنند.
تحلیل: موازنه اکوسیستم و سرعت
این پروژه این فرض را تغییر میدهد که کاربر باید بین یک اپلیکیشن بومی سریع و یک WebUI منعطف یکی را انتخاب کند. با هدف قرار دادن «درزهای» بین پایتون و Metal، این فورک مسیری میانی ایجاد میکند. کاربران میتوانند لوراها (LoRA)، چکپوینتها و افزونههای خود را حفظ کنند و در عین حال بخش بزرگی از سرعت بومی را به دست آورند. این تمرکز بر بهینهسازی چرخه تحویل، مشابه رویکردی است که در تیمهای پیشرو AI برای فشردهسازی لوپهای توسعه نرمافزاری به کار میرود تا سرعت رسیدن به نتیجه نهایی افزایش یابد.
برای تضمین پایداری، افزونههای بومی در هنگام شروع به کار، خودشان را در یک زیر-فرآیند (Subprocess) تست میکنند. آنها بر اساس محیط فعال پایتون/پایتورچ ساخته شده و اعتبارسنجی میشوند. این موضوع حیاتی است چون کدهای GPU بومی برخلاف پایتون، بهجای پرتاب یک Exception ساده، میتوانند کل مفسر پایتون را متوقف کنند. توسعهدهنده اشاره کرد که از دست دادن یک بهینهسازی بهتر از دست دادن کل WebUI است.
برای کاربر متوسط مک، این یعنی مانع تولید تصویر محلی کمتر شده است. دیگر برای دریافت بازخورد سریع به یک M3 Max گرانقیمت نیاز ندارید. با این حال، اتکا به اشکال خاص SD 1.x نشان میدهد که با تکامل معماری مدلها، این هستههای Metal خاص باید دوباره نوشته شوند.
گام بعدی شما
- اگر کاربر مک هستید، مخزن dmikey/stable-diffusion-webui-metal در گیتهاب را بررسی کنید تا ببینید سختافزار شما از این هستههای ادغامشده سود میبرد یا خیر. پیادهسازی فعلی بسیار سبک است و تنها ۲۰ مسیر از ۳۲۹ مسیر ردیابیشده را تغییر داده و چهار کامیت جلوتر از پایه dev Automatic1111 است.
- تنظیمات CFG را روی مقادیر پایین (حدود ۱.۱۵) تست کنید تا اثر بهینهسازی NGMS را در سرعت تولید حس کنید.
- در صورت بروز مشکل در نسخههای قدیمی، متغیر
A1111_MPS_FORCE_LEGACY_OPS=1را برای بازگشت به حالت سازگاری فعال کنید.
مسیرهای آینده برای بهبود شامل زمانبندی دقیقتر در سطح مرحله برای کدگذاری پرامپت، UNet و رمزگشایی VAE است. رمزگشایی VAE با دقت ترکیبی (Mixed-precision) و چیدمانهای channels-last در سراسر UNet نیز از حوزههای مورد علاقه هستند. اگرچه یک MPSGraph استاتیک برای کل UNet ممکن است، اما این کار پروژه را به سمت نگهداری یک موتور اجرای دوم میبرد که موازنه کاملاً متفاوتی است. در حال حاضر، هدف همچنان حفظ همان چکپوینتها، لوراها، رابط کاربری و افزونههاست، در حالی که زمان کمتری در مرزهای بین پایتون، PyTorch، MPSGraph و Metal سپری شود.




گفتگو