إنشاء حزم متصفح ذات حجم محسّن باستخدام TensorFlow.js

ملخص

يوفر TensorFlow.js 3.0 الدعم لبناء حزم متصفح موجهة نحو الإنتاج ومُحسَّنة الحجم . وبعبارة أخرى، نريد أن نسهل عليك إرسال كمية أقل من JavaScript إلى المتصفح.

هذه الميزة موجهة نحو المستخدمين الذين لديهم حالات استخدام إنتاجية والذين سيستفيدون بشكل خاص من إزالة وحدات البايت من حمولتهم (وبالتالي هم على استعداد لبذل الجهد لتحقيق ذلك). لاستخدام هذه الميزة، يجب أن تكون على دراية بوحدات ES ، وأدوات تجميع JavaScript مثل حزمة الويب أو مجموعة التحديثات ، ومفاهيم مثل اهتزاز الشجرة/التخلص من التعليمات البرمجية الميتة .

يوضح هذا البرنامج التعليمي كيفية إنشاء وحدة Tensorflow.js مخصصة يمكن استخدامها مع أداة تجميع لإنشاء بنية محسّنة الحجم لبرنامج يستخدم Tensorflow.js.

المصطلح

في سياق هذه الوثيقة، هناك بعض المصطلحات الأساسية التي سنستخدمها:

وحدات ES - نظام وحدة JavaScript القياسي . تم تقديمه في ES6/ES2015. يمكن تحديدها عن طريق استخدام بيانات الاستيراد والتصدير .

التجميع - أخذ مجموعة من أصول JavaScript وتجميعها/تجميعها في واحد أو أكثر من أصول JavaScript التي يمكن استخدامها في المتصفح. هذه هي الخطوة التي تنتج عادةً الأصول النهائية التي يتم تقديمها للمتصفح. ستقوم التطبيقات بشكل عام بتجميعها مباشرة من مصادر المكتبة المنقولة . تشتمل أدوات الحزم الشائعة على مجموعة التحديثات وحزمة الويب . تُعرف النتيجة النهائية للتجميع باسم الحزمة (أو أحيانًا كقطعة إذا تم تقسيمها إلى أجزاء متعددة)

هز الشجرة / إزالة الكود الميت - إزالة الكود الذي لا يستخدمه التطبيق المكتوب النهائي. ويتم ذلك أثناء التجميع، عادةً في خطوة التصغير.

العمليات (Ops) - عملية رياضية على موتر واحد أو أكثر تنتج موترًا واحدًا أو أكثر كمخرجات. العمليات عبارة عن كود "عالي المستوى" ويمكنها استخدام عمليات أخرى لتحديد منطقها.

Kernel - تنفيذ محدد لعملية مرتبطة بقدرات أجهزة محددة. النواة "منخفضة المستوى" ومخصصة للواجهة الخلفية. تحتوي بعض العمليات على تعيين واحد لواحد من العملية إلى النواة بينما تستخدم العمليات الأخرى حبات متعددة.

النطاق وحالات الاستخدام

استنتاج نماذج الرسم البياني فقط

حالة الاستخدام الأساسية التي سمعنا عنها من المستخدمين ذوي الصلة بهذا، والتي ندعمها في هذا الإصدار هي حالة الاستدلال باستخدام نماذج الرسوم البيانية TensorFlow.js . إذا كنت تستخدم نموذج طبقات TensorFlow.js ، فيمكنك تحويله إلى تنسيق نموذج الرسم البياني باستخدام محول tfjs . يعد تنسيق نموذج الرسم البياني أكثر كفاءة في حالة استخدام الاستدلال.

معالجة Tensor منخفضة المستوى باستخدام tfjs-core

حالة الاستخدام الأخرى التي ندعمها هي البرامج التي تستخدم مباشرة حزمة @tensorflow/tjfs-core لمعالجة الموتر ذي المستوى الأدنى.

نهجنا في البناء المخصص

