Extensões de fornecedores

As extensões de fornecedor da API de redes neurais (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, oferecendo suporte às extensões correspondentes do fornecedor. As extensões do fornecedor não modificam o comportamento das operações existentes.

As extensões do fornecedor fornecem uma alternativa mais estruturada para operações e tipos de dados OEM, que foram descontinuados 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 do 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 fornecedores.

Uma lista de aplicativos Android e binários com permissão para usar extensões de fornecedores NNAPI está 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 o nome de um pacote de aplicativo Android, por exemplo, com.foo.bar .

A lista de permissões é aplicada na biblioteca compartilhada do tempo de execução NNAPI. Esta 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 definição de extensão pode ser encontrado em example/fibonacci/FibonacciExtension.h .

Cada extensão deve ter um nome exclusivo que comece 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. A NNAPI usa esse nome para distinguir entre extensões de fornecedores.

As operações e os tipos de dados são declarados de maneira semelhante àquelas em 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 operandos sem extensão e tipos de operandos de outras extensões. Ao utilizar um tipo de operando de outra extensão, o driver deverá 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;

Use extensões em clientes NNAPI

O arquivo runtime/include/NeuralNetworksExtensions.h (API C) 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);

Adicionar suporte de extensão a um driver NNAPI

Os 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 altos são o prefixo da extensão e os bits Model::ExtensionTypeEncoding::LOW_BITS_TYPE baixos representam o tipo ou operação da extensão.

Ao tratar uma operação ou tipo de operando, o driver deve verificar o prefixo da extensão. Se o prefixo de extensão tiver um valor diferente de zero, o tipo de operação ou operando será um tipo de extensão. Se o valor for 0 , o tipo de operação ou 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 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

NNAPI tem uma operação OEM e tipos de dados OEM para permitir que os fabricantes de dispositivos forneçam funcionalidades personalizadas e específicas do driver. Esses tipos de operação e dados são usados ​​apenas por aplicativos OEM. A semântica da operação do OEM e os tipos de dados são específicos do OEM e podem mudar 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 .