Almacenamiento en caché de compilaciones

A partir de Android 10, la API de Neural Networks (NNAPI) proporciona funciones para respaldar almacenamiento en caché de artefactos de compilación, lo que reduce el tiempo que se usa para la compilación cuando se inicia una app. Con esta funcionalidad de almacenamiento en caché, el controlador no necesitas administrar o limpiar los archivos almacenados en caché. Esta es una función opcional se puede implementar con NN HAL 1.2. Para obtener más información sobre esta función, ver ANeuralNetworksCompilation_setCaching

El controlador también puede implementar el almacenamiento en caché de compilación independientemente de la NNAPI. Esta se puede implementar tanto si se usan las funciones de almacenamiento en caché de HAL y NDK de NNAPI. no. AOSP proporciona una biblioteca de utilidades de bajo nivel (un motor de almacenamiento en caché). Para ver más consulta Implementa un motor de almacenamiento en caché.

Descripción general del flujo de trabajo

En esta sección, se describen flujos de trabajo generales con la función de almacenamiento en caché de compilación cuando se implementa un plan.

Información de caché proporcionada y acierto de caché

  1. La app pasa un directorio de almacenamiento en caché y una suma de comprobación única para el modelo.
  2. El tiempo de ejecución de la NNAPI busca los archivos de caché según la suma de comprobación, el la preferencia de ejecución, el resultado de la partición y la búsqueda de archivos.
  3. La NNAPI abre los archivos de caché y pasa los controladores al controlador. con prepareModelFromCache
  4. El controlador prepara el modelo directamente desde los archivos de caché y muestra el modelo preparado.

Información de caché proporcionada y error de caché

  1. La app pasa una suma de comprobación única para el modelo y un almacenamiento .
  2. El tiempo de ejecución de la NNAPI busca los archivos almacenados en caché según la suma de comprobación, el la preferencia de ejecución y el resultado de la partición, y no encuentra los archivos de caché.
  3. La NNAPI crea archivos de caché vacíos basados en la suma de comprobación, la ejecución preferencia, y la partición, abre los archivos de caché y pasa el y el modelo al controlador con prepareModel_1_2
  4. El controlador compila el modelo y escribe la información de almacenamiento en caché en la caché y muestra el modelo preparado.

No se proporcionó información de la caché

  1. La app invoca la compilación sin proporcionar información de almacenamiento en caché.
  2. La app no pasa nada relacionado con el almacenamiento en caché.
  3. El tiempo de ejecución de la NNAPI pasa el modelo al controlador con prepareModel_1_2
  4. El controlador compila el modelo y muestra el modelo preparado.

Información de caché

La información de almacenamiento en caché que se proporciona a un controlador consta de un token y controladores de archivos en caché.

Token

El token es un token de longitud de almacenamiento en caché Constant::BYTE_SIZE_OF_CACHE_TOKEN que identifica el modelo preparado. Se proporciona el mismo token cuando se guarda el almacenar en caché los archivos con prepareModel_1_2 y recuperar el modelo preparado con prepareModelFromCache El cliente del controlador debe elegir un token con un baja tasa de colisión. El controlador no puede detectar una colisión de tokens. Una colisión genera una ejecución con errores o una ejecución exitosa que produce valores de salida incorrectos.

Identificadores de archivos de caché (dos tipos de archivos de caché)

Los dos tipos de archivos de caché son la caché de datos y la caché del modelo.

  • Caché de datos: Se usa para almacenar datos constantes en caché, incluso datos de tensores transformados. Una modificación de la caché de datos no debería provocarán un efecto peor que generar valores de salida erróneos en la ejecución tiempo.
  • Caché del modelo: Se usa para almacenar en caché datos sensibles de seguridad, como los código máquina ejecutable en el formato binario nativo del dispositivo. R La modificación de la caché de modelos podría afectar la ejecución del controlador comportamiento malicioso y un cliente malicioso podría usarlo para ejecutar acciones el permiso otorgado. Por lo tanto, el controlador debe verificar si la caché del modelo se daña antes de preparar el modelo desde la caché. Para obtener más información, consulta Seguridad.

