Services

Intégration du NPU i.MX8M Plus de NXP dans la plateforme Aidge

Deep Learning
IA embarquée
Computer Vision

Client

Le CEA-List est l’un des instituts de la Direction de la Recherche Technologique du CEA, spécialisé dans les systèmes numériques intelligents et dédié à l’accélération de la transformation numérique de l’industrie. 

Dans cette logique, il porte et développe Aidge, le premier framework open source et collaboratif dédié à l'IA embarquée, qui offre une chaîne d'outils complète pour concevoir, optimiser et déployer des réseaux neuronaux profonds destinés à des systèmes soumis à des contraintes. Aidge intègre des fonctionnalités innovantes visant à réduire la complexité des modèles et leurs besoins en mémoire, et permet une implémentation optimisée sur un large éventail d'architectures matérielles.

Problématique

Si Aidge intègre une large palette d’architectures matérielles telles que les microcontrôleurs (MCU), les processeurs (CPU), les cartes graphiques (GPU), les accélérateurs neuronaux (NPU) (), le CEA et ses partenaires industriels souhaitaient élargir son éventail de cible hardware en intégrant un NPU du fabricant néerlandais NXP, et plus précisément le i.MX8M Plus.

Solution développée : 

Pour assurer l’implémentation fructueuse du NPU i.MX8M Plus, Micelia a interfacé 3 opérateurs entre Aidge et le NPU tout en optimisant le Runtime desdits opérateurs. Le choix des opérateurs s’est porté sur Conv2D, Activation SiLU et Concat puisqu’ils couvrent la majorité des contraintes et difficultés techniques du NPU, tout en étant représentatifs du fonctionnement réel du modèle. Enfin, Micelia a également réalisé l’interfacage bas niveau entre Aidge et la librairie de Verisilicon (TIMVX).

Bénéfice client : 

La plateforme Aidge intègre et supporte maintenant le NPU i.MX8M Plus de NXP et intègre donc à cette occasion une nouvelle cible hardware européenne. Un vrai plus pour la souveraineté numérique européenne quand on sait que le framework Aidge vise à être utilisé par de nombreux acteurs industriels européens.

Détails de la collaboration : 

Début 2026, Micelia a reçu un cahier des charges concernant l’expression d’un besoin relatif au développement de la plateforme Aidge et plus précisément sur l’intégration du NPU i.MX8M Plus dans la plateforme.

L'expertise en IA embarquée et l’approche technique proposée par Micelia ont convaincu les équipes du CEA en charge du développement du framework Aidge .

Pour réussir l’intégration du NPU, Micelia proposa une approche en deux temps distincts. La première étape portait sur l’implémentation de quelques opérateurs YOLOv8n (un détecteur d’objet) quand la seconde se focalisait sur le développement du runtime NPU. 

L’équipe Micelia, emmenée par Michael Gellenoncourt, commença par mapper les opérateurs Aidge vers les opérateurs TIM‑VX.

Pour rester dans un périmètre maîtrisé, seuls trois opérateurs clés ont été intégrés dans cette première phase, à savoir :  

  • Conv2D 
  • Activation SiLU (ou approximation compatible NPU)
  • Concat 

Mais pourquoi ceux-là précisément ? Ils ont été choisis car ils permettent de couvrir la majorité des contraintes et difficultés techniques du NPU, tout en étant représentatifs du fonctionnement réel du modèle.

Commençons par Conv2D, il s’agit d’un opérateur central du backbone YOLOv8n, il permet de valider :

  • la gestion de la quantification INT8 (scale, zero‑point),
  • la fusion éventuelle avec BatchNorm,
  • les contraintes strictes du NPU Vivante (alignements, tailles multiples, formats),
  • la construction d’un node NPU natif via les API bas niveau,
  • la gestion des buffers intermédiaires et des layouts requis.

Son intégration s’est faite étape par étape. Tout d’abord, nous avons dû vérifier la compatibilité NPU (type INT8 supporté, contraintes sur les tailles, format de données attendu) avant de créer les tenseurs et l’opérateur de convolution TIM-VX. Par la suite, il fallait gérer les layouts et intégrer l’opérateur dans le graphe TIM-VX. Ce n’est qu’après avoir passé ces différentes étapes que nous avons pu procéder à la validation sur cible en comparant la sortie NPU avec la sortie CPU de référence (en tenant compte de la quantification) tout en vérifiant la stabilité et l’absence d’erreurs d’exécution

Concernant le choix d’intégrer l’activation SiLU (ou approximation compatible NPU), il faut savoir que YOLOv8 utilise SiLU comme activation principale. La plateforme Aidge ne la supportant pas nativement, ce kernel permet de  tester la robustesse du pipeline face à des fonctions non natives en validant les points suivants :

  • la capacité à composer plusieurs primitives NPU pour approcher une activation complexe,
  • ou à gérer un fallback CPU propre et performant,
  • la propagation correcte des quantifications entre opérateurs,
  • la compatibilité avec les formats internes Aidge.

Pour mener à bien cet interfaçage, nous avons dû choisir une stratégie d’implémentation, afin d’éviter l’écriture d’un kernel, et créer les opérateurs TIM‑VX nécessaires, à savoir un opérateur d’activation sigmoïde et un opérateur de multiplication élément‑par‑élément.

