1.11.0

roboflow/rf-detr1.11.0Sep 24, 2026by Borda

Upgrading?

AI Summary

This release expands export support with OpenVINO IR, LiteRT, and Apple Core AI formats, introduces dynamic-batch TensorRT engines, and accelerates training via CUDA graph replay and WebDataset streaming.

Key Highlights

  • New export formats: OpenVINO IR (configurable precision), LiteRT, and Apple Core AI (.aimodel for iOS/iPadOS/macOS 27+)
  • Dynamic-batch TensorRT engines via a single optimization profile for a range of batch sizes
  • Opt-in CUDA graph training replay, combinable with torch.compile and Transformer Engine FP8
  • WebDataset streaming reader for tar-shard training input at scale
  • Restructured export internals using a registry pattern with one Exporter class per format

Breaking Changes

  • Export internals restructured into one Exporter class per format (e.g., `export_onnx` -> `OnnxExporter`), requiring updated imports for direct converter functions
  • TrainConfig.multi_scale is now a three-state enum; the boolean `do_random_resize_via_padding` parameter is removed
  • TensorRT export with `backbone_only=True` now marks the engine with a `-backbone` suffix to prevent silent overwrites
  • Removed functions: `RFDETR.optimize_for_inference()`, `TrainConfig.lr_drop`/`lr_min_factor`, and the standalone synthetic-dataset generator

New Features

  • OpenVINO IR export with configurable precision (float32/float16)
  • Apple Core AI export targeting iOS, iPadOS, and macOS 27+
  • Opt-in CUDA graph training replay for single-GPU detection training
  • WebDataset streaming reader for tar-shard training input
  • Dynamic-batch TensorRT engines accepting a range of batch sizes

Full Release Notes

RF-DETR 1.11.0 widens the export surface and speeds up training. Three new export formats land — OpenVINO IR (with configurable precision), LiteRT via litert-torch, and Apple Core AI (`.aimodel`, for iOS/iPadOS/macOS 27+) — alongside dynamic-batch TensorRT engines built through a single optimization profile, so one engine now serves a batch range instead of one fixed size. Training gains opt-in CUDA-graph replay (combinable with `torch.compile`, plus a Transformer Engine FP8 capture route on supported hardware) and a WebDataset streaming reader for tar-shard training input at scale.

This release also completes a deprecation cycle from 1.9.0: `RFDETR.optimize_for_inference()`, `TrainConfig.lr_drop`/`lr_min_factor`, and the standalone synthetic-dataset generator are removed, and export internals were restructured into one `Exporter` class per format behind a registry. `RFDETR.export()`'s own signature is unchanged, but code importing the old converter functions directly needs updated imports. See the Migration guide below for exact before/after code.

## ✨ Spotlights / highlights

### Breaking: export internals restructured into one `Exporter` class per format

