На этой странице описаны структуры данных и методы, используемые для эффективной передачи буферов операндов между драйвером и фреймворком.
На этапе компиляции модели фреймворк предоставляет драйверу значения константных операндов. В зависимости от времени жизни константного операнда его значения находятся либо в векторе HIDL, либо в пуле общей памяти.
- Если время жизни равно
CONSTANT_COPY, значения находятся в полеoperandValuesструктуры модели. Поскольку значения в векторе HIDL копируются во время межпроцессного взаимодействия (IPC), он обычно используется только для хранения небольшого объема данных, таких как скалярные операнды (например, скаляр активации вADD) и небольшие параметры тензора (например, тензор формы вRESHAPE). - Если время жизни равно
CONSTANT_REFERENCE, значения находятся в полеpoolsструктуры модели. Во время межпроцессного взаимодействия дублируются только дескрипторы пулов общей памяти, а не копируются исходные значения. Поэтому хранение большого объема данных (например, параметров весов в свертках) с использованием пулов общей памяти эффективнее, чем использование векторов HIDL.
Во время выполнения модели фреймворк предоставляет драйверу буферы входных и выходных операндов. В отличие от констант времени компиляции, которые могут передаваться в векторе HIDL, входные и выходные данные выполнения всегда передаются через набор пулов памяти.
Тип данных HIDL hidl_memory используется как при компиляции, так и при выполнении для представления неотображенного пула разделяемой памяти. Драйвер должен соответствующим образом отобразить память, чтобы сделать ее доступной, исходя из имени типа данных hidl_memory . Поддерживаемые имена памяти:
-
ashmem: Общая память Android. Для получения более подробной информации см. раздел memory . -
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 и памяти, не относящихся к режиму BLOB, является необязательной.
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 , если он поддерживает выделение памяти в доменах. Поддержка режима hardware_buffer без BLOB-объектов является необязательной.
При выделении буфера его размеры могут быть определены на основе соответствующих операндов модели для всех ролей, указанных в BufferRole , и размеров, предоставленных в BufferDesc . При наличии всей информации о размерах буфер может иметь неизвестные размеры или ранг. В таком случае буфер находится в гибком состоянии: его размеры фиксированы при использовании в качестве входных данных модели, а динамические — при использовании в качестве выходных данных модели. Один и тот же буфер может использоваться с различными формами выходных данных в разных выполнениях, и драйвер должен корректно обрабатывать изменение размера буфера.
Домен памяти — это необязательная функция. Драйвер может определить, что он не может поддержать данный запрос на выделение памяти по ряду причин. Например:
- Запрошенный буфер имеет динамический размер.
- Драйвер имеет ограничения по объему памяти, что не позволяет ему обрабатывать большие буферы.
Несколько разных потоков могут одновременно считывать данные из буфера, управляемого драйвером. Одновременный доступ к буферу для записи или чтения/записи не определен, но он не должен приводить к сбою службы драйвера или блокировке вызывающего потока на неопределенное время. Драйвер может вернуть ошибку или оставить содержимое буфера в неопределенном состоянии.