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

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

  1. 爆發物件會在執行序列之前建立,並在序列結束時釋放。因此,爆發物件的生命週期會向驅動程式提示應維持高效能狀態的時間長度。
  2. 爆發物件可在執行作業之間保留資源。舉例來說,驅動程式可以在第一次執行時對應記憶體物件,並將對應項目快取在爆發物件中,以便在後續執行時重複使用。當爆發物件遭到毀損,或 NNAPI 執行階段通知爆發物件不再需要資源時,即可釋出任何快取資源。
  3. 爆發物件會使用快速訊息佇列 (FMQ),在應用程式和驅動程式程序之間進行通訊。這可以減少延遲,因為 FMQ 會略過 HIDL,並透過共用記憶體中的不可分割循環 FIFO,將資料直接傳遞至另一個程序。消費者程序會知道要將項目從佇列中移除,並開始處理,方法是輪詢 FIFO 中的元素數量,或等待 FMQ 的事件標記 (由生產者發出信號)。這個事件旗標是快速使用者空間互斥鎖 (futex)。

FMQ 是低階資料結構,無法保證跨程序的使用期限,也沒有內建機制可判斷 FMQ 另一端的程序是否正常運作。因此,如果 FMQ 的生產者停止運作,消費者可能會卡住,等待永遠不會送達的資料。如要解決這個問題,其中一種方法是讓驅動程式將 FMQ 與較高層級的爆發物件建立關聯,以便偵測爆發執行作業何時結束。

由於爆發執行作業會使用相同的引數,並傳回與其他執行路徑相同的結果,因此基礎 FMQ 必須將相同的資料傳遞至 NNAPI 服務驅動程式,以及從 NNAPI 服務驅動程式傳遞資料。不過,FMQ 只能轉移舊式資料類型。如要轉移複雜資料,請直接在 FMQ 中序列化及還原序列化巢狀緩衝區 (向量型別),並使用 HIDL 回呼物件,視需要轉移記憶體集區控制代碼。如果佇列會封鎖,FMQ 的生產者端必須使用 MessageQueue::writeBlocking,以原子方式將要求或結果訊息傳送至消費者端;如果佇列不會封鎖,則必須使用 MessageQueue::write

Burst 介面

Neural Networks 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 介面物件。

IBurstCallback.hal

IBurstCallback.hal 定義由神經網路執行階段建立的回呼的 HIDL 介面物件,神經網路服務會使用這個物件,擷取與時段 ID 相對應的 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 收發訊息。

如要使用 Burst 公用程式函式,請前往 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);

以下是 Neural Networks 範例驅動程式 (位於 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();
}