Cette page décrit les structures de données et les méthodes utilisées pour communiquer efficacement les tampons d'opérandes entre le pilote et le framework.
Au moment de la compilation du modèle, le framework fournit les valeurs des opérandes constantes au pilote. En fonction de la durée de vie de l'opérande constante, ses valeurs se trouvent dans un vecteur HIDL ou un pool de mémoire partagée.
- Si la durée de vie est
CONSTANT_COPY
, les valeurs sont situées dans le champoperandValues
de la structure du modèle. Étant donné que les valeurs du vecteur HIDL sont copiées lors de la communication inter-processus (IPC), celui-ci n'est généralement utilisé que pour contenir une petite quantité de données, telles que les opérandes scalaires (par exemple, le scalaire d'activation dansADD
) et les petits paramètres de Tensor (par exemple, le Tensor de forme dansRESHAPE
). - Si la durée de vie est
CONSTANT_REFERENCE
, les valeurs se trouvent dans le champpools
de la structure du modèle. Seuls les poignées des pools de mémoire partagée sont dupliquées lors de l'IPC, plutôt que de copier les valeurs brutes. Par conséquent, il est plus efficace de conserver une grande quantité de données (par exemple, les paramètres de pondération dans les convolutions) à l'aide de pools de mémoire partagés que de vecteurs HIDL.
Au moment de l'exécution du modèle, le framework fournit les tampons des opérateurs d'entrée et de sortie au pilote. Contrairement aux constantes au moment de la compilation qui peuvent être envoyées dans un vecteur HIDL, les données d'entrée et de sortie d'une exécution sont toujours communiquées via un ensemble de pools de mémoire.
Le type de données HIDL hidl_memory
est utilisé à la fois lors de la compilation et de l'exécution pour représenter un pool de mémoire partagée non mappé. Le pilote doit mapper la mémoire en conséquence pour la rendre utilisable en fonction du nom du type de données hidl_memory
.
Voici les noms de mémoire acceptés:
ashmem
: mémoire partagée Android. Pour en savoir plus, consultez la section Mémoire.mmap_fd
: mémoire partagée prise en charge par un descripteur de fichier viammap
.hardware_buffer_blob
: mémoire partagée prise en charge par un AHardwareBuffer au formatAHARDWARE_BUFFER_FORMAT_BLOB
. Disponible dans les réseaux de neurones (NN) HAL 1.2. Pour en savoir plus, consultez AHardwareBuffer.hardware_buffer
: mémoire partagée sauvegardée par un AHardwareBuffer général qui n'utilise pas le formatAHARDWARE_BUFFER_FORMAT_BLOB
. Le tampon matériel en mode non-BLOB n'est compatible qu'avec l'exécution du modèle.Disponible à partir de NN HAL 1.2. Pour en savoir plus, consultez AHardwareBuffer.
À partir de NN HAL 1.3, NNAPI accepte les domaines de mémoire qui fournissent des interfaces d'allocation pour les tampons gérés par le pilote. Les tampons gérés par le pilote peuvent également être utilisés comme entrées ou sorties d'exécution. Pour en savoir plus, consultez la section Domaines de mémoire.
Les pilotes NNAPI doivent prendre en charge le mappage des noms de mémoire ashmem
et mmap_fd
. À partir de NN HAL 1.3, les pilotes doivent également prendre en charge le mappage de hardware_buffer_blob
. La prise en charge des domaines de mémoire et de hardware_buffer
en mode non-BLOB général est facultative.
AHardwareBuffer
AHardwareBuffer est un type de mémoire partagée qui encapsule un tampon Gralloc. Dans Android 10, l'API Neural Networks (NNAPI) prend en charge l'utilisation de AHardwareBuffer, ce qui permet au pilote d'effectuer des exécutions sans copier de données, ce qui améliore les performances et la consommation d'énergie des applications. Par exemple, une pile HAL d'une caméra peut transmettre des objets AHardwareBuffer à NNAPI pour les charges de travail de machine learning à l'aide de poignées AHardwareBuffer générées par les API NDK d'appareil photo et Media NDK. Pour en savoir plus, consultez ANeuralNetworksMemory_createFromAHardwareBuffer
.
Les objets AHardwareBuffer utilisés dans NNAPI sont transmis au pilote via une structure hidl_memory
nommée hardware_buffer
ou hardware_buffer_blob
.
La structure hidl_memory
hardware_buffer_blob
ne représente que les objets AHardwareBuffer au format AHARDWAREBUFFER_FORMAT_BLOB
.
Les informations requises par le framework sont encodées dans le champ hidl_handle
de la structure hidl_memory
. Le champ hidl_handle
encapsule native_handle
, qui encode toutes les métadonnées requises sur AHardwareBuffer ou le tampon Gralloc.
Le pilote doit décoder correctement le champ hidl_handle
fourni et accéder à la mémoire décrite par hidl_handle
. Lorsque la méthode getSupportedOperations_1_2
, getSupportedOperations_1_1
ou getSupportedOperations
est appelée, le pilote doit détecter s'il peut décoder le hidl_handle
fourni et accéder à la mémoire décrite par hidl_handle
. La préparation du modèle doit échouer si le champ hidl_handle
utilisé pour un opérande constant n'est pas compatible. L'exécution doit échouer si le champ hidl_handle
utilisé pour une operande d'entrée ou de sortie de l'exécution n'est pas compatible. Il est recommandé que le pilote renvoie un code d'erreur GENERAL_FAILURE
en cas d'échec de la préparation ou de l'exécution du modèle.
Domaines de mémoire
Pour les appareils exécutant Android 11 ou version ultérieure, NNAPI est compatible avec les domaines de mémoire qui fournissent des interfaces d'allocation pour les tampons gérés par le pilote. Cela permet de transmettre les mémoires natives de l'appareil lors des exécutions, ce qui supprime la copie et la transformation inutiles des données entre les exécutions consécutives sur le même pilote. Ce flux est illustré dans la figure 1.
Figure 1 : Mettre en mémoire tampon le flux de données à l'aide de domaines de mémoire
La fonctionnalité de domaine de mémoire est destinée aux Tensors principalement internes au pilote et qui n'ont pas besoin d'un accès fréquent côté client. Parmi ces tenseurs, citons les tenseurs d'état dans les modèles de séquence. Pour les tenseurs qui nécessitent un accès fréquent au processeur côté client, il est préférable d'utiliser des pools de mémoire partagés.
Pour prendre en charge la fonctionnalité de domaine de mémoire, implémentez IDevice::allocate
pour permettre au framework de demander une allocation de tampon gérée par le pilote. Lors de l'allocation, le framework fournit les propriétés et les modèles d'utilisation suivants pour le tampon:
BufferDesc
décrit les propriétés requises du tampon.BufferRole
décrit le modèle d'utilisation potentiel du tampon en tant qu'entrée ou sortie d'un modèle préparé. Vous pouvez spécifier plusieurs rôles lors de l'allocation de la mémoire tampon, et la mémoire tampon allouée ne peut être utilisée que pour les rôles spécifiés.
Le tampon alloué est interne au pilote. Un pilote peut choisir n'importe quel emplacement de tampon ou mise en page de données. Une fois le tampon alloué, le client du pilote peut le référencer ou interagir avec lui à l'aide du jeton renvoyé ou de l'objet IBuffer
.
Le jeton de IDevice::allocate
est fourni lorsque vous référencez le tampon en tant qu'un des objets MemoryPool
dans la structure Request
d'une exécution. Pour empêcher un processus d'essayer d'accéder au tampon alloué dans un autre processus, le pilote doit appliquer une validation appropriée à chaque utilisation du tampon. Le pilote doit vérifier que l'utilisation de la mémoire tampon correspond à l'un des rôles BufferRole
fournis lors de l'allocation et doit échouer immédiatement si l'utilisation est illégale.
L'objet IBuffer
est utilisé pour la copie explicite de la mémoire. Dans certains cas, le client du pilote doit initialiser le tampon géré par le pilote à partir d'un pool de mémoire partagée ou copier le tampon dans un pool de mémoire partagée. Voici quelques exemples de cas d'utilisation:
- Initialisation du Tensor d'état
- Mettre en cache les résultats intermédiaires
- Exécution de remplacement sur le processeur
Pour prendre en charge ces cas d'utilisation, le pilote doit implémenter IBuffer::copyTo
et IBuffer::copyFrom
avec ashmem
, mmap_fd
et hardware_buffer_blob
s'il prend en charge l'allocation de domaines de mémoire. Le pilote peut être compatible avec le mode non-BLOB hardware_buffer
.
Lors de l'allocation de la mémoire tampon, les dimensions de la mémoire tampon peuvent être déduites des opérandes de modèle correspondants de tous les rôles spécifiés par BufferRole
et des dimensions fournies dans BufferDesc
. Avec toutes les informations dimensionnelles combinées, la mémoire tampon peut avoir des dimensions ou un rang inconnus. Dans ce cas, le tampon est dans un état flexible où les dimensions sont fixes lorsqu'elles sont utilisées comme entrée de modèle et dans un état dynamique lorsqu'elles sont utilisées comme sortie de modèle. Le même tampon peut être utilisé avec différentes formes de sorties dans différentes exécutions. Le pilote doit gérer correctement le redimensionnement du tampon.
Le domaine de mémoire est une fonctionnalité facultative. Un pilote peut déterminer qu'il ne peut pas prendre en charge une demande d'allocation donnée pour plusieurs raisons. Exemple :
- La mémoire tampon demandée a une taille dynamique.
- Le pilote présente des contraintes de mémoire qui l'empêchent de gérer de grands tampons.
Il est possible que plusieurs threads différents lisent simultanément à partir du tampon géré par le pilote. L'accès simultané au tampon en écriture ou en lecture/écriture n'est pas défini, mais il ne doit pas faire planter le service de pilote ni bloquer l'appelant indéfiniment. Le pilote peut renvoyer une erreur ou laisser le contenu du tampon dans un état indéterminé.