عمليات التنفيذ المكثّفة وقوائم الرسائل السريعة

يقدّم Neural Networks HAL 1.2 مفهوم عمليات التنفيذ المتسلسلة. عمليات تنفيذ المعالجة المكثّفة هي تسلسل عمليات تنفيذ النموذج المعدّ نفسه التي تحدث في تتابع سريع، مثل تلك التي تعمل على لقطات من الكاميرا أو نماذج صوتية متتالية. يُستخدَم عنصر المعالجة المكثّفة للتحكّم في مجموعة من عمليات التنفيذ المكثّفة، وللحفاظ على الموارد بين عمليات التنفيذ، ما يتيح لعمليات التنفيذ استخدام موارد أقل. تتيح لك ميزة "التقاط الصور المتسلسلة" إجراء ثلاثة تحسينات:

  1. يتم إنشاء عنصر معالجة مكثّفة قبل تسلسل عمليات التنفيذ، ويتم تحريره عند انتهاء التسلسل. لهذا السبب، يشير عمر ملف المعالجة المكثّفة إلى المدة التي يجب أن يظل فيها في حالة عالية الأداء.
  2. يمكن أن يحتفظ عنصر البث ببعض الموارد بين عمليات التنفيذ. على سبيل المثال، يمكن لبرنامج IDE ربط عنصر ذاكرة في التنفيذ الأول وتخزين عملية الربط في ذاكرة التخزين المؤقت في عنصر البث لإعادة استخدامه في عمليات التنفيذ اللاحقة. يمكن تحرير أيّ مورد مخزّن مؤقتًا عند إتلاف عنصر المعالجة المكثّفة أو عندما يُعلم وقت تنفيذ NNAPI عنصر المعالجة المكثّفة بأنّ المورد لم يعُد مطلوبًا.
  3. يستخدم عنصر البث قوائم الرسائل السريعة (FMQ) للتواصل بين عمليات التطبيق وعمليات برنامج التشغيل. ويمكن أن يؤدي ذلك إلى خفض وقت الاستجابة لأنّ FMQ يتجاوز HIDL وينقل البيانات مباشرةً إلى عملية أخرى من خلال ذاكرة دوارة منتظمة لاتّباع نظام "الأوّل في الدخول أولاً" في الذاكرة المشتركة. تعرف عملية الاستهلاك أنّه يجب إزالة عنصر من "قائمة الانتظار" وبدء المعالجة إما من خلال فحص عدد العناصر في "قائمة الانتظار بترتيب أولوية الوصول إلى الذاكرة" أو من خلال الانتظار إلى أن يُرسل "المنتج" إشارة إلى علامة الحدث في "قائمة الانتظار للطلبات المجمّعة". علامة الحدث هذه هي قفل ملف ثنائي سريع في مساحة المستخدم (futex).

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

بما أنّ عمليات التنفيذ المُكثّفة تعمل على الوسيطات نفسها وتُعرِض النتائج نفسها مثل مسارات التنفيذ الأخرى، يجب أن تُرسِل طوابير FMQ الأساسية البيانات نفسها إلى وتلقّاها من برامج تشغيل خدمة NNAPI. ومع ذلك، لا يمكن لواجهات FMQ نقل سوى أنواع البيانات القديمة. يتم نقل البيانات المعقدة من خلال تسلسل وإلغاء تسلسل المخزن المؤقت المتداخل (أنواع المتجهات) مباشرةً في قوائم الانتظار للطلبات المتعدّدة، واستخدام عناصر الاستدعاء HIDL لنقل عناصر ذاكرة التخزين المؤقت عند الطلب. على جانب "المُنشئ" من "قائمة الانتظار للطلبات المتعدّدة" إرسال رسائل الطلب أو النتيجة إلى جانب "المستهلك" بشكلٍ منتظم باستخدام MessageQueue::writeBlocking إذا كانت قائمة الانتظار حظرًا، أو باستخدام MessageQueue::write إذا كانت قائمة الانتظار غير حظر.

