API für neuronale Netzwerke

Die Android Neural Networks API (NNAPI) ist eine Android C API, die zum Ausführen rechenintensive Vorgänge für maschinelles Lernen auf Android-Geräten. NNAPI bietet eine Basisfunktionalität für allgemeine Frameworks für maschinelles Lernen wie TensorFlow Lite und Caffe2, die neuronale Netzwerke erstellen und trainieren. Die API ist verfügbar auf allen Android-Geräten mit Android 8.1 (API-Level 27) oder höher.

NNAPI unterstützt die Ableitung durch die Anwendung von Daten von Android-Geräten auf frühere für die Entwicklung trainierter, von Entwickler*innen definierter Modelle. Beispiele für Ableitungen sind die Klassifizierung Bilder vorhersagen, das Nutzerverhalten vorhersagen und passende Antworten Suchanfrage.

Die Inferenz auf dem Gerät hat viele Vorteile:

  • Latenz: Sie müssen keine Anfrage über eine Netzwerkverbindung senden und auf eine Antwort warten. Das kann z. B. für Videoanwendungen von entscheidender Bedeutung sein, die aufeinanderfolgende Bilder einer Kamera verarbeitet.
  • Verfügbarkeit: Die Anwendung wird auch außerhalb der Netzwerkabdeckung ausgeführt.
  • Geschwindigkeit: Neue Hardware speziell für die Verarbeitung neuronaler Netzwerke ist wesentlich schneller als eine Allzweck-CPU allein.
  • Datenschutz: Die Daten werden ausschließlich auf dem Android-Gerät gespeichert.
  • Kosten: Wenn alle Berechnungen in einem Gerät ausgeführt werden, ist keine Serverfarm erforderlich. auf dem Android-Gerät.

Entwickler sollten außerdem einige Vor- und Nachteile beachten:

  • Systemauslastung: Die Bewertung neuronaler Netzwerke beinhaltet viele und dadurch den Akkuverbrauch erhöhen. Sie sollten überlegen, Überwachen Sie den Akkuzustand, wenn dies ein Problem für Ihre App ist, insbesondere für Ihre App. für Berechnungen mit langer Ausführungszeit.
  • Anwendungsgröße: Achten Sie auf die Größe Ihrer Modelle. Modelle können mehrere Megabyte Speicherplatz einnehmen. Wenn Sie große Modelle in Ihrem APK bündeln zu stark auf Ihre Nutzer auswirken würde, sollten Sie erwägen, die nach der Installation der App, bei Verwendung kleinerer Modelle oder beim Ausführen für Berechnungen in der Cloud. NNAPI bietet keine Funktion zum Ausführen in der Cloud.

Weitere Informationen finden Sie in der Beispiel für die Android Neural Networks API ein Beispiel für die Verwendung von NNAPI.

Informationen zur Laufzeit der Neural Networks API

NNAPI soll von Bibliotheken, Frameworks und Tools für maschinelles Lernen aufgerufen werden mit denen Entwickler ihre Modelle extern trainieren und auf Android-Geräten bereitstellen können Geräte. Apps verwenden NNAPI normalerweise nicht direkt, sondern übergeordnete Frameworks für maschinelles Lernen. Diese Frameworks wiederum könnten NNAPI zur Ausführung hardwarebeschleunigter Inferenzvorgänge auf unterstützten Geräten.

Basierend auf den Anforderungen der App und den Hardwarefunktionen eines Android-Geräts kann die neuronale Netzwerklaufzeit von Android die Rechenlast auf den verfügbaren On-Device-Prozessoren, einschließlich dedizierter neuronale Netzwerkhardware, Grafikprozessoren (GPUs) und digitale Signale Prozessoren (DSP).

Für Android-Geräte ohne speziellen Anbietertreiber: NNAPI-Laufzeit führt die Anfragen auf der CPU aus.

Abbildung 1 zeigt die allgemeine Systemarchitektur für NNAPI.

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
</ph> Abbildung 1: Systemarchitektur der Android Neural Networks API

Neural Networks API-Programmiermodell

Um Berechnungen mit NNAPI durchzuführen, müssen Sie zunächst eine gerichtete Grafik, die die durchzuführenden Berechnungen definiert. Diese Berechnungsgrafik, kombiniert mit Ihren Eingabedaten (z. B. Gewichtungen und Verzerrungen, die von einem ML-Framework), bildet das Modell für die NNAPI-Laufzeitbewertung.

NNAPI verwendet vier Hauptabstraktionen:

  • Modell: Ein Berechnungsdiagramm mathematischer Operationen und der Konstante Werte, die durch einen Trainingsprozess erlernt wurden. Diese Vorgänge sind spezifisch für neuronalen Netzen. Sie umfassen zweidimensionale Faltung, Logistik (Sigmoid) Aktivierung, rektifizierte Lineareinheit (ReLU)-Aktivierung und mehr. Das Erstellen eines Modells ist ein synchroner Vorgang. Nach der Erstellung kann sie über Threads und Kompilierungen hinweg wiederverwendet werden. In NNAPI wird ein Modell als ANeuralNetworksModel Instanz.
  • Kompilierung: Stellt eine Konfiguration zum Kompilieren eines NNAPI-Modells in Low-Level-Code. Das Erstellen einer Kompilierung ist ein synchroner Vorgang. Einmal erstellt wurde, kann er in Threads und Ausführungen wiederverwendet werden. In NNAPI wird jede Kompilierung als ANeuralNetworksCompilation Instanz.
  • Arbeitsspeicher: Steht für gemeinsamen Arbeitsspeicher, Dateien mit Zuordnung im Arbeitsspeicher und ähnlichen Arbeitsspeicher Puffer. Mit einem Arbeitsspeicherpuffer kann die NNAPI-Laufzeit Daten an Treiber übertragen effizienter gestalten. Eine App erstellt in der Regel einen Zwischenspeicher für gemeinsamen Arbeitsspeicher, enthält jeden Tensor, der zum Definieren eines Modells erforderlich ist. Sie können auch Speicher Puffer zum Speichern der Ein- und Ausgaben für eine Ausführungsinstanz. In NNAPI wird jeder Zwischenspeicher als ANeuralNetworksMemory Instanz.
  • Ausführung: Schnittstelle zum Anwenden eines NNAPI-Modells auf eine Reihe von Eingaben und für die Ergebnisse zu sammeln. Die Ausführung kann synchron oder asynchron durchgeführt werden.

    Bei asynchroner Ausführung mehrere Threads auf dieselbe Ausführung warten kann. Nach Abschluss dieser Ausführung sind alle Threads veröffentlicht.

    In NNAPI wird jede Ausführung als ANeuralNetworksExecution Instanz.

Abbildung 2 zeigt den grundlegenden Programmierablauf.

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
</ph> Abbildung 2: Programmierablauf für die Android Neural Networks API

Der Rest dieses Abschnitts beschreibt die Schritte zur Einrichtung Ihres NNAPI-Modells, um Berechnungen durchführen, das Modell kompilieren und das kompilierte Modell ausführen.

Zugriff auf Trainingsdaten gewähren

Ihre trainierten Daten zu Gewichtungen und Verzerrungen sind wahrscheinlich in einer Datei gespeichert. Um die NNAPI-Laufzeit mit effizientem Zugriff auf diese Daten, erstellen Sie eine ANeuralNetworksMemory indem Sie die Methode ANeuralNetworksMemory_createFromFd() und übergeben den Dateideskriptor der geöffneten Datendatei. Außerdem Speicherschutz-Flags und einen Offset für die Region des gemeinsamen Arbeitsspeichers beginnt in der Datei.

// Create a memory buffer from the file that contains the trained data
ANeuralNetworksMemory* mem1 = NULL;
int fd = open("training_data", O_RDONLY);
ANeuralNetworksMemory_createFromFd(file_size, PROT_READ, fd, 0, &mem1);

Obwohl wir in diesem Beispiel nur eine ANeuralNetworksMemory ist es möglich, mehr als eine Methode ANeuralNetworksMemory-Instanz für mehrere Dateien.

Native Hardwarepuffer verwenden

