Anbietererweiterungen

Die in Android 10 eingeführten Anbietererweiterungen für die Neural Networks API (NNAPI) sind Sammlungen herstellerdefinierter Vorgänge und Datentypen. Auf Geräten mit NN HAL 1.2 oder höher können Treiber benutzerdefinierte hardwarebeschleunigte Vorgänge bereitstellen, indem sie entsprechende Herstellererweiterungen unterstützen. Anbietererweiterungen ändern nicht das Verhalten vorhandener Vorgänge.

Anbietererweiterungen bieten eine strukturiertere Alternative zu OEM-Vorgängen und Datentypen, die in Android 10 veraltet waren. Weitere Informationen finden Sie unter OEM-Vorgänge und Datentypen .

Zulassungsliste für die Nutzung von Erweiterungen

Herstellererweiterungen können nur von explizit angegebenen Android-Apps und nativen Binärdateien auf den Partitionen /product , /vendor , /odm und /data verwendet werden. Apps und native Binärdateien, die sich auf der /system Partition befinden, können keine Herstellererweiterungen verwenden.

Eine Liste der Android-Apps und Binärdateien, die NNAPI-Anbietererweiterungen verwenden dürfen, ist in /vendor/etc/nnapi_extensions_app_allowlist gespeichert. Jede Zeile der Datei enthält einen neuen Eintrag. Ein Eintrag kann ein nativer Binärpfad sein, dem ein Schrägstrich (/) vorangestellt ist, zum Beispiel /data/foo , oder ein Name eines Android-App-Pakets, zum Beispiel com.foo.bar .

Die Zulassungsliste wird von der gemeinsam genutzten NNAPI-Laufzeitbibliothek erzwungen. Diese Bibliothek schützt vor versehentlicher Nutzung, jedoch nicht vor absichtlicher Umgehung durch eine App, die direkt die HAL-Schnittstelle des NNAPI-Treibers verwendet.

Definition der Anbietererweiterung

Der Anbieter erstellt und verwaltet eine Header-Datei mit der Erweiterungsdefinition. Ein vollständiges Beispiel einer Erweiterungsdefinition finden Sie in example/fibonacci/FibonacciExtension.h .

Jede Erweiterung muss einen eindeutigen Namen haben, der mit dem umgekehrten Domänennamen des Anbieters beginnt.

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

Der Name fungiert als Namensraum für Operationen und Datentypen. Die NNAPI verwendet diesen Namen, um zwischen Anbietererweiterungen zu unterscheiden.

Operationen und Datentypen werden auf ähnliche Weise wie in runtime/include/NeuralNetworks.h deklariert.

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,
};

Eine Erweiterungsoperation kann jeden Operandentyp verwenden, einschließlich Nichterweiterungsoperandentypen und Operandentypen anderer Erweiterungen. Bei Verwendung eines Operandentyps einer anderen Erweiterung muss der Treiber die andere Erweiterung unterstützen.

Erweiterungen können auch benutzerdefinierte Strukturen deklarieren, um Erweiterungsoperanden zu begleiten.

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

Verwenden Sie Erweiterungen in NNAPI-Clients

Die Datei runtime/include/NeuralNetworksExtensions.h (C-API) bietet Unterstützung für Laufzeiterweiterungen. Dieser Abschnitt bietet einen Überblick über die C-API.

Um zu überprüfen, ob ein Gerät eine Erweiterung unterstützt, verwenden Sie ANeuralNetworksDevice_getExtensionSupport .

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

Um ein Modell mit einem Erweiterungsoperanden zu erstellen, verwenden Sie ANeuralNetworksModel_getExtensionOperandType , um den Operandentyp abzurufen, und rufen Sie ANeuralNetworksModel_addOperand auf.

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);

Optional können Sie ANeuralNetworksModel_setOperandExtensionData verwenden, um einem Erweiterungsoperanden zusätzliche Daten zuzuordnen.

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

Um ein Modell mit einer Erweiterungsoperation zu erstellen, verwenden Sie ANeuralNetworksModel_getExtensionOperationType , um den Operationstyp abzurufen, und rufen Sie ANeuralNetworksModel_addOperation auf.

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);

Fügen Sie einem NNAPI-Treiber Erweiterungsunterstützung hinzu

Treiber melden unterstützte Erweiterungen über die IDevice::getSupportedExtensions Methode. Die zurückgegebene Liste muss einen Eintrag enthalten, der jede unterstützte Erweiterung beschreibt.

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

Von den 32 Bits, die zum Identifizieren von Typen und Vorgängen verwendet werden, sind die hohen Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX Bits das Erweiterungspräfix und die niedrigen Model::ExtensionTypeEncoding::LOW_BITS_TYPE Bits stellen den Typ oder die Operation der Erweiterung dar.

Bei der Verarbeitung einer Operation oder eines Operandentyps muss der Treiber das Erweiterungspräfix überprüfen. Wenn das Erweiterungspräfix einen Wert ungleich Null hat, handelt es sich bei der Operation oder dem Operandentyp um einen Erweiterungstyp. Wenn der Wert 0 ist, handelt es sich bei der Operation oder dem Operandentyp nicht um einen Erweiterungstyp.

Um das Präfix einem Erweiterungsnamen zuzuordnen, suchen Sie in model.extensionNameToPrefix nach. Die Zuordnung vom Präfix zum Erweiterungsnamen ist eine Eins-zu-eins-Entsprechung (Bijektion) für ein bestimmtes Modell. Unterschiedliche Präfixwerte können in verschiedenen Modellen demselben Erweiterungsnamen entsprechen.

Der Treiber muss Erweiterungsvorgänge und Datentypen validieren, da die NNAPI-Laufzeit bestimmte Erweiterungsvorgänge und Datentypen nicht validieren kann.

Erweiterungsoperanden können zugeordnete Daten in operand.extraParams.extension haben, die von der Laufzeit als Rohdaten-Blob beliebiger Größe behandelt werden.

OEM-Betrieb und Datentypen

NNAPI verfügt über einen OEM-Vorgang und OEM-Datentypen, um Geräteherstellern die Bereitstellung benutzerdefinierter, treiberspezifischer Funktionen zu ermöglichen. Diese Operations- und Datentypen werden nur von OEM-Anwendungen verwendet. Die Semantik des OEM-Betriebs und der Datentypen ist OEM-spezifisch und kann sich jederzeit ändern. Die OEM-Operation und Datentypen werden mit OperationType::OEM_OPERATION , OperandType::OEM und OperandType::TENSOR_OEM_BYTE codiert.