Neural Networks API-Treiber

Auf dieser Seite erhalten Sie einen Überblick über die Implementierung eines NNAPI-Treibers (Neural Networks API). Weitere Informationen finden Sie in der Dokumentation in den HAL-Definitionsdateien unter 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

Das Neural Networks (NN) HAL 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-Definitionsdateien in hardware/interfaces/neuralnetworks angegeben.

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

Ablauf für neuronale Netzwerke

Abbildung 1: Ablauf für neuronale Netzwerke

Initialisierung

Bei der Initialisierung fragt das Framework den Treiber mit IDevice::getCapabilities_1_3 nach seinen Funktionen. Die @1.3::Capabilities-Struktur umfasst alle Datentypen und stellt die nicht gelockerten Leistungsdaten mithilfe eines Vektors dar.

Um zu bestimmen, wie Berechnungen den verfügbaren Geräten zugewiesen werden, verwendet das Framework die Funktionen, um zu verstehen, wie schnell und energieeffizient jeder Treiber eine Ausführung durchführen kann. Dazu muss der Treiber standardisierte Leistungszahlen auf Grundlage der Ausführung von Referenz-Workloads liefern.

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

In Android 9 und niedriger enthält die Capabilities-Struktur Informationen zur Treiberleistung nur für Gleitkomma- und quantisierte Tensoren und nicht für skalare Datentypen.

Im Rahmen der Initialisierung fragt das Framework möglicherweise weitere Informationen ab, indem es IDevice::getType, IDevice::getVersionString, IDevice:getSupportedExtensions und IDevice::getNumberOfCacheFilesNeeded verwendet.

Zwischen den Neustarts des Produkts erwartet das Framework, dass für einen bestimmten Treiber immer dieselben Werte für alle in diesem Abschnitt beschriebenen Abfragen gemeldet werden. Andernfalls kann es bei einer App, die diesen Treiber verwendet, zu Leistungseinbußen oder fehlerhaftem Verhalten kommen.

Compilation

Das Framework bestimmt, welche Geräte verwendet werden sollen, wenn es eine Anfrage von einer App erhält. In 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 eine bestimmte Operation 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 möglicherweise 3×3- und 5×5-Faltungen, aber keine 7×7-Faltungen.
  • Der Treiber hat Speicherbeschränkungen, die verhindern, dass er große Diagramme oder Eingaben verarbeiten kann.

Während der Kompilierung können die Ein-, 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 auf die Ausführung einer Teilmenge des Modells vorzubereiten, indem es IDevice::prepareModel_1_3 aufruft. Jeder Treiber kompiliert dann seine Teilmenge. Ein Treiber kann beispielsweise Code generieren oder eine neu sortierte 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 ein @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 für die Kompilierung benötigte Zeit beim Start einer App zu verkürzen, 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 für ein vorbereitetes Modell durchzuführen. Eine Anfrage kann auch asynchron mit der Methode execute_1_3, der Methode executeFenced (siehe Fenced Execution) oder mit einer Burst Execution ausgeführt werden.

Synchrone Ausführungsaufrufe verbessern die Leistung und reduzieren den Threading-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 Methode execute_1_3 wird die Steuerung nach dem Start 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 Ein- und Ausgabeargumente, die für die Ausführung verwendet werden. Der Arbeitsspeicher, in dem die Operanden-Daten gespeichert werden, muss die zeilenweise Speicherung verwenden, wobei die erste Dimension am langsamsten durchlaufen wird und am Ende einer Zeile kein Padding vorhanden ist. Weitere Informationen zu den Operandentypen finden Sie unter Operanden.

Bei NN HAL 1.2-Treibern oder höher werden nach Abschluss einer Anfrage der Fehlerstatus, die Ausgabeform und Zeitinformationen 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 Ausgabeargument eine unbekannte Dimension oder einen unbekannten Rang hat, muss der Treiber Informationen zur dynamischen Größe der Ausgabe 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 Ein- und Ausgabeargumente 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 angegebenen Rang haben.