Each export format (`onnx`, `openvino`, `coreml`, `executorch`, `tflite`, `tensorrt`, `litert`) is now written by a `<Format>Exporter` class built from that format's own frozen config dataclass, reached through a registry. `RFDETR.export()`'s own signature and return value are unchanged — this only breaks code that imported the old converter functions directly. (#1448)

```python
# Before (removed)
from rfdetr.export._onnx.exporter import export_onnx

# After
from rfdetr.export._onnx.exporter import OnnxExporter
```

### New: OpenVINO IR export

```python
from rfdetr import RFDETRNano

model = RFDETRNano()
model.export(format="openvino", openvino_precision="float32", output_dir="output")
# -> output/rfdetr-nano.xml + output/rfdetr-nano.bin
```

`openvino_precision` accepts `"float32"`, `"float16"`, or `None`. Install with `pip install "rfdetr[openvino]"`. (#1238)

### New: Apple Core AI export

```python
from rfdetr import RFDETRNano

model = RFDETRNano(pretrain_weights="<path/to/checkpoint.pth>")
model.export(format="coreai")
# -> output/rfdetr-nano_fp32.aimodel
```

Experimental. Targets iOS, iPadOS and macOS 27+ via `coreai-torch`, no ONNX step. `coreai_precision="float16"` halves asset size; the exporter runs the two-stage `topk` in float32 even in a float16 graph because the Neural Engine returns corrupt float16 `topk` indices. Install with `pip install "rfdetr[coreai]"` (Python 3.11-3.13, macOS Apple silicon or Linux x86-64 to build the asset). (#1521)

### New: opt-in CUDA graph training replay, combinable with `compile` and FP8

```python
model = RFDETRNano(cuda_graphs=True)
# or, delegated to Inductor's own CUDA graph trees:
model = RFDETRNano(cuda_graphs=True, compile=True)
```

Single-GPU detection training only. BF16/eager route supports gradient accumulation; a separate FP8 route through Transformer Engine (`amp_dtype="fp8"`, `compile=False`) is limited to `grad_accum_steps=1`. Measured on an RTX PRO 6000 (Nano, batch 64, BF16): `compile=True` alone is ~17% faster than eager; `cuda_graphs=True` combined with `compile=True` is fastest at large batch. (#1468, #1479, #1481)

### New: stream training input directly from WebDataset tar shards

```python
from rfdetr.config import TrainConfig

config = TrainConfig(dataset_file="webdataset", dataset_dir="path/to/shards")
```

Pack shards with `python -m rfdetr.cli.webdataset`. Requires the `data` extra: `pip install "rfdetr[data]"` (not `[webdataset]` — that extra never existed). (#1396)

### New: dynamic-batch TensorRT engines through a single optimization profile

```python
model.export(format="tensorrt", dynamic_batch=True, batch_size=4, max_batch_size=16)
```

One engine now accepts any batch from 1 to `max_batch_size`, tuned for `batch_size`; without `dynamic_batch` the engine still accepts only the batch size baked into the intermediate ONNX graph. (#1511)

## 🔄 Migration guide

### Breaking changes

**The export internals are now exporter classes.** `RFDETR.export()`'s own signature and return value are unchanged — only the internal modules moved (see table below). This matters only if you imported the converter functions directly. Every path with a leading underscore was already internal and carried no stability guarantee. (#1448)

| Removed                                                                        | Replacement                                                                               |
| ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- |
| `rfdetr.export.main` (whole module, including `main()` and `make_infer_image`) | `rfdetr.export.prepare.make_infer_image`; the `main()` CLI had no entry point and is gone |
| `rfdetr.export.protocols.ExporterProtocol`                                     | `rfdetr.export.base.Exporter`                                                             |
| `rfdetr.export._onnx.exporter.export_onnx`                                     | `rfdetr.export._onnx.exporter.OnnxExporter`                                               |
| `rfdetr.export._openvino.exporter.export_openvino`                             | `rfdetr.export._openvino.exporter.OpenVINOExporter`                                       |
| `rfdetr.export._coreml.converter.export_coreml`                                | `rfdetr.export._coreml.exporter.CoreMLExporter`                                           |
| `rfdetr.export._executorch.converter.export_executorch`                        | `rfdetr.export._executorch.exporter.ExecuTorchExporter`                                   |
| `rfdetr.export._tflite.converter.export_tflite`                                | `rfdetr.export._tflite.exporter.TFLiteExporter`                                           |
| `rfdetr.export._tensorrt.build_engine`                                         | `rfdetr.export._tensorrt.exporter.TensorRTExporter`                                       |
| `rfdetr.export.benchmark.TRTInference`                                         | `rfdetr.export._tensorrt.inference.TRTInference`                                          |

**`format="tensorrt"` with `backbone_only=True` and `output_name` now marks the engine.** The engine is named `{output_name}-backbone.trt` instead of `{output_name}.trt` — without the marker a backbone engine silently overwrote a full-detector engine exported under the same name. (#1448)

**`TrainConfig.multi_scale` is now a three-state enum; `do_random_resize_via_padding` is gone.** Booleans still work as input — `True` normalizes to `PER_BATCH` (the old default's behavior), `False` to `OFF`. (#1485)

```python
# Before
TrainConfig(multi_scale=True, do_random_resize_via_padding=True)

# After
TrainConfig(multi_scale="per-sample")
```

### Deprecated (removal in v1.14)

- `ModelConfig.amp` → `TrainConfig.amp_dtype`, which now accepts `None` to disable autocast entirely. An explicit `amp_dtype` always wins over the deprecated `amp` flag. (#1471)
- `TrainConfig.fp16_eval` — no runtime consumer since the PyTorch Lightning migration; evaluation precision follows `amp_dtype`.

### Removed (deprecated since v1.9.0)

- `RFDETR.optimize_for_inference()` → `RFDETR.inference()`, same signature. (#1500)
- `TrainConfig.lr_drop` / `lr_min_factor` → pass through `lr_scheduler_kwargs={"lr_drop": ..., "min_factor": ...}`. (#1500)
- `rfdetr.datasets.synthetic` (whole module) → use the [`fuse-augmentations`](https://github.com/Borda/fuse-augmentations) package's `fuse_augmentations.data` module. (#1435)

Full before/after code for every item above: [Migration Guide](../../docs/getting-started/migration.md#upgrade-110--111).

## 📝 Notable changes

### 🚀 Added

- **OpenVINO IR export** — `RFDETR.export(format="openvino", openvino_precision=...)`. `openvino_precision` picks `"float32"`, `"float16"`, or `None` to control IR weight precision; dynamic batch is not supported for this format yet. Install with `[openvino]`. (#1238)
- **LiteRT export** via litert-torch — `format="litert"` captures the model directly with `torch.export` and lowers it to `.tflite`, skipping the ONNX/TensorFlow hop the existing `format="tflite"` route takes. Experimental: float32 only, CPU (XNNPACK) delegate only, no dynamic batch, keypoint models unsupported on litert-torch 0.9.4, and its own dependency pins `torch<2.14`. Install with `[litert]`. On the pretrained Nano/Seg-Nano checkpoints it tracks eager PyTorch to about `1e-7` (boxes) and `3e-5` (class logits, mask probabilities) over the confident queries, up to `2e-3` at worst over all 300 raw mask logits; the single-level deformable-attention core no longer emits a one-output `split` in export graphs, the one op litert-torch couldn't lower, so every other export route's numbers are unaffected. (#1459)
- **Apple Core AI export** — `format="coreai"` writes an Apple Core AI `.aimodel` for iOS/iPadOS/macOS 27+ through `torch.export` and `coreai-torch`; `coreai_precision="float16"` halves asset size, and `notes` are stored in the asset metadata. Experimental. Install with `[coreai]`. The export decomposes `aten.grid_sampler_2d`, which `coreai-torch` cannot lower, and runs float16 two-stage `topk` in float32 because the Neural Engine returns corrupt float16 `topk` indices; detection, segmentation and keypoint models match eager PyTorch within 1e-4 on CPU in the new `e2e_coreai` suite, which runs on GitHub's macOS 27 (`xcode-27`) runner. (#1521)
- **Dynamic-batch TensorRT engines** — `dynamic_batch=True` with `max_batch_size` builds one engine spanning a single TensorRT optimization profile (`min=1`, `opt=batch_size`, `max=max_batch_size`) instead of a separate engine per batch size. The engine was previously refused for a dynamic ONNX graph only because no profile was passed to the build; `TRTInference` now allocates a dynamic engine's bindings at the profile's max batch and trims outputs per call, and its async path moves off `execute_async_v2` (removed in TensorRT 11) to `execute_async_v3`. Verified within `1e-2` of eager PyTorch on an RTX 4090 (batch 1/3/4 against a `batch_size=2, max_batch_size=4` engine, plus a rejected batch of 5). Latency, FP16, median of 5x100 runs: RTX 4090 Nano-384px static 0.88/1.49/2.25 ms at batch 1/4/8 vs. dynamic 1.30/1.52/2.60 ms, Medium-576px static 1.31/2.66/4.67 ms vs. dynamic 1.70/2.69/5.06 ms; a Colab T4 shows the same pattern at higher absolute latency. At the profile's `opt` batch the dynamic engine is within ~2% of a static one on the 4090 (~5% on the T4); other batch sizes pay 1-48% more — the trade for one file instead of one engine per batch size. (#1511)
- Public export inference wrapper module, `rfdetr/export/inference.py`, replacing the previous private `rfdetr.export._openvino.inference` import; the ONNX and TFLite inference helpers now route through shared `preprocess_to_nchw`/`decode_detections` helpers instead of duplicating that logic each. (#1446)
- `backbone_only=True` now exports the encoder plus feature projectors instead of calling the full detector and raising `AttributeError`. (#1438)
- Opt-in CUDA-graph training replay for single-GPU detection training, landed across three PRs:
    - `ModelConfig.cuda_graphs=True` captures each static input shape's forward and backward once and replays it on later batches (BF16 only; segmentation, keypoints, non-BF16/FP8 precision, distributed runs and gradient checkpointing stay eager with a warning, and a failed capture stops training with a process-restart instruction rather than retrying in a damaged CUDA context). On an NVIDIA L4 (RF-DETR Nano, batch 4, 8-resolution multi-scale set), the median training batch fell from 149.8 ms eager to 101.7 ms graphed — a 32.4% median reduction across 5 paired runs, all 8 signatures capturing every run. Peak allocated CUDA memory rose from 2,395 MiB to 3,466 MiB and peak reserved memory from 2,754 MiB to 14,550 MiB for the 8 graph pools (one per resolution). On real data at the other end of the range (Nano, RTX PRO 6000, batch 64, `multi_scale=False`, COCO train2017), graph replay matched eager at 3.54 it/s while `compile=True` cut the epoch from ~8m40s to ~7m10s — graphs remove launch gaps and pay off at small batch, compilation fuses kernels and pays off at large batch. (#1468)
    - Combinable with `compile=True`: Inductor's `triton.cudagraphs` option records and replays the compiled kernels, single-GPU detection only, no gradient accumulation (segmentation, keypoints, `grad_accum_steps > 1` and multi-device fall back to compile-only). On an RTX PRO 6000 (Nano, BF16), 1.20x faster than compile-alone at batch 4, matching it at batch 64; an A100 gave 1.70x at batch 4. `amp_dtype="fp8"` with both flags stays compile-only — the two capture runtimes are never nested. (#1479)
    - A Transformer Engine FP8 capture route (`amp_dtype="fp8"`, `compile=False`) captures the fixed-shape training step through `make_graphed_callables` under the active Lightning FP8 recipe, with gradients cloned before Transformer Engine reclaims its buffers; needs Transformer Engine 2.19+ (`clone_param_grads_on_return`), an older release raises rather than silently falling back. Scope: single-GPU, `grad_accum_steps=1`, `multi_scale=False`, `square_resize_div_64=True`. Synthetic RF-DETR Nano step on an RTX PRO 6000 Blackwell: 78.075 ms eager vs. 40.066 ms graphed (~1.95x), one capture serving 70 calls — a single fixed-shape synthetic measurement, not COCO parity or large-batch evidence. (#1481)
- FP8 training precision — `amp_dtype="fp8"` reaches Transformer Engine's hardware FP8 path directly, independent of the CUDA-graph capture route above. (#1431)
- `TrainConfig.eval_backend` selects the COCO evaluator used for validation/test mAP, gaining two more values on top of the existing `"hotcoco"` (default) and `"faster_coco_eval"`:
    - `"ufcoco"`, selecting [ultrafast-pycocotools](https://github.com/developer0hye/ultrafast-pycocotools) (Rust, BSD-2-Clause, `numpy`-only runtime dependency) that reproduces pycocotools' precision, recall and score arrays byte for byte; parity tests hold it to exact equality against `faster_coco_eval` for box-only and box-plus-mask evaluation at `eval_max_dets` 100 and 500. (#1449)
    - `"vernier"`, selecting [vernier](https://github.com/NoeFontana/vernier) (Rust, MIT OR Apache-2.0), run in its `corrected` parity mode, which corrects the same pycocotools quirks as `hotcoco` and `faster_coco_eval`. (#1491)
- `dataset_file="webdataset"` streams training input from pre-packed `.tar` shards instead of opening one file per image, with `python -m rfdetr.cli.webdataset` to pack a COCO split into ~100 MB shards and a small JSON index; packing needs no extra dependency, reading installs with `[data]`. Verified tensor-for-tensor identical to the source directory on 500 real COCO images (pixels, boxes, labels, label space). Training refuses to start when `world_size × num_workers` exceeds the shard count, and now raises (not just warns) past 30% shard-split skew. On a 32-vCPU NVMe instance, streaming throughput matched the loose-file loaders across six configurations (0.92x-1.03x) and construction was far faster (median 19.9s to 0.012s per DDP rank) since the annotation file is never parsed — packing pays off where per-file access is the real constraint, not raw loader throughput. Accuracy isn't established either way: over five seeds of a 3-epoch fine-tune, streaming trailed map-style by ~0.011 mAP@50:95 at 39 shards and matched it at 290, with more seed-to-seed spread in both. Detection/segmentation only — keypoint rejects the format, since its label space needs a fully parsed COCO file a shard index doesn't carry. (#1396)
- `TrainConfig.pad_targets_to` pads every image's targets to a fixed row count so the detection loss keeps one tensor shape across batches, avoiding per-batch XLA recompilation on TPU driven by the ground-truth box count (issue #1433: a new count cost ~47s and four fresh compilations vs. 0.18s repeated, never reaching steady state on realistic data). On a Cloud TPU v6e-1, this cut compiles/time from 78/1453.9s unpadded to 9/159.0s median padded. Requires `augmentation_backend` off Kornia/GPU; excess boxes past the cap are dropped with a warning; applies to the training dataloader only. Semantically transparent — padded rows carry no cost in matching or loss (masked); the unmasked classification branches, segmentation mask loss and keypoint loss raise rather than silently reporting a wrong loss. (#1450)
- `RFDETR.inference(compile_backend="inductor")` — opt-in Inductor backend for long-running inference at a fixed batch size and resolution, alongside the existing default TorchScript path. (#1425)
- `simplejpeg` JPEG decode (libjpeg-turbo straight into a NumPy buffer) for `CocoDetection` / `YoloDetection` / WebDataset readers when installed (now part of `[train]`) — same pixels as Pillow on tested wheels, though `simplejpeg` bundles its own libjpeg-turbo copy so small rounding differences are possible. Measured up to 1.8x faster on smooth content and 1.3x on detailed content at the decode stage for a reader that consumes the array directly (i7-8750H: 640x480 2.38ms to 1.30ms, 1920x1440 21.19ms to 11.78ms); consumers that wrap the array back into a PIL image see a smaller or hardware-dependent net gain. Falls back to Pillow for non-JPEG or rejected files, with the same decompression-bomb limit enforced. (#1473)
- CoreML Apple-Neural-Engine fallback boundary documented — how to choose `compute_units`, why fp32 never reaches the ANE, and the measured latency per compute unit. (#1495)

### ⚠️ Breaking Changes

- Export internals restructured into one `Exporter` class per format, each with its own config dataclass, reached through a registry mapping format name to module. `RFDETR.export()`'s own signature, accepted formats and return value are unchanged, but the modules behind it moved: `rfdetr.export.main` and `rfdetr.export.protocols.ExporterProtocol` are removed, the per-format `export_*` functions are replaced by `*Exporter` classes, and `TensorRTExporter`/`TRTInference` move under `rfdetr.export._tensorrt` — see the Migration guide below for the full symbol table. Format-independent graph prep now runs once in `rfdetr.export.prepare`, and requesting a capability a format lacks (e.g. `dynamic_batch` on CoreML/ExecuTorch/OpenVINO) is now refused from registry data before the optional dependency imports. (#1448)
- `format="tensorrt"` with `backbone_only=True` and `output_name` now writes `{output_name}-backbone.trt` instead of `{output_name}.trt`, matching every other format and what `export()`'s own documentation already described — previously a backbone engine silently overwrote a full-detector engine exported under the same name. Scripts that rebuild the engine path from `output_name` need the suffix added. (#1448)
- `TrainConfig.multi_scale` is now a `MultiScale` enum (`rfdetr.config.MultiScale`), absorbing the removed `do_random_resize_via_padding` flag: `"per-batch"` (new default) draws one random scale per batch, `"per-sample"` draws a scale per sample inside the dataset transforms and pads at collate, `"off"` trains at a fixed resolution. Booleans are still accepted as input (`True`→`"per-batch"`, matching the old default; `False`→`"off"`) but the stored value is always the enum member; the old `do_random_resize_via_padding=True` becomes `multi_scale="per-sample"`, and `do_random_resize_via_padding` itself is now rejected as an unknown field. (#1485)

### 🌱 Changed

- `training_config.json` is now written when training starts, not only when it finishes, so a run killed mid-training still leaves a configuration record — the reproducibility guarantee the file existed for. (#1496)
- `HungarianMatcher` builds its L1 box cost with a broadcast reduction over the four box coordinates instead of `torch.cdist(..., p=1)`, which cannot exploit a 4-wide feature dimension on CUDA and was the matcher's single most expensive operator (15.3% of device time in a profiler trace on RF-DETR Medium). Bit-identical results (`torch.equal` holds against `torch.cdist` on every tested shape); on CUDA, `compile=True` also compiles this cost with dynamic shapes independently of model CUDA graphs. Microbenchmarked 15-29x faster on an RTX 5060 Ti; end-to-end training throughput on a real 3,000-image subset rose +15.0% (math-SDPA) and +5.4% (fused-attention). (#1486, #1488)
- `group_detr` two-stage selection now batches each group's projection/ranking/top-k/box-MLP work — on an L4 (Nano, BF16, batch 4), median steady-state step time fell 15.76% (141.13ms to 119.09ms) — and reuses `enc_out_class_embed`'s already-computed class logits instead of the caller re-running that same linear layer a second time per group. A separate 3-seed COCO-subset screen showed mixed mAP50-95 deltas — no accuracy claim. (#1482, #1489)
- Two DDP training-step-time reductions, composing to a median -10.56% full step on 2x L4:
    - `DDPStrategy` now also sets `static_graph=True` and `gradient_as_bucket_view=True`, skipping the per-iteration autograd-graph search `find_unused_parameters=True` otherwise repeats every step — safe because an empirical probe found only one constant unused parameter across real forward/backward runs (a DINOv2 mask token untouched in supervised training), not the data-dependent set the old flag comment assumed. Scope: automatic-optimization models with `grad_accum_steps<=1`. Alone: median -8.70% full step on 2x L4; gradients verified bit-identical under real NCCL. (#1489)
    - The batched two-stage top-k pass reuses its already-computed classification logits instead of the caller re-running `enc_out_class_embed` a second time per group. Alone: median -2.75% full step on 2x L4 (below this project's 3% bar); composes with the DDP-flags change above to the reliable -10.56% combined median. (#1489)
- CUDA segmentation matching now takes a compact per-image route once it can avoid at least 728,000 cross-image entries, instead of the full-cartesian one (BCE+Dice mask cost keeps the full-batch calculation); an imbalanced batch dominated by one image's targets stays on the full-cartesian path. On an L4 (RFDETRSegNano, BF16), batch 8 improved 7.98% and batch 20 improved 12.28%; batch 4 was neutral, below the gate. Peak CUDA memory unchanged. (#1480)
- XLA val/test/train-metric callbacks now materialize each model forward once before COCO metric code reads individual tensors on the host, instead of recompiling overlapping lazy-graph fragments. On a Cloud TPU v5e-1 (RFDETRNano), median fit time fell 25.2% (310.2s to 232.0s) and validation time fell 73.4% (105.8s to 28.1s); metrics and checkpoint tensors unchanged. (#1467)
- Multi-GPU keypoint training with `grad_accum_steps > 1` now syncs gradients once per optimizer step, not once per microbatch. (#1443)
- The ONNX Runtime CPU inference session built by the ONNX export's inference helper no longer lets its intra-op thread pool busy-spin between calls, freeing CPU for other work sharing the process. (#1456)
- `predict()` now batches each image's per-tensor GPU→CPU transfers into one non-blocking sync per image instead of up to six sequential blocking round-trips. (#1420)
- Progress-bar `max_mem`/`free_mem` values switch from whole MB to GB with one decimal above 1000 MB. (#1470)
- On Python 3.14+, the four `*_jit` loss helpers are plain aliases of their eager versions, since TorchScript is unsupported there; `import rfdetr` no longer warns about it on that version. (#1406)
- The `[coreml]` extra no longer pins `torch<2.12`, so `pip install rfdetr[coreml]` resolves current torch — a two-minor jump (2.11 to 2.14). The pin was attributed to a coremltools MIL lowering regression; investigation found the real cause: untrained-weight encoder query scores sit ~1e-6 apart (within legitimate fp32 eager-vs-CoreML rounding drift), so a near-tied pair swaps rank and self-attention spreads that into every logit — a property of the ranking, not the conversion. `e2e_coreml` parity exports now assert a query-gap-vs-drift margin before comparing raw tensors, instead of trusting it blindly. (#1487)
- Detection and segmentation COCO evaluation now runs on [hotcoco](https://github.com/derekallman/hotcoco) by default — a Rust evaluator under MIT, added to `[train]`. Reported metrics unchanged: parity tests require exact equality against the previous backend for box-only and box-plus-mask evaluation. `TrainConfig.eval_backend="faster_coco_eval"` restores the previous evaluator (still installed/required). `compute()` itself measured 6.2s to 1.1s on synthetic COCO-val-shaped state (macOS CPU), mostly because predictions are handed over as one detection array instead of per-annotation dictionaries. One hotcoco quirk is handled defensively: its `dataset` getter returns a copy, so in-place mutation is silently discarded — which would otherwise leak one IoU type's annotation areas into the other's size buckets. (#1402)
- Fixed-resolution (`multi_scale=False`) `compile=True` detection training now statically specialises the model and batches the default IA-BCE classification, box, GIoU and cardinality losses across final/auxiliary/encoder outputs into one lazily compiled tensor function; multi-scale, aspect-ratio resize, CUDA graphs, segmentation/keypoint and custom criteria keep their established paths. On an L4 (Nano, BF16, batch 4, 384px), median settled epoch fell 14.43% (3.225s to 2.759s); multi-GPU, EMA-on training, convergence and mAP not measured. (#1522)

### 🗑️ Deprecated

- `ModelConfig.amp` deprecated, removal in v1.14, superseded by `TrainConfig.amp_dtype`. (#1471)
- `TrainConfig.fp16_eval` deprecated, removal in v1.14, no runtime consumer. (#1471)

### ❌ Removed

- `RFDETR.optimize_for_inference()` (deprecated since 1.9.0) — use `RFDETR.inference()`. (#1500)
- `TrainConfig.lr_drop` and `lr_min_factor` (deprecated since 1.9.0) — use `lr_scheduler_kwargs`. (#1500)
- `rfdetr.datasets.synthetic` (whole module) — use [`fuse-augmentations`](https://github.com/Borda/fuse-augmentations). (#1435)

### 🔧 Fixed

- CoreML exports of keypoint models no longer invert the keypoint self-attention mask, which caused garbled or missing keypoint detections. `nn.MultiheadAttention` turns a boolean `attn_mask` into a float one via `zeros_like(mask, dtype=...)`, and coremltools (9.0, 9.1) drops that `dtype` keyword during `torch.export` conversion, so the mask applied with the opposite meaning — with multiple keypoint classes, cross-class logits diverged from eager by up to 1.19; with one class every logit got a -3e4 bias, and float16 found 0/11 detections on `CPU_ONLY` (now finds all 11). The decoder now passes the additive float mask directly; eager outputs unchanged. Fixed upstream in apple/coremltools#2865. (#1520)
- `format="tflite"` now converts keypoint models instead of failing with `onnx2tf`'s `Dimensions must be equal, but are 17 and 100` on the keypoint decode (a rank-4 tensor broadcast over the keypoint axis). The decode now runs on a flattened `(..., K * 2)` layout with the same result; FP32 `.tflite` matches ONNX within `3.2e-3` on every output over all 100 queries. (#1518)
- `TrainConfig.eval_backend="vernier"` no longer disagrees with the other backends on `map_small`/`map_medium`/`map_large` bucketing for a combined `bbox`+`segm` run — the old code bucketed box AP by box area while `hotcoco`/`faster_coco_eval` bucket by mask area, so results disagreed for objects straddling the `32²`/`96²` size thresholds. Ground-truth `area` now always derives from the mask when `segm` is requested. Now converts through vernier's own columnar `coco_inputs_from_columns` (vernier 0.5.2+) instead of ~210 lines of hand-rolled conversion, ~2-10% faster `compute()`. (#1512)
- Albumentations `OneOf`/`Sequential` transform containers now respect an explicit container-level `p` instead of always forcing `p=1.0` regardless of what was configured. (#1515)
- `last.ckpt` and `checkpoint_<epoch>.ckpt` are now written every due epoch regardless of `eval_interval` — both `ModelCheckpoint` callbacks previously saved only from `on_validation_end`, so with `eval_interval=2` the resume checkpoint refreshed only every other epoch and a crash between validations lost up to `eval_interval - 1` epochs of progress (e.g. `checkpoint_interval=3` with `eval_interval=2` never archived epoch 2 or 8). Both callbacks now save at train epoch end, still carrying that epoch's validation results. (#1510)
- Multi-GPU validation/test metrics no longer double-count the images Lightning's `DistributedSampler` repeats to pad a split to a multiple of `world_size` — up to `world_size - 1` images were scored twice (a 21-image split over 2 GPUs held 22 in the merged mAP state). `COCOEvalCallback` now reads the sampler's own rank/replica/total-size info and skips accumulation past the dataset length. Measured: 2-GPU `val/mAR`/`val/mAP_50_95` now match the 1-GPU values exactly (were 0.1286/0.0286 vs 0.1299/0.0287). (#1508)
- `run_test=True` no longer hangs multi-GPU training at the end of `fit` — `trainer.test()` was called from the main process alone under `strategy="ddp"`'s collective test loop while other ranks had already left `fit`, so the run never finished (reproduced: rank 0 still waiting at 600s). Every rank now enters `trainer.test()`, with file work and the test-or-not decision still made and broadcast from the main process. Spawn-based launchers (`ddp_spawn`/`ddp_notebook`), which can't host a nested `trainer.test()`, now warn and skip the fit-end test instead of crashing with `DistNetworkError: EADDRINUSE`. (#1506)
- Detection/segmentation training with `grad_accum_steps > 1` no longer divides the loss by the accumulation count twice — `training_step` divided by `accumulate_grad_batches` on top of Lightning's own division, so the optimizer saw `1/N` of the mean gradient instead of the mean (verified: accumulated gradient was 0.5 at N=2, 0.25 at N=4, where it should be 1.0). AdamW is largely scale-invariant, but `clip_max_norm` engaged N times less often and SGD-family optimizers trained at an effective `lr / N`; `batch_size="auto"` selects `grad_accum_steps > 1` on its own, so unrequested runs were affected too. Introduced in 1.8.0. (#1504)
- `python -m rfdetr.export.benchmark` no longer crashes with `AttributeError: 'str' object has no attribute 'dataset'` when COCO evaluation is enabled — `main()` passed the raw annotation path to `CocoEvaluator` instead of a loaded `COCO` object; `--disable_eval` was unaffected throughout. (#1502)
- `MultiScaleProjector` no longer crashes with `IndexError` on `feat_fuse_list[0]` for `scale_factors=0.25` — an `elif continue` only broke the inner per-channel loop, leaving an empty pyramid stage built on top of nothing. The `scale==0.25` check now skips the whole scale up front, matching its intent as an extra max-pool marker. (#1498)
- Empty `gt_boxes`/`pred_boxes` lists no longer crash the GT/pred visualization saver with `IndexError` — an empty list produced a 1-D array that `xywh_to_xyxy` indexed as 2-D; `reshape(-1, 4)` now keeps it a well-formed `(0, 4)` array so the existing no-op guards actually run. (#1497)
- fp16 native CoreML exports of an untrained model now compile for the Apple Neural Engine instead of silently falling back to CPU-only — the ANE compiler rejected the whole program when the pre-`topk` norm still carried its identity affine (untrained weights) and the encoder token count was a multiple of 32, so the model ran CPU-only under `CPU_AND_NE` and failed to load under the default `ALL`. Trained checkpoints were never affected. The selection now gathers pre-norm rows and normalizes only the selected tokens (same computation, LayerNorm acts per token); eager outputs unchanged, mAP unchanged within noise. (#1495)
- Multi-device TPU/XLA training now disables EMA with a runtime warning, since per-step EMA weight reads can corrupt subsequent optimizer updates on multiple TPU cores under BF16 — single-device XLA keeps EMA (verified over 100-350 updates on a Cloud TPU v6e-1 with finite tensors). CPU and CUDA EMA unaffected. (#1465, #1476, #1494)
- Explicit and `auto` `amp_dtype="bf16"` now select XLA's `bf16-true` precision instead of silently falling back to FP32 on TPU. (#1469)
- Segmentation `_compute_mask_iou` now converts its bool→float32 masks 32 rows at a time instead of materializing every matched prediction and ground truth of a class at once (`N x H x W` / `M x H x W` at full resolution) — a densely annotated class could make both large enough to OOM. Output is bit-identical for nonzero counts, and a zero count on either side now returns an empty result instead of raising `RuntimeError`. (#1463)
- `RFDETR.export(format="tensorrt", fp16=True)` now actually builds an FP16 engine on strongly-typed TensorRT 11+ by casting the ONNX graph to FP16 first (`[tensorrt]` now also pulls `onnx` and `onnxconverter-common`, and raises `ImportError` if missing rather than silently building FP32) — previously it silently fell back to an FP32 engine reported as FP16. (#1454)
- Compiled training with `torch.compile(..., dynamic=True)` no longer silently falls back whole forward frames to eager — `triton.coalesce_tiling_analysis` is unsupported on the dynamic-shape path but still ran, hitting a size-hint assert with no symbolic-shape escape. Now disabled before the compile call. (#1455)
- The export-mode switch is now idempotent — calling it a second time in the same state no longer raises. (#1445)
- Distributed (DDP) training now preserves the minimum five optimizer steps per epoch for short datasets instead of losing the replacement sample count when Lightning injects its distributed sampler. (#1447)
- Kornia `Affine` now applies a scalar `translate_percent` to both axes and reads a scalar `scale` as a fixed range, avoiding silent horizontal-translation loss and construction failures. Scalar translation now emits a warning, since Kornia samples signed offsets where Albumentations applies a fixed positive offset. (#1444)
- TFLite INT8 documentation and warnings now reflect that dynamic-range quantization needs no calibration data. (#1364)

## 🏆 Contributors

- **Jesús Royeth** (@JESUSROYETH) — opt-in CUDA-graph training replay, combinable with `compile=True` and paired with a Transformer Engine FP8 capture route for single-GPU detection training; streaming training input directly from WebDataset tar shards; fixed EMA under XLA so multi-device XLA disables it with a warning while single-device XLA keeps it; batched fixed-resolution `compile=True` training losses into one compiled function.
- **Jirka Borovec** (@Borda, [LinkedIn](https://linkedin.com/in/jirka-borovec)) — removed the deprecated `optimize_for_inference()` method, the deprecated `lr_drop`/`lr_min_factor` training fields, and the standalone synthetic-dataset generator module; restructured export internals into one `Exporter` class plus a frozen config dataclass per format behind a registry; added FP8 training precision; made `hotcoco` the default COCO evaluation backend; skipped flaky spawn-based DDP tests on macOS CI.
- **Raghav Rathi** (@raghav-rathi) — fixed checkpoint-writing cadence so `last.ckpt` and interval checkpoints are written every due epoch regardless of `eval_interval`; fixed multi-GPU validation/test metrics to skip `DistributedSampler` padding duplicates; fixed `run_test=True` hanging at the end of `fit` under DDP; fixed loss being divided by `accumulate_grad_batches` twice.
- **Lương Thanh Hoàng Phú** (@lthphuw, [LinkedIn](https://linkedin.com/in/phu-lth)) — fixed fp16 native CoreML exports to compile onto the Apple Neural Engine and documented the ANE fallback boundary; `training_config.json` now written when training starts and rewritten at finish; dropped the `torch<2.12` pin on the `[coreml]` extra and made end-to-end parity checks tie-robust; fixed `format="tflite"` to convert keypoint models.
- **Anna Gubenkova** (@avbelova) — added OpenVINO IR as a new export format, including a configurable export precision.
- **Daisuke Majima (MLBoy)** (@john-rocky, [LinkedIn](https://www.linkedin.com/in/daisuke-majima-43a637154/)) — added LiteRT export via litert-torch as a new export format.
- **Yonghye Kwon** (@developer0hye, [LinkedIn](https://www.linkedin.com/in/yonghye-kwon-91641a174/)) — added `ufcoco` (ultrafast-pycocotools) as a selectable COCO evaluation backend.
- **Noé Fontana** (@NoeFontana, [LinkedIn](https://www.linkedin.com/in/noe-fontana/)) — added `vernier` as a selectable COCO evaluation backend; refactored it onto vernier's own columnar state adapter, fixing a bbox/segm area-bucketing mismatch and speeding up `compute()`.
- **Tuan Nguyen** (@tuan3w, [LinkedIn](https://www.linkedin.com/in/tuan3w/)) — rebuilt the matcher's L1 box cost as a broadcast reduction instead of `torch.cdist`, later compiled together with the model on CUDA.
- **Tuncay BAHADIR** (@tuncaybahadir, [LinkedIn](https://www.linkedin.com/in/tuncaybahadir)) — batched `predict()`'s per-image GPU-to-CPU tensor transfers into a single sync.
- **Vitor Toledo** (@ToledoVitor, [LinkedIn](https://www.linkedin.com/in/vitorstoledo/)) — added weekly-metrics SVG automation for the project's CI/docs.
- **Christoph Deil** (@cdeil, [LinkedIn](https://www.linkedin.com/in/christophdeil)) — made the eager loss helpers the sole implementation on Python 3.14+, where TorchScript JIT is unsupported; added Apple Core AI export as a new format; fixed CoreML keypoint exports to stop inverting the keypoint self-attention mask.
- **David Igech** (@davidiglesias) — added `simplejpeg`-based JPEG decoding for the CocoDetection, YoloDetection, and WebDataset readers.
- **Aman Harsh** (@amanharshx) — corrected the TFLite INT8 documentation and warnings to state that dynamic-range quantization needs no calibration data.

*This release also includes automated maintenance from `Copilot` (bot-authored PRs) and `pre-commit-ci[bot]` (dependency autoupdate), plus AI pair-programming co-authorship on several human-authored commits (Claude, Codex).*

---

**Full changelog**: https://github.com/roboflow/rf-detr/compare/1.10.1...v1.11.0

Related Repositories