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 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 campooperandValues
della struttura del modello. Poiché i valori nel vettore HIDL vengono copiati durante la comunicazione interprocesso (IPC), questo viene in genere utilizzato solo per contenere una piccola quantità di dati, come operandi scalari (ad esempio, lo scalare di attivazione inADD
) e piccoli parametri tensore (ad esempio, il tensore di forma inRESHAPE
). - Se la durata è
CONSTANT_REFERENCE
, i valori si trovano nel campopools
della struttura del modello. Durante la comunicazione IPC vengono duplicati solo gli handle dei pool di memoria condivisa, anziché copiare i valori non elaborati. Pertanto, è più efficiente memorizzare 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 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 in fase di compilazione che di esecuzione per
rappresentare un pool di memoria condivisa non mappato. Il driver 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 Android. Per maggiori dettagli, vedi Memoria.mmap_fd
: memoria condivisa supportata da un descrittore di file tramitemmap
.hardware_buffer_blob
: memoria condivisa supportata da un AHardwareBuffer con il formatoAHARDWARE_BUFFER_FORMAT_BLOB
. Disponibile da Neural Networks (NN) HAL 1.2. Per maggiori dettagli, vedi AHardwareBuffer.hardware_buffer
: memoria condivisa supportata da un AHardwareBuffer generale che non utilizza il formatoAHARDWARE_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, vedi AHardwareBuffer.
A partire da NN HAL 1.3, NNAPI supporta i domini di memoria che forniscono interfacce di allocazione per i buffer gestiti dai 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
. A partire 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 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'NNAPI per carichi di lavoro di machine learning utilizzando handle AHardwareBuffer generati dalle API NDK della fotocamera e NDK multimediali. Per ulteriori informazioni, vedi
ANeuralNetworksMemory_createFromAHardwareBuffer
.
Gli oggetti AHardwareBuffer utilizzati in NNAPI vengono passati al driver tramite una
struct hidl_memory
denominata hardware_buffer
o hardware_buffer_blob
.
La hidl_memory
struct 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
della struttura hidl_memory
. Il campo hidl_handle
contiene native_handle
,
che codifica tutti i metadati richiesti su AHardwareBuffer o sul buffer Gralloc.
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 può decodificare il valore 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 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 domini di memoria che forniscono interfacce di allocazione per buffer gestiti dai driver. Ciò consente di trasferire 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.
Figura 1. Bufferizzare il flusso di dati utilizzando i domini di memoria
La funzionalità del dominio di memoria è pensata per i tensori che sono per lo più interni al driver e non richiedono un accesso frequente lato client. Esempi di questi tensori includono i tensori di stato nei modelli di sequenza. Per i tensori che richiedono un accesso frequente alla CPU lato client, è preferibile utilizzare pool di memoria condivisa.
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 seguenti proprietà e i seguenti pattern 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 ruoli specificati.
Il buffer allocato è interno al driver. Un driver può scegliere qualsiasi buffer
posizione 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 una convalida corretta a ogni
utilizzo del buffer. Il conducente deve verificare che l'utilizzo del buffer sia uno dei
ruoli BufferRole
forniti durante l'allocazione e deve interrompere l'esecuzione
immediatamente 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. Esempi di casi d'uso:
- 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 di memoria. Il supporto della modalità non BLOB è facoltativo per il driver
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 si trova in uno stato flessibile in cui le dimensioni sono fisse quando viene utilizzato come input del modello e in uno stato dinamico quando viene 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 driver può determinare di non poter supportare una determinata richiesta di allocazione per diversi 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 diversi thread leggano contemporaneamente dal buffer gestito dal driver. L'accesso simultaneo al buffer per la scrittura o la lettura/scrittura non è definito, ma non deve causare l'arresto anomalo del servizio di driver o bloccare il chiamante indefinitamente. Il driver può restituire un errore o lasciare il contenuto del buffer in uno stato indeterminato.