14.2.2.2. 构建 ROMFS 镜像¶
ROMFS 镜像 是一个驻留在闪存中、只读的文件系统,运行时会将其自动挂载在 /rom。它解决了上一页结尾提出的资产问题:机器学习模型文件、标签表、JSON 配置、图像模板——任何应用只打开读取而从不写入的内容——随构建一同发布,而无需付出作为 Python 字面量嵌入的代价。
有三点使 ROMFS 成为发布资产的合适工具:
该文件系统是 固件镜像的一部分。终端用户无法从
/rom中删除某个文件、编辑某个文件,或用自己的文件替换某个文件。/rom中的文件可 就地 访问。像ml模块加载模型文件这样的消费方,会获得对闪存的直接视图而无需 RAM 副本——/rom上一个数兆字节的模型“加载”本质上是免费的,而/sdcard上的同一文件在加载时会被读入 RAM,并在该引用的整个生命周期内一直留在那里。普通的open()+read则按需复制:每次read(n)调用会在调用的那一刻把n个字节从闪存复制到 RAM,而不带参数的read()则请求整个文件。/rom和/rom/lib会在启动时添加到sys.path。放入镜像中的 Python 包可按名称导入;在调用处无需任何特殊处理。
14.2.2.2.1. 构建镜像¶
ROMFS 镜像通过 IDE 创建、编辑和烧录。请将其作为每个已发布 ROMFS 分区内容的唯一可信来源。
之所以这一点重要:模型文件带有运行时加载器会强制执行的对齐要求。.tflite 文件必须填充到 16 字节边界,而 N6 的 NPU 要求已编译模型按 32 字节对齐。IDE 在写入镜像时会自动应用该填充。那些遍历源码树却不应用填充的工具——尤其是 mpremote romfs——会生成一个能干净挂载、但其模型在首次推理调用时就失败的镜像。
IDE 的 ROMFS 编辑器是镜像内容的一个交互式视图。可以在内存中添加、重命名和删除文件与文件夹;保存会把结果写出为一个可供烧录的 .img 文件。对于一个随附了模型外加一些资产和一个 Python 包的应用,其典型结构如下:
model.tflite
labels.txt
config.json
templates/
calibration.jpg
lib/
mylib/
__init__.py
helpers.py
小技巧
IDE 和 mpremote 都会在 .py 文件进入 ROMFS 镜像的过程中将其交叉编译为 .mpy 字节码,这样摄像头导入它们时就无需在加载时付出解析代价。编辑器中的源文件仍保持为 .py;镜像中包含的是 .mpy。
镜像烧录完成后,该目录树可从 MicroPython 在 /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. 应用的大部分都驻留在 ROMFS 中¶
ROMFS 是应用所发布几乎一切内容的合适归宿:它所导入的库、它所加载的模型文件、它所读取的配置、其输出来自某个会发出文件树的构建工具(模型转换器、图像流水线、资产打包器)的任何资产,以及——重要的是——应用代码本身。
冻结模块那一侧应当保持精简:用于 REPL 之前初始化的 boot.py、作为轻量入口点的 main.py,以及仅有那些摄像头确实没有就无法启动的库。其余一切都放入 ROMFS,在那里对其进行迭代只需从 IDE 中保存出一个新的 .img 并重新烧录——无需重建固件,也无需手头有工具链来做这件事。
由此自然得出的模式是一个除了委派给驻留在 ROMFS 中的应用之外什么都不做的 main.py:
# main.py (frozen)
import app
app.run()
# /rom/app/__init__.py (in ROMFS)
def run():
...
对 app 的更改就是一次 ROMFS 编辑加一次重新烧录。除非冻结那一侧确实有东西必须更改,否则固件构建在产品的整个生命周期内都保持不变。