Extensions de fournisseur

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 version 1.2 ou 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 de fournisseur ne modifient pas le comportement des opérations existantes.

Les extensions de fournisseur fournissent une alternative plus structurée aux opérations et aux types de données OEM, qui ont été abandonnés dans Android 10. Pour en savoir plus, consultez la page Opérations et types de données OEM.

Liste d'autorisation d'utilisation des extensions

Les extensions de fournisseur ne peuvent être utilisées que par les applications Android et les 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 de fournisseur.

Une liste d'applications et de binaires Android autorisés à utiliser des 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, comme com.foo.bar.

La liste d'autorisation est appliquée à partir de la bibliothèque partagée de l'environnement d'exécution NNAPI. Cette bibliothèque protège contre l'utilisation accidentelle, mais pas contre le contournement délibéré par une application qui utilise 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. Vous trouverez un exemple complet de définition d'extension dans example/fibonacci/FibonacciExtension.h.

Chaque extension doit avoir un nom unique qui commence 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 de fournisseur.

Les opérations et les types de données sont déclarés d'une manière semblable à celles 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 d'autres extensions. Lorsque vous utilisez un type d'opérande 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) est compatible avec les extensions d'environnement 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 Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX élevés correspondent au préfixe d'extension, et les bits Model::ExtensionTypeEncoding::LOW_BITS_TYPE inférieurs représentent le type ou l'opération de l'extension.

Lors du traitement 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 non nulle, le type d'opération ou d'opérande est un type d'extension. Si la valeur est 0, cela signifie que l'opération ou l'opérande ne correspond 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 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 d'extension et les types de données, car l'environnement d'exécution NNAPI ne peut pas valider d'opérations d'extension et de types de données spécifiques.

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

Opération et types de données OEM

NNAPI dispose d'une opération OEM et de types de données OEM pour permettre aux fabricants d'appareils de fournir des fonctionnalités personnalisées spécifiques au pilote. Ces opérations et types 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 propre à chaque OEM et peut changer à tout moment. L'opération OEM et les types de données sont encodés à l'aide de OperationType::OEM_OPERATION, OperandType::OEM et OperandType::TENSOR_OEM_BYTE.