Speicherpools

Auf dieser Seite werden die Datenstrukturen und Methoden beschrieben, die zur effizienten Kommunikation von Operandenpuffern zwischen dem Treiber und dem Framework verwendet werden.

Zum Zeitpunkt der Modellkompilierung stellt das Framework dem Treiber die Werte der konstanten Operanden bereit. Abhängig von der Lebensdauer des konstanten Operanden befinden sich seine Werte entweder in einem HIDL-Vektor oder einem gemeinsam genutzten Speicherpool.

  • Wenn die Lebensdauer CONSTANT_COPY ist, befinden sich die Werte im Feld operandValues ​​der Modellstruktur. Da die Werte im HIDL-Vektor während der Interprozesskommunikation (IPC) kopiert werden, wird dieser normalerweise nur zum Speichern kleiner Datenmengen wie Skalaroperanden (z. B. des Aktivierungsskalars in ADD ) und kleiner Tensorparameter (z. B. der Formtensor in RESHAPE ).
  • Wenn die Lebensdauer CONSTANT_REFERENCE ist, befinden sich die Werte im Feld pools der Modellstruktur. Beim IPC werden nur die Handles der Shared-Memory-Pools dupliziert, statt die Rohwerte zu kopieren. Daher ist es effizienter, eine große Datenmenge (z. B. die Gewichtsparameter in Faltungen) mithilfe gemeinsam genutzter Speicherpools zu speichern als mit HIDL-Vektoren.

Zur Modellausführungszeit stellt das Framework dem Treiber die Puffer der Eingabe- und Ausgabeoperanden zur Verfügung. Im Gegensatz zu den Konstanten zur Kompilierungszeit, die möglicherweise in einem HIDL-Vektor gesendet werden, werden die Eingabe- und Ausgabedaten einer Ausführung immer über eine Sammlung von Speicherpools kommuniziert.

Der HIDL-Datentyp hidl_memory wird sowohl bei der Kompilierung als auch bei der Ausführung verwendet, um einen nicht zugeordneten gemeinsam genutzten Speicherpool darzustellen. Der Treiber sollte den Speicher entsprechend zuordnen, um ihn basierend auf dem Namen des Datentyps hidl_memory nutzbar zu machen. Die unterstützten Speichernamen sind:

  • ashmem : Gemeinsamer Android-Speicher. Weitere Einzelheiten finden Sie unter Speicher .
  • mmap_fd : Gemeinsamer Speicher, der durch einen Dateideskriptor über mmap unterstützt wird.
  • hardware_buffer_blob : Gemeinsamer Speicher, der durch einen AHardwareBuffer mit dem Format AHARDWARE_BUFFER_FORMAT_BLOB unterstützt wird. Verfügbar bei Neural Networks (NN) HAL 1.2. Weitere Einzelheiten finden Sie unter AHardwareBuffer .
  • hardware_buffer : Gemeinsamer Speicher, der durch einen allgemeinen AHardwareBuffer unterstützt wird, der nicht das Format AHARDWARE_BUFFER_FORMAT_BLOB verwendet. Der Hardwarepuffer im Nicht-BLOB-Modus wird nur bei der Modellausführung unterstützt. Verfügbar ab NN HAL 1.2. Weitere Einzelheiten finden Sie unter AHardwareBuffer .

Ab NN HAL 1.3 unterstützt NNAPI Speicherdomänen, die Zuweisungsschnittstellen für vom Treiber verwaltete Puffer bereitstellen. Die vom Treiber verwalteten Puffer können auch als Ausführungseingänge oder -ausgänge verwendet werden. Weitere Einzelheiten finden Sie unter Speicherdomänen .

NNAPI-Treiber müssen die Zuordnung von ashmem und mmap_fd Speichernamen unterstützen. Ab NN HAL 1.3 müssen Treiber auch die Zuordnung von hardware_buffer_blob unterstützen. Die Unterstützung für allgemeine hardware_buffer und Speicherdomänen im Nicht-BLOB-Modus ist optional.

