Neural Networks API

‫Android Neural Networks API ‏ (NNAPI) هي واجهة برمجة تطبيقات C لنظام التشغيل Android مصمّمة لتنفيذ عمليات تتعلّق بالذكاء الاصطناعي على أجهزة Android، وهي عمليات تتطلّب معالجة مكثفة. تم تصميم NNAPI لتوفير طبقة أساسية من الوظائف لأُطر عمل تعلُّم الآلة ذات المستوى الأعلى، مثل TensorFlow Lite وCaffe2، اللتين تُنشئان الشبكات العصبية وتدرّبانها. تتوفّر واجهة برمجة التطبيقات على جميع أجهزة Android التي تعمل بالإصدار 8.1 (المستوى 27 من واجهة برمجة التطبيقات) أو إصدار أحدث.

يتيح NNAPI الاستنتاج من خلال تطبيق البيانات من أجهزة Android على نماذج سبق أن تم تدريبها وتحديدها من قِبل المطوّر. تشمل أمثلة الاستنتاج تصنيف الصور وتوقّع سلوك المستخدِم واختيار الردود المناسبة لطلب بحث.

توفّر الاستنتاج على الجهاز العديد من المزايا:

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

هناك أيضًا بعض السلبيات التي يجب أن يأخذها المطوّر في الاعتبار:

  • استخدام النظام: يتطلّب تقييم الشبكات العصبية إجراء الكثير من العمليات الحسابية، ما قد يؤدي إلى زيادة استخدام طاقة البطارية. يجب مراعاة مراقبة حالة البطارية إذا كان ذلك يشكّل مصدر قلق لتطبيقك، خاصةً في العمليات الحسابية التي تستغرق وقتًا طويلاً.
  • حجم التطبيق: انتبه إلى حجم النماذج. قد تستهلك النماذج مساحة تبلغ عدة ميغابايت. إذا كان تجميع نماذج كبيرة في حِزمة APK سيؤثّر بشكلٍ غير ملائم في المستخدمين، ننصحك بتنزيل نماذج بعد تثبيت التطبيق أو استخدام نماذج أصغر أو تنفيذ العمليات الحسابية في السحابة الإلكترونية. لا يوفّر NNAPI وظيفة لتشغيل نماذج في السحابة الإلكترونية.

اطّلِع على نموذج Android Neural Networks API للاطّلاع على مثال واحد على كيفية استخدام NNAPI.

فهم وقت تشغيل Neural Networks API

تم تصميم NNAPI ليتم استدعاؤه من خلال مكتبات وإطارات عمل وأدوات تعلُّم الآلة التي تتيح للمطوّرين تدريب نماذجهم خارج الجهاز ونشرها على أجهزة Android. لا تستخدم التطبيقات عادةً NNAPI مباشرةً، ولكن بدلاً من ذلك، تستخدم إطارات عمل تعلُّم الآلة ذات المستوى الأعلى. ويمكن أن تستخدم هذه الأطر بدورها NNAPI لتنفيذ عمليات الاستنتاج المستندة إلى الأجهزة على الأجهزة المتوافقة.

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

بالنسبة إلى أجهزة Android التي لا تتضمّن برنامج تشغيل مخصّصًا للمورّد، ينفِّذ وقت تشغيل NNAPI الطلبات على وحدة المعالجة المركزية.

يعرض الشكل 1 بنية النظام العالية المستوى لواجهة برمجة التطبيقات NNAPI.

الشكل 1. بنية النظام لواجهة برمجة التطبيقات Android Neural Networks API

نموذج برمجة واجهة برمجة التطبيقات للشبكات العصبية

لإجراء العمليات الحسابية باستخدام NNAPI، عليك أولاً إنشاء gráph موجه يحدِّد العمليات الحسابية التي يجب إجراؤها. يشكّل هذا الرسم البياني للحساب، مع بيانات الإدخال (مثل الأوزان والانحيازات التي تم تمريرها من إطار عمل تعلُّم الآلة)، نموذج تقييم وقت التشغيل في NNAPI.

يستخدم NNAPI أربعة عناصر مجردة رئيسية:

  • النموذج: رسم بياني للعمليات الحسابية للعمليات الحسابية والقيم الثابتة التي تم تعلّمها من خلال عملية تدريب هذه العمليات خاصة بالشبكة العصبية. وتشمل هذه العمليات التفاف ثنائي الأبعاد والتفعيل اللوجستي (sigmoid) والتفعيل الخطي المصحَّح (ReLU) وغيرها. إنّ إنشاء نموذج هو عملية متزامنة. بعد إنشاء النموذج بنجاح، يمكن إعادة استخدامه في جميع المواضيع والمجموعات. في NNAPI، يتم تمثيل النموذج كمثيل ANeuralNetworksModel.
  • التجميع: يمثّل إعدادًا لتجميع نموذج NNAPI في код أدنى مستوى. إنّ إنشاء مجموعة اختبارات هو عملية متزامنة. بعد إنشاءها بنجاح، يمكن إعادة استخدامها في جميع سلاسل المحادثات وعمليات التنفيذ. في NNAPI، يتم تمثيل كل عملية تجميع على أنّها مثيل ANeuralNetworksCompilation.
  • الذاكرة: تمثّل الذاكرة المشتركة والملفات المُعرَّفة بالذاكرة ومستودعات الذاكرة المشابهة. يتيح استخدام ذاكرة تخزين مؤقت لنقل البيانات إلى برامج التشغيل في وقت تشغيل NNAPI بكفاءة أكبر. ينشئ التطبيق عادةً وحدة تخزين مؤقتة واحدة للذاكرة المشتركة تتضمن كل مصفوفة تانسور مطلوبة لتحديد نموذج. يمكنك أيضًا استخدام ملف ذاكرة المخازن المؤقتة لتخزين المدخلات والمخرجات لمثيل التنفيذ. في NNAPI، يتم تمثيل كل وحدة تخزين مؤقت للذاكرة على أنّها مثيل ANeuralNetworksMemory.
  • التنفيذ: واجهة لتطبيق نموذج NNAPI على مجموعة من المدخلات وجمع النتائج يمكن تنفيذ الإجراء بشكل متزامن أو غير متزامن.

    بالنسبة إلى التنفيذ غير المتزامن، يمكن لعدة سلاسل مهام الانتظار إلى أن يكتمل التنفيذ نفسه. عند اكتمال هذا التنفيذ، يتم إلغاء جميع سلاسل التعليمات.

    في NNAPI، يتم تمثيل كل عملية تنفيذ على أنّها مثيل ANeuralNetworksExecution.

يعرض الشكل 2 عملية البرمجة الأساسية.

الشكل 2. عملية البرمجة لواجهة برمجة التطبيقات Android Neural Networks API

يوضّح الجزء المتبقّي من هذا القسم خطوات إعداد نموذج NNAPI لتنفيذ العمليات الحسابية وتجميع النموذج وتنفيذه.

توفير إمكانية الوصول إلى بيانات التدريب

من المرجّح أن يتم تخزين بيانات الأوزان والانحيازات التي تم تدريبها في ملف. لمنح وقت تشغيل NNAPI إمكانية الوصول الفعال إلى هذه البيانات، أنشئ مثيلًا لملف ANeuralNetworksMemory من خلال استدعاء الدالة ANeuralNetworksMemory_createFromFd() وإدخال وصف الملف لملف البيانات الذي تم فتحه. يمكنك أيضًا تحديد علامات حماية الذاكرة وقيمة الإزاحة التي تبدأ عندها منطقة الذاكرة المشترَكة في الملف.

// Create a memory buffer from the file that contains the trained data
ANeuralNetworksMemory* mem1 = NULL;
int fd = open("training_data", O_RDONLY);
ANeuralNetworksMemory_createFromFd(file_size, PROT_READ, fd, 0, &mem1);

على الرغم من أنّنا في هذا المثال نستخدم مثيلًا واحدًا فقط ANeuralNetworksMemory لجميع الأوزان، من الممكن استخدام أكثر من مثيل واحد ANeuralNetworksMemory لملفات متعددة.

استخدام وحدات التخزين المؤقت للأجهزة الأصلية

