۱۶ ثانیه؛ این تمام زمانی است که یک مکبوک پرو با تراشه M2 Pro برای تنظیم دقیق (Fine-tuning) یک مدل زبانی ۳ میلیارد پارامتری نیاز دارد. این اجرا ثابت میکند که بهینهسازی مدلهای با کارایی بالا دیگر نیازمند خوشههای گرانقیمت انویدیا یا اشتراکهای ابری نیست. با بهرهگیری از فریمورک MLX اپل، این فرآیند در نقطه اوج تنها ۲.۵ گیگابایت حافظه مصرف کرد.
زمینه سختافزاری و نرمافزاری
برای اطمینان از بازتولیدپذیری نتایج، محیط تست بهطور دقیق ثبت شد:
- دستگاه: MacBook Pro, M2 Pro, ۱۶ گیگابایت حافظه یکپارچه (Unified Memory)
- سیستمعامل: macOS ۲۶.۵
- فریمورکها: MLX 0.32.3 و MLX LM 0.32.0
این دستورات روی سختافزارهای جدیدتر، مانند Mac mini M5 Pro نیز کاملاً یکسان هستند؛ در این موارد تنها سرعت اجرا و میزان مصرف حافظه تغییر خواهد کرد.
تنظیم دقیق محلی در لحظهای حیاتی برای توسعهدهندگانی میرسد که به دنبال حریم خصوصی و کاهش هزینهها هستند. همانطور که پیشتر پوشش دادیم، مدلهای کوچک مانند SmolLM2-135M ممکن است در بنچمارکهای پیچیده با توهم (Hallucination) دستوپنجه نرم کنند، اما توانایی هدایت رفتار مدل بهصورت محلی به توسعهدهندگان اجازه میدهد تا مشکلات خاص ثبات و سازگاری را بدون نیاز به آموزش مجدد از صفر، برطرف کنند. این کار شبیه به این است که به یک دستیار همهکاره، مجموعهای بسیار خاص از دستورالعملهای سبکنویسی برای یک وظیفه واحد بدهید.
مکانیسم: QLoRA و حافظه یکپارچه
کارایی این فرآیند مدیون QLoRA (انطباق کمرتبهٔ کوانتیده) است. یک مدل زبانی بهطور مکرر توکن بعدی را پیشبینی میکند. در هنگام آموزش، این حدس با پاسخ صحیح مقایسه میشود، میزان خطا (Loss) اندازهگیری شده و وزنها برای کاهش این خطا تغییر میکنند. بهروزرسانی تمام ۳ میلیارد وزن یک مدل 3B حافظه بسیار زیادی میطلبد که در سختافزارهای معمولی غیرممکن است.
تکنیک لورا (LoRA) این مشکل را با منجمد کردن وزنهای پایه و آموزش دو ماتریس بسیار کوچک در کنار هر لایه حل میکند. وزن جدید به صورت مجموع وزن قدیمی بهعلاوه یک بهروزرسانی کوچک (B × A) محاسبه میشود. در این اجرای خاص، تنها ۳.۳ میلیون پارامتر — یعنی تقریباً ۰.۱۰۸٪ از کل مدل — قابل آموزش بودند.
برخلاف PyTorch که در مک از بکاِند MPS استفاده میکند، MLX اختصاصاً برای اپل سیلیکون ساخته شده است. این فریمورک از حافظه یکپارچه استفاده میکند؛ به این معنا که CPU و GPU از یک استخر RAM مشترک استفاده میکنند. این ویژگی نیاز به کپی کردن مدل بین بانکهای حافظه مختلف را از بین میبرد و سربار سیستم را بهشدت کاهش میدهد. توسعهدهندگان نباید صرفاً کلمه cuda را با mps در یک نوتبوک CUDA جایگزین کنند و انتظار آموزش بهینه داشته باشند؛ MLX مسیر بومی (Native) برای این سختافزار است.
پیادهسازی گامبهگام
برای بازتولید این نتایج، جریان کاری از یک خط لوله (Pipeline) سختگیرانه پیروی میکند:
- راهاندازی محیط: نصب در یک محیط تازه Python 3.12 با استفاده از دستور
uv pip install "mlx-lm[train]". - کوانتیزاسیون (Quantization): مدل مورد استفاده Qwen2.5-3B-Instruct بود. با استفاده از
mlx_lm.convertو فلگ--q-bits 4، اندازه مدل از ۶.۲ گیگابایت به ۱.۶ گیگابایت کاهش یافت (تقریباً ۴.۵۰۱ بیت برای هر وزن). این تبدیل تنها ۹ ثانیه زمان برد. - تست پایه (Baseline): پیش از آموزش، یک پاسخ کنترل با استفاده از یک پرامپت سیستمی و دمای (Temperature) ۰ برای تکرارپذیری استخراج شد. سؤال «تفاوت احراز هویت (Authentication) و مجوز (Authorization) چیست» منجر به پاسخی درست اما طولانی و پراکنده شامل ۱۵۱ توکن شد که با سرعت ۸۴ توکن بر ثانیه و اوج حافظه ۱.۸۶ گیگابایت اجرا شد. این نتایج در مقایسه با بهینهسازیهای پیشرفتهتر در مکبوک پرو که سرعت استنتاج را تا ۲۲۰ توکن بر ثانیه میرساند، نشاندهنده تفاوت بین اجرای استاندارد و بهینهسازیهای تخصصی است.
- آمادهسازی دادهها: یک فایل JSONL ایجاد شد که در آن هر خط شامل یک چت است (شامل پیام سیستمی، سؤال کاربر و پاسخ ایدهآل). برای مثال، پرامپت سیستمی این بود: «شما مفاهیم نرمافزاری را در یک یا دو جمله کوتاه و واضح توضیح میدهید.»
استراتژی داده و محدودیتها
دو قانون حیاتی برای صرفهجویی در زمان روی مجموعه داده اعمال شد:
۱. استفاده از پیامهای چت: از نوشتن دستی توکنهای خاص خودداری کنید؛ MLX بهطور خودکار قالب چت (Chat Template) مخصوص مدل را اعمال میکند.
۲. مدیریت تفکیک (Split): نمونههای مشابه را در یک بخش نگه دارید تا از نشت دادههای مجموعه تست به مجموعه آموزش جلوگیری شود.
برای این دمو، ۲۰ نمونه استفاده شد: ۱۶ مورد برای آموزش، ۲ مورد برای اعتبارسنجی و ۲ مورد برای تست (train.jsonl, valid.jsonl, test.jsonl). اگرچه این تعداد برای تست خط لوله کافی است، اما یک پروژه تولیدی (Production) به صدها نمونه بازبینیشده نیاز دارد.
اجرای آموزش
آموزش به عنوان یک «تست دود» (Smoke Test) با پارامترهای زیر انجام شد:
- اندازه دسته (Batch Size): ۱
- لایهها: ۸ (
--num-layers 8) - حداکثر طول توالی: ۵۱۲
- نرخ یادگیری: 1e-5
- تکرارها: ۲۰
دو فلگ خاص به بهینهسازی اجرا استفاده شد: --grad-checkpoint برای کاهش مصرف حافظه و --mask-prompt برای اطمینان از اینکه مدل فقط از پاسخها یاد میگیرد و نه از سؤالات. تابع زیان (Training Loss) از ۴.۹۵ به ۱.۷۰ کاهش یافت، در حالی که زیان اعتبارسنجی (Validation Loss) از ۷.۰۳ به ۲.۸۱ رسید. کل این فرآیند حدود ۱۶ ثانیه طول کشید و اوج مصرف حافظه ۲.۵ گیگابایت بود. نقاط بازرسی (Checkpoints) در گامهای ۱۰ و ۲۰ ذخیره شدند.
یافتهای خلاف شهود
یک کشف حیاتی در این اجرا این بود که کمترین میزان زیان همیشه به معنای بهترین عملکرد نیست. زیان تست روی دو نمونه دیده نشده نشان داد:
- مدل پایه: ۳.۹۹
- آداپتور گام ۱۰: ۲.۲۴
- آداپتور گام ۲۰: ۱.۸۹
از نظر ریاضی، آداپتور «گام ۲۰» برنده بود. اما وقتی سؤال تست پرسیده شد، پاسخ داد: «احراز هویت تأیید میکند شما کی هستید.» مدل بخش «مجوز» را کاملاً فراموش کرده بود. مدل یاد گرفته بود کوتاه باشد، اما بیش از حد کوتاه.
در مقابل، آداپتور «گام ۱۰» با وجود زیان بیشتر، پاسخی کامل ارائه داد: «احراز هویت تأیید میکند شما کی هستید. مجوز تصمیم میگیرد چه کاری میتوانید انجام دهید.» این کار خروجی را از ۱۵۱ توکن به ۴۷ توکن کاهش داد. این یک درس حیاتی است: همیشه نقاط بازرسی را بهصورت دستی بازبینی کنید و صرفاً به نمودارهای زیان تکیه نکنید.
استقرار و یکپارچهسازی
پس از انتخاب آداپتور ایدهآل (گام ۱۰)، آن را با استفاده از mlx_lm.fuse در یک مدل مستقل ادغام کردند. مدل نهایی حجم ۱.۶ گیگابایت را حفظ کرد و با سرعت ۸۱ توکن بر ثانیه اجرا شد.
برای کسانی که در حال ساخت اپلیکیشن هستند، مدل میتواند از طریق یک برنامه FastAPI سرویسدهی شود. یک مانع فنی مشاهده شد: استریمهای MLX متعلق به رشتهای (Thread) هستند که مدل را بارگذاری کرده است. اگر از یک مسیر def معمولی استفاده شود، برنامه با خطای «There is no Stream(cpu, 0) in current thread» کرش میکند. برای جلوگیری از این اتفاق، توسعهدهندگان باید از مسیرهای async def استفاده کنند تا اجرا در رشته صحیح باقی بماند.
تحلیل: تغییر به سمت ثبات محلی
این قابلیت، هدفگذاری برای هوش مصنوعی محلی را تغییر میدهد. تنظیم دقیق یک مدل کوچک، آن را از نظر استدلال خام «باهوشتر» نمیکند، اما آن را بهطور قابل توجهی سازگارتر و متقنتر میسازد.
نکات حافظه و عملکرد
- مصرف حافظه: برای یک مدل 3B، چت کردن (۱.۹ گیگابایت) سبکتر از آموزش (۲.۵ گیگابایت) است. این بهینگی در مصرف منابع، یادآور بررسیهای ما درباره اجرای مدلهای LFM 2.5 روی مکبوکهای ۱۶ گیگابایتی است که نشان داد سختافزارهای میانرده نیز میتوانند بارهای کاری مدرن را مدیریت کنند.
- خطای Out of Memory (OOM): اگر مصرف حافظه بیش از حد بالا رفت، اندازه دسته را به ۱ کاهش دهید،
--max-seq-lengthرا کوتاه کنید یا تعداد--num-layersرا کم کنید. - مقیاسپذیری: برای اجراهای واقعی، از تکرارهای بیشتر همراه با تجمع گرادیان (Gradient Accumulation) استفاده کنید (مثلاً ۴۰۰ تکرار با تجمع ۴ برای ۱۰۰ بهروزرسانی وزن). با سرعت ۰.۲ تکرار بر ثانیه، ۴۰۰ تکرار تقریباً ۳۳ دقیقه زمان میبرد.
- سازگاری: توجه داشته باشید که خروجی GGUF در MLX LM در حال حاضر از Llama، Mistral و Mixtral پشتیبانی میکند اما از Qwen پشتیبانی نمیکند. برای Qwen از safetensors ادغامشده استفاده کنید.
اگر بین RAG و تنظیم دقیق (Fine-tuning) مردد هستید، به یاد داشته باشید که RAG برای تغییر دادههاست، در حالی که تنظیم دقیق برای تغییر لحن و رفتار است. برای اکثر موارد استفاده محلی، ترکیبی از یک پرامپت سیستمی قوی و یک تنظیم دقیق سبک با MLX، بهینهترین مسیر برای رسیدن به مرحله تولید است.
برای شروع، یک اجرای آزمایشی ۲۰ مرحلهای با یک مجموعه داده کوچک امتحان کنید تا ببینید آیا رفتار مدل در جهت مطلوب تغییر میکند یا خیر، و سپس آن را به صدها نمونه افزایش دهید. سوابق هر آزمایش — شامل نسخههای پکیجها، دانههای تصادفی (Random Seeds) و تفکیک دادهها — را بهطور دقیق ثبت کنید تا ثابت شود مدل شما واقعاً بهبود یافته است.
اما داستان سختافزاری این تحول حتی شگفتانگیزتر است — به تحلیل ما دربارهی تراشههای Blackwell مراجعه کنید.




گفتگو