Bei Nutzeranfragen, die mehrere Treiber umfassen, ist das Framework dafür verantwortlich, Zwischenspeicher zu reservieren und die Aufrufe an die einzelnen Treiber zu sequenzieren.

Auf demselben @1.3::IPreparedModel können mehrere Anfragen parallel gestellt werden. Der Treiber kann Anfragen parallel ausführen oder die Ausführungen serialisieren.

Das Framework kann einen Treiber auffordern, mehr als ein vorbereitetes Modell beizubehalten. Bereiten Sie beispielsweise das Modell m1 vor, bereiten Sie m2 vor, führen Sie die Anfrage r1 für m1 aus, führen Sie r2 für m2 aus, führen Sie r3 für m1 aus, führen Sie r4 für m2 aus, geben Sie m1 frei (siehe Bereinigung) und geben Sie m2 frei.

Um eine langsame erste Ausführung zu vermeiden, die zu einer schlechten Nutzererfahrung führen könnte (z. B. ein Ruckeln des ersten Frames), sollte der Treiber die meisten Initialisierungen in der Kompilierungsphase durchführen. Die Initialisierung bei der ersten Ausführung sollte auf Aktionen beschränkt sein, die sich negativ auf den Systemzustand auswirken, wenn sie frühzeitig ausgeführt werden, z. B. das Reservieren großer temporärer Puffer oder das Erhöhen der Taktrate eines Geräts. Treiber, die nur eine begrenzte Anzahl gleichzeitiger Modelle vorbereiten können, müssen die Initialisierung möglicherweise bei der ersten Ausführung vornehmen.

Unter Android 10 oder höher kann der Client in Fällen, in denen mehrere Ausführungen mit demselben vorbereiteten Modell in schneller Folge ausgeführt werden, 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 Ausgabeargumente nicht alle Dimensionen angegeben sind, muss der Treiber nach der Ausführung eine Liste mit Ausgabeshapes bereitstellen, die die Dimensionsinformationen für jedes Ausgabeargument enthält. 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 Ausgabeformenteile angeben, für welche Ausgabeargumente die Puffergröße nicht ausreicht. Außerdem sollte er so viele Dimensionsinformationen wie möglich angeben 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 den Kompilierungsprozess 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), wenn eine Anfrage ausgeführt wird. Der Treiber sollte alle Leistungseinbußen minimieren, die durch die Messung der Ausführungsdauer entstehen.

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

  • Ausführungszeit auf dem Gerät:Die Ausführungszeit im Treiber, der auf dem Hostprozessor ausgeführt wird, ist nicht enthalten.
  • Ausführungszeit im Treiber:Enthält die Ausführungszeit auf dem Gerät.

Diese Zeiträume müssen die Zeit umfassen, in der die Ausführung unterbrochen wird, z. B. wenn die Ausführung durch andere Aufgaben unterbrochen wurde oder wenn sie darauf wartet, dass eine Ressource verfügbar wird.

Wenn der Treiber nicht aufgefordert wurde, die Ausführungsdauer zu messen, oder wenn ein Ausführungsfehler auftritt, muss er die Dauer als UINT64_MAX melden. Auch wenn der Fahrer aufgefordert wurde, die Ausführungsdauer zu messen, kann er stattdessen UINT64_MAX für die Zeit auf dem Gerät, die Zeit im Fahrer oder beides melden. Wenn der Treiber beide Zeiträume als einen anderen Wert als UINT64_MAX meldet, muss die Ausführungszeit im Treiber der Zeit auf dem Gerät entsprechen oder sie überschreiten.

Abgegrenzte Ausführung

In Android 11 kann mit der NNAPI gewartet werden, bis eine Liste von sync_fence-Handles abgeschlossen ist. Optional kann auch ein sync_fence-Objekt zurückgegeben werden, das signalisiert wird, wenn die Ausführung abgeschlossen ist. Dadurch wird der Overhead für kleine Sequenzmodelle und Streaming-Anwendungsfälle reduziert. Die eingeschränkte Ausführung ermöglicht auch eine effizientere Interoperabilität mit anderen Komponenten, die sync_fence signalisieren oder darauf warten können. Weitere Informationen zu sync_fence finden Sie unter Synchronisierungsframework.

