Neural Networks API-Treiber

Auf dieser Seite erhalten Sie einen Überblick darüber, wie Sie einen NNAPI-Treiber (Neural Networks API) implementieren. Weitere Informationen finden Sie in der Dokumentation in den HAL-Definitiondateien in hardware/interfaces/neuralnetworks. Eine Beispielimplementierung des Treibers finden Sie unter frameworks/ml/nn/driver/sample.

Weitere Informationen zur Neural Networks API finden Sie unter Neural Networks API.

HAL für neuronale Netzwerke

Die HAL für neuronale Netze (Neural Networks, NN) definiert eine Abstraktion der verschiedenen Geräte, z. B. Grafikprozessoren (GPUs) und digitale Signalprozessoren (DSPs), die in einem Produkt (z. B. einem Smartphone oder Tablet) enthalten sind. Die Treiber für diese Geräte müssen der NN HAL entsprechen. Die Schnittstelle wird in den HAL-Definitionen in hardware/interfaces/neuralnetworks angegeben.

Der allgemeine Ablauf der Schnittstelle zwischen dem Framework und einem Treiber ist in Abbildung 1 dargestellt.

Ablauf von neuronalen Netzwerken

Abbildung 1: Ablauf von neuronalen Netzwerken

Initialisierung

Bei der Initialisierung fragt das Framework den Treiber mithilfe von IDevice::getCapabilities_1_3 nach seinen Funktionen ab. Die Struktur @1.3::Capabilities umfasst alle Datentypen und stellt die Leistung ohne Entspannung mithilfe eines Vektors dar.

Um zu bestimmen, wie Berechnungen den verfügbaren Geräten zugewiesen werden, verwendet das Framework die Funktionen, um zu ermitteln, wie schnell und energieeffizient jeder Treiber eine Ausführung ausführen kann. Dazu muss der Treiber standardisierte Leistungszahlen angeben, die auf der Ausführung von Referenzarbeitslasten basieren.

Um die Werte zu ermitteln, die der Treiber als Antwort auf IDevice::getCapabilities_1_3 zurückgibt, können Sie die NNAPI-Benchmark-App verwenden, um die Leistung für die entsprechenden Datentypen zu messen. Die Modelle „MobileNet v1“, „MobileNet v2“, asr_float und tts_float werden für die Leistungsmessung bei 32‑Bit-Gleitkommawerten empfohlen. Die quantisierten Modelle „MobileNet v1“ und „MobileNet v2“ werden für 8‑Bit-quantisierte Werte empfohlen. Weitere Informationen finden Sie unter Android Machine Learning Test Suite.

Unter Android 9 und niedriger enthält die Capabilities-Struktur nur Informationen zur Treiberleistung für Gleitkomma- und quantisierte Tensoren, aber keine skalaren Datentypen.

Im Rahmen der Initialisierung kann das Framework weitere Informationen abfragen, indem es IDevice::getType, IDevice::getVersionString, IDevice:getSupportedExtensions und IDevice::getNumberOfCacheFilesNeeded verwendet.

Zwischen den Neustarts des Produkts erwartet das Framework, dass alle in diesem Abschnitt beschriebenen Abfragen immer dieselben Werte für einen bestimmten Treiber zurückgeben. Andernfalls kann die Leistung einer App, die diesen Treiber verwendet, beeinträchtigt sein oder es kann zu einem falschen Verhalten kommen.

Compilation

Das Framework bestimmt, welche Geräte verwendet werden sollen, wenn es eine Anfrage von einer App erhält. Unter Android 10 können Apps die Geräte ermitteln und angeben, aus denen das Framework auswählt. Weitere Informationen finden Sie unter Geräteerkennung und ‑zuweisung.

Beim Kompilieren des Modells sendet das Framework das Modell an jeden Kandidatentreiber, indem es IDevice::getSupportedOperations_1_3 aufruft. Jeder Treiber gibt ein Array von Booleschen Werten zurück, die angeben, welche Vorgänge des Modells unterstützt werden. Ein Treiber kann aus verschiedenen Gründen feststellen, dass er einen bestimmten Vorgang nicht unterstützen kann. Beispiel:

  • Der Datentyp wird vom Treiber nicht unterstützt.
  • Der Treiber unterstützt nur Vorgänge mit bestimmten Eingabeparametern. Ein Treiber unterstützt beispielsweise 3 × 3 und 5 × 5, aber keine 7 × 7-Konvolutionsoperationen.
  • Der Treiber hat Speichereinschränkungen, die die Verarbeitung großer Grafiken oder Eingaben verhindern.

Während der Kompilierung können die Eingabe-, Ausgabe- und internen Operanden des Modells, wie in OperandLifeTime beschrieben, unbekannte Dimensionen oder einen unbekannten Rang haben. Weitere Informationen finden Sie unter Ausgabeform.

Das Framework weist jeden ausgewählten Treiber an, sich durch Aufruf von IDevice::prepareModel_1_3 auf die Ausführung eines Teils des Modells vorzubereiten. Jeder Treiber kompiliert dann seine Teilmenge. Ein Fahrer kann beispielsweise Code generieren oder eine neu angeordnete Kopie der Gewichte erstellen. Da zwischen der Kompilierung des Modells und der Ausführung von Anfragen viel Zeit vergehen kann, sollten Ressourcen wie große Teile des Gerätespeichers nicht während der Kompilierung zugewiesen werden.

