Extensões de fornecedores

As extensões de fornecedores 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, suportando as 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ção e tipos de dados OEM, que foram obsoletos no Android 10. Para obter mais informações, consulte Operação e tipos de dados OEM .

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 fornecedor.

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

A lista de permissões é aplicada a partir da biblioteca compartilhada de tempo de execução NNAPI. Esta biblioteca protege contra o uso acidental, mas não contra a 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 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 as extensões do fornecedor.

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 operando sem extensão e tipos de operando de outras extensões. Ao utilizar um tipo de operando de outro ramal, o driver deve suportar o outro ramal.

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 (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 suporta 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

Os drivers relatam as extensões com suporte 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 a operação da extensão.

Ao tratar uma operação ou tipo de operando, o driver deve verificar o prefixo do ramal. Se o prefixo da extensão tiver um valor diferente de zero, a operação ou o tipo de operando é 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 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 tempo de execução NNAPI não pode validar operações de extensão e tipos de dados específicos.

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 funcionalidade personalizada e específica 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 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 .