Кэширование компиляции

Начиная с Android 10, API нейронных сетей (NNAPI) предоставляет функции для поддержки кэширования артефактов компиляции, что сокращает время, затрачиваемое на компиляцию при запуске приложения. Используя эту функцию кэширования, драйверу не нужно управлять кэшированными файлами или очищать их. Это дополнительная функция, которую можно реализовать с помощью NN HAL 1.2. Дополнительные сведения об этой функции см. в разделе ANeuralNetworksCompilation_setCaching .

Драйвер также может реализовать кэширование компиляции независимо от NNAPI. Это может быть реализовано независимо от того, используются функции кэширования NNAPI NDK и HAL или нет. AOSP предоставляет низкоуровневую служебную библиотеку (механизм кэширования). Дополнительные сведения см. в разделе Реализация механизма кэширования .

Обзор рабочего процесса

В этом разделе описываются общие рабочие процессы с реализованной функцией кэширования компиляции.

Предоставленная информация о кэшировании и попадание в кеш

  1. Приложение передает каталог кэширования и контрольную сумму, уникальную для модели.
  2. Среда выполнения NNAPI ищет файлы кэша на основе контрольной суммы, предпочтения выполнения и результата разделения и находит файлы.
  3. NNAPI открывает файлы кэша и передает дескрипторы драйверу с помощью prepareModelFromCache .
  4. Драйвер подготавливает модель непосредственно из файлов кеша и возвращает подготовленную модель.

Предоставленная информация о кэшировании и отсутствие кэша

  1. Приложение передает контрольную сумму, уникальную для модели и каталога кэширования.
  2. Среда выполнения NNAPI ищет файлы кэширования на основе контрольной суммы, предпочтения выполнения и результата разделения и не находит файлы кэша.
  3. NNAPI создает пустые файлы кеша на основе контрольной суммы, предпочтения выполнения и разделения, открывает файлы кеша и передает дескрипторы и модель драйверу с помощью prepareModel_1_2 .
  4. Драйвер компилирует модель, записывает информацию о кэшировании в файлы кэша и возвращает подготовленную модель.

Информация о кэшировании не предоставлена

  1. Приложение вызывает компиляцию, не предоставляя никакой информации о кэшировании.
  2. Приложение не передает ничего, связанного с кэшированием.
  3. Среда выполнения NNAPI передает модель драйверу с помощью prepareModel_1_2 .
  4. Драйвер компилирует модель и возвращает подготовленную модель.

Кэширование информации

Информация о кэшировании, предоставляемая драйверу, состоит из маркера и дескрипторов файла кэша.

Токен

Токен представляет собой кеширующий токен длины Constant::BYTE_SIZE_OF_CACHE_TOKEN , который идентифицирует подготовленную модель. Тот же токен предоставляется при сохранении файлов кеша с помощью prepareModel_1_2 и получении подготовленной модели с помощью prepareModelFromCache . Клиент драйвера должен выбрать токен с низкой частотой столкновений. Драйвер не может обнаружить столкновение маркеров. Конфликт приводит к неудачному выполнению или к успешному выполнению, которое дает неправильные выходные значения.

Дескрипторы файлов кеша (два типа файлов кеша)

Кэш-файлы бывают двух типов: кеш данных и кеш модели .

  • Кэш данных: используется для кэширования постоянных данных, включая предварительно обработанные и преобразованные тензорные буферы. Модификация кэша данных не должна приводить ни к какому эффекту хуже, чем генерация неправильных выходных значений во время выполнения.
  • Кэш модели: используется для кэширования конфиденциальных данных, таких как скомпилированный исполняемый машинный код в собственном двоичном формате устройства. Модификация кэша модели может повлиять на поведение драйвера при выполнении, и злонамеренный клиент может использовать это для выполнения за пределами предоставленного разрешения. Таким образом, перед подготовкой модели из кэша драйвер должен проверить, не поврежден ли кэш модели. Дополнительные сведения см. в разделе Безопасность .

Драйвер должен решить, как информация кеша распределяется между двумя типами файлов кеша, и сообщить, сколько файлов кеша ему нужно для каждого типа с помощью getNumberOfCacheFilesNeeded .

Среда выполнения NNAPI всегда открывает дескрипторы файлов кэша с правами чтения и записи.

Безопасность

При кэшировании компиляции кэш модели может содержать важные для безопасности данные, такие как скомпилированный исполняемый машинный код в собственном двоичном формате устройства. Модификация кэша модели, если она не защищена должным образом, может повлиять на поведение драйвера при выполнении. Поскольку содержимое кеша хранится в каталоге приложения, файлы кеша могут быть изменены клиентом. Глючный клиент может случайно повредить кеш, а злонамеренный клиент может намеренно использовать это для выполнения непроверенного кода на устройстве. В зависимости от характеристик устройства это может быть проблемой безопасности. Таким образом, драйвер должен иметь возможность обнаруживать потенциальное повреждение кэша модели перед подготовкой модели из кэша.

Один из способов сделать это — чтобы драйвер поддерживал сопоставление токена с криптографическим хэшем кэша модели. Драйвер может сохранить токен и хэш своего кеша модели при сохранении компиляции в кеше. Драйвер сверяет новый хэш кеша модели с записанной парой токена и хэша при извлечении компиляции из кеша. Это сопоставление должно сохраняться при перезагрузке системы. Драйвер может использовать службу хранилища ключей Android , служебную библиотеку в framework/ml/nn/driver/cache или любой другой подходящий механизм для реализации диспетчера сопоставления. После обновления драйвера этот менеджер сопоставления должен быть повторно инициализирован, чтобы предотвратить подготовку файлов кэша из более ранней версии.

Чтобы предотвратить атаки типа «проверка времени на время использования » (TOCTOU), драйвер должен вычислить записанный хэш перед сохранением в файл и вычислить новый хэш после копирования содержимого файла во внутренний буфер.

Этот пример кода демонстрирует, как реализовать эту логику.

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

Расширенные варианты использования

В некоторых расширенных случаях использования драйверу требуется доступ к содержимому кэша (чтение или запись) после вызова компиляции. Примеры использования включают в себя:

  • Своевременная компиляция: компиляция откладывается до первого выполнения.
  • Многоступенчатая компиляция: сначала выполняется быстрая компиляция, а опциональная оптимизированная компиляция выполняется позднее, в зависимости от частоты использования.

Чтобы получить доступ к содержимому кэша (чтение или запись) после вызова компиляции, убедитесь, что драйвер:

  • Дублирует дескрипторы файлов во время вызова prepareModel_1_2 или prepareModelFromCache и считывает/обновляет содержимое кэша позднее.
  • Реализует логику блокировки файлов вне обычного вызова компиляции, чтобы предотвратить запись, происходящую одновременно с чтением или другой записью.

Реализация механизма кэширования

В дополнение к интерфейсу кэширования компиляции NN HAL 1.2 вы также можете найти служебную библиотеку кэширования в каталоге frameworks/ml/nn/driver/cache . nnCache содержит код постоянного хранилища для реализации драйвером кэширования компиляции без использования функций кэширования NNAPI. Эта форма кэширования компиляции может быть реализована с любой версией NN HAL. Если драйвер решает реализовать кэширование без подключения к интерфейсу HAL, драйвер отвечает за освобождение кэшированных артефактов, когда они больше не нужны.