يمكنك استخدام مخازن الأجهزة الأصلية لإدخالات النماذج ومخرجاتها وقيم المُشغِّلات الثابتة. في بعض الحالات، يمكن لمسرِّع NNAPI الوصول إلى عناصر AHardwareBuffer بدون الحاجة إلى أن ينسخ برنامج التشغيل البيانات. يحتوي AHardwareBuffer على العديد من الإعدادات المختلفة، وقد لا يتوافق كل مسرع NNAPI مع كل هذه الإعدادات. بسبب هذا القيد، يُرجى الرجوع إلى القيود المُدرَجة في مستندات مرجعية حول ANeuralNetworksMemory_createFromAHardwareBuffer وإجراء الاختبار مسبقًا على الأجهزة المستهدَفة للتأكّد من أنّ عمليات الترجمة والتنفيذ التي تستخدِم AHardwareBuffer تعمل على النحو المتوقّع، وذلك باستخدام تخصيص الجهاز لتحديد المسرِّع.

للسماح لمحطة تشغيل NNAPI بالوصول إلى عنصر AHardwareBuffer، أنشئ مثيلًا ANeuralNetworksMemory من خلال استدعاء الدالة ANeuralNetworksMemory_createFromAHardwareBuffer وضبط العنصر AHardwareBuffer، كما هو موضّح في نموذج الرمز البرمجي التالي:

// Configure and create AHardwareBuffer object
AHardwareBuffer_Desc desc = ...
AHardwareBuffer* ahwb = nullptr;
AHardwareBuffer_allocate(&desc, &ahwb);

// Create ANeuralNetworksMemory from AHardwareBuffer
ANeuralNetworksMemory* mem2 = NULL;
ANeuralNetworksMemory_createFromAHardwareBuffer(ahwb, &mem2);

عندما لا تحتاج NNAPI إلى الوصول إلى عنصر AHardwareBuffer، يمكنك تحرير مثيل ANeuralNetworksMemory المقابل:

ANeuralNetworksMemory_free(mem2);

ملاحظة:

  • يمكنك استخدام AHardwareBuffer للذاكرة المؤقتة بأكملها فقط، ولا يمكنك استخدامه مع مَعلمة ARect.
  • لن يتم تفريغ ذاكرة التخزين المؤقت في وقت تشغيل NNAPI. يجب التأكّد من إمكانية الوصول إلى ملفّي التخزين المؤقت للدخل والإخراج قبل جدولة التنفيذ.
  • لا تتوفّر إمكانية استخدام أوصاف ملفات سياج المزامنة.
  • بالنسبة إلى AHardwareBuffer التي تستخدم تنسيقات ووحدات استخدام خاصة بالمورّد، يعتمد تحديد ما إذا كان العميل أو برنامج التشغيل هو المسؤول عن تنظيف ذاكرة التخزين المؤقت على طريقة تنفيذ المورّد.

الطراز

النموذج هو الوحدة الأساسية للحوسبة في NNAPI. يتم تعريف كل نموذج بoperand واحد أو أكثر وعمليات واحدة أو أكثر.

المَعلمات

عناصر التشغيل هي عناصر بيانات تُستخدَم في تحديد الرسم البياني. وتشمل هذه المدخلات والمخرجات للنموذج، والعقد الوسيطة التي تحتوي على البيانات التي تتدفق من عملية إلى أخرى، والثوابت التي يتم تمريرها إلى هذه العمليات.

هناك نوعان من المَعلمات التي يمكن إضافتها إلى نماذج NNAPI: المَعلمات السلعية و المَعلمات المصفوفية.

يمثّل المتغيّر السلعي قيمة واحدة. يتوافق NNAPI مع القيم السلاسل في التنسيقات المنطقية والتنسيقات العشرية المتحركة بسعة 16 بت والتنسيقات العشرية المتحركة بسعة 32 بت والتنسيقات الصحيحة بسعة 32 بت.

تتضمن معظم العمليات في NNAPI مصفوفات. مصفوفات Tensors هي صفائف من n بُعد. يتوافق NNAPI مع مصفوفات الخلاصات ذات النقطة العائمة بسعة 16 بت و32 بت و8 بت المقسَّمة و16 بت مقسَّمة وعدد صحيح بسعة 32 بت وقيم منطقية بسعة 8 بت.

على سبيل المثال، يمثّل الشكل 3 نموذجًا يتضمّن عمليتين: عملية جمع followed by a multiplication. يأخذ النموذج مصفوفة إدخال وينتج عنها مصفوفة إخراج واحدة.

الشكل 3. مثال على المُعامِلات لنموذج NNAPI

يحتوي النموذج أعلاه على سبعة عوامل تشغيل. ويتم تحديد هذه المُعامِلات بشكل ضمني من خلال فهرس الترتيب الذي تتم إضافتها به إلى النموذج. يكون للمَعلمة الأولى التي تتم إضافتها فهرس 0، وللمَعلمة الثانية فهرس 1، وهكذا. المعاملات 1 و2 و3 و5 هي معاملات ثابتة.

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

للمعاملات أنواع. ويتم تحديدها عند إضافتها إلى النموذج.

لا يمكن استخدام عامل تشغيل كإدخال وإخراج نموذج في آنٍ واحد.

يجب أن يكون كلّ عامل تشغيل إمّا إدخال نموذج أو ثابتًا أو عامل تشغيل ناتجًا عن عملية واحدة بالضبط.

لمزيد من المعلومات عن استخدام المَعلمات، اطّلِع على مزيد من المعلومات عن المَعلمات.

العمليات

تحدِّد العملية العمليات الحسابية التي سيتم إجراؤها. تتألّف كل عملية من العناصر التالية:

  • نوع العملية (مثل الجمع أو الضرب أو الالتفاف)
  • قائمة بمؤشرات المعاملات التي تستخدمها العملية كمدخلات
  • قائمة بمؤشرات المعاملات التي تستخدمها العملية للإخراج

يُرجى العِلم أنّ الترتيب في هذه القوائم مهم، ويمكنك الاطّلاع على مرجع واجهة برمجة التطبيقات NNAPI للاطّلاع على المدخلات والمخرجات المتوقّعة لكل نوع من أنواع العمليات.

يجب إضافة المَعلمات التي تستهلكها العملية أو تنتجها إلى النموذج قبل إضافة العملية.

ولا يهمّ الترتيب الذي تُضيف به العمليات. تعتمد NNAPI على التبعيات التي يحدّدها الرسم البياني الحسابي للمَعلمات والعمليات لتحديد ترتيب تنفيذ العمليات.

في الجدول التالي، تم تلخيص العمليات التي تتوافق مع NNAPI:

الفئة العمليات
العمليات الحسابية على مستوى العنصر
التلاعب بالمتّجه
عمليات الصور
عمليات البحث
عمليات التسويّة
عمليات الالتفاف
عمليات تجميع البيانات
عمليات التفعيل
العمليات الأخرى

مشكلة معروفة في المستوى 28 لواجهة برمجة التطبيقات: عند تمرير مكونات ANEURALNETWORKS_TENSOR_QUANT8_ASYMM tensor إلى عملية ANEURALNETWORKS_PAD المتوفّرة على Android 9 (المستوى 28 لواجهة برمجة التطبيقات) والإصدارات الأحدث، قد لا يتطابق المخرج من NNAPI مع المخرج من إطارات عمل تعلُّم الآلة ذات المستوى الأعلى، مثل TensorFlow Lite. وبدلاً من ذلك، يجب تمرير ANEURALNETWORKS_TENSOR_FLOAT32 فقط. تم حلّ هذه المشكلة في الإصدار 10 من نظام التشغيل Android (المستوى 29 لواجهة برمجة التطبيقات) والإصدارات الأحدث.

إنشاء النماذج

في المثال التالي، ننشئ نموذجًا من عمليتين موضحًا في الشكل 3.