Bei Erfolg gibt der Treiber einen @1.3::IPreparedModel-Handle zurück. Wenn der Treiber beim Vorbereiten seiner Teilmenge des Modells einen Fehlercode zurückgibt, führt das Framework das gesamte Modell auf der CPU aus.

Um die Zeit zu verkürzen, die für die Kompilierung beim Starten einer App benötigt wird, kann ein Treiber Kompilierungsartefakte im Cache speichern. Weitere Informationen finden Sie unter Kompilierungs-Caching.

Umsetzung

Wenn eine App das Framework auffordert, eine Anfrage auszuführen, ruft das Framework standardmäßig die HAL-Methode IPreparedModel::executeSynchronously_1_3 auf, um eine synchrone Ausführung auf einem vorbereiteten Modell durchzuführen. Eine Anfrage kann auch asynchron mit der Methode execute_1_3, der Methode executeFenced (siehe Abgeschirmte Ausführung) oder mit einer Burst-Ausführung ausgeführt werden.

Synchrone Ausführungsaufrufe verbessern die Leistung und reduzieren den Thread-Overhead im Vergleich zu asynchronen Aufrufen, da die Steuerung erst nach Abschluss der Ausführung an den App-Prozess zurückgegeben wird. Das bedeutet, dass der Treiber keinen separaten Mechanismus benötigt, um den App-Prozess darüber zu informieren, dass eine Ausführung abgeschlossen ist.

Bei der asynchronen execute_1_3-Methode wird die Steuerung nach Beginn der Ausführung an den App-Prozess zurückgegeben. Der Treiber muss das Framework über die @1.3::IExecutionCallback benachrichtigen, wenn die Ausführung abgeschlossen ist.

Der Parameter Request, der an die Methode „execute“ übergeben wird, enthält die für die Ausführung verwendeten Eingabe- und Ausgabeoperanden. Der Arbeitsspeicher, in dem die Operandendaten gespeichert werden, muss die Zeilenmajoritätsreihenfolge verwenden, wobei die erste Dimension am langsamsten iteriert wird und am Ende jeder Zeile kein Padding vorhanden ist. Weitere Informationen zu den Operandentypen finden Sie unter Operanden.

Bei Treibern mit NN HAL 1.2 oder höher werden nach Abschluss einer Anfrage der Fehlerstatus, die Ausgabeform und Timing-Informationen an das Framework zurückgegeben. Während der Ausführung können Ausgabe- oder interne Operanden des Modells eine oder mehrere unbekannte Dimensionen oder einen unbekannten Rang haben. Wenn mindestens ein Ausgabeoperand eine unbekannte Dimension oder einen unbekannten Rang hat, muss der Treiber dynamisch skalierte Ausgabeinformationen zurückgeben.

Bei Treibern mit NN HAL 1.1 oder niedriger wird nur der Fehlerstatus zurückgegeben, wenn eine Anfrage abgeschlossen ist. Die Dimensionen für Eingabe- und Ausgabeoperanden müssen vollständig angegeben sein, damit die Ausführung erfolgreich abgeschlossen werden kann. Interne Operanden können eine oder mehrere unbekannte Dimensionen haben, müssen aber einen bestimmten Rang haben.

Bei Nutzeranfragen, die mehrere Treiber umfassen, ist das Framework für die Reservierung des Zwischenspeichers und die Sequenzierung der Aufrufe an jeden Treiber verantwortlich.

Mehrere Anfragen können parallel an dieselbe @1.3::IPreparedModel gesendet werden. Der Treiber kann Anfragen parallel ausführen oder die Ausführung serialisieren.

Das Framework kann einen Fahrer auffordern, mehr als ein vorbereitetes Modell zu behalten. Beispiel: Modell m1 vorbereiten, m2 vorbereiten, Anfrage r1 auf m1 ausführen, r2 auf m2 ausführen, r3 auf m1 ausführen, r4 auf m2 ausführen, m1 (siehe Bereinigung) und m2 freigeben.

Um eine langsame Erstausführung zu vermeiden, die zu einer schlechten Nutzererfahrung führen kann (z. B. ein Ruckeln beim ersten Frame), sollte der Treiber die meisten Initialisierungen in der Kompilierungsphase ausführen. Die Initialisierung bei der ersten Ausführung sollte auf Aktionen beschränkt sein, die sich bei vorzeitiger Ausführung negativ auf die Systemintegrität auswirken, z. B. das Reservieren großer temporärer Puffer oder die Erhöhung der Taktrate eines Geräts. Bei Treibern, die nur eine begrenzte Anzahl gleichzeitiger Modelle vorbereiten können, muss die Initialisierung möglicherweise bei der ersten Ausführung erfolgen.

Unter Android 10 oder höher kann der Client bei mehreren Ausführungen mit demselben vorbereiteten Modell in schneller Folge ein Ausführungs-Burst-Objekt verwenden, um zwischen App- und Treiberprozessen zu kommunizieren. Weitere Informationen finden Sie unter Burst-Ausführungen und schnelle Nachrichtenwarteschlangen.

Um die Leistung bei mehreren Ausführungen in schneller Folge zu verbessern, kann der Treiber temporäre Puffer beibehalten oder Taktraten erhöhen. Es wird empfohlen, einen Watchdog-Thread zu erstellen, um Ressourcen freizugeben, wenn nach einem bestimmten Zeitraum keine neuen Anfragen erstellt werden.

