QVAC اور Socket.io کا استعمال کرتے ہوئے Node.js میں آف لائن AI امیج جنریٹر کیسے بنایا جائے

کچھ سال پہلے، جس دن میں نے آخر کار AI امیج جنریٹر تک رسائی حاصل کی، میں اتنا پرجوش تھا کہ میں فوراً بیٹھ گیا اور اس کے بارے میں ایک مضمون لکھا (Node.js اور OpenAI کے DALL-E کا استعمال کرتے ہوئے)۔ خیالات کو براہ راست ڈیجیٹل پکسلز میں تبدیل کرنے کا جادو ایسا محسوس ہوا جیسے کوئی حقیقی جادو کی چھڑی پکڑی ہوئی ہو۔

لیکن اس وقت، ان ماڈلز تک رسائی پارک میں چہل قدمی نہیں تھی۔ ہمارا بنیادی آپشن مڈجرنی تھا۔ اس کا مطلب یہ تھا کہ مجھے ڈسکارڈ پر جدوجہد کرنی پڑی، بعض اوقات رفتار کی حد اور سرور کے اتنے زیادہ استعمال کے ساتھ کہ میں کچھ نہیں کر سکتا تھا۔

اس وقت، تصویر کی تخلیق تک پہنچنا فلیش موب کے دوران کافی کا آرڈر دینے کے مترادف تھا۔

خوش قسمتی سے، زمین کی تزئین کی مکمل طور پر بدل گیا ہے. آج، ہم نہ صرف صارفین کے ہارڈویئر پر، بلکہ مقامی طور پر، آف لائن اور مکمل طور پر مفت میں Stable Diffusion جیسے جدید ماڈلز چلا سکتے ہیں۔ کسی API کیز کی ضرورت نہیں، سبسکرپشن کی شرح کی کوئی حد نہیں، اور نمٹنے کے لیے کوئی Discord چینلز نہیں ہیں۔

اس ٹیوٹوریل میں، آپ Node.js، Express، Socket.io، اور QVAC SDK کا استعمال کرتے ہوئے ایک مقامی ویب ایپلیکیشن بناتے ہیں تاکہ کوانٹائزڈ Stable Diffusion 2.1 ماڈل چلایا جا سکے۔

انڈیکس

شرائط

اس ٹیوٹوریل سے زیادہ سے زیادہ فائدہ اٹھانے کے لیے، آپ کو ویب بیک اینڈ اور فرنٹ اینڈ کے بنیادی اصولوں میں ٹھوس بنیاد رکھنی چاہیے۔

  • Node.js اور ES ماڈیولز: تازہ ترین جاوا اسکرپٹ ماڈیولز کا بنیادی علم (import/export)، غیر مطابقت پذیر لوپس اور ایونٹ سننے والے۔

  • ایکسپریس اور ویب ساکٹس: جامد فائل روٹنگ اور ریئل ٹائم میسج ویب ساکٹس پر بھیجنے کا علم۔ socket.io.

  • ایچ ٹی ایم ایل اور ونیلا سی ایس ایس: بنیادی DOM ہیرا پھیری اور اسٹائل بائنڈنگ کو سمجھنا۔

  • ترقی کے ماحول: ایک مقامی مشین جس میں Node.js نصب ہے۔

QVAC کیا ہے؟

QVAC، Tether کے ذریعے تیار کیا گیا، مقامی انفرنس ٹولز کا ایک مجموعہ ہے جو مشین لرننگ ماڈلز کو براہ راست کلائنٹ ہارڈ ویئر پر چلانے کے لیے ڈیزائن کیا گیا ہے۔

مہنگے کلاؤڈ ہوسٹڈ APIs (مثلاً DALL-E یا Midjourney) پر تخمینے کی درخواستوں کو روٹ کرنے کے بجائے، QVAC پہلے سے مرتب شدہ مشین لرننگ رن ٹائمز کا استعمال کرتا ہے، جیسے llama.cpp متن کے لیے، whisper.cpp ٹرانسکرپشن اور کسٹم ڈفیوژن بیک اینڈز کے لیے)، یہ براہ راست Node.js، موبائل اور ڈیسک ٹاپ رن ٹائمز میں اضافہ کرتا ہے۔

QVAC کے ساتھ مقامی AI ماڈلز چلانے سے کئی عملی فوائد حاصل ہوتے ہیں:

  • کوئی API لاگت نہیں ہے۔: زیادہ سے زیادہ تصاویر بنائیں جتنی ہارڈ ویئر بار بار آنے والے اخراجات کے بغیر پروسیس کر سکتا ہے۔

  • رازداری پہلے: پرامپٹس اور تیار کردہ تصاویر کو مکمل طور پر آپ کے مقامی کمپیوٹر کی میموری میں رکھا جاتا ہے۔

  • آف لائن آزادی: ایپلیکیشن کو کسی الگ تھلگ نیٹ ورک پر چلائیں، پرواز میں، یا انٹرنیٹ تک رسائی کے بغیر کسی علاقے میں چلائیں۔

کس طرح مستحکم بازی اندرونی طور پر کام کرتی ہے۔

