Buforowanie kompilacji

Począwszy od Androida 10: Neural Networks API (NNAPI), zapewnia funkcje, które obsługują buforowanie artefaktów kompilacji, co skraca czas potrzebny na kompilację po uruchomieniu aplikacji. Dzięki tej funkcji buforowania kierowca nie musi zarządzać plikami w pamięci podręcznej ani ich czyścić. To opcjonalna funkcja, którą można zaimplementować za pomocą NN HAL 1.2. Więcej informacji o tej funkcji znajdziesz w artykule ANeuralNetworksCompilation_setCaching.

Sterownik może też stosować buforowanie kompilacji niezależnie od NNAPI. Ten można wdrożyć niezależnie od tego, czy są używane funkcje buforowania NNAPI NDK i HAL Nie. AOSP udostępnia bibliotekę narzędzi niskiego poziomu (silnik do buforowania). Więcej informacji znajdziesz w artykule Wdrażanie mechanizmu buforowania.

Omówienie przepływu pracy

W tej sekcji opisano ogólne procesy z wdrożonym oczyszczaniem pamięci podręcznej kompilacji.

Podane informacje o pamięci podręcznej i trafienie w pamięć podręczną

  1. Aplikacja przekazuje katalog pamięci podręcznej i specyficzną dla modelu sumę kontrolną.
  2. Środowisko uruchomieniowe NNAPI wyszukuje pliki pamięci podręcznej na podstawie sumy kontrolnej, preferencji wykonania i wyników podziału.
  3. NNAPI otwiera pliki pamięci podręcznej i przekazuje uchwyty do sterownika za pomocą prepareModelFromCache.
  4. Sterownik przygotowuje model bezpośrednio z plików pamięci podręcznej i zwraca przygotowany model.

Podano informacje dotyczące pamięci podręcznej i jej brak

  1. Aplikacja przekazuje unikalną dla modelu sumę kontrolną i katalog do buforowania.
  2. Środowisko wykonawcze NNAPI wyszukuje pliki w pamięci podręcznej na podstawie sumy kontrolnej, preferencji wykonania oraz wyniku partycjonowania i nie znajduje plików pamięci podręcznej.
  3. NNAPI tworzy puste pliki pamięci podręcznej na podstawie sumy kontrolnej, preferencji wykonania i partycjonowania, otwiera pliki pamięci podręcznej i przekazuje uchwyty oraz model do sterownika za pomocą prepareModel_1_2.
  4. Kompiluje on model, zapisuje informacje o buforowaniu w plikach pamięci podręcznej i zwraca przygotowany model.

Nie podano informacji o pamięci podręcznej

  1. Aplikacja wywołuje kompilację bez podawania żadnych informacji o pamięci podręcznej.
  2. Aplikacja nie przekazuje żadnych danych związanych z buforowaniem.
  3. Środowisko uruchomieniowe NNAPI przekazuje model do sterownika za pomocą prepareModel_1_2.
  4. Sterownik skompiluje model i zwróci gotowy model.

Informacje o pamięci podręcznej

Informacje dotyczące pamięci podręcznej, które są udostępniane kierowcy, obejmują token i uchwyty plików pamięci podręcznej.

Token

token to token do pamięci podręcznej o długości Constant::BYTE_SIZE_OF_CACHE_TOKEN, który identyfikuje przygotowany model. Ten sam token jest podawany podczas zapisywania parametru buforuj pliki za pomocą funkcji prepareModel_1_2 i pobieraj przygotowany model za pomocą prepareModelFromCache Klient kierowcy powinien wybrać token z niską częstotliwością kolizji. Kierowca nie może wykryć kolizji tokenów. Kolizja powoduje nieudane wykonanie lub udane wykonanie, które zwraca nieprawidłowe wartości wyjściowe.

Uchwyty plików pamięci podręcznej (dwa typy plików pamięci podręcznej)

Istnieją 2 typy plików pamięci podręcznej: pamięć podręczna danychpamięć podręczna modelu.

  • Pamięć podręczna na dane: służy do buforowania stałych danych, w tym przekształconych buforów tensorów. Modyfikacja pamięci podręcznej danych nie powinna może sprawić, że efekt będzie gorszy niż generowanie nieprawidłowych wartości wyjściowych podczas wykonywania kodu obecnie się znajdujesz.
  • Pamięć podręczna modelu: służy do buforowania danych wrażliwych, takich jak skompilowany wykonywalny kod maszynowy w rodzonym formacie binarnym urządzenia. Zmiana pamięci podręcznej modelu może wpłynąć na zachowanie sterownika, a złośliwy klient może wykorzystać to do wykonania czegoś poza udzielonym uprawnieniem. Dlatego przed przygotowaniem modelu z pamięci podręcznej kierowca musi sprawdzić, czy nie jest on uszkodzony. Aby dowiedzieć się więcej, Więcej informacji: Bezpieczeństwo.