AHardwareBuffer

AHardwareBuffer ist eine Art gemeinsam genutzter Speicher, der einen Gralloc-Puffer umschließt. In Android 10 unterstützt die Neural Networks API (NNAPI) die Verwendung von AHardwareBuffer und ermöglicht es dem Treiber, Ausführungen durchzuführen, ohne Daten zu kopieren, was die Leistung und den Stromverbrauch für Apps verbessert. Beispielsweise kann ein Kamera-HAL-Stack AHardwareBuffer-Objekte an die NNAPI für maschinelle Lern-Workloads übergeben, indem er AHardwareBuffer-Handles verwendet, die von Kamera-NDK- und Medien-NDK-APIs generiert werden. Weitere Informationen finden Sie unter ANeuralNetworksMemory_createFromAHardwareBuffer .

In NNAPI verwendete AHardwareBuffer-Objekte werden über eine hidl_memory -Struktur mit dem Namen hardware_buffer oder hardware_buffer_blob an den Treiber übergeben. Die hidl_memory -Struktur hardware_buffer_blob repräsentiert nur AHardwareBuffer-Objekte mit dem AHARDWAREBUFFER_FORMAT_BLOB -Format.

Die vom Framework benötigten Informationen sind im Feld hidl_handle der Struktur hidl_memory codiert. Das Feld hidl_handle umschließt native_handle , das alle erforderlichen Metadaten über AHardwareBuffer oder Gralloc-Puffer codiert.

Der Treiber muss das bereitgestellte hidl_handle Feld ordnungsgemäß dekodieren und auf den durch hidl_handle beschriebenen Speicher zugreifen. Wenn die Methode getSupportedOperations_1_2 , getSupportedOperations_1_1 oder getSupportedOperations aufgerufen wird, sollte der Treiber erkennen, ob er das bereitgestellte hidl_handle dekodieren und auf den durch hidl_handle beschriebenen Speicher zugreifen kann. Die Modellvorbereitung muss fehlschlagen, wenn das für einen konstanten Operanden verwendete Feld hidl_handle nicht unterstützt wird. Die Ausführung muss fehlschlagen, wenn das Feld hidl_handle das für einen Eingabe- oder Ausgabeoperanden der Ausführung verwendet wird, nicht unterstützt wird. Es wird empfohlen, dass der Treiber einen GENERAL_FAILURE Fehlercode zurückgibt, wenn die Modellvorbereitung oder -ausführung fehlschlägt.

Speicherdomänen

Für Geräte mit Android 11 oder höher unterstützt NNAPI Speicherdomänen, die Zuweisungsschnittstellen für treiberverwaltete Puffer bereitstellen. Dies ermöglicht die Weitergabe geräteeigener Speicher über Ausführungen hinweg und unterdrückt unnötiges Kopieren und Transformieren von Daten zwischen aufeinanderfolgenden Ausführungen auf demselben Treiber. Dieser Ablauf ist in Abbildung 1 dargestellt.

Pufferdatenfluss mit und ohne Speicherdomänen

Abbildung 1. Pufferdatenfluss mithilfe von Speicherdomänen

Die Speicherdomänenfunktion ist für Tensoren gedacht, die größtenteils treiberintern sind und keinen häufigen Zugriff auf der Clientseite benötigen. Beispiele für solche Tensoren sind die Zustandstensoren in Sequenzmodellen. Für Tensoren, die auf der Clientseite häufigen CPU-Zugriff benötigen, ist es vorzuziehen, gemeinsam genutzte Speicherpools zu verwenden.

Um die Speicherdomänenfunktion zu unterstützen, implementieren Sie IDevice::allocate , damit das Framework eine vom Treiber verwaltete Pufferzuweisung anfordern kann. Während der Zuweisung stellt das Framework die folgenden Eigenschaften und Nutzungsmuster für den Puffer bereit:

  • BufferDesc beschreibt die erforderlichen Eigenschaften des Puffers.
  • BufferRole beschreibt das potenzielle Nutzungsmuster des Puffers als Eingabe oder Ausgabe eines vorbereiteten Modells. Bei der Pufferzuweisung können mehrere Rollen angegeben werden, und der zugewiesene Puffer kann nur für die angegebenen Rollen verwendet werden.

