Eksekusi burst dan antrean pesan cepat

Neural Networks HAL 1.2 memperkenalkan konsep eksekusi burst. Eksekusi burst adalah urutan eksekusi model yang disiapkan sama yang terjadi secara berurutan dengan cepat, seperti yang beroperasi pada frame pengambilan gambar kamera atau sampel audio berturut-turut. Objek burst digunakan untuk mengontrol serangkaian eksekusi burst, dan untuk mempertahankan resource di antara eksekusi, sehingga eksekusi memiliki overhead yang lebih rendah. Objek burst memungkinkan tiga pengoptimalan:

  1. Objek burst dibuat sebelum urutan eksekusi, dan dibebaskan saat urutan berakhir. Oleh karena itu, masa aktif objek burst memberikan petunjuk kepada driver untuk menentukan berapa lama objek tersebut harus tetap dalam status performa tinggi.
  2. Objek burst dapat mempertahankan resource di antara eksekusi. Misalnya, driver dapat memetakan objek memori pada eksekusi pertama dan meng-cache pemetaan dalam objek burst untuk digunakan kembali dalam eksekusi berikutnya. Semua resource yang di-cache dapat dirilis saat objek burst dihancurkan atau saat runtime NN memberi tahu objek burst bahwa resource tidak lagi diperlukan.
  3. Objek burst menggunakan antrean pesan cepat (FMQ) untuk berkomunikasi antara proses aplikasi dan driver. Hal ini dapat mengurangi latensi karena FMQ mengabaikan HIDL dan meneruskan data langsung ke proses lain melalui FIFO melingkar atom dalam memori bersama. Proses konsumen mengetahui cara menghapus item dari antrean dan mulai memproses dengan melakukan polling jumlah elemen dalam FIFO atau dengan menunggu flag peristiwa FMQ, yang diberi sinyal oleh produsen. Flag peristiwa ini adalah userspace mutex yang cepat (futex).

FMQ adalah struktur data tingkat rendah yang tidak menawarkan jaminan sepanjang waktu di seluruh proses dan tidak memiliki mekanisme bawaan untuk menentukan apakah proses di sisi lain FMQ berjalan seperti yang diharapkan. Akibatnya, jika produser FMQ mati, konsumen mungkin terjebak menunggu data yang tidak pernah tiba. Salah satu solusi untuk masalah ini adalah dengan meminta driver mengaitkan FMQ dengan objek burst tingkat yang lebih tinggi untuk mendeteksi kapan eksekusi burst berakhir.

Karena eksekusi burst beroperasi pada argumen yang sama dan menampilkan hasil yang sama seperti jalur eksekusi lainnya, FMQ yang mendasarinya harus meneruskan data yang sama ke dan dari driver layanan NNAPI. Namun, FMQ hanya dapat mentransfer jenis data biasa. Mentransfer data kompleks dilakukan dengan melakukan serialisasi dan deserialisasi buffering bertingkat (jenis vektor) secara langsung di FMQ, dan menggunakan objek callback HIDL untuk mentransfer handle kumpulan memori sesuai permintaan. Sisi produsen FMQ harus mengirim pesan permintaan atau hasil ke konsumen secara otomatis menggunakan MessageQueue::writeBlocking jika antrean memblokir, atau menggunakan MessageQueue::write jika antrean tidak memblokir.

Antarmuka burst

Antarmuka burst untuk HAL Jaringan Neural ditemukan di hardware/interfaces/neuralnetworks/1.2/ dan dijelaskan di bawah. Untuk informasi selengkapnya tentang antarmuka burst di lapisan NDK, lihat frameworks/ml/nn/runtime/include/NeuralNetworks.h.

types.hal

types.hal menentukan jenis data yang dikirim melalui FMQ.

  • FmqRequestDatum: Satu elemen representasi serial dari objek Request eksekusi dan nilai MeasureTiming, yang dikirim melalui antrean pesan cepat.
  • FmqResultDatum: Elemen tunggal dari representasi serialisasi nilai yang ditampilkan dari eksekusi (ErrorStatus, OutputShapes, dan Timing), yang ditampilkan melalui antrean pesan cepat.

IBurstContext.hal

IBurstContext.hal menentukan objek antarmuka HIDL yang berada dalam layanan Jaringan Neural.

IBurstCallback.hal

IBurstCallback.hal menentukan objek antarmuka HIDL untuk callback yang dibuat oleh runtime Neural Networks dan digunakan oleh layanan Neural Networks untuk mengambil objek hidl_memory yang sesuai dengan ID slot.

  • IBurstCallback: Objek callback yang digunakan oleh layanan untuk mengambil objek memori.

IPreparedModel.hal

IPreparedModel.hal diperluas di HAL 1.2 dengan metode untuk membuat objek IBurstContext dari model yang disiapkan.

  • configureExecutionBurst: Mengonfigurasi objek burst yang digunakan untuk menjalankan beberapa inferensi pada model yang disiapkan secara berurutan dengan cepat.

Mendukung eksekusi burst dalam driver

Cara paling sederhana untuk mendukung objek burst dalam layanan HIDL NNAPI adalah dengan menggunakan fungsi utilitas burst ::android::nn::ExecutionBurstServer::create, yang ditemukan dalam ExecutionBurstServer.h dan dikemas dalam library statis libneuralnetworks_common dan libneuralnetworks_util. Fungsi factory ini memiliki dua overload:

  • Satu overload menerima pointer ke objek IPreparedModel. Fungsi utilitas ini menggunakan metode executeSynchronously dalam objek IPreparedModel untuk menjalankan model.
  • Satu overload menerima objek IBurstExecutorWithCache yang dapat disesuaikan, yang dapat digunakan untuk menyimpan resource ke dalam cache (seperti pemetaan hidl_memory) yang tetap ada di beberapa eksekusi.

Setiap overload menampilkan objek IBurstContext (yang mewakili objek burst) yang berisi dan mengelola thread pemroses khusus. Thread ini menerima permintaan dari FMQ requestChannel, melakukan inferensi, lalu menampilkan hasilnya melalui resultChannel FMQ. Thread ini dan semua resource lainnya yang terdapat dalam objek IBurstContext akan otomatis dirilis saat klien burst kehilangan referensinya ke IBurstContext.

Atau, Anda dapat membuat implementasi IBurstContext Anda sendiri yang memahami cara mengirim dan menerima pesan melalui FMQ requestChannel dan resultChannel yang diteruskan ke IPreparedModel::configureExecutionBurst.

Fungsi utilitas burst ditemukan di 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);

Berikut adalah implementasi referensi antarmuka burst yang ditemukan di driver contoh Jaringan Saraf di 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();
}