اعدام های پشت سر هم و صف های پیام سریع

شبکه‌های عصبی HAL 1.2 مفهوم اجراهای پشت سر هم (burst executions) را معرفی می‌کند. اجراهای پشت سر هم، دنباله ای از اجراهای یک مدل آماده شده هستند که به سرعت و پشت سر هم رخ می‌دهند، مانند آن‌هایی که روی فریم‌های ضبط دوربین یا نمونه‌های صوتی متوالی عمل می‌کنند. یک شیء پشت سر هم برای کنترل مجموعه‌ای از اجراهای پشت سر هم و برای حفظ منابع بین اجراها استفاده می‌شود و امکان اجرای سربار کمتر را فراهم می‌کند. اشیاء پشت سر هم سه بهینه‌سازی را ممکن می‌سازند:

  1. یک شیء burst قبل از یک توالی از اجراها ایجاد می‌شود و پس از پایان توالی آزاد می‌شود. به همین دلیل، طول عمر شیء burst به درایور نشان می‌دهد که چه مدت باید در حالت عملکرد بالا باقی بماند.
  2. یک شیء burst می‌تواند منابع را بین اجراها حفظ کند. برای مثال، یک درایور می‌تواند یک شیء حافظه را در اولین اجرا نگاشت کند و نگاشت را در شیء burst برای استفاده مجدد در اجراهای بعدی ذخیره کند. هر منبع cache شده می‌تواند زمانی که شیء burst از بین می‌رود یا زمانی که زمان اجرای NNAPI به شیء burst اطلاع می‌دهد که دیگر به منبع نیازی نیست، آزاد شود.
  3. یک شیء burst از صف‌های پیام سریع (FMQ) برای ارتباط بین فرآیندهای برنامه و درایور استفاده می‌کند. این امر می‌تواند تأخیر را کاهش دهد زیرا FMQ از HIDL عبور می‌کند و داده‌ها را مستقیماً از طریق یک FIFO دایره‌ای اتمی در حافظه مشترک به فرآیند دیگری منتقل می‌کند. فرآیند مصرف‌کننده می‌داند که باید یک آیتم را از صف خارج کند و پردازش را یا با نمونه‌برداری از تعداد عناصر موجود در FIFO یا با انتظار برای پرچم رویداد FMQ که توسط تولیدکننده علامت‌گذاری می‌شود، آغاز کند. این پرچم رویداد یک mutex سریع فضای کاربری (futex) است.

FMQ یک ساختار داده سطح پایین است که هیچ تضمینی برای طول عمر در بین فرآیندها ارائه نمی‌دهد و هیچ مکانیسم داخلی برای تعیین اینکه آیا فرآیند در انتهای دیگر FMQ مطابق انتظار اجرا می‌شود یا خیر، ندارد. در نتیجه، اگر تولیدکننده FMQ از کار بیفتد، ممکن است مصرف‌کننده در انتظار داده‌هایی بماند که هرگز نمی‌رسند. یک راه حل برای این مشکل این است که درایور، FMQها را با شیء burst سطح بالاتر مرتبط کند تا تشخیص دهد چه زمانی اجرای burst پایان یافته است.

از آنجا که اجراهای پشت سر هم بر روی آرگومان‌های یکسانی عمل می‌کنند و نتایج یکسانی مانند سایر مسیرهای اجرا را برمی‌گردانند، FMQهای زیربنایی باید داده‌های یکسانی را به و از درایورهای سرویس NNAPI منتقل کنند. با این حال، FMQها فقط می‌توانند انواع داده‌های ساده قدیمی را منتقل کنند. انتقال داده‌های پیچیده با سریال‌سازی و غیر سریال‌سازی بافرهای تو در تو (انواع برداری) مستقیماً در FMQها و استفاده از اشیاء فراخوانی HIDL برای انتقال دسته‌های حافظه در صورت تقاضا انجام می‌شود. سمت تولیدکننده FMQ باید پیام‌های درخواست یا نتیجه را به صورت اتمی با استفاده از MessageQueue::writeBlocking در صورت مسدود بودن صف، یا با استفاده از MessageQueue::write در صورت غیرمسدود بودن صف، به مصرف‌کننده ارسال کند.

