Arbeitsspeicherpools

Auf dieser Seite werden die Datenstrukturen und Methoden zur effizienten Kommunikation von Operanden-Zwischenspeichern zwischen dem Treiber und dem Framework beschrieben.

Zum Zeitpunkt der Modellkompilierung stellt das Framework dem Treiber die Werte der konstanten Operanden zur Verfügung. Abhängig von der Lebensdauer des konstanten Operanden befinden sich seine Werte entweder in einem HIDL-Vektor oder in einem gemeinsamen 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 er in der Regel nur verwendet, um eine kleine Datenmenge wie skalare Operanden (z. B. den Aktivierungsskalar in ADD) und kleine Tensorparameter (z. B. den Formtensor in RESHAPE) zu speichern.
  • Wenn die Lebensdauer CONSTANT_REFERENCE ist, befinden sich die Werte im Feld pools der Modellstruktur. Bei der IPC werden nur die Handles der Pools mit gemeinsam genutztem Arbeitsspeicher dupliziert, anstatt die Rohwerte zu kopieren. Daher ist es effizienter, eine große Datenmenge (z. B. die Gewichtungsparameter in Faltungen) in gemeinsam genutzten Speicherpools als HIDL-Vektoren zu speichern.

Zur Zeit der Modellausführung stellt das Framework dem Treiber die Zwischenspeicher der Eingabe- und Ausgabeoperanden zur Verfügung. Im Gegensatz zu den Kompilierungszeitkonstanten, die in einem HIDL-Vektor gesendet werden können, werden die Ein- 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 gemeinsamen Speicherpool darzustellen. Der Treiber sollte den Speicher entsprechend dem Namen des Datentyps hidl_memory zuordnen, damit er nutzbar ist. Folgende Arbeitsspeichernamen werden unterstützt:

  • ashmem: geteilte Android-Erinnerung. Weitere Informationen finden Sie unter memory.
  • mmap_fd: Freigegebener Arbeitsspeicher, der von einem Dateideskriptor über mmap unterstützt wird.
  • hardware_buffer_blob: gemeinsamer Speicher, unterstützt von einem AHardwareBuffer mit dem Format AHARDWARE_BUFFER_FORMAT_BLOB. Verfügbar von Neural Networks (NN) HAL 1.2. Weitere Informationen finden Sie unter AHardwareBuffer.
  • hardware_buffer: gemeinsamer Speicher, der von einem allgemeinen AHardwareBuffer gestützt wird, der nicht das Format AHARDWARE_BUFFER_FORMAT_BLOB verwendet. Der Hardware-Zwischenspeicher des Nicht-BLOB-Modus wird nur bei der Modellausführung unterstützt.Verfügbar ab NN HAL 1.2. Weitere Informationen finden Sie unter AHardwareBuffer.

Ab NN HAL 1.3 unterstützt NNAPI Speicherdomains, die Zuweisungs-Schnittstellen für Treiber-verwaltete Zwischenspeicher bereitstellen. Die Treiber-verwalteten Zwischenspeicher können auch als Ausführungseingaben oder -ausgaben verwendet werden. Weitere Informationen finden Sie unter Memory-Domains.

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 Nicht-BLOB-Modus-hardware_buffer und Speicherdomains ist optional.

AHardwareBuffer

AHardwareBuffer ist eine Art von gemeinsamem Arbeitsspeicher, der einen Gralloc-Zwischenspeicher umschließt. In Android 10 unterstützt die Neural Networks API (NNAPI) die Verwendung von AHardwareBuffer, sodass der Treiber Ausführungen ohne Kopieren von Daten ausführen kann. Dadurch werden Leistung und Stromverbrauch von Anwendungen verbessert. Ein Kamera-HAL-Stapel kann beispielsweise AHardwareBuffer-Objekte für ML-Arbeitslasten an die NNAPI übergeben. Dazu werden AHardwareBuffer-Handles verwendet, die von Kamera-NDK und Media-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 stellt nur AHardwareBuffer-Objekte mit dem Format AHARDWAREBUFFER_FORMAT_BLOB dar.

Die vom Framework erforderlichen Informationen sind im Feld hidl_handle der Struktur hidl_memory codiert. Das Feld hidl_handle umschließt native_handle, mit dem alle erforderlichen Metadaten zu AHardwareBuffer oder Gralloc-Zwischenspeicher codiert werden.

Der Treiber muss das angegebene Feld hidl_handle ordnungsgemäß decodieren und auf den in 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 angegebene hidl_handle decodieren und auf den in hidl_handle beschriebenen Speicher zugreifen kann. Die Modellvorbereitung muss fehlschlagen, wenn das für einen konstanten Operand verwendeten Feld hidl_handle nicht unterstützt wird. Die Ausführung muss fehlschlagen, wenn das Feld hidl_handle, das für einen Ein- oder Ausgabeoperanden der Ausführung verwendet wird, nicht unterstützt wird. Der Treiber sollte den Fehlercode GENERAL_FAILURE zurückgeben, wenn die Modellvorbereitung oder -ausführung fehlschlägt.

Arbeitsspeicherdomains