Sie können native Hardwarepuffer verwenden. für Modelleingaben, -ausgaben und konstante Operandenwerte. In bestimmten Fällen NNAPI-Beschleuniger kann auf AHardwareBuffer ohne dass der Treiber die Daten kopieren muss. AHardwareBuffer hat viele Konfigurationen und nicht jeder NNAPI-Beschleuniger unterstützt möglicherweise alle für diese Konfigurationen. Lesen Sie aufgrund dieser Einschränkung die Einschränkungen aufgeführt in Referenzdokumentation zu ANeuralNetworksMemory_createFromAHardwareBuffer und im Voraus auf Zielgeräten zu testen, um Kompilierungen und Ausführungen sicherzustellen die AHardwareBuffer verwenden, wie erwartet verhalten, Gerätezuweisung, um den Beschleuniger anzugeben.

Damit die NNAPI-Laufzeit auf ein AHardwareBuffer-Objekt zugreifen kann, erstellen Sie ein ANeuralNetworksMemory indem Sie die Methode ANeuralNetworksMemory_createFromAHardwareBuffer und übergeben den Parameter AHardwareBuffer-Objekt wie im folgenden Codebeispiel gezeigt:

// Configure and create AHardwareBuffer object
AHardwareBuffer_Desc desc = ...
AHardwareBuffer* ahwb = nullptr;
AHardwareBuffer_allocate(&desc, &ahwb);

// Create ANeuralNetworksMemory from AHardwareBuffer
ANeuralNetworksMemory* mem2 = NULL;
ANeuralNetworksMemory_createFromAHardwareBuffer(ahwb, &mem2);

Wenn NNAPI nicht mehr auf das AHardwareBuffer-Objekt zugreifen muss, geben Sie den Parameter entsprechende ANeuralNetworksMemory-Instanz:

ANeuralNetworksMemory_free(mem2);

Hinweis:

  • Sie können AHardwareBuffer nur für den gesamten Zwischenspeicher; können Sie es nicht mit einen ARect-Parameter.
  • Die NNAPI-Laufzeit wird nicht geleert Zwischenspeichern. Sie müssen sicherstellen, dass die Ein- und Ausgabezwischenspeicher bevor Sie die Ausführung planen.
  • Es wird keine Unterstützung für Fence-Dateideskriptoren synchronisieren.
  • Für AHardwareBuffer mit anbieterspezifischen Formaten und Nutzungsbits verwendet werden, um zu ermitteln, ob der Client oder der Fahrer Cache gespeichert werden.

Modell

Ein Modell ist die grundlegende Berechnungseinheit in NNAPI. Jedes Modell ist definiert durch einen oder mehrere Operanden und Operationen.

Operanden

Operanden sind Datenobjekte, die zum Definieren des Graphen verwendet werden. Dazu gehören die Eingaben und Ausgaben des Modells, die Zwischenknoten, die die Daten enthalten, von einer Operation zur nächsten fließt, und die Konstanten, für diese Vorgänge.

Es gibt zwei Arten von Operanden, die NNAPI-Modellen hinzugefügt werden können: Skalare und tensors.

Ein Skalar stellt einen einzelnen Wert dar. NNAPI unterstützt skalare Werte in booleschen, 16-Bit-Gleitkomma, 32-Bit-Gleitkomma, 32-Bit-Ganzzahl und ohne Vorzeichen 32-Bit-Ganzzahlformate.

Die meisten Vorgänge in NNAPI beinhalten Tensoren. Tensoren sind n-dimensionale Arrays. NNAPI unterstützt Tensoren mit 16-Bit-Gleitkomma, 32-Bit-Gleitkomma, 8-Bit quantisiert, 16 Bit quantisiert, 32 Bit-Ganzzahl und 8 Bit boolesche Werte.

Abbildung 3 zeigt beispielsweise ein Modell mit zwei Operationen: einer Addition. gefolgt von einer Multiplikation. Das Modell verwendet einen Eingabetensor und erzeugt einen Ausgabetensor

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
</ph> Abbildung 3: Beispiel von Operanden für ein NNAPI-Modell

Das obige Modell hat sieben Operanden. Diese Operanden werden implizit identifiziert durch Index der Reihenfolge, in der sie dem Modell hinzugefügt werden. Der erste Operand der hinzugefügte Index den Index 0 hat, der zweite Index 1 usw. Operanden 1, 2, 3, und 5 sind konstante Operanden.

Die Reihenfolge, in der Sie die Operanden hinzufügen, spielt keine Rolle. Zum Beispiel hat das Modell Ausgabeoperanden könnten der erste hinzugefügte Operand sein. Wichtig ist, dass Sie die korrekter Indexwert beim Verweisen auf einen Operanden.

Operanden haben Typen. Diese werden angegeben, wenn sie dem Modell hinzugefügt werden.

Ein Operand kann nicht sowohl als Eingabe als auch als Ausgabe eines Modells verwendet werden.

Jeder Operand muss entweder eine Modelleingabe, eine Konstante oder der Ausgabeoperanden von genau einen Vorgang ausführen.

Weitere Informationen zur Verwendung von Operanden finden Sie unter Weitere Informationen zu Operanden

Aufgaben und Ablauf

Ein Vorgang gibt die auszuführenden Berechnungen an. Jeder Vorgang besteht aus dieser Elemente:

  • einen Vorgangstyp (z. B. Addition, Multiplikation, Faltung)
  • eine Liste der Indexe der Operanden, die die Operation für die Eingabe verwendet, und
  • Eine Liste der Indexe der Operanden, die der Vorgang für die Ausgabe verwendet.

Die Reihenfolge in diesen Listen ist wichtig: sieh dir die NNAPI API-Referenz für die erwarteten Eingaben und die Ausgaben für jeden Vorgangstyp.

Sie müssen dem Modell die Operanden hinzufügen, die eine Operation verbraucht oder erzeugt bevor Sie den Vorgang hinzufügen.

Die Reihenfolge, in der Sie Vorgänge hinzufügen, spielt keine Rolle. NNAPI basiert auf dem die durch den Berechnungsgraphen von Operanden und Operationen geschaffen wurden, Reihenfolge festlegen, in der Vorgänge ausgeführt werden.

Die von NNAPI unterstützte Vorgänge sind in der folgenden Tabelle zusammengefasst:

Kategorie Aufgaben und Ablauf
Elementweise mathematische Operationen
Tensor-Manipulation
Image-Vorgänge
Lookup-Vorgänge
Normalisierungsvorgänge
Faltungsvorgänge
Zusammenführungsvorgänge
Aktivierungsvorgänge
Andere Vorgänge

Bekanntes Problem in API-Level 28:Beim Übergeben ANEURALNETWORKS_TENSOR_QUANT8_ASYMM Tensoren zu den ANEURALNETWORKS_PAD der unter Android 9 (API-Level 28) und höher verfügbar ist, Die NNAPI-Ausgabe stimmt möglicherweise nicht mit der Ausgabe des übergeordneten maschinellen Lernens überein Frameworks wie TensorFlow Lite Ich sollte stattdessen nur ANEURALNETWORKS_TENSOR_FLOAT32 Das Problem wurde unter Android 10 (API-Level 29) und höher behoben.

Modelle erstellen

Im folgenden Beispiel erstellen wir das Modell mit zwei Vorgängen, das sich in Abbildung 3.

