Intégrer une IA embarquée sur Android avec TensorFlow Lite sans dégrader l'autonomie ni la latence utilisateur est un défi concret que j'ai rencontré à plusieurs reprises. On imagine souvent que "mettre le modèle sur le téléphone" règle tout — en réalité, c'est juste le début. Ici, je partage une approche pragmatique, basée sur des expérimentations et des compromis concrets, pour obtenir des inférences rapides, fiables et économes en énergie.

Commencer par la bonne question : quel est vraiment l'objectif ?

Avant de toucher au modèle, je prends le temps de redéfinir le besoin fonctionnel. Est-ce que l'IA doit répondre en temps réel (ex. détection d'objet pour AR), ou des traitements asynchrones en arrière-plan suffisent (ex. classement de photos) ? Souvent, on peut ajuster les attentes : un feedback à 200–300 ms peut être acceptable là où 30 ms est critique.

Si l'usage peut tolérer de la latence ou un traitement différé, optez pour des stratégies qui économisent la batterie (traitement par lots, réveils planifiés). Si la latence est critique, concentrez-vous sur l'optimisation du modèle et l'exploitation correcte des accélérateurs.

Choisir et préparer le modèle

Le point de départ déterminera tout : architecture, taille, précision. Je privilégie toujours un modèle adapté à l'edge plutôt qu'un port direct d'un modèle serveur lourd.

  • Privilégier les architectures légères : MobileNetV3, EfficientNet-lite, TinyML architectures ou des réseaux sur mesure. Ils offrent un bon compromis entre précision et coût d'inférence.
  • Quantification : passer à int8 (ou 16-bit) réduit dramatiquement la taille du modèle et la consommation mémoire, et accélère l'exécution sur la plupart des moteurs. Je teste post-training quantization et quantization-aware training pour minimiser la perte de précision.
  • Pruning et distillation : j'utilise le pruning pour supprimer les poids inutiles et la distillation pour transférer la "sagesse" d'un grand modèle vers un petit.

Utiliser TensorFlow Lite intelligemment

TFLite est un excellent point d'entrée, mais son efficacité dépend de la configuration :

  • Choisir le delegate adéquat : CPU par défaut, mais pour beaucoup de tâches le GPU delegate (via OpenGL/ Vulkan) ou NNAPI (qui exploite NPU/ISP) est beaucoup plus efficace. Je compare systématiquement CPU vs GPU vs NNAPI sur les appareils cibles.
  • Préférer les opérateurs optimisés : évitez les opérations exotique non supportées par les delegates ; elles forcent des fallback lents sur CPU.
  • Utiliser les delegates hybrides avec prudence : combiner NNAPI et GPU peut offrir des gains, mais ajoute de la complexité. Tester sur une gamme d'appareils est crucial.

Profiling et métriques : mesurer avant d'optimiser

Je ne prends jamais d'actions basées sur des suppositions. Voici les métriques que je collecte :

  • Durée d'inférence (p50, p95, p99)
  • Consommation énergétique (mW) pendant inference — outils comme Batterystats, Trepn Profiler ou un moniteur hardware
  • Empreinte mémoire et GC impact
  • Température et fréquence du CPU/GPU (throttling)

TensorFlow Lite propose le benchmark_tool et des traces d'exécution. J'utilise aussi Android Profiler pour visualiser CPU, mémoire et réseau en simultané.

Optimisation du runtime et scheduling

