突發執行和快速訊息佇列

神經網路 HAL 1.2 引入了突發執行的概念。突發執行是快速連續發生的相同準備模型的執行序列,例如對相機捕獲的幀或連續音頻樣本進行的操作。突發物件用於控制一組突發執行,並在執行之間保留資源,從而使執行具有較低的開銷。突發物件可實現三種最佳化:

  1. 突發物件在執行序列之前創建,並在序列結束時釋放。因此,突發物件的生命週期向驅動程式提示它應該保持高效能狀態多久。
  2. 突發物件可以在執行之間保留資源。例如,驅動程式可以在第一次執行時映射記憶體對象,並將映射緩存在突發對像中,以便在後續執行中重複使用。當突發物件被銷毀或 NNAPI 執行時間通知突發物件不再需要該資源時,可以釋放任何快取的資源。
  3. 突發物件使用快速訊息佇列(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

類型.hal

types.hal定義透過 FMQ 發送的資料類型。

  • FmqRequestDatum :執行Request物件和MeasureTiming值的序列化表示的單一元素,透過快速訊息佇列傳送。
  • FmqResultDatum :執行傳回值的序列化表示形式( ErrorStatusOutputShapesTiming )的單一元素,透過快速訊息佇列傳回。

IBurstContext.hal

IBurstContext.hal定義神經網路服務中的 HIDL 介面物件。

IBurstCallback.hal

IBurstCallback.hal為神經網路運行時所建立的回呼定義 HIDL 介面對象,並由神經網路服務用來檢索與槽標識符對應的hidl_memory對象。

  • IBurstCallback :服務用來檢索記憶體物件的回呼物件。

IPreparedModel.hal

IPreparedModel.hal在 HAL 1.2 中進行了擴展,提供了從準備好的模型建立IBurstContext物件的方法。

支援驅動程式中的突發執行

在 HIDL NNAPI 服務中支援突發物件的最簡單方法是使用突發實用函數::android::nn::ExecutionBurstServer::create ,該函數位於ExecutionBurstServer.h中,並打包在libneuralnetworks_commonlibneuralnetworks_util靜態庫中。此工廠函數有兩個重載:

  • 一個重載接受指向IPreparedModel物件的指標。此實用程式函數使用IPreparedModel物件中的executeSynchronously方法來執行模型。
  • 一個重載接受可自訂的IBurstExecutorWithCache對象,可用於快取在多次執行中持續存在的資源(例如hidl_memory映射)。

每個重載都會傳回一個IBurstContext物件(代表突發物件),該物件包含並管理其自己的專用偵聽器執行緒。該執行緒接收來自requestChannel FMQ 的請求,進行推理,然後透過resultChannel FMQ 傳回結果。當突發的客戶端失去對IBurstContext的引用時,該線程和IBurstContext物件中包含的所有其他資源將自動釋放。

或者,您可以建立自己的IBurstContext實現,它可以了解如何透過傳遞給IPreparedModel::configureExecutionBurstrequestChannelresultChannel FMQ 發送和接收訊息。

突發實用程式函數可在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();
}