Ausgabeform

Bei Anfragen, bei denen für einen oder mehrere Ausgabeoperanden nicht alle Dimensionen angegeben sind, muss der Treiber nach der Ausführung eine Liste von Ausgabeformen mit den Dimensionsinformationen für jeden Ausgabeoperanden bereitstellen. Weitere Informationen zu Dimensionen finden Sie unter OutputShape.

Wenn eine Ausführung aufgrund eines zu kleinen Ausgabepuffers fehlschlägt, muss der Treiber in der Liste der Ausgabeformen angeben, welche Ausgabeoperanden eine unzureichende Puffergröße haben. Außerdem sollte er so viele Dimensionsinformationen wie möglich melden und für unbekannte Dimensionen Null verwenden.

Timing

Unter Android 10 kann eine App nach der Ausführungszeit fragen, wenn sie ein einzelnes Gerät für die Kompilierung angegeben hat. Weitere Informationen finden Sie unter MeasureTiming und Geräteerkennung und ‑zuweisung. In diesem Fall muss ein NN HAL 1.2-Treiber die Ausführungsdauer messen oder UINT64_MAX melden, um anzugeben, dass die Dauer nicht verfügbar ist. Der Treiber sollte Leistungseinbußen durch das Messen der Ausführungsdauer minimieren.

Der Treiber meldet die folgenden Zeiträume in Mikrosekunden in der Struktur Timing:

  • Ausführungszeit auf dem Gerät:Beinhaltet nicht die Ausführungszeit im Treiber, der auf dem Hostprozessor ausgeführt wird.
  • Ausführungszeit im Treiber:Umfasst die Ausführungszeit auf dem Gerät.

Diese Zeiträume müssen die Zeit umfassen, in der die Ausführung ausgesetzt ist, z. B. wenn die Ausführung durch andere Aufgaben unterbrochen wurde oder auf die Verfügbarkeit einer Ressource gewartet wird.

Wenn der Treiber nicht aufgefordert wurde, die Ausführungsdauer zu messen, oder wenn ein Ausführungsfehler auftritt, muss der Treiber die Dauer als UINT64_MAX angeben. Auch wenn der Treiber aufgefordert wurde, die Ausführungsdauer zu messen, kann stattdessen UINT64_MAX für die Zeit auf dem Gerät, die Zeit im Treiber oder beides gemeldet werden. Wenn der Treiber beide Dauern als anderen Wert als UINT64_MAX meldet, muss die Ausführungszeit im Treiber der Zeit auf dem Gerät entsprechen oder diese überschreiten.

Ausführung mit Sperre

In Android 11 können Ausführungen mit NNAPI auf eine Liste von sync_fence-Handles warten und optional ein sync_fence-Objekt zurückgeben, das signalisiert, dass die Ausführung abgeschlossen ist. Dadurch wird der Overhead für kleine Sequenzmodelle und Streaminganwendungsfälle reduziert. Die abgegrenzte Ausführung ermöglicht außerdem eine effizientere Interoperabilität mit anderen Komponenten, die sync_fence signalisieren oder darauf warten können. Weitere Informationen zu sync_fence finden Sie unter Synchronisierungs-Framework.

Bei einer abgegrenzten Ausführung ruft das Framework die Methode IPreparedModel::executeFenced auf, um eine abgegrenzte, asynchrone Ausführung auf einem vorbereiteten Modell mit einem Vektor von Synchronisationsschranken zu starten, auf die gewartet werden soll. Wenn die asynchrone Aufgabe vor dem Rückgabewert des Aufrufs abgeschlossen ist, kann für sync_fence ein leerer Handle zurückgegeben werden. Außerdem muss ein IFencedExecutionCallback-Objekt zurückgegeben werden, damit das Framework den Fehlerstatus und die Dauer abfragen kann.

Nach Abschluss einer Ausführung können die folgenden beiden Timing-Werte abgefragt werden, die die Dauer der Ausführung messen: IFencedExecutionCallback::getExecutionInfo.

  • timingLaunched: Dauer zwischen dem Aufruf von executeFenced und dem Signalisieren der zurückgegebenen syncFence durch executeFenced.
  • timingFenced: Dauer zwischen dem Signalisieren aller Synchronisationsschranken, auf die die Ausführung wartet, und dem Signalisieren der zurückgegebenen syncFence durch executeFenced.

Kontrollfluss

Auf Geräten mit Android 11 oder höher enthält die NNAPI zwei Kontrollfluss-Vorgänge, IF und WHILE, die andere Modelle als Argumente annehmen und bedingt (IF) oder wiederholt (WHILE) ausführen. Weitere Informationen zur Implementierung finden Sie unter Kontrollfluss.

Dienstqualität

In Android 11 bietet die NNAPI eine verbesserte Dienstqualität (Quality of Service, QoS), da eine App die relativen Prioritäten ihrer Modelle, die maximale Zeit, die für die Vorbereitung eines Modells benötigt wird, und die maximale Zeit, die für die Ausführung benötigt wird, angeben kann. Weitere Informationen finden Sie unter Qualität des Dienstes.

Bereinigung