RAM ختم ہونے کے بغیر مقامی طور پر امیج جنریشن چلانے کے لیے، QVAC کوانٹائزڈ استعمال کرتا ہے۔ مستحکم بازی 2.1 GGUF ماڈل (SD_V2_1_1B_Q8_0

لیکن یہ حقیقی تصویر بنانے کا عمل تصوراتی طور پر کیسے کام کرتا ہے؟ آئیے ایک بات واضح کرتے ہیں: یہ کوئی سائنسی مقالہ نہیں ہے۔. چونکہ میں کم درجے کی مشین لرننگ محقق نہیں ہوں، اس لیے میں بنیادی ملٹی ویریٹ کیلکولس، امکانی تقسیم، یا اسٹاکسٹک تفریق مساوات کے بارے میں تفصیل میں نہیں جاؤں گا۔ (آئیے ایماندار بنیں، ہم میں سے کوئی بھی یونانی علامتوں اور لکیری الجبرا کے فارمولوں کو اسکرین پر نہیں دیکھنا چاہتا جب ہم صاف جاوا اسکرپٹ لکھ رہے ہوں۔)

اس کے بجائے، آئیے یہ سمجھنے کے لیے کچھ بدیہی ڈویلپر تشبیہات استعمال کریں کہ یہ ماڈل تصوراتی طور پر کیسے کام کرتے ہیں۔

عالمی معیار کے مجسمہ ساز کی تمثیل

اس کے مرکز میں، جدید AI امیج جنریشن بے ترتیب پن کو حقیقت میں بدل دیتی ہے۔ برش کے ساتھ انسانی مصور کی طرح شروع سے پکسل بہ پکسل تصاویر کو "رنگنے” کے بجائے، AI بنیادی طور پر ایک عالمی معیار کے مجسمہ ساز کے طور پر کام کرتا ہے، ڈیجیٹل جامد بلاکس سے تصاویر تراشتا ہے۔

آج اس کی حمایت کرنے والی سب سے غالب ٹیکنالوجی ہے۔ بازییہ اسٹیبل ڈفیوژن، مڈجرنی، اور گوگل کی امیجین سیریز جیسے ماڈلز کو سپورٹ کرتا ہے۔

یہاں ایک تصوراتی مرحلہ وار بریک ڈاؤن ہے کہ یہ جامد بلاکس آرٹ میں کیسے تبدیل ہوتے ہیں۔

1. تربیت کا مرحلہ (پیٹرن سیکھنا)

اس سے پہلے کہ کوئی ماڈل کچھ بھی پیدا کر سکے، اسے اربوں تصاویر اور ان کے متن کی تفصیل کو دیکھنا پڑتا ہے۔ اس مرحلے پر، ڈویلپر کچھ متضاد کام کرتے ہیں۔ آپ جان بوجھ کر امیج کو خراب کر رہے ہیں۔.

  • شور شامل کریں: سسٹم ایک تیز تصویر لیتا ہے (مثلاً ایک بلی) اور بتدریج بے ترتیب ڈیجیٹل جامد (شور) پکسل بذریعہ پکسل شامل کرتا ہے جب تک کہ اصل تصویر مکمل طور پر ناقابل شناخت ہو جائے۔

  • اسے پلٹانے کا طریقہ سیکھیں: AI کا کام شور مچانے والی تصویر کو دیکھنا اور درست طریقے سے اندازہ لگانا ہے کہ کسی خاص مرحلے پر کتنا شور شامل ہوا ہے۔ یہ ایک ارب بار کریں، اور آپ شور کو ختم کرنے کے ماہر بن جائیں گے – افراتفری کو دوبارہ ترتیب میں بدل دیں۔

2. الفاظ کو بصری طور پر جوڑنا (CLIP)

یہ جاننے کے لیے کہ AI کیا جانتا ہے۔ "بالر ٹوپی میں بلی” ایسا لگتا ہے، اکثر اس طرح کے سسٹم سے چلنے والے ٹیکسٹ ٹو امیج پل کا استعمال کرتے ہوئے: کلپ (متضاد زبان کی تصویر سے پہلے کی تربیت)۔

  • CLIP انسانی زبان کو ریاضی کے نقشوں (جسے نقشے کہتے ہیں) میں تبدیل کرتا ہے۔ اندراج

  • اس نقشے میں ‘بلی’ کا لفظ اور بلی کے اصل پکسلز ایک دوسرے کے بہت قریب ہیں۔ اس طرح، جب آپ پرامپٹ ٹائپ کرتے ہیں، تو AI بالکل جانتا ہے کہ میموری سے کن بصری تصورات کو کھینچنا ہے۔

3. جنریشن فیز (ڈیپریڈنگ لوپ)

جب آپ پرامپٹ داخل کرتے ہیں اور "جنریٹ” کو دباتے ہیں تو جادو الٹا ہوتا ہے۔

  • خالی کینوس: AI خالص، 100% بے ترتیب ڈیجیٹل شور کے کینوس سے شروع ہوتا ہے (پرانے ٹی وی جامد کی طرح لگتا ہے)۔

  • فوری ہدایات: AI آپ کے اشارے کو دیکھتا ہے اور آپ کی آنکھوں کی رہنمائی کے لیے ٹیکسٹ ایمبیڈنگز کا استعمال کرتا ہے۔ بے ترتیب جامد کو دیکھیں اور پوچھیں: "اس گندگی میں مجھے بلی کہاں ملے گی؟”

  • مرحلہ وار شور ہٹانا: AI کچھ شور کو گھٹا کر تصویر کو قدرے تیز بناتا ہے۔ اس لوپ کو 20 سے 50 بار دہرائیں۔ ہر قدم پر، دھندلی شکلیں کھردری خاکوں میں بدل جاتی ہیں، بناوٹ ظاہر ہوتی ہے، اور آخر کار ایک تیز، صاف، نئی تصویر ابھرتی ہے۔

بیجوں کے بارے میں دلچسپ حقائق: یہ عمل ہر بار مکمل طور پر تصادفی طور پر شروع ہوتا ہے، لہٰذا ایک ہی پرامپٹ کو دو بار داخل کرنے سے ہمیشہ آپ کو بالکل مختلف تصویر ملے گی۔ بیج

ذیل میں بازی ماڈل کا استعمال کرتے ہوئے شور ہٹانے کی ایک مثال ہے۔

اویکت پھیلاؤ: تیز رہیں (VAE)

پکسل بائی پکسل ہائی ریزولوشن امیجز بنانے کے لیے بہت زیادہ کمپیوٹنگ پاور کی ضرورت ہوتی ہے۔ کنزیومر ہارڈویئر کے پکسل اسپیس میں براہ راست ایسا کرنے کی کوشش کرنے سے آپ کا کمپیوٹر پگھل جائے گا اور ایک نسل بنانے میں گھنٹے لگیں گے۔

اس مسئلے کو حل کرنے کے لیے نئے ماڈلز ممکنہ پھیلاؤ.

مکمل سائز کی تصاویر کے ساتھ کام کرنے کے بجائے انکوڈر تصاویر کو چھوٹے، زیادہ تجریدی ریاضیاتی خالی جگہوں میں تبدیل کیا جا سکتا ہے ( "ممکنہ جگہ”)۔ اس کے بارے میں ایک چھوٹے کھیل کے میدان کے طور پر سوچیں جہاں تمام شور / شور کو منسوخ کرنے والی ریاضی ہوتی ہے۔ چونکہ یہ کھیل کا میدان بہت چھوٹا ہے، حساب ناقابل یقین حد تک تیز ہے۔

جب ڈینوائزنگ لوپ اویکت جگہ میں ختم ہوتا ہے، ڈیکوڈر (خاص طور پر Variational Autoencoder (VAE)) آپ کو دیکھنے کے لیے تیز، اعلی ریزولیوشن امیجز کے ساتھ بیک اپ کرتا ہے۔

QVAC کے ذریعہ تعاون یافتہ فن تعمیرات

QVAC کے ساتھ مقامی تخمینہ چلانا SDK کو ایک اصلاح شدہ، کمیونٹی کے زیر انتظام C++ بیک اینڈ سے جوڑتا ہے۔ QVAC مختلف AI طریقوں کے لیے ہارڈویئر بائنڈنگ اور ماڈل لائف سائیکل کا انتظام کرتا ہے۔

  1. متن بنائیں (llama.cpp): یہ بڑے پیمانے پر لینگویج ماڈلز (LLMs) جیسے Llama 3 یا Mistral کے لیے استعمال ہوتا ہے اور خود بخود ٹوکن پیشین گوئیاں چلاتا ہے۔

  2. آڈیو ٹرانسکرپشن (whisper.cpp): انتہائی اصلاح شدہ اسپیچ ٹو ٹیکسٹ کنورژن کے لیے استعمال کیا جاتا ہے۔

  3. تصویر بنائیں (stable-diffusion.cpp / sdcpp-generation): اس ٹیوٹوریل کا فوکس یہ ہے: QVAC تصویر بنانے کے لیے دو الگ الگ طریقوں کی حمایت کرتا ہے، جو کہ منتخب کردہ ماڈل فن تعمیر پر منحصر ہے۔

    • بنڈل ماڈل اپروچ (مستحکم ڈفیوژن 1.5/2.1/XL): روایتی نقطہ نظر جہاں پوری پائپ لائن (ٹیکسٹ انکوڈر، VAE اور بنیادی ڈفیوژن UNet) کو ایک واحد متحد GGUF فائل میں پکایا جاتا ہے، جیسے SD_V2_1_1B_Q8_0

      یہ مقامی تعیناتی کے لیے بہت آسان ہے کیونکہ تصویر بنانا شروع کرنے کے لیے آپ کو صرف ایک فائل کو منظم اور لوڈ کرنے کی ضرورت ہے۔

    • ماڈیولر ملٹی ماڈل اپروچ (فلکس): جدید فن تعمیر جیسے بہاؤ۔1 زیادہ پیچیدہ سیٹ اپ استعمال کریں۔ ایک فائل کے بجائے، Flux اپنے کمپیوٹیشنل دماغ کو الگ الگ اجزاء میں تقسیم کرتا ہے۔ یہ کور ڈفیوژن ٹرانسڈیوسر (DiT) ماڈل کو لوڈ کرتا ہے، لیکن بڑے ٹیکسٹ انکوڈرز (جیسے T5-v1.1-xxl اور CLIP-L) اور آزاد VAE ماڈلز کو بھی الگ سے لوڈ کیا جانا چاہیے۔

      متعدد GGUF فائلوں کو بیک وقت لوڈ کرنے کے لیے زیادہ پیچیدہ آرکیسٹریشن کی ضرورت ہوتی ہے، لیکن نمایاں طور پر تیز تر تعمیل اور حقیقت پسندی فراہم کرنے کے لیے بڑے پیمانے پر متن کو سمجھنے کے لیے ایک سرشار ماڈل کا فائدہ اٹھاتا ہے۔

  4. تقریری ترکیب (TTS): پیشہ ورانہ فن تعمیر بشمول: چیٹر باکس (ٹرانسفارمر پر مبنی زیرو شاٹ آواز کی نقل) اور سپرسونک ( بازی پر مبنی تقریر کی تردید)۔

ایپل میک ہارڈویئر پر مقامی طور پر مشین لرننگ ماڈل چلاتے وقت، QVAC اس کے لیے ایک کمپیوٹ پائپ لائن مرتب کرکے خود بخود عملدرآمد کو تیز کرنے کی کوشش کرتا ہے: دھات سسٹم کے GPU کو استعمال کرنے کے لیے یہ ایک API ہے۔

اگر آپ کے پاس Apple Silicon Mac (M1, M2, M3, M4, یا M5 چپ) ہے تو یہ بغیر کسی رکاوٹ کے کام کرتا ہے اور آپ کی تخلیقات Apple Neural Engine اور مربوط GPU میموری پر سیکنڈوں میں مرتب ہوتی ہیں۔

تاہم، اگر آپ ایک پرانے انٹیل پر مبنی میک پر الگ کیبل کے ساتھ چل رہے ہیں۔ AMD Radeon GPU (مثال کے طور پر، AMD Radeon Pro 5500M عام طور پر 16 انچ کے MacBook Pro میں پایا جاتا ہے) آپ کو ڈرائیور کی سطح پر بڑی حدوں کا سامنا کرنا پڑے گا۔

  • پرانے AMD ڈسکریٹ GPUs کے لیے macOS میٹل ڈرائیورز جدید ترین مشین لرننگ کمپیوٹ شیڈرز اور میٹرکس ریڈکشن آپریٹرز کو سپورٹ نہیں کرتے ہیں stable-diffusion.cpp.

  • اگر انفرنس ورکر ان غیر تعاون یافتہ آپریشنز کو چلانے کی کوشش کرتا ہے تو ڈرائیور پائپ لائن کو مرتب کرنے میں ناکام ہو جائے گا اور ایک سخت C++ کریش ہو جائے گا (SIGABRT) داخلہ ggml-metal-ops.cpp شیڈر انکوڈر، بیک گراؤنڈ ورکر کا عمل غیر متوقع طور پر ختم ہو جاتا ہے۔

اگر آپ اس ہارڈ ویئر کی رکاوٹ کو ٹکراتے ہیں تو، ڈیفالٹ GPU کنفیگریشن آپ کی ایپلیکیشن کے کریش ہونے کا سبب بن جائے گی جب بھی آپ تصویر کی تخلیق کو متحرک کریں گے۔

اس مسئلے کو حل کرنے کے لیے، آپ کو ماڈل کنفیگریشن پیرامیٹرز سیٹ کرنے کے بجائے اپنے ماڈل کو CPU پر چلانے کے لیے کنفیگر کرنا چاہیے۔ device کو "cpu" ایک دھاگے کی وضاحت کرتا ہے، جیسے threads: 4)۔ CPU پر تصویر بنانے میں GPU کی نسبت زیادہ وقت لگتا ہے، لیکن یہ تمام سسٹمز پر کامیابی کے ساتھ چلتا ہے، اس سے قطع نظر کہ GPU کتنا پرانا یا محدود ہے۔

تصویر بنانے کی پائپ لائن

لوکل ایگزیکیوشن لائف سائیکل کو مربوط کرنے کے لیے، ہماری ایپ ریئل ٹائم ایونٹ پائپ لائن ترتیب دیتی ہے۔

[Browser Client]                                  [Node.js Server]
       |                                                 |
       | ------ 1. Connects & Checks Model --------->    |
       | <----- 2. Downloads & Loads Model ----------     | (Model Cached locally)
       |                                                 |
       | ------ 3. Submits prompt ("Cozy cabin...") ->  |
       |                                                 |
       |                                                 | === [ QVAC Inference Engine ] ===
       |                                                 | 
       | <----- 4. Denoising Step Updates (e.g. 5/20) -- | (Streams steps in real time)
       |                                                 |
       | <----- 5. Sends final image (Base64 DataURL) -- | (Direct in-memory payload)
       |                                                 |

مکمل نفاذ

آئیے عمل درآمد کو دیکھتے ہیں۔ آپ پورے پروجیکٹ کے ذخیرے کو کلون کرکے اس کی پیروی کرسکتے ہیں، یا اسے چلا کر پروجیکٹ فولڈر بنا کر شروع سے بنا سکتے ہیں: npm init -yانحصار انسٹال کریں (@qvac/sdk, express, socket.io, concurrently) اور ترتیب "type": "module" آپ کا package.json.

1. سرور کی ترتیب (server.js)

نام کی ایک فائل بنائیں۔ server.js مندرجہ ذیل نفاذ کو چسپاں کریں:

import express from 'express';
import path from 'path';
import http from 'http';
import { Server } from 'socket.io';
import fs from 'fs';
import { fileURLToPath } from 'url';
import { loadModel, unloadModel, getLoadedModelInfo, diffusion, SD_V2_1_1B_Q8_0 } from "@qvac/sdk";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const app = express();
const server = http.createServer(app);
const io = new Server(server);

const PORT = process.env.PORT || 3000;

app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));

