Anbietererweiterungen

Anbietererweiterungen der Neural Networks API (NNAPI), die in Android 10 eingeführt wurden, sind Sammlungen von vom Anbieter definierten Vorgängen und Datentypen. Auf Geräten mit NN HAL 1.2 oder höher können Treiber benutzerdefinierte hardwarebeschleunigte Vorgänge bereitstellen, indem sie entsprechende Anbietererweiterungen unterstützen. Anbietererweiterungen ändern das Verhalten vorhandener Vorgänge nicht.

Anbietererweiterungen bieten eine strukturiertere Alternative zu OEM-Betriebs- und Datentypen, die in Android 10 eingestellt wurden. Weitere Informationen finden Sie unter OEM-Betrieb und Datentypen.

Zulassungsliste für die Nutzung von Erweiterungen

Anbietererweiterungen 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 auf der Partition /system können keine Anbietererweiterungen verwenden.

In /vendor/etc/nnapi_extensions_app_allowlist wird eine Liste der Android-Apps und ‑Binärdateien gespeichert, die NNAPI-Anbietererweiterungen verwenden dürfen. Jede Zeile der Datei enthält einen neuen Eintrag. Ein Eintrag kann ein nativer Binärpfad sein, der mit einem Schrägstrich (/) beginnt, z. B. /data/foo, oder der Name eines Android-App-Pakets, z. B. com.foo.bar.

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

Definition der Anbietererweiterung

Der Anbieter erstellt und verwaltet eine Headerdatei mit der Erweiterungsdefinition. Ein vollständiges Beispiel für eine Erweiterungsdefinition finden Sie unter example/fibonacci/FibonacciExtension.h.

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

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

Der Name dient als Namespace für Vorgänge und Datentypen. Die NNAPI verwendet diesen Namen, um zwischen Anbietererweiterungen zu unterscheiden.

Operationen und Datentypen werden ähnlich 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,
};

Für einen Erweiterungsvorgang kann jeder Operandentyp verwendet werden, einschließlich Operandentypen, die nicht zu Erweiterungen gehören, und Operandentypen aus anderen Erweiterungen. Wenn Sie einen Operandentyp aus einer anderen Erweiterung verwenden, muss der Treiber die andere Erweiterung unterstützen.

Erweiterungen können auch benutzerdefinierte Strukturen für Erweiterungsoperanden deklarieren.

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

Erweiterungen in NNAPI-Clients verwenden

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

Mit ANeuralNetworksDevice_getExtensionSupport können Sie prüfen, ob ein Gerät eine Erweiterung unterstützt.

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

Wenn Sie ein Modell mit einem Erweiterungsoperanden erstellen möchten, 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 mit ANeuralNetworksModel_setOperandExtensionData zusätzliche Daten mit einem Erweiterungsoperanden verknüpfen.

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

Wenn Sie ein Modell mit einem Erweiterungsvorgang erstellen möchten, verwenden Sie ANeuralNetworksModel_getExtensionOperationType, um den Vorgangstyp 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);

Erweiterungsunterstützung für einen NNAPI-Treiber hinzufügen

Fahrer melden unterstützte Erweiterungen über die Methode IDevice::getSupportedExtensions. Die zurückgegebene Liste muss einen Eintrag für jede unterstützte Erweiterung enthalten.

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 Bits im höheren Bereich Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX das Prefix der Erweiterung und die Bits im niedrigeren Bereich Model::ExtensionTypeEncoding::LOW_BITS_TYPE stehen für den Typ oder Vorgang der Erweiterung.

Beim Umgang mit einem Vorgang oder Operandentyp muss der Treiber das Erweiterungspräfix prüfen. Wenn das Erweiterungspräfix einen Wert ungleich Null hat, ist der Vorgangs- oder Operandentyp ein Erweiterungstyp. Wenn der Wert 0 ist, ist der Vorgang oder Operand kein Erweiterungstyp.

Wenn Sie das Präfix einem Erweiterungsnamen zuordnen möchten, suchen Sie in model.extensionNameToPrefix danach. Die Zuordnung vom Präfix zum Erweiterungsnamen ist für ein bestimmtes Modell eine Eins-zu-Eins-Beziehung (Bijektion). Unterschiedliche Präfixwerte können demselben Erweiterungsnamen in verschiedenen Modellen entsprechen.

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

Erweiterungsoperanden können mit Daten in operand.extraParams.extension verknüpft sein, die von der Laufzeit als Rohdaten-Blob beliebiger Größe behandelt werden.

OEM-Vorgang und Datentypen

NNAPI bietet OEM-Vorgänge und OEM-Datentypen, damit Gerätehersteller benutzerdefinierte, treiberspezifische Funktionen bereitstellen können. Diese Vorgangs- und Datentypen werden nur von OEM-Anwendungen verwendet. Die Semantik der OEM-Funktionen und -Datentypen ist OEM-spezifisch und kann sich jederzeit ändern. Die OEM-Vorgänge und Datentypen werden mit OperationType::OEM_OPERATION, OperandType::OEM und OperandType::TENSOR_OEM_BYTE codiert.