ncnn

ncnn is Tencent's CPU inference library for mobile targets. LibreYOLO converts through PNNX, writing a model.ncnn.param graph beside a model.ncnn.bin weight file and a metadata.yaml that carries the family, task and class names.

Flag
export(format="ncnn")
Writes
A directory with model.ncnn.param, model.ncnn.bin and metadata.yaml
Extra
pip install "libreyolo[ncnn]"
Loads back
LibreYOLO("weights/LibreYOLO9t_ncnn")
Shapes
Fixed. The metadata records dynamic=False regardless of the flag.
Precision
FP32 only. half=True and int8=True are rejected.

Install

Install
# pnnx converts, ncnn runs the result.pip install "libreyolo[ncnn]"

The extra pulls both halves of the toolchain: pnnx performs the conversion and ncnn executes the result. Neither goes through ONNX on the primary path.

Export

Python
from libreyolo import LibreYOLO model = LibreYOLO("LibreYOLO9t.pt") # Writes the directory weights/LibreYOLO9t_ncnnpath = model.export(format="ncnn", imgsz=640)print(path)
CLI
libreyolo export --model LibreYOLO9t.pt --format ncnn --imgsz 640
Arguments
model.export(    format="ncnn",    imgsz=640,        # int, or (height, width)    batch=1,    simplify=True,    # applies to the ONNX fallback path only    opset=None,       # auto; applies to the ONNX fallback path only    output_path=None, # None writes weights/<stem>_ncnn) # half=True and int8=True are rejected during validation.

The artifact is a directory. weights/LibreYOLO9t_ncnn holds model.ncnn.param, model.ncnn.bin and metadata.yaml; all three are one artifact and move together.

Conversion tries PNNX directly from PyTorch first. If that fails it exports a static ONNX graph to a temporary directory and calls the pnnx command line tool on it, and the export only raises when both paths fail, reporting both errors. opset and simplify therefore only affect the fallback.

YOLOX needs one rewrite to convert at all. Its Focus layer uses strided slicing, which PNNX cannot lower, so the export swaps it for pixel_unshuffle and permutes the following convolution's input channels to compensate for the different channel ordering. The output is numerically identical, and the original weights are restored after the export.

Run the artifact

Through LibreYOLO
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("weights/LibreYOLO9t_ncnn")result = model.predict(SAMPLE_IMAGE)print(result.boxes.xyxy[:3])
Bare ncnn
import ncnnimport numpy as npimport yaml directory = "weights/LibreYOLO9t_ncnn"net = ncnn.Net()net.load_param(f"{directory}/model.ncnn.param")net.load_model(f"{directory}/model.ncnn.bin") # ncnn takes a single CHW image, not a batch.mat_in = ncnn.Mat(np.zeros((3, 640, 640), dtype=np.float32))extractor = net.create_extractor()extractor.input("in0", mat_in)ret, mat_out = extractor.extract("out0")print(ret, np.array(mat_out).shape) meta = yaml.safe_load(open(f"{directory}/metadata.yaml"))print(meta["model_family"], meta["task"], meta["names"]) # Preprocessing and postprocessing are yours on this path.

LibreYOLO() recognizes any directory holding model.ncnn.param and model.ncnn.bin, reads metadata.yaml, and returns the same Results object as the checkpoint.

The second snippet is the bare-runtime path, and two details differ from every other format here. ncnn works on a single CHW image rather than a batch, so there is no leading batch axis. Blob names come from the .param file; PNNX writes in0 and out0 by convention, and the backend parses the file rather than assuming them. Preprocessing, decoding, NMS and coordinate rescaling are yours on that path.

Constraints

FP32 on a fixed canvas. half=True and int8=True are both rejected during validation, and the exported metadata records dynamic=False whatever the flag said, so no backend assumes an axis the graph does not have.

Every DETR-style family is refused in preflight: detr, deformable_detr, dinodetr, dfine, lwdetr, deim, deimv2, rtdetr, rtdetrv2, rtdetrv4, rfdetr and ec. The message is the same for all of them, that the model needs decoder or sampling operations unavailable in ncnn, and it points at ONNX, OpenVINO, TorchScript or TensorRT instead.

What does convert is broad on the convolutional side: YOLO9 and YOLO9-E2E, YOLOX, PicoDet, YOLO-NAS detection and pose, the older YOLO1, YOLO3, YOLO4 and YOLO7 detectors, the four CNN classification families, PIDNet semantic segmentation, FOMO point detection at a fixed 96 by 96, ZipDepth, NAFNet and Real-ESRGAN.

Blocked entries name the concrete failure. Transformer graphs generally leave unsupported pnnx.Expression nodes behind, which produces a network with no runnable input blob, and that is what stops DINOv2, CLIP, SigLIP2 and SegFormer. BiRefNet needs torchvision deformable convolution, which PNNX cannot lower. YOLO2's converted graph terminates the ncnn runtime on Windows with a native integer divide by zero during output extraction.

For the full family and task grid, see the export matrix. For one combination:

Check one family and task before exporting
libreyolo formats --family yolo9 --task detect

Read from libreyolo/export/ncnn.py, libreyolo/export/exporter.py, libreyolo/export/support.py, libreyolo/backends/ncnn.py and pyproject.toml on the dev branch.