ورشة تطبيقية: بناء PWA أوفلاين لتحويل اللهجات العربية إلى فصحى باستخدام WASM
مقدمة سريعة: لماذا PWA + WASM لتحويل اللهجات إلى فصحى؟
في هذه الورشة سنبني تطبيق Progressive Web App (PWA) قادر على العمل دون اتصال ويحوّل النصوص المكتوبة باللهجات العربية المحلية (مصرية، شامية، خليجية...) إلى العربية الفصحى. الفكرة الأساسية هي تحميل نموذج صغير مُكمَّش إلى جهاز المستخدم وتشغيله داخل المتصفح باستخدام WebAssembly (WASM) مع الاستفادة من WebGPU/WebNN عندما تكون متاحة لتسريع الاستدلال. هذا النهج يُحافظ على خصوصية المستخدم (لا تُرسل البيانات للسيرفر) ويُحسّن زمن الاستجابة وموثوقية العمل في بيئات اتصال متقطّع.
التقنيات الأساسية التي سنغطيها: Service Worker لعمل التطبيق أوفلاين، نظام تخزين وإدارة أوزان النماذج (CacheStorage / IndexedDB)، واجهة WASM أو محرك استدلال خفيف مثل نسخة WASM من llama.cpp أو محركات ONNX/WASM، وواجهة مستخدم بسيطة لإدخال النص والحصول على الفصحى المحوّلة.
لمعرفة أن WebGPU/WebNN تُعدان عامل تسريع رئيسي للاستدلال داخل المتصفح، اطلع على جهود تشغيل ONNX وTransformer.js عبر WebGPU، بالإضافة إلى وثائق WebGPU.
معمارية الحل ومكوّناته
سنبني التطبيق على المكوّنات التالية:
- واجهة الويب (Frontend): صفحة HTML/JS (يمكن استخدام إطار مثل Svelte/React) مسؤولة عن إدخال النص، عرض النتائج، وإدارة حالة التحميل/التخزين.
- Service Worker: يعالج التخزين المؤقت للملفات الثابتة وملف النموذج (model weights) ويُمكّن التطبيق من العمل أوفلاين وفق استراتيجيات caching مناسبة. يُرجى اتباع ممارسات PWA القياسية عند كتابة Service Worker.
- محرك استدلال داخل المتصفح (WASM / WebGPU): مكوّن WASM مُجمَّع من مكتبة مثل llama.cpp / wllama أو محرك ONNX Runtime Web. هذا المحرك يستدعي الوزنات المحمّلة ويُنفّذ عمليات tokenization وinference. يُمكن الاستفادة من WebGPU أو WebNN عند توافرها لتسريع العمليات الحسابية.
- تخزين النماذج والوزنات: تحميل ملف نموذج بصيغة مدمجة (GGUF/ONNX/quantized) ثم تخزينه في CacheStorage أو IndexedDB بعد التحميل الأول، مع استراتيجيات تحقق وإصدار (ETag / checksum) لتحديثه عند الحاجة.
- Web Worker: تشغيل WASM بعيدًا عن الـmain thread لمنع تجميد واجهة المستخدم، والتواصل عبر postMessage.
هناك مشاريع وتجارب عملية تُظهر كيف يمكن تشغيل نسخ مصغّرة من LLMs في المتصفح باستخدام WASM وواجهات WebGPU، بما في ذلك ربطات WASM لـ llama.cpp (wllama) ومحركات WebLLM وONNX Runtime Web. هذه الموارد مفيدة كنقطة انطلاق.
خطوات تنفيذية مختصرة مع أمثلة للشفرة
فيما يلي الخطوات الرئيسية مع مقتطفات توضيحية. الهدف أن يكون التطبيق بسيطًا وقابلًا للتوسع.
1) هيكل المشروع
- /index.html — واجهة المستخدم
- /app.js — منطق الواجهة وتحميل النموذج وتشغيل الاستدلال
- /sw.js — Service Worker لإدارة الكاش والعمل أوفلاين
- /wasm/engine.wasm — محرك الاستدلال المجمّع (مثلاً wllama أو ONNX Runtime Web)
- /models/arabic_transformer.gguf — ملف النموذج المصغَّر (quantized)
2) Service Worker — استراتيجية تحميل النموذج وتخزينه
const CACHE_NAME = 'app-v1';
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
return cache.addAll(['/','/index.html','/app.js','/wasm/engine.wasm']);
})
);
});
self.addEventListener('fetch', event => {
// cache-first for app shell, network-first for model updates with fallback to cache
});
راجع أفضل ممارسات PWA لتجنب مشكلات cache-busting عند نشر تحديثات.
3) تحميل النموذج وتشغيل WASM في Web Worker
// main thread (app.js)
const worker = new Worker('inference-worker.js');
worker.postMessage({ type: 'load-model', url: '/models/arabic_transformer.gguf' });
worker.onmessage = e => {
if (e.data.type === 'result') showResult(e.data.text);
};
// inference-worker.js
self.onmessage = async (e) => {
if (e.data.type === 'load-model') {
const resp = await fetch(e.data.url);
const blob = await resp.arrayBuffer();
// تحميل إلى الذاكرة وإرسالها إلى WASM engine
// initWasmEngine(blob)...
}
if (e.data.type === 'infer') {
const output = await runInference(e.data.input);
postMessage({ type: 'result', text: output });
}
};
تأكد من تشغيل محرك WASM خارج الـmain thread لأن عمليات الاستدلال قد تكون كثيفة CPU. إذا توفَّر WebGPU أو WebNN فابدأ بالسعي لاستخدامهما كخيار تسريع عبر ONNX Runtime Web أو محركات WebLLM.
نصائح أداء، حدود وحجم النموذج المأمول
نقاط يجب مراعاتها:
- حجم النموذج: المتصفحات محدودة بالذاكرة، لذا اعمل على نماذج صغيرة جداً (عشرات إلى مئات ميغابايت بعد الـquantization) أو استخدم تقنيات distillation/quantization قبل النشر.
- الكمّيّات (Quantization): تحويل النموذج إلى دقّة منخفضة (مثل int8 أو مخططات مخصصة) يقلّل الحجم ويُسرّع الاستدلال لكنه قد يؤثر على جودة التحويل بين اللهجات والفصحى.
- WebGPU/WebNN كخيار تسريع: عند توافرها تقدّم WebGPU/WebNN تسريعاً كبيراً مقارنة بتشغيل كل شيء على CPU عبر WASM؛ لكن لا يعتمد التطبيق عليها كخيار وحيد—وفر مسارًا احتياطياً عبر WASM.
- تحميل كسيناريو تدريجي: ضع تجربة مبنية على النصوص الصغيرة أولًا، وحمّل النموذج الكامل عند الحاجة، مع progress UI ووضعيات "خفيف/مُحسّن للبيانات المحمولة".
- أمان وخصوصية: تشغيل النموذج محليًا يبقي البيانات على جهاز المستخدم، لكن تأكد من توقيع النموذج والتحقق من سلامته قبل التنفيذ لمنع استغلال ملفات WASM خبيثة.
ملحوظة عملية: المجتمع فتح مداخل عديدة لتشغيل LLMs خفيفة في المتصفح—مشروعات مثل wllama تُظهر كيفية ربط llama.cpp بـWASM لتشغيل نماذج مصغّرة محليًا. اطلع على أمثلة جاهزة قبل إعادة بناء كل شيء من الصفر.
خاتمة وخطوات قادمة
بهذه الورشة قُدمت خارطة طريق عملية لبناء PWA يعمل أوفلاين ويحوّل اللهجات العربية إلى فصحى داخل المتصفح باستخدام WASM، مع إمكانية تسريع عبر WebGPU/WebNN حيثما توفرت. التحديات الرئيسية تبقى حجم النماذج وجودة التحويل مقابل قيود الذاكرة والأداء على الأجهزة المحمولة.
خطوات مقترحة للتطوير المستقبلي:
- تحضير مجموعة بيانات موازنة للهجات مختلفة لإعادة تدريب/تقطير نموذج صغير (distillation).
- تجربة تنسيقات النماذج (GGUF/ONNX/MLC) وطرق quantization متعددة لقياس توازن الحجم/الدقة.
- دمج آلية تحديث آمنة للنموذج (signed updates) وواجهة لإدارة الإصدارات من داخل PWA.
مصادر للقراءة والتطبيق العملي: وثائق ONNX Runtime Web وملفات عرض WebGPU بالإضافة إلى مستودعات wllama وllama.cpp لتجارب تشغيل LLM داخل المتصفح.
ملاحظة: تواريخ ودعم واجهات الويب (WebGPU/WebNN) تتقدّم بسرعة—راجع روابط المشاريع الرسمية للنسخ والمزودين المدعومين قبل اختيار الاعتماد على تسريع GPU داخل المتصفح.