Kompilierungs-Caching

Ab Android 10: Neural Networks API (NNAPI) Funktionen zur Unterstützung der Caching von Kompilierungsartefakten, wodurch die für die Kompilierung benötigte Zeit reduziert wird wenn eine App gestartet wird. Mit dieser Caching-Funktion kann der Treiber die im Cache gespeicherten Dateien verwalten oder bereinigen müssen. Dies ist eine optionale Funktion, die kann mit NN HAL 1.2 implementiert werden. Weitere Informationen zu dieser Funktion Siehe ANeuralNetworksCompilation_setCaching

Der Treiber kann auch das Kompilierungs-Caching unabhängig von der NNAPI implementieren. Dieses kann implementiert werden, unabhängig davon, ob die NNAPI NDK- und HAL-Caching-Funktionen verwendet werden oder nicht. AOSP bietet eine Low-Level-Dienstprogrammbibliothek (eine Caching-Engine). Weitere Informationen finden Sie unter Caching-Engine implementieren.

Workflowübersicht

In diesem Abschnitt werden allgemeine Workflows mit der Funktion für das Kompilierungs-Caching beschrieben. implementiert.

Cache-Informationen und Cache-Treffer

  1. Die Anwendung übergibt ein Caching-Verzeichnis und eine für das Modell eindeutige Prüfsumme.
  2. Die NNAPI-Laufzeit sucht anhand der Prüfsumme nach den Cache-Dateien, der Ausführungspräferenz, Partitionierungsergebnis und findet die Dateien.
  3. Die NNAPI öffnet die Cache-Dateien und übergibt die Handles an den Treiber mit prepareModelFromCache
  4. Der Treiber bereitet das Modell direkt aus den Cache-Dateien vor und gibt das vorbereitete Modell.

Cache-Informationen und Cache-Fehler

  1. Die Anwendung übergibt eine für das Modell eindeutige Prüfsumme sowie ein Caching. -Verzeichnis.
  2. Die NNAPI-Laufzeit sucht anhand der Prüfsumme nach den Caching-Dateien, der Ausführungspräferenz und das Partitionierungsergebnis Cache-Dateien.
  3. Die NNAPI erstellt leere Cache-Dateien basierend auf der Prüfsumme, der Ausführung und die Partitionierung, öffnet die Cache-Dateien und übergibt und das Modell mit den prepareModel_1_2
  4. Der Treiber kompiliert das Modell und schreibt Caching-Informationen in den Cache. und gibt das vorbereitete Modell zurück.

Cache-Informationen nicht angegeben

  1. Die Anwendung ruft die Kompilierung auf, ohne Caching-Informationen bereitzustellen.
  2. Die Anwendung übergibt nichts im Zusammenhang mit Caching.
  3. Die NNAPI-Laufzeit übergibt das Modell mit prepareModel_1_2
  4. Der Treiber kompiliert das Modell und gibt das vorbereitete Modell zurück.

Cache-Informationen

Die Caching-Informationen, die einem Treiber bereitgestellt werden, bestehen aus einem Token und Cache-Datei-Handles.

Token

Die Token ist ein Caching-Token der Länge Constant::BYTE_SIZE_OF_CACHE_TOKEN der das vorbereitete Modell identifiziert. Dasselbe Token wird beim Speichern des Dateien mit prepareModel_1_2 im Cache speichern und das vorbereitete Modell mit prepareModelFromCache. Der Client des Treibers sollte ein Token mit einem eine niedrige Kollisionsrate. Der Treiber kann keine Tokenkollision erkennen. Eine Kollision führt zu einer fehlgeschlagenen Ausführung oder zu einer erfolgreichen Ausführung, die Falsche Ausgabewerte.

Handles für Cache-Dateien (zwei Arten von Cache-Dateien)

Die beiden Arten von Cache-Dateien sind der Daten-Cache und der Modell-Cache.

  • Daten-Cache: Wird zum Caching konstanter Daten verwendet, einschließlich vorverarbeiteter und transformierten Tensor-Zwischenspeichern. Eine Änderung am Daten-Cache sollte zu einer schlechteren Auswirkung führen als die Erzeugung ungültiger Ausgabewerte bei der Ausführung. .
  • Modell-Cache:Wird zum Caching sicherheitsrelevanter Daten verwendet, z. B. kompilierte den ausführbaren Maschinencode im nativen Binärformat des Geräts vor. A Änderungen am Modell-Cache können sich auf die Ausführung des Treibers auswirken. und ein bösartiger Client diese Möglichkeit nutzen, die erteilte Berechtigung hat. Daher muss der Treiber prüfen, ob der Modell-Cache ist beschädigt, bevor das Modell aus dem Cache vorbereitet wird. Weitere Informationen Siehe Sicherheit.

