Grupos de memoria

Esta página describe las estructuras de datos y los métodos utilizados para comunicar eficientemente los buffers de operandos entre el controlador y el marco.

En el momento de la compilación del modelo, el marco proporciona los valores de los operandos constantes al controlador. Dependiendo de la vida útil del operando constante, sus valores se ubican en un vector HIDL o en un grupo de memoria compartida.

  • Si la vida útil es CONSTANT_COPY , los valores se ubican en el campo operandValues ​​de la estructura del modelo. Debido a que los valores en el vector HIDL se copian durante la comunicación entre procesos (IPC), esto generalmente se usa solo para contener una pequeña cantidad de datos, como operandos escalares (por ejemplo, el escalar de activación en ADD ) y parámetros tensoriales pequeños (por ejemplo, el tensor de forma en RESHAPE ).
  • Si la vida útil es CONSTANT_REFERENCE , los valores se ubican en el campo pools de la estructura del modelo. Sólo los identificadores de los grupos de memoria compartida se duplican durante la IPC en lugar de copiar los valores sin procesar. Por lo tanto, es más eficiente almacenar una gran cantidad de datos (por ejemplo, los parámetros de peso en convoluciones) utilizando grupos de memoria compartida que los vectores HIDL.

En el momento de la ejecución del modelo, el marco proporciona los buffers de los operandos de entrada y salida al controlador. A diferencia de las constantes en tiempo de compilación que podrían enviarse en un vector HIDL, los datos de entrada y salida de una ejecución siempre se comunican a través de una colección de grupos de memoria.

El tipo de datos HIDL hidl_memory se utiliza tanto en la compilación como en la ejecución para representar un grupo de memoria compartida no asignado. El controlador debe asignar la memoria en consecuencia para que sea utilizable según el nombre del tipo de datos hidl_memory . Los nombres de memoria admitidos son:

  • ashmem : memoria compartida de Android. Para más detalles, ver memoria .
  • mmap_fd : Memoria compartida respaldada por un descriptor de archivo a través de mmap .
  • hardware_buffer_blob : Memoria compartida respaldada por un AHardwareBuffer con el formato AHARDWARE_BUFFER_FORMAT_BLOB . Disponible en redes neuronales (NN) HAL 1.2. Para obtener más detalles, consulte AHardwareBuffer .
  • hardware_buffer : Memoria compartida respaldada por un AHardwareBuffer general que no utiliza el formato AHARDWARE_BUFFER_FORMAT_BLOB . El búfer de hardware en modo no BLOB solo se admite en la ejecución del modelo. Disponible en NN HAL 1.2. Para obtener más detalles, consulte AHardwareBuffer .

Desde NN HAL 1.3, NNAPI admite dominios de memoria que proporcionan interfaces de asignación para búferes administrados por el controlador. Los buffers administrados por el controlador también se pueden utilizar como entradas o salidas de ejecución. Para obtener más detalles, consulte Dominios de memoria .

Los controladores NNAPI deben admitir el mapeo de nombres de memoria ashmem y mmap_fd . A partir de NN HAL 1.3, los controladores también deben admitir el mapeo de hardware_buffer_blob . La compatibilidad con dominios de memoria y hardware_buffer en modo general no BLOB es opcional.

AHardwareBuffer

AHardwareBuffer es un tipo de memoria compartida que envuelve un búfer Gralloc . En Android 10, la API de redes neuronales (NNAPI) admite el uso de AHardwareBuffer , lo que permite al controlador realizar ejecuciones sin copiar datos, lo que mejora el rendimiento y el consumo de energía de las aplicaciones. Por ejemplo, una pila HAL de cámara puede pasar objetos AHardwareBuffer a NNAPI para cargas de trabajo de aprendizaje automático utilizando identificadores AHardwareBuffer generados por el NDK de cámara y las API de NDK multimedia. Para obtener más información, consulte ANeuralNetworksMemory_createFromAHardwareBuffer .

Los objetos AHardwareBuffer utilizados en NNAPI se pasan al controlador a través de una estructura hidl_memory denominada hardware_buffer o hardware_buffer_blob . La estructura hidl_memory hardware_buffer_blob representa solo objetos AHardwareBuffer con el formato AHARDWAREBUFFER_FORMAT_BLOB .

La información requerida por el marco está codificada en el campo hidl_handle de la estructura hidl_memory . El campo hidl_handle envuelve native_handle , que codifica todos los metadatos requeridos sobre el búfer AHardwareBuffer o Gralloc.

