Skip to content

Benchmarks

Comparison of vernier against the third-party libraries it targets parity against, on a single machine and a single git revision. The numbers below are the median total-stage wall time over the non-warmup reps recorded by the local bench harness (ADR-0017, extended cross-paradigm in ADR-0033). The IQR column reports the spread (Q3 - Q1) across the 10 measurement reps and the same value as a percentage of the median; release mode gates each cell at 5% relative IQR.

Provenance — git SHA 3a509df6c525 · machine fingerprint 37652a58e939 · CPU AMD EPYC-Milan Processor (x86_64) · harness mode release · build profile = cargo release defaults (opt-level=3, lto=thin, codegen-units=1, no target-cpu). The release wheel on PyPI is built with the same profile — no benchmarking-only flags.

Baselines pinned for these numbersfaster-coco-eval==1.7.2 · pycocotools==2.0.11 · boundary-iou-api @ 37d2558 · panopticapi @ 7bb4655 · mmsegmentation @ c685fe6 · lvis-api @ 031ac21. Each baseline is locked in its own uv-managed venv per ADR-0017.

For the full per-cell deep-dive (per-stage breakdown, RSS evolution, parity gating, narrative on what moved each round), see docs/engineering/benchmarking/.

This page is regenerated from the harness result tree by tools/render_benchmarks.py. To refresh after a new bench run, see the release runbook §0.

Instance — bbox / segm / boundary / keypoints (AP)

Workload: coco_val2017_jittered_seed0

bbox

impl median IQR RSS (max) vs vernier
vernier 369.6 ms 13.1 ms (3.54%) 262 MiB 1.00×
faster-coco-eval 2.132 s 75.3 ms (3.53%) 661 MiB 5.77×
pycocotools 5.930 s 72.5 ms (1.22%) 576 MiB 16.04×

segm

impl median IQR RSS (max) vs vernier
vernier 987.4 ms 18.0 ms (1.83%) 263 MiB 1.00×
faster-coco-eval 3.631 s 64.2 ms (1.77%) 721 MiB 3.68×
pycocotools 6.870 s 54.7 ms (0.80%) 569 MiB 6.96×

boundary

impl median IQR RSS (max) vs vernier
vernier 3.208 s 34.3 ms (1.07%) 265 MiB 1.00×
faster-coco-eval 17.747 s 268.9 ms (1.52%) 794 MiB 5.53×
boundary-iou-api 61.749 s 1.098 s (1.78%) 666 MiB 19.25×

Workload: coco_val2017_keypoints_jittered_seed0

keypoints

impl median IQR RSS (max) vs vernier
vernier 136.0 ms 1.8 ms (1.31%) 128 MiB 1.00×
faster-coco-eval 1.671 s 14.8 ms (0.89%) 154 MiB 12.28×
pycocotools 2.276 s 4.9 ms (0.22%) 163 MiB 16.74×

Panoptic — PQ

Workload: coco_panoptic_val2017_perfect

pq

impl median IQR RSS (max) vs vernier
vernier 10.528 s 1.029 s (9.78%) * 144 MiB 1.00×
panopticapi 34.711 s 136.6 ms (0.39%) 146 MiB 3.30×

Semantic — mIoU

Workload: coco_val2017_semantic_perfect

miou

impl median IQR RSS (max) vs vernier
vernier 2.822 s 11.0 ms (0.39%) 97 MiB 1.00×
mmsegmentation 20.894 s 199.4 ms (0.95%) 647 MiB 7.40×

Workload: synthetic_semantic_n200_c19_s0

miou

impl median IQR RSS (max) vs vernier
vernier 63.8 ms 674.1 μs (1.06%) 88 MiB 1.00×
mmsegmentation 442.8 ms 50.1 ms (11.33%) * 631 MiB 6.94×

Instance — LVIS federated AP

Workload: lvis_v1_val_perfect

bbox

impl median IQR RSS (max) vs vernier
vernier 3.625 s 23.4 ms (0.65%) 1.48 GiB 1.00×
lvis-api 207.370 s 2.867 s (1.38%) 15.01 GiB 57.21×

Cells marked * next to their IQR exceeded the release-mode 5% relative-IQR gate. Median still reported; treat the gap to the next impl as the load-bearing signal rather than the precise ratio.

Threading scaling

ADR-0047 adds an opt-in num_threads kwarg on every public evaluate surface (batch, streaming, background) for all four paradigms — instance (bbox / segm / boundary / keypoints), semantic, panoptic, and lvis. The default num_threads=None is byte-for-byte the sequential post-0.0.4 path; no rayon symbol is entered.

