Estensioni del fornitore

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

Le estensioni dei fornitori offrono un'alternativa più strutturata ai tipi di dati e operazioni OEM, che sono stati ritirati in Android 10. Per maggiori informazioni, consulta la pagina relativa ai tipi di operazioni e di dati dell'OEM.

Lista consentita per l'utilizzo delle estensioni

Le estensioni del fornitore possono essere utilizzate solo da app per Android specificate esplicitamente e da programmi binari nativi nelle partizioni /product, /vendor, /odm e /data. Le app e i programmi binari nativi situati nella partizione /system non possono utilizzare le estensioni del fornitore.

Un elenco di app per Android e programmi binari autorizzati a usare le estensioni dei fornitori 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 dal prefisso 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 in modo forzato dalla libreria condivisa del runtime NNAPI. Questa libreria protegge dall'utilizzo accidentale, ma non dall'elusione intenzionale di un'app utilizzando direttamente l'interfaccia HAL del driver NNAPI.

Definizione dell'estensione del fornitore

Il fornitore crea e gestisce un file di intestazione con la definizione dell'estensione. Un esempio completo di definizione di un'estensione è disponibile in example/fibonacci/FibonacciExtension.h.

Ogni estensione deve avere un nome univoco che inizi 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 usa questo nome per distinguere le estensioni dei fornitori.

Le operazioni e i tipi di dati vengono dichiarati in modo simile a quello 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 usi un tipo di operando di un'altra estensione, il driver deve supportare l'altra estensione.

Le estensioni possono anche dichiarare strutture personalizzate da associare agli operandi delle estensioni.

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

Usa le estensioni nei client NNAPI

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

Per verificare se un dispositivo supporta un'estensione, utilizza 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, utilizza 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);

Se vuoi, utilizza 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, utilizza 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 delle estensioni a un driver NNAPI

I conducenti segnalano le estensioni supportate tramite il metodo IDevice::getSupportedExtensions. L'elenco restituito deve contenere una voce che descriva ogni 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, quelli a Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX elevati sono il prefisso dell'estensione, mentre quelli a bit Model::ExtensionTypeEncoding::LOW_BITS_TYPE più bassi rappresentano il tipo o l'operazione dell'estensione.

Quando 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 determinato modello. Valori di prefisso diversi possono corrispondere allo stesso nome di estensione in modelli diversi.

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

Gli operandi delle estensioni possono avere dati associati in operand.extraParams.extension, che il runtime considera come un blob di dati non elaborati di dimensione arbitraria.

Tipi di operazioni e dati dell'OEM

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