El controlador debe decodificar correctamente el campo hidl_handle proporcionado y acceder a la memoria descrita por hidl_handle . Cuando se llama al método getSupportedOperations_1_2 , getSupportedOperations_1_1 o getSupportedOperations , el controlador debe detectar si puede decodificar el hidl_handle proporcionado y acceder a la memoria descrita por hidl_handle . La preparación del modelo debe fallar si no se admite el campo hidl_handle utilizado para un operando constante. La ejecución debe fallar si el campo hidl_handle utilizado para un operando de entrada o salida de la ejecución no es compatible. Se recomienda que el controlador devuelva un código de error GENERAL_FAILURE si falla la preparación o ejecución del modelo.

Dominios de memoria

Para dispositivos que ejecutan Android 11 o superior, NNAPI admite dominios de memoria que proporcionan interfaces de asignación para búferes administrados por el controlador. Esto permite pasar memorias nativas del dispositivo entre ejecuciones, suprimiendo la copia y transformación de datos innecesarias entre ejecuciones consecutivas en el mismo controlador. Este flujo se ilustra en la Figura 1.

Flujo de datos del búfer con y sin dominios de memoria

Figura 1. Flujo de datos del búfer utilizando dominios de memoria

La función de dominio de memoria está destinada a tensores que en su mayoría son internos al controlador y no necesitan acceso frecuente en el lado del cliente. Ejemplos de tales tensores incluyen los tensores de estado en modelos de secuencia. Para los tensores que necesitan acceso frecuente a la CPU en el lado del cliente, es preferible utilizar grupos de memoria compartida.

Para admitir la característica de dominio de memoria, implemente IDevice::allocate para permitir que el marco solicite la asignación de búfer administrada por el controlador. Durante la asignación, el marco proporciona las siguientes propiedades y patrones de uso para el búfer:

  • BufferDesc describe las propiedades requeridas del búfer.
  • BufferRole describe el patrón de uso potencial del búfer como entrada o salida de un modelo preparado. Se pueden especificar varios roles durante la asignación del búfer, y el búfer asignado solo se puede utilizar para esos roles especificados.

El búfer asignado es interno al controlador. Un conductor puede elegir cualquier ubicación del búfer o diseño de datos. Cuando el búfer se asigna correctamente, el cliente del controlador puede hacer referencia al búfer o interactuar con él utilizando el token devuelto o el objeto IBuffer .

El token de IDevice::allocate se proporciona cuando se hace referencia al búfer como uno de los objetos MemoryPool en la estructura Request de una ejecución. Para evitar que un proceso intente acceder al búfer asignado en otro proceso, el controlador debe aplicar la validación adecuada en cada uso del búfer. El controlador debe validar que el uso del búfer es uno de los roles BufferRole proporcionados durante la asignación y debe fallar la ejecución inmediatamente si el uso es ilegal.

El objeto IBuffer se utiliza para la copia de memoria explícita. En determinadas situaciones, el cliente del controlador debe inicializar el búfer administrado por el controlador desde un grupo de memoria compartida o copiar el búfer a un grupo de memoria compartida. Los casos de uso de ejemplo incluyen:

  • Inicialización del tensor de estado.
  • Almacenamiento en caché de resultados intermedios
  • Ejecución alternativa en la CPU

Para admitir estos casos de uso, el controlador debe implementar IBuffer::copyTo e IBuffer::copyFrom con ashmem , mmap_fd y hardware_buffer_blob si admite la asignación de dominio de memoria. Es opcional que el controlador admita hardware_buffer en modo no BLOB.

Durante la asignación del búfer, las dimensiones del búfer se pueden deducir de los operandos del modelo correspondiente de todos los roles especificados por BufferRole y las dimensiones proporcionadas en BufferDesc . Con toda la información dimensional combinada, es posible que el búfer tenga dimensiones o rango desconocidos. En tal caso, el búfer está en un estado flexible donde las dimensiones son fijas cuando se usa como entrada del modelo y en un estado dinámico cuando se usa como salida del modelo. El mismo búfer se puede utilizar con diferentes formas de salidas en diferentes ejecuciones y el controlador debe manejar el cambio de tamaño del búfer correctamente.

El dominio de memoria es una característica opcional. Un conductor puede determinar que no puede respaldar una solicitud de asignación determinada por varios motivos. Por ejemplo:

  • El búfer solicitado tiene un tamaño dinámico.
  • El controlador tiene limitaciones de memoria que le impiden manejar grandes buffers.

Es posible que varios subprocesos diferentes lean simultáneamente desde el búfer administrado por el controlador. El acceso al búfer simultáneamente para escritura o lectura/escritura no está definido, pero no debe bloquear el servicio del controlador ni bloquear a la persona que llama indefinidamente. El controlador puede devolver un error o dejar el contenido del búfer en un estado indeterminado.