Wenn eine App die Verwendung eines vorbereiteten Modells beendet hat, gibt das Framework die Referenz auf das @1.3::IPreparedModel-Objekt frei. Wenn auf das IPreparedModel-Objekt nicht mehr verwiesen wird, wird es automatisch im Treiberdienst gelöscht, in dem es erstellt wurde. Modellspezifische Ressourcen können derzeit in der Implementierung des Destruktors des Treibers zurückgefordert werden. Wenn der Treiberdienst möchte, dass das IPreparedModel-Objekt automatisch gelöscht wird, wenn es vom Client nicht mehr benötigt wird, darf es nach der Rückgabe des IPreparedeModel-Objekts über IPreparedModelCallback::notify_1_3 keine Verweise mehr auf das IPreparedModel-Objekt enthalten.

CPU-Auslastung

Treiber sollen die CPU zum Einrichten von Berechnungen verwenden. Treiber sollten die CPU nicht für Graphenberechnungen verwenden, da dies die Fähigkeit des Frameworks beeinträchtigt, die Arbeit richtig zuzuweisen. Der Treiber sollte die Teile, die er nicht verarbeiten kann, an das Framework melden und den Rest dem Framework überlassen.

Das Framework bietet eine CPU-Implementierung für alle NNAPI-Vorgänge mit Ausnahme der vom Anbieter definierten Vorgänge. Weitere Informationen finden Sie unter Anbietererweiterungen.

Für die in Android 10 eingeführten Vorgänge (API-Level 29) gibt es nur eine Referenz-CPU-Implementierung, um zu prüfen, ob die CTS- und VTS-Tests korrekt sind. Die optimierten Implementierungen in mobilen Frameworks für maschinelles Lernen werden der CPU-Implementierung von NNAPI vorgezogen.

Dienstfunktionen

Die NNAPI-Codebase enthält Dienstprogrammfunktionen, die von Treiberdiensten verwendet werden können.

Die Datei frameworks/ml/nn/common/include/Utils.h enthält verschiedene Dienstprogrammfunktionen, z. B. für die Protokollierung und für die Umwandlung zwischen verschiedenen NN HAL-Versionen.

  • Vlogging: VLOG ist ein Wrapper-Makro für LOG von Android, das die Nachricht nur protokolliert, wenn das entsprechende Tag in der Property debug.nn.vlog festgelegt ist. initVLogMask() muss vor allen Aufrufen von VLOG aufgerufen werden. Mit dem Makro VLOG_IS_ON lässt sich prüfen, ob VLOG derzeit aktiviert ist. So kann komplizierter Logging-Code übersprungen werden, wenn er nicht benötigt wird. Der Wert der Property muss einer der folgenden sein:

    • Ein leerer String, der angibt, dass keine Protokollierung erfolgen soll.
    • Das Token 1 oder all, das angibt, dass alle Protokolle erstellt werden sollen.
    • Eine Liste von Tags, die durch Leerzeichen, Kommas oder Doppelpunkte getrennt ist und angibt, welche Protokollierung durchgeführt werden soll. Die Tags sind compilation, cpuexe, driver, execution, manager und model.
  • compliantWithV1_*: Gibt true zurück, wenn ein NN HAL-Objekt in denselben Typ einer anderen HAL-Version umgewandelt werden kann, ohne dass Informationen verloren gehen. Wenn Sie beispielsweise compliantWithV1_0 auf V1_2::Model anwenden, wird false zurückgegeben, wenn das Modell Vorgangstypen enthält, die in NN HAL 1.1 oder NN HAL 1.2 eingeführt wurden.

  • convertToV1_*: Konvertiert ein NN HAL-Objekt von einer Version in eine andere. Wenn die Umwandlung zu einem Informationsverlust führt, also wenn der Wert mit der neuen Version des Typs nicht vollständig dargestellt werden kann, wird eine Warnung protokolliert.

  • Funktionen: Mit den Funktionen nonExtensionOperandPerformance und update können Sie das Feld Capabilities::operandPerformance erstellen.

  • Abfragen von Eigenschaften von Typen: isExtensionOperandType, isExtensionOperationType, nonExtensionSizeOfData, nonExtensionOperandSizeOfData, nonExtensionOperandTypeIsScalar, tensorHasUnspecifiedDimensions.

Die Datei frameworks/ml/nn/common/include/ValidateHal.h enthält Dienstprogrammfunktionen, mit denen geprüft werden kann, ob ein HAL-Objekt für NN gemäß der Spezifikation der HAL-Version gültig ist.

  • validate*: Gibt true zurück, wenn das NN HAL-Objekt gemäß der Spezifikation der HAL-Version gültig ist. OEM-Typen und Erweiterungstypen werden nicht überprüft. Beispielsweise gibt validateModel false zurück, wenn das Modell einen Vorgang enthält, der auf einen nicht vorhandenen Operandenindex verweist, oder einen Vorgang, der von dieser HAL-Version nicht unterstützt wird.

Die Datei frameworks/ml/nn/common/include/Tracing.h enthält Makros, mit denen sich Informationen zum Systracing in den Code von neuronalen Netzwerken einfügen lassen. Ein Beispiel finden Sie in den NNTRACE_*-Makroaufrufen im Beispieltreiber.

Die Datei frameworks/ml/nn/common/include/GraphDump.h enthält eine Dienstfunktion, mit der der Inhalt einer Model-Datei zu Debugzwecken in grafischer Form gedumpt werden kann.

  • graphDump: Schreibt eine Darstellung des Modells im Graphviz-Format (.dot) in den angegebenen Stream (falls angegeben) oder in den Logcat (falls kein Stream angegeben ist).

