進行我們的可用性調查以改進此站點。
本頁面由 Cloud Translation API 翻譯而成。
Switch to English

編譯緩存

從Android 10開始,神經網絡API(NNAPI)提供了支持緩存編譯工件的功能,從而減少了應用啟動時用於編譯的時間。使用此緩存功能,驅動程序不需要管理或清理緩存的文件。這是可以使用NN HAL 1.2實施的可選功能。有關此功能的更多信息,請參見ANeuralNetworksCompilation_setCaching

驅動程序還可以獨立於NNAPI來實現編譯緩存。無論是否使用NNAPI NDK和HAL緩存功能,都可以實現。 AOSP提供了一個低級實用程序庫(緩存引擎)。有關更多信息,請參見實施緩存引擎

工作流程概述

本節描述了實現了編譯緩存功能的常規工作流程。

提供的緩存信息和緩存命中

  1. 該應用程序傳遞該模型唯一的緩存目錄和校驗和。
  2. NNAPI運行時根據校驗和,執行首選項和分區結果查找緩存文件,然後找到文件。
  3. NNAPI將打開緩存文件,並使用prepareModelFromCache將句柄傳遞給驅動程序。
  4. 驅動程序直接從緩存文件準備模型並返回準備好的模型。

提供的緩存信息和緩存未命中

  1. 該應用程序傳遞模型唯一的校驗和和緩存目錄。
  2. NNAPI運行時根據校驗和,執行首選項和分區結果查找緩存文件,但未找到緩存文件。
  3. NNAPI根據校驗和,執行首選項和分區創建空的緩存文件,打開緩存文件,然後使用prepareModel_1_2將句柄和模型傳遞給驅動程序。
  4. 驅動程序編譯模型,將緩存信息寫入緩存文件,然後返回準備好的模型。

未提供緩存信息

  1. 該應用程序在不提供任何緩存信息的情況下調用編譯。
  2. 該應用程序沒有傳遞任何與緩存相關的信息。
  3. 該NNAPI運行時通過該模型與司機prepareModel_1_2
  4. 驅動程序編譯模型並返回準備好的模型。

緩存信息

提供給驅動程序的緩存信息由令牌和緩存文件句柄組成。

代幣

令牌是一個長度為Constant::BYTE_SIZE_OF_CACHE_TOKEN的緩存令牌,用於標識準備好的模型。節能與緩存文件時,提供同樣的道理prepareModel_1_2和檢索與準備好的模型prepareModelFromCache 。駕駛員的客戶應選擇碰撞率低的令牌。驅動程序無法檢測到令牌衝突。衝突會導致執行失敗或成功執行,從而產生錯誤的輸出值。

緩存文件句柄(兩種類型的緩存文件)

兩種類型的緩存文件是數據緩存模型緩存

  • 數據緩存:用於緩存常量數據,包括預處理和轉換後的張量緩衝區。對數據緩存的修改不會比在執行時生成錯誤的輸出值更糟。
  • 模型緩存:用於緩存對安全敏感的數據,例如以設備的本機二進制格式編譯的可執行機器代碼。對模型緩存的修改可能會影響驅動程序的執行行為,並且惡意客戶端可能會利用此行為執行超出所授予權限的行為。因此,驅動程序必須在從高速緩存準備模型之前檢查模型高速緩存是否損壞。有關更多信息,請參閱安全性

驅動程序必須確定如何在兩種類型的緩存文件之間分配緩存信息,並使用getNumberOfCacheFilesNeeded報告每種類型需要多少緩存文件。

NNAPI運行時始終打開具有讀取和寫入權限的緩存文件句柄。

安全