Bei einer eingeschränkten Ausführung ruft das Framework die Methode IPreparedModel::executeFenced auf, um eine eingeschränkte, asynchrone Ausführung für ein vorbereitetes Modell mit einem Vektor von Synchronisationssperren zu starten, auf die gewartet werden soll. Wenn die asynchrone Aufgabe abgeschlossen ist, bevor der Aufruf zurückgegeben wird, kann ein leerer Handle für sync_fence zurückgegeben werden. Außerdem muss ein IFencedExecutionCallback-Objekt zurückgegeben werden, damit das Framework Fehlerstatus und Dauerinformationen abfragen kann.

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

  • timingLaunched: Dauer vom Aufrufen von executeFenced bis zu dem Zeitpunkt, an dem executeFenced das zurückgegebene syncFence signalisiert.
  • timingFenced: Die Dauer vom Zeitpunkt, an dem alle Synchronisations-Fences, auf die die Ausführung wartet, signalisiert werden, bis executeFenced das zurückgegebene syncFence signalisiert.

Kontrollfluss

Für Geräte mit Android 11 oder höher enthält die NNAPI zwei Kontrollflussvorgänge, IF und WHILE, die andere Modelle als Argumente verwenden und sie 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 (QoS), da eine App die relativen Prioritäten ihrer Modelle, die maximale Zeit, die für die Vorbereitung eines Modells erwartet wird, und die maximale Zeit, die für die Ausführung erwartet wird, angeben kann. Weitere Informationen finden Sie unter Quality of Service.

Bereinigung

Wenn eine App ein vorbereitetes Modell nicht mehr verwendet, gibt das Framework die Referenz zum Objekt @1.3::IPreparedModel frei. Wenn nicht mehr auf das IPreparedModel-Objekt verwiesen wird, wird es automatisch im Treiberdienst zerstört, der es erstellt hat. Modellspezifische Ressourcen können zu diesem Zeitpunkt in der Implementierung des Destruktors durch den Treiber freigegeben werden. Wenn das IPreparedModel-Objekt automatisch zerstört werden soll, sobald es vom Client nicht mehr benötigt wird, darf der Treiberservice keine Verweise auf das IPreparedModel-Objekt enthalten, nachdem das IPreparedeModel-Objekt über IPreparedModelCallback::notify_1_3 zurückgegeben wurde.

CPU-Auslastung

Es wird erwartet, dass Fahrer die CPU verwenden, um Berechnungen einzurichten. Treiber sollten die CPU nicht für die Durchführung von Berechnungen im Zusammenhang mit dem Diagramm verwenden, da dies die Fähigkeit des Frameworks beeinträchtigt, Arbeit korrekt zuzuweisen. Der Treiber sollte die Teile, die er nicht verarbeiten kann, an das Framework melden und das Framework den Rest erledigen lassen.

Das Framework bietet eine CPU-Implementierung für alle NNAPI-Vorgänge mit Ausnahme von anbieterspezifischen Vorgängen. Weitere Informationen finden Sie unter Vendor Extensions.

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

Hilfsfunktionen

Die NNAPI-Codebasis enthält Hilfsfunktionen, die von Treiberservices verwendet werden können.

