Mise en cache de la compilation

À partir d'Android 10, l'API Neural Networks (NNAPI) fournit des fonctions pour prendre en charge mise en cache des artefacts de compilation, ce qui réduit le temps consacré à la compilation au démarrage d'une application. À l'aide de cette fonctionnalité de mise en cache, pour gérer ou nettoyer les fichiers mis en cache. Cette fonctionnalité facultative peut être implémenté avec NN HAL 1.2. Pour en savoir plus sur cette fonction, voir ANeuralNetworksCompilation_setCaching

Le pilote peut également implémenter la mise en cache de la compilation indépendamment de la NNAPI. Ce peuvent être implémentés que les fonctionnalités de mise en cache NNAPI NDK et HAL soient utilisées ou non. AOSP fournit une bibliothèque d'utilitaires de bas niveau (un moteur de mise en cache). Pour plus plus d'informations, consultez l'article Implémenter un moteur de mise en cache.

Présentation du workflow

Cette section décrit les workflows généraux avec la fonctionnalité de mise en cache de la compilation mise en œuvre.

Informations sur le cache fournies et succès de cache

  1. L'application transmet un répertoire de mise en cache et une somme de contrôle propre au modèle.
  2. L'environnement d'exécution NNAPI recherche les fichiers de cache en fonction de la somme de contrôle, la préférence d'exécution et le résultat du partitionnement, puis trouve les fichiers.
  3. NNAPI ouvre les fichiers de cache et transmet les identifiants au pilote. par prepareModelFromCache
  4. Le pilote prépare le modèle directement à partir des fichiers de cache et renvoie le modèle préparé.

Informations de cache fournies et défaut de cache

  1. L'application transmet une somme de contrôle propre au modèle et une mise en cache .
  2. L'environnement d'exécution NNAPI recherche les fichiers de mise en cache en fonction de la somme de contrôle, la préférence d'exécution et le résultat du partitionnement, et qu'il ne trouve pas des fichiers de cache.
  3. Le réseau NNAPI crée des fichiers de cache vides en se basant sur la somme de contrôle, l'exécution puis le partitionnement, ouvre les fichiers de cache et transmet et le modèle au pilote prepareModel_1_2
  4. Le pilote compile le modèle et écrit les informations de mise en cache dans le cache. fichiers, puis renvoie le modèle préparé.

Informations sur le cache non fournies

  1. L'application appelle la compilation sans fournir d'informations de mise en cache.
  2. L'application ne transmet rien concernant la mise en cache.
  3. L'environnement d'exécution NNAPI transmet le modèle au pilote prepareModel_1_2
  4. Le pilote compile le modèle et renvoie le modèle préparé.

Informations sur le cache

Les informations de mise en cache fournies à un pilote se composent d'un jeton et de fichiers de cache.

Jeton

La jeton est un jeton de mise en cache de longueur Constant::BYTE_SIZE_OF_CACHE_TOKEN qui identifie le modèle préparé. Le même jeton est fourni lors de l'enregistrement les fichiers de cache avec prepareModel_1_2 et en récupérant le modèle préparé avec prepareModelFromCache Le client du pilote doit choisir un jeton avec un un faible taux de collision. Le conducteur ne peut pas détecter la collision de jetons. Collision entraîne l'échec de l'exécution ou une exécution réussie qui génère des valeurs de sortie incorrectes.

Poignées de fichiers de cache (deux types de fichiers de cache)

Les deux types de fichiers de cache sont le cache de données et le cache du modèle.

  • Cache de données:permet de mettre en cache des données constantes, y compris les données des tampons de Tensor transformés. Une modification du cache de données ne doit pas entraîne un effet plus grave que de générer de mauvaises valeurs de sortie lors de l'exécution. en temps réel.
  • Cache du modèle:permet de mettre en cache des données sensibles comme les données compilées code machine exécutable au format binaire natif de l'appareil. A toute modification apportée au cache du modèle peut affecter l'exécution du pilote et un client malveillant pourrait l'utiliser pour s'exécuter au-delà l'autorisation accordée. Le pilote doit donc vérifier si le cache du modèle est corrompue avant de préparer le modèle à partir du cache. Pour plus d'informations, consultez la section Sécurité.

