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 vorhandener Vorgänge nicht.
Anbietererweiterungen bieten eine strukturiertere Alternative zu den 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.
Anwendungen und native Binärdateien, die sich in der Partition /system
befinden, 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 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 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.
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,
};
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 von einer anderen Erweiterung verwenden, muss der Treiber die andere Erweiterung unterstützen.
Erweiterungen können auch benutzerdefinierte Strukturen deklarieren, die zu Erweiterungs-Operanden 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) bietet Unterstützung für Laufzeiterweiterungen. Dieser Abschnitt bietet einen Überblick über die C API.
Mit ANeuralNetworksDevice_getExtensionSupport
kannst du 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 mit ANeuralNetworksModel_setOperandExtensionData
zusätzliche Daten mit einem Erweiterungsoperanden verknüpfen.
ExampleTensorParams params{
.scale = 0.5,
.zeroPoint = 128,
};
CHECK_EQ(ANeuralNetworksModel_setOperandExtensionData(model, operandIndex, ¶ms, 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 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 hohen Bits Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX
das Prefix der Erweiterung und die niedrigen Bits Model::ExtensionTypeEncoding::LOW_BITS_TYPE
der Typ oder Vorgang der Erweiterung.
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 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 Daten in operand.extraParams.extension
haben, 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.