Buforowanie kompilacji

Od Androida 10 interfejs Neural Networks API (NNAPI) udostępnia funkcje obsługujące buforowanie artefaktów kompilacji, co skraca czas potrzebny na kompilację podczas uruchamiania aplikacji. Dzięki tej funkcji buforowania sterownik nie zarządzać plikami w pamięci podręcznej lub je usunąć. Jest to opcjonalna funkcja, która można wdrożyć za pomocą NN HAL 1.2. Aby dowiedzieć się więcej o tej funkcji, zobacz ANeuralNetworksCompilation_setCaching

Sterownik może też wdrożyć buforowanie kompilacji niezależnie od NNAPI. Można to zaimplementować niezależnie od tego, czy korzystasz z funkcji buforowania NNAPI NDK i HAL. AOSP udostępnia bibliotekę narzędzi niskiego poziomu (mechanizm 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.

Podano informacje o pamięci podręcznej i trafienie w pamięci podręcznej

  1. Aplikacja przekazuje katalog pamięci podręcznej i specyficzną dla modelu sumę kontrolną.
  2. Środowisko wykonawcze NNAPI wyszukuje pliki pamięci podręcznej na podstawie sumy kontrolnej, preferencji wykonania, wyniku partycjonowania i znalezienia plików.
  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.

Podane informacje o pamięci podręcznej i brak dostępu do pamięci podręcznej

  1. Aplikacja przekazuje sumę kontrolną unikalną dla modelu i pamięć podręczną katalogu.
  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, i partycjonowanie, otwiera pliki pamięci podręcznej i przekazuje uchwytów, a następnie model prepareModel_1_2
  4. Sterownik kompiluje model i zapisuje informacje o buforowaniu w pamięci podręcznej i zwraca gotowy 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 niczego związanego z buforowaniem.
  3. Środowisko wykonawcze NNAPI przekazuje model do sterownika z 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 buforowania o długości Constant::BYTE_SIZE_OF_CACHE_TOKEN który pozwala zidentyfikować gotowy 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 ze znakiem niski współczynnik zderzeń. 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)

Dwa typy plików pamięci podręcznej to data podręczna i pamięć podręczna modelu.

  • Pamięć podręczna danych: służy do buforowania stałych danych, w tym przetworzonych i przekształconych buforów tensorów. Modyfikacja pamięci podręcznej danych nie powinna powodować żadnych efektów gorszych niż generowanie nieprawidłowych wartości wyjściowych podczas wykonywania.
  • Pamięć podręczna modelu: służy do buforowania danych wrażliwych dotyczących bezpieczeństwa, takich jak skompilowane wykonywalny kod maszynowy w natywnym formacie binarnym urządzenia. O modyfikacja pamięci podręcznej modelu może wpłynąć na wykonanie sterownika i szkodliwy klient może go wykorzystać do wykonania dodatkowych czynności przyznane uprawnienie. Dlatego sterownik musi sprawdzić, czy model pamięci podręcznej modelu jest uszkodzony przed przygotowaniem modelu z pamięci podręcznej. Więcej informacji znajdziesz w artykule 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, jest prawidłowo zabezpieczona, modyfikacja pamięci podręcznej modelu może wpłynąć działania powodującego konwersję. Treść pamięci podręcznej jest przechowywana w katalogu aplikacji, więc pliki pamięci podręcznej mogą być modyfikowane przez klienta. Wadliwy klient może może przypadkowo spowodować uszkodzenie pamięci podręcznej, w wyniku czego szkodliwy klient nie może być używany do uruchomienia niezweryfikowanego kodu na urządzeniu. W zależności od od konkretnych cech urządzenia, może to stanowić problem z bezpieczeństwem. Dlatego przed przygotowaniem modelu z pamięci podręcznej sterownik musi być w stanie wykryć potencjalne uszkodzenie pamięci podręcznej modelu.

Jednym ze sposobów jest utrzymywanie przez kierowcę mapy od tokena do szyfrowanego hasza 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ługa magazynu kluczy Androida – biblioteka narzędziowa framework/ml/nn/driver/cache, lub inny odpowiedni mechanizm wdrażania menedżera map. 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 zapobiegać od godziny kontroli do godziny użycia ataki (TOCTOU), kierowca musi obliczyć zahaszowany hasz przed zapisaniem na i obliczyć nowy hasz po skopiowaniu zawartości pliku do wewnętrznego bufora.

Przykładowy kod pokazuje, jak zaimplementować 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 sterownik wymaga dostępu do zawartości pamięci podręcznej (do odczytu lub zapisu) po wywołaniu kompilacji. Przykładowe zastosowania:

  • Kompilacja „just-in-time”: kompilacja jest opóźniona do pierwszego uruchomienia.
  • 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:

  • Powiela uchwyty plików podczas wywoływania funkcji prepareModel_1_2 lub prepareModelFromCache i odczytuje/aktualizuje pamięć podręczną aby móc je udostępnić 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 do buforowania kompilacji NN HAL 1.2 znajdziesz w katalogu frameworks/ml/nn/driver/cache bibliotekę narzędzi do buforowania. 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 decyduje się na wdrożenie buforowania odłączonego od interfejsu HAL, kierowca jest odpowiada za zwolnienie artefaktów zapisanych w pamięci podręcznej, gdy nie są już potrzebne.