Sterownik musi zdecydować, w jaki sposób informacje z pamięci podręcznej są rozpowszechniane między nimi typów plików pamięci podręcznej i raportować, ile plików pamięci podręcznej jest potrzebnych do poszczególnych typów. z getNumberOfCacheFilesNeeded

Środowisko wykonawcze NNAPI zawsze otwiera uchwyty plików pamięci podręcznej z obsługą odczytu i zapisu uprawnienia.

Bezpieczeństwo

W pamięci podręcznej kompilacji pamięć podręczna modelu może zawierać dane poufne, takie jak jako skompilowany wykonywalny kod maszynowy w natywnym formacie binarnym urządzenia. Jeśli nie są odpowiednio chronione, modyfikacja pamięci podręcznej modelu może wpłynąć na sposób działania sterownika. Treść pamięci podręcznej jest przechowywana w katalogu aplikacji, więc pliki pamięci podręcznej mogą być modyfikowane przez klienta. Klient z błędem może przypadkowo uszkodzić pamięć podręczną, a złośliwy klient może celowo wykorzystać tę możliwość, aby wykonać na urządzeniu niesprawdzony kod. W zależności od cech urządzenia może to stanowić problem z bezpieczeństwem. Dlatego kierowca musi mieć możliwość wykrycia potencjalne uszkodzenie pamięci podręcznej modelu przed przygotowaniem modelu z tej pamięci.

Jednym ze sposobów może być zachowanie przez kierowcę mapy z tokena kryptograficzny skrót pamięci podręcznej modelu. Kierowca może zapisać token oraz hasz jej pamięci podręcznej modelu podczas zapisywania kompilacji w tej pamięci. Kierowca sprawdza, nowy hasz pamięci podręcznej modelu z zarejestrowaną parą tokena i skrótu, na pobranie kompilacji z pamięci podręcznej. Mapowanie powinno być trwałe po ponownym uruchomieniu systemu. Kierowca może użyć usługi magazynu kluczy Androida, biblioteki narzędzi framework/ml/nn/driver/cache lub innego odpowiedniego mechanizmu do implementowania menedżera mapowania. Po kierowcy aktualizacji, należy ponownie zainicjować ten menedżer mapowania, aby zapobiec przygotowywaniu pamięci podręcznej plików z wcześniejszej wersji.

Aby zapobiec atakom czas-sprawdzenia-czas-użytkowania (TOCTOU), sterownik musi obliczyć zapisany ciąg znaków zanim zapisze plik, a po skopiowaniu zawartości pliku do wewnętrznego bufora musi obliczyć nowy ciąg znaków.

Ten przykładowy kod pokazuje, jak wdrożyć tę logikę.

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

Zaawansowane przypadki użycia

W niektórych zaawansowanych przypadkach użycia sterownik wymaga dostępu do zawartości pamięci podręcznej (odczyt lub zapis) po wywołaniu kompilacji. Przykładowe przypadki użycia:

  • Kompilacja w czasie wykonywania: kompilacja jest opóźniona do momentu pierwszego wykonania.
  • Kompilacja wieloetapowa: najpierw wykonywana jest szybka kompilacja, a później (w zależności od częstotliwości użycia) opcjonalna kompilacja zoptymalizowana.

Aby uzyskać dostęp do zawartości pamięci podręcznej (do odczytu lub zapisu) po wywołaniu kompilacji, upewnij się, że sterownik:

  • Duplikaty uchwytów plików podczas wywołania funkcji prepareModel_1_2 lub prepareModelFromCache oraz odczytuje lub zaktualizuje zawartość pamięci podręcznej w późniejszym czasie.
  • Wprowadza logikę blokowania plików poza zwykłym wywołaniem kompilacji, aby zapobiec zapisowi odbywającemu się równolegle z odczytem lub innym zapisem.

Wdróż mechanizm buforowania

Oprócz interfejsu buforowania kompilacji NN HAL 1.2 możesz też znaleźć biblioteka narzędzi do buforowania frameworks/ml/nn/driver/cache katalogu. nnCache podkatalog zawiera kod trwałej pamięci masowej, który ma zostać wdrożony przez sterownika buforowanie kompilacji bez korzystania z funkcji buforowania NNAPI. Ta forma Buforowanie kompilacji można wdrożyć w dowolnej wersji NN HAL. Jeśli sterownik zdecyduje się zaimplementować buforowanie bez połączenia z interfejsem HAL, będzie odpowiedzialny za zwalnianie elementów z bufora, gdy nie będą już potrzebne.