لإنشاء النموذج، اتّبِع الخطوات التالية:

  1. استخدِم الدالة ANeuralNetworksModel_create() لتحديد نموذج فارغ.

    ANeuralNetworksModel* model = NULL;
    ANeuralNetworksModel_create(&model);
  2. أضِف المُعامِلات إلى النموذج من خلال استدعاء ANeuralNetworks_addOperand(). ويتم تحديد أنواع بياناتها باستخدام بنية البيانات ANeuralNetworksOperandType.

    // In our example, all our tensors are matrices of dimension [3][4]
    ANeuralNetworksOperandType tensor3x4Type;
    tensor3x4Type.type = ANEURALNETWORKS_TENSOR_FLOAT32;
    tensor3x4Type.scale = 0.f;    // These fields are used for quantized tensors
    tensor3x4Type.zeroPoint = 0;  // These fields are used for quantized tensors
    tensor3x4Type.dimensionCount = 2;
    uint32_t dims[2] = {3, 4};
    tensor3x4Type.dimensions = dims;

    // We also specify operands that are activation function specifiers ANeuralNetworksOperandType activationType; activationType.type = ANEURALNETWORKS_INT32; activationType.scale = 0.f; activationType.zeroPoint = 0; activationType.dimensionCount = 0; activationType.dimensions = NULL;

    // Now we add the seven operands, in the same order defined in the diagram ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 0 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 1 ANeuralNetworksModel_addOperand(model, &activationType); // operand 2 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 3 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 4 ANeuralNetworksModel_addOperand(model, &activationType); // operand 5 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 6
  3. بالنسبة إلى المُعامِلات التي لها قيم ثابتة، مثل الأوزان والانحيازات التي يحصل عليها تطبيقك من عملية تدريب، استخدِم الدالتَين ANeuralNetworksModel_setOperandValue() و ANeuralNetworksModel_setOperandValueFromMemory().

    في المثال التالي، نضبط قيمًا ثابتة من ملف بيانات التدريب المتوافقة مع ذاكرة التخزين المؤقت التي أنشأناها في إتاحة الوصول إلى بيانات التدريب.

    // In our example, operands 1 and 3 are constant tensors whose values were
    // established during the training process
    const int sizeOfTensor = 3 * 4 * 4;    // The formula for size calculation is dim0 * dim1 * elementSize
    ANeuralNetworksModel_setOperandValueFromMemory(model, 1, mem1, 0, sizeOfTensor);
    ANeuralNetworksModel_setOperandValueFromMemory(model, 3, mem1, sizeOfTensor, sizeOfTensor);

    // We set the values of the activation operands, in our example operands 2 and 5 int32_t noneValue = ANEURALNETWORKS_FUSED_NONE; ANeuralNetworksModel_setOperandValue(model, 2, &noneValue, sizeof(noneValue)); ANeuralNetworksModel_setOperandValue(model, 5, &noneValue, sizeof(noneValue));
  4. لكل عملية في الرسم البياني الموجَّه تريد احتسابها، أضِف العملية إلى النموذج من خلال استدعاء الدالة ANeuralNetworksModel_addOperation().

    يجب أن يقدّم تطبيقك ما يلي كمَعلمات لهذا الطلب:

    • نوع العملية
    • عدد قيم الإدخال
    • مصفوفة الفهارس لمَعلمات الإدخال
    • عدد قيم الإخراج
    • مصفوفة الفهارس لمعاملات الإخراج

    يُرجى العلم أنّه لا يمكن استخدام عامل حسابي لكل من إدخال العملية نفسها وإخراجها.

    // We have two operations in our example
    // The first consumes operands 1, 0, 2, and produces operand 4
    uint32_t addInputIndexes[3] = {1, 0, 2};
    uint32_t addOutputIndexes[1] = {4};
    ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_ADD, 3, addInputIndexes, 1, addOutputIndexes);

    // The second consumes operands 3, 4, 5, and produces operand 6 uint32_t multInputIndexes[3] = {3, 4, 5}; uint32_t multOutputIndexes[1] = {6}; ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_MUL, 3, multInputIndexes, 1, multOutputIndexes);
  5. حدِّد المعاملات التي يجب أن يتعامل معها النموذج كمدخلات ومخرجات من خلال استدعاء الدالة ANeuralNetworksModel_identifyInputsAndOutputs().

    // Our model has one input (0) and one output (6)
    uint32_t modelInputIndexes[1] = {0};
    uint32_t modelOutputIndexes[1] = {6};
    ANeuralNetworksModel_identifyInputsAndOutputs(model, 1, modelInputIndexes, 1 modelOutputIndexes);
  6. يمكنك اختياريًا تحديد ما إذا كان يُسمح بحساب ANEURALNETWORKS_TENSOR_FLOAT32 باستخدام نطاق أو دقة منخفضة مثل التنسيق المتغير النقطة الثابتة الذي يبلغ 16 بتًا وفقًا لمعيار IEEE 754 من خلال استدعاء ANeuralNetworksModel_relaxComputationFloat32toFloat16().

  7. يُرجى الاتصال على ANeuralNetworksModel_finish() لإنهاء تعريف النموذج. في حال عدم حدوث أي أخطاء، تعرِض هذه الدالة رمز النتيجة ANEURALNETWORKS_NO_ERROR.

    ANeuralNetworksModel_finish(model);

بعد إنشاء نموذج، يمكنك تجميعه أي عدد من المرات وتنفيذ كل عملية compiling أي عدد من المرات.

التحكّم في التدفق

لدمج تدفّق التحكّم في نموذج NNAPI، اتّبِع الخطوات التالية:

  1. أنشئ الرسوم البيانية الفرعية للتنفيذ المقابلة (الرسوم البيانية الفرعية then وelse لعبارة IF والرسوم البيانية الفرعية condition وbody لحلقة WHILE) كنماذج ANeuralNetworksModel* مستقلة:

    ANeuralNetworksModel* thenModel = makeThenModel();
    ANeuralNetworksModel* elseModel = makeElseModel();
  2. أنشئ عناصر تشغيل تشير إلى هذه النماذج ضمن النموذج الذي يحتوي على تدفق التحكّم:

    ANeuralNetworksOperandType modelType = {
        .type = ANEURALNETWORKS_MODEL,
    };
    ANeuralNetworksModel_addOperand(model, &modelType);  // kThenOperandIndex
    ANeuralNetworksModel_addOperand(model, &modelType);  // kElseOperandIndex
    ANeuralNetworksModel_setOperandValueFromModel(model, kThenOperandIndex, &thenModel);
    ANeuralNetworksModel_setOperandValueFromModel(model, kElseOperandIndex, &elseModel);
  3. أضِف عملية تدفّق التحكّم:

    uint32_t inputs[] = {kConditionOperandIndex,
                         kThenOperandIndex,
                         kElseOperandIndex,
                         kInput1, kInput2, kInput3};
    uint32_t outputs[] = {kOutput1, kOutput2};
    ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_IF,
                                      std::size(inputs), inputs,
                                      std::size(output), outputs);

موسيقى مجمّعة

تحدِّد خطوة الترجمة المعالجات التي سيتم تنفيذ النموذج عليها وتطلب من برامج التشغيل المقابلة الاستعداد لتنفيذه. وقد يشمل ذلك إنشاء رمز آلي خاص بالمعالجات التي سيتم تشغيل النموذج عليها.

لتجميع نموذج، اتّبِع الخطوات التالية:

  1. استخدِم الدالة ANeuralNetworksCompilation_create() لإنشاء مثيل جديد للترجمة.

    // Compile the model
    ANeuralNetworksCompilation* compilation;
    ANeuralNetworksCompilation_create(model, &compilation);

    يمكنك اختياريًا استخدام تخصيص الجهاز لتحديد الأجهزة التي سيتم تنفيذها عليها.

  2. يمكنك اختياريًا التأثير في كيفية توازن وقت التشغيل بين استخدام طاقة البطارية وسرعة التنفيذ. يمكنك إجراء ذلك من خلال الاتصال بالرقم ANeuralNetworksCompilation_setPreference().

    // Ask to optimize for low power consumption
    ANeuralNetworksCompilation_setPreference(compilation, ANEURALNETWORKS_PREFER_LOW_POWER);

    تشمل الإعدادات المفضّلة التي يمكنك تحديدها ما يلي:

    • ANEURALNETWORKS_PREFER_LOW_POWER: يُفضَّل تنفيذها بطريقة تقلل من استهلاك البطارية. ويُفضَّل استخدام هذا الإجراء لعمليات التجميع التي يتم تنفيذها كثيرًا.
    • ANEURALNETWORKS_PREFER_FAST_SINGLE_ANSWER: يُفضَّل عرض إجابة واحدة في أسرع وقت ممكن، حتى إذا كان ذلك يؤدي إلى زيادة استهلاك الطاقة. هذا هو الخيار التلقائي.
    • ANEURALNETWORKS_PREFER_SUSTAINED_SPEED: يُفضَّل زيادة معدل نقل البيانات إلى أقصى حد في اللقطات المتتالية، مثل عند معالجة اللقطات المتتالية الواردة من الكاميرا.
  3. يمكنك اختياريًا إعداد ذاكرة التخزين المؤقت للترجمة من خلال استدعاء ANeuralNetworksCompilation_setCaching.

    // Set up compilation caching
    ANeuralNetworksCompilation_setCaching(compilation, cacheDir, token);

    استخدِم getCodeCacheDir() للرمز cacheDir. يجب أن يكون token المحدّد فريدًا لكل نموذج ضمن التطبيق.

  4. يمكنك إنهاء تعريف عملية الترجمة من خلال استدعاء ANeuralNetworksCompilation_finish(). في حال عدم حدوث أي أخطاء، تعرِض هذه الدالة رمز النتيجة ANEURALNETWORKS_NO_ERROR.

    ANeuralNetworksCompilation_finish(compilation);

