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

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

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

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

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

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

يمكن العثور على واجهات نقل البيانات السريع في طبقة HAL الخاصة بالشبكات العصبية في hardware/interfaces/neuralnetworks/1.2/ وهي موضّحة أدناه. لمزيد من المعلومات حول واجهات نقل البيانات السريع في طبقة NDK، يُرجى الاطّلاع على 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 في الإصدار 1.2 من طبقة تجريد الأجهزة (HAL) لإنشاء عنصر IBurstContext من نموذج مُعدّ.

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

تنفيذ عمليات متسلسلة في برنامج تشغيل

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

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

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

بدلاً من ذلك، يمكنك إنشاء عملية تنفيذ خاصة بك للوظيفة IBurstContext تفهم كيفية إرسال الرسائل واستلامها عبر requestChannel وresultChannel FMQ التي تم تمريرها إلى 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);

في ما يلي تنفيذ مرجعي لواجهة نقل البيانات السريع التي تم العثور عليها في برنامج تشغيل نموذج &quot;الشبكات العصبية&quot; في 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();
}