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 los 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 proveedor 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 explícitamente especificadas y binarios nativos en las particiones /product , /vendor , /odm y /data . Las aplicaciones y los binarios nativos ubicados en la partición /system no pueden usar extensiones de proveedores.

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 inclinada (/), 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 en 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 usa 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 la 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 usa este nombre para distinguir entre extensiones de proveedores.

Las operaciones y los tipos de datos se declaran de forma similar a las 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 utiliza un tipo de operando de otra extensión, el controlador debe ser compatible con 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;

Uso de extensiones en clientes NNAPI

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

Para verificar si un dispositivo admite una extensión, use 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 sobre 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 que se utilizan para identificar tipos y operaciones, el Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX bits es el prefijo de la extensión y el Model::ExtensionTypeEncoding::LOW_BITS_TYPE bits representa el tipo o la operación de la extensión.

Al manejar una operación o un tipo de operando, el controlador debe verificar el prefijo de extensión. Si el prefijo de extensión tiene un valor distinto de cero, la operación o el tipo 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úsquelo en model.extensionNameToPrefix . El mapeo del prefijo al nombre de la extensión es una correspondencia uno a uno (biyección) para un modelo dado. Diferentes valores de prefijo pueden corresponder al mismo nombre de extensión en diferentes modelos.

El controlador debe validar operaciones de extensión y tipos de datos porque el tiempo de ejecución de NNAPI no puede validar operaciones de extensión y tipos de datos 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.

Operación OEM y tipos de datos

NNAPI tiene una operación de OEM y tipos de datos de 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 .