So erstellen Sie das Modell:

  1. Rufen Sie die Methode ANeuralNetworksModel_create() um ein leeres Modell zu definieren.

    ANeuralNetworksModel* model = NULL;
    ANeuralNetworksModel_create(&model);
    
  2. Fügen Sie Ihrem Modell die Operanden hinzu, indem Sie ANeuralNetworks_addOperand() Ihre Datentypen werden mithilfe der ANeuralNetworksOperandType Datenstruktur.

    // In our example, all our tensors are matrices of dimension [3][4]
    ANeuralNetworksOperandType tensor3x4Type;
    tensor3x4Type.type = ANEURALNETWORKS_TENSOR_FLOAT32;
    tensor3x4Type.scale = 0.f;    // These fields are used for quantized tensors
    tensor3x4Type.zeroPoint = 0;  // These fields are used for quantized tensors
    tensor3x4Type.dimensionCount = 2;
    uint32_t dims[2] = {3, 4};
    tensor3x4Type.dimensions = dims;

    // We also specify operands that are activation function specifiers ANeuralNetworksOperandType activationType; activationType.type = ANEURALNETWORKS_INT32; activationType.scale = 0.f; activationType.zeroPoint = 0; activationType.dimensionCount = 0; activationType.dimensions = NULL;

    // Now we add the seven operands, in the same order defined in the diagram ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 0 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 1 ANeuralNetworksModel_addOperand(model, &activationType); // operand 2 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 3 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 4 ANeuralNetworksModel_addOperand(model, &activationType); // operand 5 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 6
  3. Für Operanden mit konstanten Werten, z. B. Gewichtungen und Verzerrungen, App aus einem Trainingsprozess bezieht, verwenden Sie ANeuralNetworksModel_setOperandValue() und ANeuralNetworksModel_setOperandValueFromMemory() Funktionen.

    Im folgenden Beispiel legen wir konstante Werte aus der Trainingsdatendatei fest. entsprechend dem Zwischenspeicher, den wir in Zugriff auf Trainingsdaten

    // In our example, operands 1 and 3 are constant tensors whose values were
    // established during the training process
    const int sizeOfTensor = 3 * 4 * 4;    // The formula for size calculation is dim0 * dim1 * elementSize
    ANeuralNetworksModel_setOperandValueFromMemory(model, 1, mem1, 0, sizeOfTensor);
    ANeuralNetworksModel_setOperandValueFromMemory(model, 3, mem1, sizeOfTensor, sizeOfTensor);

    // We set the values of the activation operands, in our example operands 2 and 5 int32_t noneValue = ANEURALNETWORKS_FUSED_NONE; ANeuralNetworksModel_setOperandValue(model, 2, &noneValue, sizeof(noneValue)); ANeuralNetworksModel_setOperandValue(model, 5, &noneValue, sizeof(noneValue));
  4. Fügen Sie für jeden Vorgang im gerichteten Graphen, den Sie berechnen möchten, den Wert auf Ihr Modell anwenden, indem Sie die Methode ANeuralNetworksModel_addOperation() .

    Als Parameter für diesen Aufruf muss Ihre App Folgendes bereitstellen:

    • Vorgangstyp
    • die Anzahl der Eingabewerte
    • das Array der Indexe für Eingabeoperanden
    • die Anzahl der Ausgabewerte
    • das Array der Indexe für Ausgabeoperanden

    Beachten Sie, dass ein Operand nicht für die Ein- und Ausgabe desselben .

    // We have two operations in our example
    // The first consumes operands 1, 0, 2, and produces operand 4
    uint32_t addInputIndexes[3] = {1, 0, 2};
    uint32_t addOutputIndexes[1] = {4};
    ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_ADD, 3, addInputIndexes, 1, addOutputIndexes);

    // The second consumes operands 3, 4, 5, and produces operand 6 uint32_t multInputIndexes[3] = {3, 4, 5}; uint32_t multOutputIndexes[1] = {6}; ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_MUL, 3, multInputIndexes, 1, multOutputIndexes);
  5. Festlegen, welche Operanden das Modell als Ein- und Ausgaben behandeln soll das Aufrufen der ANeuralNetworksModel_identifyInputsAndOutputs() .

    // Our model has one input (0) and one output (6)
    uint32_t modelInputIndexes[1] = {0};
    uint32_t modelOutputIndexes[1] = {6};
    ANeuralNetworksModel_identifyInputsAndOutputs(model, 1, modelInputIndexes, 1 modelOutputIndexes);
    
  6. Geben Sie optional an, ob ANEURALNETWORKS_TENSOR_FLOAT32 mit einem Bereich oder einer Genauigkeit berechnet werden, die so niedrig ist wie der IEEE 754 16-Bit-Gleitkommaformat durch Aufrufen von ANeuralNetworksModel_relaxComputationFloat32toFloat16()

  7. ANeuralNetworksModel_finish() anrufen um die Definition Ihres Modells abzuschließen. Wenn keine Fehler auftreten, gibt den Ergebniscode zurück: ANEURALNETWORKS_NO_ERROR

    ANeuralNetworksModel_finish(model);
    

Nachdem Sie ein Modell erstellt haben, können Sie es beliebig oft kompilieren und jedes beliebig oft kompiliert werden.

Ablauf steuern

So binden Sie den Ablauf steuern in ein NNAPI-Modell ein:

  1. Die entsprechenden Teilgraphen für die Ausführung (then- und else-Teilgraphen) erstellen für eine IF-Anweisung, condition- und body-Teildiagramme für eine WHILE-Schleife) als eigenständige ANeuralNetworksModel*-Modelle:

    ANeuralNetworksModel* thenModel = makeThenModel();
    ANeuralNetworksModel* elseModel = makeElseModel();
    
  2. Erstellen Sie Operanden, die auf diese Modelle innerhalb des Modells verweisen, das die Ablauf steuern:

    ANeuralNetworksOperandType modelType = {
        .type = ANEURALNETWORKS_MODEL,
    };
    ANeuralNetworksModel_addOperand(model, &modelType);  // kThenOperandIndex
    ANeuralNetworksModel_addOperand(model, &modelType);  // kElseOperandIndex
    ANeuralNetworksModel_setOperandValueFromModel(model, kThenOperandIndex, &thenModel);
    ANeuralNetworksModel_setOperandValueFromModel(model, kElseOperandIndex, &elseModel);
    
  3. Fügen Sie den Ablauf für die Ablaufsteuerung hinzu:

    uint32_t inputs[] = {kConditionOperandIndex,
                         kThenOperandIndex,
                         kElseOperandIndex,
                         kInput1, kInput2, kInput3};
    uint32_t outputs[] = {kOutput1, kOutput2};
    ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_IF,
                                      std::size(inputs), inputs,
                                      std::size(output), outputs);
    

Compilation

Der Kompilierungsschritt bestimmt, auf welchen Prozessoren Ihr Modell ausgeführt wird und bittet die entsprechenden Treiber, sich auf die Ausführung vorzubereiten. Dies könnte Erzeugen von Maschinencode, der für die Prozessoren Ihres Modells spezifisch ist ausgeführt werden soll.

So kompilieren Sie ein Modell:

  1. Rufen Sie die Methode ANeuralNetworksCompilation_create() zum Erstellen einer neuen Kompilierungsinstanz.

    // Compile the model
    ANeuralNetworksCompilation* compilation;
    ANeuralNetworksCompilation_create(model, &compilation);
    

    Optional können Sie die Gerätezuweisung nutzen, um explizit auf welchen Geräten ausgeführt werden soll.

  2. Sie können optional beeinflussen, wie die Laufzeit zwischen Akkuleistung wechselt. Nutzungs- und Ausführungsgeschwindigkeit. Rufen Sie hierzu einfach ANeuralNetworksCompilation_setPreference()

    // Ask to optimize for low power consumption
    ANeuralNetworksCompilation_setPreference(compilation, ANEURALNETWORKS_PREFER_LOW_POWER);
    

    Sie können folgende Einstellungen festlegen:

  3. Optional können Sie das Kompilierungs-Caching einrichten, indem Sie folgenden Befehl aufrufen: ANeuralNetworksCompilation_setCaching

    // Set up compilation caching
    ANeuralNetworksCompilation_setCaching(compilation, cacheDir, token);
    

    getCodeCacheDir() verwenden für cacheDir. Die angegebene token muss für jedes Modell innerhalb der Anwendung.

  4. Schließen Sie die Kompilierungsdefinition ab, indem Sie ANeuralNetworksCompilation_finish() Wenn keine Fehler vorliegen, gibt diese Funktion den Ergebniscode zurück: ANEURALNETWORKS_NO_ERROR

    ANeuralNetworksCompilation_finish(compilation);
    

Geräteerkennung und -zuweisung

