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 FeldoperandValues
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 inADD
) und kleine Tensorparameter (z. B. den Formtensor inRESHAPE
) zu speichern. - Wenn die Lebensdauer
CONSTANT_REFERENCE
ist, befinden sich die Werte im Feldpools
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 übermmap
unterstützt wird.hardware_buffer_blob
: gemeinsamer Speicher, unterstützt von einem AHardwareBuffer mit dem FormatAHARDWARE_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 FormatAHARDWARE_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.
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.