Anbietererweiterungen

Die in Android 10 eingeführten NNAPI-Anbietererweiterungen (Neural Networks API) sind Sammlungen von anbieterdefinierten 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 bestehender 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 in den Partitionen /product, /vendor, /odm und /data verwendet werden. Anwendungen und native Binärdateien, die sich in der Partition /system befinden, können keine Anbietererweiterungen 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 mit vorangestelltem Schrägstrich (/) sein, 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 versehentlicher Nutzung, aber nicht vor absichtlicher Umgehung durch eine Anwendung direkt über die HAL-Schnittstelle des NNAPI-Treibers.

Definition der Anbietererweiterung

Der Anbieter erstellt und verwaltet eine Headerdatei mit der Definition der Erweiterung. 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.

Vorgänge 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,
};

Bei einem Erweiterungsvorgang kann jeder Operandentyp verwendet werden, auch Operandentypen ohne Erweiterung und Operandtypen von anderen Erweiterungen. Wenn Sie einen Operandentyp von einer anderen Erweiterung verwenden, muss der Treiber die andere Erweiterung unterstützen.

Erweiterungen können auch benutzerdefinierte Strukturen deklarieren, die zu Erweiterungsoperanden gehören.

/**
 * 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) unterstützt 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.
    ...
}

Zum Erstellen eines Modells mit einem Erweiterungsoperanden verwenden Sie ANeuralNetworksModel_getExtensionOperandType, um den Operandentyp abzurufen und ANeuralNetworksModel_addOperand aufzurufen.

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 zusätzliche Daten mit einem Erweiterungsoperanden zu verknüpfen.

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

Verwenden Sie zum Erstellen eines Modells mit einem Erweiterungsvorgang ANeuralNetworksModel_getExtensionOperationType, um den Vorgangstyp abzurufen und ANeuralNetworksModel_addOperation aufzurufen.

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 zu einem NNAPI-Treiber hinzufügen

Treiber melden unterstützte Erweiterungen über die Methode IDevice::getSupportedExtensions. 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, stellen die hohen Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX-Bits das Präfix der Erweiterung dar und die niedrigen Model::ExtensionTypeEncoding::LOW_BITS_TYPE-Bits stellen den Typ oder Betrieb der Erweiterung dar.

Bei der Verarbeitung eines Vorgangs- oder Operandentyps muss der Treiber das Erweiterungspräfix prüfen. Wenn das Erweiterungspräfix einen Wert ungleich null hat, ist der Vorgang oder Operand ein Erweiterungstyp. Wenn der Wert 0 ist, ist der Vorgangs- oder Operandentyp kein Erweiterungstyp.

Wenn Sie das Präfix einem Erweiterungsnamen zuordnen möchten, suchen Sie es in model.extensionNameToPrefix. Die Zuordnung vom Präfix zum Erweiterungsnamen erfolgt für ein bestimmtes Modell im Rahmen einer Eins-zu-Eins-Übereinstimmung. In verschiedenen Modellen können unterschiedliche Präfixwerte 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 zugehörige Daten in operand.extraParams.extension haben, die von der Laufzeit als Rohdaten-Blob beliebiger Größe behandelt werden.

OEM-Betrieb und Datentypen

NNAPI umfasst einen OEM-Vorgang und OEM-Datentypen, damit Gerätehersteller benutzerdefinierte, treiberspezifische Funktionen bereitstellen können. Diese Vorgänge und Datentypen werden nur von OEM-Anwendungen verwendet. Die Semantik des OEM-Betriebs und die Datentypen sind OEM-spezifisch und können sich jederzeit ändern. Der OEM-Vorgang und die Datentypen werden mit OperationType::OEM_OPERATION, OperandType::OEM und OperandType::TENSOR_OEM_BYTE codiert.