Auf Android-Geräten mit Android 10 (API-Level 29) und höher bietet NNAPI Funktionen, mit denen ML-Framework-Bibliotheken und -Apps Informationen zu den verfügbaren Geräten und die Geräte, die verwendet werden sollen, Ausführung. Wenn Sie Informationen über die verfügbaren Geräte angeben, können Apps die genaue Version der Treiber auf einem Gerät, um bekannte Inkompatibilitäten. Apps können festlegen, auf welchen Geräten um verschiedene Abschnitte eines Modells auszuführen, auf dem sie bereitgestellt werden.

Geräteerkennung

Verwenden Sie ANeuralNetworks_getDeviceCount um die Anzahl der verfügbaren Geräte zu ermitteln. Verwenden Sie für jedes Gerät ANeuralNetworks_getDevice um eine ANeuralNetworksDevice-Instanz auf einen Verweis auf dieses Gerät festzulegen.

Sobald du eine Gerätereferenz hast, kannst du zusätzliche Informationen zu mit den folgenden Funktionen:

Gerätezuweisung

Verwenden Sie ANeuralNetworksModel_getSupportedOperationsForDevices um zu ermitteln, welche Vorgänge eines Modells auf bestimmten Geräten ausgeführt werden können.

Um zu steuern, welche Beschleuniger für die Ausführung verwendet werden sollen, rufen Sie ANeuralNetworksCompilation_createForDevices anstelle von ANeuralNetworksCompilation_create. Verwende wie gewohnt das resultierende ANeuralNetworksCompilation-Objekt. Die Funktion gibt einen Fehler zurück, wenn das bereitgestellte Modell Vorgänge enthält, die wird von den ausgewählten Geräten nicht unterstützt.

Wenn mehrere Geräte angegeben sind, sorgt die Laufzeit für die Verteilung geräteübergreifend arbeiten.

Ähnlich wie bei anderen Geräten wird die NNAPI-CPU-Implementierung durch ein ANeuralNetworksDevice mit dem Namen nnapi-reference und dem Typ ANEURALNETWORKS_DEVICE_TYPE_CPU Beim Anrufen ANeuralNetworksCompilation_createForDevices, die CPU-Implementierung ist nicht zur Bewältigung von Fehlern bei der Modellkompilierung und -ausführung verwendet wird.

Es liegt in der Verantwortung einer Anwendung, ein Modell in Untermodelle zu unterteilen, auf den angegebenen Geräten ausgeführt werden kann. Anwendungen, die keine manuellen Schritte ausführen müssen sollte bei der Partitionierung weiterhin die einfachere ANeuralNetworksCompilation_create damit alle verfügbaren Geräte (einschließlich der CPU) genutzt werden, Modell. Wenn das Modell von den angegebenen Geräten nicht vollständig unterstützt werden konnte mit ANeuralNetworksCompilation_createForDevices, ANEURALNETWORKS_BAD_DATA zurückgegeben.

Modellpartitionierung

Wenn für das Modell mehrere Geräte verfügbar sind, wird die NNAPI-Laufzeit und verteilt die Arbeit auf alle Geräte. Wenn beispielsweise mehr als ein Gerät bereitgestellt ANeuralNetworksCompilation_createForDevices, alle angegebenen werden bei der Zuweisung der Arbeit berücksichtigt. Wenn das CPU-Gerät nicht in der Liste enthalten ist, wird die CPU-Ausführung deaktiviert. Bei Verwendung von ANeuralNetworksCompilation_create werden alle verfügbaren Geräte berücksichtigt, einschließlich der CPU.

Die Verteilung erfolgt, indem für jedes Gerät ein Gerät aus der Liste der verfügbaren Geräte ausgewählt wird. die Vorgänge im Modell, das Gerät, das den Betrieb und die beste Leistung, d.h. die schnellste Ausführungszeit, den niedrigsten Energieverbrauch, abhängig von der Ausführungspräferenz, die durch Kundschaft. Dieser Partitionierungsalgorithmus berücksichtigt mögliche Ineffizienzen aufgrund der E/A zwischen den verschiedenen Prozessoren. mehrere Prozessoren angeben (entweder explizit bei ANeuralNetworksCompilation_createForDevices oder implizit durch Verwendung ANeuralNetworksCompilation_create) ist es wichtig, ein Profil für die .

Informationen dazu, wie Ihr Modell durch NNAPI partitioniert wurde, finden Sie in der Android-Protokolle für eine Nachricht (auf INFO-Ebene mit dem Tag ExecutionPlan):

ModelBuilder::findBestDeviceForEachOperation(op-name): device-index

op-name ist der beschreibende Name des Vorgangs in der Grafik. device-index ist der Index des Kandidatengeräts in der Geräteliste. Diese Liste wird ANeuralNetworksCompilation_createForDevices zur Verfügung gestellt oder, wenn Sie ANeuralNetworksCompilation_createForDevices verwenden, die Liste der Geräte zurückgegeben, wenn Iteration über alle Geräte mit ANeuralNetworks_getDeviceCount und ANeuralNetworks_getDevice

Die Nachricht (auf INFO-Ebene mit dem Tag ExecutionPlan):

ModelBuilder::partitionTheWork: only one best device: device-name

Diese Meldung gibt an, dass das gesamte Diagramm auf dem Gerät beschleunigt wurde. device-name

Umsetzung

Beim Ausführungsschritt wird das Modell auf eine Reihe von Eingaben angewendet Die Berechnungen werden in einem oder mehreren Nutzerzwischenspeichern oder Arbeitsspeicherbereichen ausgegeben, die Ihre App zugewiesen sind.

So führen Sie ein kompiliertes Modell aus:

  1. Rufen Sie die Methode ANeuralNetworksExecution_create() zum Erstellen einer neuen Ausführungsinstanz.

    // Run the compiled model against a set of inputs
    ANeuralNetworksExecution* run1 = NULL;
    ANeuralNetworksExecution_create(compilation, &run1);
    
  2. Geben Sie an, wo die App die Eingabewerte für die Berechnung liest. Ihre App kann Eingabewerte entweder aus einem Nutzerzwischenspeicher oder einem zugewiesenen Speicherplatz lesen durch einen Anruf ANeuralNetworksExecution_setInput() oder ANeuralNetworksExecution_setInputFromMemory() .

    // Set the single input to our sample model. Since it is small, we won't use a memory buffer
    float32 myInput[3][4] = { ...the data... };
    ANeuralNetworksExecution_setInput(run1, 0, NULL, myInput, sizeof(myInput));
    
  3. Geben Sie an, wo die Anwendung die Ausgabewerte schreibt. Ihre Anwendung kann Ausgabewerte entweder in eine oder einen zugewiesenen Speicherplatz haben, indem Sie ANeuralNetworksExecution_setOutput() oder ANeuralNetworksExecution_setOutputFromMemory() .

    // Set the output
    float32 myOutput[3][4];
    ANeuralNetworksExecution_setOutput(run1, 0, NULL, myOutput, sizeof(myOutput));
    
  4. Planen Sie den Start der Ausführung, indem Sie die Methode ANeuralNetworksExecution_startCompute() . Wenn keine Fehler vorliegen, gibt diese Funktion den Ergebniscode zurück: ANEURALNETWORKS_NO_ERROR

    // Starts the work. The work proceeds asynchronously
    ANeuralNetworksEvent* run1_end = NULL;
    ANeuralNetworksExecution_startCompute(run1, &run1_end);
    
  5. Rufen Sie die ANeuralNetworksEvent_wait() auf. um auf den Abschluss der Ausführung zu warten. Wenn die Ausführung erfolgreich ist, gibt diese Funktion den Ergebniscode ANEURALNETWORKS_NO_ERROR Das Warten kann in einem anderen Thread ausgeführt werden als dem, der die Ausführung gestartet hat.

    // For our example, we have no other work to do and will just wait for the completion
    ANeuralNetworksEvent_wait(run1_end);
    ANeuralNetworksEvent_free(run1_end);
    ANeuralNetworksExecution_free(run1);
    
  6. Optional können Sie einen anderen Satz von Eingaben auf das kompilierte Modell anwenden, indem Sie mit derselben Kompilierungsinstanz eine neue ANeuralNetworksExecution Instanz.

    // Apply the compiled model to a different set of inputs
    ANeuralNetworksExecution* run2;
    ANeuralNetworksExecution_create(compilation, &run2);
    ANeuralNetworksExecution_setInput(run2, ...);
    ANeuralNetworksExecution_setOutput(run2, ...);
    ANeuralNetworksEvent* run2_end = NULL;
    ANeuralNetworksExecution_startCompute(run2, &run2_end);
    ANeuralNetworksEvent_wait(run2_end);
    ANeuralNetworksEvent_free(run2_end);
    ANeuralNetworksExecution_free(run2);
    

