14.2.2.2. 建置 ROMFS 映像

ROMFS 映像 是一個常駐於 flash、唯讀的檔案系統,執行階段會自動將其掛載在 /rom。它解決了上一頁結尾所提及的資產問題:機器學習模型檔、標籤表、JSON 設定、影像範本——任何應用程式只會開啟並讀取、卻從不寫入的東西——都能搭上建置的便車,而無需付出被當成 Python 字面值嵌入的代價。

有三點使 ROMFS 成為處理出貨資產的恰當工具:

  • 這個檔案系統是 韌體映像的一部分。終端使用者無法從 /rom 刪除某個檔案、編輯它,或用自己的檔案替換它。

  • /rom 中的檔案可 原地 存取。像 ml 模組這類的取用方在載入模型檔時,會取得一個直接指向 flash 的檢視,完全不需要 RAM 副本——位於 /rom 上一個數百萬位元組的模型,「載入」起來基本上不花成本,而同一個檔案若位於 /sdcard,則會在載入時被讀入 RAM,並在該參照的整個生命週期中一直留存於其中。一般的 open() + read 則是按需複製:每一次 read(n) 呼叫都會在該呼叫的當下從 flash 複製 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 編輯加一次重新燒錄。除非凍結那一側真的有東西非改不可,否則韌體建置在產品的整個生命週期中都維持不動。