Memorizzazione nella cache delle compilazioni

A partire da Android 10, l'API Neural Networks (NNAPI) offre funzioni per supportare di memorizzazione nella cache degli artefatti di compilazione, che riduce il tempo utilizzato per la compilazione all'avvio di un'app. Utilizzando questa funzionalità di memorizzazione nella cache, gestire o ripulire i file memorizzati nella cache. Si tratta di una funzionalità facoltativa può essere implementata con NN HAL 1.2. Per ulteriori informazioni su questa funzione, vedi ANeuralNetworksCompilation_setCaching

Il driver può anche implementare la memorizzazione nella cache di compilazione indipendentemente dall'NNAPI. Questo può essere implementato indipendentemente dal fatto che vengano utilizzate le funzionalità di memorizzazione nella cache NNAPI NDK e HAL . AOSP offre una libreria di utilità di basso livello (un motore di memorizzazione nella cache). Per ulteriori informazioni consulta Implementazione di un motore di memorizzazione nella cache.

Panoramica del flusso di lavoro

Questa sezione descrive i flussi di lavoro generali con la funzionalità di memorizzazione nella cache di compilazione implementate.

Informazioni cache fornite e successo della cache

  1. L'app passa una directory di memorizzazione nella cache e un checksum univoci per il modello.
  2. Il runtime NNAPI cerca i file della cache in base al checksum, preferenza di esecuzione e il risultato del partizionamento e trova i file.
  3. La NNAPI apre i file della cache e passa gli handle al driver con prepareModelFromCache
  4. Il driver prepara il modello direttamente dai file della cache e restituisce del modello preparato.

Informazioni cache fornite e fallimento della cache

  1. L'app passa un checksum univoco per il modello e una memorizzazione nella cache .
  2. Il runtime NNAPI cerca i file di memorizzazione nella cache in base al checksum, preferenza di esecuzione e il risultato di partizionamento e non trova .
  3. La NNAPI crea dei file di cache vuoti in base al checksum, l'esecuzione la preferenza e il partizionamento, apre i file della cache e passa le maniglie e il modello al conducente prepareModel_1_2
  4. Il driver compila il modello, scrive le informazioni di memorizzazione nella cache e restituisce il modello preparato.

Informazioni cache non fornite

  1. L'app richiama la compilazione senza fornire informazioni sulla memorizzazione nella cache.
  2. L'app non trasmette nulla riguardo alla memorizzazione nella cache.
  3. Il runtime NNAPI passa il modello al driver con prepareModel_1_2
  4. Il driver compila il modello e restituisce il modello preparato.

Informazioni cache

Le informazioni di memorizzazione nella cache fornite a un driver sono costituite da un token e degli handle dei file di cache.

Token

La token è un token di memorizzazione nella cache Constant::BYTE_SIZE_OF_CACHE_TOKEN: che identifica il modello preparato. Viene fornito lo stesso token Memorizza i file nella cache con prepareModel_1_2 e recupera il modello preparato con prepareModelFromCache. Il client del conducente deve scegliere un token con un un basso tasso di collisione. Il conducente non è in grado di rilevare una collisione tra token. Una collisione un'esecuzione non riuscita o una riuscita che produce valori di output errati.

Handle dei file cache (due tipi di file cache)

I due tipi di file di cache sono cache dei dati e cache del modello.

  • Cache dei dati: da utilizzare per la memorizzazione nella cache dei dati costanti, inclusi pre-elaborati e trasformati in buffer tensoriali. Una modifica alla cache dei dati non dovrebbe l'effetto è peggiore rispetto alla generazione di valori di output errati al momento dell'esecuzione nel tempo.
  • Cache del modello: da utilizzare per memorizzare nella cache dati sensibili alla sicurezza, come eseguibile del codice macchina nel formato binario nativo del dispositivo. R modifiche apportate alla cache del modello potrebbero influire sull'esecuzione del driver e un client dannoso potrebbe farne uso per eseguire l'autorizzazione concessa. Il driver deve quindi controllare se la cache del modello viene danneggiato prima di preparare il modello dalla cache. Per ulteriori informazioni, vedi Sicurezza.