Synchrone Ausführung

Bei der asynchronen Ausführung wird Zeit benötigt, um Threads zu erstellen und zu synchronisieren. Darüber hinaus kann die Latenz stark variieren, Verzögerungen von bis zu 500 Mikrosekunden zwischen der Benachrichtigung über einen Thread oder und der Zeitpunkt, an dem sie schließlich an einen CPU-Kern gebunden ist.

Um die Latenz zu verbessern, können Sie eine Anwendung stattdessen anweisen, eine synchrone Inferenzaufruf an die Laufzeit. Dieser Aufruf wird erst zurückgegeben, wenn eine Inferenz abgeschlossen sein, anstatt nach dem Start einer Inferenz zurückzukehren. Stattdessen des Anrufs ANeuralNetworksExecution_startCompute Für einen asynchronen Inferenzaufruf an die Laufzeit ruft die Anwendung ANeuralNetworksExecution_compute um einen synchronen Aufruf an die Laufzeit zu tätigen. Ein Aufruf an ANeuralNetworksExecution_compute nimmt kein ANeuralNetworksEvent und ist nicht mit einem Aufruf von ANeuralNetworksEvent_wait gekoppelt.

Serienausführungen

Auf Android-Geräten mit Android 10 (API-Level 29) und höher unterstützt die NNAPI Burst Ausführungen über die ANeuralNetworksBurst -Objekt enthält. Burst-Ausführungen sind eine Abfolge von Ausführungen derselben Kompilierung die sehr schnell nacheinander auftreten, z. B. wenn sie auf den Einzelbild einer Kamera Audio-Samples nacheinander aufnehmen. Die Verwendung von ANeuralNetworksBurst-Objekten kann die Ausführung beschleunigen, da sie für Beschleuniger darauf hinweisen, dass Ressourcen zwischen Ausführungen wiederverwendet werden und dass Beschleuniger in einem für die Dauer des Bursts hoch.

ANeuralNetworksBurst führt nur zu einer kleinen Änderung bei der normalen Ausführung Pfad. Sie erstellen ein Burst-Objekt mit ANeuralNetworksBurst_create, Dies wird im folgenden Code-Snippet gezeigt:

// Create burst object to be reused across a sequence of executions
ANeuralNetworksBurst* burst = NULL;
ANeuralNetworksBurst_create(compilation, &burst);

Burst-Ausführungen erfolgen synchron. Statt jedoch die ANeuralNetworksExecution_compute um jede Ableitung durchzuführen, kombinieren Sie die verschiedenen ANeuralNetworksExecution Objekte mit demselben ANeuralNetworksBurst in Aufrufen der Funktion ANeuralNetworksExecution_burstCompute

// Create and configure first execution object
// ...

// Execute using the burst object
ANeuralNetworksExecution_burstCompute(execution1, burst);

// Use results of first execution and free the execution object
// ...

// Create and configure second execution object
// ...

// Execute using the same burst object
ANeuralNetworksExecution_burstCompute(execution2, burst);

// Use results of second execution and free the execution object
// ...

Geben Sie das Objekt ANeuralNetworksBurst kostenlos mit ANeuralNetworksBurst_free wenn sie nicht mehr benötigt werden.

// Cleanup
ANeuralNetworksBurst_free(burst);

Asynchrone Befehlswarteschlangen und Fenced-Ausführung

Ab Android 11 unterstützt NNAPI eine zusätzliche Möglichkeit, asynchrone Ausführung über die ANeuralNetworksExecution_startComputeWithDependencies() . Bei dieser Methode wartet die Ausführung auf alle zu signalisieren, bevor die Auswertung gestartet wird. Sobald die Ausführung abgeschlossen ist und die Ausgaben bereit sind, wird das zurückgegebene Ereignis signalisiert.

Je nachdem, auf welchen Geräten die Ausführung erfolgt, wird das Ereignis möglicherweise durch eine Sync Fence. Ich muss anrufen ANeuralNetworksEvent_wait() um auf das Ereignis zu warten und die bei der Ausführung verwendeten Ressourcen wiederherzustellen. Ich können Synchronisierungszäune mithilfe von ANeuralNetworksEvent_createFromSyncFenceFd(), und Sie können Synchronisierungszäune aus einem Ereignisobjekt exportieren, indem Sie ANeuralNetworksEvent_getSyncFenceFd()

Ausgaben mit dynamischer Größe

Um Modelle zu unterstützen, bei denen die Größe der Ausgabe von der Eingabe abhängt Daten, d. h., die Größe kann bei der Modellausführung nicht bestimmt werden Uhrzeit – verwenden Sie ANeuralNetworksExecution_getOutputOperandRank und ANeuralNetworksExecution_getOutputOperandDimensions

Das folgende Codebeispiel zeigt, wie dies funktioniert:

// Get the rank of the output
uint32_t myOutputRank = 0;
ANeuralNetworksExecution_getOutputOperandRank(run1, 0, &myOutputRank);

// Get the dimensions of the output
std::vector<uint32_t> myOutputDimensions(myOutputRank);
ANeuralNetworksExecution_getOutputOperandDimensions(run1, 0, myOutputDimensions.data());

Bereinigung

Im Bereinigungsschritt werden interne Ressourcen freigegeben, Berechnung.

// Cleanup
ANeuralNetworksCompilation_free(compilation);
ANeuralNetworksModel_free(model);
ANeuralNetworksMemory_free(mem1);

Fehlerverwaltung und CPU-Fallback

Tritt während der Partitionierung ein Fehler auf und ein Treiber kann ein oder wenn ein Treiber ein kompiliertes Modell nicht ausführen kann, NNAPI greift möglicherweise auf seine eigene CPU-Implementierung der einen oder mehrerer Geschäftsabläufe.

Wenn der NNAPI-Client optimierte Versionen des Vorgangs enthält (z. B. z. B. TFLite), kann es vorteilhaft sein, das CPU-Fallback zu deaktivieren und Fehler mit der optimierten Vorgangsimplementierung des Clients beheben.

Wenn in Android 10 die Kompilierung mit ANeuralNetworksCompilation_createForDevices, dann wird das CPU-Fallback deaktiviert.

In Android P greift die NNAPI-Ausführung auf die CPU zurück, wenn die Ausführung auf dem Treiber fehlschlägt. Dies gilt auch für Android 10, wenn ANeuralNetworksCompilation_create statt ANeuralNetworksCompilation_createForDevices verwendet wird.

Bei der ersten Ausführung wird für diese eine Partition schlägt das gesamte Modell auf der CPU fehl.

Wenn die Partitionierung oder Kompilierung fehlschlägt, wird das gesamte Modell auf der CPU ausprobiert.

Es gibt Fälle, in denen einige Vorgänge auf der CPU nicht unterstützt werden. schlägt die Kompilierung oder Ausführung fehl.

Auch nach dem Deaktivieren des CPU-Fallbacks kann es noch Vorgänge im Modell geben die auf der CPU geplant sind. Wenn die CPU in der Liste der bereitgestellten Prozessoren enthalten ist an ANeuralNetworksCompilation_createForDevices. Dies ist entweder die einzige der Auftragsverarbeiter ist, der diese Vorgänge unterstützt, oder der Auftragsverarbeiter ist, der behauptet, am besten, Leistung für diese Vorgänge hat, wird er als primärer (kein Fallback) ausgewählt. Executor.