Der zugewiesene Puffer ist treiberintern. Ein Fahrer kann einen beliebigen Pufferspeicherort oder ein beliebiges Datenlayout wählen. Wenn der Puffer erfolgreich zugewiesen wurde, kann der Client des Treibers mithilfe des zurückgegebenen Tokens oder des IBuffer Objekts auf den Puffer verweisen oder mit ihm interagieren.

Das Token von IDevice::allocate wird bereitgestellt, wenn auf den Puffer als eines der MemoryPool Objekte in der Request Struktur einer Ausführung verwiesen wird. Um zu verhindern, dass ein Prozess versucht, auf den in einem anderen Prozess zugewiesenen Puffer zuzugreifen, muss der Treiber bei jeder Verwendung des Puffers eine ordnungsgemäße Validierung durchführen. Der Treiber muss überprüfen, ob es sich bei der Puffernutzung um eine der bei der Zuweisung bereitgestellten BufferRole Rollen handelt, und die Ausführung muss sofort fehlschlagen, wenn die Nutzung unzulässig ist.

Das IBuffer Objekt wird zum expliziten Kopieren des Speichers verwendet. In bestimmten Situationen muss der Client des Treibers den vom Treiber verwalteten Puffer aus einem gemeinsam genutzten Speicherpool initialisieren oder den Puffer in einen gemeinsam genutzten Speicherpool kopieren. Beispielhafte Anwendungsfälle sind:

  • Initialisierung des Zustandstensors
  • Zwischenergebnisse zwischenspeichern
  • Fallback-Ausführung auf der CPU

Um diese Anwendungsfälle zu unterstützen, muss der Treiber IBuffer::copyTo und IBuffer::copyFrom mit ashmem , mmap_fd und hardware_buffer_blob implementieren, wenn er die Speicherdomänenzuweisung unterstützt. Es ist optional, dass der Treiber den Nicht-BLOB-Modus hardware_buffer unterstützt.

Während der Pufferzuweisung können die Abmessungen des Puffers aus den entsprechenden Modelloperanden aller durch BufferRole angegebenen Rollen und den in BufferDesc bereitgestellten Dimensionen abgeleitet werden. Wenn alle Dimensionsinformationen kombiniert werden, kann es sein, dass der Puffer unbekannte Dimensionen oder einen unbekannten Rang aufweist. In einem solchen Fall befindet sich der Puffer in einem flexiblen Zustand, in dem die Abmessungen festgelegt sind, wenn er als Modelleingabe verwendet wird, und in einem dynamischen Zustand, wenn er als Modellausgabe verwendet wird. Derselbe Puffer kann mit unterschiedlichen Ausgabeformen in unterschiedlichen Ausführungen verwendet werden und der Treiber muss die Puffergrößenänderung ordnungsgemäß handhaben.

Die Speicherdomäne ist eine optionale Funktion. Ein Treiber kann aus verschiedenen Gründen feststellen, dass er eine bestimmte Zuweisungsanforderung nicht unterstützen kann. Zum Beispiel:

  • Der angeforderte Puffer hat eine dynamische Größe.
  • Der Treiber weist Speicherbeschränkungen auf, die ihn daran hindern, große Puffer zu verarbeiten.

Es ist möglich, dass mehrere verschiedene Threads gleichzeitig aus dem vom Treiber verwalteten Puffer lesen. Der gleichzeitige Zugriff auf den Puffer zum Schreiben oder Lesen/Schreiben ist undefiniert, darf jedoch den Treiberdienst nicht zum Absturz bringen oder den Aufrufer auf unbestimmte Zeit blockieren. Der Treiber kann einen Fehler zurückgeben oder den Inhalt des Puffers in einem unbestimmten Zustand belassen.