StudioForge: خادم LLM يعمل على GPU فقط، استبدل LM Studio على جهازي ويديره وكلائي عن بُعد

تاريخ النشر
23 أغسطس 2026
بقلم
جاكوب لويد — كُتب بمساعدة الذكاء الاصطناعي بعد إنجاز المشروع
مدة القراءة
قراءة 13 دقيقة

بعبارة مبسطة: هذا برنامج يعمل على الحاسوب الذي يحتوي على بطاقات الرسوميات. تطلب منه التطبيقات إجابة من نموذج ذكاء اصطناعي، فيحسب على أي البطاقات يتّسع ذلك النموذج، ويشغّله، ويبقيه دافئًا أثناء استخدامه، ثم يطفئه من جديد عندما يهدأ. القاعدة التي لا يكسرها أبدًا هي أن النموذج إما يتّسع بالكامل على بطاقات الرسوميات أو يُرفض — فلن يشغّل نصفه بهدوء على المعالج البطيء ويتركك تتساءل لماذا صار كل شيء زاحفًا. المأزق: يحتاج إلى بطاقات NVIDIA، وأفضل اختبار له على Windows، ولم أختر له ترخيصًا بعد.

StudioForge هو خادم النماذج المحلي الذي كتبته ليحل محل LM Studio على جهاز GPU الخاص بي: مشرفٌ على llama.cpp مع واجهة برمجية متوافقة مع OpenAI على المنفذ 1234 — منفذ LM Studio، عمدًا — ولوحة تحكم في المتصفح، ومرافق استرداد (sidecar) يجيب عندما تعجز العملية الرئيسية عن الإجابة، ومستوى إدارة منشور عبر MCP ليديره وكيلٌ على جهاز آخر دون الحاجة إلى طرفية سطر أوامر.

وُجد بسبب سجل حوادث توقّف عن كونه مضحكًا. ردّ 200 لا يثبت شيئًا: يجيب LM Studio عن المسارات غير الموجّهة بحالة نجاح مع جسم خطأ، وسجلّه الخاص يقرأ Returning 200 anyway. قائمة /v1/models تعرض كل ما تم تنزيله بدلًا مما تم تحميله، فلا إجابة عن «ما المقيم فعليًا؟». نماذج الاستدلال تتجاوز نافذة 8,192 رمزًا، لأن السياق الذي ضبطته في إعدادات العميل لم يكن هو السياق الذي استخدمه التحميل — في LM Studio تكون النافذة مثبتة عند التحميل، ولم يخبرني أحد. والأغلى منها: نموذج لم يتّسع تم اقتطاعه بدلًا من رفضه — السلوك الموثّق هو أنه «سيقلّل تلقائيًا حجم التحميل على GPU … والباقي في ذاكرة النظام»، ما يستبدل سرعة GPU بسرعة ذاكرة النظام ويُبلغ بالنجاح في الحالتين.

إذا قرأت مقال dsh فقد قابلت هذا الخادم من قبل: كتلة المزوّد المسماة «StudioForge (GPU rig)» هي هو. إنه ما تصل إليه منظومة الوكلاء الثمانية عبر الشبكة من أجل العمل الثقيل، وما تتحدث إليه DisPatch، وما يطلب منه OpenClaw Email نموذج دردشة ونموذج تضمين. وقد سمّى مقال OpenClaw لدي المشكلة — «دقائق من وقت تحميل النموذج، وتنقّل VRAM بين الخدمات» — وما يلي هو الجواب، وكل قاعدة تحمل القياس الذي فرضها.

خلاصة سريعة

  • ما هو: خادم LLM يعمل على GPU فقط ومتوافق مع OpenAI، مبني على llama-server من llama.cpp، ومُقاس على الإصدار b10425 (CUDA 13.3). بوابة على 1234، ولوحة تحكم على 8080، ومراقب على 1235، وعملية فرعية واحدة لكل نموذج محمّل على 18100–18200.
  • ماذا يفعل: يحمّل النموذج عند أول استخدام، مخطّطًا سياقه ونوع ذاكرة KV المؤقتة وموضع GPU وعدد الفتحات (slots) مقابل ذاكرة VRAM المتاحة في تلك اللحظة؛ ويُخمِده بعد الخمول؛ ويُبقي النماذج المثبّتة مقيمة؛ ويمنح بطاقات كاملة لنموذج واحد عند الطلب؛ وينشر 29 أداة عبر MCP — 19 للإدارة و10 للاسترداد.
  • ما لا يفعله أبدًا: التسريب إلى المعالج المركزي (النموذج يتّسع بالكامل في VRAM أو يُرفض مع الأرقام)، أو الاتصال بالخارج من تلقاء نفسه (الاتصالات الصادرة الوحيدة هي Hugging Face لجلب النماذج، وGitHub لإصدار llama-server المثبّت وفحص تحديثه، وفحص إصدار StudioForge الاختياري، وروابط الصور التي يسمّيها الطلب)، أو تنفيذ الاستدلال عبر MCP — فمستوى التحكم لا يملك أداة إكمال ويقول ذلك بأحرف كبيرة.
  • ما تحتاجه: بطاقات NVIDIA على تعريف من سلسلة 580 أو أحدث، وPython 3.12+، وuv، ومجلد ملفات GGUF. لم تشغّل نموذجًا محليًا من قبل؟ ابدأ من هنا بدلًا من ذلك.
  • ما تنتهي إليه: رابط أساسي واحد يستخدمه كل عميل OpenAI على شبكتك دون تغيير، ولوحة تسمّي ما يحتل كل غيغابايت على كل بطاقة، ومساعد يستطيع أن يقول «حمّل النموذج 27B بسياق 128k على بطاقتي 5090» فيتحقق ذلك.
  • الجزء الصريح: Windows هو المنصة المرجعية، وNVIDIA فقط، ولا يوجد ملف ترخيص بعد — اقرأ احصل عليه أولًا.

ماذا يفعل، في صورة واحدة

ما ليس هو:

  • ليس نموذجًا. يشغّل ملفات GGUF التي تملكها، ويجلب المزيد إلى نفس المجلد وبنفس التخطيط الذي يستخدمه LM Studio.
  • ليس محرك استدلال. llama.cpp هو من يقوم بالحسابات؛ وهذا يقرر أي عملية تعمل بأي معاملات وعلى أي بطاقات.
  • ليس تطبيق دردشة. يوجد تبويب دردشة (Chat)، وهو موجود لإثبات أن مسار الطلب الحقيقي يعمل.
  • ليس عنقودًا (cluster). جهاز واحد وبطاقاته الخاصة. واجهة RPC الخلفية في llama.cpp موجودة لكنها غير موصولة.
  • ليس خادم استدلال على المعالج المركزي. لا توجد قيمة لـ--n-gpu-layers غير 999 في أي مكان من الكود.
  • ليس سطح واجهة برمجية ثانيًا. لا يوجد /api/generate الخاص بـOllama ولا واجهة KoboldCpp — فقط سطح OpenAI، ومرآة /api/v0 بنكهة LM Studio، وواجهة الإدارة REST عبر /api، وهذا كل شيء.

ماذا يعمل وأين

العنصرأينما الغرض منه
البوابةمضيف GPU، عملية واحدة، 1234/v1 و/mcp (19 أداة) و/api. تحوي السجل والمخطّط والمشرف.
لوحة التحكمالعملية نفسها، خادم uvicorn ثانٍ، 8080اللوحة الرئيسية، والإعداد، والنماذج، والتنزيل، والدردشة، والخادم، والسجلات.
المراقبعملية منفصلة، 1235خادم MCP خاص به، و10 أدوات استرداد. يعيش أطول مما يشرف عليه.
عمليات llama-server الفرعية18100–18200، على الحلقة المحلية فقطواحدة لكل نموذج محمّل؛ الانهيار يُسقط نموذجًا واحدًا، لا البوابة أبدًا.
رفيق sfctlجهاز الوكيلعميل HTTP خالص — Python 3.11+، دون CUDA ودون اعتماد على الخادم. وهو أيضًا جسر stdio الخاص بـMCP.
مكتبة GGUFmodels.dir، حيث هي بالفعلمفهرسة في مكانها؛ لا شيء يُنسخ. يواصل LM Studio استخدام المجلد نفسه.

ما يقوله الجدول:

  • مضيف GPU وحده يثبّت أي شيء. يحتاج العميل إلى رابط أساسي؛ ويحتاج الوكيل إلى حزمة Python صغيرة واحدة، ولأدوات الإدارة فقط.
  • العمليات الفرعية غير مرئية من الخارج. ترتبط بـ127.0.0.1 ولا شيء غيره، فالبوابة هي السطح العام الوحيد — وهو ما يجعل مفتاح API واحدًا حدًا حقيقيًا.

كيف يسري الطلب

كل ما يمكن أن يخطئ فيه العميل يُفحص قبل أول بايت، لأن الطلب السيئ يجب أن يحصل على 4xx حقيقي (404 لمعرّف نموذج غير موجود) مع جسم JSON بدلًا من إطار خطأ مدفون داخل تدفق SSE برمز 200 — العملاء يتعاملون مع الأول جيدًا ويخطئون بشكل متكرر في الثاني. ما يقوله GET /health على جهازي:

{"status": "ok", "version": "1.26-08-23", "uptime_s": 12690.6,
 "loaded_models": ["ggml-org/SmolVLM-256M-Instruct-GGUF/SmolVLM-256M-Instruct-Q8_0"],
 "busy": {"active_requests": 0, "busy_models": [], "loading": [], "testing": null},
 "draining": false, "instance": "primary",
 "boot": {"phase": "ready", "ready": true, "elapsed_s": 0.2, "error": null},
 "engine": {"ok": true, "tag": "b10425", "variant": "cuda", "smoke_tested": true},
 "gpu_count": 4, "models_indexed": 34, "can_serve": true}
  • can_serve هو جواب مشكلة «الـ200 الذي لا يثبت شيئًا». يكون false أثناء تشغيل أول فحص للمكتبة، بينما يبقى status على ok لأن العملية حية وهذا ما يسأله عنه فاحص النبض (liveness poller). ينفّذ GET /health?deep=true إكمالًا حقيقيًا من 8 رموز (واستدعاء تضمين لنماذج التضمين) على كل نموذج محمّل — ومع عدم وجود أي نموذج محمّل يجيب بـno_models_loaded بدلًا من النجاح، لأن المسبار الذي لا يمكن أن يفشل أسوأ من غياب المسبار.
  • الاسم المستعار local-model يُحل. كلاً من local-model وdefault وauto وcurrent يُوجَّه إلى models.default_model. يعود عملاء LM Studio إلى تلك السلسلة الحرفية، فإرجاع 404 لها يكسرهم بلا داعٍ.
  • النموذج البارد لا يبدو كأنه معلّق. يحمل التدفق المفتوح : loading <model id> (5s) كل خمس ثوانٍ، ثم : prefilling <model id> (Ns) حتى أول رمز حقيقي — أسطر تعليق SSE يتجاهلها كل محلل. المقبس الصامت لهذه المدة يطلق مهلة قراءة، والعميل الذي يعيد المحاولة يكدّس مزيدًا من التعبئة المسبقة (prefill) على دفعة مشبعة.
  • تحميل واحد في كل مرة، على مستوى الجهاز كله. خُطّط نموذجان باردان ذات مرة في نفس الوقت على البطاقات نفسها؛ مات أحدهما بخطأ CUDA error: out of memory، وأخلت إعادة محاولته النموذج الجديد الآخر، فاصطدم طلب ذلك العميل بعملية فرعية ميتة.
  • مدة ttl على مستوى الطلب تحرّك مؤقت الخمول ولا شيء غير ذلك. إن ttl: 0 هو الصيغة الشبكية للتثبيت في كل مكان هنا، لذا يُتجاهل بدلًا من أن يُنفَّذ — فعميل يرسل {"ttl": 60} كان يفك تثبيت ما ثبّته صاحبه.