Um sicherzustellen, dass keine CPU ausgeführt wird, verwenden Sie ANeuralNetworksCompilation_createForDevices während nnapi-reference aus der Geräteliste ausgeschlossen wird. Ab Android P ist es möglich, das Fallback bei der Ausführung auf Die FEHLERBEHEBUNG wird erstellt, indem die Eigenschaft debug.nn.partition auf 2 gesetzt wird.

Arbeitsspeicherdomains

In Android 11 und höher unterstützt NNAPI Speicherdomains, die eine Zuordnung bereitstellen für undurchsichtige Erinnerungen. Dadurch können Anwendungen gerätenative über mehrere Ausführungen hinweg, sodass NNAPI keine Daten unnötig, wenn aufeinanderfolgende Ausführungen für denselben Treiber erfolgen.

Das Memory Domain-Feature ist für Tensoren gedacht, die hauptsächlich intern und nicht häufig auf Clientseite zugreifen müssen. Beispiele für Zu diesen Tensoren gehören die Zustandstensoren in Sequenzmodellen. Für Tensoren, die häufigen CPU-Zugriff auf der Clientseite haben, verwenden Sie stattdessen Pools mit gemeinsam genutztem Arbeitsspeicher.

Führen Sie die folgenden Schritte aus, um einen intransparenten Speicher zuzuweisen:

  1. Rufen Sie die Methode ANeuralNetworksMemoryDesc_create() -Funktion zum Erstellen eines neuen Speicherdeskriptors:

    // Create a memory descriptor
    ANeuralNetworksMemoryDesc* desc;
    ANeuralNetworksMemoryDesc_create(&desc);
    
  2. Geben Sie alle gewünschten Eingabe- und Ausgaberollen an, indem Sie den Aufruf ANeuralNetworksMemoryDesc_addInputRole() und ANeuralNetworksMemoryDesc_addOutputRole()

    // Specify that the memory may be used as the first input and the first output
    // of the compilation
    ANeuralNetworksMemoryDesc_addInputRole(desc, compilation, 0, 1.0f);
    ANeuralNetworksMemoryDesc_addOutputRole(desc, compilation, 0, 1.0f);
    
  3. Geben Sie optional die Speicherabmessungen an, indem Sie den Aufruf ANeuralNetworksMemoryDesc_setDimensions()

    // Specify the memory dimensions
    uint32_t dims[] = {3, 4};
    ANeuralNetworksMemoryDesc_setDimensions(desc, 2, dims);
    
  4. Schließen Sie die Deskriptordefinition ab, indem Sie ANeuralNetworksMemoryDesc_finish()

    ANeuralNetworksMemoryDesc_finish(desc);
    
  5. Weisen Sie so viele Erinnerungen wie nötig zu, indem Sie den Deskriptor an ANeuralNetworksMemory_createFromDesc()

    // Allocate two opaque memories with the descriptor
    ANeuralNetworksMemory* opaqueMem;
    ANeuralNetworksMemory_createFromDesc(desc, &opaqueMem);
    
  6. Geben Sie den Arbeitsspeicherdeskriptor kostenlos, wenn Sie ihn nicht mehr benötigen.

    ANeuralNetworksMemoryDesc_free(desc);
    

Der Client darf das erstellte ANeuralNetworksMemory-Objekt nur mit ANeuralNetworksExecution_setInputFromMemory() oder ANeuralNetworksExecution_setOutputFromMemory() entsprechend den Rollen die im ANeuralNetworksMemoryDesc-Objekt angegeben sind. Der Versatz und die Länge Argumente müssen auf 0 gesetzt werden, was bedeutet, dass der gesamte Arbeitsspeicher verwendet wird. Der Kunde kann den Speicherinhalt auch explizit festlegen oder extrahieren, indem ANeuralNetworksMemory_copy()

Sie können intransparente Erinnerungen mit Rollen mit nicht angegebenen Dimensionen oder Rängen erstellen. In diesem Fall schlägt die Speichererstellung möglicherweise mit dem Fehler ANEURALNETWORKS_OP_FAILED-Status, wenn er vom zugrunde liegenden Dienst nicht unterstützt wird. . Dem Client wird empfohlen, eine Fallback-Logik zu implementieren, indem eine Der Zwischenspeicher ist groß genug und wird von Ashmem- oder BLOB-Modus AHardwareBuffer unterstützt.

Wenn NNAPI nicht mehr auf das undurchsichtige Speicherobjekt zugreifen muss, geben Sie das Objekt entsprechende ANeuralNetworksMemory-Instanz:

ANeuralNetworksMemory_free(opaqueMem);

Leistungsmessung

Sie können die Leistung Ihrer App bewerten, indem Sie die Ausführungszeit messen oder Profilerstellung.

Ausführungszeit

Wenn Sie die Gesamtausführungszeit über die Laufzeit ermitteln möchten, können Sie Synchronous Execution API und messen die vom Aufruf benötigte Zeit. Wenn Sie Sie möchten die Gesamtausführungszeit über eine niedrigere Softwareebene ermitteln. können Sie ANeuralNetworksExecution_setMeasureTiming und ANeuralNetworksExecution_getDuration Sie erhalten:

  • Ausführungszeit auf einem Beschleuniger (nicht im Treiber, der auf dem Host ausgeführt wird) des Prozessors.
  • Ausführungszeit im Treiber, einschließlich der Zeit auf dem Beschleuniger.

Die Ausführungszeit im Treiber schließt Overhead wie den der Laufzeit aus und der IPC, die für die Kommunikation zwischen Laufzeit und Treiber erforderlich sind.

Diese APIs messen die Dauer zwischen der eingereichten und der abgeschlossenen Arbeit. Ereignisse und nicht die Zeit, die ein Fahrer oder Beschleuniger Inferenz, möglicherweise durch Kontextwechsel unterbrochen.

Wenn beispielsweise Inferenz 1 beginnt, stoppt der Fahrer die Arbeit. Inferenz 2, dann wird fortgesetzt und Inferenz 1 beendet, die Ausführungszeit Inferenz 1 enthält die Zeit, zu der die Arbeit angehalten wurde, um Inferenz 2 auszuführen.

Diese Zeitangaben können bei der Produktionsbereitstellung eines zum Erfassen von Telemetriedaten zur Offlinenutzung. Anhand der Zeitdaten können Sie die App modifizieren, um die Leistung zu steigern.

Beachten Sie bei der Verwendung dieser Funktion Folgendes:

  • Das Erfassen von Zeitinformationen kann Leistungskosten verursachen.
  • Die für die Fahrt aufgewendete Zeit kann nur vom Fahrer berechnet werden. Beschleuniger, ohne die in der NNAPI-Laufzeit und im IPC verbrachte Zeit.
  • Sie können diese APIs nur mit einem ANeuralNetworksExecution verwenden, der erstellt mit ANeuralNetworksCompilation_createForDevices mit numDevices = 1.
  • Es ist kein Fahrer erforderlich, um Zeitinformationen zu melden.

Profil für Ihre Anwendung mit Android Systrace erstellen

Ab Android 10 generiert NNAPI automatisch systrace-Ereignissen, die mit denen Sie ein Profil für Ihre Anwendung erstellen können.

Die NNAPI Source wird mit einem parse_systrace-Dienstprogramm geliefert, um systrace-Ereignisse, die von Ihrer Anwendung generiert wurden, und generieren eine Tabellenansicht, in der die in den verschiedenen Phasen des Modelllebenszyklus verbrachte Zeit (Instanziierung, Vorbereitung, Durchführung und Beendigung der Zusammenstellung) und verschiedener Ebenen des Anwendungen. Ihre Anwendung ist in folgende Ebenen aufgeteilt:

  • Application: der Hauptanwendungscode
  • Runtime: NNAPI-Laufzeit
  • IPC: Die Kommunikation zwischen Prozessen zwischen der NNAPI-Laufzeit und dem Treiber Code
  • Driver: der Beschleunigertreiberprozess.

Analysedaten zur Profilerstellung generieren

