MNN

MNNはAlibabaの軽量な推論エンジンです。LibreYOLOは静的なONNXグラフをエクスポートし、MNNパッケージに同梱されるmnnconvertツールで変換したうえで、入力名と出力名、固定された入力形状、クラス名を記録したJSONのサイドカーを書き出します。

フラグ
export(format="mnn")
出力
.mnnファイル1つと、メタデータのサイドカーである.mnn.jsonが1つ
追加インストール
pip install "libreyolo[mnn]"
再読み込み
LibreYOLO("weights/LibreYOLO9t.mnn")
形状
固定のNCHWです。dynamic=Trueは拒否されます。
数値精度
FP32のみ、CPUのみです。
タスク
このバージョンでは検出のみ

インストール

インストール
# MNNはONNXの中間表現から変換するため、この追加インストールにはlibreyolo[onnx]が含まれるpip install "libreyolo[mnn]"
コンバーターがパス上にあるか確認
mnnconvert --version

変換はONNXの中間表現を経由して行われるため、この追加インストールにはlibreyolo[onnx]が 含まれます。あわせてmnnconvertの実行ファイルも入り、エクスポーターはこれをまず実行中の Pythonインタープリターの隣で、次にPATH上で探します。コンバーターが見つからない場合は、 変換の途中で失敗するのではなく、インストールコマンドを示すImportErrorが送出されます。

エクスポート

Python
from libreyolo import LibreYOLO model = LibreYOLO("LibreYOLO9t.pt") # weights/LibreYOLO9t.mnnとweights/LibreYOLO9t.mnn.jsonを出力path = model.export(format="mnn", imgsz=640)print(path)
CLI
libreyolo export --model LibreYOLO9t.pt --format mnn --imgsz 640
引数
model.export(    format="mnn",    imgsz=640,        # int、または (height, width)    batch=1,          # 成果物に焼き込まれる    simplify=True,    # ONNXの中間表現に対してonnxsimを適用    output_path=None, # Noneならweights/<stem>.mnnに出力    verbose=False,    # Trueにするとmnnconvertのログを逐次出力) # dynamic=TrueはValueErrorを送出。half=Trueとint8=Trueは拒否される

グラフを渡す前に、エクスポーターはONNXの入力契約を読み取り、表現できないものを拒否します: 画像入力が複数ある場合と、入力形状にシンボリックな次元がある場合です。このバージョンのMNNは 完全に固定されたNCHW形状を必要とし、batchは読み込み時に調整されるのではなく成果物に 焼き込まれます。

サイドカーは省略できる付随情報ではありません。weights/LibreYOLO9t.mnn.jsonには入力名と 出力名、固定された入力形状、バッチ、クラス名、使用したMNNのバージョン、成果物を構築した 対象のバックエンドが記録され、ランタイムは読み込み時にそれらのフィールドをすべて検証します。

Windowsでは、MNN 3.6.1が変換を終えたあとにプロセスの終了処理でアクセス違反やfail-fastの ステータスとともに異常終了することがあります。エクスポーターはそれらの特定の終了コードを 認識し、出力ファイルが存在する場合は変換が成功したものとして扱います。

成果物を実行する

LibreYOLOから実行
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("weights/LibreYOLO9t.mnn")result = model.predict(SAMPLE_IMAGE)print(result.boxes.xyxy[:3])
MNN単体
import json import MNNimport numpy as np meta = json.load(open("weights/LibreYOLO9t.mnn.json"))print(meta["mnn_input_names"], meta["mnn_output_names"], meta["mnn_input_shape"]) runtime = MNN.nn.create_runtime_manager(    ({"backend": 0, "precision": 1, "numThread": 4},))module = MNN.nn.load_module_from_file(    "weights/LibreYOLO9t.mnn",    meta["mnn_input_names"],    meta["mnn_output_names"],    runtime_manager=runtime,    dynamic=False,    shape_mutable=False,) blob = np.zeros(meta["mnn_input_shape"], dtype=np.float32)input_var = MNN.expr.const(    blob, list(blob.shape), MNN.expr.NCHW, MNN.expr.float)outputs = module.forward([input_var])for out in outputs:    print(np.array(MNN.expr.convert(out, MNN.expr.NCHW).read()).shape) # この経路では前処理と後処理は自前で行う

LibreYOLO().mnnという拡張子で振り分け、チェックポイントと同じResultsオブジェクトを 返します。読み込みは設計上厳密です:サイドカーはformat=mnnmnn_backend=cpudynamic=falseprecision=fp32、サイズ、検出タスク、記録された画像サイズと一致する固定で 正の値のNCHW形状、そして0からnc - 1までのすべてのインデックスを網羅するクラス名を宣言して いる必要があります。食い違いがあれば、推測せずに例外を送出します。

成果物を構築したときと異なるimgszで推論した場合も例外になり、ここではMNNのエクスポートは CPUで動作するためdeviceは警告とともに無視されます。

2つ目のスニペットはランタイムを直接使う経路です。この経路では前処理、デコード、NMS、座標の スケール戻しは自前の作業になり、MNNのモジュールローダーが明示的に要求するため、入力名と 出力名はサイドカーから取得します。

制約

検出のみです。バックエンドは読み込み時にそれ以外のタスクを拒否し、エクスポート側もそれに 合わせて、記録された組み合わせ以外では事前チェックが「MNN v1 has no implemented runtime contract for this family and task.」という例外を送出します。

FP32、CPU、固定形状です。dynamic=TrueValueErrorを送出し、half=Trueint8=Trueは 検証中に拒否されます。

検証済みの検出ファミリーはYOLO9、YOLO9-E2E、YOLO9-P2、RF-DETR、EC、RT-DETR、RT-DETRv2、 RT-DETRv4、D-FINE、DEIM、YOLO-NASで、いずれも変換、成果物の新規読み込み、MNNのCPUでの実行、 メタデータの確認、PyTorchモデルに対するNMS後の検出結果の一致まで確認しています。DEIMv2は 変換、再読み込み、実行ができ、NMS後の検出結果も保たれますが、中間のONNX経路でクエリ単位の スコアの一致が不完全なため、検証済みではなく利用可能として記録されています。

ファミリーとタスクの全体表はエクスポート対応表を 参照してください。1つの組み合わせを確認するには:

エクスポート前にファミリーとタスクを確認
libreyolo formats --family yolo9 --task detect

devブランチのlibreyolo/export/mnn.py、libreyolo/export/exporter.py、libreyolo/export/support.py、libreyolo/backends/mnn.py、pyproject.tomlを確認しました。