const CONFIG_PATH = path.join(__dirname, '.device-preference.json');

function getPreferredDevice() {
  try {
    if (fs.existsSync(CONFIG_PATH)) {
      const data = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
      return data.device || null;
    }
  } catch (err) {
    console.error('Failed to read device preference:', err.message);
  }
  return null;
}

function setPreferredDevice(device) {
  try {
    fs.writeFileSync(CONFIG_PATH, JSON.stringify({ device }), 'utf8');
  } catch (err) {
    console.error('Failed to write device preference:', err.message);
  }
}

// Global model state
let loadedModelId = process.modelId || null;
let modelLoadPercent = 0;
let modelLoadStatus="Awaiting trigger...";
let isModelLoading = false;

const modelSize = (SD_V2_1_1B_Q8_0.expectedSize / (1024 * 1024 * 1024)).toFixed(2) + ' GB';

function broadcastModelProgress(percent, status) {
  io.emit('model-download-progress', { percent, status, size: modelSize });
}

io.on('connection', (socket) => {
  console.log('Client connected:', socket.id);

  socket.on('disconnect', () => {
    console.log('Client disconnected:', socket.id);
  });

  // Trigger model download
  socket.on('trigger-model-download', async () => {
    // If already loaded, verify it's still alive in the worker
    if (loadedModelId) {
      try {
        await getLoadedModelInfo({ modelId: loadedModelId });
        socket.emit('model-download-progress', {
          percent: 100,
          status: 'Model fully loaded locally.',
          size: modelSize
        });
        return;
      } catch (err) {
        console.log('Model ID was stale/not found, resetting state and reloading...', err.message);
        loadedModelId = null;
        process.modelId = null;
      }
    }

    // If currently loading, report current progress
    if (isModelLoading) {
      socket.emit('model-download-progress', {
        percent: Math.round(modelLoadPercent),
        status: modelLoadStatus,
        size: modelSize
      });
      return;
    }

    isModelLoading = true;
    modelLoadPercent = 0;
    modelLoadStatus="Initiating model download...";
    broadcastModelProgress(modelLoadPercent, modelLoadStatus);

    try {
      console.log('Starting model download...');
      const preferredDevice = getPreferredDevice();
      const loadConfig = { prediction: "v" };
      if (preferredDevice) {
        loadConfig.device = preferredDevice;
        if (preferredDevice === 'cpu') {
          loadConfig.threads = 4;
        }
        console.log(`Using cached device preference: ${preferredDevice}`);
      }

      loadedModelId = await loadModel({
        modelSrc: SD_V2_1_1B_Q8_0,
        modelType: "sdcpp-generation",
        modelConfig: loadConfig,
        onProgress: (p) => {
          modelLoadPercent = p.percentage;
          modelLoadStatus = p.percentage >= 100 ? 'Model fully loaded locally.' : `Downloading model weights... (${p.percentage.toFixed(1)}%)`;
          broadcastModelProgress(Math.round(modelLoadPercent), modelLoadStatus);
        }
      });
      process.modelId = loadedModelId;

      isModelLoading = false;
      console.log('Model loaded successfully. ID:', loadedModelId);
    } catch (err) {
      isModelLoading = false;
      modelLoadPercent = 0;
      modelLoadStatus="Failed to load model: " + err.message;
      console.error('Failed to load model:', err);
      broadcastModelProgress(0, modelLoadStatus);
      socket.emit('error_event', { message: 'Failed to load model: ' + err.message });
    }
  });

  socket.on('generate', async (data) => {
    const { prompt, ratio } = data;
    if (!prompt || prompt.trim() === '') {
      socket.emit('error_event', { message: 'Prompt is required' });
      return;
    }

    if (!loadedModelId) {
      socket.emit('error_event', { message: 'Model is not loaded yet' });
      return;
    }

    const runDiffusion = async (modelIdToUse) => {
      socket.emit('progress', {
        percent: 0,
        status: 'Starting diffusion process...',
        sub: 'DIFFUSION INITIALIZING'
      });

      console.log(`Generating image for prompt: "({prompt}" with ratio: ){ratio} using model ID: ${modelIdToUse}`);

      const { progressStream, outputs, stats } = diffusion({
        modelId: modelIdToUse,
        prompt,
      });

      // Stream progress steps
      for await (const { step, totalSteps } of progressStream) {
        const percent = Math.round((step / totalSteps) * 100);
        socket.emit('progress', {
          percent,
          status: `Denoising step ({step}/){totalSteps}...`,
          sub: 'RUNNING DIFFUSION'
        });
      }

      // Resolve output buffers
      const buffers = await outputs;
      if (!buffers || buffers.length === 0) {
        throw new Error('No image buffer returned from diffusion model.');
      }

      // Convert image buffer to a base64 Data URL instead of saving to disk
      const base64Data = Buffer.from(buffers[0]).toString('base64');
      const dataUrl = `data:image/png;base64,${base64Data}`;

      // Emit success
      socket.emit('success', {
        url: dataUrl,
        prompt,
        seed: (await stats).seed || -1
      });

      console.log(`Image generated and emitted successfully as base64 Data URL.`);
    };

    try {
      await runDiffusion(loadedModelId);
    } catch (err) {
      console.error('Image generation failed:', err);

      const isCrash = err.code === 50205 || (err.message && err.message.includes('WORKER_CRASHED'));
      if (isCrash) {
        console.log('Worker crashed during GPU execution. Attempting CPU fallback...');

        // Save device preference so we load CPU directly next time and prevent double loading
        setPreferredDevice('cpu');

        // Reset the stale model state
        loadedModelId = null;
        process.modelId = null;

        socket.emit('progress', {
          percent: 0,
          status: 'GPU driver crashed. Automatically falling back to CPU mode...',
          sub: 'CPU FALLBACK LOADING'
        });

        try {
          console.log('Loading model on CPU...');
          isModelLoading = true;
          modelLoadPercent = 0;
          modelLoadStatus="Loading CPU model weights...";
          broadcastModelProgress(modelLoadPercent, modelLoadStatus);

          loadedModelId = await loadModel({
            modelSrc: SD_V2_1_1B_Q8_0,
            modelType: "sdcpp-generation",
            modelConfig: { prediction: "v", device: 'cpu', threads: 4 },
            onProgress: (p) => {
              modelLoadPercent = p.percentage;
              modelLoadStatus = `Loading CPU model weights... (${p.percentage.toFixed(1)}%)`;
              broadcastModelProgress(Math.round(modelLoadPercent), modelLoadStatus);
            }
          });
          process.modelId = loadedModelId;
          isModelLoading = false;
          console.log('Model loaded successfully on CPU. ID:', loadedModelId);

          // Retry diffusion on CPU
          await runDiffusion(loadedModelId);
        } catch (cpuErr) {
          console.error('CPU fallback execution failed:', cpuErr);
          isModelLoading = false;
          socket.emit('error_event', { message: 'Image generation failed on CPU: ' + cpuErr.message });
        }
      } else {
        if (err.message && (err.message.includes('MODEL_NOT_FOUND') || err.message.includes('not found'))) {
          loadedModelId = null;
          process.modelId = null;
          broadcastModelProgress(0, 'Model state lost. Please re-trigger download.');
        }
        socket.emit('error_event', { message: 'Image generation failed: ' + err.message });
      }
    }
  });
});

