ثلاثة حقول ألغام ستواجهها عند الانتقال من TensorFlow.js إلى LiteRT.js
TuBrief 편집팀
2026년 7월 18일
0
Computing/Software원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
커뮤니티의 다른 글
댓글 (0)
Log in to leave a comment
아직 작성된 글이 없습니다
원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
Log in to leave a comment
아직 작성된 글이 없습니다
إن تشغيل نماذج الذكاء الاصطناعي في متصفح الويب أمر مثير، لكنك سرعان ما تصطدم بالحدود. فعند معالجة صور عالية الدقة أو بدء عمليات حسابية ثقيلة، تبدأ الشاشة بالتقطع. يستخدم TensorFlow.js (TF.js) واجهة WebGL البرمجية وارتباطات نواة JavaScript، وهو ما يتسبب في حمل زائد (overhead) كبير في مصفوفات الحسابات واسعة النطاق.
على الجانب الآخر، قامت LiteRT.js بترحيل وقت تشغيل C++ الأصلي إلى المتصفح عبر تجميعه باستخدام WebAssembly (Wasm). هذا الهيكل يعالج الاختناقات من جذورها. علاوة على ذلك، أصبحت عملية جلب نماذج PyTorch إلى الويب أبسط. ففي السابق، كان عليك التحويل من PyTorch إلى ONNX، ثم المرور عبر TensorFlow للوصول إلى تنسيق TF.js، مما يؤدي إلى كسر توافقية المعاملات وفقدان الدقة. الآن، يمكنك الاكتفاء بمكتبة واحدة (ai-edge-torch) لاستخراج ملف .tflite قياسي وانتهى الأمر.
إذا كنت لا ترغب في التخلي عن أصولك البرمجية الحالية، يمكنك استخدام حزمة @litertjs/tfjs-interop التي توفرها Google. تعتمد هذه الطريقة على الإبقاء على مسار معالجة البيانات (Pre-processing/Post-processing) الخاص بـ TF.js كما هو، واستبدال جزء تنفيذ تنبؤ النموذج الأساسي بـ LiteRT.js فقط.
عادة ما تتطلب نماذج TFLite المحولة من PyTorch بنية تعتمد على القنوات أولاً (Channel-first) وهي (القنوات، الارتفاع، العرض). ومع ذلك، فإن مصفوفة ImageData الخاصة بـ Canvas في المتصفح تكون بتنسيق (الارتفاع، العرض، القنوات) حيث يتم ترتيب البكسلات متتالياً. أنت بحاجة إلى كود معالجة أولية يقوم بهذا التحويل بشكل متزامن دون إثقال كاهل الخيط الرئيسي (Main Thread).
أولاً، نقوم بإنشاء Float32Array من خلال ضرب إجمالي عدد البكسلات في ImageData في 3. بعد ذلك، نحدد إزاحة مواقع القنوات الحمراء والخضراء والزرقاء المسطحة عند 0، وإجمالي عدد البكسلات، وضعف إجمالي عدد البكسلات على التوالي. أخيراً، نقوم بتطبيع قيم البكسل التي تتراوح بين 0 و 255 بالقسمة على 255.0 وتخصيصها لكل إزاحة قناة. هذه هي عملية إعادة ترتيب بيانات الإدخال إلى مخزن تينسور (Tensor) مسطح بتنسيق .
`javascript
/**
const rChannelOffset = 0;
const gChannelOffset = totalPixels;
const bChannelOffset = totalPixels * 2;
for (let i = 0; i < totalPixels; i++) {
const srcIndex = i * 4;
nchwBuffer[rChannelOffset + i] = data[srcIndex] / 255.0;
nchwBuffer[gChannelOffset + i] = data[srcIndex + 1] / 255.0;
nchwBuffer[bChannelOffset + i] = data[srcIndex + 2] / 255.0;
}
return nchwBuffer;
}
`
عند تشغيل التعلم العميق في الواجهة الأمامية، فإن أسوأ سيناريو هو تجمد واجهة المستخدم. لكي يعرض المتصفح رسومًا متحركة سلسة بمعدل 60 إطاراً في الثانية، يجب أن يعالج حلقة الأحداث (Event Loop) المهام المتزامنة في أقل من 16.6 مللي ثانية. لكن عمليات التينسور تعيق بسهولة حلقة العرض الرئيسية أحادية الخيط.
تتميز LiteRT.js بسرعة تنفيذ أساسية أسرع بحوالي 3 مرات من الأدوات المستندة إلى JavaScript التقليدية. وعند إضافة أجهزة تسريع مثل WebGPU أو WebNN، تصل السرعة إلى ما بين 5 إلى 60 ضعفاً مقارنة بوضع CPU. تتطلب واجهة WebNN، التي تستخدم NPU مخصصاً، تفعيل ميزة تكامل وعود JavaScript (JSPI) لربط مجدول نواة WebAssembly المتزامن بحلقة التحكم في الأجهزة غير المتزامنة للمتصفح. للاستفادة من موارد التسريع هذه مع الحفاظ على استجابة الخيط الرئيسي، يجب عزل تهيئة المكتبة ومسار الاستدلال بالكامل داخل Web Worker.
في هذه الحالة، إذا قمت بتمرير المخازن المؤقتة (Buffers) كما هي أثناء تبادل البيانات بين الخيوط، فسيحدث نسخ داخلي للذاكرة مما يؤدي إلى تراكم الحمل الزائد في الذاكرة (CPU و Heap). يجب استخدام الكائنات القابلة للنقل (Transferable Objects) لنقل ملكية عنوان الذاكرة الفيزيائي نفسه للقضاء على زمن الوصول. يتم إلغاء صلاحية المخزن المؤقت الذي نُقلت ملكيته فوراً في سياق المرسل، مما يضمن أمان العمليات بين الخيوط.
`javascript
// litert-worker.js - وحدة Web Worker مخصصة لعمليات استدلال الخلفية
import { loadLiteRt, loadAndCompile, Tensor } from '@litertjs/core';
let compiledModel = null;
let isLoaded = false;
self.onmessage = async (event) => {
const { type, payload } = event.data;
switch (type) {
case 'LOAD_MODEL':
try {
await loadLiteRt(payload.wasmDirectory, { jspi: payload.enableJspi || false });
compiledModel = await loadAndCompile(payload.modelUrl, {
accelerator: payload.accelerator || 'webgpu'
});
isLoaded = true;
self.postMessage({ type: 'MODEL_READY' });
} catch (err) {
self.postMessage({ type: 'ERROR', error: Initialization failed: ${err.message} });
}
break;
case 'RUN_INFERENCE':
if (!isLoaded || !compiledModel) {
self.postMessage({ type: 'ERROR', error: 'Model has not been loaded' });
return;
}
try {
const rawInputData = payload.bufferData;
const inputShape = payload.shape;
const inputTensor = new Tensor(rawInputData, inputShape);
const results = await compiledModel.run(inputTensor);
const cpuOutputTensor = await results[0].moveTo('wasm');
const outputBuffer = cpuOutputTensor.toTypedArray();
inputTensor.delete();
cpuOutputTensor.delete();
results[0].delete();
self.postMessage(
{
type: 'INFERENCE_COMPLETE',
payload: {
data: outputBuffer,
shape: results[0].shape
}
},
[outputBuffer.buffer]
);
} catch (err) {
self.postMessage({ type: 'ERROR', error: `Inference failed: ${err.message}` });
}
break;
default:
self.postMessage({ type: 'UNKNOWN_OP' });
}
};
`
`javascript
// litert-bridge.js - فئة منسق الذكاء الاصطناعي للخيط الرئيسي
export class LiteRtBridge {
constructor(workerPath) {
this.worker = new Worker(workerPath);
this.promiseMap = new Map();
this.tokenCounter = 0;
this.worker.onmessage = (event) => {
const { type, payload, error } = event.data;
if (type === 'MODEL_READY') {
if (this.initResolve) this.initResolve();
} else if (type === 'INFERENCE_COMPLETE') {
const currentToken = this.tokenCounter;
const promiseHandler = this.promiseMap.get(currentToken);
if (promiseHandler) {
promiseHandler.resolve(payload);
this.promiseMap.delete(currentToken);
}
} else if (type === 'ERROR') {
const currentToken = this.tokenCounter;
const promiseHandler = this.promiseMap.get(currentToken);
if (promiseHandler) {
promiseHandler.reject(new Error(error));
this.promiseMap.delete(currentToken);
} else if (this.initReject) {
this.initReject(new Error(error));
}
}
};
}
bootstrap(wasmDirectory, modelUrl, accelerator = 'webgpu') {
return new Promise((resolve, reject) => {
this.initResolve = resolve;
this.initReject = reject;
this.worker.postMessage({
type: 'LOAD_MODEL',
payload: { wasmDirectory, modelUrl, accelerator, enableJspi: true }
});
});
}
execute(inputFloat32Array, inputShape) {
return new Promise((resolve, reject) => {
this.tokenCounter++;
this.promiseMap.set(this.tokenCounter, { resolve, reject });
this.worker.postMessage(
{
type: 'RUN_INFERENCE',
payload: {
bufferData: inputFloat32Array,
shape: inputShape
}
},
[inputFloat32Array.buffer]
);
});
}
}
`
عند استخدام TF.js، كان نمط tf.tidy() هو المعيار لتنظيف التينسورات ضمن نطاق الاستدعاء المتزامن. ولكن مع تداخل الكود غير المتزامن أو الوعود (Promises)، كانت تنشأ أخطاء تؤدي إلى تحرير التينسورات خارج النطاق قبل انتهاء المهمة أو نسيان تجميعها.
تعد LiteRT.js أكثر صرامة؛ فهي ليست خاضعة لعملية جمع القمامة (GC) الخاصة بمحرك المتصفح. لا يمكن لمحركات JavaScript مثل V8 تتبع حالة الذاكرة (Heap) لمساحة ذاكرة WebAssembly الخطية الافتراضية ومخازن WebGPU المؤقتة. إذا لم تقم باستدعاء .delete() بشكل صريح على مثيل التينسور الذي انتهى عمله، فإن ذاكرة المتصفح ستتراكم إلى ما لا نهاية. في الخدمات التي تبث إطارات فيديو عالية الدقة عشرات المرات في الثانية، ستموت علامة التبويب في غضون دقائق.
راحة بالك تكمن في إنشاء فئة تعقب النطاق (Scope Tracker) تقوم بتسجيل عمر التينسورات التي تم إنشاؤها عبر مسار الاستدلال غير المتزامن وتضمن تدميرها بشكل جماعي.
`javascript
/**
/**
/**
/**
`
`javascript
// مثال تنفيذ استدلال ذكاء اصطناعي متعدد غير متزامن آمن وقوي باستخدام متعقب نطاق الذاكرة
export async function runRobustVisionInference(rawPixelArray, compiledModel) {
const scopeTracker = new LiteRtScopeTracker();
try {
return await scopeTracker.enforceScope(async (scope) => {
const inputTensor = scope.register(new Tensor(rawPixelArray, [1, 3, 224, 224]));
const predictionResults = await compiledModel.run(inputTensor);
predictionResults.forEach((tensor) => scope.register(tensor));
const firstOutputTensor = predictionResults[0];
const wasmTransferTensor = scope.register(await firstOutputTensor.moveTo('wasm'));
const targetJsArray = wasmTransferTensor.toTypedArray();
return targetJsArray;
});
} catch (err) {
console.error('حدث عطل فادح أثناء تنفيذ مسار النموذج:', err);
throw err;
}
}
`
لا يمكن تجاهل مشكلة زيادة حجم تحميل الموارد. لضمان عمل "هز الشجرة" (Tree Shaking) في حزم البناء (Bundlers)، يجب إزالة المراجع الثابتة بنمط CommonJS وكتابة الكود المصدري بناءً على بناء جملة وحدات ES6 (import/export). يجب أيضاً التأكد من إعداد sideEffects: false في أداة البناء لجعل الحزمة أخف وزناً.
تختار مكتبة @litertjs/core، وهي وقت تشغيل LiteRT.js الأساسي، التحميل الانتقائي لثلاث مجموعات من أنوية WebAssembly بناءً على أداء الجهاز. في المتصفحات الحديثة مثل Chrome أو Edge، يتم اختيار الوحدة التي تدعم تعدد الخيوط وSIMD (litert_wasm_simd.wasm)، بينما في البيئات القديمة مثل Safari يتم تحميل وحدة الاحتياط الأساسية (litert_wasm.wasm). إذا فشل تجميع GPU، يتم تفعيل وقت تشغيل XNNPACK كجهاز مساعد لنقل معاملات الأجهزة بالكامل إلى بيئة Wasm الآمنة (Sandbox) الخاصة بـ CPU.
لمنع تأخير التحميل الأولي، يجب التحقق من المُسرع المناسب للمواصفات وتحميل الوحدة ديناميكياً.
`javascript
// litert-loader.js - محرك استكشاف الجهاز في وقت التشغيل والربط الديناميكي للمسرعات
export async function bootstrapHighPerformanceInferenceEngine() {
const supportsWebGpu = 'gpu' in navigator;
let chosenAccelerator = 'wasm';
if (supportsWebGpu) {
try {
const gpuAdapter = await navigator.gpu.requestAdapter();
if (gpuAdapter) {
const info = await gpuAdapter.requestDevice();
if (info) {
chosenAccelerator = 'webgpu';
}
}
} catch (e) {
console.warn("فشل فحص ملف تعريف GPU، يتم حل سلسلة التنفيذ إلى WASM كخيار احتياطي.");
}
}
const { loadLiteRt, loadAndCompile } = await import('@litertjs/core');
const cdnWasmHostPath = 'https://cdn.jsdelivr.net/npm/@litertjs/core/wasm/';
await loadLiteRt(cdnWasmHostPath, {
jspi: chosenAccelerator === 'webnn'
});
return {
loadAndCompile,
chosenAccelerator
};
}
`
اجعل نقل ملكية الذاكرة عبر Web Worker وتدمير الكائنات في نطاق Wasm بشكل صريح ضمن ركائز تصميمك. عندما تبدأ في التحكم في تدفق البيانات بشكل مباشر، يمكنك طرح خدمات الذكاء الاصطناعي على الجهاز (On-device AI) في بيئة الإنتاج دون القلق بشأن انهيار المتصفح.