Zertifizierungsstufe

Verwenden Sie zum Testen Ihrer Implementierung der NNAPI die VTS- und CTS-Tests, die im Android-Framework enthalten sind. VTS testet Ihre Treiber direkt (ohne Framework), während CTS sie indirekt über das Framework testet. Dabei wird jede API-Methode getestet und geprüft, ob alle von den Treibern unterstützten Vorgänge ordnungsgemäß funktionieren und Ergebnisse liefern, die den Genauigkeitsanforderungen entsprechen.

Die Anforderungen an die Genauigkeit in CTS und VTS für die NNAPI sind:

  • Gleitkomma: abs(erwarteter Wert − tatsächlicher Wert) <= atol + rtol  * abs(erwarteter Wert); wobei:

    • Für fp32: atol = 1e-5f, rtol = 5.0f * 1.1920928955078125e-7
    • Für fp16: atol = rtol = 5.0f * 0.0009765625f
  • Quantisiert:Abweichung von 1 (außer mobilenet_quantized, bei dem es eine Abweichung von 3 gibt)

  • Boolescher Wert:genaue Übereinstimmung

Eine Möglichkeit, wie CTS NNAPI testet, besteht darin, feste pseudozufällige Graphen zu generieren, mit denen die Ausführungsergebnisse der einzelnen Treiber mit der NNAPI-Referenzimplementierung getestet und verglichen werden. Wenn die Ergebnisse für Treiber mit NN HAL 1.2 oder höher nicht die Genauigkeitskriterien erfüllen, meldet CTS einen Fehler und lädt eine Spezifikationsdatei für das fehlgeschlagene Modell unter /data/local/tmp zum Debugging herunter. Weitere Informationen zu den Kriterien für die Genauigkeit finden Sie unter TestRandomGraph.cpp und TestHarness.h.

Fuzz-Test

Der Zweck von Fuzz-Tests besteht darin, Abstürze, Behauptungen, Speicherverstöße oder allgemein undefiniertes Verhalten im zu testenden Code aufgrund von Faktoren wie unerwarteten Eingaben zu finden. Für NNAPI-Fuzz-Tests verwendet Android Tests, die auf libFuzzer basieren. Diese sind beim Fuzzing effizient, da sie die Zeilenabdeckung früherer Testfälle verwenden, um neue zufällige Eingaben zu generieren. LibFuzzer bevorzugt beispielsweise Testfälle, die in neuen Codezeilen ausgeführt werden. Dadurch wird die Zeit, die für die Suche nach problematischem Code benötigt wird, erheblich reduziert.

Wenn Sie Fuzz-Tests zur Validierung Ihrer Treiberimplementierung ausführen möchten, ändern Sie frameworks/ml/nn/runtime/test/android_fuzzing/DriverFuzzTest.cpp im libneuralnetworks_driver_fuzzer-Testdienstprogramm in AOSP, um Ihren Treibercode einzubinden. Weitere Informationen zu NNAPI-Fuzz-Tests finden Sie unter frameworks/ml/nn/runtime/test/android_fuzzing/README.md.

Sicherheit

Da App-Prozesse direkt mit dem Prozess eines Treibers kommunizieren, müssen Treiber die Argumente der empfangenen Aufrufe prüfen. Diese Validierung wird von VTS durchgeführt. Der Bestätigungscode befindet sich in frameworks/ml/nn/common/include/ValidateHal.h.

Außerdem müssen Fahrer dafür sorgen, dass Apps andere Apps nicht beeinträchtigen, wenn sie dasselbe Gerät verwenden.

Android Machine Learning Test Suite

Die Android Machine Learning Test Suite (MLTS) ist ein NNAPI-Benchmark, der in CTS und VTS enthalten ist und die Genauigkeit echter Modelle auf Geräten von Anbietern validiert. Der Benchmark bewertet Latenz und Genauigkeit und vergleicht die Ergebnisse der Treiber mit den Ergebnissen, die mit TF Lite auf der CPU für dasselbe Modell und dieselben Datensätze erzielt wurden. So wird sichergestellt, dass die Genauigkeit eines Treibers nicht schlechter ist als die der CPU-Referenzimplementierung.

Entwickler der Android-Plattform verwenden MLTS auch, um die Latenz und Genauigkeit von Treibern zu bewerten.

Der NNAPI-Benchmark ist in zwei Projekten in AOSP zu finden:

Modelle und Datensätze

Für den NNAPI-Benchmark werden die folgenden Modelle und Datasets verwendet.

  • MobileNetV1 mit Quantisierung in Gleitkomma- und u8-Format in verschiedenen Größen, ausgeführt auf einer kleinen Teilmenge (1.500 Bilder) des Open Images Datasets Version 4.
  • MobileNetV2 mit Quantisierung in Gleitkomma- und u8-Format in verschiedenen Größen, ausgeführt auf einer kleinen Teilmenge (1.500 Bilder) des Open Images Datasets Version 4.
  • LSTM-basiertes (Long Short-Term Memory) akustisches Modell für die Text-zu-Sprache-Technologie, das auf einer kleinen Teilmenge des CMU Arctic-Sets ausgeführt wird.
  • LSTM-basiertes akustisches Modell für die automatische Spracherkennung, das auf einem kleinen Teil des LibriSpeech-Datasets ausgeführt wird.