واجهات التصوير المتسلسل

يمكن العثور على واجهات النبضات لـ HAL للشبكات العصبية في hardware/interfaces/neuralnetworks/1.2/ ، وهي موضّحة أدناه. لمزيد من المعلومات حول واجهات البث المفاجئ في frameworks/ml/nn/runtime/include/NeuralNetworks.h، يُرجى الاطّلاع على frameworks/ml/nn/runtime/include/NeuralNetworks.h.

types.hal

types.hal يحدِّد نوع البيانات التي يتم إرسالها عبر FMQ.

  • FmqRequestDatum: عنصر واحد لتمثيل تسلسلي لشيء تنفيذ Request وقيمته MeasureTiming، ويتم إرساله عبر قائمة انتظار الرسائل السريعة.
  • FmqResultDatum: عنصر واحد لتمثيل تسلسلي للقيم التي يتم عرضها من التنفيذ (ErrorStatus وOutputShapes وTiming)، والتي يتم عرضها من خلال قائمة الرسائل السريعة.

IBurstContext.hal

IBurstContext.hal يحدِّد عنصر واجهة HIDL الذي يتوفّر في خدمة الشبكات العصبية.

  • IBurstContext: عنصر سياق لإدارة موارد الفاصل الزمني.

IBurstCallback.hal

IBurstCallback.hal يحدِّد عنصر واجهة HIDL لمكالمة إعادة الاتصال التي أنشأها وقت تنفيذ Neural Networks ، وتستخدمها خدمة Neural Networks لاسترداد عناصر hidl_memory المتوافقة مع معرّفات الشريحة.

  • IBurstCallback: عنصر استدعاء تستخدمه الخدمة لاسترداد عناصر الذاكرة.

IPreparedModel.hal

تم توسيع نطاق IPreparedModel.hal في HAL 1.2 باستخدام طريقة لإنشاء عنصر IBurstContext من نموذج معدّ.

  • configureExecutionBurst: يضبط عنصر الاندفاع المستخدَم لتنفيذ استنتاجات متعدّدة على نموذج تم إعداده في تتابع سريع.

إتاحة عمليات التنفيذ المكثّفة في برنامج تشغيل

إنّ أبسط طريقة لتفعيل عناصر البث في خدمة HIDL NNAPI هي استخدام وظيفة أداة البث ::android::nn::ExecutionBurstServer::create، والتي يمكن العثور عليها في ExecutionBurstServer.h وتعبئتها في المكتبتَين الثابتتَين libneuralnetworks_common وlibneuralnetworks_util. تحتوي دالة المصنع هذه على تحميل زائدَين:

  • يقبل أحد أشكال الترجيع المؤشر إلى عنصر IPreparedModel. تستخدِم دالة المساعدة هذه الطريقة executeSynchronously في كائن IPreparedModel لتنفيذ النموذج.
  • يقبل أحد أساليب التحميل الزائد عنصر IBurstExecutorWithCache قابل للتخصيص، يمكن استخدامه لتخزين الموارد مؤقتًا (مثل عمليات الربط hidl_memory) التي تظل محفوظة على مدار عمليات تنفيذ متعددة.

تعرض كل طريقة معالجة زائدة عنصر IBurstContext (الذي يمثّل عنصر النفث) الذي يحتوي على سلسلة محادثات مستمع مخصّصة له ويديرها. تتلقّى هذه السلسلة من الرسائل طلبات من "الطلبات الفورية في الذاكرة" في requestChannel، ثم تُجري الاستنتاج، ثم تُعرض النتائج من خلال "الطلبات الفورية في الذاكرة" في resultChannel. يتم تلقائيًا تحرير سلسلة المحادثات هذه وجميع موارد الأخرى المضمّنة في عنصر IBurstContext عندما يفقد عميل البث المفاجئ مرجعه إلى IBurstContext.

