突發執行和快速消息隊列

神經網絡 HAL 1.2 引入了突發執行的概念。突發執行是同一準備好的模型的一系列執行,這些執行快速連續發生,例如在相機捕獲的幀或連續音頻樣本上操作的那些。突發對像用於控制一組突發執行,並在執行之間保留資源,從而使執行具有更低的開銷。 Burst 對象支持三種優化:

  1. Burst 對像在一系列執行之前創建,並在序列結束時釋放。因此,突發對象的生命週期會向驅動程序暗示它應該保持高性能狀態多長時間。
  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對象。

IPreparedModel.hal

IPreparedModel.hal在 HAL 1.2 中進行了擴展,提供了一種從準備好的模型創建IBurstContext對象的方法。

  • configureExecutionBurst :配置一個突發對象,用於在準備好的模型上快速連續地執行多個推理。

支持驅動程序中的突發執行

在 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();
}