Weitere Informationen finden Sie unter platform/test/mlts/models.

Stresstests

Die Android Machine Learning Test Suite umfasst eine Reihe von Crashtests, mit denen die Resilienz von Treibern bei hoher Auslastung oder in Extremfällen des Nutzerverhaltens überprüft wird.

Alle Crashtests bieten die folgenden Funktionen:

  • Blockierungserkennung:Wenn der NNAPI-Client während eines Tests hängt, schlägt der Test mit dem Fehlergrund HANG fehl und die Testsuite geht zum nächsten Test über.
  • NNAPI-Clientabsturzerkennung:Tests überstehen Clientabstürze und schlagen mit dem Fehlergrund CRASH fehl.
  • Treiberabsturzerkennung:Mit Tests können Treiberabstürze erkannt werden, die zu einem Fehler bei einem NNAPI-Aufruf führen. Beachten Sie, dass es zu Abstürzen in Treiberprozessen kommen kann, die weder zu einem NNAPI-Fehler noch zu einem Testfehler führen. Um diese Art von Fehlern abzudecken, wird empfohlen, den Befehl tail im Systemprotokoll für treiberbezogene Fehler oder Abstürze auszuführen.
  • Ausrichtung auf alle verfügbaren Accelerators:Die Tests werden auf alle verfügbaren Treiber ausgeführt.

Bei allen Crashtests gibt es vier mögliche Ergebnisse:

  • SUCCESS: Die Ausführung wurde ohne Fehler abgeschlossen.
  • FAILURE: Die Ausführung ist fehlgeschlagen. Wird in der Regel durch einen Fehler beim Testen eines Modells verursacht und bedeutet, dass der Treiber das Modell nicht kompilieren oder ausführen konnte.
  • HANG: Der Testprozess reagiert nicht mehr.
  • CRASH: Der Testprozess ist abgestürzt.

Weitere Informationen zu Stresstests und eine vollständige Liste der Crashtests finden Sie unter platform/test/mlts/benchmark/README.txt.

MLTS verwenden

So verwenden Sie die MLTS:

  1. Verbinden Sie ein Zielgerät mit Ihrer Workstation und prüfen Sie, ob es über adb erreichbar ist. Exportieren Sie die Umgebungsvariable ANDROID_SERIAL für das Zielgerät, wenn mehr als ein Gerät verbunden ist.
  2. cd in das oberste Android-Quellverzeichnis.

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

    Am Ende eines Benchmark-Laufs werden die Ergebnisse als HTML-Seite angezeigt und an xdg-open übergeben.

Weitere Informationen finden Sie unter platform/test/mlts/benchmark/README.txt.

HAL-Versionen für neuronale Netzwerke

In diesem Abschnitt werden die Änderungen beschrieben, die in den HAL-Versionen von Android und Neural Networks eingeführt wurden.

Android 11

In Android 11 wird NN HAL 1.3 eingeführt. Diese Version enthält die folgenden wichtigen Änderungen:

  • Unterstützung für signierte 8-Bit-Quantisierung in NNAPI Fügt den Operandentyp TENSOR_QUANT8_ASYMM_SIGNED hinzu. Treiber mit NN HAL 1.3, die Operationen mit unsignierter Quantisierung unterstützen, müssen auch die signierten Varianten dieser Operationen unterstützen. Bei der Ausführung signierter und nicht signierter Versionen der meisten quantisierten Vorgänge müssen die Treiber bis zu einem Offset von 128 dieselben Ergebnisse liefern. Es gibt fünf Ausnahmen von dieser Anforderung: CAST, HASHTABLE_LOOKUP, LSH_PROJECTION, PAD_V2 und QUANTIZED_16BIT_LSTM. Der Vorgang QUANTIZED_16BIT_LSTM unterstützt keine signierten Operanden. Die anderen vier Vorgänge unterstützen die Quantisierung mit Vorzeichen, die Ergebnisse müssen jedoch nicht gleich sein.
  • Unterstützung für abgegrenzte Ausführungen, bei denen das Framework die Methode IPreparedModel::executeFenced aufruft, um eine abgegrenzte, asynchrone Ausführung auf einem vorbereiteten Modell mit einem Vektor von Synchronisationsschranken zu starten, auf die gewartet werden soll. Weitere Informationen finden Sie unter Geschützte Ausführung.
  • Unterstützung für die Ablaufsteuerung. Hiermit werden die Vorgänge IF und WHILE hinzugefügt, die andere Modelle als Argumente annehmen und diese bedingt (IF) oder wiederholt (WHILE) ausführen. Weitere Informationen finden Sie unter Ablaufsteuerung.
  • Verbesserte Dienstqualität (Quality of Service, QoS), da Apps die relativen Prioritäten ihrer Modelle, die maximale Zeit, die für die Vorbereitung eines Modells benötigt wird, und die maximale Zeit, die für die Ausführung benötigt wird, angeben können. Weitere Informationen finden Sie unter Qualität des Dienstes.
  • Unterstützung für Speicherbereiche, die Allocator-Schnittstellen für treiberverwaltete Puffer bereitstellen. So können geräteeigene Speicher zwischen Ausführungen übergeben werden, wodurch unnötiges Kopieren und Transformieren von Daten zwischen aufeinanderfolgenden Ausführungen desselben Treibers vermieden wird. Weitere Informationen finden Sie unter Arbeitsspeicherbereiche.

Android 10