Wall-clock numbers below are the evaluate-stage stage timer on COCO val2017 perfect-DT (the jittered-seed-0 workload for the instance paradigm; coco_panoptic_val2017_perfect for panoptic; coco_val2017_semantic_perfect for semantic). Hardware: AMD EPYC Milan, 4 physical cores + SMT-2 (8 vCPUs total). Dev-mode single rep per cell; expect ±10–20% noise on small workloads. Note: these numbers were recorded at 0.0.4 ship time, before the kernel-level perf wins in #260 (panoptic sparse-remap cache) and #261 (semantic chunked-u8 kernel). Scaling factors (ratio nt=N / nt=1) stay representative because both the kernel and its parallel dispatch moved by the same multiplier on the cold-cache path; absolute medians for panoptic and semantic-val2017 are ~17% and ~44% lower respectively at num_threads=None on the current HEAD — see the cross-paradigm tables above for the post-perf-round headline numbers.

Instance (val2017 jittered-seed-0)

iou_type nt=1 nt=2 nt=4 nt=8 scaling at nt=4
bbox 280 ms 296 ms 285 ms 291 ms ~flat (parse-bound)
segm 890 ms 570 ms 389 ms 346 ms 2.29×
boundary 3146 ms 1700 ms 969 ms 864 ms 3.25×

Bbox is parse-bound: the bytes-path GT JSON parse dominates the evaluate stage (the bench-timings split records ~85 ms parse + ~30 ms DT parse on val2017 — together ~45% of the nt=4 total). The rayon par_iter region itself scales 3.2× at nt=8; the apparent flatness is the single-threaded JSON parse and FFI overhead, not a scheduling problem. Segm and boundary scale near-linearly on 4 physical cores after the ThreadLocal<RefCell<Scratch>> + image-major dispatch fixes.

Repeated-eval speedup (out of band). Callers running multiple evaluations against the same GT can pre-parse once via CocoDataset.from_json(gt_bytes) and dispatch through the Evaluator.evaluate(dataset, dt) path (which routes to evaluate_*_grid_with_dataset internally). On val2017 bbox nt=4 this drops a follow-up call from ~250 ms to ~140 ms — a 1.8× speedup, amortizing the ~95 ms one-time parse. The table above deliberately re-parses every call so the comparison against faster-coco-eval below stays apples-to-apples.

Semantic (val2017 perfect-DT, ~5000 images)

nt=1 nt=2 nt=4 nt=8
5024 ms 2699 ms 1372 ms 936 ms

3.66× at nt=4 on 4 physical cores, 5.37× at nt=8 with SMT. The per-image confusion-matrix fold is u64-additive so strict-mode bit-equality is unconditional regardless of reduction order.

Panoptic (val2017 perfect-DT, BackgroundPanopticEvaluator)

nt=1 nt=2 nt=4 nt=8
12850 ms 8156 ms 5280 ms 4479 ms

2.43× at nt=4. The threaded path runs PNG decode inside the per-worker rayon pool (zero-copy via PyBackedBytes), so libpng parallelises across submissions; the single-threaded path keeps producer/consumer overlap against the worker thread and is byte-for-byte unchanged from 0.0.4.

vs faster-coco-eval (boundary at nt={1,4,8})

faster-coco-eval exposes parallelism only on boundary IoU (boundary_cpu_count); bbox / segm / keypoints stay single-threaded.

impl nt=1 nt=4 nt=8
vernier 3146 ms 969 ms 864 ms
faster-coco-eval 49500 ms 17206 ms 15654 ms
vernier advantage 15.8× 17.2× 18.1×

Reproduce

# Instance paradigm sweep across thread counts on val2017
just bench-run --impl vernier --workload coco_val2017_jittered_seed0 \
    --iou boundary --num-threads "1,2,4,8" --no-parity

# Plumbing smoke (small synthetic fixture, no external data)
just bench-threads-smoke

Result JSONs land under bench/results/<sha>/<fp>/<paradigm>/<workload>_t<N>/<metric>/<impl>.json.

Methodology in one paragraph

Every cell runs in its own subprocess with its own uv-managed venv (one per impl), so a single Python process never has competing pycocotools-flavored packages on its sys.path. The harness records (load, evaluate, accumulate, summarize, total) wall_ns per stage, discards the warmup reps, and reports the median total plus the inter-quartile range (IQR = Q3 - Q1, with the relative spread shown as a percentage of the median). Release mode (N=10 + 2 warmup) gates each impl on relative IQR ≤ 5%; cells where the gate failed are marked with * next to their IQR value — the median is still the best estimator, just with a wider confidence band than the gate accepts. Parity is a side effect of every timing run — strict-tier (vs pycocotools) and aligned-tier (vs faster-coco-eval) where applicable; failed parity fails the cell. Memory is getrusage(RUSAGE_CHILDREN).ru_maxrss, high-water-marked across the rep set.