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

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

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

هناك العديد من المزايا التي توفّرها تقنية الإبلاغ على الجهاز فقط:

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

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

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

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

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

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

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

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

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

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

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

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

الجهات المعنيّة

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

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

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

تشتمل معظم العمليات في NNAPI على متسللات. المتغيرات عبارة عن صفائف مكوّنة من n. يتوافق NNAPI مع أدوات وحدة قياس بتنسيق النقاط العائمة 16 بت، والنقطة العائمة 32 بت، والكمّية بنظام 8 بت، وعدد 16 بت الصحيح، والقيم المنطقية 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 بنطاق أو دقة منخفضة على نحو أقل من نطاق تنسيق النقطة العائمة بتنسيق IEEE 754 16 بت من خلال استدعاء 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 التي تعمل بالإصدار 10 من نظام التشغيل Android (المستوى 29 لواجهة برمجة التطبيقات) والإصدارات الأحدث، توفّر واجهة برمجة التطبيقات NNAPI وظائف تتيح للمكتبات والتطبيقات المستندة إلى أُطر عمل تعلُّم الآلة الحصول على معلومات حول الأجهزة المتاحة وتحديد الأجهزة التي سيتم استخدامها في التنفيذ. إنّ توفير معلومات عن الأجهزة المتاحة يسمح للتطبيقات بالحصول على الإصدار الدقيق من برامج التشغيل المتوفّرة على الجهاز لتجنّب حالات عدم التوافق المعروفة. ويمكن تحسين التطبيقات لتلائم جهاز Android الذي يتم نشر التطبيقات عليه، وذلك من خلال منح التطبيقات إمكانية تحديد الأجهزة التي ستُنفذ أقسامًا مختلفة من أحد النماذج.

رصد الأجهزة

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

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

تخصيص الجهاز

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

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

وإذا تم تحديد عدة أجهزة، تكون بيئة التشغيل مسؤولة عن توزيع العمل على الأجهزة.

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

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

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

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

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

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

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

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

الرسالة (على مستوى المعلومات مع العلامة 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 ميكرو ثانية بين وقت إرسال إشعار إلى سلسلة التعليمات أو تشغيلها ووقت ربطها بوحدة معالجة مركزية (CPU) في النهاية.

لتحسين وقت الاستجابة، يمكنك بدلاً من ذلك توجيه أحد التطبيقات لإجراء استدعاء استنتاج متزامن إلى بيئة التشغيل. سيعود هذا الاستدعاء فقط عند اكتمال الاستنتاج بدلاً من العودة بمجرد بدء الاستنتاج. بدلاً من استدعاء ANeuralNetworksExecution_startCompute لطلب استنتاج غير متزامن لبيئة التشغيل، يطلب التطبيق ANeuralNetworksExecution_compute لإجراء استدعاء متزامن لبيئة التشغيل. لن تؤدي المكالمة إلى "ANeuralNetworksExecution_compute" إلى إجراء ANeuralNetworksEvent ولا يتم إقرانها بمكالمة إلى "ANeuralNetworksEvent_wait".

عمليات تنفيذ الصور المتسلسلة

على أجهزة Android التي تعمل بالإصدار 10 من نظام التشغيل Android (المستوى 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) الاحتياطية

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

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

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

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

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

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

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

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

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

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

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

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

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

  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 وفي 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. عند اكتمال الاختبار، يمكنك إنهاء سجلّ النظام من خلال الضغط على 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: يطبع جميع الأحداث التي تمّ جمعها من النظام - --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_parser

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

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

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

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

جودة الخدمة

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

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

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

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

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

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

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

نقاط التوتر الكمّية

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

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

المعادلة هي:

(cellValue - zeroPoint) * scale

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

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

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

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

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

بالإضافة إلى مأخذ القوة غير المتماثلة 8 بت، تدعم NNAPI ما يلي:

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

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

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

قيم ترتيب غير معروف

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

مقياس أداء 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"

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

  • أداة إنشاء الصور المصغّرة
  • طلبات معاودة الاتصال
  • أداة إنشاء التحويل البرمجي
  • وحدة المعالجة المركزية (CPU)
  • أداة إنشاء التنفيذ
  • وحدة التحكّم التنفيذية
  • خادم ExecutionBurstServer
  • خطة التنفيذ
  • سائق فيبوناتشي
  • تفريغ الرسم البياني
  • ملف IndexedShapeWrapper
  • ساعة IonWatcher
  • مدير
  • الذاكرة
  • أدوات الذاكرة
  • نموذج وصفي
  • معلومات النموذج
  • مصمم النماذج
  • الشبكات العصبية
  • أداة حلّ العمليات
  • العمليات
  • عمليات التشغيل
  • معلومات الحزمة
  • رمز 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 لإنشاء معلومات سجلّ مطوَّل.