Extensions de fournisseur

Les extensions du fournisseur Neural Networks API (NNAPI), introduites dans Android 10, sont des ensembles d'opérations et de types de données définis par le fournisseur. Sur les appareils exécutant NN HAL 1.2 ou version ultérieure, les pilotes peuvent fournir des opérations personnalisées accélérées par le matériel en prenant en charge les extensions de fournisseur correspondantes. Les extensions du fournisseur ne modifient pas le comportement des opérations existantes.

Les extensions de fournisseur offrent une alternative plus structurée aux opérations et types de données OEM, qui étaient obsolètes dans Android 10. Pour plus d'informations, consultez Opérations et types de données OEM .

Liste autorisée d'utilisation des extensions

Les extensions de fournisseur ne peuvent être utilisées que par des applications Android explicitement spécifiées et des binaires natifs sur les partitions /product , /vendor , /odm et /data . Les applications et les binaires natifs situés sur la partition /system ne peuvent pas utiliser les extensions du fournisseur.

Une liste des applications et binaires Android autorisés à utiliser les extensions du fournisseur NNAPI est stockée dans /vendor/etc/nnapi_extensions_app_allowlist . Chaque ligne du fichier contient une nouvelle entrée. Une entrée peut être un chemin binaire natif préfixé par une barre oblique (/), par exemple /data/foo , ou le nom d'un package d'application Android, par exemple com.foo.bar .

La liste blanche est appliquée à partir de la bibliothèque partagée d'exécution NNAPI. Cette bibliothèque protège contre une utilisation accidentelle mais pas contre un contournement délibéré par une application utilisant directement l'interface HAL du pilote NNAPI.

Définition de l'extension du fournisseur

Le fournisseur crée et gère un fichier d'en-tête avec la définition de l'extension. Un exemple complet de définition d'extension peut être trouvé dans example/fibonacci/FibonacciExtension.h .

Chaque extension doit avoir un nom unique commençant par le nom de domaine inversé du fournisseur.

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

Le nom agit comme un espace de noms pour les opérations et les types de données. Le NNAPI utilise ce nom pour faire la distinction entre les extensions de fournisseur.

Les opérations et les types de données sont déclarés d'une manière similaire à ceux 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,
};

Une opération d'extension peut utiliser n'importe quel type d'opérande, y compris les types d'opérandes sans extension et les types d'opérandes provenant d'autres extensions. Lors de l'utilisation d'un type d'opérande provenant d'une autre extension, le pilote doit prendre en charge l'autre extension.

Les extensions peuvent également déclarer des structures personnalisées pour accompagner les opérandes d'extension.

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

Utiliser des extensions dans les clients NNAPI

Le fichier runtime/include/NeuralNetworksExtensions.h (API C) fournit la prise en charge des extensions d'exécution. Cette section fournit une présentation de l'API C.

Pour vérifier si un appareil prend en charge une extension, utilisez ANeuralNetworksDevice_getExtensionSupport .

bool isExtensionSupported;
CHECK_EQ(ANeuralNetworksDevice_getExtensionSupport(device, EXAMPLE_EXTENSION_NAME,
                                                   &isExtensionSupported),
         ANEURALNETWORKS_NO_ERROR);
if (isExtensionSupported) {
    // The device supports the extension.
    ...
}

Pour créer un modèle avec un opérande d'extension, utilisez ANeuralNetworksModel_getExtensionOperandType pour obtenir le type d'opérande et appelez 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);

Vous pouvez éventuellement utiliser ANeuralNetworksModel_setOperandExtensionData pour associer des données supplémentaires à un opérande d'extension.

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

Pour créer un modèle avec une opération d'extension, utilisez ANeuralNetworksModel_getExtensionOperationType pour obtenir le type d'opération et appelez 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);

Ajouter la prise en charge des extensions à un pilote NNAPI

Les pilotes signalent les extensions prises en charge via la méthode IDevice::getSupportedExtensions . La liste renvoyée doit contenir une entrée décrivant chaque extension prise en charge.

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

Parmi les 32 bits utilisés pour identifier les types et les opérations, les bits Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX hauts sont le préfixe de l'extension et les bits Model::ExtensionTypeEncoding::LOW_BITS_TYPE représentent le type ou le fonctionnement de l'extension.

Lors de la gestion d'une opération ou d'un type d'opérande, le pilote doit vérifier le préfixe d'extension. Si le préfixe d’extension a une valeur différente de zéro, le type d’opération ou d’opérande est un type d’extension. Si la valeur est 0 , le type d’opération ou d’opérande n’est pas un type d’extension.

Pour mapper le préfixe à un nom d'extension, recherchez-le dans model.extensionNameToPrefix . Le mappage du préfixe au nom de l'extension est une correspondance bijective (bijection) pour un modèle donné. Différentes valeurs de préfixe peuvent correspondre au même nom d'extension dans différents modèles.

Le pilote doit valider les opérations d’extension et les types de données, car le runtime NNAPI ne peut pas valider des opérations d’extension et des types de données particuliers.

Les opérandes d'extension peuvent avoir des données associées dans operand.extraParams.extension , que le runtime traite comme un blob de données brutes de taille arbitraire.

Fonctionnement OEM et types de données

NNAPI dispose d'un fonctionnement OEM et de types de données OEM pour permettre aux fabricants de périphériques de fournir des fonctionnalités personnalisées spécifiques au pilote. Ces types d’opérations et de données ne sont utilisés que par les applications OEM. La sémantique du fonctionnement OEM et les types de données sont spécifiques à l'OEM et peuvent changer à tout moment. L'opération OEM et les types de données sont codés à l'aide de OperationType::OEM_OPERATION , OperandType::OEM et OperandType::TENSOR_OEM_BYTE .