Die Datei frameworks/ml/nn/common/include/Utils.h enthält verschiedene Dienstprogrammfunktionen, z. B. für die Protokollierung und die Konvertierung 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 Eigenschaft 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 Eigenschaft muss einer der folgenden sein:

    • Ein leerer String, der angibt, dass keine Protokollierung erfolgen soll.
    • Das Token 1 oder all, das angibt, dass das gesamte Logging erfolgen soll.
    • Eine Liste von Tags, die durch Leerzeichen, Kommas oder Doppelpunkte getrennt sind und angeben, welche Protokollierung erfolgen soll. Die Tags sind compilation, cpuexe, driver, execution, manager und model.
  • compliantWithV1_*: Gibt true zurück, wenn ein NN HAL-Objekt ohne Informationsverlust in denselben Typ einer anderen HAL-Version konvertiert werden kann. Wenn Sie beispielsweise compliantWithV1_0 für ein V1_2::Model aufrufen, 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. Eine Warnung wird protokolliert, wenn die Konvertierung zu einem Informationsverlust führt, d. h. wenn der Wert in der neuen Version des Typs nicht vollständig dargestellt werden kann.

  • Funktionen: Mit den Funktionen nonExtensionOperandPerformance und update lässt sich 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 Hilfsfunktionen zum Validieren, ob ein NN HAL-Objekt gemäß der Spezifikation seiner HAL-Version gültig ist.

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

Die Datei frameworks/ml/nn/common/include/Tracing.h enthält Makros, mit denen sich systracing-Informationen einfacher in den Code für neuronale Netze 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 zum Ausgeben des Inhalts eines Model in grafischer Form zu Debugging-Zwecken.

  • 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 NNAPI-Implementierung die im Android-Framework enthaltenen VTS- und CTS-Tests. VTS testet Ihre Treiber direkt (ohne das Framework), während CTS sie indirekt über das Framework testet. Dabei wird jede API-Methode getestet und es wird geprüft, ob alle von den Treibern unterstützten Vorgänge korrekt ausgeführt werden und Ergebnisse liefern, die den Anforderungen an die Genauigkeit entsprechen.

Die Genauigkeitsanforderungen in CTS und VTS für die NNAPI sind wie folgt:

  • Gleitkomma:abs(expected - actual) <= atol + rtol  * abs(expected); where:

    • Für fp32: atol = 1e-5f, rtol = 5.0f * 1.1920928955078125e-7
    • Für fp16: atol = rtol = 5.0f * 0.0009765625f
  • Quantisiert:Abweichung um 1 (außer bei mobilenet_quantized, wo die Abweichung um 3 ist)

  • Boolesch:Genaue Übereinstimmung

Bei CTS-Tests für NNAPI werden unter anderem feste pseudozufällige Grafiken generiert, mit denen die Ausführungsergebnisse der einzelnen Treiber mit der NNAPI-Referenzimplementierung verglichen werden. Wenn die Ergebnisse für Treiber mit NN HAL 1.2 oder höher die Genauigkeitskriterien nicht erfüllen, meldet CTS einen Fehler und speichert eine Spezifikationsdatei für das fehlgeschlagene Modell unter /data/local/tmp zur Fehlerbehebung. Weitere Informationen zu den Genauigkeitskriterien finden Sie unter TestRandomGraph.cpp und TestHarness.h.

Fuzzing

Der Zweck von Fuzz-Tests besteht darin, Abstürze, Assertions, Speicherverletzungen oder allgemeines 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 effizient, da sie die Zeilenabdeckung früherer Testläufe nutzen, um neue zufällige Eingaben zu generieren. libFuzzer bevorzugt beispielsweise Testläufe für neue Codezeilen. Dadurch wird die Zeit, die Tests zum Auffinden von problematischem Code benötigen, erheblich verkürzt.

Wenn Sie Fuzz-Tests durchführen möchten, um Ihre Treiberimplementierung zu validieren, müssen Sie frameworks/ml/nn/runtime/test/android_fuzzing/DriverFuzzTest.cpp im Test-Utility libneuralnetworks_driver_fuzzer in AOSP ändern, damit Ihr Treibercode enthalten ist. Weitere Informationen zu NNAPI-Fuzzing-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 validieren. Diese Validierung wird von VTS überprüft. Der Validierungscode befindet sich in frameworks/ml/nn/common/include/ValidateHal.h.

Außerdem müssen sie dafür sorgen, dass Apps andere Apps nicht beeinträchtigen können, wenn sie auf demselben Gerät verwendet werden.