الحقيقة حول VRAM: المخطِّط

هذا هو الجزء الذي لا يفعله أحد غيري، لذا إليك بالتفصيل بدلًا من الصفات. إن core/planner.py بطول 3,188 سطرًا يجيب عن سؤال واحد قبل أن يبدأ النموذج: بالنظر إلى ذاكرة VRAM الحرة فعلًا على كل بطاقة الآن، ما أفضل نافذة وجودة ذاكرة مؤقتة وعدد فتحات يمكن أن يحصل عليها هذا النموذج — وإن كانت الإجابة «لا شيء»، فماذا يجب أن أخبرك؟

السلّم، وما يرفض المقايضة عليه

الدرجات المرسومة هناك توضيحية وليست الإعدادات الافتراضية الموزّعة: الآلية دقيقة، والأرقام مجرد مثال. ما يوزّعه config.example.yaml هو target_ctx: 1048576 كهدف وdefault_ctx: 8192 كأرضية، مع قصّ الهدف على نافذة النموذج المدرَّبة أولًا — لكن عند أول تشغيل يكتب tune_for_hardware القيمة default_ctx: 16384 عندما تملك أصغر بطاقة 24 GiB أو أكثر، و8192 اعتبارًا من 12 GiB فما فوق؛ وجهازي يعمل بأرضية 128000. تجاوز النافذة المدرَّبة يتطلب تحجيم RoPE ويُضعف الجودة، لذا لا تُعرض أبدًا درجة فوقها. أما ctx_size الصريح فسلّم بدرجة واحدة.

تمريرتان، الثانية فقط عندما تفشل الأولى في كل مكان. السبب تحميل حصل في الساعة 12:03 من ظهيرة أحد الأيام: 79,832 ميجابايت حرة، و19,423 ميجابايت أخرى قابلة للاسترداد من نموذج خامل واحد. عند تلك الميزانية، اتّسع 262144 على ذاكرة مؤقتة q4_0 بحجم 96,004 ميجابايت، واتّسع 65536 على f16 كامل بحجم 95,236 ميجابايت. ما حُمّل فعلًا هو 8192/f16 بحجم 89,860 ميجابايت — دفع النموذج الثمن الكامل للإخلاء وحصل على أصغر نافذة في السلّم.

ذاكرة KV ليست رقمًا واحدًا لكل نموذج

تقريبًا كل حاسبة VRAM على الإنترنت تحسب KV على أنها طبقات × رؤوس × بُعد الرأس × 2 × بايتات × سياق. صحيحة لنموذج Llama، لكنها خاطئة بشدة لعائلتين يشغّلهما الناس فعلًا: Gemma 3 و4 تتداخلان بخمس طبقات نافذة منزلقة لكل طبقة كاملة عند نصف بُعد الرأس، وQwen3.5 و3.6 و3.8 تعلن full_attention_interval = 4، لذا توجد ذاكرة KV مؤقتة في كل طبقة رابعة والباقي طبقات متكررة Gated-DeltaNet بحالة ثابتة لكل تسلسل.

كلفة ارتكاب هذا الخطأ: نموذج Gemma-4 31B طُلِب له 262,144 رمزًا فقُدّرت ذاكرة KV بـ480 GiB وحُدّ عند 65,536، بينما كان سجل المعايرة يسجّل predicted_mb=95615 actual_mb=40037 لأسابيع. بعد إصلاح الهندسة، يهبط المتوقع والفعلي معًا إلى 38 GiB عبر بطاقتي 5090 عند n_ctx=262144 — فتحٌ بمقدار 4× جرّ معه أسطول Gemma-4 كله. أما تحميل ذاكرة KV على كل طبقة من Qwen3.5 فكان الخطأ نفسه بقبعة مختلفة: مغالاة مستقيمة بمقدار 4×.

تفصيلان يستحقان الاستعارة. يجب أن يطابق عدّ خلايا النافذة المنزلقة llama.cpp تمامًا؛ المضاعف الثابت 1.25× كان خطأً في الاتجاه الخطير، أقل بـ3.6× عند أربع فتحات. وattention_kind يُشتق من هندسة الطبقات بدلًا من general.architecture — وحيثما يتعذر اشتقاقه يُبلغ unknown، التي تعني «لا تثق بأي رقم KV هنا»، لا «افترض الحالة الرخيصة» أبدًا.

تُختار جودة الذاكرة المؤقتة داخل كل درجة بدلًا من مقايضتها بنافذة أوسع: f16/f16 → q8_0/q8_0 → q8_0 K + q4_0 V، مع إزالة q4_0 المتماثل من كل مسار تلقائي. ليس ذوقًا — فمع ذاكرة K مؤقتة q4_0، يعيد نموذج Qwen2.5-7B إنتاج 11.7% فقط من الرموز التي تنتجها نسخته f16، بينما يستقر الزوج المتطابق q8_0/q8_0 عند تباعد KL مقداره 0.0018.

أربع بطاقات، وجيلان

الجهاز يتألف من بطاقتي RTX 5090 وبطاقتي RTX 3090 — بطاقات تبلغ اسميًا 32 GB و24 GB، وتحسبها اللوحة 31.84 GiB و24.0 GiB، بإجمالي 111.7 GiB — على تعريف 610.88 ومشغل CUDA 13.3. البطاقة الواحدة أولًا، دائمًا — فالنموذج المقسّم على PCIe دون NVLink أبطأ بشكل ملموس — ولا يتجاوز المخطّط ذلك إلا عندما يكون موضع البطاقة الواحدة متضورًا عند فتحة واحدة، ويضاعف التقسيم الفتحات على الأقل، وتكون كل بطاقة مضافة بنفس القدرة على الأقل، وتكون قد تركت عدد الفتحات على الوضع التلقائي. الشرط الأخير ليس مجاملة: التقسيم يعمل بوتيرة أبطأ أعضائه.

الموضع (1.5B Q4_K_M، بطاقتا 3090، سياق 8k)التوليدمعالجة الموجه
بطاقة 3090 واحدة352.5 tok/s2803.6 tok/s
اثنتان، -sm layer344.4 tok/s2722.5 tok/s
اثنتان، -sm tensor294.3 tok/s1182.0 tok/s
اثنتان، -sm rowيفشل: error loading model: device CUDA2 does not support split buffers

ما يقوله الجدول:

  • بطاقة واحدة تفوقت على اثنتين في المحورين. يكلف تقسيم الطبقات نحو 2% من التوليد؛ ويكلف تقسيم الموترات 17% من التوليد و58% من معالجة الموجه — لذا وضع الموترات اختياري، ولا يجوز إلا للقياس أن يختاره.
  • -sm row ميت على CUDA. يقبله المحلل ثم يفشل التحميل، لذا يُحجب قبل إنشاء العملية الفرعية بدلًا من بعدها.

تفصيلان في الموضع لا يظهران إلا عندما تقيس لكل بطاقة. تُحمَّل طبقة الإخراج على آخر جهاز، لأن أدوات التكميم تُبقي موترات التضمين والإخراج عند Q6_K أو Q8_0 حتى داخل ملف Q4: فنموذج 27B خُطّط له --device CUDA1,CUDA0 --tensor-split 0.5079,0.4921 انتهى إلى 15.52 GiB على CUDA0 — آخر جهاز، وهو الذي أعطاه التقسيم أقل — مقابل 14.48 على CUDA1. ويفتح llama.cpp سياق CUDA على كل جهاز مرئي — نحو 0.22 GiB على 3090، و0.43 GiB على 5090 — ولهذا يملك عمود الموضع أرضية 512 MiB.

كم محادثة يستحقها الموضع

إن --ctx-size في llama.cpp هو إجمالي ميزانية KV المشتركة عبر الفتحات، وليس النافذة لكل فتحة — معلَم يُساء قراءته على نطاق واسع، ولا يوضحه README الخاص بالمشروع الأصلي. فالتحميل بـ--ctx-size 4096 دون --parallel يُبلغ total_slots: 4: أي 1,024 رمزًا لكل محادثة. يُطلق StudioForge بـctx_per_slot × parallel. ثم: كم عدد الفتحات التي تستحق الوجود، وهنا توقفت عن الثقة بحساباتي.

متزامنلكل تدفقإجماليp50p95الدفعة المحققة
1302.8 tok/s302.8 tok/s0.41 ث0.41 ث1.00
2225.3 tok/s425.3 tok/s0.46 ث0.49 ث1.84
4134.5 tok/s436.0 tok/s0.83 ث1.00 ث3.46
883.3 tok/s576.9 tok/s1.57 ث1.77 ث6.03

نموذج Qwen2.5-1.5B-Instruct-Q4_K_M، بطاقة RTX 3090 واحدة، 8,192 رمزًا لكل فتحة، ذاكرة KV من نوع f16، ثماني فتحات مُطلقة، موجهات من 512 رمزًا، و192 رمزًا مولّدًا لكل منها.

ما يقوله الجدول:

  • قال المقدِّر 8. وقال القياس 2. عند أربع فتحات يهبط كل تدفق إلى 44% من سرعة الانفراد، تحت أرضية 65%؛ تأخذ القاعدة أكبر عدد من 1/2/4/8 يتجاوز تلك الأرضية مع اكتساب 15% من الإجمالي عن المستوى الأدنى.
  • الإجمالي لا يتوقف عن الصعود — ثماني فتحات تنقل 1.9× من الرموز التي تنقلها فتحة واحدة — بينما تنهار المحادثة الواحدة إلى 27%. قاعدة تعظّم الإجمالي كانت ستختار 8، وسيعيش كل مستخدم نموذجًا أبطأ بثلاث مرات مما تستطيع البطاقة تشغيله به.
  • التجميع (batching) حقيقي وليس طابور انتظار. صعود الدفعة المحققة 1.00 ← 1.84 ← 3.46 ← 6.03 يثبت خطوات فك ترميز مشتركة؛ وقد أُعيد إنتاجه عبر ثلاث جولات ضمن 2%.

جولة ثانية على بطاقتي 3090 عند 32,768 لكل فتحة سارت من 301.7 إلى 230.5 لكل تدفق وأجابت 2 مرة أخرى. وبالصدفة كان الفهرس قد تنبأ بـ308.0 tok/s لذلك الموضع بينما قاست الجولة 301.7 — انحراف 2%، أفضل مما توقعت. تحمل الصفوف الآن max_parallel (كم يتّسع) بجانب recommended_parallel (كم يستحق التشغيل).

علمان قستهما قبل أن أثق بهما

فك الترميز التخميني (speculative decoding) ربح للتدفق الواحد. نموذج Qwen3.8-27B Q5_K_S برأس MTP، وبطاقة 3090 واحدة، وأربعة موجهات متباينة من 256 رمزًا، مع إيقاف ذاكرة الموجه المؤقتة: دون تخمين 37.75 tok/s؛ وdraft-mtp عند عمق 3 أعطى 50.70 tok/s، أي +34.3%، عند قبول 0.528. يهبط العمق 4 إلى 47.48، لأن القبول ينخفض إلى 0.446 وكل رمز مرفوض إضافي تم التحقق منه بلا فائدة. أما ngram-mod فحقق +0.4% ولم يصدر أي مسودات إطلاقًا.