Der Treiber muss entscheiden, wie Cache-Informationen zwischen den beiden verteilt werden sollen und geben an, wie viele Cache-Dateien jeweils benötigt werden. mit getNumberOfCacheFilesNeeded

Die NNAPI-Laufzeit öffnet Cache-Datei-Handles sowohl mit Lese- als auch Schreibvorgängen Berechtigung.

Sicherheit

Beim Kompilierungs-Caching kann der Modell-Cache sicherheitsrelevante Daten wie als kompilierter ausführbarer Maschinencode im nativen Binärformat des Geräts. Falls nicht nicht ordnungsgemäß geschützt ist, kann sich eine Änderung am Modell-Cache auf den Treiber des Ausführungsverhaltens. Weil der Cache-Inhalt in der App gespeichert wird können die Cache-Dateien vom Client geändert werden. Ein fehlerhafter Client kann den Cache versehentlich beschädigen und ein bösartiger Client absichtlich um nicht verifizierten Code auf dem Gerät auszuführen. Je nach Merkmale des Geräts haben, könnte es sich um ein Sicherheitsproblem handeln. Das heißt, die Der Fahrer muss in der Lage sein, potenzielle Beschädigung des Modell-Cache, bevor das Modell aus dem Cache vorbereitet wird.

Eine Möglichkeit, dies zu tun, besteht darin, dass der Fahrer eine Karte vom Token zu einem kryptografischer Hash des Modell-Cache. Der Treiber kann das Token und den Hash des Modell-Cache gespeichert, wenn die Kompilierung im Cache gespeichert wird. Der Fahrer prüft den neuen Hash des Modell-Cache mit dem aufgezeichneten Token und Hash-Paar, wenn das Abrufen der Kompilierung aus dem Cache. Diese Zuordnung sollte für alle das System neu gestartet wird. Der Fahrer kann die Android Keystore Service, die Dienstprogrammbibliothek in framework/ml/nn/driver/cache, oder einen anderen geeigneten Mechanismus zur Implementierung eines Kartenmanagers implementieren. Beim Fahren aktualisieren, muss dieser Zuordnungsmanager neu initialisiert werden, um zu verhindern, dass der Cache vorbereitet wird aus einer früheren Version.

Um dies zu verhindern, von der Prüfung bis zur Nutzungszeit (TOCTOU)-Angriffen nutzen, muss der Fahrer den aufgezeichneten Hashwert berechnen, bevor er unter und berechnen den neuen Hash nach dem Kopieren des Dateiinhalts in ein internes Puffer.

Dieser Beispielcode zeigt, wie diese Logik implementiert wird.

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

Erweiterte Anwendungsfälle

In bestimmten erweiterten Anwendungsfällen benötigt ein Treiber Zugriff auf den Cache-Inhalt (Lesen oder Schreiben) nach dem Kompilierungsaufruf. Beispiele für Anwendungsfälle:

  • Just-in-Time-Kompilierung:Die Kompilierung wird bis zum bei der ersten Ausführung.
  • Kompilierung mit mehreren Phasen:Zu Beginn wird eine schnelle Kompilierung ausgeführt. und zu einem späteren Zeitpunkt wird eine optionale, optimierte Kompilierung durchgeführt. je nach Nutzungshäufigkeit.

Um nach dem Kompilierungsaufruf auf den Cache-Inhalt zugreifen zu können (Lese- oder Schreibzugriff), dass der Fahrer:

  • Dupliziert die Datei-Handles während des Aufrufs von prepareModel_1_2 oder prepareModelFromCache und liest/aktualisiert den Cache zu einem späteren Zeitpunkt wieder aufnehmen.
  • Implementiert Dateisperrlogik außerhalb des normalen Kompilierungsaufrufs um einen Schreibvorgang zu verhindern, der gleichzeitig mit einem Lese- oder einem anderen Schreibvorgang ausgeführt wird.

Caching-Engine implementieren

Neben der NN HAL 1.2-Schnittstelle für das Kompilierungs-Caching finden Sie auch einen Caching-Dienstprogrammbibliothek in der frameworks/ml/nn/driver/cache -Verzeichnis. Die nnCache Das Unterverzeichnis enthält permanenten Speichercode für den zu implementierenden Treiber. Kompilierungs-Caching ohne die NNAPI-Caching-Funktionen. Diese Form von Kompilierungs-Caching kann mit jeder Version von NN HAL implementiert werden. Wenn die um ein von der HAL-Schnittstelle getrenntes Caching zu implementieren, der Fahrer ist dafür verantwortlich, im Cache gespeicherte Artefakte freizugeben, wenn sie nicht mehr benötigt werden.