Extensiones de proveedores

Las extensiones de proveedores 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 dispositivos que ejecutan NN HAL 1.2 o versiones posteriores, los controladores pueden proporcionar operaciones personalizadas aceleradas por hardware si admiten las extensiones del proveedor correspondientes. Las extensiones de proveedores no modifican el comportamiento de las operaciones existentes.

Las extensiones de proveedores ofrecen una alternativa más estructurada a la operación del OEM y que dejaron de estar disponibles en Android 10. Para obtener más información, consulta Operación de OEM y tipos de datos.

Lista de entidades permitidas para el uso de extensiones

Solo las apps para Android especificadas de forma explícita y los objetos binarios nativos de las particiones /product, /vendor, /odm y /data pueden usar las extensiones de proveedores. Las apps y los objetos binarios nativos ubicados en la partición /system no pueden usar el proveedor extensiones.

En /vendor/etc/nnapi_extensions_app_allowlist, se almacena una lista de apps y objetos binarios de Android que pueden usar extensiones de proveedores de NNAPI. Cada línea del archivo contiene una entrada nueva. Una entrada puede ser una ruta binaria nativa que tiene el prefijo una barra (/), por ejemplo, /data/foo, o el nombre de un paquete de apps para Android, para 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 brinda protección contra el uso accidental, pero no contra la elusión deliberada por una app directamente con la interfaz de HAL del controlador de NNAPI

Definición de extensión de proveedor

El proveedor crea y mantiene un archivo de encabezado con la definición de la extensión. R Puedes encontrar un ejemplo completo de la definición de una 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 espacio de nombres para las operaciones y los tipos de datos. La NNAPI usa para distinguir entre extensiones de proveedores.

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, incluido un operando sin extensión y los tipos de operando 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 a 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 runtime/include/NeuralNetworksExtensions.h (API de C) proporciona compatibilidad con extensiones de entorno de ejecución. En esta sección, se proporciona un descripción general de la API de C.

Para comprobar 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 crear un modelo con un operando de extensión, usa ANeuralNetworksModel_getExtensionOperandType para obtener el tipo de operando y llamar 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, &params, 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 NNAPI

Los controladores informan las extensiones compatibles a través del método IDevice::getSupportedExtensions. La lista que se muestra 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 controla una operación o un tipo de operando, el controlador debe verificar la extensión . Si el prefijo de la extensión tiene un valor distinto de cero, la operación o el operando es un tipo de extensión. Si el valor es 0, el tipo de operación o 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 uno a uno (biinyección) para un modelo determinado. Diferentes valores de prefijo pueden corresponder al el mismo nombre de extensión en distintos modelos.

El controlador debe validar las operaciones y los tipos de datos de la extensión, ya que el entorno de ejecución de NNAPI no puede validar operaciones y tipos de datos de extensión específicos.

Los operandos de extensión pueden tener datos asociados en operand.extraParams.extension, que el entorno de ejecución considera un BLOB de datos sin procesar de tamaño arbitrario.

Operación del OEM y tipos de datos

NNAPI tiene una operación y tipos de datos de OEM para permitir que los fabricantes de dispositivos proporcionen una funcionalidad personalizada y específica del controlador. Solo las aplicaciones de OEM usan estos tipos de operación y datos. Semántica de OEM las operaciones y los tipos de datos son específicos de OEM y pueden cambiar en cualquier momento. El OEM operación y los tipos de datos se codifican con OperationType::OEM_OPERATION, OperandType::OEM y OperandType::TENSOR_OEM_BYTE.