وهنا الفخ: الموجّه نفسه ثلاث مرات قاس +751% على ذلك النموذج 27B. كرّر موجهًا واحدًا وستكون تقيس ذاكرة الموجه المؤقتة وتسميها تخمينًا. فوق أربع فتحات يعيد auto الآن none ويقول السبب — «التخمين ربح للتدفق الواحد ويضر الدفعة المشبعة» — بعد جولة حمّلت نموذج 27B عند --parallel 8 وما زال auto يختار draft-mtp، لرؤيته رأس MTP دون عدد الفتحات.

الدفعة الصغرى (micro-batch) تشتري التعبئة المسبقة مقابل VRAM. نفس النموذج 1.5B، وموجّه من 5,166 رمزًا: أعطى -ub 512 (افتراضي المحرك) 15,232 tok/s عند 1492 MiB؛ و-ub 1024، 17,307 tok/s (+13.6%) عند 1562 MiB؛ و-ub 2048، 18,061 tok/s (+18.6%) عند 1702 MiB. كان متوقفًا لوقت طويل لأن مخزن الحساب ينمو مع -ub، ولم يكن المخطّط يحاكيه، والمخزن غير المحاكى يحوّل التلاؤم إلى نفاد ذاكرة. يخصمه المخطّط الآن، مقرّبًا إلى الأعلى كي يميل نحو الرفض، ويرفع الدفعة الصغرى تلقائيًا فوق أربع فتحات فقط.

الرفض، مع الأرقام

كل إطلاق يمرر --fit off و--n-gpu-layers 999، والثاني ثابت وليس إعدادًا. وهذا أهم مما كان عليه: الإصدار المثبّت b10425 يوزّع -fit, --fit [on|off]«هل تُعدَّل الوسائط غير المعيّنة لتتلاءم مع ذاكرة الجهاز»مفعّلًا افتراضيًا، إلى جانب --n-gpu-layers auto؛ وكلاهما وصل إلى المشروع الأصلي في PR #16653 في ديسمبر 2025. هذا الزوج هو بالضبط مسار تفريغ جزئي صامت: افتراض معقول لخادم متعدد الأغراض، والسلوك نفسه الذي وُجد هذا المشروع لرفضه. عندما لا يتّسع شيء، يكون الجواب HTTP 507 مع الحساب في الجسم، مختصرًا هنا:

HTTP 507  {"error": {"code": "insufficient_vram", "type": "server_error", "message":
 "Cannot load 'lmstudio-community/gemma-4-31B-it-QAT-GGUF/gemma-4-31B-it-QAT-Q4_0'
  entirely in VRAM: needs 29.09 GiB, 20.90 GiB usable. largest single GPU offers
  20.90 GiB usable (headroom 10% reserved). Suggestions: set KV cache type to q8_0
  (roughly halves KV cache VRAM for a small quality cost); VRAM is held by other
  processes: 5.87 GiB held by python.exe (pid 45072) on CUDA1; 0.83 GiB held by
  dwm.exe (pid 2468) on CUDA0; …; clear the per-model device override so the
  planner can use other GPUs",
 "studioforge": {
   "required_bytes": 31235974510, "available_bytes": 22438368871,
   "per_gpu_free": {"3": 22438368871},
   "max_ctx_that_fits": null, "max_parallel_that_fits": null,
   "suggestions": ["set KV cache type to q8_0 (roughly halves KV cache VRAM
                   for a small quality cost)",
                   "VRAM is held by other processes: …",
                   "clear the per-model device override so the planner can use
                   other GPUs"],
   "notes": ["wanted up to 262144 tokens of context but not even the 128000 floor
             fits in the VRAM available right now",
             "device placement forced by per-model device_override"],
   "estimate_mb": {"weights_bytes": 16818.2, "kv_bytes": 8575.0,
                   "compute_bytes": 2438.6, "mmproj_bytes": 1145.1,
                   "mmproj_compute_bytes": 512.0, "cuda_context_bytes": 300.0,
                   …, "total": 29788.9},
   "vram_holders": [ … one entry per process, per card … ],
   "busy_models": [], "retry_after_s": null }}}

هذا رفض حقيقي، التقطته أثناء كتابة هذا المقال: النموذج 31B طُلب تحميله على بطاقة RTX 3090 واحدة. يسمّي النص النقص والمحتجزين؛ ويحمل error.studioforge الفشل نفسه كبيانات — البايتات المطلوبة والمتاحة، وذاكرة VRAM الحرة لكل بطاقة، والتقدير مقسّمًا إلى أوزان وKV ومخزن حساب ونموذج إسقاط وسياق CUDA، وكل عملية تحجز VRAM، وnotes توضح على أي درجة من السلّم كان واقفًا عندما استسلم. وعندما تتّسع نافذة أصغر، يسمّيها max_ctx_that_fits، محسوبة على هندسة الطبقات ليكون العرض عرضًا يقبله التحميل التالي؛ هنا لم تتّسع حتى الأرضية، لذا القيمة null بدلًا من رقم سيفشل. والرفض الذي لا يتعلق بنموذج مشغول لا يحمل retry_after_s، لأن «حاول لاحقًا» نصيحة سيئة عندما لا شيء سيتغير.

التثبيتات ومدد TTL والاستئجار وأداة إعادة التوازن

التثبيت حالة مرغوبة، وليس إعفاءً. كان يعني سابقًا مدة TTL صفرًا، والاستبعاد من كل سلّم إخلاء، وتسخينًا واحدًا عند الإقلاع — وليس الشيء الرابع: النموذج المثبّت لم يكن يُعاد تحميله أبدًا، فطفل دار في حلقة انهيار متجاوزًا max_restarts الخاص بنموذجه بقي عند state="failed" دون أن يحمل شيئًا. يركب الموفّق الآن دورة الـ15 ثانية، متراجعًا من 60 ثانية إلى سقف 900 ثانية. والشيء الوحيد الذي يغلب التثبيت هو الإنسان: إلغاء التحميل الصريح يعلّم المعرّف كمكبوت ويبقى متوقفًا.

الاستئجار بطاقة تنتمي لنموذج واحد. البطاقة المستأجرة غائبة عن رؤية GPU لكل نموذج آخر — ليست في المرتبة الأخيرة وليست خيارًا — ويُجبر المالك على تلك البطاقات بالضبط، بحجم يحدده المقدّر، وفي وضع التقسيم الذي قاسه معياره الخاص أسرعَ هناك. البطاقة المستأجرة مسبقًا تعارض 409، وليست استيلاءً أبدًا. والاستئجار دون نموذج يحجز بطاقات لشيء خارج الخادم: reserve_gpus(devices=[3], reason="ComfyUI render") هو سبب توقف توليد الصور لدي ونماذج اللغة عن التصارع.

أداة إعادة التوازن تصحح قرار الأمس الجيد. عند 13:42 خُطّط نموذج 27B على البطاقتين [1, 3] — تقسيم عبر فئتين يتشارك GPU1 — لأن نموذج 31B كان يحتل [1, 0, 2] وتطبيق توليد صور يحتل 7.5 GiB من GPU2. عند 13:53 انكمش النموذج 31B إلى [0, 1]؛ ومنذ ذلك الحين بقيت [2, 3] حرة وأفضل بشكل صارم، وبقي النموذج 27B مكانه. لذا يُنقل النموذج الخامل الآن عندما تُنزله خطة بلا إخلاء عند إعداداته الحالية بالضبط خارج كل بطاقة مشتركة. والرموز المقدرة في الثانية لا تبرر نقلًا أبدًا.

تنظر مرة كل دقيقة، وفقط عندما يتغير العالم: فقط على جهاز هادئ، وفقط لنموذج خامل خمس دقائق، ونقل واحد لكل نموذج كل 30 دقيقة — لأن النقل إعادة تحميل، وإعادة التحميل تُسقط ذاكرة الموجه المؤقتة، وفي حمولة المحادثات الطويلة على هذا الجهاز كانت تلك الذاكرة 93% من موجه من 98 ألف رمز. للإخلاء ثلاث قواعد صارمة إضافة إلى ذلك — لا نموذج مثبّت أبدًا، ولا نموذج في منتصف طلب، ولا نسخة قيد التحميل — والبطاقة المستأجرة ليست في رؤية المخطّط من الأساس. والتحميل الفوري (just-in-time) لا يستطيع أبدًا ضبط force.

عندما تتعطل الأمور

تموت VRAM مع العملية التي أخذتها. على Windows، يعيش الأطفال في كائن مهمة مجهول أُنشئ بـJOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE، فتقتل النواة كل عضو عندما يُغلق آخر مقبض — مهما كانت نهاية الأصل. مجهول، لأن المهمة المسماة ستُشارك مع أي شيء يخمّن الاسم. يحصل Linux على مُحوّل PR_SET_PDEATHSIG، يغطي kill -9 للبوابة؛ وهو جهد أفضل وليس ضمانة نواة، لذا تلتقط دورة الإقلاع وreclaim_orphan_engines ما يتسلل.

ولهذا توجد دورة إقلاع. في 18 أغسطس 2026، كان نحو 10 GiB على GPU0 ونحو 15.6 GiB على GPU1 غير متاحة مع «توقف كل شيء». كان المحتجزون ثلاثة أطفال llama-server.exe لعملية python -m pytest tests -q بدأها وكيل برمجة ثم خرج منذ ذلك الحين. يُصنَّف كل محتجز الآن — ours وchild-of-live-process وorphan وother-instance وforeignولا يُقتل أبدًا إلا orphan، آمن بالبناء لأن لا شيء آخر يطلق ثنائيات من شجرة محركاتنا.

تسمية من يحجز ماذا استغرقت محاولتين. تُبلغ NVML عن صفر من الذاكرة المستخدمة لكل عملية تحت Windows، لذا تأتي الأحجام من العداد الذي يقرأه عمود «Dedicated GPU memory» في إدارة المهام — لكنه إجمالي لكل عملية عبر المحولات، لذا كان عمود الجهاز خاطئًا: عملية مُبلغ عنها على CUDA0,1,2,3 كانت تحمل فعلًا 15.52 GiB على CUDA0 ولا شيء على بطاقتي 3090. الإصلاح يربط LUID المحول بعنوان ناقل PCI، وفخ واحد يستحق الفقرة: رقم الناقل في busId الخاص بـNVML نظام عدّ ست عشري. "00000000:42:00.0" هو الناقل 66 وليس 42 — إجابة خاطئة واثقة، لا يمكن تمييزها عن الإجابة الصحيحة.

إلغاء التحميل يُتحقق منه، لا يُعلن. يعيد المشرف فحص معرّف العملية (pid) مع حارس زمن الإنشاء ضد إعادة استخدام المعرّف، ويصعّد الناجي إلى قتل شجري قسري، ويسجّل VRAM قبل وبعد؛ والناجي يرفع خطأ 500 يطلب منك قتله يدويًا. إلغاء تحميل يُبلغ بالنجاح بينما تبقى العملية مقيمة هو أغلى كذبة يستطيع هذا النظام قولها، لأن كل تحميل لاحق سيُخطَّط حينها مقابل VRAM غير حرة.