Android Machine Learning Test Suite

Die Android Machine Learning Test Suite (MLTS) ist ein NNAPI-Benchmark, der in CTS und VTS enthalten ist, um die Genauigkeit echter Modelle auf Geräten von Anbietern zu validieren. 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 Datasets erzielt wurden. So wird sichergestellt, dass die Genauigkeit eines Treibers nicht schlechter ist als die CPU-Referenzimplementierung.

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

Der NNAPI-Benchmark ist in zwei Projekten in AOSP verfügbar:

Modelle und Datasets

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

  • MobileNetV1-Gleitkomma- und u8-quantisiert in verschiedenen Größen, ausgeführt für eine kleine Teilmenge (1.500 Bilder) von Open Images Dataset v4.
  • MobileNetV2-Gleitkomma- und u8-quantisiert in verschiedenen Größen, ausgeführt für eine kleine Teilmenge (1.500 Bilder) von Open Images Dataset v4.
  • Auf LSTM basierendes akustisches Modell für die Sprachsynthese, das mit einer kleinen Teilmenge des CMU Arctic-Datasets ausgeführt wird.
  • LSTM-basiertes akustisches Modell für die automatische Spracherkennung, das für eine kleine Teilmenge 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 Absturztests, mit denen die Stabilität von Treibern unter starker Nutzung oder in Grenzsituationen des Clientverhaltens validiert wird.

Alle Absturztests bieten die folgenden Funktionen:

  • Erkennung von Hängern:Wenn der NNAPI-Client während eines Tests hängen bleibt, schlägt der Test mit dem Fehlergrund HANG fehl und die Testsuite fährt mit dem nächsten Test fort.
  • Erkennung von NNAPI-Clientabstürzen:Tests überstehen Clientabstürze und schlagen mit dem Fehlergrund CRASH fehl.
  • Erkennung von Treiberabstürzen:Mit Tests kann ein Treiberabsturz erkannt werden, der einen Fehler bei einem NNAPI-Aufruf verursacht. Beachten Sie, dass es in Treiberprozessen zu Abstürzen kommen kann, die keinen NNAPI-Fehler und keinen Testfehler verursachen. Um diese Art von Fehler zu beheben, wird empfohlen, den Befehl tail im Systemprotokoll nach treiberbezogenen Fehlern oder Abstürzen zu suchen.
  • Targeting aller verfügbaren Accelerators:Tests werden für alle verfügbaren Treiber ausgeführt.

Alle Absturztests haben die folgenden vier möglichen 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. Dies deutet darauf hin, 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 Absturztests 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 sorgen Sie dafür, dass es über adb erreichbar ist. Exportieren Sie die Umgebungsvariable ANDROID_SERIAL des Zielgeräts, wenn mehr als ein Gerät verbunden ist.
  2. cd in das Android-Quellverzeichnis der obersten Ebene.

    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 präsentiert 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 Android- und Neural Networks HAL-Versionen eingeführt wurden.

Android 11