app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

server.listen(PORT, () => {
  console.log(`Server is running at http://localhost:${PORT}`);
});

// Clean exit handler
async function handleCleanup() {
  const modelId = process.modelId || loadedModelId;
  if (modelId && modelId !== 'mock-model-id') {
    try {
      await unloadModel({ modelId, clearStorage: false });
    } catch (err) {}
  }
  process.exit(0);
}

process.on('SIGINT', handleCleanup);
process.on('SIGTERM', handleCleanup);

2. فرنٹ اینڈ آرکیٹیکچر کا خلاصہ

چونکہ ہماری ایپلیکیشن مکمل طور پر مقامی طور پر چلتی ہے، فرنٹ اینڈ ایک صفحہ کی ویب ایپ ہے جو ونیلا ایچ ٹی ایم ایل، سی ایس ایس، اور کلائنٹ سائیڈ جاوا اسکرپٹ کے ساتھ بنائی گئی ہے جو ایکسپریس سرور کے ساتھ بات چیت کرتی ہے۔ Socket.io ویب ساکٹ۔

UI ٹیمپلیٹس اور اسٹائل شیٹس کی سیکڑوں لائنوں کے ساتھ اس ٹیوٹوریل کو پیچیدہ بنانے کے بجائے، ہم مکمل طور پر بیک اینڈ آرکیسٹریشن پر توجہ مرکوز کریں گے۔ آپ ہمارے GitHub ریپوزٹری سے مکمل HTML لے آؤٹ، Tailwind طرزیں، اور کلائنٹ اسکرپٹس حاصل کر سکتے ہیں۔