رموز الخروج مفردات لغوية. 2 خطأ إعدادات يسمّي المفتاح. و3 تعارض منفذ، ولا تعيد الأيقونة الإحياء أبدًا على المنفذ المتعارض — بل تنتظر أن يجيب المحتجز على /health كخادم StudioForge وتلتحق به بدلًا من ذلك. و75 تعني «إعادة تشغيل مطلوبة»: يصرّف الخادم الطلبات ويضبط الرمز ويغلق بأمان — 1.0 ثانية من الطلب إلى الخروج، مقاسة — وتعيد الأيقونة الإحياء دون صرف محاولة انهيار. وُجد هذا التمييز لأن إعادة تشغيل من الواجهة أنتجت ذات مرة خادمين يتسابقان على 1234، وبعد ثلاثة انهيارات معدودة جلست الأيقونة على Crashed — see the logs folder بجانب خادم سليم لم تعد تستطيع إيقافه.

عندما تكون البوابة عالقة بدلًا من ميتة، تخاطب المراقب: عملية منفصلة تعمل دائمًا على 1235 مبنية من argparse وتسجيل stdlib، فيبدأ حتى عندما يكون config.yaml هو الشيء المكسور. أدواته العشر هي health وget_config وset_config وrestart_server وkill_model وnuke_all_models وreclaim_orphan_engines وtail_logs وgpu_status وrollback_update. يعيد تنفيذ قاعدة اليتيم محليًا بدلًا من استيراد الوحدة التي تملكها — يجب ألا تستورد عملية الاسترداد المنظومة التي تصلحها.

لوحة التحكم

اللوحة على 8080 هي خادم uvicorn ثانٍ داخل العملية نفسها، يشارك مخطط الكائنات الخاص بالبوابة بالمرجعية — لا توجد فيها روابط مطلقة إطلاقًا، وهو ما يجعلها تعمل بشكل متطابق عبر HTTP عادي على شبكة VPN شبكية وخلف واجهة HTTPS أمامية.

لوحة StudioForge الرئيسية في منتصف طلب: ترويسة تقرأ 2 محمّل و4 GPU و45.5 من 111.7 GiB حرة؛ وأربع بطاقات GPU — بطاقتا RTX 5090 وبطاقتا RTX 3090 — مع أرقام المستخدم والحر ونسب الاستخدام؛ وقائمة محتجزي VRAM تضم عمليتي llama-server.exe معلّمتين بـours وخمس عمليات خارجية؛ ولوحة استئجار GPU فارغة؛ وبطاقتا نموذج محمّل، إحداهما في مرحلة التعبئة المسبقة والأخرى مثبّتة وخاملة
اللوحة الرئيسية في منتصف طلب: نموذج 31B مقسّم عبر بطاقتي 5090 يملّأ مسبقًا موجهًا من 2,700 رمز، ونموذج 26B مثبّت على بطاقة 3090 واحدة، وكل عملية تحجز VRAM على أي بطاقة مسماة بحصتها. توجد لوحة المحتجزين لأن 25 GiB اختفت ذات مرة.
لقطة مقرّبة لبطاقتي النموذج المحمّل: يقرأ النموذج 31B المنفذ 18100، والسياق الفعلي 262144، و1 مشغول / 0 خامل، وGPU0 وGPU1 (طبقات)، ومدة TTL تبلغ 15 د 00 ث، والطلبات النشطة 1 والإجمالي 3، والأخير 58.77 tok/s، والإصدار b10425، وسطر فتحة يقرأ Processing prompt 2202/2742 (80%), cache hit 7/2742؛ بينما يقرأ النموذج 26B المنفذ 18101، وGPU2، ومدة TTL مثبّتة، والأخير 46.67 tok/s، والفتحة 0 خاملة
البطاقتان نفساهما، مقصوصتان من الإطار أعلاه: المنفذ الذي مُنح فعليًا لكل نموذج، والسياق الذي حصل عليه فعلًا، والبطاقات التي استقر عليها فعلًا، وعدّاد TTL التنازلي — ثم سطر لكل فتحة محرك. «الفعلي» يُقرأ من العملية الفرعية، لا مما طلبناه.
تبويب الإعداد: قائمة تحقق عنوانها Ready to serve, but fix before exposing it: قائمة تعرض الشبكة، مع صفوف خضراء لدليل البيانات، و50 ملف GGUF تحت E:\LLM\Models، و34 نموذجًا مفهرسًا، و4 بطاقات GPU على التعريف 610.88 وCUDA 13.3، والمحرك b10425 مختبرًا تجريبيًا والبوابة على 0.0.0.0:1234، وصف كهرماني واحد في تعرض الشبكة مع زر Set API key؛ ثم قسم مكتبة النماذج وحقول Defaults for every load — أرضية السياق 128000 والهدف 1048576 وأرضية نموذج التفكير 32768
تفتح النسخة الجديدة هنا، و«الجديدة» تُقاس بدلًا من أن تُتذكر — لا دليل نماذج، ولا شيء مفهرس، أو لا محرك. كل صف غير مستوفٍ يحمل الزر الذي يصلحه؛ وعلى جهازي الصف الكهرماني الوحيد هو البوابة التي تستمع على كل واجهة دون مفتاح API، وهو خيار وليس حادثًا.
جدول مكتبة تبويب النماذج: أعمدة للاسم والتنزيل والحجم والتكميم والمعمارية والميزات وآخر استخدام والحالة والإجراءات؛ أحد عشر صفًا من إصدارَي Qwen3.8-27B بحجم 21.29 و17.95 GiB نزولًا إلى Qwen2.5-0.5B بحجم 0.49 GiB، مع أيقونات ميزات للدردشة والرؤية والتضمين؛ يحمل صف gemma-4-26B شارة مثبّت ورقاقة خضراء محمّل؛ وتُنهي كل صف ستة أزرار إجراءات صغيرة
جدول بدلًا من بطاقات، لأن الأسئلة مع بضع عشرات من النماذج تصبح مقارَنية — أيها وصل أخيرًا، وأيها الأكبر، وأيها يستطيع رؤية الصور. الترتيب الافتراضي هو الأحدث تنزيلًا أولًا، ورقاقات الحالة على اليمين هي الحقيقة الحية حول أي منها مقيم.
حوار إعدادات النموذج الواحد لـgemma-4-31B-it-QAT-Q4_0 (16.44 GiB، سياق مدرَّب 262144): كتلة الإعدادات المثلى مع سطر لكل مجموعة بطاقات — بطاقتا RTX 5090 عند سياق 262144 وq8_0 و3 فتحات ونحو 62 tok/s؛ وبطاقتا RTX 3090 عند فتحة واحدة ونحو 34 tok/s؛ والبطاقات الأربع عند 3 فتحات ونحو 40 tok/s، معلّمة fits now؛ وبطاقة RTX 5090 واحدة عند سياق 131072 ونحو 66 tok/s — لكل منها زرا Load here وMeasure parallel؛ وصف Load at exactly بقيم 64K و128K و256K و512K؛ وحكم تلاؤم أخضر يقرأ Fits across GPU1, GPU0, GPU3 (split: layer) مع ذاكرة KV من نوع q8_0 وflash-attn مفعّل وVRAM متوقعة 32.55 GiB مقسّمة لكل بطاقة؛ وحقول Basic — طول السياق ونوع KV للـK والـV وTTL وخانة Pinned ونموذج المسودة وتجاوز الجهاز
هذا الحوار هو الحجة كلها في شاشة واحدة: ما الذي يمكن أن يفعله النموذج على كل مجموعة بطاقات لو كانت حرة، وحكم تلاؤم يعاد حسابه عند كل تغيير مع ذاكرة VRAM المتوقعة لكل بطاقة، ثم كل مقبض لتجاوزه (وتستمر طبقتا Advanced وExpert أسفل القصّ). أحجام السياق التي لا يمكن الوصول إليها تُرمَّد مع ذكر السبب بدلًا من إخفائها — «لماذا لا يستطيع هذا النموذج عمل 512k» هو السؤال الذي وُجد الزر للإجابة عنه.
تبويب التنزيل بعد البحث عن gemma-4 في Hugging Face، مرتّبًا حسب Downloads (30d): اثنا عشر مستودعًا من unsloth وlmstudio-community وgoogle، يعرض كل صف الناشر وعدد تنزيلات من 1,187,348 نزولًا إلى 458,712 والإعجابات وعدد التكميمات ووقت آخر تحديث، مع رابط بطاقة النموذج وزر Quants
بحث Hugging Face وطابور التنزيل في تبويب واحد. تسمية الترتيب تقول «Downloads (30d)» عمدًا — ذلك الرقم عدّ متحرك لثلاثين يومًا، وليس إجماليًا تراكميًا — وزر Quants هو حيث تبدأ حسابات التلاؤم.
أعلى منتقي التكميم لـunsloth/Qwen3.8-27B-GGUF: ملاحظة في الترويسة تقول إن التلاؤم تقدير للأوزان فقط حتى تُقرأ ترويسة النموذج، وسطر هندسة يقرأ attention hybrid, 65 layers, KV 65 KB per token، ثم ستة عشر صفًا من BF16 بحجم 51.77 GiB مع رقاقة كهرمانية needs-multiple-GPUs نزولًا إلى Q5_K_M بحجم 19.28 GiB، لكل منها رقاقة خضراء fits-one-GPU وسطر سياق لكل موضع لبطاقة 5090 واحدة وبطاقتي 5090 والبطاقات الأربع، وزر Download
قبل تنزيل أي شيء، يُفحص كل صف مقابل ذاكرة VRAM الحرة فعلًا الآن: رقاقة تلاؤم لكل تكميم، وتحتها السياق الذي سيحصل عليه كل موضع — بطاقة 5090 واحدة، أو اثنتان، أو البطاقات الأربع. يمتلئ المنتقي مرتين: حجم الملف مقابل VRAM الحرة كي يظهر على الشاشة فورًا، ثم قراءة عن بُعد لترويسة GGUF لتصبح الشارة جواب المخطّط نفسه. لم يكن دائمًا بهذه النظافة: حتى إصلاح أُجري أثناء كتابة هذا، كان المنتقي يعرض أيضًا وحدة المسودة MTP الخاصة بالمستودع وملف imatrix كما لو كانا تكميمين للنموذج. يستمر الحوار أسفل القصّ — 25 صفًا إجمالًا.
تبويب الدردشة: مفتاح Use the loaded model يُحل إلى gemma-4-31B-it-QAT-Q4_0، الأحدث استخدامًا من بين 2 محمّلين؛ ومنتقي نموذج، ودرجة حرارة 0.7، وtop_p 0.95، وmax_tokens 1024، ووسم معدل يقرأ 9.7 tok/s؛ وصندوق موجه نظام؛ ونص محادثة يضم موجه المستخدم الذي يطلب شرحًا من ثلاث جمل لماهية ذاكرة KV المؤقتة ولماذا يعتمد حجمها على نافذة السياق، متبوعًا بإجابة النموذج المكتملة
أداة اختبار، وليست تطبيق دردشة: تستدعي مسار التحميل نفسه وتتدفق من منفذ العملية الفرعية نفسه الذي تستخدمه نقاط نهاية OpenAI. وسم 9.7 tok/s هو حساب هذا التبويب الخاص — أجزاء متدفقة مقسومة على وقت الساعة منذ لحظة مغادرة الطلب، بما في ذلك التعبئة المسبقة — وليس توقيت توليد المحرك، الذي يظهر 58.77 tok/s على بطاقة النموذج. الدردشة الناجحة هنا دليل على أن العميل سيعمل، وليست مسارًا وهميًا يمكن أن ينحرف.
أعلى تبويب الخادم: شريط Ready to serve يسرد دليل النماذج و34 نموذجًا مفهرسًا و4 بطاقات GPU والمحرك b10425؛ ولوحة Point OpenClaw at this server موسّعة لإظهار زوجي OPENAI_BASE_URL وOPENAI_API_KEY المولّدين، وJSON خادم MCP مع openclaw_cli وopenclaw_config تحت mcp.servers وكتلة mcpServers عامة، وإعدادات الرفيق؛ ثم كتلة Health تقرأ الإصدار 1.26-08-23 ووقت التشغيل ومسار الإعدادات والنموذجين المحمّلين
يكتب تبويب الخادم إعدادات العميل نيابة عنك — الكتلة نفسها التي لصقتها في OpenClaw، بالشكلين — ويعرض أسطر الصحة تحتها؛ وأسفل التبويب نفسه يجلس قائمة المحركات وتقرير عن كم من المكتبة يستطيع الصندوق تشغيله فعلًا. لأن إدخال MCP يستدعي sfctl كعملية خارجية، لا يضطر رمز PIN للاقتران أن يظهر في ملف إعدادات إطلاقًا.
تبويب السجلات: المصدر خادم StudioForge، والمستوى INFO، وFollow مفعّل، ويتابع مخزنًا دائريًا في الذاكرة من 84 سطرًا — أسطر load planned متكررة من studioforge.core.planner تقرأ chosen ctx=262144 kv=q8_0 parallel=1, devices=[1, 0, 3] وestimate_mb=33331، ثم unloading idle model عند ttl_s=900 وmodel_unload_verified مع vram_reclaimed_mb=37086
يتابع تبويب السجلات المخزن الدائري في الذاكرة: هنا يكتب المخطّط كل قرار قبل أن ينفذه — السياق ونوع الذاكرة المؤقتة وعدد الفتحات والأجهزة التي اختارها، وما توقع أن يكلّفه التحميل — ثم إلغاء تحميل خامل وذاكرة VRAM التي تحقق من استردادها. لا شيء هنا أُعيد بناؤه لاحقًا؛ إنه ما فكّر فيه المخطّط في ذلك الوقت.