Micelia a ensuite connecter les deux opérateurs pour former un sous-graphe équivalent à SiLU et gérer la quantification pour s’assurer que les paramètres de quantification (échelles, zero‑points) restent cohérents entre les deux opérateurs et les ajuster, si nécessaire, pour limiter la perte de précision. 

Une fois le sous-graphe créé, il a été intégré dans le graphe TIM‑VX global, un travail essentiel pour valider la précision. Une validation qui repose sur la comparaison entre la sortie de ce sous‑graphe NPU avec la sortie CPU de référence et la vérification que l’erreur reste acceptable pour YOLOv8n

Ne restait donc plus qu’à intégrer Concat, un opérateur indispensable pour les fusions de features dans YOLOv8n. Cet opérateur est essentiel pour vérifier la capacité du backend à gérer des topologies complexes.  Il permet ainsi de valider :

  • la gestion des buffers multiples en entrée,
  • les contraintes d’alignement mémoire du NPU,
  • la construction de graphes NPU non linéaires,
  • la cohérence des shapes et quantifications entre branches.

Comme pour Conv2D, nous avons commencé par vérifier sa compatibilité. Ici, nous nous sommes assuré que TIM‑VX supporte la concaténation tout en vérifiant que toutes les entrées ont des dimensions compatibles. Par la suite, il était une nouvelle fois nécessaire de créer les tenseurs d’entrée et de sortie ainsi que l’opérateur de concaténation TIM‑VX.

Enfin, nous nous sommes occupés des layouts, nous assurant que les tenseurs sont dans le format attendu par TIM‑VX (par exemple WHCN en colonne major), insérant, au besoin, des conversions si nécessaire.

Ces étapes passées, nous avons pu valider l’intégration de l’opérateur Concat en testant avec plusieurs cas (2, 3, 4 entrées... et variations des axes de concaténation) et en comparant avec la concaténation CPU. 

Les différents opérateurs de YOLOv8n maintenant intégrés, nous pouvions passer à l’optimisation de leur runtime.

Ici, le terme « runtime » désigne moins une optimisation de performance qu'un travail de fiabilisation. Il s’agit de faire en sorte que le graphe assemblé à partir de ces trois opérateurs s'exécute correctement, une fois compilé et déployé, sur le NPU de la carte i.MX8M Plus.

Pour chaque opérateur intégré, nous avons développé, côté génération de code, le mécanisme permettant de produire l'appel TIM‑VX correspondant : 

  • la création des tenseurs concernés, 
  • la création de l'opérateur TIM‑VX natif associé, 
  • et l'injection des paramètres nécessaires (stride, padding, dilation, etc.). 

Il ne s'agit donc pas d'un opérateur qui s'exécute directement au sein d'Aidge, mais d'un générateur qui produit, pour chaque graphe fourni, le code C++ correspondant.

Restait alors à ce que ce code sache construire et faire vivre le graphe TIM‑VX lui‑même. Le code généré crée ainsi, à son démarrage, un contexte TIM‑VX puis un graphe, avant de, pour chaque opérateur du graphe Aidge d'origine :

  • créer les tenseurs TIM‑VX nécessaires (ou réutiliser ceux déjà créés par un opérateur voisin)
  • créer l'opérateur TIM‑VX natif correspondant
  • connecter les opérateurs entre eux selon la topologie du graphe Aidge
  • finaliser puis compiler le graphe via les fonctions prévues par TIM‑VX

Le graphe TIM‑VX était donc désormais généré. Encore fallait‑il s'assurer qu'il s'exécute et se synchronise correctement une fois le binaire compilé et déployé sur la carte.

Pour le vérifier, le code généré prépare les buffers d'entrée (copie des données depuis Aidge vers les tenseurs TIM‑VX), lance l'exécution du graphe puis récupère les tenseurs de sortie. À chaque étape critique  (création du contexte, création du graphe, compilation, exécution, récupération des sorties), le code vérifie le retour TIM‑VX et remonte une erreur explicite en cas d'échec, plutôt que de laisser l'exécution échouer silencieusement sur cible. 

La validation de la justesse du résultat, elle, repose sur la comparaison entre la sortie capturée sur la carte et un modèle de référence numpy reproduisant fidèlement l'arithmétique int8 attendue (accumulation, arrondi au plus proche pair, saturation), la même règle de calcul que celle appliquée par TIM‑VX.

Une fois ce mécanisme de génération et d'exécution fiabilisé, on pouvait passer à l'intégration dans Aidge à proprement parler. 

TIM‑VX a été enregistré comme bibliothèque d'export à part entière au sein d'Aidge, aux côtés des exports déjà existants pour CPU et ARM Cortex‑M, et donc sélectionnable comme cible depuis la configuration du projet. 

Cette intégration a été validée par une suite de tests unitaires exportant plusieurs graphes représentatifs (Conv2D, Concat, SiLU, ainsi que des cas limites (dilatation, noyaux asymétriques, concaténation sur les axes batch et largeur) et en comparant systématiquement leur sortie au modèle de référence. 

À ce stade, l'ensemble du graphe exporté doit être quantifié en int8 de bout en bout pour être éligible à l'export. 

Cette première réalisation débouchera, à n’en point douter, sur l’intégration de nombreux autres opérateurs. Des intégrations qui permettront à la plateforme Aidge de supporter toujours plus d’architectures ouvrant un champ des possibles considérable pour les acteurs industriels souhaitant déployer des systèmes d’IA embarqués.