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

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

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

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

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

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

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

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

فهم وقت تشغيل واجهة برمجة تطبيقات Neur Networks API

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

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

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

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

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

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

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

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

  • النموذج: رسم بياني حاسوبي للعمليات الرياضية والقيم الثابتة التي تم تعلُّمها من خلال عملية تدريبية. وتخص هذه العمليات الشبكات العصبونية. وهي تشمل ثنائي الأبعاد (2D) الالتفاف والخدمات اللوجستية (السيغية) التفعيل، الالتفاف الخطي<br class="ph-1-5) وRectified line (Recrim="ph-1-5)> إنشاء نموذج هو عملية متزامنة. وبعد إنشاء المحتوى بنجاح، يمكن إعادة استخدامه في سلاسل المحادثات والمجموعات. في NNAPI، يتم تمثيل النموذج على أنّه ANeuralNetworksModel مثيل.
  • التجميع: يمثل هذا النوع إعدادًا لتجميع نموذج NNAPI في رمز بمستوى أدنى. إنشاء تجميع هو عملية متزامنة. بمجرد إنشائه بنجاح، يمكن إعادة استخدامه عبر سلاسل المحادثات وعمليات التنفيذ. في NNAPI، يتم تمثيل كل تجميع على شكل مثيل ANeuralNetworksCompilation.
  • الذاكرة: تمثّل الذاكرة المشتركة والملفات التي تم ربطها بالذاكرة والمخازن الاحتياطية المماثلة. يتيح استخدام المخزن المؤقت للذاكرة لوقت تشغيل NNAPI نقل البيانات إلى برامج التشغيل بكفاءة أكبر. ينشئ التطبيق عادةً مخزنًا مؤقتًا واحدًا للذاكرة المشتركة يحتوي على كل موتر مطلوب لتحديد نموذج. يمكنك أيضًا استخدام المخزن المؤقت للذاكرة لتخزين المدخلات والمخرجات لمثيل التنفيذ. في NNAPI، يتم تمثيل كل مخزن احتياطي للذاكرة كمثيل ANeuralNetworksMemory.
  • التنفيذ: واجهة لتطبيق نموذج NNAPI على مجموعة من المدخلات ولجمع النتائج. يمكن تنفيذ عملية التنفيذ بشكل متزامن أو غير متزامن.

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

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

يوضح الشكل 2 تدفق البرمجة الأساسي.

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

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

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

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

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

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

استخدام الموارد الاحتياطية للأجهزة الأصلية

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

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

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

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

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

ANeuralNetworksMemory_free(mem2);

ملاحظة:

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

الطراز

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

الخاص

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

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

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

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

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

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

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

الرسالة (على مستوى 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 ميكرو ثانية بين وقت إعلام سلسلة التعليمات أو تنشيطها ووقت ارتباطها بوحدة معالجة مركزية (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 إلى وحدة المعالجة المركزية (CPU) في حال تعذُّر التنفيذ في برنامج التشغيل. وينطبق ذلك أيضًا على نظام التشغيل Android 10 عند استخدام ANeuralNetworksCompilation_create بدلاً من ANeuralNetworksCompilation_createForDevices.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    ANeuralNetworksMemoryDesc_free(desc);
    

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

الصيغة هي:

(cellValue - zeroPoint) * scale

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

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

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

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

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

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

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

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

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

عشرات التصنيف غير معروف

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

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

يعرض هذا التعبير في الوقت الحالي العلامات التالية:

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