مندرجہ ذیل خلاصہ کرتا ہے کہ کس طرح کلائنٹ سرور کے ساتھ اندرونی طور پر بات چیت کرتا ہے۔

  1. پری فلائٹ مطابقت پذیری (trigger-model-download): جیسے ہی صفحہ لوڈ ہوتا ہے، کلائنٹ ایک WebSocket کنکشن قائم کرتا ہے اور trigger-model-download. سرور اسے روکتا ہے، چیک کرتا ہے کہ آیا ماڈل کیشڈ/لوڈ ہے، اور براڈکاسٹنگ کا عمل شروع کرتا ہے۔

  2. شور ہٹانے کا سلسلہ (progress): تصویر کی تخلیق کے دوران، سرور مسلسل پیش رفت کے واقعات کو سٹریم کرتا ہے جس میں منحرف اعدادوشمار ہوتے ہیں، جیسے Denoising step 12/20...)۔ کلائنٹ اس کے مطابق بصری پیش رفت بار اور اسٹیٹس لیبل کو اپ ڈیٹ کرتا ہے۔

  3. ڈیٹا URL پاس کریں (success): پھیلنے کا مرحلہ مکمل ہونے کے بعد، سرور بائنری امیج بفر کو بیس 64 سٹرنگ میں تبدیل کرتا ہے اور success واقعہ کلائنٹ اس Base64 ڈیٹا URL کو براہ راست ماخذ سے منسلک کرتا ہے۔ براہ راست مقامی ڈسپلے اور فوری ڈاؤن لوڈ کے لیے عناصر۔