Il conducente deve decidere in che modo le informazioni della cache sono distribuite tra i due tipi di file di cache e segnala il numero di file di cache necessari per ogni tipo con getNumberOfCacheFilesNeeded

Il runtime NNAPI apre sempre gli handle dei file della cache sia con operazioni di lettura che di scrittura autorizzazione.

Sicurezza

Nella memorizzazione nella cache di compilazione, la cache del modello può contenere dati sensibili alla sicurezza, come sotto forma di codice macchina eseguibile compilato nel formato binario nativo del dispositivo. In caso contrario adeguatamente protetto, una modifica alla cache del modello potrebbe incidere un comportamento di esecuzione. Poiché i contenuti della cache sono archiviati nell'app, i file della cache possono essere modificati dal client. Un client con problemi potrebbe danneggiare accidentalmente la cache e un client dannoso potrebbe per eseguire codice non verificato sul dispositivo. In base caratteristiche del dispositivo, potrebbe trattarsi di un problema di sicurezza. Di conseguenza, il conducente deve essere in grado di rilevare della cache del modello prima di prepararlo dalla cache.

Un modo per farlo è mantenere una mappa dal token a di crittografia della cache del modello. Il conducente può archiviare il token dell'hash della cache del modello durante il salvataggio della compilazione nella cache. Il conducente controlla il nuovo hash della cache del modello con il token e la coppia di hash registrati quando recuperando la compilazione dalla cache. Questa mappatura deve essere permanente di sistema. Il conducente può utilizzare Servizio di archiviazione chiavi Android, la libreria di utilità in framework/ml/nn/driver/cache, o qualsiasi altro meccanismo appropriato per implementare un gestore di mappatura. Al conducente aggiornamento, questo mapping manager deve essere reinizializzato per evitare di preparare la cache i file di una versione precedente.

Per evitare dal momento del controllo al momento dell'utilizzo (TOCTOU), il conducente deve calcolare l'hash registrato prima di salvare e calcolare il nuovo hash dopo aver copiato i contenuti del file in una buffer.

Questo codice di esempio mostra come implementare questa logica.

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

Casi d'uso avanzati

In alcuni casi d'uso avanzati, un driver richiede l'accesso ai contenuti della cache (lettura o scrittura) al termine della chiamata di compilazione. Esempi di casi d'uso includono:

  • Compilation in tempo reale: la compilazione viene posticipata fino al alla prima esecuzione.
  • Compilation multifase: viene eseguita una compilazione rapida e una compilazione ottimizzata facoltativa eseguita in un secondo momento in base alla frequenza di utilizzo.

Per accedere ai contenuti della cache (lettura o scrittura) dopo la chiamata di compilazione, assicurati che il conducente:

  • Duplica gli handle del file durante la chiamata di prepareModel_1_2 o prepareModelFromCache e legge/aggiorna la cache contenuti in un secondo momento.
  • Implementa una logica di blocco dei file al di fuori della normale chiamata di compilazione per evitare che una scrittura si verifichi contemporaneamente a una lettura o a un'altra scrittura.

Implementazione di un motore di memorizzazione nella cache

Oltre all'interfaccia di memorizzazione nella cache per la compilazione NN HAL 1.2, puoi anche trovare una di memorizzazione nella cache frameworks/ml/nn/driver/cache . La nnCache La sottodirectory contiene un codice di archiviazione permanente che il driver deve implementare Memorizzazione nella cache di compilazione senza utilizzare le funzionalità di memorizzazione nella cache NNAPI. Questa forma di La memorizzazione nella cache di compilazione può essere implementata con qualsiasi versione dell'HAL NN. Se sceglie di implementare la memorizzazione nella cache disconnessa dall'interfaccia HAL, il conducente liberando gli artefatti memorizzati nella cache quando non sono più necessari.