爆發執行作業與快速訊息佇列

類神經網路 HAL 1.2 介紹爆發執行的概念。爆發執行作業是針對同一準備好的模型,快速連續進行的一系列執行作業,例如執行相機所拍攝的影格或連續音訊取樣。爆發物件可用於控制一組爆發執行作業,並在執行作業之間保留資源,讓執行作業的額外負擔降低。Burst 物件可啟用三種最佳化功能:

  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

types.hal

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

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

IBurstContext.hal

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

  • IBurstContext:用於管理連拍相片資源的內容物件。

IBurstCallback.hal

IBurstCallback.hal 為類神經網路建立的回呼定義 HIDL 介面物件 執行階段,並由類神經網路服務用來擷取 hidl_memory 對應至運算單元 ID 的物件

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

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