Pool di memoria

Questa pagina descrive le strutture di dati e i metodi utilizzati per comunicare in modo efficiente i buffer degli operando tra il driver e il framework.

Al momento della compilazione del modello, il framework fornisce i valori degli operandi costanti al driver. A seconda della durata dell'operando costante, i suoi valori si trovano in un vettore HIDL o in un pool di memoria condivisa.

  • Se la durata è CONSTANT_COPY, i valori si trovano nel campo operandValues della struttura del modello. Poiché i valori del vettore HIDL vengono copiati durante la comunicazione tra processi (IPC), questo viene generalmente utilizzato solo per contenere una piccola quantità di dati come operandi scalari (ad esempio lo scalare di attivazione in ADD) e piccoli parametri tensoriali (ad esempio il tensore di forma in RESHAPE).
  • Se la durata è CONSTANT_REFERENCE, i valori si trovano nel campo pools della struttura del modello. Durante l'IPC vengono duplicati solo gli handle dei pool di memoria condivisa che non vengono copiati i valori non elaborati. Di conseguenza, è più efficiente conservare una grande quantità di dati (ad esempio, i parametri di peso nelle convoluzioni) utilizzando pool di memoria condivisa rispetto ai vettori HIDL.

Al momento dell'esecuzione del modello, il framework fornisce i buffer degli operandi di input e output al driver. A differenza delle costanti di tempo di compilazione che potrebbero essere inviate in un vettore HIDL, i dati di input e di output di un'esecuzione vengono sempre comunicati attraverso una raccolta di pool di memoria.

Il tipo di dati HIDL hidl_memory viene utilizzato sia nella compilazione che nell'esecuzione per rappresentare un pool di memoria condivisa non mappata. Il conducente deve mappare la memoria di conseguenza per renderla utilizzabile in base al nome del tipo di dati hidl_memory. I nomi delle memorie supportati sono:

  • ashmem: ricordo condiviso di Android. Per maggiori dettagli, vedi memory.
  • mmap_fd: memoria condivisa supportata da un descrittore di file fino a mmap.
  • hardware_buffer_blob: memoria condivisa supportata da un AHardwareBuffer nel formato AHARDWARE_BUFFER_FORMAT_BLOB. Disponibile presso le reti neurali (NN) HAL 1.2. Per maggiori dettagli, consulta AHardwareBuffer.
  • hardware_buffer: memoria condivisa supportata da un AHardwareBuffer generale che non usa il formato AHARDWARE_BUFFER_FORMAT_BLOB. Il buffer hardware in modalità non BLOB è supportato solo nell'esecuzione del modello.Disponibile da NN HAL 1.2. Per maggiori dettagli, consulta AHardwareBuffer.

A partire da NN HAL 1.3, NNAPI supporta i domini di memoria che forniscono interfacce allocatore per i buffer gestiti dal driver. I buffer gestiti dal driver possono essere utilizzati anche come input o output di esecuzione. Per maggiori dettagli, vedi Domini Memory.

I driver NNAPI devono supportare la mappatura dei nomi di memoria ashmem e mmap_fd. A partire dall'HAL 1.3 NN, i driver devono supportare anche la mappatura di hardware_buffer_blob. Il supporto per la modalità generale non BLOB hardware_buffer e per i domini di memoria è facoltativo.

Bufferhardware

AHardwareBuffer è un tipo di memoria condivisa che aggrega un buffer Gralloc. In Android 10, l'API Neural Networks (NNAPI) supporta l'utilizzo di AHardwareBuffer, che consente al driver di eseguire esecuzioni senza copiare i dati, migliorando le prestazioni e il consumo energetico delle app. Ad esempio, uno stack HAL della fotocamera può passare oggetti AHardwareBuffer alla NNAPI per i carichi di lavoro di machine learning utilizzando gli handle AHardwareBuffer generati dall'NDK della fotocamera e dalle API NDK dei contenuti multimediali. Per maggiori informazioni, vedi ANeuralNetworksMemory_createFromAHardwareBuffer.

Gli oggetti AHardwareBuffer utilizzati in NNAPI vengono passati al driver tramite uno struct hidl_memory denominato hardware_buffer o hardware_buffer_blob. Lo struct hidl_memory hardware_buffer_blob rappresenta solo gli oggetti AHardwareBuffer con il formato AHARDWAREBUFFER_FORMAT_BLOB.

Le informazioni richieste dal framework sono codificate nel campo hidl_handle dello struct hidl_memory. Il campo hidl_handle include native_handle, che codifica tutti i metadati richiesti su AHardwareBuffer o Gralloc buffer.