رصد الأجهزة وتحديدها

على أجهزة Android التي تعمل بالإصدار 10 (المستوى 29 لواجهة برمجة التطبيقات) والإصدارات الأحدث، توفّر واجهة برمجة التطبيقات NNAPI وظائف تسمح لتطبيقات ومكتبات إطار عمل تعلُّم الآلة بالحصول على معلومات عن الأجهزة المتاحة وتحديد الأجهزة التي سيتم استخدامها لتنفيذ العمليات. من خلال تقديم معلومات عن الأجهزة المتاحة، يمكن للتطبيقات الحصول على الإصدار الدقيق لبرامج التشغيل المتوفّرة على الجهاز لتجنُّب عدم التوافق المعروف. من خلال منح التطبيقات إمكانية تحديد الأجهزة التي ستبدأ في تنفيذ أقسام مختلفة من النموذج، يمكن تحسين التطبيقات لجهاز Android الذي يتم نشرها عليه.

رصد الأجهزة

استخدِم رمز ANeuralNetworks_getDeviceCount للحصول على عدد الأجهزة المتاحة. بالنسبة إلى كل جهاز، استخدِم ANeuralNetworks_getDevice لضبط مثيل ANeuralNetworksDevice على إشارة إلى هذا الجهاز.

بعد الحصول على مرجع الجهاز، يمكنك الاطّلاع على معلومات إضافية عن هذا الجهاز باستخدام الدوالّ التالية:

تعيين الجهاز

استخدِم رمز ANeuralNetworksModel_getSupportedOperationsForDevices لاكتشاف العمليات التي يمكن تنفيذها في نموذج معيّن على أجهزة محدّدة.

للتحكّم في مسرعات التنفيذ التي تريد استخدامها، استخدِم ANeuralNetworksCompilation_createForDevices بدلاً من ANeuralNetworksCompilation_create. استخدِم عنصر ANeuralNetworksCompilation الناتج كالمعتاد. تعرض الدالة خطأ إذا كان النموذج المقدَّم يحتوي على عمليات غير متوافقة مع الأجهزة المحدّدة.

في حال تحديد أجهزة متعددة، يكون وقت التشغيل مسؤولاً عن توزيع العمل على الأجهزة.

على غرار الأجهزة الأخرى، يتم تمثيل تنفيذ وحدة المعالجة المركزية لـ NNAPI باستخدام ملف ANeuralNetworksDevice يحمل الاسم nnapi-reference والنوع ANEURALNETWORKS_DEVICE_TYPE_CPU. عند استدعاء ANeuralNetworksCompilation_createForDevices، لا يتم استخدام تنفيذ وحدة المعالجة المركزية للتعامل مع حالات الفشل في تجميع النموذج وتنفيذه.

تقع على عاتق التطبيق مسؤولية تقسيم النموذج إلى نماذج فرعية يمكن تشغيلها على الأجهزة المحدّدة. بالنسبة إلى التطبيقات التي لا تحتاج إلى مشاركة ملف التمهيد يدويًا، يجب أن تستمر في طلب ANeuralNetworksCompilation_create البسيط لاستخدام جميع الأجهزة المتاحة (بما في ذلك وحدة المعالجة المركزية) لتسريع ANeuralNetworksCompilation_create النموذج. إذا تعذّر توافق النموذج بالكامل مع الأجهزة التي حدّدتها باستخدام ANeuralNetworksCompilation_createForDevices، ANEURALNETWORKS_BAD_DATA يتم عرض القيمة التالية:

تقسيم النماذج

عندما تتوفّر أجهزة متعددة للنموذج، يوزع وقت تشغيل NNAPI العمل على الأجهزة. على سبيل المثال، إذا تم توفير أكثر من جهاز واحد لـ ANeuralNetworksCompilation_createForDevices، سيتم أخذ جميع الأجهزة المحدّدة في الاعتبار عند تخصيص العمل. يُرجى العِلم أنّه في حال عدم إدراج وحدة المعالجة المركزية في القائمة، سيتم إيقاف تنفيذ وحدة المعالجة المركزية. عند استخدام ANeuralNetworksCompilation_create ، سيتم أخذ جميع الأجهزة المتاحة في الاعتبار، بما في ذلك وحدة المعالجة المركزية.

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

لفهم كيفية تقسيم النموذج بواسطة NNAPI، راجِعملفّات log في Android بحثًا عن رسالة (على مستوى INFO باستخدام العلامة ExecutionPlan):

ModelBuilder::findBestDeviceForEachOperation(op-name): device-index

op-name هو الاسم الوصفي للعملية في الرسم البياني، وdevice-index هو فهرس الجهاز المعنيّ في قائمة الأجهزة. هذه القائمة هي الإدخال المقدَّم إلى ANeuralNetworksCompilation_createForDevices أو، في حال استخدام ANeuralNetworksCompilation_createForDevices، قائمة الأجهزة المعروضة عند تكرار جميع الأجهزة باستخدام ANeuralNetworks_getDeviceCount و ANeuralNetworks_getDevice.

الرسالة (على مستوى INFO مع العلامة ExecutionPlan):

ModelBuilder::partitionTheWork: only one best device: device-name

تشير هذه الرسالة إلى أنّه تم تسريع الرسم البياني بالكامل على الجهاز device-name.

التنفيذ

تطبِّق خطوة التنفيذ النموذج على مجموعة من المدخلات وتخزِّن مخرجات العمليات الحسابية في مساحة تخزين مؤقت أو مساحة ذاكرة واحدة أو أكثر للمستخدمين خصصها تطبيقك.

لتنفيذ نموذج مجمَّع، اتّبِع الخطوات التالية:

  1. استخدِم الدالة ANeuralNetworksExecution_create() لإنشاء مثيل تنفيذ جديد.

    // Run the compiled model against a set of inputs
    ANeuralNetworksExecution* run1 = NULL;
    ANeuralNetworksExecution_create(compilation, &run1);
  2. حدِّد المكان الذي يقرأ فيه تطبيقك قيم الإدخال لإجراء العمليات الحسابية. يمكن لتطبيقك قراءة قيم الإدخال من مخزن مؤقت للمستخدم أو مساحة ذاكرة مخصّصة من خلال استدعاء ANeuralNetworksExecution_setInput() أو ANeuralNetworksExecution_setInputFromMemory() على التوالي.

    // Set the single input to our sample model. Since it is small, we won't use a memory buffer
    float32 myInput[3][4] = { ...the data... };
    ANeuralNetworksExecution_setInput(run1, 0, NULL, myInput, sizeof(myInput));
  3. حدِّد المكان الذي يكتب فيه تطبيقك قيم الإخراج. يمكن لتطبيقك كتابة قيم الإخراج في ملف تدخُّل مستخدم أو مساحة ذاكرة مخصّصة، وذلك من خلال استدعاء ANeuralNetworksExecution_setOutput() أوANeuralNetworksExecution_setOutputFromMemory() على التوالي.

    // Set the output
    float32 myOutput[3][4];
    ANeuralNetworksExecution_setOutput(run1, 0, NULL, myOutput, sizeof(myOutput));
  4. حدِّد موعد بدء التنفيذ من خلال استدعاء الدالة ANeuralNetworksExecution_startCompute(). في حال عدم حدوث أي أخطاء، تعرِض هذه الدالة رمز النتيجة ANEURALNETWORKS_NO_ERROR.

    // Starts the work. The work proceeds asynchronously
    ANeuralNetworksEvent* run1_end = NULL;
    ANeuralNetworksExecution_startCompute(run1, &run1_end);
  5. استخدِم الدالة ANeuralNetworksEvent_wait() للانتظار إلى أن تكتمل عملية التنفيذ. إذا كان التنفيذ ناجحًا، تعرض هذه الدالة رمز النتيجة ANEURALNETWORKS_NO_ERROR. يمكن تنفيذ الانتظار في سلسلة مهام مختلفة عن سلسلة المهام التي تبدأ التنفيذ.

    // For our example, we have no other work to do and will just wait for the completion
    ANeuralNetworksEvent_wait(run1_end);
    ANeuralNetworksEvent_free(run1_end);
    ANeuralNetworksExecution_free(run1);
  6. يمكنك اختياريًا تطبيق مجموعة مختلفة من المدخلات على النموذج المجمَّع من خلال استخدام مثيل الترجمة نفسه لإنشاء مثيل ANeuralNetworksExecution جديد.

    // Apply the compiled model to a different set of inputs
    ANeuralNetworksExecution* run2;
    ANeuralNetworksExecution_create(compilation, &run2);
    ANeuralNetworksExecution_setInput(run2, ...);
    ANeuralNetworksExecution_setOutput(run2, ...);
    ANeuralNetworksEvent* run2_end = NULL;
    ANeuralNetworksExecution_startCompute(run2, &run2_end);
    ANeuralNetworksEvent_wait(run2_end);
    ANeuralNetworksEvent_free(run2_end);
    ANeuralNetworksExecution_free(run2);