在編譯緩存中,模型緩存可能包含對安全敏感的數據,例如以設備的本機二進制格式編譯的可執行機器代碼。如果未得到適當的保護,則對模型緩存的修改可能會影響驅動程序的執行行為。由於緩存內容存儲在應用程序目錄中,因此客戶端可以修改緩存文件。有漏洞的客戶端可能會意外破壞緩存,而惡意客戶端可能有意利用此漏洞在設備上執行未經驗證的代碼。根據設備的特性,這可能是安全問題。因此,驅動程序必須能夠在從高速緩存準備模型之前檢測到潛在的模型高速緩存損壞。

一種方法是驅動程序維護從令牌到模型緩存的加密哈希的映射。將編譯保存到高速緩存時,驅動程序可以存儲令牌和其模型高速緩存的哈希。從緩存檢索編譯時,驅動程序將使用記錄的令牌和哈希對檢查模型緩存的新哈希。此映射應在系統重新引導後保持不變。驅動程序可以使用Android keystore服務framework/ml/nn/driver/cache的實用程序庫或任何其他合適的機制來實現映射管理器。更新驅動程序後,應重新初始化此映射管理器,以防止從較早版本準備緩存文件。

為了防止檢查時間到使用時間(TOCTOU)攻擊,驅動程序必須在保存到文件之前計算記錄的哈希,並在將文件內容複製到內部緩衝區之後計算新的哈希。

此示例代碼演示瞭如何實現此邏輯。

bool saveToCache(const sp<V1_2::IPreparedModel> preparedModel,
                 const hidl_vec<hidl_handle>& modelFds, const hidl_vec<hidl_handle>& dataFds,
                 const HidlToken& token) {
    // Serialize the prepared model to internal buffers.
    auto buffers = serialize(preparedModel);

    // This implementation detail is important: the cache hash must be computed from internal
    // buffers instead of cache files to prevent time-of-check to time-of-use (TOCTOU) attacks.
    auto hash = computeHash(buffers);

    // Store the {token, hash} pair to a mapping manager that is persistent across reboots.
    CacheManager::get()->store(token, hash);

    // Write the cache contents from internal buffers to cache files.
    return writeToFds(buffers, modelFds, dataFds);
}

sp<V1_2::IPreparedModel> prepareFromCache(const hidl_vec<hidl_handle>& modelFds,
                                          const hidl_vec<hidl_handle>& dataFds,
                                          const HidlToken& token) {
    // Copy the cache contents from cache files to internal buffers.
    auto buffers = readFromFds(modelFds, dataFds);

    // This implementation detail is important: the cache hash must be computed from internal
    // buffers instead of cache files to prevent time-of-check to time-of-use (TOCTOU) attacks.
    auto hash = computeHash(buffers);

    // Validate the {token, hash} pair by a mapping manager that is persistent across reboots.
    if (CacheManager::get()->validate(token, hash)) {
        // Retrieve the prepared model from internal buffers.
        return deserialize<V1_2::IPreparedModel>(buffers);
    } else {
        return nullptr;
    }
}

高級用例

在某些高級用例中,驅動程序要求在編譯調用之後訪問緩存內容(讀或寫)。用例示例包括:

  • 即時編譯:編譯延遲到第一次執行。
  • 多階段編譯:根據使用頻率,首先執行快速編譯,然後在以後執行可選的優化編譯。

要在編譯調用後訪問緩存內容(讀或寫),請確保驅動程序:

  • 在調用prepareModel_1_2prepareModelFromCache期間複製文件句柄,並在以後讀取/更新緩存內容。
  • 在普通編譯調用之外實現文件鎖定邏輯,以防止與讀或其他寫操作同時發生寫操作。

實現緩存引擎

除了NN HAL 1.2編譯緩存接口之外,您還可以在frameworks/ml/nn/driver/cache目錄中找到一個緩存實用程序庫。 nnCache子目錄包含用於驅動程序的持久性存儲代碼,以在不使用NNAPI緩存功能的情況下實現編譯緩存。可以使用任何版本的NN HAL來實現這種形式的編譯緩存。如果驅動程序選擇實現與HAL接口斷開連接的緩存,則驅動程序負責在不再需要緩存的工件時釋放它們。