Extensiones de proveedor

Las extensiones de proveedor de Neural Networks API (NNAPI), introducidas en Android 10, son colecciones de operaciones y tipos de datos definidos por el proveedor. En dispositivos que ejecutan NN HAL 1.2 o superior, los controladores pueden proporcionar operaciones personalizadas aceleradas por hardware al admitir las extensiones correspondientes del proveedor. Las extensiones de proveedores no modifican el comportamiento de las operaciones existentes.

Las extensiones de proveedor brindan una alternativa más estructurada a la operación OEM y los tipos de datos, que quedaron obsoletos en Android 10. Para obtener más información, consulte Operación OEM y tipos de datos .

Lista de permitidos de uso de extensiones

Las extensiones de proveedor solo pueden ser utilizadas por aplicaciones de Android y archivos binarios nativos especificados explícitamente en las particiones /product , /vendor , /odm y /data . Las aplicaciones y los archivos binarios nativos ubicados en la partición /system no pueden usar extensiones de proveedor.

Una lista de aplicaciones y archivos binarios de Android permitidos para usar extensiones de proveedores NNAPI se almacena en /vendor/etc/nnapi_extensions_app_allowlist . Cada línea del archivo contiene una nueva entrada. Una entrada puede ser una ruta binaria nativa con el prefijo de una barra diagonal (/), por ejemplo, /data/foo , o el nombre de un paquete de aplicación de Android, por ejemplo, com.foo.bar .

La lista de permitidos se aplica desde la biblioteca compartida del tiempo de ejecución de NNAPI. Esta biblioteca protege contra el uso accidental, pero no contra la elusión deliberada por parte de una aplicación que utiliza directamente la interfaz HAL del controlador NNAPI.

Definición de extensión de proveedor

El proveedor crea y mantiene un archivo de encabezado con la definición de extensión. Puede 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 operaciones y tipos de datos. La NNAPI utiliza este nombre para distinguir entre extensiones de proveedores.

Las operaciones y los tipos de datos se declaran de forma 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 utilizar cualquier tipo de operando, incluidos tipos de operandos que no son de extensión y tipos de operandos de otras extensiones. Cuando se utiliza 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;

Usar extensiones en clientes NNAPI

El archivo runtime/include/NeuralNetworksExtensions.h (C API) proporciona soporte de extensión de tiempo de ejecución. Esta sección proporciona una descripción general de la API de C.

Para comprobar si un dispositivo admite una extensión, utilice ANeuralNetworksDevice_getExtensionSupport .

bool isExtensionSupported;
CHECK_EQ(ANeuralNetworksDevice_getExtensionSupport(device, EXAMPLE_EXTENSION_NAME,
                                                   &isExtensionSupported),
         ANEURALNETWORKS_NO_ERROR);
if (isExtensionSupported) {
    // The device supports the extension.
    ...
}

Para construir un modelo con un operando de extensión, use ANeuralNetworksModel_getExtensionOperandType para obtener el tipo de operando y llame 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);

Opcionalmente, use 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 construir un modelo con una operación de extensión, use ANeuralNetworksModel_getExtensionOperationType para obtener el tipo de operación y llame 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);

Agregar soporte de extensión a un controlador NNAPI

Los controladores informan de las extensiones admitidas a través del método IDevice::getSupportedExtensions . La lista devuelta debe contener una entrada que describa cada extensión admitida.

Extension {
    .name = EXAMPLE_EXTENSION_NAME,
    .operandTypes = {
        {
            .type = EXAMPLE_SCALAR,
            .isTensor = false,
            .byteSize = 8,
        },
        {
            .type = EXAMPLE_TENSOR,
            .isTensor = true,
            .byteSize = 8,
        },
    },
}

De los 32 bits utilizados para identificar tipos y operaciones, los bits altos Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX son el prefijo de extensión y los bits bajos Model::ExtensionTypeEncoding::LOW_BITS_TYPE representan el tipo u operación de la extensión.

Al manejar una operación o tipo 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 u operando es un tipo de extensión. Si el valor es 0 , el tipo de operación u operando no es un tipo de extensión.

Para asignar el prefijo a un nombre de extensión, búsquelo en model.extensionNameToPrefix . La asignación del prefijo al nombre de la extensión es una correspondencia uno a uno (biyección) para un modelo determinado. Diferentes valores de prefijo pueden corresponder al mismo nombre de extensión en diferentes modelos.

El controlador debe validar las operaciones de extensión y los tipos de datos porque el tiempo de ejecución de NNAPI no puede validar operaciones de extensión y tipos de datos concretos.

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.

Operación OEM y tipos de datos

NNAPI tiene una operación OEM y tipos de datos OEM para permitir que los fabricantes de dispositivos proporcionen una funcionalidad personalizada y específica del controlador. Estos tipos de operaciones y datos solo los utilizan las aplicaciones OEM. La semántica del funcionamiento del OEM y los tipos de datos son específicos del OEM y pueden cambiar en cualquier momento. La operación OEM y los tipos de datos se codifican mediante OperationType::OEM_OPERATION , OperandType::OEM y OperandType::TENSOR_OEM_BYTE .