In Android 10 wird NN HAL 1.2 eingeführt. Diese Version enthält die folgenden wichtigen Änderungen:

  • Das Capabilities-Objekt enthält alle Datentypen, einschließlich Skalardatentypen, und stellt die Leistung ohne Entspannung mit einem Vektor anstelle von benannten Feldern dar.
  • Mit den Methoden getVersionString und getType kann das Framework den Gerätetyp (DeviceType) und Versionsinformationen abrufen. Weitere Informationen finden Sie unter Geräteerkennung und ‑zuweisung.
  • Die Methode executeSynchronously wird standardmäßig aufgerufen, um eine Ausführung synchron auszuführen. Die Methode execute_1_2 weist das Framework an, eine Ausführung asynchron auszuführen. Siehe Ausführung.
  • Der Parameter MeasureTiming für executeSynchronously, execute_1_2 und die Burst-Ausführung gibt an, ob der Treiber die Ausführungsdauer messen soll. Die Ergebnisse werden in der Timing-Struktur erfasst. Weitere Informationen finden Sie unter Timing.
  • Unterstützung für Ausführungen, bei denen mindestens ein Ausgabeoperand eine unbekannte Dimension oder einen unbekannten Rang hat. Weitere Informationen finden Sie unter Ausgabeform.
  • Unterstützung für Anbietererweiterungen, also Sammlungen von vom Anbieter definierten Vorgängen und Datentypen. Der Treiber meldet unterstützte Erweiterungen über die Methode IDevice::getSupportedExtensions. Weitere Informationen finden Sie unter Anbietererweiterungen.
  • Ein Burst-Objekt kann eine Reihe von Burst-Ausführungen mithilfe von schnellen Nachrichtenwarteschlangen (Fast Message Queues, FMQs) steuern, um zwischen App- und Treiberprozessen zu kommunizieren und so die Latenz zu reduzieren. Weitere Informationen finden Sie unter Burst-Ausführungen und schnelle Nachrichtenwarteschlangen.
  • Unterstützung für AHardwareBuffer, damit der Treiber Ausführungen ausführen kann, ohne Daten zu kopieren. Siehe AHardwareBuffer.
  • Verbesserte Unterstützung für das Caching von Kompilierungsartefakten, um die Zeit für die Kompilierung beim Starten einer App zu reduzieren. Siehe Kompilierungs-Caching.

In Android 10 werden die folgenden Operandentypen und -vorgänge eingeführt.

  • Operandentypen

    • ANEURALNETWORKS_BOOL
    • ANEURALNETWORKS_FLOAT16
    • ANEURALNETWORKS_TENSOR_BOOL8
    • ANEURALNETWORKS_TENSOR_FLOAT16
    • ANEURALNETWORKS_TENSOR_QUANT16_ASYMM
    • ANEURALNETWORKS_TENSOR_QUANT16_SYMM
    • ANEURALNETWORKS_TENSOR_QUANT8_SYMM
    • ANEURALNETWORKS_TENSOR_QUANT8_SYMM_PER_CHANNEL
  • Vorgänge

    • ANEURALNETWORKS_ABS
    • ANEURALNETWORKS_ARGMAX
    • ANEURALNETWORKS_ARGMIN
    • ANEURALNETWORKS_AXIS_ALIGNED_BBOX_TRANSFORM
    • ANEURALNETWORKS_BIDIRECTIONAL_SEQUENCE_LSTM
    • ANEURALNETWORKS_BIDIRECTIONAL_SEQUENCE_RNN
    • ANEURALNETWORKS_BOX_WITH_NMS_LIMIT
    • ANEURALNETWORKS_CAST
    • ANEURALNETWORKS_CHANNEL_SHUFFLE
    • ANEURALNETWORKS_DETECTION_POSTPROCESSING
    • ANEURALNETWORKS_EQUAL
    • ANEURALNETWORKS_EXP
    • ANEURALNETWORKS_EXPAND_DIMS
    • ANEURALNETWORKS_GATHER
    • ANEURALNETWORKS_GENERATE_PROPOSALS
    • ANEURALNETWORKS_GREATER
    • ANEURALNETWORKS_GREATER_EQUAL
    • ANEURALNETWORKS_GROUPED_CONV_2D
    • ANEURALNETWORKS_HEATMAP_MAX_KEYPOINT
    • ANEURALNETWORKS_INSTANCE_NORMALIZATION
    • ANEURALNETWORKS_LESS
    • ANEURALNETWORKS_LESS_EQUAL
    • ANEURALNETWORKS_LOG
    • ANEURALNETWORKS_LOGICAL_AND
    • ANEURALNETWORKS_LOGICAL_NOT
    • ANEURALNETWORKS_LOGICAL_OR
    • ANEURALNETWORKS_LOG_SOFTMAX
    • ANEURALNETWORKS_MAXIMUM
    • ANEURALNETWORKS_MINIMUM
    • ANEURALNETWORKS_NEG
    • ANEURALNETWORKS_NOT_EQUAL
    • ANEURALNETWORKS_PAD_V2
    • ANEURALNETWORKS_POW
    • ANEURALNETWORKS_PRELU
    • ANEURALNETWORKS_QUANTIZE
    • ANEURALNETWORKS_QUANTIZED_16BIT_LSTM
    • ANEURALNETWORKS_RANDOM_MULTINOMIAL
    • ANEURALNETWORKS_REDUCE_ALL
    • ANEURALNETWORKS_REDUCE_ANY
    • ANEURALNETWORKS_REDUCE_MAX
    • ANEURALNETWORKS_REDUCE_MIN
    • ANEURALNETWORKS_REDUCE_PROD
    • ANEURALNETWORKS_REDUCE_SUM
    • ANEURALNETWORKS_RESIZE_NEAREST_NEIGHBOR
    • ANEURALNETWORKS_ROI_ALIGN
    • ANEURALNETWORKS_ROI_POOLING
    • ANEURALNETWORKS_RSQRT
    • ANEURALNETWORKS_SELECT
    • ANEURALNETWORKS_SIN
    • ANEURALNETWORKS_SLICE
    • ANEURALNETWORKS_SPLIT
    • ANEURALNETWORKS_SQRT
    • ANEURALNETWORKS_TILE
    • ANEURALNETWORKS_TOPK_V2
    • ANEURALNETWORKS_TRANSPOSE_CONV_2D
    • ANEURALNETWORKS_UNIDIRECTIONAL_SEQUENCE_LSTM
    • ANEURALNETWORKS_UNIDIRECTIONAL_SEQUENCE_RNN