التنفيذ المتزامن

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

لتحسين وقت الاستجابة، يمكنك بدلاً من ذلك توجيه تطبيق لإجراء مكالمة تحليل تزامنية إلى وقت التشغيل. ولن يتم عرض هذا الطلب إلا بعد اكتمال عملية الاستنتاج بدلاً من عرضه بعد بدء عملية الاستنتاج. بدلاً من memanggil ANeuralNetworksExecution_startCompute لإجراء مكالمة استنتاج غير متزامنة مع وقت التشغيل، يُجري التطبيق مكالمة ANeuralNetworksExecution_compute لإجراء مكالمة متزامنة مع وقت التشغيل. لا تتطلب المكالمة إلى ANeuralNetworksExecution_compute استخدام ANeuralNetworksEvent ولا تتم إقرانها بمكالمة إلى ANeuralNetworksEvent_wait.

عمليات التنفيذ المكثّفة

على أجهزة Android التي تعمل بالإصدار 10 (المستوى 29 من واجهة برمجة التطبيقات) والإصدارات الأحدث، تتيح واجهة برمجة التطبيقات NNAPI تنفيذ العمليات المكثّفة من خلال العنصر ANeuralNetworksBurst. عمليات التنفيذ المتسلسلة هي سلسلة من عمليات تنفيذ المحتوى نفسه التي تحدث بشكل متتابع سريع، مثل عمليات التنفيذ التي تعمل على لقطات من تسجيل كاميرا أو عيّنات صوتية متتالية. قد يؤدي استخدام عناصر ANeuralNetworksBurst إلى تنفيذ عمليات أسرع، لأنّها تشير إلى مُسرِّعات الأداء بأنّه يمكن مجددًا استخدام الموارد بين عمليات التنفيذ وأنّه يجب أن تظل المُسرِّعات في حالة أداء عالٍ طوال مدة الزيادة المفاجئة في عدد العمليات.

لا يُجري ANeuralNetworksBurst سوى تغيير بسيط في مسار التنفيذ المعتاد. يمكنك إنشاء عنصر انفجار باستخدام ANeuralNetworksBurst_create، كما هو موضّح في مقتطف الرمز البرمجي التالي:

// Create burst object to be reused across a sequence of executions
ANeuralNetworksBurst* burst = NULL;
ANeuralNetworksBurst_create(compilation, &burst);

تكون عمليات تنفيذ عمليات البث المفاجئ متزامنة. ومع ذلك، بدلاً من استخدام ANeuralNetworksExecution_compute لإكمال كل عملية استنتاج، يمكنك إقران العناصر المختلفة ANeuralNetworksExecution بـ ANeuralNetworksBurst نفسه في طلبات الدالة ANeuralNetworksExecution_burstCompute.

// Create and configure first execution object
// ...

// Execute using the burst object
ANeuralNetworksExecution_burstCompute(execution1, burst);

// Use results of first execution and free the execution object
// ...

// Create and configure second execution object
// ...

// Execute using the same burst object
ANeuralNetworksExecution_burstCompute(execution2, burst);

// Use results of second execution and free the execution object
// ...

يمكنك تحرير عنصر ANeuralNetworksBurst باستخدام ANeuralNetworksBurst_free عندما لا يعود مطلوبًا.

// Cleanup
ANeuralNetworksBurst_free(burst);

قوائم انتظار الأوامر غير المتزامنة والتنفيذ المحدود

في الإصدار 11 من Android والإصدارات الأحدث، تتيح مكتبة NNAPI طريقة إضافية لجدولة تنفيذ تعليمات غير المتزامنة من خلال الأسلوب ANeuralNetworksExecution_startComputeWithDependencies(). عند استخدام هذه الطريقة، ينتظر التنفيذ حتى يتم إرسال إشارة إلى جميع الأحداث المُعتمَدة قبل بدء التقييم. بعد اكتمال التنفيذ واستعداد النتائج للاستخدام، يتم إعلام الحدث المعروض.

استنادًا إلى الأجهزة التي تُجري التنفيذ، قد يكون الحدث مدعومًا بأحد حدود المزامنة. عليك استدعاء ANeuralNetworksEvent_wait() في انتظار الحدث واسترداد الموارد التي استخدمتها عملية التنفيذ. يمكنك استيراد حدود المزامنة إلى عنصر حدث باستخدام ANeuralNetworksEvent_createFromSyncFenceFd()، ويمكنك تصدير حدود المزامنة من عنصر حدث باستخدام ANeuralNetworksEvent_getSyncFenceFd().

النتائج ذات الحجم الديناميكي

لتتوافق النماذج التي يعتمد حجم المخرجات فيها على بيانات المدخلات، أي التي لا يمكن تحديد حجمها في وقت تنفيذ النموذج، استخدِم ANeuralNetworksExecution_getOutputOperandRank وANeuralNetworksExecution_getOutputOperandDimensions.

يوضّح الرمز البرمجي النموذجي التالي كيفية إجراء ذلك:

// Get the rank of the output
uint32_t myOutputRank = 0;
ANeuralNetworksExecution_getOutputOperandRank(run1, 0, &myOutputRank);

// Get the dimensions of the output
std::vector<uint32_t> myOutputDimensions(myOutputRank);
ANeuralNetworksExecution_getOutputOperandDimensions(run1, 0, myOutputDimensions.data());

تنظيف

تعالج خطوة التنظيف تحرير الموارد الداخلية المستخدَمة في عملية حسابك.

// Cleanup
ANeuralNetworksCompilation_free(compilation);
ANeuralNetworksModel_free(model);
ANeuralNetworksMemory_free(mem1);

إدارة الأخطاء واستخدام وحدة المعالجة المركزية (CPU) الاحتياطية

في حال حدوث خطأ أثناء التقسيم، أو إذا تعذّر على أحد برامج التشغيل تجميع (جزء من) نموذج، أو إذا تعذّر على أحد برامج التشغيل تنفيذ (جزء من) نموذج مجمَّع، قد يعود NNAPI إلى تنفيذ وحدة المعالجة المركزية (CPU) لعملية واحدة أو أكثر من العمليات.

إذا كان برنامج تشغيل NNAPI يحتوي على إصدارات محسّنة من العملية (مثل TFLite)، قد يكون من المفيد إيقاف الإجراء الاحتياطي لوحدة المعالجة المركزية ومعالجة الأعطال من خلال تنفيذ العملية المحسّنة للبرنامج.

في Android 10، إذا تم إجراء عملية التجميع باستخدام ANeuralNetworksCompilation_createForDevices، سيتم إيقاف الإجراء الاحتياطي لوحدة المعالجة المركزية.

في نظام التشغيل Android P، يتم تنفيذ NNAPI على وحدة المعالجة المركزية إذا تعذّر التنفيذ على برنامج التشغيل. وينطبق ذلك أيضًا على نظام التشغيل Android 10 عند استخدام ANeuralNetworksCompilation_create بدلاً من ANeuralNetworksCompilation_createForDevices.

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

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

في بعض الحالات، لا تكون بعض العمليات متوافقة مع وحدة المعالجة المركزية، وفي مثل هذه الحالات، سيتعذّر التجميع أو التنفيذ بدلاً من الرجوع إلى الإجراء البديل.