کوڈ بیس تجزیہ

آئیے ان کلیدی میکانزم پر گہری نظر ڈالتے ہیں جو مقامی آف لائن امیج جنریٹرز کو آسانی سے کام کرتے ہیں۔

1. ملٹی کلائنٹ ماڈل ID بائنڈنگ (process.modelId)

کوانٹائزڈ وزن میموری کی ایک قابل ذکر مقدار استعمال کرتے ہیں۔ ہر بار جب ہم کال کرتے ہیں loadModel()QVAC ایک علیحدہ C++ پس منظر کے عمل کو بوٹ کرتا ہے ( Bare کارکنان) جی جی ایم ایل رن ٹائم کی میزبانی کے لیے۔

جب کلائنٹ صفحہ کو ریفریش کرتا ہے یا کوئی مختلف براؤزر ٹیب کھولتا ہے تو متعدد پراسیسز پیدا کرنے یا 2.3GB GGUF ماڈل کو متعدد بار لوڈ کرنے سے بچنے کے لیے، ہم لوڈ شدہ ماڈل ID کو عالمی سطح پر نوڈ پر اسٹور کرتے ہیں۔ process اعتراض:

let loadedModelId = process.modelId || null;
// ...
process.modelId = loadedModelId;

یہ پورے عمل کے لیے سنگلٹن رجسٹری کے طور پر کام کرتا ہے۔ تاہم، عالمی متغیرات کا استعمال درج ذیل مسائل کا سبب بنتا ہے: پرانے کارکن کے عمل. اگر بیک گراؤنڈ ورکر کا عمل بعد میں کریش ہو جاتا ہے یا کلائنٹ کے ماڈل لوڈ کو متحرک کرنے اور ID حاصل کرنے کے بعد ختم ہو جاتا ہے۔ process.modelId یہ مردہ حوالوں سے بھرا ہوا ہے۔