أربعة أسطر في أسفل المخزن نفسه هي ما أقرؤه عندما يفاجئني تحميل — سطر الأوامر الذي بناه، والعملية التي أجابت، والمخطّط وهو يصحح واجبه بنفسه (مع قصّ الطوابع الزمنية وأسماء المسجّلين):

model_spawn  argv='C:\Users\<you>\…\engines\b10425\llama-server.exe
  --model E:\LLM\Models\…\gemma-4-31B-it-QAT-Q4_0.gguf --host 127.0.0.1 --port 18100
  --n-gpu-layers 999 --ctx-size 262144 --parallel 1 --device CUDA1,CUDA0
  --tensor-split 0.5368,0.4632 --split-mode layer --cache-type-k q8_0 --cache-type-v q8_0
  --flash-attn on --fit off --cache-reuse 256 --cache-ram 32603 --reasoning-format deepseek
  --mmproj …'  port=18100 source=jit:/v1/chat/completions
model_ready  pid=34732 port=18100 source=jit:/v1/chat/completions
load observation  actual_mb=37046 predicted_mb=33031 ratio=1.122 ctx=262144
  devices=[1, 0] per_device_mb={'0': 18908, '1': 18138}
[warning] a device holds more than its planned share  devices=[1, 0]
  overruns={'CUDA0': {'planned_mb': 15886, 'actual_mb': 18908}}
  detail='llama.cpp places the output layer on the last device of the list; the planner
  now charges it there, so a persistent overrun means the charge is too small for this model'

ذلك هو المخطّط وهو يضبط نفسه متجاوزًا بـ3 GB على بطاقة واحدة، و4 GB عبر الزوج، ويقول أي بطاقة ولماذا — طبقة الإخراج استقرت على آخر جهاز، بالضبط حيث يخصم تكلفتها، ومع ذلك كان الخصم ناقصًا.

قائمة منطقة إشعارات Windows لـStudioForge: سطر ترويسة يقرأ Running — 1 model loaded, 23.4 GiB free، ثم Open control panel وOpen API docs وOpen logs folder وOpen models folder وUnload all models (free VRAM) وRestart engines وStart server بلون باهت وStop server وRestart server وCopy MCP URL وCopy MCP PIN وStart at login معلّمة بعلامة صح وQuit
يوميًا، كل شيء عبارة عن أيقونة نظام. سطر الترويسة هو الحالة كاملة — ما المحمّل وكم تبقى من VRAM — ورمز PIN هو المؤهل الوحيد الذي تضعه الأيقونة على الحافظة.

استخدامه كخلفية لأداة التشغيل (harness)

ستة عملاء على شبكتي يتحدثون إلى هذا الشيء، وواحد فقط منهم يعرف أنه StudioForge. وهذه هي النقطة. خُمسة منهم مرسومون أدناه؛ وOpenClaw Email هو السادس.

أي عميل OpenAI

متغيرا بيئة اثنان. يكون server.api_key هو null افتراضيًا، لذا تعمل أي سلسلة غير فارغة — معظم عملاء OpenAI يرفضون البدء بسلسلة فارغة.

export OPENAI_BASE_URL=http://my-gpu-rig:1234/v1
export OPENAI_API_KEY=not-required        # any non-empty string while server.api_key is unset

curl http://my-gpu-rig:1234/v1/chat/completions -H "Content-Type: application/json" \
  -d '{"model": "<id from /v1/models>", "messages": [{"role": "user", "content": "hello"}]}'
from openai import OpenAI
client = OpenAI(base_url="http://my-gpu-rig:1234/v1", api_key="none")   # any non-empty string, until you set one
print(client.models.list())   # every downloaded model; naming an unloaded one loads it on demand

يسرد GET /v1/models كل ما تم تنزيله، على طريقة LM Studio، مضيفًا state و—عندما يكون مقيمًا— ctx_per_slot وmax_parallel وparallel_limited_by، لأن طول السياق وحده يصبح ملتبسًا بمجرد أن يعمل النموذج بأكثر من فتحة واحدة. تعود المعرّفات كما هي: معرّف publisher/repo/file الكامل، أو اسم ملف عارٍ، أو publisher/name، دون حساسية لحالة الأحرف. لم يحتج DisPatch سوى رابط أساسي جديد؛ أما OpenClaw Email فعميل أكثر تطلبًا، يريد نموذج دردشة ونموذج تضمين ويستدعي /v1/models عند الإقلاع ليسأل عمّا يُقدَّم فعلًا.

OpenClaw، على جهاز آخر

يثبّت صندوق الوكيل حزمة صغيرة واحدة: sfctl، التي تستهدف Python 3.11 بدلًا من 3.12 الخاصة بالخادم لأن الجهاز الذي يشغّل الوكيل غالبًا ما يتخلف عن جهاز التشغيل، والتي لا تعتمد عمدًا على حزمة الخادم — لا CUDA ولا مخطّط ولا سجل.

sfctl servers add rig http://my-gpu-rig:1234 --api-key <PIN> --use
openclaw mcp add studioforge --command sfctl --arg mcp

أو يدويًا — التفصيل الذي يكلّف الناس ظهيرة كاملة. مفتاح OpenClaw هو mcp.servers، متداخل تحت mcp: خريطة mcpServers المسطحة صحيحة لـClaude Code وCline وLibreChat، لكنها ليست مفتاحًا يعرفه مخطط OpenClaw. أما الاستدلال فمسار منفصل، تحت models.providers — لاحظ baseUrl بحرفي rl صغيرين:

// ~/.openclaw/openclaw.json
{ "mcp": { "servers": {
      "studioforge": { "command": "sfctl", "args": ["mcp"] } } },
  "models": { "providers": {
      "studioforge": {
        "baseUrl": "http://my-gpu-rig:1234/v1",
        "apiKey": "not-required",
        "api": "openai-completions",
        "models": [ { "id": "<id from /v1/models>", "name": "Rig 27B", "contextWindow": 131072 } ] } } } }

ما يحصل عليه الوكيل هو قائمة أدوات مدمجة واحدة من 29: 19 الخاصة بالبوابة زائد 10 الخاصة بالمراقب، ثلاثة منها أُعيدت تسميتها بصيغة recovery_*get_config وset_config لأنهما يتصادمان مع أدوات البوابة، وhealth للتناظر. ويحتفظ restart_server باسمه المجرد، لأنه الاسم الذي تخبر رسالة الخطأ الخاصة بأداة إدارة ميتة الوكيلَ باستدعائه. وعندما يكون الخادم الرئيسي متوقفًا، ما يزال الجسر يعرض كل أدوات الإدارة الـ19 مع ملاحظة ملحقة — فالوكيل الذي لا يرى load_model لا يعرف أن القدرة موجودة. الحلقة التي يديرها:

  1. list_models(limit=N) — الفهرس، الأحدث تنزيلًا أولًا. اقرأ الصف الموصى به.
  2. load_model(**row["load_args"]) — مرّره كما هو دون تغيير؛ الوكيل الذي اختار صفًا انتهى من الاختيار.
  3. load_recommended(model_id, ctx_size=N) عندما يكون ما تعرفه هو السياق الذي تحتاجه — مسار التحميل الوحيد الذي يرفض بدلًا من الانكماش.
  4. الاستدلال عبر HTTP، لا MCP. تسمية نموذج غير محمّل تحمّله، بالإعدادات الافتراضية للمخطّط بدلًا من الصف الذي كنت تقرؤه.
  5. model_options(model_id) عندما لا يكفي الصف الموصى به: كل درجات السياق، مع السرعات.
  6. search_modelsrepo_detailsdownload_model للحصول على شيء جديد.
  7. pin_model للنموذج الذي يجب أن يجيب دائمًا؛ وreserve_gpus / release_gpus لبطاقات خاصة به.
  8. server_status وconnection_info — ما المقيم، ومن يحجز VRAM، وكل عنوان يجيب عليه.

والفقرة التي أفتخر بها أكثر من غيرها، والتي تُقدَّم لكل عميل عند الاتصال:

INFERENCE IS NOT HERE. This server exposes no chat/completion/generation tool
by design. To actually run a prompt, use the OpenAI-compatible HTTP API on the
gateway port (POST /v1/chat/completions, /v1/embeddings; GET /v1/models).
Naming an unloaded model in a request just-in-time loads it, so you usually do
not need load_model at all -- reach for it only to pre-warm a model or to load
one with non-default context/quantization settings.

dsh وClaude Code وخمسة أوامر من سطر واحد

يبدّل dsh (DeepSeek Harness) النماذج بتعديل ملف YAML واحد، يُعاد تحميله فورًا للطلب التالي. كتلة المزوّد بصيغة camelCase — baseURL وapiKeyEnv:

# ~/.dsh/settings.yaml
llm-pi-ai:
  providers:
    gpu-rig:
      displayName: StudioForge (GPU rig)
      apiKeyEnv: STUDIOFORGE_PLACEHOLDER_KEY   # a reference, not a value
      api: openai-completions
      baseURL: http://my-gpu-rig:1234/v1
      defaultContextWindow: 131072
      compat:
        supportsDeveloperRole: false   # many local servers reject role: "developer"
        maxTokensField: max_tokens
      models:
        - id: <id from /v1/models>

