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

類神經網路 HAL 1.2 介紹爆發執行的概念。爆發執行作業是針對相同預先準備的模型,快速連續執行,例如在相機拍攝影格或連續音訊樣本中執行的一系列執行作業。爆發物件可用來控制一組爆發執行作業,並在執行作業之間保留資源,讓執行作業能減少負載。爆發物件支援三種最佳化功能:

  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 則可透過 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 介面物件。

IBurstCallback.hal

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

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

IPreparedModel.hal

IPreparedModel.hal 已在 HAL 1.2 中擴充,其中包含從預先設定的模型建立 IBurstContext 物件的方法。

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

如要在 HIDL NNAPI 服務中支援爆發物件,最簡單的方法就是使用在 ExecutionBurstServer.h 中,並封裝在 libneuralnetworks_commonlibneuralnetworks_util 靜態資料庫中的爆發公用程式函式 ::android::nn::ExecutionBurstServer::create。這個工廠函式有兩個超載:

  • 其中一個超載接受指向 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();
}