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

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

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

وهناك فوائد عديدة للاستنتاج على الجهاز فقط:

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

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

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

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

التعرّف على وقت تشغيل واجهة برمجة التطبيقات Neural Networks

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

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

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

يوضح الشكل 1 بنية النظام عالية المستوى لـ NNAPI.

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

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

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

يعتمد NNAPI على أربعة تجريدات رئيسية:

  • النموذج: هو رسم بياني حسابي للعمليات الرياضية والقيم الثابتة التي تم تعلُّمها من خلال عملية تدريب. هذه العمليات مخصصة للشبكات العصبية. وهي تشمل تفعيل الالتفاف الثنائي الأبعاد (2D) وتفعيل اللوجستي (السينية) والتفعيل الخطي المصحَّح (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. يتم تعريف كل نموذج من خلال واحد أو أكثر من المعاملات والعمليات.

المنتجات الخاصة

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

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

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

تتضمن معظم العمليات في NNAPI أدوات الموتر. الموتّرات عبارة عن صفائف ذات أبعاد ن. يتوافق NNAPI مع أدوات الكثافة مع نقطة عائمة 16 بت، ونقطة عائمة 32 بت، وكمية 8 بت، وكمية 16 بت، وعدد صحيح 32 بت، وقيم منطقية 8 بت.

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

الشكل 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 فقط. تم حل المشكلة في نظام التشغيل Android 10 (مستوى واجهة برمجة التطبيقات 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 بت في معهد الهندسة الكهربائية والإلكترونية IEE 754، وذلك من خلال طلب ANeuralNetworksModel_relaxComputationFloat32toFloat16().

  7. اطلب ANeuralNetworksModel_finish() لوضع اللمسات الأخيرة على تعريف نموذجك. إذا لم تكن هناك أخطاء، ستعرض هذه الدالة رمز النتيجة ANEURALNETWORKS_NO_ERROR.

    ANeuralNetworksModel_finish(model);
    

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

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

لدمج تدفق التحكم في نموذج 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 التي تعمل بنظام التشغيل 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، لا يتم استخدام تنفيذ وحدة المعالجة المركزية (CPU) للتعامل مع حالات الفشل الخاصة بتجميع النماذج وتنفيذها.

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

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

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

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

لفهم الطريقة التي تم بها تقسيم نموذجك بواسطة NNAPI، يمكنك مراجعة سجلّات Android بحثًا عن رسالة (في مستوى "معلومات" مع العلامة 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 التي تعمل بنظام التشغيل 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);

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

في نظام التشغيل Android 11 والإصدارات الأحدث، توفّر 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)

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

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

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

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

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

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

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

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

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

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

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

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

لتخصيص ذاكرة مبهمة، نفِّذ الخطوات التالية:

  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-mode في AHardwareBuffer.

إذا لم تعُد NNAPI بحاجة إلى الوصول إلى كائن الذاكرة المبهم، يمكنك إخلاء جزء ANeuralNetworksMemory المقابل:

ANeuralNetworksMemory_free(opaqueMem);

قياس الأداء

يمكنك تقييم أداء تطبيقك عن طريق قياس وقت التنفيذ أو إنشاء ملفات تعريفية.

وقت التنفيذ

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

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

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

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

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

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

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

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

وضع علامة على تطبيقك باستخدام Android Systrace

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

يأتي مصدر NNAPI مع أداة parse_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. بعد بدء أداة تجميع النظم، ابدأ تشغيل التطبيق وقم بإجراء اختبار قياس الأداء.

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

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

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

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

إليك مثال على طريقة تغيير الرمز البرمجي لنموذج تصنيف الصور TFLite، وذلك من خلال إضافة قسم runInferenceModel لمرحلة Execution والطبقة Application التي تحتوي على أقسام أخرى preprocessBitmap لن يتم إدراجها في عمليات تتبُّع NNAPI. سيكون القسم runInferenceModel جزءًا من أحداث 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 جودة الخدمة (QoS) من خلال السماح للتطبيق بالإشارة إلى الأولويات النسبية لنماذجه، والحد الأقصى لمقدار الوقت المتوقع لإعداد نموذج معيّن، والحد الأقصى لمقدار الوقت المتوقع لإكمال عملية حسابية معيّنة. يقدّم Android 11 أيضًا رموز نتائج NNAPI إضافية تمكّن التطبيقات من التعرّف على حالات التعذُّر، مثل المواعيد النهائية الفائتة للتنفيذ.

تحديد أولوية عبء العمل

لتحديد أولوية عبء العمل NNAPI، يمكنك استدعاء ANeuralNetworksCompilation_setPriority() قبل طلب ANeuralNetworksCompilation_finish().

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

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

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

يتناول القسم التالي مواضيع متقدمة عن استخدام المعاملات.

الموتر الكمّي

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

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

والمعادلة المستخدَمة في ذلك هي:

(cellValue - zeroPoint) * scale

التي تكون فيها قيمة نقطة الصفر هي عدد صحيح 32 بت، والمقياس هو قيمة النقطة العائمة 32 بت.

بالمقارنة مع موترات قيم النقاط العائمة 32 بت، فإن المكافئات الكمية 8 بت لها ميزتان:

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

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

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

بالإضافة إلى الموصّلات الكمية غير المتماثلة ذات 8 بت، تدعم NNAPI ما يلي:

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

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

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

شدات الرتبة غير معروفة

قدّم Android 9 (المستوى 28 من واجهة برمجة التطبيقات) معاملات نموذج ذات أبعاد غير معروفة ولكن ترتيبًا معروفًا (عدد السمات). قدّم Android 10 (المستوى 29 من واجهة برمجة التطبيقات) مقاييس تردد غير معروفة، كما هو موضّح في ANeural NetworksوحType.

معيار 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: تنفيذ العمليات باستخدام وحدة المعالجة المركزية NNAPI
  • manager: إضافات NNAPI، الواجهات المتاحة والمعلومات المتعلّقة بالإمكانات
  • all أو 1: كل العناصر المذكورة أعلاه

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

عند التفعيل، ينشئ التسجيل المطوَّل إدخالات السجلّ على مستوى "معلومات" مع ضبط علامة على اسم المرحلة أو المكوِّن.

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

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

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

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

  • أداة إنشاء متقطع
  • عمليات معاودة الاتصال
  • أداة إنشاء التجميع
  • أداة تنفيذ المعالجة المركزية (CpuExecutor)
  • أداة إنشاء التنفيذ
  • وحدة التحكّم التنفيذية
  • خادم التنفيذ ExecutionBurstServer
  • خطة التنفيذ
  • حامل فيبوناتشي
  • مؤتمر GraphDump
  • ملف IndexedShapeWrapper
  • قناة IonWatcher
  • مدير
  • Memory
  • ملفات MemoryUtils
  • نموذج تعريفي
  • معلومات نموذج الوسيطة
  • أداة إنشاء النماذج
  • الشبكات العصبية
  • محلل العملية
  • العمليات
  • أدوات استخدام العمليات
  • معلومات الحزمة
  • رمز TokenHasher
  • مدير الأنواع
  • الاستخدامات
  • التحقق من صحة هال
  • الواجهات ذات الإصدارات

للتحكّم في مستوى رسائل السجلّ المعروضة من خلال 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 لإنشاء معلومات سجل مطوَّل.