حتى بعد إيقاف وضع "الرجوع إلى وحدة المعالجة المركزية"، قد تظل هناك عمليات في النموذج مُجدوَلة على وحدة المعالجة المركزية. إذا كانت وحدة المعالجة المركزية مضمّنة في قائمة المعالجات التي تم إرسالها إلى ANeuralNetworksCompilation_createForDevices، وكانت إما المعالج الوحيد الذي يتيح تنفيذ هذه العمليات أو المعالج الذي يُزعَم أنّه يقدّم أفضل أداء لهذه العمليات، سيتم اختياره كمنفّذ أساسي (غير احتياطي).

لضمان عدم تنفيذ وحدة المعالجة المركزية، استخدِم ANeuralNetworksCompilation_createForDevices مع استبعاد nnapi-reference من قائمة الأجهزة. بدءًا من Android P، أصبح من الممكن إيقاف الإجراء الاحتياطي في وقت التنفيذ على ملفّات برمجية قيد التطوير من خلال ضبط القيمة debug.nn.partition على 2.

نطاقات الذكريات

في الإصدار 11 من نظام التشغيل Android والإصدارات الأحدث، تتيح واجهة برمجة التطبيقات NNAPI نطاقات الذاكرة التي توفّر واجهات تخصيص للذاكرة غير الشفافة. ويسمح ذلك للتطبيقات بتمرير ملف ذاكرة الخاص بالجهاز على مستوى عمليات التنفيذ، كي لا تنسخ NNAPI البيانات أو تنقلها بشكل غير ضروري عند تنفيذ عمليات متتالية على برنامج التشغيل نفسه.

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

لتخصيص ذاكرة غير شفافة، اتّبِع الخطوات التالية:

  1. استخدِم الدالة ANeuralNetworksMemoryDesc_create() لإنشاء وصف ذاكرة جديد:

    // Create a memory descriptor
    ANeuralNetworksMemoryDesc* desc;
    ANeuralNetworksMemoryDesc_create(&desc);
  2. حدِّد جميع أدوار الإدخال والإخراج المقصودة من خلال استدعاء الدوالّ ANeuralNetworksMemoryDesc_addInputRole() و ANeuralNetworksMemoryDesc_addOutputRole().

    // Specify that the memory may be used as the first input and the first output
    // of the compilation
    ANeuralNetworksMemoryDesc_addInputRole(desc, compilation, 0, 1.0f);
    ANeuralNetworksMemoryDesc_addOutputRole(desc, compilation, 0, 1.0f);
  3. يمكنك اختياريًا تحديد سمات الذاكرة من خلال استدعاء ANeuralNetworksMemoryDesc_setDimensions().

    // Specify the memory dimensions
    uint32_t dims[] = {3, 4};
    ANeuralNetworksMemoryDesc_setDimensions(desc, 2, dims);
  4. يمكنك إنهاء تعريف الوصف من خلال الاتصال بالرقم ANeuralNetworksMemoryDesc_finish().

    ANeuralNetworksMemoryDesc_finish(desc);
  5. يمكنك تخصيص أي عدد تريده من الذكريات من خلال تمرير الوصف إلى ANeuralNetworksMemory_createFromDesc().

    // Allocate two opaque memories with the descriptor
    ANeuralNetworksMemory* opaqueMem;
    ANeuralNetworksMemory_createFromDesc(desc, &opaqueMem);
  6. تحرير وصف الذاكرة عندما لا تكون بحاجة إليه

    ANeuralNetworksMemoryDesc_free(desc);

لا يجوز للعميل استخدام عنصر ANeuralNetworksMemory الذي تم إنشاؤه إلا مع ANeuralNetworksExecution_setInputFromMemory() أو ANeuralNetworksExecution_setOutputFromMemory() وفقًا للأدوار المحدّدة في عنصر ANeuralNetworksMemoryDesc. يجب ضبط وسيطات البدء والطول على 0، ما يشير إلى استخدام الذاكرة بالكامل. يمكن للعميل أيضًا ضبط محتوى الذاكرة أو استخراجه بشكل صريح باستخدام ANeuralNetworksMemory_copy().

يمكنك إنشاء ذكريات غير شفافة بأدوار لسمات أو ترتيب غير محدّدَين. في هذه الحالة، قد يتعذّر إنشاء الذاكرة مع ظهور الحالة ANEURALNETWORKS_OP_FAILED إذا لم يكن ذلك متوافقًا مع البرنامج الأساسي. ننصح العميل بتنفيذ منطق التراجع عن طريق تخصيص ملف احتياطي كبير بما يكفي يستند إلى Ashmem أو وضع BLOB AHardwareBuffer.

عندما لا يحتاج NNAPI إلى الوصول إلى عنصر الذاكرة غير الشفاف، يجب تحرير مثيل ANeuralNetworksMemory المقابل:

ANeuralNetworksMemory_free(opaqueMem);

قياس الأداء

يمكنك تقييم أداء تطبيقك من خلال قياس وقت التنفيذ أو من خلال التحليل.

وقت التنفيذ

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

  • وقت التنفيذ على مسرع (وليس في برنامج التشغيل الذي يعمل على معالج المضيف ).
  • وقت التنفيذ في برنامج التشغيل، بما في ذلك الوقت المستغرق في المسرّع

يستبعد وقت التنفيذ في برنامج التشغيل الوقت المستغرَق في عمليات الدعم، مثل وقت بيئة التشغيل نفسه ووقت واجهة برمجة التطبيقات (IPC) اللازمة لبيئة التشغيل للتواصل مع برنامج التشغيل.

تقيس واجهات برمجة التطبيقات هذه المدة بين حدثَي إرسال العمل وإكماله، بدلاً من الوقت الذي يخصصه برنامج التشغيل أو المسرِّع لإجراء عملية المعالجة، والتي قد يتم إيقافها بسبب تبديل السياق.

على سبيل المثال، إذا بدأ الاستنتاج 1، يوقف السائق العمل لتنفيذ الاستنتاج 2، ثم يستأنف الاستنتاج 1 ويكمله، وسيتضمّن وقت التنفيذ للاستنتاج 1 الوقت الذي تم فيه إيقاف العمل لتنفيذ الاستنتاج 2.

قد تكون معلومات التوقيت هذه مفيدة عند نشر أحد التطبيقات في قناة الإصدار العلني لجمع بيانات القياس عن بُعد لاستخدامها بلا إنترنت. يمكنك استخدام بيانات التوقيت لتعديل التطبيق وتحسين أدائه.

عند استخدام هذه الوظيفة، يُرجى مراعاة ما يلي:

  • قد يؤدي جمع معلومات التوقيت إلى التأثير سلبًا في الأداء.
  • لا يمكن إلا لبرنامج التشغيل احتساب الوقت الذي يقضيه في نفسه أو في المسرِّع، باستثناء الوقت الذي يقضيه في وقت تشغيل NNAPI وفي واجهة برمجة التطبيقات بين العمليات.
  • لا يمكنك استخدام واجهات برمجة التطبيقات هذه إلا مع ANeuralNetworksExecution تم إنشاؤه باستخدام ANeuralNetworksCompilation_createForDevices مع numDevices = 1.
  • لا يُشترط أن يكون لدى السائق إذن لتسجيل معلومات التوقيت.

إنشاء ملف شخصي لتطبيقك باستخدام أداة Android Systrace

بدءًا من Android 10، تنشئ NNAPI تلقائيًا أحداث systrace التي يمكنك استخدامها لإنشاء ملف شخصي لتطبيقك.

يتضمّن مصدر NNAPI أداة parse_systrace لمعالجة أحداث systrace التي ينشئها تطبيقك وإنشاء عرض جدولي يعرض المدّة التي تمّ إنفاقها في المراحل المختلفة لدورة حياة النموذج (إنشاء النموذج والتحضير والترجمة والتنفيذ والإيقاف) والطبقات المختلفة للتطبيقات. في ما يلي الطبقات التي يتم تقسيم تطبيقك إليها:

  • Application: رمز التطبيق الرئيسي
  • Runtime: وقت تشغيل NNAPI
  • IPC: الاتّصال بين العمليات بين NNAPI Runtime ورمز السائق
  • Driver: عملية برنامج تشغيل المسرّع

إنشاء بيانات تحليل الملف الشخصي

بافتراض أنّك نزّلت شجرة مصادر AOSP في $ANDROID_BUILD_TOP، وباستخدام مثال تصنيف الصور باستخدام مكتبة TFLite كتطبيق مستهدف، يمكنك إنشاء بيانات ملف تعريف أداء NNAPI باتّباع الخطوات التالية:

  1. ابدأ أداة systrace في Android باستخدام الأمر التالي:
$ANDROID_BUILD_TOP/external/chromium-trace/systrace.py  -o trace.html -a org.tensorflow.lite.examples.classification nnapi hal freq sched idle load binder_driver

تشير المَعلمة -o trace.html إلى أنّه سيتم تسجيل المسارات في trace.html. عند إنشاء ملف شخصي لتطبيقك، عليك استبدال org.tensorflow.lite.examples.classification باسم العملية المحدّد في بيان التطبيق.

سيؤدي ذلك إلى إبقاء إحدى وحدات تحكّم وحدة التحكّم في البرامج مشغولة، ولا تشغِّل الأمر في الخلفية لأنّه ينتظر بشكل تفاعلي انتهاء عملية enter.

  1. بعد بدء أداة جمع بيانات systrace، ابدأ تطبيقك وشغِّل اختبار الأداء.

في هذه الحالة، يمكنك تشغيل تطبيق تصنيف الصور من Android Studio أو من واجهة مستخدم الهاتف التجريبي مباشرةً إذا سبق تثبيت التطبيق. لإنشاء بعض بيانات NNAPI، عليك ضبط التطبيق لاستخدام NNAPI من خلال اختيار NNAPI كجهاز مستهدف في مربّع حوار إعدادات التطبيق.

  1. عند اكتمال الاختبار، أوقِف systrace بالضغط على enter في وحدة تحكّم وحدة التحكّم النشطة منذ الخطوة 1.

  2. شغِّل الأداة systrace_parser لإنشاء إحصاءات تراكمية:

$ANDROID_BUILD_TOP/frameworks/ml/nn/tools/systrace_parser/parse_systrace.py --total-times trace.html

يقبل المُحلِّل المَعلمات التالية: - --total-times: تعرِض هذه المَعلمة إجمالي الوقت الذي تمّ قضاؤه في إحدى الطبقات، بما في ذلك الوقت الذي تمّ قضاؤه في انتظار التنفيذ عند طلب الوصول إلى طبقة أساسية. - --print-detail: تُطبع هذه المَعلمة جميع الأحداث التي تمّ جمعها من systrace - --per-execution: تُطبع هذه المَعلمة التنفيذ والمراحل الفرعية فقط (حسب أوقات التنفيذ) بدلاً من الإحصاءات لجميع المراحل - --json: تُنشئ هذه المَعلمة الإخراج بتنسيق JSON

في ما يلي مثال على الإخراج:

===========================================================================================================================================
NNAPI timing summary (total time, ms wall-clock)                                                      Execution
                                                           ----------------------------------------------------
              Initialization   Preparation   Compilation           I/O       Compute      Results     Ex. total   Termination        Total
              --------------   -----------   -----------   -----------  ------------  -----------   -----------   -----------   ----------
Application              n/a         19.06       1789.25           n/a           n/a         6.70         21.37           n/a      1831.17*
Runtime                    -         18.60       1787.48          2.93         11.37         0.12         14.42          1.32      1821.81
IPC                     1.77             -       1781.36          0.02          8.86            -          8.88             -      1792.01
Driver                  1.04             -       1779.21           n/a           n/a          n/a          7.70             -      1787.95

Total                   1.77*        19.06*      1789.25*         2.93*        11.74*        6.70*        21.37*         1.32*     1831.17*
===========================================================================================================================================
* This total ignores missing (n/a) values and thus is not necessarily consistent with the rest of the numbers

قد يتعذّر على المُحلِّل تحليل الأحداث التي تم جمعها إذا لم تكن تمثّل أثر استخدام التطبيق بالكامل. قد يتعذّر ذلك على وجه الخصوص إذا كانت أحداث systrace التي تم إنشاؤها لوضع علامة على نهاية قسم متوفّرة في التتبُّع بدون حدث مرتبط ببدء القسم. يحدث ذلك عادةً إذا تم إنشاء بعض الأحداث من جلسة تحليل أداء سابقة عند بدء أداة جمع بيانات systrace. في هذه الحالة، عليك إجراء عملية وضع الملف الشخصي مرة أخرى.

إضافة إحصاءات لرمز تطبيقك إلى ناتج systrace_parser

يستند تطبيق parse_systrace إلى وظيفة systrace المضمّنة في Android. يمكنك إضافة عمليات تتبُّع لعمليات معيّنة في تطبيقك باستخدام واجهة برمجة التطبيقات systrace API (للغة Java ، للتطبيقات الأصلية ) مع أسماء أحداث مخصّصة.

لربط الأحداث المخصّصة بمراحل دورة حياة التطبيق، أضِف إحدى السلاسل التالية إلى مقدمة اسم الحدث:

  • [NN_LA_PI]: حدث على مستوى التطبيق لبدء التشغيل
  • [NN_LA_PP]: حدث على مستوى التطبيق للتحضير
  • [NN_LA_PC]: حدث على مستوى التطبيق للترجمة
  • [NN_LA_PE]: حدث على مستوى التطبيق للتنفيذ

في ما يلي مثال على كيفية تغيير مثال رمز معالجة الصور في TFLite من خلال إضافة قسم runInferenceModel للمرحلة Execution وApplication الطبقتين اللتين تحتويان على أقسام أخرى preprocessBitmap لن يتم أخذها في الاعتبار في عمليات تتبُّع NNAPI. سيكون القسم runInferenceModel جزءًا من أحداث systrace التي يعالجها محلل systrace في nnapi:

Kotlin

/** Runs inference and returns the classification results. */
fun recognizeImage(bitmap: Bitmap): List {
   // This section won’t appear in the NNAPI systrace analysis
   Trace.beginSection("preprocessBitmap")
   convertBitmapToByteBuffer(bitmap)
   Trace.endSection()

   // Run the inference call.
   // Add this method in to NNAPI systrace analysis.
   Trace.beginSection("[NN_LA_PE]runInferenceModel")
   long startTime = SystemClock.uptimeMillis()
   runInference()
   long endTime = SystemClock.uptimeMillis()
   Trace.endSection()
    ...
   return recognitions
}

Java

/** Runs inference and returns the classification results. */
public List recognizeImage(final Bitmap bitmap) {

 // This section won’t appear in the NNAPI systrace analysis
 Trace.beginSection("preprocessBitmap");
 convertBitmapToByteBuffer(bitmap);
 Trace.endSection();

 // Run the inference call.
 // Add this method in to NNAPI systrace analysis.
 Trace.beginSection("[NN_LA_PE]runInferenceModel");
 long startTime = SystemClock.uptimeMillis();
 runInference();
 long endTime = SystemClock.uptimeMillis();
 Trace.endSection();
  ...
 Trace.endSection();
 return recognitions;
}

جودة الخدمة

في الإصدار 11 من نظام التشغيل Android والإصدارات الأحدث، يتيح NNAPI جودة خدمة أفضل من خلال السماح للتطبيق بتحديد الأولويات النسبية لنماذجه، والحد الأقصى للوقت المتوقّع لإعداد نموذج معيّن، والحد الأقصى للوقت المتوقّع لإكمال عملية حسابية معيّنة. يقدّم نظام التشغيل Android 11 أيضًا رموز نتائج إضافية لـ NNAPI تتيح للتطبيقات فهم حالات الفشل، مثل عدم الالتزام بالمهلة الزمنية للتنفيذ.

ضبط أولوية حمولة العمل

لضبط أولوية "وحدة عمل" NNAPI، يمكنك الاتصال بـ ANeuralNetworksCompilation_setPriority() قبل الاتصال بـ ANeuralNetworksCompilation_finish().

تحديد المواعيد النهائية

يمكن للتطبيقات تحديد مواعيد نهائية لكل من تجميع النماذج والاستنتاج.

مزيد من المعلومات حول المَعلمات

يتناول القسم التالي مواضيع متقدّمة حول استخدام المُعامِلات.

المتجهات الكمّية

مصفوفة الكمّية هي طريقة مختصرة لتمثيل صفيف من n بُعد من قيم النقطة الثابتة.

يتوافق NNAPI مع مصفوفات تنشيط الخلايا الكمّية غير المتماثلة بسعة 8 بت. بالنسبة إلى مصفوفات الخلاصات هذه، يتم تمثيل قيمة كل خلية بالعدد الصحيح المكوّن من 8 بت. يرتبط بالtensor مقياس وقيمة نقطة الصفر. وتُستخدَم هذه الوحدات لتحويل الأعداد الكاملة المكوّنة من 8 بت إلى قيم الكسور العشرية التي يتم تمثيلها.