La façon dont vous déclenchez l'inférence a un impact majeur sur batterie et latence :

  • Batching : si vous pouvez accumuler plusieurs inputs, le batching réduit overheads par échantillon et améliore l'efficacité.
  • Déduplication et throttling : ne relancez pas l'inférence à chaque frame si la scène est statique. J'implémente des gardes simples (ex. lancer toutes les 200 ms si rien n'a changé).
  • Edge caching : pour des tâches comme la classification d'images similaires, je memoïse les résultats récents. C'est une économie énorme.
  • Travailler avec JobScheduler / WorkManager pour les tâches non-urgentes afin d'aligner l'exécution sur les périodes d'activité CPU hautes ou d'IDLE device.

Threads, affinage du CPU et affinity

Configurer correctement les threads qui exécutent le modèle est critique. TFLite permet de définir le nombre de threads. Quelques règles que j'applique :

  • Pour les appareils multicœurs, définir threads = cores / 2 souvent donne un bon compromis pour éviter la contention et le throttling thermique.
  • Sur NNAPI, éviter d'overcommit le CPU si le delegate utilise des copros : laisser des cœurs libres pour le système évite le lag UI.
  • Mesurer le comportement pendant des sessions longues : trop de threads conduisent à plus de conso et à une montée en température.

Pré/post processing efficient

On sous-estime souvent le coût du preprocessing (rééchantillonnage, normalisation) et du postprocessing (NMS, décodage). J'optimise ces étapes :

  • Utiliser des APIs natives (RenderScript deprecated, mais Vulkan/Neon optimisé) ou des bibliothèques C++ via JNI pour opérations lourdes.
  • Minimiser allocations mémoire : réutiliser buffers, éviter la création d'objets dans la boucle d'inférence.
  • Déplacer autant que possible le prétraitement sur le GPU si le delegate le gère.

Expérience utilisateur et feedback en temps réel

La perception de latence est autant psychologique que technique. Quelques astuces UX que j'utilise :

  • Donner immédiatement un retour visuel léger (squelettes, placeholders) pendant l'inférence pour masquer 100–300 ms.
  • Synchronous vs asynchronous : privilégier l'async pour ne pas bloquer le fil UI. Coupler avec des animations fluides pour lisser la transition.
  • Informer l'utilisateur quand la fonctionnalité est mise en pause pour économiser la batterie (ex. "Mode économie activé : précision limitée").

Stratégies d'adaptation à l'appareil

Un seul binaire ne conviendra pas à tous les appareils. J'opte pour des optimisations dynamiques :

  • Détecter la présence de NPU/GPU avec NNAPI et choisir le delegate optimal à l'install/runtime.
  • Avoir plusieurs variantes du modèle (full, lite, ultra-lite) et sélectionner en fonction du profil matériel et de la batterie.
  • Permettre à l'utilisateur de choisir un mode (économie / équilibré / performance) si l'usage s'y prête.

Surveiller et dégrader gracieusement

Le système doit s'adapter : si le device chauffe ou si la batterie tombe, il faut réduire la qualité. J'implémente :

  • Un fallback vers un modèle plus petit ou réduction du framerate d'inférence.
  • Une logique de backoff exponentiel en cas d'échecs répétés.
  • Des logs et telemetry (consentement respecté) pour comprendre comportement sur le terrain.
Technique Gain typique Coût / Risque
Quantification int8 Réduction modèle 4x, latency ↓ Perte légère de précision; tests nécessaires
Utiliser NNAPI / NPU Latence et conso ↓ significatives Variabilité selon device; intégration plus complexe
Batching Efficacité par inference ↑ Latence individuelle↑ si pas toléré

Tests et déploiement

Je mets en place des pipelines de test sur plusieurs profils d'appareils (low-end, mid, flagship) et mes scénarios incluent :

  • Sessions longues (10–30 minutes) pour détecter thermal throttling et dérive de performance.
  • Tests en conditions réelles : luminosité, autres apps en arrière-plan, modes éco ativés.
  • Mesures de qualité (précision) et UX (temps perçu) pour valider les compromis.

Mettre une IA embarquée sur Android sans ruiner la batterie n'est pas magique : c'est un exercice de compromis et d'optimisation systématique. En combinant modèle adapté, quantification, selection intelligente de delegate, scheduling réfléchi et optimisations de preprocessing, on peut atteindre des résultats qui tiennent à la fois la promesse de la réactivité et celle de l'autonomie. Si vous voulez, je peux partager une checklist pratique ou un petit guide de benchmark que j'utilise pour évaluer rapidement un modèle sur un parc d'appareils.