Extensions de fournisseur

Les extensions du 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 la prise en charge des extensions des fournisseurs correspondants. Les extensions de fournisseur ne modifient pas le comportement des opérations existantes.

Les extensions du fournisseur constituent 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 Opération 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 applications Android explicitement spécifiées 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 d'applications et de binaires Android autorisés à utiliser des extensions de fournisseurs 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 l'utilisation accidentelle, mais pas contre le contournement délibéré une application directement à l'aide de 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. A un exemple complet de définition d'extension se trouve 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. 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 similaire à 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 autres que les extensions et les types d'opérandes d'autres extensions. Lorsque vous utilisez un type d'opérande 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'exécution. Cette section fournit de 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 une 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 appeler 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 supérieurs Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX correspondent au préfixe de l'extension, et les bits inférieurs Model::ExtensionTypeEncoding::LOW_BITS_TYPE 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 l'extension . Si le préfixe d'extension a une valeur non nulle, l'opération ou l'opérande type 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. La mise en correspondance du préfixe au nom de l'extension est une correspondance individuelle (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 l'environnement d'exécution NNAPI ne peut pas valider certaines opérations et types de données d'extension.

Les opérandes d'extension peuvent avoir des données associé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 et les types de données ne sont utilisés que par les applications OEM. La sémantique de l'opération OEM et des types de données est spécifique à l'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.