Od Androida 10 interfejs Neural Networks API (NNAPI) udostępnia funkcje do obsługi buforowania artefaktów kompilacji, co skraca czas potrzebny na kompilację podczas uruchamiania aplikacji. Dzięki tej funkcji buforowania kierowca nie musi zarządzać plikami w buforze 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. 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 opisaliśmy ogólne procesy z wdrożonym oczyszczaniem pamięci podręcznej kompilacji.
Podane informacje o pamięci podręcznej i trafienie do pamięci podręcznej
- Aplikacja przekazuje katalog pamięci podręcznej i niepowtarzalną dla danego modelu sumę kontrolną.
- Środowisko uruchomieniowe NNAPI wyszukuje pliki pamięci podręcznej na podstawie sumy kontrolnej, preferencji wykonania i wyników podziału.
- NNAPI otwiera pliki pamięci podręcznej i przekazuje uchwyty do sterownika za pomocą
prepareModelFromCache
. - 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
- Aplikacja przekazuje unikalną dla modelu sumę kontrolną i katalog pamięci podręcznej.
- Środowisko uruchomieniowe NNAPI wyszukuje pliki pamięci podręcznej na podstawie sumy kontrolnej, preferencji wykonania i wyników podziału, ale nie znajduje plików pamięci podręcznej.
- 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
. - Sterownik kompiluje model, zapisuje informacje o pamięci podręcznej w plikach pamięci podręcznej i zwraca przygotowany model.
Brak informacji o pamięci podręcznej
- Aplikacja wywołuje kompilację bez podawania żadnych informacji o pamięci podręcznej.
- Aplikacja nie przekazuje niczego związanego z buforowaniem.
- Środowisko uruchomieniowe NNAPI przekazuje model do sterownika za pomocą
prepareModel_1_2
. - Sterownik kompiluje model i zwraca przygotowany 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 używany podczas zapisywania plików pamięci podręcznej za pomocą funkcji prepareModel_1_2
i pobierania przygotowanego modelu za pomocą funkcji 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 (2 typy plików pamięci podręcznej)
Istnieją 2 typy plików pamięci podręcznej: pamięć podręczna danych 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, 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. Więcej informacji znajdziesz w artykule Bezpieczeństwo.
Sterownik musi określić, jak informacje o pamięci podręcznej są rozprowadzane między 2 typami plików pamięci podręcznej, oraz podać, ile plików pamięci podręcznej jest potrzebnych dla każdego typu za pomocą getNumberOfCacheFilesNeeded
.
Runtime NNAPI zawsze otwiera uchwyty plików pamięci podręcznej z uprawnieniami do odczytu i zapisu.
Bezpieczeństwo
W przypadku kompilacji z wykorzystaniem pamięci podręcznej pamięć podręczna modelu może zawierać dane wrażliwe na zagrożenia bezpieczeństwa, takie jak 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 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 z tokenu na hasz kryptograficzny pamięci podręcznej modelu. Podczas zapisywania kompilacji w pamięci podręcznej sterownik może przechowywać token i sumę kontrolną pamięci podręcznej modelu. Podczas pobierania kompilacji z pamięci podręcznej sterownik sprawdza nowy hash pamięci podręcznej modelu za pomocą zarejestrowanej pary token–hash. Mapowanie powinno być trwałe po ponownym uruchomieniu systemu. Kierowca może użyć usługi klucza Androida, biblioteki narzędzi framework/ml/nn/driver/cache
lub innego odpowiedniego mechanizmu do implementacji menedżera mapowania. Po aktualizacji sterownika należy zresetować menedżera mapowania, aby zapobiec przygotowywaniu plików pamięci podręcznej 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.
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 zastosowania
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 przypadki użycia:
- Kompilacja w czasie wykonywania: kompilacja jest opóźniana 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 optymalizacja.
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
lubprepareModelFromCache
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.
Wdrożenie mechanizmu pamięci podręcznej
Oprócz interfejsu do buforowania kompilacji NN HAL 1.2 możesz też znaleźć bibliotekę narzędzi do buforowania w katalogu frameworks/ml/nn/driver/cache
. Podkatalog nnCache
zawiera kod trwałego miejsca na dane, który umożliwia sterownikowi implementowanie funkcji buforowania kompilacji bez używania funkcji buforowania NNAPI. Tę formę pamięci podręcznej kompilacji można zaimplementować w dowolnej wersji interfejsu 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.