apiKeyEnv هو مرجع، لا قيمة حرفية أبدًا — ولا يزال الخادم المحلي دون مفتاح يحتاج إلى الإشارة إلى مؤهل ما، لأن العميل المتوافق مع OpenAI يُصرّ على رمز حامل (bearer token). أما Claude Code، على النقيض، فلا يستطيع توجيه استدلاله الخاص إلى هنا إطلاقًا: مرجع بروتوكول بوابته يسرد Anthropic Messages وBedrock وVertex، ولا أحد منها هو /v1/chat/completions. لذا فـStudioForge هو أداته، لا دماغه — claude mcp add studioforge -- sfctl mcp (علامة -- إلزامية) يسلّمه كل الـ29.

أنتج bench-llm أرقام الجهاز التي يقتبسها الناس لي — Gemma 4 26B-A4B (QAT, Q4) عند 229.0 tok/s، و110 مللي ثانية حتى أول رمز، مقيسة عبر LM Studio آنذاك — وهو عميل OpenAI عادي، فلا يحتاج إلا رابطًا أساسيًا؛ لكنه يشغّل pkill -f llama-server بين القياسات، ما يقتل كل خلفية StudioForge على الصندوق. أما البقية فسطر واحد لكل منها: Open WebUI، OPENAI_API_BASE_URL؛ وLibreChat، نقطة نهاية custom مع baseURL وmodels.fetch: true؛ وaider، OPENAI_API_BASE ثم --model openai/<id>؛ وContinue، provider: openai زائد apiBase. يُكتب مفتاح الرابط الأساسي بشكل مختلف في كل واحد منها، وهو المصدر الأكثر موثوقية لأمسيات ضائعة في هذا النظام البيئي.

ممّا يختار منه الوكيل

يجعل الفهرس اختيار النموذج بحثًا بدلًا من تخمين: مرتّب بالأحدث تنزيلًا أولًا، وصف واحد لكل درجة سياق، كل صف يحمل fits مقابل VRAM الحرة الحية، وdevices، وأنواع KV، وmax_parallel، وrecommended_parallel، وconfidence، وعمود if_gpus_idle، وload_args. الصف الواحد هو استدعاء تخطيط حقيقي واحد، لذا لا يستطيع أن يَعِد بما سيرفضه التحميل — وif_gpus_idle هو الفرق بين وكيل يستسلم ووكيل يستدعي unload_model.

وrepo_details هي الجديرة بالتسمية: تقرأ ترويسة GGUF عن بُعد، عبر طلبات Range من HTTP — 2–15 ميجابايت، معظمها مصفوفات النصوص بطول مسبوق في أداة التجزئة، مخزّنة مؤقتًا على القرص — بدلًا من تنزيل 20 جيجابايت لمعرفة ما إذا كان يتّسع، وأي CDN يجيب عن طلب نطاق بـ200 والجسم كاملًا يُكتشف ويُرفض. وما يعود هو مصفوفة context_fit من المخطّط نفسه الذي يستخدمه التحميل الحقيقي:

التكميم1× RTX 50902× RTX 5090البطاقات الأربع
BF16 (51.8 GiB)— الأوزان وحدها لا تتّسع32k عند q8_0256k
Q8_0 (27.9 GiB)256k256k
Q5_K_M (19.3 GiB)128k عند q8_0256k256k
IQ2_M (10.5 GiB)256k256k256k

من دليل OpenClaw الخاص بالمستودع، محسوب على هذا الجهاز، لـunsloth/Qwen3.8-27B-GGUF. إن max_ctx هو أكبر نافذة عند ذاكرة f16 كاملة الجودة؛ ولا يظهر رقم q8_0 إلا حيث يصل أبعد.

ما يقوله الجدول: التكميم هو من يقرر النافذة، لا عدد البطاقات — تحويل BF16 إلى Q5_K_M يقلب «لا يتّسع إطلاقًا» إلى 128k على بطاقة واحدة — وهو جواب المخطّط نفسه، لذا حيثما لا تُبلغ درجة إلا بتكميم الذاكرة المؤقتة تقول المصفوفة «عند q8_0» بدلًا من اعتبارها فوزًا. للأرقام الحقيقية، الخطة ثلاث خطوات: قياس موضع (كل وضع GPU تحت استئجاره الخاص، والإنتاجية من توقيتات llama-server نفسها)، ثم benchmark_parallel على الفائز، ثم reserve_gpus لتثبيته. لا تقس أبدًا نموذجًا يكون أحدهم في منتصف محادثة معه.

بديل مباشر عن LM Studio

كان التوافق هو قيد التصميم، ويسرد المستودع ما اقتُرض حتى لا يضطر أحد للتخمين: المنفذ 1234؛ و/v1/models الذي يسرد المنزّل بدلًا من المحمّل؛ والتحميل الفوري؛ ومدة الخمول TTL؛ وttl لكل طلب؛ وتخطيط publisher/repo/ المستخدم في مكانه، فلا توجد خطوة استيراد ويشترك البرنامجان في مكتبة واحدة؛ ومرآة /api/v0/models؛ والرابط العميق lmstudio://open_from_hf. وترحيل عميل ما هو تغيير للمضيف، لا تغيير للمضيف والمنفذ معًا.

LM Studio 0.4.21StudioForge 1.26-08-23
المحركإصدارات llama.cpp خاصة به بالإضافة إلى MLX على Applellama-server الأصلي، إصدار مثبّت واحد (b10425، CUDA 13.3)، مختبر تجريبيًا قبل التفعيل
المسارات غير الموجّهة200 مع جسم خطأ — يقول سجله Returning 200 anyway404 مع غلاف JSON، وJSON على كل حالة
الأخطاءنثر غير منظم يطابقه العملاء بتعبيرات نمطيةerror.code ثابت، وتشخيصات تحت error.studioforge
إعداد التحميلcontext_length متجاهَل في أحد مساري التحميل؛ وrepetition_penalty متجاهَل بصمتمسار تحميل واحد، كل حقل مُنفَّذ، والقيم الفعلية تُردَّد؛ وأسماء مستعارة لأداة المعاينة مقبولة
عندما لا يتّسع«سيقلّل تلقائيًا حجم التحميل على GPU … والباقي في ذاكرة النظام» — تسريب صامت إلى المعالج المركزي، بالتصميم507 insufficient_vram مع البايتات المطلوبة والمتاحة، والحر لكل GPU، وأكبر سياق قد يتّسع، واقتراحات مرتّبة
تعدد GPUتقسيم بالأولوية أو متساوٍ، ومفاتيح لكل GPU، وموازاة موترات منذ 0.4.15مخطّط يحدد حجم السياق ونوع KV والفتحات لكل موضع، ويميل كسور التقسيم لصالح طبقة الإخراج
مدة الخمول TTL60 دقيقة؛ والإخلاء التلقائي يُبقي نموذجًا واحدًا محمّلًا فوريًا على الأكثر1,800 ثانية موزّعة (15 دقيقة على جهازي)، تُمسح كل 15 ثانية؛ وبقدر ما يتّسع، مع التثبيتات والاستئجار يقرران من يبقى
الإدارة عن بُعد/api/v1 للتحميل/الإلغاء/التنزيل؛ وLM Link في المعاينة وسيُحوَّل إلى خدمة مدفوعة، والاكتشاف عبر مركز LM Studio/api REST، وsfctl، و29 أداة MCP؛ والوصول عبر شبكتك المحلية أو شبكة VPN الشبكية الخاصة بك
MCPمضيف فقط — فهو يستهلك خوادم MCPخادم MCP لإدارته الخاصة، بالإضافة إلى واحد على المراقب
المصدرمغلق؛ مجاني للاستخدام الشخصي والتجاري الداخليالمصدر في ملف zip، ولم يُختر ترخيص بعد

تم التحقق مقابل سجل تغييرات LM Studio ووثائقه ومتعقّب الأخطاء في 2026-08-23، الإصدار 0.4.21 (صدر في 12 أغسطس 2026). الصف 2 موجود في متعقّب الأخطاء العام لديه؛ والصفان 3–4 هما ما اضطر عميلي الخاص للالتفاف حوله على واجهة 0.3.x. لم أعد اختبار أي منها مقابل 0.4.21، لذا اقرأها على أنها «موثقة في مرحلة ما»، لا «مكسورة اليوم».

ما يقوله الجدول: إنه تطبيق سطح مكتب أفضل مما سيكون عليه هذا أبدًا — واجهة مصقولة، وMLX على Apple Silicon، ونقطة نهاية متوافقة مع Anthropic، ورفيق للهاتف، وفريق يصدر كل أسبوعين — والانقسام فلسفي وليس في الميزات. الوضع الافتراضي لـLM Studio هو بذل أقصى جهد: اجعله يعمل بأي طريقة. أما وضعي فهو الرفض والتفسير. وحيث يقرر وكيل ما الذي يُحمَّل، فإن بذل أقصى جهد هو الافتراضي الخاطئ، لأن لا شيء في المراحل التالية يستطيع تمييز السريع من البطيء دون قياس.

كيف يقارن بالآخرين