Angenommen, Sie haben sich den AOSP-Quellbaum unter $ANDROID_BUILD_TOP angesehen und mit dem Beispiel zur TFLite-Bildklassifizierung als Zielanwendung haben, können Sie die NNAPI-Profildaten mit der Methode folgenden Schritten:

  1. Starten Sie das Android-Systrace mit dem folgenden Befehl:
$ANDROID_BUILD_TOP/external/chromium-trace/systrace.py  -o trace.html -a org.tensorflow.lite.examples.classification nnapi hal freq sched idle load binder_driver

Der Parameter -o trace.html gibt an, dass die Traces geschrieben in den trace.html. Wenn Sie für eine eigene Anwendung ein Profil erstellen, Ersetzen Sie org.tensorflow.lite.examples.classification durch den Prozessnamen. die in deinem App-Manifest angegeben sind.

Dadurch wird eine Ihrer Shell-Konsolen ausgelastet. Führen Sie den Befehl nicht in Hintergrund, da sie interaktiv auf das Beenden eines enter wartet.

  1. Starten Sie nach dem Start des Systrace-Collectors Ihre App und führen Sie den Benchmark-Test zu testen.

In unserem Fall können Sie die App Image Classification (Bildklassifizierung) in Android Studio starten. oder direkt über die Benutzeroberfläche des Testtelefons, falls die App bereits installiert ist. Zum Generieren einiger NNAPI-Daten müssen Sie die App für die Verwendung von NNAPI konfigurieren, indem Sie Auswahl von NNAPI als Zielgerät im Dialogfeld für die App-Konfiguration.

  1. Beenden Sie nach Abschluss des Tests Systrace, indem Sie enter auf das Konsolenterminal seit Schritt 1 aktiv ist.

  2. Führen Sie das Dienstprogramm systrace_parser aus, um kumulative Statistiken zu generieren:

$ANDROID_BUILD_TOP/frameworks/ml/nn/tools/systrace_parser/parse_systrace.py --total-times trace.html

Der Parser akzeptiert die folgenden Parameter: – --total-times: zeigt die Gesamtzeit an, die in einer Ebene verbracht wurde, einschließlich der Zeit Wartezeit für die Ausführung eines Aufrufs einer zugrunde liegenden Ebene – --print-detail: gibt alle von Systrace erfassten Ereignisse aus - --per-execution: gibt nur die Ausführung und ihre Unterphasen aus (anhand der Ausführungszeit) anstelle der Statistiken für alle Phasen – --json: erzeugt die Ausgabe im JSON-Format

Hier ein Beispiel für die Ausgabe:

===========================================================================================================================================
NNAPI timing summary (total time, ms wall-clock)                                                      Execution
                                                           ----------------------------------------------------
              Initialization   Preparation   Compilation           I/O       Compute      Results     Ex. total   Termination        Total
              --------------   -----------   -----------   -----------  ------------  -----------   -----------   -----------   ----------
Application              n/a         19.06       1789.25           n/a           n/a         6.70         21.37           n/a      1831.17*
Runtime                    -         18.60       1787.48          2.93         11.37         0.12         14.42          1.32      1821.81
IPC                     1.77             -       1781.36          0.02          8.86            -          8.88             -      1792.01
Driver                  1.04             -       1779.21           n/a           n/a          n/a          7.70             -      1787.95

Total                   1.77*        19.06*      1789.25*         2.93*        11.74*        6.70*        21.37*         1.32*     1831.17*
===========================================================================================================================================
* This total ignores missing (n/a) values and thus is not necessarily consistent with the rest of the numbers

Der Parser kann fehlschlagen, wenn die erfassten Ereignisse keinen vollständigen Anwendungs-Trace. Insbesondere kann es fehlschlagen, wenn Systrace-Ereignisse generiert wurden. zum Markieren des Endes eines Abschnitts sind im Trace ohne zugehörige Startereignis des Abschnitts. Das passiert in der Regel, wenn Ereignisse aus früheren Profilerstellungssitzung generiert, wenn Sie den Systrace-Collector starten. In diesem Fall müssen Sie die Profilerstellung noch einmal ausführen.

Statistiken für Ihren Anwendungscode zur systrace_parser-Ausgabe hinzufügen

Die Anwendung „parse_systrace“ basiert auf dem integrierten Android-Systrace Funktionalität. Sie können Traces für bestimmte Vorgänge in Ihrer Anwendung mithilfe der Systrace-API (für Java , für native Anwendungen ) mit benutzerdefinierten Ereignisnamen.

So verknüpfen Sie Ihre benutzerdefinierten Ereignisse mit Phasen des Anwendungslebenszyklus: Stellen Sie Ihrem Ereignisnamen einen der folgenden Strings voran:

  • [NN_LA_PI]: Ereignis auf Anwendungsebene zur Initialisierung
  • [NN_LA_PP]: Ereignis auf Anwendungsebene für die Vorbereitung
  • [NN_LA_PC]: Ereignis auf Anwendungsebene für die Kompilierung
  • [NN_LA_PE]: Ereignis auf Anwendungsebene für die Ausführung

Hier ist ein Beispiel dafür, wie Sie das Beispiel für die TFLite-Bildklassifizierung ändern können indem Sie einen runInferenceModel-Abschnitt für die Execution-Phase und den Application Ebene mit weiteren Abschnitten preprocessBitmap, die nicht in NNAPI-Traces berücksichtigt. Der Bereich runInferenceModel wird Teil der systrace-Ereignisse, die vom nnapi-Systrace-Parser verarbeitet wurden:

Kotlin

/** Runs inference and returns the classification results. */
fun recognizeImage(bitmap: Bitmap): List {
   // This section won’t appear in the NNAPI systrace analysis
   Trace.beginSection("preprocessBitmap")
   convertBitmapToByteBuffer(bitmap)
   Trace.endSection()

   // Run the inference call.
   // Add this method in to NNAPI systrace analysis.
   Trace.beginSection("[NN_LA_PE]runInferenceModel")
   long startTime = SystemClock.uptimeMillis()
   runInference()
   long endTime = SystemClock.uptimeMillis()
   Trace.endSection()
    ...
   return recognitions
}

Java

/** Runs inference and returns the classification results. */
public List recognizeImage(final Bitmap bitmap) {

 // This section won’t appear in the NNAPI systrace analysis
 Trace.beginSection("preprocessBitmap");
 convertBitmapToByteBuffer(bitmap);
 Trace.endSection();

 // Run the inference call.
 // Add this method in to NNAPI systrace analysis.
 Trace.beginSection("[NN_LA_PE]runInferenceModel");
 long startTime = SystemClock.uptimeMillis();
 runInference();
 long endTime = SystemClock.uptimeMillis();
 Trace.endSection();
  ...
 Trace.endSection();
 return recognitions;
}

Servicequalität

Ab Android 11 ermöglicht NNAPI eine bessere Dienstqualität, So kann eine Anwendung die relativen Prioritäten ihrer Modelle angeben, die maximale Zeit, die für die Vorbereitung eines bestimmten Modells erwartet wird, sowie für die Durchführung einer bestimmten Berechnung benötigt. Außerdem werden mit Android 11 zusätzliche NNAPI-Ergebniscodes mit denen Anwendungen Fehler wie verpasste Ausführungen verstehen können Fristen einzuhalten.

Priorität einer Arbeitslast festlegen

Rufen Sie zum Festlegen der Priorität einer NNAPI-Arbeitslast den ANeuralNetworksCompilation_setPriority() bevor Sie ANeuralNetworksCompilation_finish() angerufen haben.

Fristen festlegen

Anwendungen können Fristen sowohl für die Modellkompilierung als auch für die Inferenz festlegen.

Weitere Informationen zu Operanden

Im folgenden Abschnitt werden weiterführende Themen zur Verwendung von Operanden behandelt.

Quantisierte Tensoren

Ein quantisierter Tensor ist eine kompakte Möglichkeit, ein n-dimensionales Array von Gleitkommawerte.

NNAPI unterstützt asymmetrische quantisierte 8-Bit-Tensoren. Für diese Tensoren jeder Zelle wird durch eine 8-Bit-Ganzzahl dargestellt. Verknüpft mit Tensor eine Skala und ein Nullpunktwert. Diese werden verwendet, um die 8-Bit- in die dargestellten Gleitkommawerte umgewandelt werden.

