Estensioni del fornitore

Le estensioni del fornitore dell'API Neural Networks (NNAPI), introdotte in Android 10, sono raccolte di operazioni e tipi di dati definiti dal fornitore. Sui dispositivi che eseguono NN HAL 1.2 o versione successiva, i driver possono fornire operazioni personalizzate con accelerazione hardware supportando le estensioni del fornitore corrispondente. Le estensioni del fornitore non modificano il comportamento delle operazioni esistenti.

Le estensioni del fornitore forniscono un'alternativa più strutturata alle operazioni e ai tipi di dati OEM, che erano deprecati in Android 10. Per ulteriori informazioni, consulta Operazioni OEM e tipi di dati .

Lista consentita per l'utilizzo delle estensioni

Le estensioni del fornitore possono essere utilizzate solo da app Android e file binari nativi specificati in modo esplicito nelle partizioni /product , /vendor , /odm e /data . Le app e i file binari nativi che si trovano nella partizione /system non possono utilizzare le estensioni del fornitore.

Un elenco di app e file binari Android autorizzati a utilizzare le estensioni del fornitore NNAPI è archiviato in /vendor/etc/nnapi_extensions_app_allowlist . Ogni riga del file contiene una nuova voce. Una voce può essere un percorso binario nativo preceduto da una barra (/), ad esempio /data/foo o il nome di un pacchetto di app Android, ad esempio com.foo.bar .

La lista consentita viene applicata dalla libreria condivisa del runtime NNAPI. Questa libreria protegge dall'utilizzo accidentale ma non dall'elusione intenzionale da parte di un'app che utilizza direttamente l'interfaccia HAL del driver NNAPI.

Definizione dell'estensione del fornitore

Il fornitore crea e mantiene un file di intestazione con la definizione dell'estensione. Un esempio completo di definizione di estensione può essere trovato in example/fibonacci/FibonacciExtension.h .

Ciascuna estensione deve avere un nome univoco che inizia con il nome di dominio inverso del fornitore.

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

Il nome funge da spazio dei nomi per operazioni e tipi di dati. La NNAPI utilizza questo nome per distinguere tra le estensioni del fornitore.

Le operazioni e i tipi di dati sono dichiarati in modo simile a quelli in 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,
};

Un'operazione di estensione può utilizzare qualsiasi tipo di operando, inclusi i tipi di operando non di estensione e i tipi di operando di altre estensioni. Quando si utilizza un tipo di operando da un'altra estensione, il driver deve supportare l'altra estensione.

Le estensioni possono anche dichiarare strutture personalizzate per accompagnare gli operandi dell'estensione.

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

Utilizza le estensioni nei client NNAPI

Il file runtime/include/NeuralNetworksExtensions.h (API C) fornisce il supporto dell'estensione runtime. Questa sezione fornisce una panoramica dell'API C.

Per verificare se un dispositivo supporta un'estensione, utilizzare ANeuralNetworksDevice_getExtensionSupport .

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

Per creare un modello con un operando di estensione, utilizzare ANeuralNetworksModel_getExtensionOperandType per ottenere il tipo di operando e chiamare 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);

Facoltativamente, utilizzare ANeuralNetworksModel_setOperandExtensionData per associare dati aggiuntivi a un operando di estensione.

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

Per creare un modello con un'operazione di estensione, utilizzare ANeuralNetworksModel_getExtensionOperationType per ottenere il tipo di operazione e chiamare 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);

Aggiungi il supporto dell'estensione a un driver NNAPI

I driver segnalano le estensioni supportate tramite il metodo IDevice::getSupportedExtensions . L'elenco restituito deve contenere una voce che descrive ciascuna estensione supportata.

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

Dei 32 bit utilizzati per identificare tipi e operazioni, i bit alti Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX sono il prefisso dell'estensione e i bit bassi Model::ExtensionTypeEncoding::LOW_BITS_TYPE rappresentano il tipo o l'operazione dell'estensione.

Quando si gestisce un'operazione o un tipo di operando, il driver deve controllare il prefisso dell'estensione. Se il prefisso dell'estensione ha un valore diverso da zero, il tipo di operazione o operando è un tipo di estensione. Se il valore è 0 , il tipo di operazione o operando non è un tipo di estensione.

Per mappare il prefisso al nome di un'estensione, cercalo in model.extensionNameToPrefix . La mappatura dal prefisso al nome dell'estensione è una corrispondenza uno a uno (biiezione) per un dato modello. Valori di prefisso diversi potrebbero corrispondere allo stesso nome di estensione in modelli diversi.

Il driver deve convalidare le operazioni di estensione e i tipi di dati perché il runtime NNAPI non può convalidare particolari operazioni di estensione e tipi di dati.

Agli operandi di estensione possono essere associati dati in operand.extraParams.extension , che il runtime considera come un BLOB di dati non elaborati di dimensioni arbitrarie.

Operazioni OEM e tipi di dati

NNAPI dispone di un'operazione OEM e di tipi di dati OEM per consentire ai produttori di dispositivi di fornire funzionalità personalizzate e specifiche del driver. Queste operazioni e tipi di dati vengono utilizzati solo dalle applicazioni OEM. La semantica delle operazioni OEM e i tipi di dati sono specifici dell'OEM e possono cambiare in qualsiasi momento. L'operazione OEM e i tipi di dati vengono codificati utilizzando OperationType::OEM_OPERATION , OperandType::OEM e OperandType::TENSOR_OEM_BYTE .