MNN
MNN is Alibaba's lightweight inference engine. LibreYOLO exports a static ONNX graph, converts it with the mnnconvert tool shipped by the MNN package, and writes a JSON sidecar recording the input and output names, the fixed input shape and the class names.
- Flag
export(format="mnn")- Writes
- One .mnn file plus a .mnn.json metadata sidecar
- Extra
pip install "libreyolo[mnn]"- Loads back
LibreYOLO("weights/LibreYOLO9t.mnn")- Shapes
- Fixed NCHW. dynamic=True is rejected.
- Precision
- FP32 only, CPU only.
- Tasks
- Detection only in this version
Install
# The extra includes libreyolo[onnx]: MNN converts from an ONNX intermediate.pip install "libreyolo[mnn]"mnnconvert --versionThe extra includes libreyolo[onnx], because the conversion runs over an ONNX
intermediate. It also brings the mnnconvert executable, which the exporter looks
for next to the active Python interpreter first and on PATH second. A missing
converter raises an ImportError naming the install command rather than failing
mid-conversion.
Export
from libreyolo import LibreYOLO model = LibreYOLO("LibreYOLO9t.pt") # Writes weights/LibreYOLO9t.mnn and weights/LibreYOLO9t.mnn.jsonpath = model.export(format="mnn", imgsz=640)print(path)libreyolo export --model LibreYOLO9t.pt --format mnn --imgsz 640model.export( format="mnn", imgsz=640, # int, or (height, width) batch=1, # baked into the artifact simplify=True, # onnxsim over the ONNX intermediate output_path=None, # None writes weights/<stem>.mnn verbose=False, # True streams the mnnconvert log) # dynamic=True raises ValueError. half=True and int8=True are rejected.Before handing the graph over, the exporter reads the ONNX input contract and
refuses anything it cannot express: more than one image input, or an input shape
with a symbolic dimension. MNN in this version requires a fully fixed NCHW shape,
and batch is baked into the artifact rather than negotiated at load time.
The sidecar is not optional bookkeeping. weights/LibreYOLO9t.mnn.json records
the input and output names, the fixed input shape, the batch, the class names, the
MNN version used, and the backend the artifact was built for, and the runtime
validates every one of those fields on load.
On Windows, MNN 3.6.1 sometimes completes the conversion and then terminates during process teardown with an access violation or a fail-fast status. The exporter recognizes those specific exit codes and treats the conversion as successful when the output file is present.
Run the artifact
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("weights/LibreYOLO9t.mnn")result = model.predict(SAMPLE_IMAGE)print(result.boxes.xyxy[:3])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) # Preprocessing and postprocessing are yours on this path.LibreYOLO() dispatches on the .mnn suffix and returns the same Results object
as the checkpoint. The load is strict by design: the sidecar has to declare
format=mnn, mnn_backend=cpu, dynamic=false, precision=fp32, a size, a
detection task, a fixed positive NCHW shape that agrees with the recorded image
size, and class names covering every index from 0 to nc - 1. Any mismatch raises
rather than guessing.
Prediction at a different imgsz than the artifact was built for raises too, and
device is ignored with a warning, because MNN exports run on CPU here.
The second snippet is the bare-runtime path. Preprocessing, decoding, NMS and coordinate rescaling become yours there, and the input and output names come from the sidecar because MNN's module loader wants them explicitly.
Constraints
Detection only. The backend refuses any other task on load, and the export side matches: outside the recorded combinations, preflight raises with "MNN v1 has no implemented runtime contract for this family and task."
FP32, CPU, fixed shape. dynamic=True raises ValueError, and half=True and
int8=True are rejected during validation.
Validated detection families are YOLO9, YOLO9-E2E, YOLO9-P2, RF-DETR, EC, RT-DETR, RT-DETRv2, RT-DETRv4, D-FINE, DEIM and YOLO-NAS, each covered by conversion, a fresh artifact reload, MNN CPU execution, metadata checks and matched post-NMS detection parity against the PyTorch model. DEIMv2 converts, reloads, executes and preserves post-NMS detections, but its intermediate ONNX route has incomplete query-level score parity, so it is recorded as available rather than validated.
For the full family and task grid, see the export matrix. For one combination:
libreyolo formats --family yolo9 --task detect