Extensions de fournisseurs

Les extensions de fournisseur de l'API Neural Networks (NNAPI), introduites dans Android 10, sont des collections 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 avec accélération matérielle en prenant en charge les extensions de fournisseur correspondantes. Les extensions de fournisseur ne modifient pas le comportement des opérations existantes.

Les extensions de fournisseur offrent une alternative plus structurée aux types d'opérations et de données OEM, qui ont été abandonnés dans Android 10. Pour en savoir plus, consultez Fonctionnement et types de données des OEM.

Liste d'autorisation d'utilisation des extensions

Les extensions du fournisseur ne peuvent être utilisées que par des applications Android et des binaires natifs spécifiés explicitement 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 des binaires Android autorisés à utiliser les extensions de 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écédé d'une barre oblique (/), par exemple /data/foo, ou le nom d'un package d'application Android, par exemple com.foo.bar.

La liste d'autorisation 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 est disponible 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 sert d'espace de noms pour les opérations et les types de données. La NNAPI utilise ce nom pour distinguer les extensions du fournisseur.

Les opérations et les types de données sont déclarés de manière semblable à 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 non d'extension et les types d'opérandes provenant d'autres extensions. Lorsque vous utilisez un type d'opérande provenant d'une autre extension, le pilote doit être compatible avec 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 une assistance pour les extensions d'exécution. Cette section présente l'API C.

Pour vérifier si un appareil est compatible avec 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 également 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 compatibles via la méthode IDevice::getSupportedExtensions. La liste renvoyée doit contenir une entrée décrivant chaque extension compatible.

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

Sur les 32 bits utilisés pour identifier les types et les opérations, les bits de poids fort Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX correspondent au préfixe de l'extension, et les bits de poids faible Model::ExtensionTypeEncoding::LOW_BITS_TYPE représentent le type ou l'opération de l'extension.

Lors de la gestion d'un type d'opération ou 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 associer 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 un-à-un (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 et les types de données d'extension, car le runtime NNAPI ne peut pas valider les opérations et les types de données d'extension spécifiques.

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

Fonctionnement et types de données de l'OEM

NNAPI dispose d'une opération et de types de données OEM pour permettre aux fabricants d'appareils de fournir des fonctionnalités personnalisées spécifiques aux pilotes. Ces types d'opérations et de données ne sont utilisés que par les applications OEM. La sémantique des opérations et des types de données OEM est spécifique aux OEM et peut changer à tout moment. Les types d'opération et de données OEM sont encodés à l'aide de OperationType::OEM_OPERATION, OperandType::OEM et OperandType::TENSOR_OEM_BYTE.