El controlador debe decidir cómo se distribuye la información de la caché entre los dos. tipos de archivos de caché y, además, informa cuántos archivos de caché necesita para cada tipo con getNumberOfCacheFilesNeeded

El tiempo de ejecución de la NNAPI siempre abre los controladores del archivo de caché con las funciones de lectura y escritura. permiso.

Seguridad

En el almacenamiento en caché de compilación, la caché del modelo puede contener datos sensibles de seguridad, como como código máquina ejecutable compilado en el formato binario nativo del dispositivo. Si no es así estén debidamente protegidos, una modificación en la caché de modelos puede afectar el comportamiento de ejecución. Debido a que el contenido de la caché se almacena en la app , el cliente puede modificar los archivos de caché. Un cliente con errores puede dañar la caché por accidente, y un cliente malicioso podría para ejecutar códigos sin verificar en el dispositivo. Según el características del dispositivo, esto puede ser un problema de seguridad. Por lo tanto, el conductor debe poder detectar de la caché del modelo antes de prepararlo a partir de ella.

Una forma de hacerlo es que el conductor mantenga un mapa desde el token hasta una hash criptográfico de la caché del modelo. El controlador puede almacenar el token y el hash de la caché del modelo cuando se guarda la compilación en la caché. El conductor verifica el nuevo hash de la caché del modelo con el token registrado y el par de hash cuando recuperar la compilación desde la caché. Esta asignación debe ser persistente en toda se reinicia el sistema. El conductor puede usar el Servicio Android Keystore, la biblioteca de utilidades de framework/ml/nn/driver/cache: o cualquier otro mecanismo adecuado para implementar un administrador de mapas. Cuando el conductor actualización, este administrador de asignación debería reiniciarse para evitar que se prepare la caché archivos de una versión anterior.

Para evitar desde el tiempo de verificación hasta el tiempo de uso (TOCTOU), el controlador debe calcular el hash grabado antes de guardar y calcular el hash nuevo después de copiar el contenido del archivo en una red tiempo de reserva.

En este código de muestra, se muestra cómo implementar esta lógica.

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

Casos de uso avanzados

En algunos casos de uso avanzados, un controlador requiere acceso al contenido de la caché (lectura o escritura) después de la llamada de compilación. Estos son algunos ejemplos de casos de uso:

  • Compilación justo a tiempo: La compilación se retrasa hasta primera ejecución.
  • Compilación de varias etapas: Inicialmente, se realiza una compilación rápida. y se realiza una compilación optimizada opcionalmente según la frecuencia de uso.

Para acceder al contenido de la caché (de lectura o escritura) después de la llamada de compilación, asegúrate de que que el conductor hace lo siguiente:

  • Duplica los controladores del archivo durante la invocación de prepareModel_1_2 o prepareModelFromCache, y lee o actualiza la caché contenido en otro momento.
  • Implementa una lógica de bloqueo de archivos fuera de la llamada de compilación común para evitar que una escritura se produzca en simultáneo con una lectura o con otra escritura.

Implementa un motor de almacenamiento en caché

Además de la interfaz de almacenamiento en caché de la compilación NN HAL 1.2, también puedes encontrar una de una biblioteca de utilidades de frameworks/ml/nn/driver/cache . El nnCache el subdirectorio contiene el código de almacenamiento persistente para que el controlador implemente almacenamiento en caché de compilación sin usar las funciones de almacenamiento en caché de la NNAPI Esta forma de El almacenamiento en caché de compilación se puede implementar con cualquier versión de la NN HAL. Si el botón el controlador elige implementar el almacenamiento en caché desconectado de la interfaz de la HAL, el conductor es responsable de liberar los artefactos almacenados en caché cuando ya no son necesarios.