رابط‌های پشت سر هم

رابط‌های burst برای شبکه‌های عصبی HAL در hardware/interfaces/neuralnetworks/1.2/ یافت می‌شوند و در زیر توضیح داده شده‌اند. برای اطلاعات بیشتر در مورد رابط‌های burst در لایه NDK، به frameworks/ml/nn/runtime/include/NeuralNetworks.h مراجعه کنید.

انواع.هال

types.hal نوع داده‌ای را که از طریق FMQ ارسال می‌شود، تعریف می‌کند.

  • FmqRequestDatum : یک عنصر واحد از نمایش سریالی شده از یک شیء Request اجرا و یک مقدار MeasureTiming که از طریق صف پیام سریع ارسال می‌شود.
  • FmqResultDatum : یک عنصر واحد از نمایش سریالی شده مقادیر برگشتی از یک اجرا ( ErrorStatus ، OutputShapes و Timing ) که از طریق صف پیام سریع برگشت داده می‌شود.

IBurstContext.hal

IBurstContext.hal شیء رابط HIDL را که در سرویس شبکه‌های عصبی قرار دارد، تعریف می‌کند.

  • IBurstContext : شیء Context برای مدیریت منابع یک burst.

IBurstCallback.hal

IBurstCallback.hal شیء رابط HIDL را برای یک فراخوانی برگشتی ایجاد شده توسط زمان اجرای شبکه‌های عصبی تعریف می‌کند و توسط سرویس شبکه‌های عصبی برای بازیابی اشیاء hidl_memory مربوط به شناسه‌های اسلات استفاده می‌شود.

  • IBurstCallback : شیء فراخوانی که توسط یک سرویس برای بازیابی اشیاء حافظه استفاده می‌شود.

IPreparedModel.hal

IPreparedModel.hal در HAL 1.2 با روشی برای ایجاد یک شیء IBurstContext از یک مدل آماده، توسعه داده شده است.

  • configureExecutionBurst : یک شیء burst را پیکربندی می‌کند که برای اجرای چندین استنتاج روی یک مدل آماده‌شده به صورت متوالی و سریع استفاده می‌شود.

پشتیبانی از اجرای پشت سر هم در یک درایور

ساده‌ترین راه برای پشتیبانی از اشیاء burst در یک سرویس HIDL NNAPI، استفاده از تابع کاربردی burst ::android::nn::ExecutionBurstServer::create است که در ExecutionBurstServer.h یافت می‌شود و در کتابخانه‌های استاتیک libneuralnetworks_common و libneuralnetworks_util بسته‌بندی شده است. این تابع factory دو overload دارد:

  • یک overload، یک اشاره‌گر به یک شیء IPreparedModel را می‌پذیرد. این تابع کاربردی از متد executeSynchronously در یک شیء IPreparedModel برای اجرای مدل استفاده می‌کند.
  • یک overload، یک شیء IBurstExecutorWithCache قابل تنظیم را می‌پذیرد که می‌تواند برای ذخیره منابع (مانند نگاشت‌های hidl_memory ) که در طول چندین اجرا باقی می‌مانند، استفاده شود.

هر overload یک شیء IBurstContext (که نشان دهنده شیء burst است) را برمی‌گرداند که شامل و مدیریت کننده thread شنونده اختصاصی خود است. این thread درخواست‌ها را از requestChannel FMQ دریافت می‌کند، استنتاج را انجام می‌دهد، سپس نتایج را از طریق resultChannel FMQ برمی‌گرداند. این thread و تمام منابع دیگر موجود در شیء IBurstContext به طور خودکار زمانی که کلاینت burst ارجاع خود را به IBurstContext از دست می‌دهد، آزاد می‌شوند.

به عنوان یک روش جایگزین، می‌توانید پیاده‌سازی خودتان از IBurstContext را ایجاد کنید که نحوه ارسال و دریافت پیام‌ها از طریق FMQهای requestChannel و resultChannel ارسال شده به IPreparedModel::configureExecutionBurst درک کند.

توابع کاربردی burst در 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);

در ادامه، یک پیاده‌سازی مرجع از رابط burst که در درایور نمونه شبکه‌های عصبی در 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();
}