Las extensiones del proveedor de la API de Neural Networks (NNAPI), que se introdujeron en Android 10, son colecciones de operaciones y tipos de datos definidos por el proveedor. En los dispositivos que ejecutan NN HAL 1.2 o versiones posteriores, los controladores pueden proporcionar operaciones personalizadas aceleradas por hardware admitiendo las extensiones del proveedor correspondientes. Las extensiones del proveedor no modifican el comportamiento de las operaciones existentes.
Las extensiones del proveedor proporcionan una alternativa más estructurada a los tipos de datos y operaciones del OEM, que dejaron de estar disponibles en Android 10. Para obtener más información, consulta Tipos de datos y operaciones del OEM.
Lista de entidades permitidas para el uso de extensiones
Las extensiones del proveedor solo pueden usarse en apps para Android y archivos binarios nativos especificados de forma explícita en las particiones /product
, /vendor
, /odm
y /data
.
Las apps y los archivos binarios nativos ubicados en la partición /system
no pueden usar extensiones del proveedor.
En /vendor/etc/nnapi_extensions_app_allowlist
, se almacena una lista de apps y archivos binarios para Android que tienen permiso para usar extensiones del proveedor de NNAPI. Cada línea del archivo contiene una entrada nueva. Una entrada puede ser una ruta de acceso binaria nativa que tenga un prefijo con una barra (/) (por ejemplo, /data/foo
) o el nombre de un paquete de una app para Android (por ejemplo, com.foo.bar
).
La lista de entidades permitidas se aplica desde la biblioteca compartida del tiempo de ejecución de la NNAPI. Esta biblioteca protege contra el uso accidental, pero no contra la elusión deliberada por parte de una app que usa directamente la interfaz HAL del controlador de la NNAPI.
Definición de la extensión del proveedor
El proveedor crea y mantiene un archivo de encabezado con la definición de la extensión. Puedes encontrar un ejemplo completo de una definición de extensión en example/fibonacci/FibonacciExtension.h
.
Cada extensión debe tener un nombre único que comience con el nombre de dominio inverso del proveedor.
const char EXAMPLE_EXTENSION_NAME[] = "com.example.my_extension";
El nombre actúa como un espacio de nombres para las operaciones y los tipos de datos. La NNAPI usa este nombre para distinguir entre las extensiones del proveedor.
Las operaciones y los tipos de datos se declaran de manera similar a los de runtime/include/NeuralNetworks.h
.
enum {
/**
* A custom scalar type.
*/
EXAMPLE_SCALAR = 0,
/**
* A custom tensor type.
*
* Attached to this tensor is {@link ExampleTensorParams}.
*/
EXAMPLE_TENSOR = 1,
};
enum {
/**
* Computes example function.
*
* Inputs:
* * 0: A scalar of {@link EXAMPLE_SCALAR}.
*
* Outputs:
* * 0: A tensor of {@link EXAMPLE_TENSOR}.
*/
EXAMPLE_FUNCTION = 0,
};
Una operación de extensión puede usar cualquier tipo de operando, incluidos los tipos de operandos que no son de extensión y los tipos de operandos de otras extensiones. Cuando se usa un tipo de operando de otra extensión, el controlador debe admitir la otra extensión.
Las extensiones también pueden declarar estructuras personalizadas para acompañar los operandos de extensión.
/**
* Quantization parameters for {@link EXAMPLE_TENSOR}.
*/
typedef struct ExampleTensorParams {
double scale;
int64_t zeroPoint;
} ExampleTensorParams;
Cómo usar extensiones en clientes de NNAPI
El archivo de runtime/include/NeuralNetworksExtensions.h
(API de C) proporciona compatibilidad con la extensión del tiempo de ejecución. En esta sección, se proporciona una descripción general de la API de C.
Para verificar si un dispositivo admite una extensión, usa ANeuralNetworksDevice_getExtensionSupport
.
bool isExtensionSupported;
CHECK_EQ(ANeuralNetworksDevice_getExtensionSupport(device, EXAMPLE_EXTENSION_NAME,
&isExtensionSupported),
ANEURALNETWORKS_NO_ERROR);
if (isExtensionSupported) {
// The device supports the extension.
...
}
Para compilar un modelo con un operando de extensión, usa ANeuralNetworksModel_getExtensionOperandType
para obtener el tipo de operando y llama a ANeuralNetworksModel_addOperand
.
int32_t type;
CHECK_EQ(ANeuralNetworksModel_getExtensionOperandType(model, EXAMPLE_EXTENSION_NAME, EXAMPLE_TENSOR, &type),
ANEURALNETWORKS_NO_ERROR);
ANeuralNetworksOperandType operandType{
.type = type,
.dimensionCount = dimensionCount,
.dimensions = dimensions,
};
CHECK_EQ(ANeuralNetworksModel_addOperand(model, &operandType), ANEURALNETWORKS_NO_ERROR);
De manera opcional, usa ANeuralNetworksModel_setOperandExtensionData
para asociar datos adicionales con un operando de extensión.
ExampleTensorParams params{
.scale = 0.5,
.zeroPoint = 128,
};
CHECK_EQ(ANeuralNetworksModel_setOperandExtensionData(model, operandIndex, ¶ms, sizeof(params)),
ANEURALNETWORKS_NO_ERROR);
Para compilar un modelo con una operación de extensión, usa ANeuralNetworksModel_getExtensionOperationType
para obtener el tipo de operación y llama a ANeuralNetworksModel_addOperation
.
ANeuralNetworksOperationType type;
CHECK_EQ(ANeuralNetworksModel_getExtensionOperationType(model, EXAMPLE_EXTENSION_NAME, EXAMPLE_FUNCTION,
&type),
ANEURALNETWORKS_NO_ERROR);
CHECK_EQ(ANeuralNetworksModel_addOperation(model, type, inputCount, inputs, outputCount, outputs),
ANEURALNETWORKS_NO_ERROR);
Cómo agregar compatibilidad con extensiones a un controlador de la NNAPI
Los drivers informan las extensiones compatibles a través del método IDevice::getSupportedExtensions
. La lista devuelta debe contener una entrada que describa cada extensión compatible.
Extension {
.name = EXAMPLE_EXTENSION_NAME,
.operandTypes = {
{
.type = EXAMPLE_SCALAR,
.isTensor = false,
.byteSize = 8,
},
{
.type = EXAMPLE_TENSOR,
.isTensor = true,
.byteSize = 8,
},
},
}
De los 32 bits que se usan para identificar tipos y operaciones, los bits altos Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX
son el prefijo de la extensión, y los bits bajos Model::ExtensionTypeEncoding::LOW_BITS_TYPE
representan el tipo o la operación de la extensión.
Cuando se controla un tipo de operación o de operando, el controlador debe verificar el prefijo de extensión. Si el prefijo de extensión tiene un valor distinto de cero, el tipo de operación o de operando es un tipo de extensión. Si el valor es 0
, el tipo de operación o de operando no es un tipo de extensión.
Para asignar el prefijo a un nombre de extensión, búscalo en model.extensionNameToPrefix
.
La asignación del prefijo al nombre de la extensión es una correspondencia biunívoca (biyección) para un modelo determinado. Es posible que diferentes valores de prefijo correspondan al mismo nombre de extensión en diferentes modelos.
El controlador debe validar las operaciones y los tipos de datos de la extensión, ya que el tiempo de ejecución de la NNAPI no puede validar operaciones y tipos de datos de extensión particulares.
Los operandos de extensión pueden tener datos asociados en operand.extraParams.extension
, que el tiempo de ejecución trata como un BLOB de datos sin procesar de tamaño arbitrario.
Tipos de datos y operaciones del OEM
La NNAPI tiene una operación y tipos de datos del OEM para permitir que los fabricantes de dispositivos proporcionen funcionalidad personalizada específica del controlador. Estos tipos de operación y datos solo se usan en las aplicaciones de OEM. La semántica de la operación y los tipos de datos del OEM son específicos del OEM y pueden cambiar en cualquier momento. Los tipos de datos y las operaciones del OEM se codifican con OperationType::OEM_OPERATION
, OperandType::OEM
y OperandType::TENSOR_OEM_BYTE
.