اسے ٹھیک کرنے کے لیے، جب بھی کوئی نیا کلائنٹ جوڑتا ہے اور ماڈل ڈاؤن لوڈ ٹرگر کی درخواست کرتا ہے، تو ہم ماڈل ID کو پہلے سے چلاتے ہیں: getLoadedModelInfo:

if (loadedModelId) {
  try {
    await getLoadedModelInfo({ modelId: loadedModelId });
    socket.emit('model-download-progress', { percent: 100, status: 'Model fully loaded locally.' });
    return;
  } catch (err) {
    console.log('Model ID was stale, resetting state...', err.message);
    loadedModelId = null;
    process.modelId = null;
  }
}

جب بیک گراؤنڈ ورکر مر جاتا ہے۔ getLoadedModelInfo ایک خرابی ہوتی ہے۔ کیچ بلاک اسے روکتا ہے، پرانے حوالہ کو صاف کرتا ہے، اور لوڈنگ روٹین کو محفوظ طریقے سے دوبارہ شروع کرتا ہے۔

[!IMPORTANT] عمل سنگلٹن سالمیت: تخمینہ شروع کرنے سے پہلے ہمیشہ پری فلائٹ ماڈل کی حالت کی مرئیت رکھیں۔ توثیق کے بغیر کوشش کریں۔ diffusion() پرانے ماڈل IDs کا نتیجہ فوری طور پر کلائنٹ سائیڈ کنکشن ٹائم آؤٹ اور خودکار بیک اینڈ ورکر کی خرابیوں کا باعث بنتا ہے۔

2. ان میموری امیج سیریلائزیشن (صفر ڈسک لکھتا ہے)

تخلیق کردہ امیج کو سرور کی ہارڈ ڈرائیو پر لکھنے سے اہم I/O اوور ہیڈ ہوتا ہے۔ پرانی تصویری فائلوں کو حذف کرنے کے لیے حسب ضرورت کرون کلین اپ اسکرپٹ لکھنا پڑتا ہے، اور زیادہ صارف ٹریفک والے سسٹم پر آپ کو ڈسک کی جگہ ختم ہونے کا خطرہ ہوتا ہے۔

