Paddle

PaddlePaddle inference models are a model.pdmodel graph beside a model.pdiparams weight file. LibreYOLO exports a static opset-15 ONNX graph, converts it with X2Paddle, and packages the result with a metadata.yaml so it loads through the same factory as every other runtime.

Flag
export(format="paddle")
Writes
A directory with model.pdmodel, model.pdiparams and metadata.yaml
Extra
pip install "libreyolo[paddle]"
Loads back
LibreYOLO("weights/LibreYOLO9t_paddle", device="cpu")
Shapes
Static, batch 1, opset 15. All three are enforced.
Precision
FP32 only, CPU only.
Toolchain
PaddlePaddle 2.6.2, X2Paddle 1.6.0, ONNX 1.17 or earlier, checked exactly

Install

Install
# Python 3.10 to 3.12. WSL2 with Ubuntu 22.04 is the validated Windows path.pip install "libreyolo[paddle]"
Confirm the pinned versions
python -c "from importlib.metadata import version; print(version('paddlepaddle'), version('x2paddle'), version('onnx'))"

The extra pins the exact stack the parity work measured: PaddlePaddle 2.6.2, X2Paddle 1.6.0 and ONNX 1.17 or earlier. Those pins are checked at export time, not just at install time, and a different version raises an ImportError naming the expected one. Newer Paddle releases reject parts of the static code X2Paddle 1.6.0 generates, so failing early is better than producing an artifact nobody has validated.

Export

Python
from libreyolo import LibreYOLO model = LibreYOLO("LibreYOLO9t.pt") # Writes the directory weights/LibreYOLO9t_paddlepath = model.export(format="paddle")print(path)
CLI
libreyolo export --model LibreYOLO9t.pt --format paddle
Arguments
model.export(    format="paddle",    imgsz=640,        # int; this family's square canvas    batch=1,          # any other value raises ValueError    dynamic=False,    # True raises ValueError    simplify=True,    # False raises ValueError    opset=15,         # any other value raises ValueError    output_path=None, # None writes weights/<stem>_paddle)

Four arguments are fixed rather than defaulted. dynamic must be False, batch must be 1, simplify must be True for a fully static conversion graph, and opset must be 15, which is the ceiling X2Paddle 1.6.0 accepts. Passing anything else raises before tracing.

One normalization runs on the intermediate graph. ONNX defines an omitted MaxPool dilation as one, PyTorch writes the explicit all-ones attribute, and X2Paddle 1.6.0 rejects it, so the exporter removes that redundant default and leaves the specified operation unchanged.

The artifact is a directory: model.pdmodel, model.pdiparams and metadata.yaml. The Python that X2Paddle generates during conversion is not part of it.

Run the artifact

Through LibreYOLO
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("weights/LibreYOLO9t_paddle", device="cpu")result = model.predict(SAMPLE_IMAGE)print(result.boxes.xyxy[:3])
CLI
libreyolo predict --model weights/LibreYOLO9t_paddle \  --source https://raw.githubusercontent.com/LibreYOLO/libreyolo/release/libreyolo/assets/parkour.jpg --device cpu --save
Bare Paddle
import numpy as npimport paddle.inference as paddle_inferimport yaml directory = "weights/LibreYOLO9t_paddle"config = paddle_infer.Config(    f"{directory}/model.pdmodel", f"{directory}/model.pdiparams")config.disable_gpu()config.disable_mkldnn()config.switch_ir_optim(False) predictor = paddle_infer.create_predictor(config)handle = predictor.get_input_handle(predictor.get_input_names()[0])handle.reshape([1, 3, 640, 640])handle.copy_from_cpu(np.zeros((1, 3, 640, 640), dtype=np.float32))predictor.run()for name in predictor.get_output_names():    print(name, predictor.get_output_handle(name).copy_to_cpu().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 both model.pdmodel and model.pdiparams, reads metadata.yaml, and returns the same Results object as the checkpoint. A device other than auto or cpu raises: this backend is CPU only.

The bare-runtime snippet mirrors what the backend configures, and the three disabled options are deliberate. The Paddle 2.6 CPU fusion pipeline can crash while optimizing the large gather and scatter graphs emitted for deformable attention, so the portable unfused static graph is the one parity was measured against. Preprocessing, decoding, NMS and coordinate rescaling become yours on that path.

Constraints

No dynamic shapes, no FP16, no INT8, no embedded NMS, no GPU runtime.

Validated combinations are YOLO9 detection, YOLO9-E2E and YOLO9-P2 detection, EC detection, pose and segmentation, RT-DETRv4, D-FINE, DEIM and DEIMv2 detection, and YOLO-NAS detection and pose. Each is covered by conversion, a CPU runtime reload, raw-output parity and matched public results.

Blocked, with the reason recorded per combination:

CombinationWhy
RF-DETR, all tasksNeeds ONNX opset 17 and GridSample; X2Paddle 1.6.0 accepts opset 15 or lower and has no GridSample mapper
RT-DETR and RT-DETRv2 detectionThe trained graphs need GridSample at opset 16 or newer
D-FINE segmentationConverts and reloads, but mask-logit relative RMS error is 3.52% and minimum matched-mask IoU is 0.582
YOLO9 segmentationYOLO9 is detection only in LibreYOLO
RTMDet-Ins segmentationThe dynamic-kernel mask decode has no exported-runtime contract

Anything not listed as validated or blocked is refused with the note that it has not been validated through the ONNX-to-Paddle conversion path.

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/paddle.py, libreyolo/export/exporter.py, libreyolo/export/support.py, libreyolo/backends/paddle.py, docs/paddle.md and pyproject.toml on the dev branch.