Пулы памяти

На этой странице описаны структуры данных и методы, используемые для эффективной передачи буферов операндов между драйвером и фреймворком.

Во время компиляции модели фреймворк предоставляет драйверу значения постоянных операндов. В зависимости от срока жизни постоянного операнда его значения хранятся либо в векторе HIDL, либо в пуле общей памяти.

  • Если срок жизни равен CONSTANT_COPY, значения находятся в поле operandValues структуры модели. Поскольку значения в векторе HIDL копируются во время межпроцессного взаимодействия (IPC), обычно он используется только для хранения небольшого объема данных, например скалярных операндов (скаляр активации в ADD) и небольших параметров тензора (тензор формы в RESHAPE).
  • Если срок жизни равен CONSTANT_REFERENCE, значения находятся в поле pools структуры модели. При межпроцессном взаимодействии дублируются только дескрипторы пулов общей памяти, а не копируются необработанные значения. Поэтому для хранения больших объемов данных (например, параметров веса в свертках) эффективнее использовать общие пулы памяти, а не векторы HIDL.

Во время выполнения модели фреймворк предоставляет драйверу буферы входных и выходных операндов. В отличие от констант времени компиляции, которые могут быть отправлены в векторе HIDL, входные и выходные данные выполнения всегда передаются через набор пулов памяти.

Тип данных HIDL hidl_memory используется как при компиляции, так и при выполнении, чтобы представлять неназначенный пул общей памяти. Драйвер должен сопоставить память соответствующим образом, чтобы ее можно было использовать на основе названия типа данных hidl_memory. Поддерживаются следующие названия:

  • ashmem – общая память Android. Подробнее о памяти…
  • mmap_fd – общее воспоминание, поддерживаемое дескриптором файла через mmap.
  • hardware_buffer_blob – общая память, поддерживаемая AHardwareBuffer в формате AHARDWARE_BUFFER_FORMAT_BLOB. Доступно в HAL 1.2 для нейронных сетей. Подробнее об объекте AHardwareBuffer…
  • hardware_buffer – общая память, поддерживаемая общим буфером AHardwareBuffer, который не использует формат AHARDWARE_BUFFER_FORMAT_BLOB. Буфер оборудования в режиме без BLOB поддерживается только при выполнении модели.Доступно в NN HAL 1.2. Подробнее об объекте AHardwareBuffer…

Начиная с NN HAL 1.3, NNAPI поддерживает домены памяти, которые предоставляют интерфейсы распределителя для буферов, управляемых драйвером. Буферы, управляемые драйвером, также можно использовать в качестве входных или выходных данных для выполнения. Подробнее о доменах памяти…

Драйверы NNAPI должны поддерживать сопоставление названий памяти ashmem и mmap_fd. Начиная с NN HAL 1.3, драйверы также должны поддерживать сопоставление hardware_buffer_blob. Поддержка общего режима без BLOB-объектов hardware_buffer и доменов памяти не обязательна.

AHardwareBuffer

AHardwareBuffer – это тип общей памяти, который содержит буфер Gralloc. В Android 10 API нейронных сетей (NNAPI) поддерживает использование AHardwareBuffer, что позволяет драйверу выполнять операции без копирования данных. Это повышает производительность и снижает энергопотребление приложений. Например, стек HAL камеры может передавать объекты AHardwareBuffer в NNAPI для рабочих нагрузок машинного обучения, используя дескрипторы AHardwareBuffer, сгенерированные API NDK камеры и NDK мультимедиа. Подробнее ANeuralNetworksMemory_createFromAHardwareBuffer…

Объекты AHardwareBuffer, используемые в NNAPI, передаются драйверу через структуру hidl_memory, которая называется hardware_buffer или hardware_buffer_blob. Структура hidl_memory hardware_buffer_blob представляет только объекты AHardwareBuffer с форматом AHARDWAREBUFFER_FORMAT_BLOB.

