Grupos de memoria

En esta página, se describen las estructuras de datos y los métodos usados para comunicar de manera eficiente los búferes de operando entre el controlador y el framework.

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

  • Si el ciclo de vida es CONSTANT_COPY, los valores se encuentran 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 de tensor pequeños (por ejemplo, el tensor de forma en RESHAPE).
  • Si el ciclo de vida es CONSTANT_REFERENCE, los valores se encuentran en el campo pools de la estructura del modelo. Solo los controladores 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 retener una gran cantidad de datos (por ejemplo, los parámetros de peso en convoluciones) con grupos de memoria compartida que los vectores HIDL.

En el tiempo de ejecución del modelo, el framework proporciona los búferes de los operandos de entrada y salida al controlador. A diferencia de las constantes de 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 un conjunto de grupos de memoria.

El tipo de datos de HIDL hidl_memory se usa en la compilación y en la ejecución para representar un grupo de memoria compartida sin asignar. El controlador debe asignar la memoria de manera adecuada para que se pueda usar en función del nombre del tipo de datos hidl_memory. Los nombres de memoria compatibles son los siguientes:

  • ashmem: Memoria compartida de Android. Para obtener más detalles, consulta memory.
  • mmap_fd: Memoria compartida respaldada por un descriptor de archivos mediante mmap.
  • hardware_buffer_blob: Es la memoria compartida respaldada por un AHardwareBuffer con el formato AHARDWARE_BUFFER_FORMAT_BLOB. Disponible en la HAL 1.2 de redes neuronales (NN). Para obtener más información, consulta AHardwareBuffer.
  • hardware_buffer: Es la memoria compartida respaldada por un AHardwareBuffer general que no usa el formato AHARDWARE_BUFFER_FORMAT_BLOB. El búfer de hardware en modo que no es BLOB solo se admite en la ejecución del modelo.Disponible en NNHAL 1.2. Para obtener más información, consulta AHardwareBuffer.

A partir de NN HAL 1.3, la NNAPI es compatible con dominios de memoria que proporcionan interfaces asignables para búferes administrados por controladores. Los búferes administrados por el controlador también se pueden usar como entradas o salidas de ejecución. Para obtener más detalles, consulta Dominios de memoria.

Los controladores de la NNAPI deben admitir la asignación de nombres de memoria ashmem y mmap_fd. A partir de NN HAL 1.3, los controladores también deben admitir la asignación de hardware_buffer_blob. La compatibilidad con dominios de memoria y hardware_buffer generales que no sean del modo BLOB es opcional.

Búfer de hardware

AHardwareBuffer es un tipo de memoria compartida que une un búfer de Gralloc. En Android 10, la API de Neural Networks (NNAPI) admite el uso de AHardwareBuffer, lo que permite que el controlador realice ejecuciones sin copiar datos, lo que mejora el rendimiento y el consumo de energía de las apps. Por ejemplo, una pila de HAL de cámara puede pasar objetos AHardwareBuffer a la NNAPI para las cargas de trabajo de aprendizaje automático mediante controladores de AHardwareBuffer generados por las APIs del NDK de contenido multimedia y la cámara. Para obtener más información, consulta ANeuralNetworksMemory_createFromAHardwareBuffer.

Los objetos AHardwareBuffer que se usan en la NNAPI se pasan al controlador mediante una estructura hidl_memory llamada hardware_buffer o hardware_buffer_blob. El struct hardware_buffer_blob de hidl_memory representa solo objetos AHardwareBuffer con el formato AHARDWAREBUFFER_FORMAT_BLOB.

La información requerida por el framework está codificada en el campo hidl_handle de la estructura hidl_memory. El campo hidl_handle une native_handle, que codifica todos los metadatos necesarios sobre AHardwareBuffer o Gralloc.

El controlador debe decodificar correctamente el campo hidl_handle proporcionado y acceder a la memoria que se describe en hidl_handle. Cuando se llama a los métodos getSupportedOperations_1_2, getSupportedOperations_1_1 o getSupportedOperations, el controlador debe detectar si puede decodificar el hidl_handle proporcionado y acceder a la memoria que describe hidl_handle. La preparación del modelo debe fallar si no se admite el campo hidl_handle que se usa para un operando constante. La ejecución debe fallar si no se admite el campo hidl_handle que se usa para un operando de entrada o salida de la ejecución. Se recomienda que el controlador muestre un código de error GENERAL_FAILURE si falla la preparación o ejecución del modelo.

Dominios de memoria

En los dispositivos que ejecutan Android 11 o versiones posteriores, la NNAPI es compatible con dominios de memoria que proporcionan interfaces asignables para búferes administrados por controladores. Esto permite pasar memorias nativas del dispositivo entre ejecuciones, lo que elimina la transformación y la copia innecesarias de datos entre ejecuciones consecutivas en el mismo controlador. Este flujo se ilustra en la Figura 1.

Almacenar el flujo de datos en búfer con y sin dominios de memoria

Figura 1: Almacenar el flujo de datos en búfer con dominios de memoria

La función de dominio de la memoria está destinada a tensores que son mayormente internos al controlador y que no necesitan acceso frecuente del lado del cliente. Algunos ejemplos de estos 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 usar grupos de memoria compartida.

Para admitir la función de dominio de la memoria, implementa IDevice::allocate para permitir que el framework solicite la asignación de búfer administrado por el controlador. Durante la asignación, el framework 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 una entrada o salida de un modelo preparado. Se pueden especificar varias funciones durante la asignación del búfer, y el búfer asignado solo se puede usar como esas funciones especificadas.

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

Se proporciona el token de IDevice::allocate 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 sea una de las funciones BufferRole proporcionadas durante la asignación y debe fallar la ejecución de inmediato si el uso es ilegal.

El objeto IBuffer se usa para copiar memoria explícita. En ciertas situaciones, el cliente del controlador debe inicializar el búfer administrado por este desde un grupo de memoria compartida o copiar el búfer a un grupo de memoria compartida. Estos son algunos ejemplos de casos de uso:

  • Inicialización del tensor de estado
  • Cómo almacenar en caché los resultados intermedios
  • Ejecución de resguardo en CPU

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

Durante la asignación del búfer, las dimensiones del búfer se pueden deducir de los operandos del modelo correspondientes de todas las funciones especificadas por BufferRole y de las dimensiones proporcionadas en BufferDesc. Con toda la información dimensional combinada, el búfer puede tener dimensiones o clasificaciones desconocidas. En ese caso, el búfer se encuentra en un estado flexible en el que las dimensiones se fijan cuando se usan como entrada del modelo y en estado dinámico cuando se usan como salida del modelo. Se puede usar el mismo búfer con diferentes formas de salidas en distintas ejecuciones, y el controlador debe controlar el cambio de tamaño del búfer de manera correcta.

El dominio de memoria es una función opcional. Un controlador puede determinar que no puede admitir una solicitud de asignación determinada por varios motivos. Por ejemplo:

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

Es posible que varios subprocesos diferentes lean desde el búfer administrado por el controlador de forma simultánea. El acceso al búfer de manera simultánea para escritura o lectura/escritura no está definido, pero no debe generar una falla en el servicio del controlador ni bloquear al emisor de forma indefinida. El controlador puede mostrar un error o dejar el contenido del búfer en un estado indeterminado.