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 al driver i valori degli operatori costanti. A seconda del ciclo di vita 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 operandValues campo della struttura del modello. Poiché i valori nel vettore HIDL vengono copiati durante la comunicazione interprocessuale (IPC), questo viene solitamente utilizzato solo per contenere una piccola quantità di dati, come operandi scalari (ad esempio, l'operando scalare di attivazione in ADD) e piccoli parametri tensore (ad esempio, il tensore della 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 anziché copiare 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 al driver i buffer degli operandi di input e output. A differenza delle costanti di compilazione che potrebbero essere inviate in un vettore HIDL, i dati di input e output di un'esecuzione vengono sempre comunicati tramite 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: memoria condivisa di Android. Per maggiori dettagli, consulta la sezione sulla memoria.
  • mmap_fd: memoria condivisa basata su un descrittore file tramite mmap.
  • hardware_buffer_blob: memoria condivisa basata su un AHardwareBuffer con il formato AHARDWARE_BUFFER_FORMAT_BLOB. Disponibile da Neural Networks (NN) HAL 1.2. Per maggiori dettagli, consulta AHardwareBuffer.
  • hardware_buffer: memoria condivisa basata su un AHardwareBuffer generale che non utilizza il formato AHARDWARE_BUFFER_FORMAT_BLOB. Il buffer hardware in modalità non BLOB è supportato solo durante l'esecuzione del modello.Disponibile da NN HAL 1.2. Per maggiori dettagli, consulta AHardwareBuffer.

A partire da NN HAL 1.3, NNAPI supporta domini di memoria che forniscono interfacce di 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 di memoria.

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

AHardwareBuffer

AHardwareBuffer è un tipo di memoria condivisa che racchiude un buffer Gralloc. In Android 10, l'API Neural Networks (NNAPI) supporta l'utilizzo di AHardwareBuffer, consentendo al driver di eseguire le esecuzioni senza copiare i dati, il che migliora le prestazioni e il consumo energetico delle app. Ad esempio, uno stack HAL della fotocamera può passare oggetti AHardwareBuffer all'API NNAPI per i carichi di lavoro di machine learning utilizzando handle AHardwareBuffer generati dalle API NDK della fotocamera e NDK multimediale. Per ulteriori informazioni, consulta ANeuralNetworksMemory_createFromAHardwareBuffer.

Gli oggetti AHardwareBuffer utilizzati in NNAPI vengono passati al driver tramite una struttura hidl_memory denominata hardware_buffer o hardware_buffer_blob. La struct hidl_memory hardware_buffer_blob rappresenta solo 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 inserisce un a capo in native_handle, che codifica tutti i metadati richiesti relativi ad AHardwareBuffer o Gralloc buffer.

Il driver 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 driver deve rilevare se è in grado di decodificare il hidl_handle fornito e accedere alla memoria descritta da hidl_handle. La preparazione del modello deve non riuscire 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 di output dell'esecuzione non è supportato. È consigliabile che il driver restituisca 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 le 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. Mettere in coda 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 accesso frequente alla CPU lato client, è preferibile utilizzare pool di memoria condivisa.

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

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

Il buffer allocato è interno al driver. Un driver può scegliere qualsiasi posizione del buffer o layout dei dati. Quando il buffer viene allocato correttamente, il client del driver può fare riferimento al buffer o interagire con esso 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 impedire a un processo di tentare di accedere al buffer allocato in un altro processo, il driver deve applicare la convalida appropriata 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 determinate situazioni, il client del driver deve inizializzare il buffer gestito dal driver da un pool di memoria condivisa o copiare il buffer in un pool di memoria condivisa. Ecco alcuni esempi di casi d'uso:

  • Inizializzazione del tensore dello 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 di memoria. È facoltativo per il driver supportare la modalità non BLOB 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 se utilizzato come input del modello e in uno stato dinamico se utilizzato come output del modello. Lo stesso buffer può essere utilizzato con forme diverse di output in esecuzioni diverse 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. Ad esempio:

  • 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 leggano 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.