الخادم · المحرك · الترخيصالتبديل الفوري / مدة الخمول TTLالموضع متعدد GPUيرفض التسريب إلى المعالج المركزيالإدارة عن بُعد / MCP
StudioForge 1.26-08-23
llama.cpp، إصدار مثبّت واحد · الترخيص: لا شيء بعد
✅ تحميل فوري · مدة TTL 1,800 ثانية، ومسح كل 15 ثانيةمخطَّط لكل نموذج، بطاقات مختلطة، تثبيتات + استئجار-ngl 999 + --fit off، و507 مع الأرقام✅ REST + 29 أداة MCP
LM Studio 0.4.21
llama.cpp خاص به + MLX · مغلق
✅ تحميل فوري · 60 دقيقة، إخلاء تلقائي حتى 1تقسيم بالأولوية/متساوٍ، وموازاة موترات❌ يقلل التحميل، والباقي في الذاكرةREST؛ وMCP مضيف فقط
Ollama 0.32.15
llama.cpp/GGML؛ وMLX على Apple · MIT
keep_alive 5 دقائق · 3 مقيمون لكل GPUتوزيع تلقائي عبر البطاقات❌ يسرّب، ويعرض نسبة المعالج المركزي في ollama ps/api/* غني؛ دون خادم MCP
موجّه llama-server (b105xx، أغسطس 2026)
هو llama.cpp · MIT
✅ عملية فرعية لكل نموذج · --sleep-idle-seconds، وLRU بالعدديدوي -sm / -ts / -dev--fit on يقلّص خطتك/models/load|unload
llama-swap v251
وكيل ينشئ غيره · MIT
✅ المنتج كله · ttl لكل نموذج/مجموعة❌ ما يقوله cmd لديكغير قابل للتطبيق — وكيل فقط/ui + مسارات المنبع
vLLM 0.27.1
خاص به (PagedAttention) · Apache-2.0
❌ نموذج واحد لكل عمليةموازاة موترات/خطوط/خبراءجزئي — لا مسار تفريغ طبقاتLoRA فقط، «تطوير محلي»
KoboldCpp 1.119
تفرّع llama.cpp + صور/صوت · AGPL-3.0
--admin + --routermodeيدوي --tensor_split❌ يسرّب/api/admin/*؛ عميل MCP فقط
TextGen (oobabooga سابقًا) 4.9
5 محمّلات منها ExLlamaV3 وTRT-LLM · AGPL-3.0
✅ تبديل دون إعادة تشغيل · TTL؟يدوي --tensor-split❌ يسرّب/v1/internal/model/*؛ عميل MCP
TabbyAPI (متحرك)
ExLlamaV3 فقط — لا GGUF · AGPL-3.0
✅ إدارة + تحميل مضمّن · TTL؟gpu_split_auto مفعّل افتراضيًا✅ فعليًا — لا مسار معالج مركزي في ExLlama/v1/model/load بمفتاح إدارة
Jan 0.8.4
وضع موجّه llama.cpp · Apache-2.0
✅ عبر الموجّه · TTL؟موروث من llama.cpp❌ يسرّب/v1/orchestrations؛ عميل MCP
LocalAI 4.9.0
أكثر من 60 خلفية كصور حاويات · MIT
✅ عند الطلب · WATCHDOG_IDLE_TIMEOUT«ملاءمة نماذج تلقائية لـGPU»❌ «لا حاجة إلى GPU»REST + واجهة؛ عميل MCP فقط
GPUStack 2.2.3
vLLM وSGLang وMindIE وVoxBox · Apache-2.0
جزئي — نشر عنقوديSpread/Binpack تلقائي، متعدد العقد؟واجهة برمجية كاملة لإدارة العنقود

تم التحقق من المصادر الأولية في 2026-08-23. علامة الاستفهام تعني غير معروف، لا «لا». وعمّال GPUStack لنظام Linux فقط؛ وvLLM بلا دعم أصلي لنظام Windows (WSL أو التفرعات فقط).

ما يقوله الجدول:

  • التحميل الفوري ومدة الخمول TTL ليسا جديدين، وأنا لا أدّعيهما. كل من LM Studio وOllama وllama-swap وLocalAI يفعل الأمرين، ومفاتيح swap / exclusive / persistent لكل مجموعة في llama-swap محرك سياسات أنيق فعلًا.
  • ثلاثة أشياء نادرة هنا: الرفض بدلًا من التسريب إلى المعالج المركزي (TabbyAPI وحده يقترب، وفقط لأن ExLlama بلا مسار معالج مركزي)؛ ومخطّط يحدد حجم السياق والفتحات على البطاقات التي وجدها فعلًا؛ والإدارة المنشورة كأدوات MCP، التي لا يفعلها أي شيء آخر في الفئة على حد علمي.
  • أقرب شيء في المنبع هو موجّه llama.cpp نفسه، منافسة عادلة: نماذج متعددة مع عزل عمليات، مجاني، في الثنائي الذي تملكه. ما ينقصه هو الإخلاء القائم على الذاكرة بدلًا من العدد، والتثبيتات، والاستئجار، ورفض يحمل أرقامًا؛ لكن لديه نوم الخمول (--sleep-idle-seconds)، رغم أن استطلاع /metrics يوقظه.

استُعيرت عدة أفكار جيدة، ويسرد المستودع أيّها. أصبح Modelfile الخاص بـOllama نماذج افتراضية، فيتشاطر شخصان فوق قاعدة واحدة llama-server واحدة، وأصبح keep_alive لديه هو ttl لكل طلب. واعتُمد سطح الإعدادات ثلاثي الطبقات الخاص بـTextGen مباشرة، بما في ذلك «المعاملات الإضافية» الخام، مع التحقق من المعاملات مقابل --help الخاص بالمحرك المثبّت عند الحفظ. وفلسفة الأثر الواحد لدى KoboldCpp هي سبب عيش المحركات في أدلة مرقّمة بالإصدارات.

التثبيت

Windows، المنصة المرجعية، في أربع خطوات: ثبّت Git وPython 3.12+ وuv وتعريف NVIDIA حديثًا؛ وانسخ المستودع أو فكّ ضغط ملف التنزيل؛ وانقر نقرًا مزدوجًا على launchers\Update StudioForge.bat، الذي رغم اسمه هو خطوة التشغيل الأول — يبني البيئة الافتراضية، ويثبّت أحدث إصدار llama.cpp يملك إصدارًا لتعريفك، ويختبره تجريبيًا ويثبّته (b10425 هو الإصدار الذي قيست عليه هذه المقالة، وليس الإصدار الذي ستحصل عليه)؛ ثم launchers\Start StudioForge.bat، أو launchers\StudioForge Tray.bat إذا أردته في منطقة الإشعارات، وتفتح اللوحة على http://127.0.0.1:8080 في تبويب الإعداد.

Linux، أربعة أسطر — بالإضافة إلى cmake ومجموعة أدوات CUDA يوافق nvcc فيها تعريفك، لأن المشروع الأصلي لا ينشر أرشيف CUDA لنظام Linux عند أي وسم، والمحرك يُبنى من المصدر مرة لكل إصدار:

git clone https://github.com/LaserLloyd/StudioForge.git && cd StudioForge
uv venv --python 3.12 .venv
uv pip install --python .venv/bin/python -e ".[dev]"
.venv/bin/studioforge serve --open     # first run builds the engine

للجهاز دون شاشة، يحمل deploy/ وحدتي systemd مستخدم — وحدات مستخدم عمدًا، لأن العملية يجب أن تعمل كمستخدم الدخول الذي يملك مكتبة النماذج والبيئة الافتراضية وعقد أجهزة GPU. والمراقب عمدًا ليس BindsTo= للبوابة ويستخدم Restart=always: فهو موجود ليكون قيد التشغيل عندما لا تكون البوابة كذلك. ثم sudo loginctl enable-linger "$USER"، النمط نفسه كما في مقال ComfyUI دون شاشة. يفتح التشغيل الأول على تبويب الإعداد، حيث يفحص Detect LM Studio library قيمة downloadsFolder في ~/.lmstudio/settings.json أولًا.

الخدمةالمنفذ الافتراضيمفتاح الإعداد
البوابة — /v1 و/api و/mcp1234server.port
لوحة التحكم على الويب8080gui.port
مراقب الاسترداد1235watchdog.port
عمليات llama-server الفرعية (الحلقة المحلية فقط)18100–18200gateway.child_port_start / _end

ما يقوله الجدول: المنفذ 1234 يعني «خادم النماذج المحلي» على جهازيّ، وليسا الشيء نفسه — يشغّل صندوق الوكيل منفذه الخاص على 127.0.0.1:1234، على الحلقة المحلية، بينما يخدم جهاز التشغيل my-gpu-rig:1234 عبر شبكة VPN الشبكية — وثلاثة منافذ فقط يمكن الوصول إليها أبدًا، مع رفض التحقق من الإعدادات لأي تصادم بين أي منفذ خدمة ومدى العمليات الفرعية عند التحميل.

قاعدة دليل البيانات هي SF_DATA_DIR أولًا، ثم مجلد ملف --config، ثم <repo>/data في نسخة منسوخة — الترتيب الكامل، وسبب عدم كتابة data_dir أبدًا مرة أخرى في config.yaml، موجود في docs/SETUP.md. تملك نسخة واحدة دليل بيانات واحدًا، بفرض قفل نظام تشغيل حصري؛ والنسخة الثانية للقراءة فقط.

الأمان، بصراحة

قاعدة واحدة تحتها كلها: تظل القراءة والاستدلال والإقامة مفتوحة؛ أما تغيير الصندوق فلا. مع عدم ضبط server.api_key، لا يُقبل طلب مغيّر لمسار يغيّر الصندوق إلا من مستدعٍ على هذا الجهاز، أو مع إرسال رمز PIN الخاص بـMCP كـX-MCP-Pin أو كرمز حامل — وأي شيء آخر يحصل على 403 remote_admin_requires_credential. المجموعة المحجوبة هي الإعدادات وإعادة التشغيل والمحركات والتحديثات واسترداد VRAM والتنزيلات والاستئجار والحذف وكتابتا النموذج اللتان تعيشان بعد النسخة. المشكلة التي أصلحها كانت لي: أي شخص على الشبكة المحلية كان يستطيع PATCH /api/config وضبط server.api_key بنفسه ويحبسني خارجًا — بينما أداة MCP set_config، بالقدرة نفسها وفي العملية نفسها، كانت تطالب برمز PIN.

  • رمز PIN يحمي MCP فقط. إنه رمز اقتران تقرؤه من لافتة الإقلاع، ومحدود النطاق بأدوات الإدارة. وهو ليس مفتاح API.
  • server.api_key هو المؤهل الحقيقي وهو null افتراضيًا. اضبطه فيغطي /v1 و/api و/mcp والمراقب؛ ويظل رمز PIN يعمل على نقطتي MCP إلى جانبه.
  • الربط الموزّع هو 0.0.0.0 على المستمعين الثلاثة. يتحول صف Network exposure في تبويب الإعداد إلى كهرماني ويلزم فورًا بمجرد كشف أي مستمع دون مفتاح — بفحص الثلاثة، لأن server.host على الحلقة المحلية مع gui.host على 0.0.0.0 كان يقرأ أخضر بينما اللوحة مفتوحة على مصراعيها.
  • طلب المتصفح عبر الأصول ليس «هذا الجهاز»، حتى على الحلقة المحلية. مع cors_origins: ["*"]، يمكن لأي صفحة تزورها أن تستعلم مسبقًا PATCH /api/config عند 127.0.0.1:1234 وتصل وتبدو محلية — لذا تتضمن مقارنة الأصل المنفذ، ويُعتبر Origin: null أجنبيًا. تنظم CORS ما يجوز للصفحة قراءته، ولا تحدد أبدًا من يثق به الخادم. وللـwebsocket الخاص باللوحة نسخة مقتصرة على المضيف من البوابة نفسها، لأن اللوحة يُوصَل إليها عبر المنفذ الذي قُدّمت منه.
  • المتصفح البعيد على تثبيت دون مفتاح يحصل على القراءة والاستدلال، و403 على تغييرات الصندوق، ويُحجب عنه رمز PIN — وإلا لاستطاع أي شيء على الشبكة المحلية قراءة رمز PIN من نقطة نهاية مفتوحة واستخدامه. كان رمز PIN مسرحية تمامًا في اللحظة التي كان يهم فيها.
  • تُجلب الصور تحت حارس SSRF يحجب الحلقة المحلية والرابط المحلي والخاص وULA ونطاق CGNAT — مدى 100.64/10، حيث يعيش كل نظير في شبكة VPN الشبكية — ويحل مرة واحدة، ويتصل بالعنوان المدقق مع Host وSNI الأصليين.
  • لا شيء يغادر الصندوق دون طلب. الاتصالات الصادرة الوحيدة هي Hugging Face للنماذج، وGitHub لإصدار llama-server المثبّت وفحص تحديثه، وفحص إصدار StudioForge الاختياري، وروابط الصور التي يسمّيها الطلب؛ ويُبلغ التحديث الذاتي عن «غير مُهيأ» دون اتصال شبكي حتى تضبط update.repo، واختبار وحدة يثبّت ذلك.

حدّان يُذكران بدلًا من إخفائهما: فحص عنوان النظير يثق بأي شيء على الحلقة المحلية، وخلف الوكيل العكسي يكون الوكيل هو ذلك الشيء، لذا ضع الوكيل خلف server.api_key؛ ولا توجد مصادقة تتجاوز مفتاحًا مشتركًا واحدًا — لا حسابات ولا تحديد معدل. وما تزال القاعدة المنزلية من مقال OpenClaw سارية: الحلقة المحلية مع وكيل مصادق، أو شبكة VPN شبكية، لا الإنترنت الخام أبدًا.

الجزء الصريح

مكتوب بوضوح كي تقرر قبل أن تثبّته:

  • Windows هو المنصة المرجعية — فهو ما بُنيت عليه الأيقونة وحارس VRAM بكائن المهمة وعدادات GPU لكل عملية. وLinux مدعوم ويعمل CI على كليهما، لكنه أقل اختبارًا في الميدان؛ مسار البناء من المصدر اختبار بناؤه للأوامر لكنه لم يُختبر من البداية إلى النهاية هنا أبدًا. وmacOS غير مدعوم: لا CUDA.
  • NVIDIA فقط. يقرأ المخطّط NVML، والمحرك إصدار CUDA، وتُعبَّر ألفة التكميم بقدرات الحوسبة.
  • تقدير VRAM هو تقدير. تستقر الأوزان ضمن 2% من حجم الملف وKV دقيق من هندسة الطبقات، لكن مخزن الحساب كسر معاير، يُضبط مرة عند الإقلاع، ويُقيَّد بين 0.03–0.15 ويُحفظ في الذاكرة فقط — لذا يُلغى أي ضبط سيئ بإعادة تشغيل. تاريخا معايرة على هذا الصندوق تلوثا وأصبحا متجاهلين بالكامل.
  • التقسيم متعدد GPU نسبي، وليس مقيسًا. لا يحاكي عرض نطاق الربط البيني، وخلط الأجيال يعمل بوتيرة البطاقة الأبطأ.
  • تقديرات التزامن حسابية. يفترض المقدّر أن الفتحات نصف ممتلئة ويخفّض نماذج MoE بنصف ثابت، و--ctx-checkpoints غير محاكى إطلاقًا. يشير الخطآن نحو فتحات أقل، وهو الاتجاه الآمن — لكن شغّل قياس التوازي قبل أن تثق بالرقم 8.
  • تستخدم تقديرات السرعة أرقام البائعين الاسمية — 5090 عند 1792 GB/s و209 تيرافلوب fp16، و3090 عند 936 و71، ولا شيء منها مقيس هنا. يوجد مرساة معايرة اثنتان بالضبط، كلتاهما على هذا الجهاز وكلتاهما عند فتحة واحدة: نموذج كثيف 31B قيس 39.4 tok/s مقابل تقدير 36.1، ونموذج 122B MoE قيس 37.3 مقابل 47.4. لا شيء موثق عند أربع أو ثماني فتحات، ولا على نموذج كثيف فوق 31B.
  • حجز GPU لبرنامج آخر يقيّد مخطّطي فقط. لا شيء يفرضه على البرنامج الآخر، ولا شيء يمنعه من أخذ الذاكرة أولًا.
  • نسخة واحدة لكل دليل بيانات، ويغطي القفل دليل البيانات بدلًا من مكتبة النماذج — نسختان بدليلي بيانات مختلفين فوق مكتبة واحدة تظلان كاتبين اثنين.
  • لم يُختر ترخيص. لا يوجد ملف LICENSE عمدًا، وpyproject.toml يقول ذلك في تعليق — وفي احصل عليه ما يعنيه ذلك عمليًا.
  • لا يوجد تدقيق أمني من طرف ثالث. الادعاءات أعلاه تصف ما يفعله الكود؛ وقد كتبت كليهما. اقرأ المصدر — ولهذا هو تنزيل وليس خدمة.

2,503 اختبار وحدة ينجح و17 يتخطى، في 327 ثانية على هذا الصندوق؛ ويشغّل CI الحزمة نفسها على Windows وUbuntu مع إجبار مسبار GPU على خلفية null. وحزمة ثانية تحمّل أوزانًا حقيقية على بطاقات GPU حقيقية وهي غير محددة افتراضيًا ومحجوبة خلف متغير بيئة — حزام وحمّالة، بعد حادثة اليتيم أعلاه. ما لم يُنجز، أو لم يُفعَّل: اختبار A/B للدفعة الصغرى عند 8 فتحات، وإعدادات المعاينة المسماة، وmypy في CI؛ والتحديث الذاتي للتطبيق مكتوب لكنه يظل متوقفًا حتى تضبط update.repo بنفسك.

مطبات يجب الانتباه لها

القائمة الصريحة — أشياء عضّت فعلًا، بترتيب تقريبي حسب كمية الوقت التي كلفتها:

  • يجب إغلاق LM Studio على 1234 أولًا. لا يستطيع كلاهما احتلال المنفذ؛ يسمّي الفحص المسبق المحتجز بدلًا من طباعة أثر ربط، وserver.port ينقله. أما المكتبة فلا بأس في مشاركتها.
  • معاملان في llama.cpp لا يعنيان ما يبدوان عليه. --ctx-size هو الميزانية عبر كل الفتحات، لا لكل فتحة؛ و--fit مفعّل افتراضيًا في المنبع، إلى جانب --n-gpu-layers auto — كلاهما أعلاه، تحت المخطّط.
  • نماذج الاستدلال تعيد ردًا فارغًا تحت --reasoning-format auto. نفس الموجه، وتغيّر المعامل فقط: content بطول 0 حرف وreasoning_content بطول 316 تحت auto؛ وcontent بطول 323 تحت none. وreasoning_content ليس في مخطط OpenAI، فيقرأ العميل القياسي سلسلة فارغة ويستنتج أن النموذج لم يقل شيئًا. أشغّل النموذج 31B على deepseek لأن عملائي يقرؤون ذلك الحقل؛ وكل شيء آخر يحصل على none.
  • إصدارات vX.Y.Z التجريبية من llama.cpp لا تحمل أصل Windows CUDA. إصدار موسوم v0.1.2 جلس فوق إصدارين عاديين bNNNN دون أي أرشيفات مبنية مسبقًا إطلاقًا، فعرض تبويب الخادم تحديثًا خلف زر لا يمكن إلا أن يفشل. تُفلتر الوسوم إلى ^b\d+$ الآن.
  • لا تقتل أبدًا جذر الأيقونة قتلًا شجريًا مع فتح تبويب متصفح على اللوحة. تحت كعب مشغّل البيئة الافتراضية يكون المراقب حفيدًا للخادم، وانتهت إعادة تشغيل ذات مرة بخادم ميت ومراقب ميت ولا شيء أُنشئ. استخدم قائمة الأيقونة، أو اللوحة، أو sfctl recover --restart.
  • pkill -f llama-server من أداة أخرى يقتل خلفياتك. يفعل bench-llm ذلك بالضبط بين الجولات — أعلاه، تحت عملاء أداة التشغيل.
  • رمز PIN الخاص بـMCP ليس مفتاح API، ولا يزال الخادم دون مفتاح يحتاج مؤهلًا نائبًا في بعض العملاء — أعلاه، تحت dsh.
  • مفتاح MCP في OpenClaw هو mcp.servers، متداخل تحت mcp. خريطة mcpServers المسطحة ليست مفتاحًا يعرفه مخططه — أعلاه، تحت OpenClaw.
  • اختصارا قياس سيكذبان عليك. تكرار موجه واحد في قياس تخميني يوقّت ذاكرة الموجه المؤقتة (+751% مقابل +0.4%)، وصيغة KV بالطبقات × الرؤوس × السياق تخطئ بمقدار 4× على Qwen3.5 — كلاهما أعلاه، تحت المخطّط. ويُبلغ /props أيضًا عن speculative.types: "none" أثناء التخمين، لذا اقرأ timings.draft_n من إكمال حقيقي.
  • نماذج الرؤية لا تستفيد من ذاكرة الموجه المؤقتة. يعطّل llama.cpp إعادة استخدام الذاكرة المؤقتة للنماذج متعددة الوسائط بنفسه، وتُقدَّر كل صورة بـ1,024 رمزًا ما لم تقل بيانات mmproj الوصفية غير ذلك — بما يكفي لتكون نافذة 8k في معظمها صورًا.

احصل عليه

ملف zip أدناه هو كل شيء: شجرة المصدر الموسومة، والاختبارات، والوثائق، والمشغلات، ووحدات systemd، بالإضافة إلى مجلد dist/ يحمل الحزمتين (wheels) وsdist حتى تستطيع التثبيت دون خطوة بناء. studioforge-2026-08.zip — الإصدار v1.26-08-23، بحجم 4,238,728 بايت (4.04 MiB)، وبصمة SHA-256:

84f4f828b5c75206236890f28e8651c96146a7bb39c14e21b13a0922a13f7d3f  studioforge-2026-08.zip

226 مدخلًا تحت دليل علوي واحد، مبني بأمر git archive من الوسم المشروح v1.26-08-23 عند الالتزام 0610446، فلا يمكن أن يحتوي إلا ملفات متتبعة — لا config.yaml ولا data/ ولا تجاوزات محلية. والمصدر موجود أيضًا على github.com/LaserLloyd/StudioForge. يحتاج إلى Python 3.12+ وuv وتعريف NVIDIA من سلسلة 580 فما فوق ومجلد ملفات GGUF. الترخيص: لم يُختر بعد، لذا كل الحقوق محفوظة رسميًا — وعمليًا، عامله كما تعامل بقية تنزيلات هذا الموقع: مجاني للاستخدام الشخصي، وإن أردته تجاريًا فاسألني.

إذا تعطل شيء ما، راسلني بالبريد الإلكتروني — العنوان في صفحة حول — وأرسل شكل العطل بدلًا من إعداداتك: error.code، والأرقام من رد 507، وآخر عشرين سطرًا من logs/models/<model>.log. لا ترسل أبدًا رمز PIN ولا المفتاح. وإن بنيت مخطّط التعبئة المشتركة (bin-packing)، أو إعدادات المعاينة المسماة، أو مسار AMD يعمل فعلًا قبلي، فأنا أفضل دمج نسختك على كتابة نسختي.

أين يتركني هذا

ما لم أتوقعه هو كم تحوّل من هذا إلى قياس بدلًا من كود. المخطّط حساب يستطيع أي شخص كتابته؛ ما جعله جديرًا بالثقة هو قراءة هندسة KV الخاصة بـllama.cpp نفسها بدلًا من صيغة، ثم قياس منحنى الفتحات عند 2 بينما قال المقدّر 8. تقريبًا كل قرار في ذلك السجل بدأ رقمًا يخالف اعتقادًا. إذا كنت تشغّل LM Studio بالفعل على 1234، فالتجربة كلها نسخة، وملف دفعي واحد، وتوجيه models.dir إلى المجلد الذي تملكه بالفعل — وإن لم يكسب مكانه في ظهيرة واحدة، فإعدادك القديم لم يُمس.

مقالات ذات صلة: DeepSeek Harness (dsh) (حيث يظهر هذا الخادم أول مرة، ككتلة مزوّد غير مفسرة)، وbench-llm (حيث تأتي أرقام الرموز في الثانية للجهاز، والأداة التي ستقتل خلفياتك)، وإعداد OpenClaw لدي (المقال الذي سمّى المشكلة التي يحلها هذا)، وDisPatch (تطبيق الدردشة أمامه).

التنزيلات

مجاني للاستخدام الشخصي. إذا وفّر لك عصرًا كاملًا من الوقت، فزر القهوة قريب منك.


← المزيد من الذكاء الاصطناعي والنماذج المحلية