1.9.2
roboflow/rf-detr1.9.2Aug 11, 2026by Borda
AI Summary
A fix-and-performance release introducing a breaking change for hierarchical COCO datasets. It significantly speeds up the HungarianMatcher and restores callback state during training resume.
Key Highlights
- Breaking: Hierarchical COCO datasets now get a correct, shared label mapping
- HungarianMatcher is ~51% faster and uses ~73-76% less peak CUDA memory
- Training resume from lightweight checkpoints now restores per-callback state
Breaking Changes
- Hierarchical COCO label filtering: Unannotated grouping categories no longer consume a label slot
New Features
- Faster and lower-memory HungarianMatcher
- Training resume restores per-callback state
Full Release Notes
RF-DETR 1.9.2 is a fix-and-performance release: one breaking change, no new public APIs. Hierarchical COCO datasets — most commonly Roboflow exports carrying a synthetic unannotated grouping category — now get a correct, shared label mapping across `train`/`valid`/`test`, closing a case where per-class metrics could silently corrupt on the smaller split. The matcher's compact cost-matrix path is faster and lower-memory on real batches, training-resume from `BestModelCallback`'s lightweight checkpoints now restores per-callback state instead of restarting cold, and a handful of training/eval fixes round it out.
## ✨ Spotlights / highlights
### Breaking: hierarchical COCO datasets get a correct, shared label space
Roboflow COCO exports prepend a synthetic root category (unannotated, `supercategory: "none"`) that every real class lists as its own `supercategory`. It never carried annotations but still consumed a label slot — training such a dataset built an *N+1*-class head instead of *N*.
```python
from rfdetr.datasets.coco import filter_parent_categories, annotated_category_ids
categories = [
{"id": 0, "name": "project-root", "supercategory": "none"},
{"id": 1, "name": "car", "supercategory": "project-root"},
{"id": 2, "name": "truck", "supercategory": "project-root"},
]
anns = {"annotations": [{"category_id": 1}, {"category_id": 2}]}
kept = filter_parent_categories(categories, annotated_category_ids(anns))
[c["name"] for c in kept]
# -> ['car', 'truck'] — the unannotated root no longer takes a label slot
```
For datasets where a *parent* category does carry its own annotations, label indices are now derived once from the `train` split and shared into `valid`/`test`, so a grouping category annotated in one split but not another no longer shifts that split's label indices independently of the others.
> ⚠️ **Checkpoints trained before 1.9.2 keep their old head width and label ordering.** Evaluating one against a re-filtered dataset misaligns per-class metrics (an existing `UserWarning` fires). See the migration notes below.
### Matcher: faster and lower-memory on real COCO batches
`HungarianMatcher`'s detection-only cost matrix is now built padded to each batch's `max(T_i)` target count instead of the cross-image `sum(T_i)`, when a fast eligibility check passes — identical results on eligible batches, automatic full-computation fallback otherwise.
| | before | after | change |
| -------------------- | ---------- | ---------- | ------------- |
| Matcher time | — | — | ~51% faster |
| Peak CUDA memory | — | — | ~73–76% lower |
| Training step (A100) | 288.364 ms | 232.457 ms | — |
The saving scales with target-count evenness across a batch — a batch where nearly all targets sit in one image sees little to no improvement.
### Training resume restores per-callback state
Resuming from one of `BestModelCallback`'s four lightweight checkpoints (`checkpoint_best_regular.pth`, `checkpoint_best_ema.pth`, `checkpoint_best_total.pth`, `last_ema.pth`) previously restarted best-score tracking, EMA, and early-stopping cold, silently. It now restores that state:
```python
model.train(
dataset_dir="my_dataset",
resume="output/checkpoint_best_ema.pth",
output_dir="output", # must match the original run for best-score restore
)
```
## 📝 Notable changes
### 🌱 Changed
- **`HungarianMatcher` compact-path speedup** — detection-only cost matrix padded to `max(T_i)` instead of `sum(T_i)`; ~51% faster matcher, ~73–76% lower peak CUDA memory on real COCO batches. Compact path also copies only diagonal cost blocks to CPU and batches its safety-gate sweeps into one sync. (#1297, #1281, #1312)
- **Deterministic-algorithm coverage** — `seed_all()` now also enables `torch.use_deterministic_algorithms(True, warn_only=True)`; ops without a deterministic kernel warn at execution time instead of raising. (#1307)
- **`predict()`** — pins CPU image tensors before the CUDA transfer. (#1313)
- **Two-stage query selection** — avoids materialising repeated top-k gather indices. (#1278)
- **Evaluation matching** — counts labels on host instead of device. (#1276)
- **Keypoint postprocessing** — skips redundant CUDA presence checks. (#1282)
### 🔧 Fixed
- **`BestModelCallback` resume** — resuming from a lightweight checkpoint (`checkpoint_best_*.pth`, `last_ema.pth`) now restores per-callback state instead of restarting best-score tracking, EMA, and early-stopping cold; a warning distinguishes checkpoints that intentionally omit optimizer state from ones that predate callback-state persistence entirely. (#1318)
- **Rich epoch progress bar** — no longer corrupted or duplicated by training-time log calls under `RichProgressBar(leave=True)`. (#1316)
- **`_kp_active_mask` false-positive warning** — loading a pre-keypoint-support checkpoint no longer warns about a deterministic schema buffer as a missing parameter. (#1302)
- **Deferred CUDA-device move** — an index-less `torch.device("cuda")` is normalised to the current device index before comparison, fixing spurious re-moves of every parameter on every call. (#1311)
- **Legacy query-embedding fallback** — only warns when it actually truncates weights. (#1301)
- **`eval_ema_only`** — no longer logs an empty validation pass when the base metric is empty; EMA metrics (`val/ema_mAP_50_95`, `val/ema_mAP_50`, `val/ema_mAR`, per-class AP, `val (ema)` summary table) are computed instead, and `val/F1` is no longer silently dropped. (#1289)
- **`ModelContext.reinitialize_detection_head`** — raises a clear `RuntimeError` instead of `AttributeError: 'NoneType'` after `RFDETR.inference(inplace=True)` clears the weights. (#1283)
- **`evaluate()`** — builds its datamodule from the resolution-override config. (#1280)
### ⚠️ Breaking Changes
- **Hierarchical COCO label filtering** — unannotated grouping categories no longer consume a label slot; `train`/`valid`/`test` now share one label mapping derived from `train`. Checkpoints trained before this change need retraining against the new label space — see [Migration guide](MIGRATION.md). (#1303)
## 🏆 Contributors
- **@JESUSROYETH** — matcher perf (padded cost matrix, diagonal-only CPU copy, batched safety-gate sync), `BestModelCallback` resume-state restore, Rich progress-bar fix, `eval_ema_only` fix, and five other training/inference fixes
- **@atikulmunna** ([LinkedIn](https://www.linkedin.com/in/aimunna/)) — guarded `ModelContext.reinitialize_detection_head` against cleared weights; strict-typed `rfdetr.detr` and `datasets.yolo` module boundaries
- **@Jonathan-Jesni** — strict-typed `rfdetr.datasets.save_grids` module boundary
- **@Borda** ([LinkedIn](https://linkedin.com/in/jirka-borovec)) — hierarchical COCO label-filtering fix and release maintenance
---
**Full changelog**: https://github.com/roboflow/rf-detr/compare/1.9.1...1.9.2