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

‫Neural Networks HAL 1.2 مفهوم اجرای دسته‌ای را معرفی می‌کند. اجراهای پی‌درپی توالی اجراهای مدل آماده‌شده یکسانی هستند که به‌سرعت پشت سر هم رخ می‌دهند، مثل اجراهایی که روی قاب‌های ضبط دوربین یا نمونه‌های صدای متوالی عمل می‌کنند. از شیء انفجاری برای کنترل مجموعه‌ای از اجراهای انفجاری و برای حفظ منابع بین اجراها استفاده می‌شود، که باعث می‌شود اجراها سربار کمتری داشته باشند. اشیاء Burst سه بهینه‌سازی را فعال می‌کنند:

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

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

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

میاناهای رگباری

میاناهای انفجاری برای «لایه انتزاع سخت‌افزاری شبکه‌های عصبی» در 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 را برای یک برگشت تماس که توسط زمان اجرای «شبکه‌های عصبی» ایجاد شده است تعریف می‌کند و سرویس «شبکه‌های عصبی» از آن برای بازیابی اشیای 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 FMQ دریافت می‌کند، استنتاج را انجام می‌دهد، سپس نتایج را ازطریق resultChannel FMQ برمی‌گرداند. این رشته و همه منابع دیگر موجود در شیء IBurstContext به‌طور خودکار آزاد می‌شوند وقتی مشتری burst مرجع خود را به IBurstContext ازدست می‌دهد.

همچنین می‌توانید پیاده‌سازی خودتان را از IBurstContext ایجاد کنید که بداند چگونه پیام‌ها را ازطریق FMQهای requestChannel و resultChannel ارسال و دریافت کند که به 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();
}