Skip to content

v1.9.2: Fixed Roboflow COCO & perf. optim

Choose a tag to compare

@Borda Borda released this 11 Aug 12:32
· 146 commits to release/latest since this release

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.

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:

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 coverageseed_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. (#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) — 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) — hierarchical COCO label-filtering fix and release maintenance

Full changelog: 1.9.1...1.9.2