Пулы памяти

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

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

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

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

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

  • ashmem : Общая память Android. Подробнее см. в разделе «память» .
  • mmap_fd : Общая память, поддерживаемая файловым дескриптором через mmap .
  • hardware_buffer_blob : общая память, поддерживаемая буфером AHardwareBuffer в формате AHARDWARE_BUFFER_FORMAT_BLOB . Доступно в Neural Networks (NN) 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 . Поддержка доменов hardware_buffer и memory общего режима, отличного от BLOB, необязательна.

A HardwareBuffer

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 используется для явного копирования памяти. В определённых ситуациях клиент драйвера должен инициализировать управляемый драйвером буфер из общего пула памяти или скопировать буфер в общий пул памяти. Примеры использования:

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

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

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

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

  • Запрошенный буфер имеет динамический размер.
  • Драйвер имеет ограничения памяти, не позволяющие ему обрабатывать большие буферы.

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