تتضمن مبادئنا الأساسية عند تصميم هذه الوظيفة ما يلي:

  • حقق أقصى استفادة من نظام وحدة JavaScript (ESM) واسمح لمستخدمي TensorFlow.js بالقيام بنفس الشيء.
  • اجعل TensorFlow.js قابلاً للاهتزاز قدر الإمكان من خلال المجمعات الموجودة (مثل حزمة الويب، أو مجموعة التحديثات، وما إلى ذلك). يتيح ذلك للمستخدمين الاستفادة من جميع إمكانيات أدوات الحزم هذه بما في ذلك ميزات مثل تقسيم التعليمات البرمجية.
  • حافظ قدر الإمكان على سهولة الاستخدام للمستخدمين الذين ليسوا حساسين لحجم الحزمة . وهذا يعني أن إنشاءات الإنتاج ستتطلب المزيد من الجهد نظرًا لأن العديد من الإعدادات الافتراضية في مكتباتنا تدعم سهولة الاستخدام على الإصدارات المحسنة الحجم.

الهدف الأساسي لسير العمل لدينا هو إنتاج وحدة JavaScript مخصصة لـ TensorFlow.js تحتوي فقط على الوظائف المطلوبة للبرنامج الذي نحاول تحسينه. نحن نعتمد على المجمعات الموجودة للقيام بالتحسين الفعلي.

بينما نعتمد بشكل أساسي على نظام وحدة JavaScript، فإننا نوفر أيضًا أداة CLI مخصصة للتعامل مع الأجزاء التي ليس من السهل تحديدها من خلال نظام الوحدة في التعليمات البرمجية التي يواجهها المستخدم. مثالان على ذلك هما:

  • مواصفات النموذج المخزنة في ملفات model.json
  • نظام إرسال النواة الخاص بالواجهة الخلفية الذي نستخدمه.

وهذا يجعل إنشاء بنية tfjs مخصصة أكثر تعقيدًا من مجرد توجيه المجمّع إلى حزمة @tensorflow/tfjs العادية.

كيفية إنشاء حزم مخصصة محسنة الحجم

الخطوة 1: تحديد النواة التي يستخدمها برنامجك

تتيح لنا هذه الخطوة تحديد جميع النوى المستخدمة بواسطة أي نماذج تقوم بتشغيلها أو كود ما قبل/ما بعد المعالجة في ضوء الواجهة الخلفية التي حددتها.

استخدم tf.profile لتشغيل أجزاء تطبيقك التي تستخدم Tensorflow.js والحصول على النواة. سيبدو شيئا من هذا القبيل

const profileInfo = await tf.profile(() => {
  // You must profile all uses of tf symbols.
  runAllMyTfjsCode();
});

const kernelNames = profileInfo.kernelNames
console.log(kernelNames);

انسخ قائمة النوى هذه إلى الحافظة الخاصة بك للانتقال إلى الخطوة التالية.

تحتاج إلى إنشاء ملف تعريف للكود باستخدام نفس الواجهة (الواجهات) الخلفية التي تريد استخدامها في الحزمة المخصصة الخاصة بك.

ستحتاج إلى تكرار هذه الخطوة إذا تغير النموذج الخاص بك أو تغير رمز ما قبل/بعد المعالجة.

الخطوة 2. اكتب ملف تكوين لوحدة tfjs المخصصة

فيما يلي مثال لملف التكوين.

تبدو هكذا:

{
  "kernels": ["Reshape", "_FusedMatMul", "Identity"],
  "backends": [
      "cpu"
  ],
  "models": [
      "./model/model.json"
  ],
  "outputPath": "./custom_tfjs",
  "forwardModeOnly": true
}
  • النوى: قائمة النوى التي سيتم تضمينها في الحزمة. انسخ هذا من مخرجات الخطوة 1.
  • الواجهات الخلفية: قائمة الواجهات الخلفية التي تريد تضمينها. تتضمن الخيارات الصالحة "cpu" و"webgl" و"wasm".
  • النماذج: قائمة بملفات model.json للنماذج التي تقوم بتحميلها في تطبيقك. يمكن أن يكون فارغًا إذا كان برنامجك لا يستخدم tfjs_converter لتحميل نموذج رسم بياني.
  • outputPath: مسار إلى مجلد لوضع الوحدات النمطية التي تم إنشاؤها فيها.
  • ForwardModeOnly: اضبط هذا على false إذا كنت تريد تضمين تدرجات للنوى المذكورة مسبقًا.