Le pilote doit décider de la façon dont les informations de cache sont réparties entre les deux types de fichiers de cache et indiquent le nombre de fichiers de cache dont il a besoin pour chaque type par getNumberOfCacheFilesNeeded

L'environnement d'exécution NNAPI ouvre toujours les poignées de fichiers de cache en lecture et en écriture l'autorisation.

Sécurité

Lors de la mise en cache de la compilation, le cache du modèle peut contenir des données sensibles liées à la sécurité telles que en tant que code machine exécutable compilé dans le format binaire natif de l'appareil. Si ce n'est pas le cas correctement protégées, toute modification apportée au cache du modèle peut affecter le comportement le comportement d'exécution. Comme le contenu du cache est stocké dans l'application, les fichiers de cache peuvent être modifiés par le client. Un client présentant des bugs peut corrompre accidentellement le cache, et un client malveillant pourrait intentionnellement l'utiliser pour exécuter du code non vérifié sur l'appareil. En fonction du de l'appareil, il peut s'agir d'un problème de sécurité. Ainsi, le conducteur doit pouvoir détecter une corruption potentielle du cache du modèle avant de le préparer à partir du cache.

Pour ce faire, le conducteur peut gérer une carte du jeton le hachage cryptographique du cache du modèle. Le pilote peut stocker le jeton le hachage du cache de son modèle lors de l'enregistrement de la compilation dans le cache. Le conducteur vérifie le nouveau hachage du cache du modèle avec le jeton et la paire de hachage enregistrés récupérer la compilation à partir du cache. Ce mappage doit être persistant le système redémarre. Le conducteur peut utiliser Service Keystore Android, la bibliothèque d'utilitaires disponible dans framework/ml/nn/driver/cache ou tout autre mécanisme approprié pour implémenter un gestionnaire de mappage. Au conducteur mise à jour, vous devez réinitialiser ce gestionnaire de mappage afin d'éviter la préparation du cache fichiers d'une version antérieure.

Pour éviter de l'heure d'arrivée à l'heure d'utilisation (TOCTOU), le pilote doit calculer le hachage enregistré avant de l'enregistrer et calculer le nouveau hachage après avoir copié le contenu du fichier tampon.

Cet exemple de code montre comment implémenter cette logique.

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

Cas d'utilisation avancés

Dans certains cas d'utilisation avancée, un pilote a besoin d'accéder au contenu du cache (lecture ou écriture) après l'appel de compilation. Voici quelques exemples de cas d'utilisation:

  • Compilation juste à temps:la compilation est retardée jusqu'à la première exécution.
  • Compilation à plusieurs étapes:une compilation rapide est effectuée initialement et une compilation optimisée facultative est effectuée ultérieurement selon la fréquence d'utilisation.

Pour accéder au contenu du cache (en lecture ou en écriture) après l'appel de compilation, assurez-vous que le pilote:

  • Il duplique les descripteurs de fichier lors de l'appel de prepareModel_1_2 ou prepareModelFromCache, et lit/met à jour le cache le contenu par la suite.
  • Implémente une logique de verrouillage de fichier en dehors de l'appel de compilation ordinaire pour éviter qu'une écriture ne se produise en même temps qu'une lecture ou une autre écriture.

Implémenter un moteur de mise en cache

En plus de l'interface de mise en cache de la compilation NN HAL 1.2, vous disposez d'un bibliothèque d'utilitaires de mise en cache frameworks/ml/nn/driver/cache . La nnCache contenant le code de stockage persistant que le pilote doit implémenter de la compilation sans utiliser les fonctionnalités de mise en cache NNAPI. Cette forme de la mise en cache de la compilation peut être implémentée avec n'importe quelle version du NN HAL. Si le le pilote choisit d’implémenter la mise en cache déconnecté de l’interface HAL, le conducteur est est responsable de la libération des artefacts mis en cache lorsqu’ils ne sont plus nécessaires.