編譯快取

自 Android 10 起,Neural Networks API (NNAPI) 提供多項功能,支援快取構件快取功能,可減少應用程式啟動時用於編譯的時間。使用這個快取功能,驅動程式不需要管理或清除快取檔案。這是可透過 NN HAL 1.2 實作的選用功能。如要進一步瞭解這個函式,請參閱 ANeuralNetworksCompilation_setCaching

驅動程式也可以獨立於 NNAPI 之外實作編譯快取。無論是否使用 NNAPI NDK 和 HAL 快取功能,都可以實作這項功能。Android 開放原始碼計畫提供低階公用程式庫 (快取引擎)。詳情請參閱「實作快取引擎」一文。

工作流程總覽

本節說明已實作編譯快取功能的一般工作流程。

提供的快取資訊和快取命中

  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 介面中斷連線的快取,驅動程式將負責釋出不再需要的快取成果。