Информация, необходимая для фреймворка, закодирована в поле hidl_handle структуры hidl_memory. Поле hidl_handle содержит native_handle, в котором закодированы все необходимые метаданные о буфере AHardwareBuffer или Gralloc.

Драйвер должен правильно декодировать поле hidl_handle и получить доступ к памяти, описанной в этом полеhidl_handle. Когда вызывается метод getSupportedOperations_1_2, getSupportedOperations_1_1 или getSupportedOperations, драйвер должен определить, может ли он декодировать предоставленный объект hidl_handle и получить доступ к памяти, описанной в hidl_handle. Если поле hidl_handle, используемое для постоянного операнда, не поддерживается, подготовка модели должна завершиться ошибкой. Модель Выполнение должно завершиться ошибкой, если поле hidl_handle, используемое для входного или выходного операнда выполнения, не поддерживается. Если подготовка или выполнение модели завершились неудачно, драйвер должен вернуть код ошибки GENERAL_FAILURE.

Домены памяти

На устройствах с Android 11 и более поздними версиями NNAPI поддерживает домены памяти, которые предоставляют интерфейсы распределителя для буферов, управляемых драйвером. Это позволяет передавать данные из памяти устройства между выполнениями, подавляя ненужное копирование и преобразование данных между последовательными выполнениями на одном и том же драйвере. На рисунке 1 показан этот процесс.

Поток данных буфера с доменами памяти и без них

Рисунок 1. Буферизация потока данных с помощью доменов памяти

Функция домена памяти предназначена для тензоров, которые в основном используются внутри драйвера и не требуют частого доступа на стороне клиента. Примерами таких тензоров являются тензоры состояния в моделях последовательностей. Для тензоров, которым требуется частый доступ к ЦП на стороне клиента, предпочтительно использовать пулы общей памяти.

Чтобы поддерживать функцию домена памяти, реализуйте IDevice::allocate, чтобы фреймворк мог запрашивать выделение буфера, управляемого драйвером. Во время распределения фреймворк предоставляет следующие свойства и шаблоны использования буфера:

  • BufferDesc описывает обязательные свойства буфера.
  • BufferRole описывает потенциальный шаблон использования буфера в качестве входных или выходных данных подготовленной модели. При выделении буфера можно указать несколько ролей, и выделенный буфер можно будет использовать только в этих ролях.

Выделенный буфер является внутренним для драйвера. Драйвер может выбрать любое расположение буфера или макет данных. После успешного выделения буфера клиент драйвера может ссылаться на буфер или взаимодействовать с ним, используя возвращенный токен или объект IBuffer.

Токен из IDevice::allocate предоставляется при ссылке на буфер как на один из объектов MemoryPool в структуре Request выполнения. Чтобы процесс не пытался получить доступ к буферу, выделенному в другом процессе, драйвер должен применять надлежащую проверку при каждом использовании буфера. Драйвер должен проверить, что буфер используется в одной из BufferRole ролей, указанных при выделении памяти, и немедленно остановить выполнение , если использование незаконно.

Объект IBuffer используется для явного копирования памяти. В некоторых случаях клиент драйвера должен инициализировать буфер, управляемый драйвером, из пула общей памяти или скопировать буфер в пул общей памяти. Примеры использования:

  • Инициализация тензора состояния
  • Кеширование промежуточных результатов
  • Резервное выполнение на ЦП

Чтобы поддерживать эти варианты использования, драйвер должен реализовать IBuffer::copyTo и IBuffer::copyFrom с ashmem, mmap_fd и hardware_buffer_blob, если он поддерживает выделение домена памяти. Поддержка режима без BLOB-объектов не является обязательной для драйвера. hardware_buffer

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

Домен памяти – это необязательная функция. Драйвер может определить, что не может выполнить запрос на выделение памяти, по ряду причин. Пример:

  • Запрошенный буфер имеет динамический размер.
  • У драйвера недостаточно памяти для обработки больших буферов.

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