В Android 10 Neural Networks API (NNAPI) предоставляет функции для поддержки кеширования артефактов компиляции, что сокращает время, затрачиваемое на компиляцию при запуске приложения. При использовании этой функции драйверу не нужно управлять кешированными файлами или удалять их. Это необязательная функция, которую можно реализовать с помощью NN HAL 1.2. Подробнее
об этой функцииANeuralNetworksCompilation_setCaching…
Драйвер также может реализовать кеширование компиляции независимо от NNAPI. Это можно реализовать независимо от того, используются ли функции кеширования NNAPI NDK и HAL. AOSP предоставляет низкоуровневую библиотеку утилит (механизм кеширования). Подробнее о реализации механизма кеширования…
Обзор рабочего процесса
В этом разделе описаны общие рабочие процессы с использованием функции кеширования компиляции.
Предоставление информации о кеше и совпадение в кеше
- Приложение передает каталог кеширования и контрольную сумму, уникальную для модели.
- Среда выполнения NNAPI ищет файлы кеша на основе контрольной суммы, предпочтения выполнения и результата разбиения и находит файлы.
- NNAPI открывает файлы кеша и передает дескрипторы драйверу с помощью
prepareModelFromCache. - Драйвер подготавливает модель непосредственно из файлов кеша и возвращает подготовленную модель.
Информация о кеше предоставлена, но нет совпадения в кеше
- Приложение проходит проверку контрольной суммы, уникальной для модели и каталога кеширования.
- Среда выполнения NNAPI ищет файлы кэширования на основе контрольной суммы, предпочтения выполнения и результата разбиения и не находит файлы кэша.
- NNAPI создает пустые файлы кеша на основе контрольной суммы, предпочтительного режима выполнения и разбиения, открывает их и передает дескрипторы и модель драйверу с помощью
prepareModel_1_2. - Драйвер компилирует модель, записывает информацию о кешировании в файлы кеша и возвращает подготовленную модель.
Информация о кеше не предоставлена
- Приложение вызывает компиляцию без предоставления информации о кешировании.
- Приложение не передает ничего, связанного с кешированием.
- Среда выполнения NNAPI передает модель драйверу с помощью функции
prepareModel_1_2. - Драйвер компилирует модель и возвращает подготовленную модель.
Информация о кеше
Информация о кешировании, которая предоставляется драйверу, состоит из токена и дескрипторов кеш-файлов.
Токен
Токен – это токен кеширования длиной Constant::BYTE_SIZE_OF_CACHE_TOKEN, который идентифицирует подготовленную модель. Тот же токен предоставляется при сохранении файлов кеша с помощью prepareModel_1_2 и получении подготовленной модели с помощью prepareModelFromCache. Клиент драйвера должен выбрать токен с низким коэффициентом коллизий. Драйвер не может обнаружить коллизию токенов. В результате коллизии выполнение может завершиться сбоем или успешным получением неверных выходных значений.
Дескрипторы кешированных файлов (два типа кешированных файлов)
Существует два типа файлов кеша: кеш данных и кеш моделей.
- Кеш данных. Используется для кеширования постоянных данных, в том числе предварительно обработанных и преобразованных буферов тензоров. Изменение кеша данных не должно приводить к последствиям, худшим, чем создание неверных выходных значений во время выполнения.
- Кэш моделей. Используется для кэширования конфиденциальных данных, например скомпилированного исполняемого машинного кода в собственном двоичном формате устройства. Изменение кеша модели может повлиять на поведение драйвера, и вредоносный клиент может использовать это, чтобы выполнить действия за пределами предоставленных разрешений. Поэтому перед тем, как подготовить модель из кеша, драйвер должен проверить, не поврежден ли кеш модели. Подробнее о безопасности…
Драйвер должен решить, как распределить информацию между двумя типами файлов кеша, и сообщить, сколько файлов каждого типа ему нужно, с помощью getNumberOfCacheFilesNeeded.
Среда выполнения NNAPI всегда открывает дескрипторы файлов кеша с разрешениями на чтение и запись.
Безопасность
При кешировании компиляции кеш модели может содержать конфиденциальные данные, например скомпилированный исполняемый машинный код в собственном двоичном формате устройства. Если кеш модели не защищен должным образом, его изменение может повлиять на поведение драйвера при выполнении. Поскольку содержимое кеша хранится в каталоге приложения, клиент может изменять файлы кеша. Клиент с ошибками может случайно повредить кеш, а вредоносный клиент может намеренно использовать это для выполнения непроверенного кода на устройстве. В зависимости от характеристик устройства это может быть проблемой безопасности. Поэтому перед тем, как подготовить модель из кеша, драйвер должен обнаружить возможные повреждения кеша модели.
Один из способов сделать это – создать в драйвере карту, которая сопоставляет токен с криптографическим хешем кеша модели. Драйвер может сохранить токен и хеш кеша модели при сохранении компиляции в кеш. При извлечении компиляции из кеша драйвер проверяет новый хеш кеша модели с помощью записанной пары токена и хеша. Это сопоставление должно сохраняться при перезагрузке системы. Драйвер может использовать сервис хранилища ключей Android, библиотеку утилит в framework/ml/nn/driver/cache или любой другой подходящий механизм для реализации менеджера сопоставлений. После обновления драйвера этот менеджер сопоставлений необходимо инициализировать заново, чтобы предотвратить подготовку файлов кеша из более ранней версии.
Чтобы предотвратить атаки time-of-check to time-of-use (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, он должен освобождать кешированные артефакты, когда они больше не нужны.