In Android 10 wurden viele der vorhandenen Vorgänge aktualisiert. Die Änderungen betreffen hauptsächlich Folgendes:

  • Unterstützung für das NCHW-Speicherlayout
  • Unterstützung von Tensoren mit einem anderen Rang als 4 bei Softmax- und Normalisierungsoperationen
  • Unterstützung für erweiterte Convolutionen
  • Unterstützung für Eingaben mit gemischter Quantisierung in ANEURALNETWORKS_CONCATENATION

In der folgenden Liste sind die Vorgänge aufgeführt, die in Android 10 geändert wurden. Ausführliche Informationen zu den Änderungen finden Sie unter OperationCode in der NNAPI-Referenzdokumentation.

  • ANEURALNETWORKS_ADD
  • ANEURALNETWORKS_AVERAGE_POOL_2D
  • ANEURALNETWORKS_BATCH_TO_SPACE_ND
  • ANEURALNETWORKS_CONCATENATION
  • ANEURALNETWORKS_CONV_2D
  • ANEURALNETWORKS_DEPTHWISE_CONV_2D
  • ANEURALNETWORKS_DEPTH_TO_SPACE
  • ANEURALNETWORKS_DEQUANTIZE
  • ANEURALNETWORKS_DIV
  • ANEURALNETWORKS_FLOOR
  • ANEURALNETWORKS_FULLY_CONNECTED
  • ANEURALNETWORKS_L2_NORMALIZATION
  • ANEURALNETWORKS_L2_POOL_2D
  • ANEURALNETWORKS_LOCAL_RESPONSE_NORMALIZATION
  • ANEURALNETWORKS_LOGISTIC
  • ANEURALNETWORKS_LSH_PROJECTION
  • ANEURALNETWORKS_LSTM
  • ANEURALNETWORKS_MAX_POOL_2D
  • ANEURALNETWORKS_MEAN
  • ANEURALNETWORKS_MUL
  • ANEURALNETWORKS_PAD
  • ANEURALNETWORKS_RELU
  • ANEURALNETWORKS_RELU1
  • ANEURALNETWORKS_RELU6
  • ANEURALNETWORKS_RESHAPE
  • ANEURALNETWORKS_RESIZE_BILINEAR
  • ANEURALNETWORKS_RNN
  • ANEURALNETWORKS_ROI_ALIGN
  • ANEURALNETWORKS_SOFTMAX
  • ANEURALNETWORKS_SPACE_TO_BATCH_ND
  • ANEURALNETWORKS_SPACE_TO_DEPTH
  • ANEURALNETWORKS_SQUEEZE
  • ANEURALNETWORKS_STRIDED_SLICE
  • ANEURALNETWORKS_SUB
  • ANEURALNETWORKS_SVDF
  • ANEURALNETWORKS_TANH
  • ANEURALNETWORKS_TRANSPOSE

Android 9

NN HAL 1.1 wurde in Android 9 eingeführt und enthält die folgenden wichtigen Änderungen.

  • IDevice::prepareModel_1_1 enthält einen ExecutionPreference-Parameter. Ein Fahrer kann dies nutzen, um seine Vorbereitung anzupassen, da er weiß, dass die App den Akku bevorzugt schont oder das Modell in schnellen aufeinanderfolgenden Aufrufen ausführt.
  • Neu hinzugekommen sind neun neue Vorgänge: BATCH_TO_SPACE_ND, DIV, MEAN, PAD, SPACE_TO_BATCH_ND, SQUEEZE, STRIDED_SLICE, SUB und TRANSPOSE.
  • Eine App kann angeben, dass 32‑Bit-Float-Berechnungen mit einem 16‑Bit-Float-Bereich und/oder einer 16‑Bit-Genauigkeit ausgeführt werden können, indem Model.relaxComputationFloat32toFloat16 auf true gesetzt wird. Die Struktur Capabilities enthält das zusätzliche Feld relaxedFloat32toFloat16Performance, damit der Treiber dem Framework seine reduzierte Leistung melden kann.

Android 8.1

Die erste HAL für neuronale Netze (1.0) wurde in Android 8.1 veröffentlicht. Weitere Informationen finden Sie unter /neuralnetworks/1.0/.