Mit Android 11 wird NN HAL 1.3 eingeführt, das die folgenden wichtigen Änderungen enthält.

  • Unterstützung für die signierte 8-Bit-Quantisierung in NNAPI. Fügt den Operandentyp TENSOR_QUANT8_ASYMM_SIGNED hinzu. Treiber mit NN HAL 1.3, die Vorgänge mit nicht signierter Quantisierung unterstützen, müssen auch die signierten Varianten dieser Vorgänge unterstützen. Beim Ausführen signierter und nicht signierter Versionen der meisten quantisierten Vorgänge müssen 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 QUANTIZED_16BIT_LSTM-Vorgang unterstützt keine signierten Operanden und die anderen vier Vorgänge unterstützen die signierte Quantisierung, erfordern jedoch nicht, dass die Ergebnisse gleich sind.
  • Unterstützung für eingeschränkte Ausführungen, bei denen das Framework die Methode IPreparedModel::executeFenced aufruft, um eine eingeschränkte, asynchrone Ausführung für ein vorbereitetes Modell mit einem Vektor von Synchronisierungs-Fences zu starten, auf die gewartet werden soll. Weitere Informationen finden Sie unter Abgeschirmte Ausführung.
  • Unterstützung für den Kontrollfluss. Fügt die Vorgänge IF und WHILE hinzu, die andere Modelle als Argumente verwenden und sie bedingt (IF) oder wiederholt (WHILE) ausführen. Weitere Informationen finden Sie unter Ablaufsteuerung.
  • Verbesserte Servicequalität (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 Quality of Service.
  • Unterstützung für Speicherbereiche, die Zuweisungsschnittstellen für vom Treiber verwaltete Puffer bereitstellen. So können geräteinterne Speicher über Ausführungen hinweg weitergegeben werden, wodurch unnötiges Kopieren und Transformieren von Daten zwischen aufeinanderfolgenden Ausführungen auf demselben Treiber unterdrückt wird. Weitere Informationen finden Sie unter Speicherdomains.

Android 10

Mit Android 10 wird NN HAL 1.2 eingeführt, das die folgenden wichtigen Änderungen enthält.

  • Der Struct Capabilities enthält alle Datentypen, einschließlich skalarer Datentypen, und stellt die nicht eingeschränkte Leistung mit einem Vektor anstelle von benannten Feldern dar.
  • Mit den Methoden getVersionString und getType kann das Framework 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 durchzuführen. Die execute_1_2-Methode 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 zurückgegeben. Weitere Informationen finden Sie unter Timing.
  • Unterstützung für Ausführungen, bei denen mindestens ein Ausgabeargument eine unbekannte Dimension oder einen unbekannten Rang hat. Siehe Ausgabeform.
  • Unterstützung für Anbietererweiterungen, die Sammlungen von anbieterdefinierten Vorgängen und Datentypen sind. Der Treiber meldet unterstützte Erweiterungen über die Methode IDevice::getSupportedExtensions. Weitere Informationen finden Sie unter Anbieterextensions.
  • Ein Burst-Objekt kann eine Reihe von Burst-Ausführungen steuern. Dazu werden Fast Message Queues (FMQs) für die Kommunikation zwischen App- und Treiberprozessen verwendet, wodurch die Latenz reduziert wird. Weitere Informationen finden Sie unter Burst Executions and Fast Message Queues.
  • Unterstützung für AHardwareBuffer, damit der Treiber Ausführungen ohne Kopieren von Daten durchführen kann. Siehe AHardwareBuffer.
  • Verbesserte Unterstützung für das Caching von Kompilierungsartefakten, um die für die Kompilierung benötigte Zeit beim Start einer App zu verkürzen. Weitere Informationen finden Sie unter Kompilierungs-Caching.

Mit 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 Aktualisierungen beziehen sich hauptsächlich auf Folgendes:

  • Unterstützung für das NCHW-Speicherlayout
  • Unterstützung für Tensoren mit einem anderen Rang als 4 bei Softmax- und Normalisierungsvorgängen
  • Unterstützung für erweiterte Faltungen
  • 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 in der NNAPI-Referenzdokumentation unter OperationCode.

  • 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 schonen möchte oder das Modell in schnellen aufeinanderfolgenden Aufrufen ausführt.
  • Es wurden neun neue Vorgänge hinzugefügt: BATCH_TO_SPACE_ND, DIV, MEAN, PAD, SPACE_TO_BATCH_ND, SQUEEZE, STRIDED_SLICE, SUB, TRANSPOSE.
  • Eine App kann angeben, dass 32-Bit-Gleitkomma-Berechnungen mit 16-Bit-Gleitkomma-Bereich und/oder -Genauigkeit ausgeführt werden können, indem Model.relaxComputationFloat32toFloat16 auf true gesetzt wird. Die Capabilities-Struktur hat das zusätzliche Feld relaxedFloat32toFloat16Performance, damit der Treiber seine gelockerte Leistung an das Framework melden kann.

Android 8.1

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