14.2.2.2. Construire une image ROMFS

Une image ROMFS est un système de fichiers en lecture seule résidant en mémoire flash que l’environnement d’exécution monte automatiquement sur /rom. Elle résout le problème des ressources sur lequel se concluait la page précédente : fichiers de modèles d’apprentissage automatique, tables d’étiquettes, configuration JSON, modèles d’image – tout ce que l’application ouvre et lit mais n’écrit jamais – embarque dans la compilation sans payer le coût d’être intégré en tant que littéraux Python.

Trois éléments font de ROMFS le bon outil pour les ressources livrées :

  • Le système de fichiers fait partie de l’image du micrologiciel. Les utilisateurs finaux ne peuvent pas supprimer un fichier de /rom, en modifier un, ni en remplacer un par le leur.

  • Les fichiers dans /rom sont accessibles sur place. Des consommateurs comme le module ml chargeant un fichier de modèle obtiennent une vue directe en mémoire flash sans copie en RAM – un modèle de plusieurs mégaoctets sur /rom se « charge » essentiellement gratuitement, là où le même fichier sur /sdcard est lu en RAM au moment du chargement et y reste pendant toute la durée de vie de la référence. Un open() + read ordinaire copie à la demande : chaque appel read(n) copie n octets de la mémoire flash vers la RAM au moment de l’appel, un read() nu demandant la totalité du fichier.

  • /rom et /rom/lib sont ajoutés à sys.path au démarrage. Les paquets Python déposés dans l’image sont importables par leur nom ; rien de particulier sur le site d’appel.

14.2.2.2.1. Construire une image

Les images ROMFS sont créées, éditées et flashées via l’IDE. Utilisez-le comme source de vérité pour le contenu de chaque partition ROMFS livrée.

La raison pour laquelle cela compte : les fichiers de modèles sont assortis d’exigences d’alignement que le chargeur impose à l’exécution. Les fichiers .tflite doivent être complétés jusqu’à des frontières de 16 octets, et le NPU de la N6 exige un alignement de 32 octets pour les modèles compilés. L’IDE applique automatiquement ce remplissage lorsqu’il écrit l’image. Les outils qui parcourent l’arborescence source sans appliquer le remplissage – mpremote romfs en particulier – produisent une image qui se monte proprement mais dont les modèles échouent au premier appel d’inférence.

L’éditeur ROMFS de l’IDE est une vue interactive du contenu de l’image. Les fichiers et dossiers peuvent être ajoutés, renommés et supprimés en mémoire ; l’enregistrement écrit le résultat sous forme de fichier .img prêt à flasher. Une structure typique pour une application qui livre un modèle aux côtés de quelques ressources et d’un paquet Python ressemble à ceci

model.tflite
labels.txt
config.json
templates/
    calibration.jpg
lib/
    mylib/
        __init__.py
        helpers.py

Astuce

L’IDE comme mpremote compilent de manière croisée les fichiers .py en bytecode .mpy lors de leur entrée dans une image ROMFS, de sorte que la caméra les importe sans payer le coût d’analyse au chargement. Les fichiers source dans l’éditeur restent en .py ; l’image contient des .mpy.

Une fois l’image flashée, l’arborescence est visible depuis MicroPython sur /rom/

>>> import os
>>> os.listdir('/rom')
['model.tflite', 'labels.txt', 'config.json', 'templates', 'lib']
>>> import mylib
>>> mylib.helpers
<module 'mylib.helpers' from '/rom/lib/mylib/helpers.mpy'>

14.2.2.2.2. L’essentiel de l’application réside dans ROMFS

ROMFS est le bon foyer pour presque tout ce qu’une application livre : les bibliothèques qu’elle importe, les fichiers de modèles qu’elle charge, la configuration qu’elle lit, toute ressource dont la sortie provient d’un outil de compilation produisant une arborescence de fichiers (convertisseurs de modèles, chaînes de traitement d’image, empaqueteurs de ressources) et – élément important – le code de l’application lui-même.

Le côté modules gelés devrait rester petit : boot.py pour la configuration avant REPL, main.py comme point d’entrée léger, et seulement les bibliothèques sans lesquelles la caméra ne peut véritablement pas démarrer. Tout le reste va dans ROMFS, où itérer dessus revient à un nouveau .img enregistré depuis l’IDE et reflashé – aucune recompilation du micrologiciel requise, aucune chaîne d’outils nécessaire pour le faire.

Le modèle qui en découle est un main.py qui ne fait rien d’autre que déléguer à l’application résidant dans ROMFS

# main.py (frozen)
import app
app.run()

# /rom/app/__init__.py (in ROMFS)
def run():
    ...

Une modification de app est une édition de ROMFS et un reflashage. La compilation du micrologiciel reste inchangée pendant toute la durée de vie du produit, à moins que quelque chose du côté gelé doive réellement changer.