QVAC کے بعد diffusion() فنکشن تیار کردہ PNG فائل کو براہ راست ان میموری بائنری بفر میں آؤٹ پٹ کرتا ہے (Uint8Arrayمقامی فائل سسٹم کو مکمل طور پر نظرانداز کرتے ہوئے بائنری سرنی کو براہ راست میموری میں بیس 64 سٹرنگ میں سیریلائز کریں۔

const base64Data = Buffer.from(buffers[0]).toString('base64');
const dataUrl = `data:image/png;base64,${base64Data}`;

یہ ڈیٹا URL کلائنٹ کو WebSocket کے ذریعے بھیجا جاتا ہے، اور کلائنٹ اسے فوری طور پر تصویری عنصر سے جوڑ دیتا ہے۔

  • کوئی ڈسک اوور ہیڈ نہیں: سرور کبھی بھی ہارڈ ڈرائیو پر ایک بائٹ نہیں لکھتا، SSD لائف کو محفوظ رکھتا ہے اور اسٹوریج بلوٹ کو روکتا ہے۔

  • فوری ترسیل: ڈسک سیریلائزیشن لیٹنسی کو نظرانداز کرتے ہوئے ٹرانسفرز کو مکمل طور پر نیٹ ورک میموری بفرز میں پروسیس کیا جاتا ہے۔

  • آسان کلائنٹ انضمام: کلائنٹس کو جامد تصویری URL پاتھ کی درخواست کرنے کی ضرورت نہیں ہے۔ یہ Base64 ڈیٹا URLs کو براہ راست رینڈر کرتا ہے، جس سے صارفین کو فوری طور پر تصاویر کو محفوظ یا ڈاؤن لوڈ کرنے کی اجازت ملتی ہے۔

3. GPU-CPU فال بیک اور ترجیحی کیش حکمت عملی

مقامی-پہلے AI کے سب سے بڑے چیلنجوں میں سے ایک کلائنٹ ہارڈ ویئر کی نسبت ہے۔ مثال کے طور پر، مجرد AMD Radeon GPUs کے ساتھ پرانے انٹیل میک ایپل کے میٹل فریم ورک کو سپورٹ کرتے ہیں، لیکن اسٹیبل ڈفیوژن انجن کے ذریعے استعمال ہونے والے جدید ٹینسر ریڈکشن آپریٹرز کی کمی ہے، جس کے نتیجے میں سخت C++ کریش ہوتے ہیں (SIGABRT) اندر ggml-metal-ops.cpp.

اپنی ایپلیکیشن کو جاری رکھنے کے لیے مستقل لوڈنگ کا استعمال کریں اور ماڈل لوڈنگ کو دو بار ٹرگر کرنے سے بچیں (ایک بار اسٹارٹ اپ کے وقت غیر موافق GPU پر اور ایک بار پہلے پرامپٹ کریش ہونے کے بعد CPU فال بیک پر)۔ ڈیوائس ترجیحی کیشے فائل(.device-preference.json) C++ کارکن کریش انٹرسیپٹر کے ساتھ:

try {
  await runDiffusion(loadedModelId);
} catch (err) {
  const isCrash = err.code === 50205 || err.message.includes('WORKER_CRASHED');
  if (isCrash) {
    // 1. Cache the CPU preference on disk
    setPreferredDevice('cpu');

    // 2. Reset stale references
    loadedModelId = null;
    process.modelId = null;

    // 3. Automatically load the model on CPU with multi-threading
    loadedModelId = await loadModel({
      modelSrc: SD_V2_1_1B_Q8_0,
      modelType: "sdcpp-generation",
      modelConfig: { prediction: "v", device: "cpu", threads: 4 }
    });
    process.modelId = loadedModelId;

    // 4. Transparently retry generation
    await runDiffusion(loadedModelId);
  }
}

یہ نقطہ نظر دو پرت کے دفاع کا فائدہ اٹھاتا ہے۔

  1. متحرک بحالی: اگر GPU ڈرائیور کی خرابی کی وجہ سے کوئی حادثہ پیش آتا ہے، تو ایپ اسے روک لے گی اور اسے محفوظ کر لے گی۔ "device": "cpu" کو .device-preference.json متحرک طور پر فائل سے وزن کو CPU تھریڈ پر دوبارہ لوڈ کریں اور تخلیق کی دوبارہ کوشش کریں۔ کلائنٹ اسٹیٹس اپ ڈیٹ کی جانچ کرتا ہے جس سے یہ ظاہر ہوتا ہے کہ CPU فال بیک ہو رہا ہے، اور بصورت دیگر ایک مہلک حادثے سے بچ جاتا ہے۔

  2. ترجیحی استقامت: اگلی بار جب سرور شروع ہوتا ہے یا کوئی صفحہ لوڈ ہوتا ہے، پری لوڈ کا معمول ڈسک سے کیش شدہ ترجیحات کو پڑھتا ہے اور فوری طور پر CPU ماڈل کو لوڈ کرتا ہے۔

const preferredDevice = getPreferredDevice(); // Reads .device-preference.json
const loadConfig = { prediction: "v" };
if (preferredDevice) {
  loadConfig.device = preferredDevice;
  if (preferredDevice === 'cpu') {
    loadConfig.threads = 4;
  }
}
loadedModelId = await loadModel({
  modelSrc: SD_V2_1_1B_Q8_0,
  modelType: "sdcpp-generation",
  modelConfig: loadConfig,
  // ...
});

یہ سرور کو بعد کے سیشنز میں ڈپلیکیٹ GPU لوڈ کرنے کی کوششیں کرنے سے روکتا ہے، اس بات کو یقینی بناتا ہے کہ ماڈل صرف ایک بار درست ہارڈویئر ایگزیکیوشن ٹارگٹ پر براہ راست لوڈ ہو۔

[!WARNING] CPU فال بیک لیٹنسی: CPU موڈ موجودہ ہارڈ ویئر میں لچک کو یقینی بناتا ہے، لیکن GPU ہارڈویئر کور کے بجائے ترتیب وار ملٹی تھریڈ کمپیوٹیشن کا استعمال کرتا ہے۔ نتیجے کے طور پر، جنریشن کا وقت نمایاں طور پر لمبا ہوتا ہے (ہم آہنگ GPUs کے لیے 10-15 سیکنڈ کے مقابلے عام طور پر CPUs کے لیے 1-2 منٹ)۔ فال بیک کے دوران صارف کی توقعات کا نظم کرنے کے لیے، آپ کو اپنے UI میں ایک ریسپانسیو پروگریس لوڈر ڈیزائن کرنے کی ضرورت ہے۔

نتیجہ

QVAC کے ساتھ مقامی فرسٹ اسٹیبل ڈفیوژن کو لاگو کرنا آپ کو تخمینہ لاگت اور ڈیٹا کی رازداری پر مکمل کنٹرول فراہم کرتا ہے۔ آن ڈیوائس GGML ماڈلز کو ایک سادہ Node.js WebSocket بیک اینڈ کے ساتھ ملا کر، آپ ریسپانسیو ویب ٹولز بنا سکتے ہیں جو کلاؤڈ APIs پر پیسہ خرچ کیے بغیر مکمل طور پر آف لائن چلتے ہیں۔

چونکہ موبائل اور ڈیسک ٹاپ سسٹم آن چپ آرکیٹیکچرز میں مزید نیورل انجن شامل ہوتے رہتے ہیں، لوکل فرسٹ اے آئی آرکیٹیکچرز جدید ڈویلپرز کے لیے تیزی سے طاقتور آپشن بن جائیں گے۔

وسائل اور اضافی وسائل

اوپر تک سکرول کریں۔