Auf Geräten mit Android 11 oder höher unterstützt NNAPI Speicherdomains, die Zuweisungsschnittstellen für Treiber-verwaltete Zwischenspeicher bereitstellen. Dadurch können gerätenative Speicher für mehrere Ausführungen übergeben werden, wodurch unnötiges Kopieren und Transformieren von Daten zwischen aufeinanderfolgenden Ausführungen auf demselben Treiber unterdrückt wird. Dieser Ablauf ist in Abbildung 1 dargestellt.

Zwischenspeichern des Datenflusses mit und ohne Speicherdomains

Abbildung 1: Datenfluss mit Speicherdomains zwischenspeichern

Das Speicherdomainfeature ist für Tensoren vorgesehen, die hauptsächlich intern im Treiber sind und keinen häufigen Clientzugriff benötigen. Beispiele für solche Tensoren sind die Zustandstensoren in Sequenzmodellen. Für Tensoren, die clientseitigen häufigen CPU-Zugriff benötigen, empfiehlt es sich, gemeinsame Speicherpools zu verwenden.

Implementieren Sie IDevice::allocate, damit das Framework die Treiber-verwaltete Zwischenspeicherzuweisung anfordern kann, um das Feature für Arbeitsspeicherdomains zu unterstützen. Während der Zuweisung stellt das Framework die folgenden Attribute und Nutzungsmuster für den Zwischenspeicher bereit:

  • In BufferDesc werden die erforderlichen Attribute des Zwischenspeichers beschrieben.
  • BufferRole beschreibt das potenzielle Nutzungsmuster des Zwischenspeichers als Eingabe oder Ausgabe eines vorbereiteten Modells. Während der Zwischenspeicherzuweisung können mehrere Rollen angegeben werden. Der zugewiesene Zwischenspeicher kann nur als die angegebenen Rollen verwendet werden.

Der zugewiesene Zwischenspeicher ist Treiberintern. Ein Fahrer kann einen beliebigen Pufferort oder ein Datenlayout wählen. Nachdem der Zwischenspeicher erfolgreich zugewiesen wurde, kann der Client des Treibers mithilfe des zurückgegebenen Tokens oder des IBuffer-Objekts auf den Zwischenspeicher verweisen oder mit ihm interagieren.

Das Token aus IDevice::allocate wird bereitgestellt, wenn auf den Zwischenspeicher als eines der MemoryPool-Objekte in der Request-Struktur einer Ausführung verwiesen wird. Um zu verhindern, dass ein Prozess versucht, auf den Zwischenspeicher zuzugreifen, der in einem anderen Prozess zugewiesen wurde, muss der Treiber bei jeder Verwendung des Zwischenspeichers eine ordnungsgemäße Validierung durchführen. Der Treiber muss validieren, dass die Zwischenspeichernutzung eine der BufferRole-Rollen ist, die während der Zuweisung bereitgestellt werden. Die Ausführung muss sofort fehlschlagen, wenn die Nutzung illegal ist.

Das Objekt IBuffer wird zum expliziten Kopieren von Arbeitsspeicher verwendet. In bestimmten Situationen muss der Client des Treibers den Treiber-verwalteten Zwischenspeicher aus einem gemeinsamen Arbeitsspeicherpool initialisieren oder den Puffer in einen Pool mit gemeinsam genutztem Arbeitsspeicher kopieren. Beispiele für Anwendungsfälle:

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

Der Treiber muss IBuffer::copyTo und IBuffer::copyFrom mit ashmem, mmap_fd und hardware_buffer_blob implementieren, wenn die Zuweisung von Arbeitsspeicherdomains unterstützt wird. Der Treiber kann optional den Nicht-BLOB-Modus hardware_buffer unterstützen.

Während der Zwischenspeicherzuweisung können die Dimensionen des Zwischenspeichers aus den entsprechenden Modelloperanden aller mit BufferRole angegebenen Rollen und den in BufferDesc angegebenen Dimensionen abgeleitet werden. Wenn alle Dimensionsinformationen kombiniert sind, kann der Zwischenspeicher unbekannte Dimensionen oder einen unbekannten Rang haben. In einem solchen Fall befindet sich der Zwischenspeicher in einem flexiblen Zustand, bei dem die Abmessungen fest sind, wenn sie als Modelleingabe verwendet werden, und in einem dynamischen Zustand, wenn sie als Modellausgabe verwendet werden. Derselbe Zwischenspeicher kann in verschiedenen Ausführungen mit unterschiedlichen Ausgabeformen verwendet werden und der Treiber muss die Größenanpassung des Zwischenspeichers entsprechend anpassen.

Memory Domain ist eine optionale Funktion. Ein Treiber kann aus verschiedenen Gründen feststellen, dass er eine bestimmte Zuweisungsanfrage nicht unterstützen kann. Beispiel:

  • Der angeforderte Zwischenspeicher hat eine dynamische Größe.
  • Aufgrund von Arbeitsspeicherbeschränkungen des Treibers kann er große Puffer nicht verarbeiten.

Es ist möglich, dass mehrere verschiedene Threads gleichzeitig aus dem Treiber verwalteten Zwischenspeicher lesen. Der gleichzeitige Zugriff auf den Zwischenspeicher für Schreib- oder Lese-/Schreibvorgänge ist nicht definiert. Der Treiberdienst darf jedoch nicht zum Absturz gelangen oder den Aufrufer dauerhaft blockieren. Der Treiber kann einen Fehler zurückgeben oder den Inhalt des Zwischenspeichers in einem unbestimmten Zustand belassen.