Pool di memoria

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

Al momento della compilazione del modello, il framework fornisce al driver i valori degli operandi costanti. 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 nel vettore HIDL vengono copiati durante la comunicazione interprocesso (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 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 grezzi. Pertanto, è più efficiente conservare una grande quantità di dati (ad esempio, i parametri di peso nelle convoluzioni) utilizzando pool di memoria condivisi 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 in fase di compilazione che potrebbero essere inviate in un vettore HIDL, i dati di input e 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 mappato. Il driver dovrebbe mappare la memoria di conseguenza per renderla utilizzabile in base al nome del tipo di dati hidl_memory . I nomi delle memorie supportate sono:

  • ashmem : memoria condivisa Android. Per maggiori dettagli vedere memoria .
  • mmap_fd : memoria condivisa supportata da un descrittore di file tramite mmap .
  • hardware_buffer_blob : memoria condivisa supportata da un AHardwareBuffer con il formato AHARDWARE_BUFFER_FORMAT_BLOB . Disponibile da Reti neurali (NN) HAL 1.2. Per ulteriori dettagli, vedere AHardwareBuffer .
  • hardware_buffer : memoria condivisa supportata da un AHardwareBuffer generale che non utilizza 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 ulteriori dettagli, vedere AHardwareBuffer .

Da NN HAL 1.3, NNAPI supporta domini di memoria che forniscono interfacce di allocazione per buffer gestiti dal driver. I buffer gestiti dal driver possono essere utilizzati anche come input o output di esecuzione. Per ulteriori dettagli, vedere Domini di memoria .

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

AHardwareBuffer

AHardwareBuffer è un tipo di memoria condivisa che avvolge un buffer Gralloc . In Android 10, la Neural Networks API (NNAPI) supporta l'utilizzo di AHardwareBuffer , consentendo 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 carichi di lavoro di machine learning utilizzando gli handle AHardwareBuffer generati dall'NDK della fotocamera e dalle API NDK multimediali. 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 struttura 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 della struttura hidl_memory . Il campo hidl_handle racchiude native_handle , che codifica tutti i metadati richiesti sul buffer AHardwareBuffer o Gralloc.

Il driver deve decodificare correttamente il campo hidl_handle fornito e accedere alla memoria descritta da hidl_handle . Quando viene richiamato il metodo getSupportedOperations_1_2 , getSupportedOperations_1_1 o getSupportedOperations , il driver dovrebbe rilevare se è in grado di decodificare l' hidl_handle fornito e accedere alla memoria descritta da hidl_handle . La preparazione del modello deve fallire se il campo hidl_handle utilizzato per un operando costante non è supportato. L'esecuzione deve fallire se il campo hidl_handle utilizzato per un operando di input o output dell'esecuzione non è supportato. Si consiglia al driver di restituire un codice di errore GENERAL_FAILURE se la preparazione o l'esecuzione del modello non riesce.

Domini di memoria

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

Buffer del flusso di dati con e senza domini di memoria

Figura 1. Flusso di dati nel buffer utilizzando i domini di memoria

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

Per supportare la funzionalità del dominio di memoria, implementare 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 del buffer:

  • BufferDesc descrive le proprietà richieste del buffer.
  • BufferRole descrive il potenziale modello 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 ruoli specificati.

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

Il token da 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 adeguata a ogni utilizzo del buffer. Il driver deve verificare che l'utilizzo del buffer sia uno dei ruoli BufferRole forniti durante l'allocazione e deve fallire 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 copiare il buffer in un pool di memoria condivisa. I casi d'uso di esempio includono:

  • Inizializzazione del tensore degli stati
  • Memorizzazione nella cache dei risultati intermedi
  • Esecuzione del fallback 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 che il driver supporti la modalità non BLOB hardware_buffer .

Durante l'allocazione del buffer, le dimensioni del buffer possono essere dedotte dai corrispondenti operandi del modello 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 rango sconosciuti. In tal caso, il buffer è in uno stato flessibile in cui le dimensioni sono fisse quando utilizzato come input del modello e in uno stato dinamico quando utilizzato 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 di memoria è una funzionalità opzionale. Un conducente può determinare che non può supportare una determinata richiesta di allocazione per una serie di motivi. Per esempio:

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

È possibile che diversi thread leggano contemporaneamente dal buffer gestito dal driver. L'accesso simultaneo al buffer per scrittura o lettura/scrittura non è definito, ma non deve bloccare il servizio driver o bloccare il chiamante a tempo indeterminato. Il driver può restituire un errore o lasciare il contenuto del buffer in uno stato indeterminato.