YOLOv9

A single-stage convolutional detector: one pass scores a dense grid of boxes and NMS drops the duplicates. LibreYOLO carries three variants of it, one of which has no NMS step.

Tasks
detection
Sizes
yolo9: t, s, m, c at 640 px; yolo9_p2: t, s at 640 px
Install
pip install libreyolo
Support tier
Flagship, since v1.0.0. Features are designed and fully GPU-validated here first.
Upstream
YOLOv9 by MultimediaTechLab, MIT. Paper, source
Licenses
Code MIT, weights MIT. Commercial use

Install

YOLOv9 needs no extra beyond the base package.

bash
pip install libreyolo

Predict

Weights download from Hugging Face on first use and are cached locally.

Python
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreYOLO9s.pt")result = model(SAMPLE_IMAGE, save=True) for box in result.boxes:    print(box.cls, box.conf, box.xyxy)
CLI
libreyolo predict model=LibreYOLO9s.pt save=True \  source=https://raw.githubusercontent.com/LibreYOLO/libreyolo/release/libreyolo/assets/parkour.jpg
Without NMS
from libreyolo import LibreYOLO, SAMPLE_IMAGE # Same call, different checkpoint. The end-to-end head returns its own# top-scoring predictions, so no NMS runs and iou is ignored.model = LibreYOLO("LibreYOLO9E2Es.pt")result = model(SAMPLE_IMAGE, conf=0.25, max_det=300) print(len(result.boxes))

The returned Results object is the one every family returns, so swapping in a different detector is a one line change. On the base and stride-4 models, conf sets the confidence threshold and iou the NMS threshold. The end-to-end model runs no NMS and ignores iou, so conf and max_det are what shape its output. See prediction for sources, streaming and result handling.

Variants

Three variants share a backbone. All three detect only, and they take the same arguments.

The base model predicts on three feature scales and clears duplicate boxes with NMS.

The end-to-end model keeps that head and adds a one-to-one matching branch beside it. Inference reads the one-to-one branch alone and takes its top-scoring predictions, so no NMS runs. Choose it when the runtime you deploy to has no NMS operator.

The stride-4 model surfaces one level further up the backbone, extends the neck down to it and predicts on four scales instead of three. The extra scale is for objects that cover few pixels; the one published checkpoint for it is trained on aerial imagery. Base detection checkpoints transfer into it: the backbone and neck load unchanged, the three pretrained head towers shift up one slot, and the stride-4 tower starts from random initialization.

CheckpointInput (px)mAP 50-95Params (M)
LibreYOLO9c64056.425.5
LibreYOLO9m64055.320.12
LibreYOLO9s64055.97.2
LibreYOLO9t64041.82.04

COCO val2017, 500 images. Measured by the LibreYOLO benchmark harness and published on Vision Analysis, where latency across hardware and runtimes is compared and the full run records live.

Train

Python
from libreyolo import LibreYOLO model = LibreYOLO("LibreYOLO9s.pt")model.train(data="my-dataset.yaml", epochs=100, imgsz=640, batch=16)
CLI
libreyolo train model=LibreYOLO9s.pt data=my-dataset.yaml \  epochs=100 imgsz=640 batch=16
Small objects
from libreyolo import LibreYOLO9P2 # The stride-4 variant has no COCO checkpoint of its own, so name a# base detection one: its backbone and neck load unchanged and the# stride-4 head tower starts from random initialization.model = LibreYOLO9P2(None, size="s")model.train(data="my-dataset.yaml", epochs=100, pretrained="LibreYOLO9s.pt")

pretrained decides what the run starts from. Pass True to load the published checkpoint for the same model and size, or a name or path for anything else. Tensors whose shape does not match are skipped rather than refused, and the run logs how many loaded, so a checkpoint trained on a different class count is still a usable starting point.

The stride-4 model has no published COCO checkpoint of its own, so True resolves there to a file that does not exist and the download fails. Name a base detection checkpoint instead.

See training for datasets, augmentation, multi-GPU and loggers.

The base model can train a PGI auxiliary branch next to its main head. The branch is training-only: prediction and export use the main head. A run attaches it when the starting weights carry PGI tensors or when it trains from scratch. The published base checkpoints carry none, so a fine-tune from them trains the main head only and logs that. aux_weight=0.25 attaches a new branch at that loss weight anyway, and aux_weight=0 keeps it off. A resumed run keeps its checkpoint's PGI setup. On the m and c sizes the branch is the upstream one: a second backbone on the input image, whose stages CBFuse sums with CBLinear taps of the main backbone, and its own head, 12.8M and 25.7M training-only parameters at 80 classes. The end-to-end and stride-4 models have no PGI branch.

All three variants clip the gradient L2 norm at 10 before each optimizer step; clip_max_norm=0 turns clipping off. Label assignment scores anchors in fp32, also under float16 autocast. max_labels=300 caps the boxes kept per training image, and boxes past it are dropped with a one-time warning; assignment memory follows each image's real boxes, not the cap. SGD momentum warms from 0.8 to 0.937 over three epochs. letterbox_pad=None inherits the checkpoint stamp: unmarked weights use topleft, while new official conversions record center.

YOLO9 and YOLOX mosaic prefer annotated partners with at most 20 draws; YOLO9 MixUp uses the same policy. MixUp is off by default (mixup_prob=0). When it is on, the blend ratio is drawn from Beta(1, 1), uniform on [0, 1], as in MultimediaTechLab/YOLO. See event histograms for non-RGB input profiles.

Validate

val() returns a dictionary of metrics/ keys covering precision, recall, mAP 50 and mAP 50-95, measured against any dataset in the format you trained on.

Python
from libreyolo import LibreYOLO model = LibreYOLO("LibreYOLO9s.pt")metrics = model.val(data="my-dataset.yaml") print(metrics["metrics/mAP50-95"])print(metrics["metrics/mAP50"])
CLI
libreyolo val model=LibreYOLO9s.pt data=my-dataset.yaml
Against COCO
# The bundled COCO yaml carries an embedded download script, so it# needs explicit permission unless the dataset is already local.libreyolo val model=LibreYOLO9c.pt data=coco.yaml imgsz=640 \  allow_download_scripts=True

Export

TaskONNXTorchScriptExecuTorchTensorRTOpenVINOPaddleMNNRKNNncnnTFLiteCoreMLCore AI
DetectionDetection to ONNX: supportedDetection to TorchScript: supportedDetection to ExecuTorch: supportedDetection to TensorRT: supportedDetection to OpenVINO: supportedDetection to Paddle: supportedDetection to MNN: supportedDetection to RKNN: not supportedDetection to ncnn: supportedDetection to TFLite: not supportedDetection to CoreML: not supportedDetection to Core AI: supported

A tick holds for all three variants: where they differ, the matrix carries the weakest of the three.

An exported artifact loads back through LibreYOLO() on its file suffix, so a .onnx or .engine file behaves like a checkpoint and returns the same Results. Running the graph in a bare runtime, with no LibreYOLO installed, is also supported, but then preprocessing and postprocessing are yours to write.

For the base detection model, the postprocessing half of that can move into the graph. nms=True on an ONNX export puts suppression inside the model, and the first output becomes a fixed (1, max_det, 6) tensor whose rows are x1, y1, x2, y2, score, class, zero-padded past the detection count. That graph is batch 1 and carries no dynamic axes. The end-to-end and stride-4 models do not accept the flag.

Each format installs a different extra and takes a few arguments of its own. Both are on that format's page.

Python
from libreyolo import LibreYOLO model = LibreYOLO("LibreYOLO9s.pt")model.export(format="onnx", imgsz=640)
CLI
libreyolo export model=LibreYOLO9s.pt format=onnx imgsz=640
With NMS in the graph
libreyolo export model=LibreYOLO9s.pt format=onnx nms=True \  conf=0.25 iou=0.45 max_det=300
Use the exported file
from libreyolo import LibreYOLO, SAMPLE_IMAGE # The factory routes on the file suffix, so an exported artifact loads# like any checkpoint and returns the same Results object.model = LibreYOLO("LibreYOLO9s.onnx")result = model(SAMPLE_IMAGE) print(result.boxes.xyxy)

TFLite INT8 uses int8=True with calibration data.

ONNX inputs and outputs

LibreYOLO9

Tensorimages float32 [batch, 3, height, width]
ColorRGB
Scalingpixel / 255
Normalizationnone
Resizeletterbox, bilinear, pad value 114, pad position from the letterbox_pad metadata key

Scale by r = min(height / h, width / w), resize to (int(w * r), int(h * r)) and paste onto a canvas filled with 114.

Paste position comes from the letterbox_pad metadata key: topleft puts the image in the top-left corner, center splits the padding on both sides. A missing key means topleft.

Detection, default export
OutputShapeMeaning
output[batch, 4 + nc, anchors]Rows 0 to 3 are x1, y1, x2, y2 in input-canvas pixels. Rows 4 onward are per-class scores, sigmoid already applied. anchors is the cell count over strides 8, 16 and 32, 8400 at 640 x 640.

Postprocessing

  1. Transpose to [anchors, 4 + nc].
  2. Keep every (anchor, class) pair whose score is above the confidence threshold (0.25 by default). One anchor can yield several classes.
  3. Run NMS per class with IoU 0.45 and keep at most 300 detections.
  4. Map boxes back to the original image: subtract the pad offset, divide by r, clip to the image.
  5. Drop boxes whose width or height is 0 after clipping.
Detection, nms=True
OutputShapeMeaning
output[1, max_det, 6]Rows are x1, y1, x2, y2, score, class in input-canvas pixels, zero-padded past the detection count. Confidence, IoU and max_det are baked in and recorded as nms_conf, nms_iou and max_det metadata.
raw[1, 4 + nc, anchors]The same tensor as the default export's output, before suppression.

Postprocessing

  1. Drop rows whose score is 0 or below your threshold.
  2. Map boxes back to the original image: subtract the pad offset, divide by r, clip to the image.

LibreYOLO9E2E

Tensorimages float32 [batch, 3, height, width]
ColorRGB
Scalingpixel / 255
Normalizationnone
Resizeletterbox, bilinear, pad value 114, pad position from the letterbox_pad metadata key

Scale by r = min(height / h, width / w), resize to (int(w * r), int(h * r)) and paste onto a canvas filled with 114.

Paste position comes from the letterbox_pad metadata key: topleft puts the image in the top-left corner, center splits the padding on both sides. A missing key means topleft.

This model has no nms=True export. The exporter raises NotImplementedError, so only the default export below exists.

Detection, default export
OutputShapeMeaning
output[batch, 4 + nc, anchors]Predictions of the one-to-one head only. Rows 0 to 3 are x1, y1, x2, y2 in input-canvas pixels. Rows 4 onward are per-class scores, sigmoid already applied. anchors is the cell count over strides 8, 16 and 32, 8400 at 640 x 640.

Postprocessing

  1. Transpose to [anchors, 4 + nc].
  2. Flatten the [anchors, nc] scores into one list of anchors x nc (anchor, class) pairs. Pair i belongs to anchor i / nc (integer division) and class i % nc.
  3. Keep the 300 highest-scoring pairs (max_det), highest first. One anchor can yield several classes.
  4. Of those, keep the pairs whose score is above the confidence threshold (0.25 by default).
  5. Do not run NMS. The one-to-one head is trained to fire once per object, and LibreYOLO ignores the iou setting for this model.
  6. Map boxes back to the original image: subtract the pad offset, divide by r, clip to the image.
  7. Drop boxes whose width or height is 0 after clipping.

LibreYOLO9P2

Tensorimages float32 [batch, 3, height, width]
ColorRGB
Scalingpixel / 255
Normalizationnone
Resizeletterbox, bilinear, pad value 114, pad position from the letterbox_pad metadata key

Scale by r = min(height / h, width / w), resize to (int(w * r), int(h * r)) and paste onto a canvas filled with 114.

Paste position comes from the letterbox_pad metadata key: topleft puts the image in the top-left corner, center splits the padding on both sides. A missing key means topleft.

This model has no nms=True export. The exporter raises NotImplementedError, so only the default export below exists.

Detection, default export
OutputShapeMeaning
output[batch, 4 + nc, anchors]Rows 0 to 3 are x1, y1, x2, y2 in input-canvas pixels. Rows 4 onward are per-class scores, sigmoid already applied. anchors is the cell count over strides 4, 8, 16 and 32, 34000 at 640 x 640. nc is 10 for LibreYOLO9P2s-visdrone.

Postprocessing

  1. Transpose to [anchors, 4 + nc].
  2. Keep every (anchor, class) pair whose score is above the confidence threshold (0.25 by default). One anchor can yield several classes.
  3. Run NMS per class with IoU 0.45 and keep at most 300 detections.
  4. Map boxes back to the original image: subtract the pad offset, divide by r, clip to the image.
  5. Drop boxes whose width or height is 0 after clipping.

To check your own implementation, run the same file through LibreYOLO: LibreYOLO("model.onnx").predict("image.jpg") returns the reference results. Verified with LibreYOLO 1.7.0.

Checkpoints

Every published weight file for this family.

FileInput (px)Weights license
Detection
LibreYOLO9t.pt640mit
LibreYOLO9s.pt640mit
LibreYOLO9m.pt640mit
LibreYOLO9c.pt640mit
LibreYOLO9E2Et.pt640mit
LibreYOLO9E2Es.pt640mit
LibreYOLO9E2Em.pt640mit
LibreYOLO9E2Ec.pt640mit
LibreYOLO9P2s-visdrone.ptcc-by-nc-sa-3.0

Every file above exists in the LibreYOLO org today and downloads on first use.

Licensing

Check the license on the Hugging Face repository of the specific weights you download. Every checkpoint in the LibreYOLO org carries one, and they are not always the same across a family. That repository is the authoritative source; the summary below describes what applied when this page was last verified.

This is a description of the licenses involved, not legal advice. If the answer matters commercially, read the licenses yourself and take your own counsel.

Original work
YOLOv9, MultimediaTechLab
Upstream license
MIT
LibreYOLO code
MIT
Weights
MIT, republished at huggingface.co/LibreYOLO
Interpretation
MIT is a permissive license, so these weights can be used in commercial and closed-source products. The one standing obligation is to keep the license text and the copyright notice, Kin-Yiu Wong and Hao-Tang Tsui, with any copy you redistribute. It places no condition on your own application code, and a model you train yourself on your own data is yours. Two things are worth knowing beyond that. The code is ported from the authors' MIT re-release of YOLOv9, MultimediaTechLab/YOLO at commit c4cb5f6f, not the GPL-3.0 repository that carries the same model, so the permissive terms come from the source LibreYOLO actually derives from. And one checkpoint in this family is not MIT: the weight repository of the stride-4 VisDrone model, LibreYOLO9P2s-visdrone.pt, declares CC BY-NC-SA 3.0, which rules out commercial use and requires share-alike on derivatives.

The code of all three variants is ported from MultimediaTechLab/YOLO at commit c4cb5f6f (MIT). The end-to-end model's one-to-one predictor follows the design of DATE by Yiqun Chen (Apache-2.0), implemented for LibreYOLO with no DATE code copied. The stride-4 level is LibreYOLO code.

The stride-4 aerial checkpoint predicts the VisDrone classes. Use the publisher-declared license recorded on its weight repository.

Citation

@inproceedings{wang2024yolov9,
      title={{YOLOv9}: Learning What You Want to Learn Using Programmable Gradient Information},
      author={Wang, Chien-Yao and Yeh, I-Hau and Liao, Hong-Yuan Mark},
      year={2024},
      booktitle={Proceedings of the European Conference on Computer Vision (ECCV)},
}

Copied from the authors' citation block at github.com/MultimediaTechLab/YOLO#citations.

Verified against LibreYOLO v1.6.0. Support tables, checkpoints and benchmark numbers on this page are generated from the released library and the published weights, not written by hand.

YOLOv9 architecture

Select a variant or family overview, then select a block for details. Open the full diagram to zoom or download SVG and PNG.