Extensões de fornecedores

As extensões de fornecedor da Neural Networks API (NNAPI), introduzidas no Android 10, são coleções de operações e tipos de dados definidos pelo fornecedor. Em dispositivos que executam NN HAL 1.2 ou superior, os drivers podem fornecer operações personalizadas aceleradas por hardware ao oferecer suporte às extensões correspondentes do fornecedor. As extensões do fornecedor não modificam o comportamento das operações existentes.

As extensões de fornecedor fornecem uma alternativa mais estruturada à operação OEM e aos tipos de dados, que foram preteridos no Android 10. Para obter mais informações, consulte Operação OEM e tipos de dados .

Lista de permissões de uso de extensões

As extensões de fornecedor só podem ser usadas por aplicativos Android explicitamente especificados e binários nativos nas partições /product , /vendor , /odm e /data . Aplicativos e binários nativos localizados na partição /system não podem usar extensões de fornecedor.

Uma lista de aplicativos e binários Android com permissão para usar extensões de fornecedor NNAPI é armazenada em /vendor/etc/nnapi_extensions_app_allowlist . Cada linha do arquivo contém uma nova entrada. Uma entrada pode ser um caminho binário nativo prefixado com uma barra (/), por exemplo, /data/foo , ou um nome de um pacote de aplicativo Android, por exemplo, com.foo.bar .

A lista de permissões é imposta a partir da biblioteca compartilhada de tempo de execução NNAPI. Essa biblioteca protege contra uso acidental, mas não contra evasão deliberada por um aplicativo usando diretamente a interface HAL do driver NNAPI.

Definição de extensão do fornecedor

O fornecedor cria e mantém um arquivo de cabeçalho com a definição de extensão. Um exemplo completo de uma definição de extensão pode ser encontrado em example/fibonacci/FibonacciExtension.h .

Cada extensão deve ter um nome exclusivo que começa com o nome de domínio reverso do fornecedor.

const char EXAMPLE_EXTENSION_NAME[] = "com.example.my_extension";

O nome atua como um namespace para operações e tipos de dados. O NNAPI usa esse nome para distinguir entre as extensões do fornecedor.

As operações e os tipos de dados são declarados de maneira semelhante a 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,
};

Uma operação de extensão pode usar qualquer tipo de operando, incluindo tipos de operando sem extensão e tipos de operando de outras extensões. Ao usar um tipo de operando de outra extensão, o driver deve suportar a outra extensão.

As extensões também podem declarar estruturas personalizadas para acompanhar os operandos de extensão.

/**
 * Quantization parameters for {@link EXAMPLE_TENSOR}.
 */
typedef struct ExampleTensorParams {
    double scale;
    int64_t zeroPoint;
} ExampleTensorParams;

Usando extensões em clientes NNAPI

O arquivo runtime/include/NeuralNetworksExtensions.h (C API) fornece suporte à extensão de tempo de execução. Esta seção fornece uma visão geral da API C.

Para verificar se um dispositivo oferece suporte a uma extensão, 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 um modelo com um operando de extensão, use ANeuralNetworksModel_getExtensionOperandType para obter o tipo de operando e chame 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 associar dados adicionais a um operando de extensão.

ExampleTensorParams params{
        .scale = 0.5,
        .zeroPoint = 128,
};
CHECK_EQ(ANeuralNetworksModel_setOperandExtensionData(model, operandIndex, &params, sizeof(params)),
         ANEURALNETWORKS_NO_ERROR);

Para construir um modelo com uma operação de extensão, use ANeuralNetworksModel_getExtensionOperationType para obter o tipo de operação e chame 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);

Adicionando suporte de extensão a um driver NNAPI

Drivers relatam extensões suportadas por meio do método IDevice::getSupportedExtensions . A lista retornada deve conter uma entrada descrevendo cada extensão suportada.

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

Dos 32 bits usados ​​para identificar tipos e operações, os bits Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX são o prefixo da extensão e os bits Model::ExtensionTypeEncoding::LOW_BITS_TYPE representam o tipo ou operação da extensão.

Ao manipular uma operação ou tipo de operando, o driver deve verificar o prefixo do ramal. Se o prefixo de extensão tiver um valor diferente de zero, a operação ou o tipo de operando será um tipo de extensão. Se o valor for 0 , a operação ou tipo de operando não é um tipo de extensão.

Para mapear o prefixo para um nome de extensão, procure-o em model.extensionNameToPrefix . O mapeamento do prefixo para o nome da extensão é uma correspondência de um para um (bijeção) para um determinado modelo. Valores de prefixo diferentes podem corresponder ao mesmo nome de extensão em modelos diferentes.

O driver deve validar operações de extensão e tipos de dados porque o runtime NNAPI não pode validar operações de extensão e tipos de dados específicos.

Os operandos de extensão podem ter dados associados em operand.extraParams.extension , que o tempo de execução trata como um blob de dados brutos de tamanho arbitrário.

Operação OEM e tipos de dados

A NNAPI tem uma operação OEM e tipos de dados OEM para permitir que os fabricantes de dispositivos forneçam funcionalidades personalizadas específicas do driver. Esses tipos de operação e dados são usados ​​apenas por aplicativos OEM. A semântica da operação OEM e os tipos de dados são específicos do OEM e podem ser alterados a qualquer momento. A operação OEM e os tipos de dados são codificados usando OperationType::OEM_OPERATION , OperandType::OEM e OperandType::TENSOR_OEM_BYTE .