بدلاً من ذلك، يمكنك إنشاء تطبيقك الخاص لـ IBurstContext الذي يفهم كيفية إرسال الرسائل واستلامها من خلال requestChannel و resultChannel FMQs التي تم تمريرها إلى IPreparedModel::configureExecutionBurst.

يمكن العثور على دوال أدوات الصور المتسلسلة في ExecutionBurstServer.h.

/**
 * Create automated context to manage FMQ-based executions.
 *
 * This function is intended to be used by a service to automatically:
 * 1) Receive data from a provided FMQ
 * 2) Execute a model with the given information
 * 3) Send the result to the created FMQ
 *
 * @param callback Callback used to retrieve memories corresponding to
 *     unrecognized slots.
 * @param requestChannel Input FMQ channel through which the client passes the
 *     request to the service.
 * @param resultChannel Output FMQ channel from which the client can retrieve
 *     the result of the execution.
 * @param executorWithCache Object which maintains a local cache of the
 *     memory pools and executes using the cached memory pools.
 * @result IBurstContext Handle to the burst context.
 */
static sp<ExecutionBurstServer> create(
        const sp<IBurstCallback>& callback, const FmqRequestDescriptor& requestChannel,
        const FmqResultDescriptor& resultChannel,
        std::shared_ptr<IBurstExecutorWithCache> executorWithCache);

/**
 * Create automated context to manage FMQ-based executions.
 *
 * This function is intended to be used by a service to automatically:
 * 1) Receive data from a provided FMQ
 * 2) Execute a model with the given information
 * 3) Send the result to the created FMQ
 *
 * @param callback Callback used to retrieve memories corresponding to
 *     unrecognized slots.
 * @param requestChannel Input FMQ channel through which the client passes the
 *     request to the service.
 * @param resultChannel Output FMQ channel from which the client can retrieve
 *     the result of the execution.
 * @param preparedModel PreparedModel that the burst object was created from.
 *     IPreparedModel::executeSynchronously will be used to perform the
 *     execution.
 * @result IBurstContext Handle to the burst context.
 */
  static sp<ExecutionBurstServer> create(const sp<IBurstCallback>& callback,
                                         const FmqRequestDescriptor& requestChannel,
                                         const FmqResultDescriptor& resultChannel,
                                         IPreparedModel* preparedModel);

في ما يلي تنفيذ مرجعي لواجهة المعالجة المكثّفة للبيانات في ملف رمز المصدر المخصص لبرنامج التشغيل نموذج الشبكات العصبية على الرابط frameworks/ml/nn/driver/sample/SampleDriver.cpp.

Return<void> SamplePreparedModel::configureExecutionBurst(
        const sp<V1_2::IBurstCallback>& callback,
        const MQDescriptorSync<V1_2::FmqRequestDatum>& requestChannel,
        const MQDescriptorSync<V1_2::FmqResultDatum>& resultChannel,
        configureExecutionBurst_cb cb) {
    NNTRACE_FULL(NNTRACE_LAYER_DRIVER, NNTRACE_PHASE_EXECUTION,
                 "SampleDriver::configureExecutionBurst");
    // Alternatively, the burst could be configured via:
    // const sp<V1_2::IBurstContext> burst =
    //         ExecutionBurstServer::create(callback, requestChannel,
    //                                      resultChannel, this);
    //
    // However, this alternative representation does not include a memory map
    // caching optimization, and adds overhead.
    const std::shared_ptr<BurstExecutorWithCache> executorWithCache =
            std::make_shared<BurstExecutorWithCache>(mModel, mDriver, mPoolInfos);
    const sp<V1_2::IBurstContext> burst = ExecutionBurstServer::create(
            callback, requestChannel, resultChannel, executorWithCache);
    if (burst == nullptr) {
        cb(ErrorStatus::GENERAL_FAILURE, {});
    } else {
        cb(ErrorStatus::NONE, burst);
    }
    return Void();
}