الخطوة 3. قم بإنشاء وحدة tfjs المخصصة

قم بتشغيل أداة الإنشاء المخصصة باستخدام ملف التكوين كوسيطة. يجب أن يكون لديك حزمة @tensorflow/tfjs مثبتة لتتمكن من الوصول إلى هذه الأداة.

npx tfjs-custom-module  --config custom_tfjs_config.json

سيؤدي هذا إلى إنشاء مجلد في outputPath يحتوي على بعض الملفات الجديدة.

الخطوة 4. قم بتكوين المجمع الخاص بك إلى الاسم المستعار tfjs للوحدة المخصصة الجديدة.

في المجمعات مثل webpack وrollup، يمكننا استخدام أسماء مستعارة للمراجع الموجودة لوحدات tfjs للإشارة إلى وحدات tfjs المخصصة التي تم إنشاؤها حديثًا. هناك ثلاث وحدات تحتاج إلى أسماء مستعارة لتحقيق أقصى قدر من التوفير في حجم الحزمة.

فيما يلي مقتطف لما يبدو عليه في حزمة الويب ( المثال الكامل هنا ):

...

config.resolve = {
  alias: {
    '@tensorflow/tfjs$':
        path.resolve(__dirname, './custom_tfjs/custom_tfjs.js'),
    '@tensorflow/tfjs-core$': path.resolve(
        __dirname, './custom_tfjs/custom_tfjs_core.js'),
    '@tensorflow/tfjs-core/dist/ops/ops_for_converter': path.resolve(
        __dirname, './custom_tfjs/custom_ops_for_converter.js'),
  }
}

...

وإليك مقتطف الشفرة المكافئ للمجموعة ( المثال الكامل هنا ):

import alias from '@rollup/plugin-alias';

...

alias({
  entries: [
    {
      find: /@tensorflow\/tfjs$/,
      replacement: path.resolve(__dirname, './custom_tfjs/custom_tfjs.js'),
    },
    {
      find: /@tensorflow\/tfjs-core$/,
      replacement: path.resolve(__dirname, './custom_tfjs/custom_tfjs_core.js'),
    },
    {
      find: '@tensorflow/tfjs-core/dist/ops/ops_for_converter',
      replacement: path.resolve(__dirname, './custom_tfjs/custom_ops_for_converter.js'),
    },
  ],
}));

...

إذا كان المجمع الخاص بك لا يدعم الأسماء المستعارة للوحدة، فستحتاج إلى تغيير عبارات import الخاصة بك لاستيراد Tensorflow.js من custom_tfjs.js الذي تم إنشاؤه في الخطوة 3. لن يتم التخلص من تعريفات العملية، لكن النواة ستظل شجرة -اهتزت. بشكل عام، فإن حبات اهتزاز الأشجار هي ما يوفر أكبر قدر من التوفير في حجم الحزمة النهائية.

إذا كنت تستخدم الحزمة @tensoflow/tfjs-core فقط، فستحتاج فقط إلى تسمية تلك الحزمة باسم مستعار.

الخطوة 5. قم بإنشاء الحزمة الخاصة بك

قم بتشغيل أداة التجميع الخاصة بك (على سبيل المثال webpack أو rollup ) لإنتاج الحزمة الخاصة بك. يجب أن يكون حجم الحزمة أصغر مما لو قمت بتشغيل المجمع دون الاسم المستعار للوحدة. يمكنك أيضًا استخدام أدوات تصور مثل هذه لمعرفة ما الذي وصل إلى مجموعتك النهائية.

الخطوة 6. اختبر تطبيقك

تأكد من اختبار أن تطبيقك يعمل كما هو متوقع!