الصيغة هي:

(cellValue - zeroPoint) * scale

حيث تكون قيمة zeroPoint عددًا صحيحًا بسعة 32 بت، ويكون scale قيمة نقطة عائمة بسعة 32 بت.

مقارنةً بالكتل المصغّرة التي تتألف من قيم النقطة العائمة بسعة 32 بت، تتمتع الكتل المصغّرة التي تتألف من قيم بسعة 8 بت بميزتَين:

  • يكون تطبيقك أصغر حجمًا، لأنّ الأوزان المدربة تشغل ربع حجم المصفوفات الثنائية 32 بت.
  • يمكن تنفيذ العمليات الحسابية غالبًا بشكل أسرع. ويعود ذلك إلى الكمية المنخفضة من البيانات التي يجب جلبها من الذاكرة وكفاءة المعالجات مثل وحدات معالجة الإشارات الرقمية (DSP) في إجراء العمليات الحسابية الصحيحة.

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

في NNAPI، يمكنك تحديد أنواع مصفوفات الخلاصات المحوَّلة إلى أرقام ثنائية من خلال ضبط حقل النوع في بنية بيانات ANeuralNetworksOperandType على ANEURALNETWORKS_TENSOR_QUANT8_ASYMM. يمكنك أيضًا تحديد مقياس مصفوفة تينسور وقيمة نقطة الصفر لها في بنية البيانات هذه.

بالإضافة إلى مصفوفات تنشيط الخلايا الكميّة غير المتماثلة بسعة 8 بت، يتيح NNAPI ما يلي:

المَعلمات الاختيارية

تأخذ بعض العمليات، مثل ANEURALNETWORKS_LSH_PROJECTION، مَعلمات اختيارية. للإشارة في النموذج إلى أنّه تمت حذف المُشغِّل الاختياري، استدِع الدالة ANeuralNetworksModel_setOperandValue() ، مع ضبط القيمة NULL للتخزين المؤقت والقيمة 0 للطول.

إذا كان القرار بشأن ما إذا كان الم Operand متوفّرًا أم لا يختلف لكل عملية تنفيذ، يمكنك الإشارة إلى أنّه تم حذف الم Operand باستخدام الدالتَين ANeuralNetworksExecution_setInput() أو ANeuralNetworksExecution_setOutput() ، مع ضبط NULL للوسيط و0 للطول.

مصفوفات ذات ترتيب غير معروف

أدخل نظام التشغيل Android 9 (المستوى 28 من واجهة برمجة التطبيقات) عوامل تشغيل نماذج ذات أبعاد غير معروفة ولكن مرتبة معروفة (عدد الأبعاد). أدخل نظام التشغيل Android 10 (المستوى 29 لواجهة برمجة التطبيقات) مصفوفات ذات ترتيب غير معروف، كما هو موضّح في ANeuralNetworksOperandType.

مقياس أداء NNAPI

يتوفّر مقياس أداء NNAPI على AOSP في platform/test/mlts/benchmark (تطبيق قياس الأداء) وplatform/test/mlts/models (النماذج ومجموعات البيانات).

يُقيِّم مقياس الأداء وقت الاستجابة والدقة ويقارن بين برامج التشغيل والأداء نفسه الذي تمّ باستخدام Tensorflow Lite على وحدة المعالجة المركزية (CPU) للنماذج ومجموعات البيانات نفسها.

لاستخدام مقياس الأداء، اتّبِع الخطوات التالية:

  1. وصِّل جهاز Android مستهدفًا بالكمبيوتر، وافتح نافذة طرفية، وتأكّد من إمكانية الوصول إلى الجهاز من خلال adb.

  2. إذا كان هناك أكثر من جهاز Android متصل، يمكنك تصدير متغيّر البيئة ANDROID_SERIAL للجهاز المستهدَف.

  3. انتقِل إلى دليل المصدر من المستوى الأعلى في Android.

  4. شغِّل الأوامر التالية:

    lunch aosp_arm-userdebug # Or aosp_arm64-userdebug if available
    ./test/mlts/benchmark/build_and_run_benchmark.sh
    

    في نهاية إجراء اختبار قياس الأداء، سيتم عرض نتائجه كصفحة HTML تم تمريرها إلى xdg-open.

سجلّات NNAPI

يُنشئ NNAPI معلومات تشخيصية مفيدة في سجلات النظام. لتحليل السجلات، استخدِم الأداة logcat.

يمكنك تفعيل تسجيل NNAPI التفصيلي لمراحل أو مكوّنات معيّنة من خلال ضبط السمة debug.nn.vlog (باستخدام adb shell) على القائمة التالية من القيم، المفصولة بمسافة أو فاصلة أو قوس:

  • model: إنشاء النماذج
  • compilation: إنشاء خطة تنفيذ النموذج وتجميعها
  • execution: تنفيذ النموذج
  • cpuexe: تنفيذ العمليات باستخدام تنفيذ وحدة المعالجة المركزية (CPU) لـ NNAPI
  • manager: معلومات حول إضافات NNAPI وواجهات الاستخدام والإمكانات المتاحة
  • all أو 1: جميع العناصر أعلاه

على سبيل المثال، لتفعيل أسلوب التسجيل المطوَّل بالكامل، استخدِم الأمر adb shell setprop debug.nn.vlog all. لإيقاف التسجيل المطوَّل، استخدِم الأمر adb shell setprop debug.nn.vlog '""'.

بعد تفعيل التسجيل التفصيلي، يتم إنشاء إدخالات السجل على مستوى INFO مع ضبط علامة على اسم المرحلة أو المكوّن.

إلى جانب debug.nn.vlog الرسائل الخاضعة للرقابة، توفّر مكوّنات واجهة برمجة التطبيقات NNAPI إدخالات سجلّ أخرى على مستويات مختلفة، يستخدم كلّ منها علامة سجلّ محدّدة.

للحصول على قائمة بالمكونات، ابحث في شجرة المصدر باستخدام العبارة التالية:

grep -R 'define LOG_TAG' | awk -F '"' '{print $2}' | sort -u | egrep -v "Sample|FileTag|test"

يعرض هذا التعبير حاليًا العلامات التالية:

  • BurstBuilder
  • عمليات معاودة الاتصال
  • CompilationBuilder
  • CpuExecutor
  • ExecutionBuilder
  • ExecutionBurstController
  • ExecutionBurstServer
  • ExecutionPlan
  • FibonacciDriver
  • GraphDump
  • IndexedShapeWrapper
  • IonWatcher
  • مدير
  • الذاكرة
  • MemoryUtils
  • MetaModel
  • ModelArgumentInfo
  • ModelBuilder
  • NeuralNetworks
  • OperationResolver
  • العمليات
  • OperationsUtils
  • PackageInfo
  • TokenHasher
  • TypeManager
  • Utils
  • ValidateHal
  • VersionedInterfaces

للتحكّم في مستوى رسائل السجلّ التي يعرضها logcat، استخدِم متغيّر البيئة ANDROID_LOG_TAGS.

لعرض المجموعة الكاملة من رسائل سجلّ NNAPI وإيقاف أي رسائل أخرى، اضبط ANDROID_LOG_TAGS على النحو التالي:

BurstBuilder:V Callbacks:V CompilationBuilder:V CpuExecutor:V ExecutionBuilder:V ExecutionBurstController:V ExecutionBurstServer:V ExecutionPlan:V FibonacciDriver:V GraphDump:V IndexedShapeWrapper:V IonWatcher:V Manager:V MemoryUtils:V Memory:V MetaModel:V ModelArgumentInfo:V ModelBuilder:V NeuralNetworks:V OperationResolver:V OperationsUtils:V Operations:V PackageInfo:V TokenHasher:V TypeManager:V Utils:V ValidateHal:V VersionedInterfaces:V *:S.

يمكنك ضبط ANDROID_LOG_TAGS باستخدام الأمر التالي:

export ANDROID_LOG_TAGS=$(grep -R 'define LOG_TAG' | awk -F '"' '{ print $2 ":V" }' | sort -u | egrep -v "Sample|FileTag|test" | xargs echo -n; echo ' *:S')

يُرجى العِلم أنّ هذا مجرد فلتر ينطبق على logcat. لا يزال عليك ضبط القيمة debug.nn.vlog على all لإنشاء معلومات حول السجلّ التفصيلي.