Die Formel lautet:

(cellValue - zeroPoint) * scale

wobei der Wert "nullPoint" eine 32-Bit-Ganzzahl und die Skalierung eine 32-Bit-Gleitkommazahl Punktzahl.

Im Vergleich zu Tensoren von 32-Bit-Gleitkommawerten, quantisierten 8-Bit-Tensoren haben zwei Vorteile:

  • Ihre Anwendung ist kleiner, da die trainierten Gewichtungen ein Viertel der Größe einnehmen von 32-Bit-Tensoren.
  • Berechnungen können oft schneller ausgeführt werden. Grund dafür ist der geringere Betrag der Daten, die aus dem Speicher abgerufen werden müssen, und die Effizienz von Prozessoren, wie z. B. DSPs bei der Berechnung ganzer Zahlen.

Es ist zwar möglich, ein Gleitkommamodell in ein quantisiertes Modell zu konvertieren, Erfahrung hat gezeigt, dass bessere Ergebnisse erzielt werden, wenn man ein quantisiertes modellieren können. Tatsächlich lernt das neuronale Netzwerk, den Detaillierungsgrad jedes Werts. Für jeden quantisierten Tensor werden die Skala und ZeroPoint-Werte werden während des Trainingsprozesses bestimmt.

In NNAPI definieren Sie quantisierte Tensortypen, indem Sie das Typfeld des ANeuralNetworksOperandType Datenstruktur zu verstehen, ANEURALNETWORKS_TENSOR_QUANT8_ASYMM Sie geben auch den „scale“- und „nullPoint“-Wert des Tensors in diesen Daten an Struktur.

Neben den asymmetrischen quantisierten 8-Bit-Tensoren unterstützt NNAPI Folgendes:

Optionale Operanden

Einige Vorgänge wie ANEURALNETWORKS_LSH_PROJECTION, optionale Operanden. Um im Modell anzugeben, dass der optionale Operand ist wird die Funktion ANeuralNetworksModel_setOperandValue() und übergeben Sie NULL für den Puffer und 0 für die Länge.

Ob die Entscheidung, ob der Operand vorhanden ist oder nicht, bei jedem -Ausführung angeben, geben Sie an, dass der Operand weggelassen wird, indem ANeuralNetworksExecution_setInput() oder ANeuralNetworksExecution_setOutput() und übergeben NULL für den Puffer und 0 für die Länge.

Tensoren mit unbekanntem Rang

Mit Android 9 (API-Level 28) wurden Modelloperanden mit unbekannten Dimensionen eingeführt, aber bekannten Rang (die Anzahl der Dimensionen). Android 10 (API-Level 29) eingeführt Tensoren unbekannten Rangs, wie in ANeuralNetworksOperandType.

NNAPI-Benchmark

Der NNAPI-Benchmark ist auf AOSP in platform/test/mlts/benchmark verfügbar (Benchmark-Anwendung) und platform/test/mlts/models (Modelle und Datasets).

Die Benchmark bewertet Latenz und Genauigkeit und vergleicht Fahrer mit denselben mit Tensorflow Lite auf der CPU für dieselben Modelle und Datasets.

So verwenden Sie die Benchmark:

  1. Schließen Sie ein Android-Zielgerät an Ihren Computer an, öffnen Sie ein Terminalfenster und Achte darauf, dass das Gerät über ADB erreichbar ist.

  2. Wenn mehrere Android-Geräte verbunden sind, exportieren Sie das Zielgerät ANDROID_SERIAL.

  3. Rufen Sie das Android-Quellverzeichnis der obersten Ebene auf.

  4. Führen Sie folgende Befehle aus:

    lunch aosp_arm-userdebug # Or aosp_arm64-userdebug if available
    ./test/mlts/benchmark/build_and_run_benchmark.sh
    

    Am Ende einer Benchmarkausführung werden die Ergebnisse als HTML-Seite dargestellt. an xdg-open übergeben.

NNAPI-Logs

NNAPI generiert nützliche Diagnoseinformationen in den Systemprotokollen. Verwenden Sie zum Analysieren der Logs den logcat. Dienstprogramm.

Aktivieren Sie die ausführliche NNAPI-Protokollierung für bestimmte Phasen oder Komponenten, indem Sie die Attribut debug.nn.vlog (mit adb shell) der folgenden Liste von Werten hinzufügen, durch Leerzeichen, Doppelpunkt oder Komma getrennt:

  • model: Modellerstellung
  • compilation: Generierung des Modellausführungsplans und Kompilierung
  • execution: Modellausführung
  • cpuexe: Ausführung von Vorgängen mit der NNAPI-CPU-Implementierung
  • manager: NNAPI-Erweiterungen sowie Informationen zu verfügbaren Schnittstellen und Funktionen
  • all oder 1: alle Elemente oben

Um beispielsweise ein vollständiges ausführliches Logging zu aktivieren, verwenden Sie den Befehl adb shell setprop debug.nn.vlog all Deaktivieren Sie die ausführliche Protokollierung mit dem Befehl adb shell setprop debug.nn.vlog '""'

Nach der Aktivierung generiert die ausführliche Protokollierung Logeinträge auf INFO-Ebene mit einem Tag, das auf den Phasen- oder Komponentennamen eingestellt ist.

Neben den von debug.nn.vlog gesteuerten Nachrichten bieten NNAPI-Komponenten weitere Logeinträge auf verschiedenen Ebenen, die jeweils ein bestimmtes Log-Tag verwenden.

Um eine Liste der Komponenten zu erhalten, durchsuchen Sie die Quellstruktur mithilfe der folgenden Ausdruck:

grep -R 'define LOG_TAG' | awk -F '"' '{print $2}' | sort -u | egrep -v "Sample|FileTag|test"

Dieser Ausdruck gibt derzeit die folgenden Tags zurück:

  • BurstBuilder
  • Rückrufe
  • CompilationBuilder
  • CPUExecutor
  • AusführungsBuilder
  • ExecutionBurstController
  • ExecutionBurstServer
  • Ausführungsplan
  • Fibonacci-Treiber
  • GraphDump
  • IndexedShapeWrapper
  • IonWatcher
  • Manager
  • Arbeitsspeicher
  • MemoryUtils
  • MetaModel
  • Modellargumentinformationen
  • Modell-Builder
  • Neuronale Netzwerke
  • OperationResolver
  • Aufgaben und Ablauf
  • Betriebsprogramme
  • Paketinformationen
  • TokenHasher
  • Typmanager
  • Dienstprogramme
  • ValidHal
  • Versionierte Schnittstellen

Um die Ebene der Logeinträge zu steuern, die von logcat angezeigt werden, verwenden Sie die Umgebungsvariable ANDROID_LOG_TAGS.

Um alle NNAPI-Logmeldungen anzuzeigen und alle anderen zu deaktivieren, legen Sie ANDROID_LOG_TAGS auf Folgendes:

BurstBuilder:V Callbacks:V CompilationBuilder:V CpuExecutor:V ExecutionBuilder:V ExecutionBurstController:V ExecutionBurstServer:V ExecutionPlan:V FibonacciDriver:V GraphDump:V IndexedShapeWrapper:V IonWatcher:V Manager:V MemoryUtils:V Memory:V MetaModel:V ModelArgumentInfo:V ModelBuilder:V NeuralNetworks:V OperationResolver:V OperationsUtils:V Operations:V PackageInfo:V TokenHasher:V TypeManager:V Utils:V ValidateHal:V VersionedInterfaces:V *:S.

Sie können ANDROID_LOG_TAGS mit dem folgenden Befehl festlegen:

export ANDROID_LOG_TAGS=$(grep -R 'define LOG_TAG' | awk -F '"' '{ print $2 ":V" }' | sort -u | egrep -v "Sample|FileTag|test" | xargs echo -n; echo ' *:S')

Beachten Sie, dass dies nur ein Filter ist, der für logcat gilt. Sie müssen noch Legen Sie das Attribut debug.nn.vlog auf all fest, um ausführliche Loginformationen zu generieren.