Follow the procedure listed at https://rfdetr.roboflow.com/learn/deploy/
Important
- Python version: 3.10+ (upstream
rfdetrat the pinned version; Python 3.11 venv still recommended here) - Starting with RF-DETR 1.6.0, the export extra was renamed: use
pip install rfdetr[onnx] - Tested version: the
rfdetr[onnx]pin indeploy/requirements.txt(RFDETR_VERSIONinversions.env) - Starting with RF-DETR 1.7.0, ONNX exports use variant filenames (e.g.
rfdetr-medium.onnx,rfdetr-seg-medium.onnx) instead of the genericinference_model.onnx - The
--simplifyflag was removed in 1.8.0 (already deprecated in 1.7.0). Export scripts no longer accept it. - RF-DETR 1.8.x adds keypoint model export support via
RFDETRKeypointPreview. - 1.9.0 relaxes the
onnxsimpin to>=0.7.0. Earlier releases pinned<0.6.0, which had no prebuilt wheels for CPython 3.11/3.13 and madepip install rfdetr[onnx]hang building it from source — relevant here because this guide recommends a 3.11 venv. - 1.9.0 fixes ONNX export at a non-native resolution, which previously crashed.
- 1.9.0 adds ExecuTorch (
.pte) export viarfdetr[executorch]; see ExecuTorch Model Export. - 1.9.2 changes hierarchical COCO label mapping: unannotated grouping categories no longer consume a class slot, and all splits share the training split mapping. Retrain pre-1.9.2 checkpoints when adopting the re-filtered label space; otherwise per-class metrics can be misaligned. Exported model I/O is unchanged.
- 1.9.1 gates the extras to interpreters that ship wheels:
[onnx]pinsonnxruntime<1.24on Python 3.10 (newer releases dropped cp310 wheels), and[executorch]installs empty on Python 3.14 (ExecuTorch ships cp310–cp313 only). On the recommended 3.11 venv both resolve exactly as before. - 1.9.1 makes the exported models' own inference helpers resize the way
predict()does (bilinear, half-pixel centers,antialias=False) instead of PIL's antialiased filters. This C++ project already resizes that way — see Preprocessing parity — so exported ONNX/.ptemodels score here exactly as they did before; only upstream's Python-side ONNX/TFLite inference and INT8 calibration change. Re-export INT8 TFLite models if you ship any (not consumed by this project). - 1.9.1 speeds up ExecuTorch/XNNPACK export ~2.5× by recombining undelegated
addmmintoaten.linear. This changes which kernels the.pteneeds at run time — see ExecuTorch Model Export. - 1.9.2 changes nothing on the export or inference path — it is a training/dataset release. One item to know before retraining: hierarchical COCO datasets (Roboflow exports carrying a synthetic unannotated root category) no longer let that root consume a label slot, and
train/valid/testnow share one label mapping derived fromtrain. A model retrained after that change has a different class layout than one trained before it, so the label file you pass toinference_appmust come from the same side of the change as the checkpoint. - 1.9.3 makes exported-model decoding multi-label. Its ONNX/TFLite reference decoders had been taking a per-query
argmax, so any query scoring above threshold on more than one class silently lost the rest; they now rank the flattened (query, class) grid the wayPostProcess._select_topkdoes.PostProcessitself switched fromtorch.topkto a stableargsort, fixing the tie order to descending score, then ascending flattened index. This project's C++ postprocessing mirrors both changes — see How detections are selected. - 1.9.3 also fixes
SegmentationHead'sskip_blocksbranch to apply the learnedspatial_features_proj. That is a training-loss fix only: the export path already applied the projection unconditionally, so exported models are byte-identical and no re-export is needed. - 1.9.4 makes the background logit slot explicit, via a
background_class_idargument on the same reference decoders (-1default,Noneto keep every slot,0for background-first checkpoints). Mirrored here as--background-class-id. Note upstream's-1default mis-decodes the official pretrained COCO weights — a real foreground category occupies their final slot — which is why the C++ default is0; see the flag's README entry. - 1.9.4 also stops the TFLite helper from treating any lone rank-4 output as a segmentation mask (a keypoint export's
pred_keypointscould be upsampled intoDetections.mask). Not reachable here: this project selects output tensors by position under an explicit--segmentation/--keypointmode, never by guessing from rank. - 1.10.0 is primarily training and Python
predict()performance work. Exported input/output names, order, shapes, dtypes, and decode semantics are unchanged. It addsoutput_nametoRFDETR.export(); this repository's scripts pass it explicitly and report the returned path so their documented filenames stay stable. - Without
output_name, 1.10.0 encodes load-bearing details in generated names: ExecuTorch adds its delegate (for example_xnnpack.pte) and TensorRT adds its resolved precision (_fp16.trtor_fp32.trt). - The remaining 1.9.2–1.9.4 fixes are training, augmentation, dataset, and TFLite-conversion changes with no bearing on an exported detection/segmentation/keypoint model.
- 1.11.0 restructures export internals into one
Exporterclass per format (rfdetr.export._onnx.exporter.export_onnx→OnnxExporter, and so on) and removesRFDETR.optimize_for_inference().RFDETR.export()is unchanged, and it is the only export API thedeploy/scripts call. It also adds OpenVINO, LiteRT and Core AI export and dynamic-batch TensorRT engines; this project consumes none of those (every tensor contract here is batch 1). - 1.11.0 makes
export(format="tensorrt", fp16=True)build a real FP16 engine on TensorRT 11 by casting the ONNX graph to FP16 with float32 I/O; see TensorRT 11 and FP16. - 1.11.2 rewrites three ONNX patterns that ONNX Runtime's CoreML provider rejects, including the segmentation head's
Einsum(now aMatMul), and 1.11.0 reshapes the keypoint decode. Exported input/output names, order, shapes, dtypes and opset are unchanged, and detection, segmentation and keypoint exports give bit-identical outputs to 1.10.1. - 1.11.2 caps the
[executorch]extra at ExecuTorch>=1.3,<1.4; see ExecuTorch Model Export. - The rest of 1.11.0–1.11.2 is training, dataset, evaluation, threading and Python-side inference work (LoRA checkpoint loading, crowd-region mAP,
predict()thread safety,class_namesvalidation) with no bearing on an exported model.
# install python3.11 on Ubuntu 24.04
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt update
sudo apt install python3.11
sudo apt install python3.11-venv python3.11-distutils -y
# Create virtual environment with Python 3.11
python3.11 -m venv rfdetr_venv
source rfdetr_venv/bin/activate
# Install RF-DETR with export dependencies (tested version)
pip install -r deploy/requirements.txtAn exported model only reproduces predict()'s numbers if the caller resizes the way predict()
does: bilinear, half-pixel centers, no antialias filter, on the raw tensor, then ImageNet
mean/std normalization. 1.9.1 added rfdetr/export/_resize.py and routed upstream's own ONNX/TFLite
inference helpers, INT8 calibration, and benchmark paths through it, replacing
PIL.Image.resize(BILINEAR) — which low-pass filters on downscale and drifts from training.
This project's C++ preprocessing (media.cpp::preprocess_bgr_image) uses exactly that convention:
src = (dst + 0.5) * scale - 0.5, clamped into the source extent, with a plain 2×2 bilinear tap and
no area averaging. Segmentation mask upsampling (resize_threshold_mask, and the CUDA kernel behind
--gpu-postprocess) uses the same formula.
Writing the convention down exposed one divergence, fixed in this release: the C++ clamped the
derived sample index rather than the source coordinate, so output pixels mapping before the first
source pixel kept a negative interpolation weight and extrapolated past the edge instead of holding
it. Mask upsampling always upscales, so this affected the leading rows and columns of every mask.
The convention is now locked in by PreprocessFrame.ResizeIsAntialiasFree,
MaskResize.HalfPixelCenterBilinear, MaskResize.LeadingEdgeClampsInsteadOfExtrapolating, and
PreprocessFrame.UpscaleDoesNotExtrapolatePastEdge.
RF-DETR supports exporting detection models to the ONNX format, which enables interoperability with various inference frameworks and can improve deployment efficiency.
from rfdetr import RFDETRMedium # or RFDETRNano/Small/Medium/Large
model = RFDETRMedium(pretrain_weights=<CHECKPOINT_PATH>)
model.export()Model Outputs:
dets: Bounding boxes[batch, num_queries, 4]in cxcywh format (normalized)labels: Class logits[batch, num_queries, num_classes]
This command saves the ONNX model to the output directory as rfdetr-medium.onnx (filename includes the model variant).
For instance segmentation, use the sized RFDETRSeg* model classes or the provided export script.
python deploy/export_segmentation.py --model_type medium --input_size 432Available Options:
--model_type: Model type:nano,small,medium,large,xlarge,2xlarge(default: medium)--output_dir: Path to save exported model (default:output)--opset_version: ONNX opset version (default: 17)--batch_size: Batch size for export (default: 1)--input_size: Input image size (default: 640)
from rfdetr import RFDETRSegMedium # or RFDETRSegNano/Small/Large/XLarge/2XLarge
model = RFDETRSegMedium(pretrain_weights=<CHECKPOINT_PATH>)
model.export(
opset_version=17,
batch_size=1
)Model Outputs:
dets: Bounding boxes[batch, num_queries, 4]in cxcywh format (normalized)labels: Class logits[batch, num_queries, num_classes]masks: Segmentation masks[batch, num_queries, mask_h, mask_w](e.g., 108x108)
This command saves the ONNX segmentation model to the output directory as rfdetr-seg-medium.onnx.
RF-DETR 1.8.x adds keypoint detection via RFDETRKeypointPreview. Export with the provided script:
python deploy/export_keypoint.pyAvailable Options:
--output_dir: Path to save exported model (default:output)--opset_version: ONNX opset version (default: 17)--batch_size: Batch size for export (default: 1)--input_size: Input image size (default: 576, model resolution). Must be divisible by 24 (patch_size=12×num_windows=2).--device: Device for export, e.g.cpuorcuda(default: RF-DETR auto)
Model Outputs:
dets: Bounding boxes[batch, num_queries, 4]in cxcywh format (normalized)labels: Class logits[batch, num_queries, num_keypoint_classes](preview model: 2 classes for COCO person)keypoints: Keypoints[batch, num_queries, C*K_max, 8]where C = keypoint classes, K_max = max keypoints per class
This command saves the ONNX keypoint model to the output directory as rfdetr-keypoint-preview.onnx. The export script also writes a compatibility copy as rfdetr-keypoint.onnx.
Note
Keypoint schema: the official checkpoint is background-first; active-first needs a flag.
The RFDETRKeypointPreview checkpoint this script exports is background-first
(num_keypoints_per_class=[0, 17]): labels has 2 classes and keypoints has 2 × 17 = 34
channels, which the default Config::keypoint_counts {0, 17} decodes directly.
Upstream 1.8.2 changed the default schema to active-first [17]
(#1160) for models configured that way — a
single keypoint class with 17 channels and no background slot. Decode such an export with
--keypoint-counts 17 --background-class-id none.
Separately, re-export any keypoint model built with 1.8.0: 1.8.1
#1135 fixed eval-mode query routing, and
export traces the eval graph.
Note
RFDETRKeypointPreview is a Preview API — tensor layout may change in future releases.
RF-DETR 1.9.0 adds ExecuTorch (.pte) export for on-device inference. The C++ side runs these
through the ExecuTorch backend (-DUSE_EXECUTORCH=ON).
source scripts/versions.sh # exports RFDETR_VERSION and EXECUTORCH_VERSION
pip install "rfdetr[executorch]==${RFDETR_VERSION}"
pip show executorch # confirm the runtime version it resolveddeploy/export_executorch.py passes an explicit output_name, preserving names such as
rfdetr-medium.pte. Calling upstream 1.10.0 directly without that argument produces
rfdetr-medium_xnnpack.pte for the default delegate.
Important
The exporter and the C++ runtime are different ExecuTorch versions. Since rfdetr 1.11.2 the
extra caps ExecuTorch at >=1.3,<1.4, so it exports with 1.3.x. Upstream gives two reasons:
1.4.x needs torch>=2.13, and the 1.5.0 Python runtime fails every Method.execute. Do not force
executorch==${EXECUTORCH_VERSION#v} into the venv, because that conflicts with the cap. The C++
runtime stays at EXECUTORCH_VERSION in versions.env. A .pte exported with 1.3.1 loads and
runs on it, with results matching the ONNX export to 1e-6 (verified with rfdetr 1.11.2, see
the alignment record).
Run the export with the venv activated: ExecuTorch calls the flatc binary installed in the
venv's bin/, and fails with No such file or directory: 'flatc' when that is not on PATH.
Warning
A 1.9.1+ .pte needs the optimized kernel set. 1.9.1 recombines the addmm ops XNNPACK
leaves un-delegated back into aten.linear — 6 such calls in an RFDETRNano export at 384×384,
which is where its ~2.5× XNNPACK speedup comes from. aten::linear.out is registered only by
ExecuTorch's optimized kernels; the portable set registers addmm.out and mm.out but not
linear.out, so a portable-only runtime fails at load with an unregistered-operator error.
Build the ExecuTorch prefix with -DEXECUTORCH_BUILD_KERNELS_OPTIMIZED=ON (it defaults to OFF);
see Building the ExecuTorch install
prefix. The C++ build links
optimized_native_cpu_ops_lib when the prefix has it and warns at configure time when it does not.
python deploy/export_executorch.py --model_type medium --input_size 640Available Options:
--model_type: Model type:nano,small,medium,large,xlarge,2xlarge(default: medium)--output_dir: Path to save exported model (default:output)--batch_size: Batch size for export (default: 1)--input_size: Input image size (default: 640)--backend:xnnpack(CPU, fp32),coreml(Apple, fp16), orqnn(Qualcomm HTP, fp16) (default: xnnpack)--soc: Target SoC identifier, required when--backend qnn(e.g.SM8650)
Important
Only --backend xnnpack produces a .pte this project can run. The delegate is baked into
the program and must be linked into the runtime to self-register, but -DEXECUTORCH_DELEGATE
accepts only xnnpack or portable — there is no coreml or qnn delegate in this build. A
CoreML or QNN .pte therefore fails at load with Backend ... is not registered. Those options
exist for exporting to a different ExecuTorch runtime; the script warns before and after export.
Note also that portable is only useful for a .pte exported with no delegate at all — every
rfdetr ExecuTorch export applies one, so xnnpack is the setting that matches this workflow.
from rfdetr import RFDETRMedium # or RFDETRNano/Small/Large/XLarge/2XLarge
model = RFDETRMedium(pretrain_weights=<CHECKPOINT_PATH>)
model.export(format="executorch", backend="xnnpack")Model Outputs (same layout and order as the ONNX detection export):
dets: Bounding boxes[batch, num_queries, 4]in cxcywh format (normalized)labels: Class logits[batch, num_queries, num_classes]
This saves the model to the output directory as rfdetr-medium.pte.
Important
The delegate is baked into the .pte at export time, and the C++ build must link the matching
delegate library for it to self-register. Export with --backend xnnpack (the default) and build
with -DEXECUTORCH_DELEGATE=xnnpack (also the default). A mismatch fails at run time, not at link
time. The ExecuTorch backend checks at load that the program returns dets before labels and
refuses to run otherwise, rather than decoding class logits as box coordinates.
Note
ExecuTorch's format="executorch" with backend="coreml" produces a .pte and is distinct from
format="coreml", which produces a native .mlpackage. This project consumes .pte only.
Warning
FileNotFoundError: [Errno 2] No such file or directory: 'flatc' — .pte serialization shells
out to the flatc FlatBuffers compiler, which ships in the venv's bin/. ExecuTorch resolves it
from PATH, so invoking the interpreter by absolute path (/path/to/venv/bin/python export.py)
without activating the venv fails. Either source <venv>/bin/activate first, or put the venv's
bin on PATH for the command.
rf-detr-nano.pth exported at 384×384 to both formats and run through inference_app produced
identical detections — same count, classes, and order at thresholds 0.5 and 0.05, with a maximum
box delta of 1e-4 px and score delta of 1e-6. The ExecuTorch backend requires no preprocessing or
postprocessing differences from the ONNX Runtime path.
Re-run for 1.9.1 (rfdetr[onnx,executorch]==1.9.1, ExecuTorch runtime v1.4.0 built with
EXECUTORCH_BUILD_KERNELS_OPTIMIZED=ON, data/dog.jpg): both backends found the same 3 detections
with boxes equal to every printed digit and scores differing by at most 1e-6 (car 0.854236 vs
0.854237). Against the previous portable-only prefix the same .pte refused to run:
E executorch:method.cpp:819] Missing operator: [29] aten::linear.out
E executorch:method.cpp:1125] There are 6 instructions don't have corresponding operator registered
Error: ExecuTorch forward() failed with error code 20
For GPU deployment, you can convert the ONNX model to TensorRT format for optimized performance.
1.9.0 can build the engine itself, without a separate trtexec step:
from rfdetr import RFDETRMedium
model = RFDETRMedium(pretrain_weights=<CHECKPOINT_PATH>)
model.export(format="tensorrt", fp16=True) # alias: format="trt"Requires pip install "rfdetr[tensorrt]==${RFDETR_VERSION}" (after source scripts/versions.sh), which provides tensorrt + polygraphy. The engine is built in-process through the polygraphy API rather than by shelling out to trtexec, so no trtexec binary is needed, and it is built for the local GPU architecture. Pass fp16=False on TensorRT builds that do not expose the FP16 builder flag. On TensorRT 11, fp16=True builds an FP16 engine from rfdetr 1.11.0 on. Earlier releases fell back to FP32 without an error (roboflow/rf-detr#1453); see TensorRT 11 and FP16.
In 1.10.0 the default filename records the resolved precision, for example
rfdetr-medium_fp16.trt. Pass output_name="model" when an exact model.trt path is required.
The repository's export_trt.sh remains explicit through trtexec --saveEngine.
Note that [tensorrt] no longer installs pycuda as of 1.9.0 — that moved to the separate [tensorrt-bench] extra and is only needed for TRTInference's async benchmarking mode. The standard export-to-engine path is unaffected.
The trtexec recipes below remain valid and are what export_trt.sh in this repo uses; prefer them when you want to build an engine on a machine without the Python package installed.
trtexec --onnx=/path/to/model.onnx \
--saveEngine=/path/to/model.engine \
--memPoolSize=workspace:4096 \
--useCudaGraph \
--useSpinWait \
--warmUp=500 \
--avgRuns=1000 \
--duration=10source scripts/versions.sh # exports TENSORRT_IMAGE, derived from NGC_CONTAINER_TAG
docker run --rm -it --gpus=all \
-v $(pwd)/exports:/exports \
--ipc=host \
--ulimit memlock=-1 \
--ulimit stack=67108864 \
-v $(pwd)/model.onnx:/workspace/model.onnx \
-w /workspace \
"${TENSORRT_IMAGE}" \
/bin/bash -cx "trtexec --onnx=model.onnx \
--saveEngine=/exports/model.engine \
--memPoolSize=workspace:4096 \
--useCudaGraph \
--useSpinWait \
--warmUp=500 \
--avgRuns=1000 \
--duration=10"The recipes target the pinned TensorRT 11, which has no --fp16: see the next section for FP16.
TensorRT 11 removed weak typing, and with it BuilderFlag::kFP16 and trtexec --fp16. Every
network is strongly typed: each layer runs in the precision the ONNX graph declares. rfdetr exports
are float32, so on TensorRT 11 — through trtexec or the C++ backend building from an .onnx —
you get an FP32 engine unless the ONNX is converted first.
model.export(format="tensorrt", fp16=True) does that conversion itself from rfdetr 1.11.0 on: it
casts the graph to FP16 and adds boundary casts that keep the graph inputs and outputs float32.
That is the I/O contract the C++ backend requires. The .trt it writes (*_fp16.trt) loads directly
in the C++ TensorRT 11 backend: on an RTX 3060, nano detection and segmentation engines exported by
1.11.2 ran 3.6–3.8× faster than their FP32 builds, with scores within 0.004 and mask IoU ≥ 0.998.
Run the export with the same TensorRT version as the runtime. The [tensorrt] extra only
requires tensorrt>=8.6.1, and an engine built by another version fails to deserialize. Inside
nvcr.io/nvidia/tensorrt:<NGC_CONTAINER_TAG>-py3, constrain pip to the container's TensorRT:
pip list | awk '$1=="tensorrt"{print "tensorrt=="$2}' > /tmp/trt.txt
pip install -c /tmp/trt.txt "rfdetr[tensorrt]==${RFDETR_VERSION}"To build with trtexec or the C++ backend instead, convert the ONNX to mixed precision with NVIDIA ModelOpt AutoCast, keeping the graph inputs and
outputs in float32 — the C++ backend exchanges float32 buffers with the engine and refuses to
load one whose I/O tensors are not float32:
pip install 'nvidia-modelopt[onnx]'
python -m modelopt.onnx.autocast \
--onnx_path model.onnx \
--output_path model_fp16.onnx \
--low_precision_type fp16 \
--keep_io_typesThen build from model_fp16.onnx as usual — trtexec --onnx=model_fp16.onnx --saveEngine=…
without --fp16, or pass it straight to inference_app.
Note
TensorRT optimization works for both detection and segmentation models. The C++ inference engine supports ONNX Runtime, TensorRT, and ExecuTorch backends with compile-time backend selection — exactly one is compiled in.