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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ANeuralNetworksMemory_free(mem2);

ملاحظة:

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

الطراز

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

الخاصون

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

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

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

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

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

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

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

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

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

لا يمكن استخدام معامل كإدخال وإخراج لنموذج معيّن.

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

للحصول على معلومات إضافية عن استخدام المعاملات، يُرجى الاطّلاع على مزيد من المعلومات عن معاملات.

العمليات

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

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

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

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

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

يتم تلخيص العمليات التي يدعمها NNAPI في الجدول أدناه:

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

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

إنشاء نماذج

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

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

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

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

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

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

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

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

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

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

    كمَعلمات لهذه الاستدعاء، يجب أن يوفّر تطبيقك ما يلي:

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

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

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

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

    // Our model has one input (0) and one output (6)
    uint32_t modelInputIndexes[1] = {0};
    uint32_t modelOutputIndexes[1] = {6};
    ANeuralNetworksModel_identifyInputsAndOutputs(model, 1, modelInputIndexes, 1 modelOutputIndexes);
    
  6. اختياريًا، حدِّد ما إذا كان يُسمح لحساب ANEURALNETWORKS_TENSOR_FLOAT32 بمدى دقة أو دقة منخفضة مثل تنسيق النقطة العائمة 16 بت 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 الناتج كالمعتاد. تعرض الدالة خطأ إذا كان النموذج الذي يحتوي على عمليات غير متوافقة مع الأجهزة المحددة.

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

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

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

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

عندما تتوفّر عدة أجهزة للنموذج، يتم توزيع العمل على جميع الأجهزة في وقت تشغيل 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.

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

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

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

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

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

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

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

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

لضمان عدم تنفيذ وحدة المعالجة المركزية (CPU)، استخدِم ANeuralNetworksCompilation_createForDevices مع استبعاد nnapi-reference من قائمة الأجهزة. بدءًا من نظام التشغيل Android P، يمكن إيقاف الإجراء الاحتياطي في وقت التنفيذ على إصدارات DEBUG من خلال ضبط السمة 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 systrace باستخدام الأمر التالي:
$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 على وظيفة نظام Android systrace المدمجة. يمكنك إضافة عمليات تتبُّع لعمليات محدّدة في تطبيقك باستخدام واجهة برمجة تطبيقات systrace (للغة Java والتطبيقات الأصلية ) مع أسماء الأحداث المخصّصة.

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

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

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

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

لتحديد أولوية عبء عمل NNAPI، يُرجى الاتصال بالرقم ANeuralNetworksCompilation_setPriority() قبل طلب الرقم ANeuralNetworksCompilation_finish().

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

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

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

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

متوترات كمية

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

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

الصيغة هي:

(cellValue - zeroPoint) * scale

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

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

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

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

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

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

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

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

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

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

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

مقياس أداء NNAPI

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

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

لاستخدام مقياس الأداء، يُرجى اتّباع الخطوات التالية:

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

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

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

  4. قم بتشغيل الأوامر التالية:

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

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

سجلّات NNAPI

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

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

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

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

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

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

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

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

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

  • أداة إنشاء الصور المتسلسلة
  • عمليات معاودة الاتصال
  • منشئ التجميع
  • أداة CpuExecutor
  • أداة إنشاء التنفيذ
  • وحدة التحكّم ExecutionBurst
  • خادم ExecutionBurstServer
  • خطة التنفيذ
  • سلسلة FibonacciDriver
  • GraphDump
  • IndexedShapeWrapper
  • Ionwatcher
  • مدير
  • Memory
  • استخدامات الذاكرة
  • نموذج تجريبي
  • معلومات ModelArgumentInfo
  • أداة إنشاء النماذج
  • الشبكات العصبونية
  • أداة حلّ العمليات
  • العمليات
  • أدوات العمليات
  • معلومات الحزمة
  • رمز TokenHasher
  • مدير الأنواع
  • الاستخدامات
  • التحقق من صحة Hal
  • الواجهات ذات الإصدارات

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