Core ML

Core ML is Apple's on-device model format. LibreYOLO traces the detector behind a per-family preprocessing wrapper so the converted graph always takes a canonical RGB image input, then writes an ML Program .mlpackage with the model metadata attached.

Flag
export(format="coreml")
Writes
One .mlpackage bundle (a directory) in ML Program format
Extra
pip install "libreyolo[coreml]"
Loads back
LibreYOLO("weights/LibreYOLO9t.mlpackage") on macOS
Shapes
Fixed. The input is a hard-shaped ct.ImageType.
Precision
FP32, FP16 (half=True). No INT8.
Families
Detection only, for yolox, yolo9, rtdetr and rfdetr

Install

Install
pip install "libreyolo[coreml]"

Prediction needs macOS. LibreYOLO() refuses a .mlpackage on any other platform with a message naming the current one, and the support matrix records these combinations as available on the grounds that runtime parity needs a macOS runner.

Export

Python
from libreyolo import LibreYOLO model = LibreYOLO("LibreYOLO9t.pt") # Writes the bundle weights/LibreYOLO9t.mlpackagepath = model.export(format="coreml")print(path)
CLI
libreyolo export --model LibreYOLO9t.pt --format coreml
Arguments
model.export(    format="coreml",    imgsz=640,    batch=1,    half=False,           # True converts with FLOAT16 compute precision    compute_units="all",  # all | cpu_and_gpu | cpu_and_ne | cpu_only    output_path=None,     # None writes weights/<stem>.mlpackage) # dynamic is accepted but the input is a fixed-shape ct.ImageType,# and the embedded metadata records dynamic=False either way.

The bundle is written to weights/ under the checkpoint's stem, with _fp16 appended when half=True. A .mlpackage is a directory, so copy the whole tree.

Every family is traced behind a preprocessing wrapper, so the converted graph takes one canonical input: RGB, scale=1/255, no bias, declared as ct.ImageType. The wrapper absorbs the family's own convention, which is BGR in the range 0 to 255 for YOLOX, ImageNet mean and standard deviation for RF-DETR, and identity for YOLO9 and RT-DETR. That is why a Core ML consumer feeds an ordinary image rather than a family-specific tensor.

Conversion targets ML Program with a minimum deployment target of iOS 15. compute_units is stored on the converted model and can be overridden again when the artifact is loaded.

Model metadata goes into user_defined_metadata as strings, which is where the backend reads the family, task, class names, input size and pose schema.

Embedded NMS

Embed Apple's NMS layer
from libreyolo import LibreYOLO # YOLOX and YOLO9 detection only, batch 1.LibreYOLO("LibreYOLO9t.pt").export(    format="coreml",    nms=True,    conf=0.25,    iou=0.45,)
CLI
libreyolo export --model LibreYOLO9t.pt --format coreml --nms \  --conf 0.25 --iou 0.45

nms=True wraps the model in a Core ML pipeline that ends in Apple's NonMaximumSuppression layer. The result has two outputs: confidence, shaped N by the class count, and coordinates, shaped N by 4 as normalized xywh.

It applies to YOLOX and YOLO9 detection only, and it requires batch 1. The DETR-style families are refused by name, because set prediction takes a top-k over queries and classes with no IoU step and cannot use that layer. max_det is not exposed here either; when the detection cap matters, use ONNX embedded NMS instead.

Run the artifact

Through LibreYOLO, on macOS
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO(    "weights/LibreYOLO9t.mlpackage",    compute_units="all",   # or cpu_and_ne to pin the Neural Engine)result = model.predict(SAMPLE_IMAGE)print(result.boxes.xyxy[:3])
Bare coremltools
import coremltools as ctfrom PIL import Image mlmodel = ct.models.MLModel("weights/LibreYOLO9t.mlpackage")print(mlmodel.user_defined_metadata["model_family"])print(mlmodel.user_defined_metadata["names"]) # The input is an image named "image" at the fixed export size.image = Image.open(SAMPLE_IMAGE).convert("RGB").resize((640, 640))out = mlmodel.predict({"image": image})print({name: value.shape for name, value in out.items()}) # Letterboxing and postprocessing are yours on this path.

LibreYOLO() recognizes a directory with the .mlpackage suffix and returns the same Results object as the checkpoint. compute_units is the one argument the factory passes through for this format, and it accepts all, cpu_and_gpu, cpu_and_ne and cpu_only. The device argument is ignored, because Core ML routes through compute units instead.

The second snippet is the bare-runtime path. Letterboxing, decoding, NMS and coordinate rescaling become yours there, and the class names live in user_defined_metadata.

Constraints

Four families, detection only: yolox, yolo9, rtdetr and rfdetr. Anything else is refused in preflight, because the family-aware preprocessing wrapper is what makes the fixed image input contract correct, and a family outside it would convert with the wrong normalization. The error names ONNX and TorchScript as the alternatives.

The input shape is hard-fixed by ct.ImageType, so dynamic=True changes nothing and the metadata records dynamic=False. Export a second bundle for a second resolution.

half=True converts with FP16 compute precision. There is no INT8 path from this exporter.

For the full family and task grid, see the export matrix. For Apple's newer on-device format, see Core AI. For one combination:

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

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