Neural Networks API

واجهة برمجة تطبيقات Android Neral Networks API (NNAPI) هي واجهة برمجة تطبيقات لنظام التشغيل Android C مصمَّمة لتشغيل عمليات حسابية مكثفة لتعلُّم الآلة على أجهزة 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 أربعة عناصر مجردة رئيسية:

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

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

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

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

الشكل 2. مسار البرمجة لواجهة برمجة تطبيقات الشبكة العصبونية في Android

يصف الجزء المتبقي من هذا القسم خطوات إعداد نموذج 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 مصفوفات. المتغيرات عبارة عن صفائف مكوّنة من n. يتوافق NNAPI مع أدوات وحدة قياس بتنسيق النقاط العائمة 16 بت، والنقطة العائمة 32 بت، والكمّية بنظام 8 بت، وعدد 16 بت الصحيح، والقيم المنطقية 8 بت.

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

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

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

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

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

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

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

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

العمليات

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

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

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

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

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

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

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

مشكلة معروفة في المستوى 28 من واجهة برمجة التطبيقات: عند نقل عشرات ANEURALNETWORKS_TENSOR_QUANT8_ASYMM إلى العملية 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 باستخدام نطاق أو دقة منخفضة مثل التنسيق IEEE 754 للنقطة العائمة 16 بت من خلال استدعاء 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 CPU باستخدام ANeuralNetworksDevice بالاسم nnapi-reference والنوع ANEURALNETWORKS_DEVICE_TYPE_CPU. عند استدعاء الدالة ANeuralNetworksCompilation_createForDevices، لا يتم استخدام عملية تنفيذ وحدة المعالجة المركزية (CPU) لمعالجة حالات تعذُّر تجميع النماذج وتنفيذها.

تقع على عاتق التطبيق مسؤولية تقسيم النموذج إلى نماذج فرعية يمكن تشغيلها على الأجهزة المحدّدة. بالنسبة إلى التطبيقات التي لا تحتاج إلى مشاركة ملف التمهيد يدويًا، يجب أن تستمر في طلب 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 ميكرو ثانية بين وقت إرسال إشعار إلى سلسلة مهام أو بدء معالجة سلسلة مهام ووقت ربطها أخيرًا بأحد نوى وحدة المعالجة المركزية.

لتحسين وقت الاستجابة، يمكنك بدلاً من ذلك توجيه تطبيق لإجراء مكالمة تحليل تزامنية إلى وقت التشغيل. ولن يتم عرض هذا الطلب إلا بعد اكتمال عملية الاستنتاج بدلاً من عرضه بعد بدء عملية الاستنتاج. بدلاً من استدعاء 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 إلى تنفيذ وحدة المعالجة المركزية الخاصة به للعمليات أو أكثر.

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

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

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

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

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

هناك حالات لا تتوفر فيها بعض العمليات على وحدة المعالجة المركزية (CPU)، وفي مثل هذه الحالات، يتعذّر التجميع أو التنفيذ بدلاً من التراجع عن العملية.

حتى بعد إيقاف وضع "الرجوع إلى وحدة المعالجة المركزية"، قد تظل هناك عمليات في النموذج مُجدوَلة على وحدة المعالجة المركزية. إذا كانت وحدة المعالجة المركزية مضمّنة في قائمة المعالجات التي تم إرسالها إلى 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" ورمز برنامج التشغيل
  • Driver: عملية برنامج تشغيل المسرّع

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

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

  1. يمكنك بدء تتبُّع نظام 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().

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

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

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

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

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

يعتبر المتنسور الكمي طريقة مضغوطة لتمثيل صفيفة الأبعاد النائية لقيم النقاط العائمة.

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

المعادلة هي:

(cellValue - zeroPoint) * scale

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

مقارنةً بوحدات التخزين المكثّفة التي تتضمّن قيمًا بنقطة عائمة 32 بت، تتمتع وحدات التخزين المكثّفة التي تتضمّن قيمًا بدقة 8 بت بميزتَين:

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

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

في 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
  • أداة إنشاء التنفيذ
  • ExecutionBurstController
  • ExecutionBurstServer
  • ExecutionPlan
  • FibonacciDriver
  • GraphDump
  • IndexedShapeWrapper
  • IonWatcher
  • مدير
  • الذاكرة
  • أدوات الذاكرة
  • MetaModel
  • ModelArgumentInfo
  • مصمم النماذج
  • 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 لإنشاء معلومات حول السجلّ التفصيلي.