Skip to content

perf(evaluate): narrow segm RLE cache to both-non-empty cells - #20

Merged
derekallman merged 1 commit into
derekallman:mainfrom
Borda:perf/F
Sep 26, 2026
Merged

derekallman merged 1 commit into
derekallman:mainfrom
Borda:perf/F

Conversation

@Borda

@Borda Borda commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

This pull request optimizes how segmentation masks are converted to RLE during evaluation, resulting in significant speed and memory improvements for segmentation runs. The main change ensures that only masks from detection/ground-truth pairs sharing a category within an image are converted, rather than all masks in scope. This avoids unnecessary work, especially in workloads where most detections have no matching ground truth in their category. The change is fully backward-compatible for results, and bounding-box evaluation is unaffected.

Segmentation mask conversion optimization:

  • Only convert segmentation masks to RLE for detections and ground truth that share a category within an image, instead of all in-scope masks. This greatly reduces unnecessary conversions and improves segmentation evaluation speed by 58–60% while lowering memory usage, with no change to results. [1] [2] [3] [4] [5]

Testing and validation:

  • Added a regression test (segm_rle_cache_skips_dt_only_cells) to ensure that only relevant detection IDs are included in the RLE cache, and that detections in categories with no matching ground truth are excluded.

Documentation and changelog:

  • Updated documentation and changelog to reflect the new, more efficient mask conversion behavior and its impact on performance and memory usage. [1] [2] [3]

Other:

  • Minor cleanup in the changelog formatting.

  • SegmRles::prepare now takes explicit gt_ids/dt_ids instead of deriving its own scope from params, and only converts masks for (img, cat) cells where both GT and DT are present
  • new COCOeval::segm_cell_ann_ids walks the sparse_pairs cells and selects the both-non-empty subset via the same get_anns_static lookup compute_iou_static uses, so use_cats=false, LVIS neg_cats, and Open Images hierarchy expansion all narrow correctly with no mode-specific branch
  • a cell with only GT or only DT never reaches compute_iou_static's mask read, so converting its side was wasted work; on a 1.5M-DT, 80-category RF-DETR-shaped workload 90.7% of detections sit in a category with no matching GT in their image
  • evaluate() on a segm run is 58-60% faster at both 2 and 16 threads, agreeing across thread counts; peak RSS during that phase down ~2.5 GB
  • confusion_matrix()/tide() still read the same cache and correctly fall back to converting on the spot for an excluded id (documented cost shift, not a regression: nothing tests that combination today)
  • 4/4 bit-identical on a new segm dump/compare harness (two workload sizes, varied maxDets) plus 10/10 on the existing bbox baseline, confirming no cross-talk with the untouched bbox path
  • new test segm_rle_cache_skips_dt_only_cells fails if the both-non-empty gate is dropped

- SegmRles::prepare now takes explicit gt_ids/dt_ids instead of
  deriving its own scope from params, and only converts masks for
  (img, cat) cells where both GT and DT are present
- new COCOeval::segm_cell_ann_ids walks the sparse_pairs cells and
  selects the both-non-empty subset via the same get_anns_static
  lookup compute_iou_static uses, so use_cats=false, LVIS neg_cats,
  and Open Images hierarchy expansion all narrow correctly with no
  mode-specific branch
- a cell with only GT or only DT never reaches compute_iou_static's
  mask read, so converting its side was wasted work; on a 1.5M-DT,
  80-category RF-DETR-shaped workload 90.7% of detections sit in a
  category with no matching GT in their image
- evaluate() on a segm run is 58-60% faster at both 2 and 16 threads,
  agreeing across thread counts; peak RSS during that phase down
  ~2.5 GB
- confusion_matrix()/tide() still read the same cache and correctly
  fall back to converting on the spot for an excluded id (documented
  cost shift, not a regression: nothing tests that combination today)
- 4/4 bit-identical on a new segm dump/compare harness (two workload
  sizes, varied maxDets) plus 10/10 on the existing bbox baseline,
  confirming no cross-talk with the untouched bbox path
- new test segm_rle_cache_skips_dt_only_cells fails if the
  both-non-empty gate is dropped
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@derekallman
derekallman merged commit f96d28b into derekallman:main Sep 26, 2026
6 checks passed
@Borda
Borda deleted the perf/F branch September 26, 2026 08:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants