DEIM
A detection transformer trained with dense one-to-one matching, which converges in far fewer epochs than the DETR recipes it builds on. LibreYOLO carries two versions of it, told apart by the checkpoint you load.
- Tasks
- detection
- Sizes
- deim: n, s, m, l, x at 640 px
- Install
pip install libreyolo- Support tier
- Core, since v1.2.0. Core trainable detectors: features follow the flagships in the same release wave.
- Upstream
- DEIM and DEIMv2 by Intellindust AI Lab, Apache-2.0; the DEIMv2 DINOv3 backbone adds Meta's DINOv3 License. Paper, source
- Licenses
- Code Apache-2.0, weights Apache-2.0; the DEIMv2 DINOv3 backbone adds Meta's DINOv3 License. Commercial use
Install
Neither version needs an optional extra. Everything they import is in the base install.
pip install libreyoloAdapter fine-tuning with lora=True is the exception, and needs the lora
extra.
pip install "libreyolo[lora]"Predict
Weights download from Hugging Face on first use and are cached locally.
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreDEIMn.pt")result = model(SAMPLE_IMAGE, save=True) for box in result.boxes: print(box.cls, box.conf, box.xyxy)libreyolo predict model=LibreDEIMn.pt save=True \ source=https://raw.githubusercontent.com/LibreYOLO/libreyolo/release/libreyolo/assets/parkour.jpgfrom libreyolo import LibreYOLO # The version is part of the file name, and the factory routes on the# checkpoint, so both load the same way.model = LibreYOLO("LibreDEIMv2pico.pt") # Any source the library accepts: file, folder, URL, webcam index,# RTSP stream, or a .streams listfor result in model.predict("clip.mp4", stream=True, save=True): 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. conf and max_det filter a top-k
decode over queries and classes; there is no NMS step to tune, and iou is
accepted but unused. See prediction for sources, streaming and
result handling.
DEIM accepts rectangular imgsz=(height, width) for prediction. DEIMv2 rejects rectangular predict/validation requests explicitly. This does not imply rectangular training or export support.
Variants
Version 1 ships five sizes, all at the same input size. Version 2 keeps those
five names and adds three smaller ones, atto, femto and pico, the first
two of which are native at a lower input size than the rest. Five size codes
therefore exist in both versions and name different models; the version is
written into the checkpoint file name.
| Checkpoint | Input (px) | mAP 50-95 | Params (M) |
|---|---|---|---|
| LibreDEIMl | 640 | 57.8 | 31.24 |
| LibreDEIMm | 640 | 55.4 | 19.59 |
| LibreDEIMn | 640 | 46.8 | 3.78 |
| LibreDEIMs | 640 | 52.1 | 10.32 |
| LibreDEIMx | 640 | 59.6 | 62.62 |
| LibreDEIMv2atto | 320 | 27.5 | 0.51 |
| LibreDEIMv2femto | 416 | 34.5 | 0.98 |
| LibreDEIMv2l | 640 | 58.6 | 32.55 |
| LibreDEIMv2m | 640 | 56.0 | 18.36 |
| LibreDEIMv2n | 640 | 46.7 | 3.6 |
| LibreDEIMv2pico | 640 | 42.2 | 1.54 |
| LibreDEIMv2s | 640 | 53.0 | 9.78 |
| LibreDEIMv2x | 640 | 61.3 | 51.21 |
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.
Version 1 keeps D-FINE's architecture and swaps its classification objective
for the matchability-aware loss from the dense one-to-one recipe, so the two
families share almost every state dict key and are told apart by the metadata
in the checkpoint. Version 2 keeps that training contract and mixes backbones:
HGNetv2 below s, and a DINOv3 vision transformer with a spatial tuning
adapter at s and above. That backbone is what puts a second license on those
four checkpoints, so read licensing before you ship one.
Train
Training starts from a published checkpoint. pretrained never reaches the
trainer: version 1 warns that the key is unknown and ignores it, version 2
removes it. Neither gives you a randomly initialized model.
from libreyolo import LibreYOLO model = LibreYOLO("LibreDEIMn.pt") # coco128.yaml downloads a 128-image sample on first use. Point `data`# at your own dataset YAML for a real run.model.train(data="coco128.yaml", epochs=50, batch=8, lr0=1e-4)libreyolo train model=LibreDEIMn.pt data=coco128.yaml \ epochs=50 batch=8 lr0=1e-4from libreyolo import LibreYOLO # Left unset, epochs, batch, imgsz and lr0 come from the released# recipe for the size that was loaded.model = LibreYOLO("LibreDEIMv2pico.pt")model.train(data="coco128.yaml", epochs=50)# Needs the lora extra: pip install "libreyolo[lora]"from libreyolo import LibreYOLO model = LibreYOLO("LibreDEIMn.pt")model.train(data="coco128.yaml", epochs=50, lora=True)libreyolo train model=LibreDEIMn.pt data=coco128.yaml \ epochs=50 device=0,1Pass lr0 yourself on version 1. Its Python train() signature defaults to
4e-4, the rate from the published COCO recipe, while the family's training
config carries 1e-4 as its fine-tune default, and that lower value is what
the CLI resolves when the argument is absent. The config records the
measurement behind it: at the batch sizes a fine-tune actually uses, on small
datasets, the COCO rate measurably degraded transfer.
Version 2 resolves those defaults itself. Leaving epochs, batch, imgsz
and lr0 unset makes it read each one from the released recipe for the size
that was loaded, so the small sizes train at their own input resolution without
being told, and a value you pass overrides the recipe. imgsz is the argument
it constrains: it has to be a positive multiple of 32, and version 2 raises
before the run starts otherwise.
See training for datasets, augmentation, multi-GPU and loggers.
DEIM enables AMP by default with amp_dtype="float16". Pass amp=False for FP32 training.
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.
from libreyolo import LibreYOLO model = LibreYOLO("LibreDEIMn.pt") # val() returns a plain dict, not an objectmetrics = model.val(data="coco128.yaml") print(metrics["metrics/mAP50-95"])print(metrics["metrics/mAP50"])print(metrics["metrics/precision"], metrics["metrics/recall"])libreyolo val model=LibreDEIMn.pt data=coco128.yaml# coco-val-only.yaml fetches the 5000 val2017 images and skips the# training set. It carries an embedded download script, so it needs# explicit permission unless the dataset is already local.libreyolo val model=LibreDEIMn.pt data=coco-val-only.yaml \ allow_download_scripts=TrueThe rows in the benchmark table above come from the LibreYOLO benchmark harness; the note under that table records which dataset produced them and links the run records.
Export
| Task | ONNX | TorchScript | ExecuTorch | TensorRT | OpenVINO | Paddle | MNN | RKNN | ncnn | TFLite | CoreML | Core AI |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Detection | Detection to ONNX: supported | Detection to TorchScript: supported | Detection to ExecuTorch: not supported | Detection to TensorRT: supported | Detection to OpenVINO: supported | Detection to Paddle: supported | Detection to MNN: supported | Detection to RKNN: not supported | Detection to ncnn: not supported | Detection to TFLite: not supported | Detection to CoreML: not supported | Detection to Core AI: supported |
The matrix covers both versions as one page: where they disagree about a format, the cell shows the weaker of the two, so nothing here is oversold for whichever version you load.
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.
# Needs the onnx extra: pip install "libreyolo[onnx]"from libreyolo import LibreYOLO model = LibreYOLO("LibreDEIMn.pt")path = model.export(format="onnx")print(path)libreyolo export model=LibreDEIMn.pt format=onnxfrom 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("LibreDEIMn.onnx")result = model(SAMPLE_IMAGE) print(result.boxes.xyxy)ONNX inputs and outputs
LibreDEIM, and LibreDEIMv2 atto, femto, pico and n
| Tensor | images float32 [batch, 3, height, width] |
|---|---|
| Color | RGB |
| Scaling | pixel / 255 |
| Normalization | none |
| Resize | stretch to width x height (no padding, aspect ratio not kept), Pillow bilinear |
How to reproduce Pillow's bilinear resize
LibreYOLO resizes with Pillow's BILINEAR filter (Image.resize). It is not OpenCV INTER_LINEAR or INTER_AREA: when shrinking, Pillow widens the filter by the shrink factor, and OpenCV's resizes change detections and move boxes by several pixels. To reproduce it without Pillow, resize one axis, round to 8 bits, then resize the other axis and round again. Per axis, let s = in_size / out_size and f = max(s, 1). Output pixel i has center c = (i + 0.5) * s and reads source pixels j from int(c - f + 0.5) to int(c + f + 0.5) - 1, clipped to the image, each with weight max(0, 1 - |j + 0.5 - c| / f), divided by the sum of the weights. Pillow works in fixed point and picks the axis order from the image shape, so float weights can differ from it by one level on a few pixels. That does not change the results in our checks.
Square input only. Export rejects a non-square imgsz. height and width equal the imgsz metadata key: 640 by default, 320 for DEIMv2 atto and 416 for DEIMv2 femto.
Batch size 1 only. The graph declares a dynamic batch axis, but batches of 2 or more fail in 1.7.0.
To run several images per call, export with batch=2. That graph takes any batch size and gives the same results per image.
Divide by 255 after the resize. There is no mean or std normalization for these sizes.
Only the default export exists. nms=True raises NotImplementedError for this family.
Detection, default export
| Output | Shape | Meaning |
|---|---|---|
pred_logits | [batch, queries, nc] | One logit per query and class. Sigmoid is not applied, and there is no background class. 300 queries, except DEIMv2 atto with 100 queries, femto with 150 queries and pico with 200 queries. |
pred_boxes | [batch, queries, 4] | cx, cy, w, h of each query, normalized 0 to 1 of the input. The input is the whole image stretched, so these are also fractions of the original image. |
Postprocessing
- Apply sigmoid to pred_logits.
- Flatten the [queries, nc] scores and take the top 300 (max_det, 300 by default). For flat index k, the query is k // nc and the class is k % nc. One query can yield several classes.
- Keep the pairs whose score is above the confidence threshold (0.25 by default).
- Convert the query's box to x1 = cx - w / 2, y1 = cy - h / 2, x2 = cx + w / 2, y2 = cy + h / 2. Multiply x by the original image width and y by the original image height.
- There is no NMS and the iou threshold is not used. DEIM predicts a set of boxes.
- LibreYOLO does not clip these boxes, so an edge can sit slightly outside the image.
LibreDEIMv2 s, m, l and x
| Tensor | images float32 [batch, 3, height, width] |
|---|---|
| Color | RGB |
| Scaling | pixel / 255 |
| Normalization | mean [0.485,0.456,0.406], std [0.229,0.224,0.225] |
| Resize | stretch to width x height (no padding, aspect ratio not kept), Pillow bilinear |
How to reproduce Pillow's bilinear resize
LibreYOLO resizes with Pillow's BILINEAR filter (Image.resize). It is not OpenCV INTER_LINEAR or INTER_AREA: when shrinking, Pillow widens the filter by the shrink factor, and OpenCV's resizes change detections and move boxes by several pixels. To reproduce it without Pillow, resize one axis, round to 8 bits, then resize the other axis and round again. Per axis, let s = in_size / out_size and f = max(s, 1). Output pixel i has center c = (i + 0.5) * s and reads source pixels j from int(c - f + 0.5) to int(c + f + 0.5) - 1, clipped to the image, each with weight max(0, 1 - |j + 0.5 - c| / f), divided by the sum of the weights. Pillow works in fixed point and picks the axis order from the image shape, so float weights can differ from it by one level on a few pixels. That does not change the results in our checks.
These sizes use a DINOv3 backbone, so they take ImageNet mean and std. The atto, femto, pico and n sizes do not.
Square input only. Export rejects a non-square imgsz. height and width equal the imgsz metadata key, 640 by default.
Batch size 1 only. The graph declares a dynamic batch axis, but batches of 2 or more fail in 1.7.0.
To run several images per call, export with batch=2. That graph takes any batch size and gives the same results per image.
After the resize, divide by 255, then subtract the ImageNet mean and divide by the ImageNet std, per channel in RGB order.
Only the default export exists. nms=True raises NotImplementedError for this family.
Detection, default export
| Output | Shape | Meaning |
|---|---|---|
pred_logits | [batch, queries, nc] | One logit per query and class. Sigmoid is not applied, and there is no background class. 300 queries. |
pred_boxes | [batch, queries, 4] | cx, cy, w, h of each query, normalized 0 to 1 of the input. The input is the whole image stretched, so these are also fractions of the original image. |
Postprocessing
- Apply sigmoid to pred_logits.
- Flatten the [queries, nc] scores and take the top 300 (max_det, 300 by default). For flat index k, the query is k // nc and the class is k % nc. One query can yield several classes.
- Keep the pairs whose score is above the confidence threshold (0.25 by default).
- Convert the query's box to x1 = cx - w / 2, y1 = cy - h / 2, x2 = cx + w / 2, y2 = cy + h / 2. Multiply x by the original image width and y by the original image height.
- There is no NMS and the iou threshold is not used. DEIM predicts a set of boxes.
- LibreYOLO does not clip these boxes, so an edge can sit slightly outside the image.
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.
| File | Input (px) | Weights license |
|---|---|---|
| Detection | ||
| LibreDEIMn.pt | 640 | apache-2.0 |
| LibreDEIMs.pt | 640 | apache-2.0 |
| LibreDEIMm.pt | 640 | apache-2.0 |
| LibreDEIMl.pt | 640 | apache-2.0 |
| LibreDEIMx.pt | 640 | apache-2.0 |
| LibreDEIMv2n.pt | 640 | apache-2.0 |
| LibreDEIMv2s.pt | 640 | other |
| LibreDEIMv2m.pt | 640 | other |
| LibreDEIMv2l.pt | 640 | other |
| LibreDEIMv2x.pt | 640 | other |
| LibreDEIMv2atto.pt | apache-2.0 | |
| LibreDEIMv2femto.pt | apache-2.0 | |
| LibreDEIMv2pico.pt | apache-2.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
- DEIM and DEIMv2, Intellindust AI Lab
- Upstream license
- Apache-2.0; the DEIMv2 DINOv3 backbone adds Meta's DINOv3 License
- Upstream source
- github.com/Intellindust-AI-Lab/DEIM
- LibreYOLO code
- MIT
- Weights
- Apache-2.0; the DEIMv2 DINOv3 backbone adds Meta's DINOv3 License, republished at huggingface.co/LibreYOLO
- Interpretation
- Apache-2.0 is a permissive license, so these weights can be used in commercial and closed-source products. It asks you to keep its license text and attribution notices with any copy of the weights you redistribute, and it grants a patent license. It places no obligation on your own application code, and weights you train yourself on your own data are yours. DEIM is Apache-2.0 throughout, and so are the DEIMv2 sizes below S, which use an HGNetv2 backbone. The DEIMv2 S, M, L and X sizes take their backbone from DINOv3, so both their weights and the backbone source vendored in LibreYOLO are additionally governed by Meta's DINOv3 License Agreement, which is not an OSI license: redistribution has to carry the agreement with it, and it forbids use for military or warfare purposes, weapons development, espionage, nuclear industries and anything subject to ITAR. Their Hugging Face repositories declare both licenses, and DEIMv2 is also cited separately (arXiv 2509.20787).
Citation
@misc{huang2024deim,
title={DEIM: DETR with Improved Matching for Fast Convergence},
author={Shihua, Huang and Zhichao, Lu and Xiaodong, Cun and Yongjun, Yu and Xiao, Zhou and Xi, Shen},
booktitle={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition},
year={2025},
}Copied from the authors' citation block at github.com/Intellindust-AI-Lab/DEIM#5-citation.
DEIMv2 is a separate paper and has its own citation block at github.com/Intellindust-AI-Lab/DEIMv2; cite that one if you used a version 2 checkpoint.