Extensões do fornecedor

As extensões de fornecedor da API Neural Networks (NNAPI), introduzidas no Android 10, são coleções de operações e tipos de dados definidos pelo fornecedor. Em dispositivos com a NN HAL 1.2 ou mais recente, os drivers podem fornecer operações personalizadas aceleradas por hardware, oferecendo suporte às extensões de fornecedor correspondentes. As extensões de fornecedor não modificam o comportamento das operações atuais.

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

Lista de permissões de uso de extensões

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

Uma lista de apps e binários do Android permitidos a usar extensões de fornecedor da 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 com prefixo de uma barra (/), por exemplo, /data/foo, ou um nome de um pacote de app Android, por exemplo, com.foo.bar.

A lista de permissões é aplicada na biblioteca compartilhada do ambiente de execução da NNAPI. Essa biblioteca protege contra o uso acidental, mas não contra a evasão deliberada por um app que usa diretamente a interface HAL do driver da NNAPI.

Definição da extensão do fornecedor

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

Cada extensão precisa ter um nome exclusivo que comece com o nome de domínio inverso 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 do fornecedor.

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

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 precisa oferecer suporte a ela.

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;

Usar extensões em clientes NNAPI

O arquivo runtime/include/NeuralNetworksExtensions.h (API C) oferece suporte à extensão de ambiente 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 criar um modelo com um operando de extensão, use ANeuralNetworksModel_getExtensionOperandType para acessar o tipo de operando e chamar 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 outros dados 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 criar um modelo com uma operação de extensão, use ANeuralNetworksModel_getExtensionOperationType para saber o tipo de operação e chamar 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 de NNAPI

Os drivers informam as extensões compatíveis pelo método IDevice::getSupportedExtensions. A lista retornada precisa conter uma entrada que descreva cada extensão compatível.

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 processar um tipo de operação ou operando, o driver precisa verificar o prefixo da extensão. 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, o tipo de operação ou de operando não é um tipo de extensão.

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

O driver precisa validar operações de extensão e tipos de dados porque o ambiente de execução da 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 ambiente de execução trata como um blob de dados brutos de tamanho arbitrário.

Operação do OEM e tipos de dados

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