Il conducente deve decodificare correttamente il campo hidl_handle fornito e accedere alla memoria descritta da hidl_handle. Quando viene chiamato il metodo getSupportedOperations_1_2, getSupportedOperations_1_1 o getSupportedOperations, il conducente deve rilevare se è in grado di decodificare l'elemento hidl_handle fornito e accedere alla memoria descritta da hidl_handle. La preparazione del modello deve avere esito negativo se il campo hidl_handle utilizzato per un operando costante non è supportato. L'esecuzione deve non riuscire se il campo hidl_handle utilizzato per un operando di input o output dell'esecuzione non è supportato. Consigliamo al driver di restituire un codice di errore GENERAL_FAILURE se la preparazione o l'esecuzione del modello non va a buon fine.

Domini di memoria

Per i dispositivi con Android 11 o versioni successive, NNAPI supporta i domini di memoria che forniscono interfacce allocatore per i buffer gestiti dai driver. Ciò consente di passare le memorie native del dispositivo tra le esecuzioni, eliminando la copia e la trasformazione non necessarie dei dati tra esecuzioni consecutive sullo stesso driver. Questo flusso è illustrato nella Figura 1.

Memorizzazione nel buffer del flusso di dati con e senza domini di memoria

Figura 1. Memorizza nel buffer il flusso di dati utilizzando i domini di memoria

La funzionalità del dominio della memoria è destinata ai tensori che sono per lo più interni al driver e non richiedono un accesso frequente sul lato client. Esempi di questi tensori includono i tensori di stato nei modelli in sequenza. Per i tensori che richiedono frequente accesso alla CPU sul lato client, è preferibile utilizzare pool di memoria condivise.

Per supportare la funzionalità del dominio di memoria, implementa IDevice::allocate per consentire al framework di richiedere l'allocazione del buffer gestito dal driver. Durante l'allocazione, il framework fornisce le proprietà e i pattern di utilizzo seguenti per il buffer:

  • BufferDesc descrive le proprietà richieste del buffer.
  • BufferRole descrive il pattern di utilizzo potenziale del buffer come input o output di un modello preparato. Durante l'allocazione del buffer è possibile specificare più ruoli e il buffer allocato può essere utilizzato solo come ruoli specificati.

Il buffer allocato è interno al driver. Un conducente può scegliere qualsiasi posizione del buffer o layout dei dati. Una volta allocato il buffer, il client del driver può fare riferimento al buffer o interagire con il buffer utilizzando il token restituito o l'oggetto IBuffer.

Il token di IDevice::allocate viene fornito quando si fa riferimento al buffer come uno degli oggetti MemoryPool nella struttura Request di un'esecuzione. Per evitare che un processo tenti di accedere al buffer allocato in un altro processo, il driver deve applicare un'adeguata convalida a ogni utilizzo del buffer. Il driver deve confermare che l'utilizzo del buffer sia uno dei ruoli BufferRole forniti durante l'allocazione e deve interrompere immediatamente l'esecuzione se l'utilizzo è illegale.

L'oggetto IBuffer viene utilizzato per la copia esplicita della memoria. In alcune situazioni, il client del driver deve inizializzare il buffer gestito dal driver da un pool di memoria condivisa o copiarlo in un pool di memoria condivisa. Esempi di casi d'uso includono:

  • Inizializzazione del tensore di stato
  • Memorizzazione nella cache dei risultati intermedi
  • Esecuzione di riserva sulla CPU

Per supportare questi casi d'uso, il driver deve implementare IBuffer::copyTo e IBuffer::copyFrom con ashmem, mmap_fd e hardware_buffer_blob se supporta l'allocazione del dominio della memoria. Il driver può supportare la modalità non BLOB in modo facoltativo hardware_buffer.

Durante l'allocazione del buffer, le dimensioni del buffer possono essere dedotte dagli operandi del modello corrispondenti di tutti i ruoli specificati da BufferRole e dalle dimensioni fornite in BufferDesc. Con tutte le informazioni dimensionali combinate, il buffer potrebbe avere dimensioni o ranking sconosciuti. In questo caso, il buffer è in uno stato flessibile in cui le dimensioni sono fisse quando sono utilizzate come input del modello e in uno stato dinamico quando vengono utilizzate come output del modello. Lo stesso buffer può essere utilizzato con diverse forme di output in diverse esecuzioni e il driver deve gestire correttamente il ridimensionamento del buffer.

Il dominio della memoria è una funzionalità facoltativa. Un conducente può stabilire di non poter supportare una determinata richiesta di allocazione per vari motivi. Ecco alcuni esempi:

  • Il buffer richiesto ha una dimensione dinamica.
  • Il driver ha vincoli di memoria che gli impediscono di gestire buffer di grandi dimensioni.

È possibile che più thread diversi possano leggere contemporaneamente dal buffer gestito dal driver. L'accesso simultaneo al buffer per le operazioni di scrittura o lettura/scrittura non è definito, ma non deve causare l'arresto anomalo del servizio driver o bloccare il chiamante per un tempo indeterminato. Il driver può restituire un errore o lasciare i contenuti del buffer in uno stato indeterminato.