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 neloperandValues
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 inADD
) e piccoli parametri tensore (ad esempio, il tensore della forma inRESHAPE
). - Se la durata è
CONSTANT_REFERENCE
, i valori si trovano nel campopools
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 tramitemmap
.hardware_buffer_blob
: memoria condivisa basata su un AHardwareBuffer con il formatoAHARDWARE_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 formatoAHARDWARE_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.
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.