forgesight / docs /benchmark-protocol.md
Ankit
Deploy ForgeSight to Hugging Face Spaces
1d0f191
|
Raw History Blame Contribute Delete
5.74 kB
# Benchmark protocol
Version 1. Every number in every report comes from a run of this procedure, and
each report embeds the environment the run happened in.
## Preconditions
The runner refuses by default when any of these hold:
- running on **battery** power
- **Low Power Mode** on
- free memory below **25%**
`--allow-unfavourable` takes a labelled measurement anyway. The report then
carries the refusal reason and says the numbers should not be compared with a
nominal run. This is a deliberate trade: on a machine that cannot be put on AC
power, a labelled measurement is more useful than nothing and an unlabelled one
is worse than either.
## Environment capture
Recorded with every run, and printed at the top of every report:
CPU brand and the performance/efficiency core split, physical and logical core
counts, total memory, macOS version, architecture, Python version, and the
versions of numpy, torch, torchvision, transformers, onnxruntime, onnx, Pillow,
OpenCV, reportlab, pypdfium2, pycocotools, psycopg and FastAPI. Plus the thread
environment variables, power source, Low Power Mode state, memory pressure
immediately before and after, the git sha, and a host fingerprint built from the
performance-relevant fields.
**Two runs are only ever compared when their manifests agree** on CPU, core
layout, memory, OS, architecture and library versions. A refusal to compare
states which field differed.
## Procedure
1. Native processes, one worker per configuration, no other workers running.
The API and the database are on the same host, and that is stated in the
report rather than left to be guessed.
2. **Warmup:** 20 pages, discarded. The first inference of a model pays for lazy
initialisation, allocator growth and first-touch faults; counting it makes a
fast configuration look slow.
3. **Closed-loop throughput:** 200 pages of `synth-bench` in manifest order,
reported as pages/s over the measured window.
4. **Open-loop latency:** Poisson arrivals at 0.5×, 0.8× and 0.95× of measured
closed-loop capacity, 300 s per rate, from a seeded arrival schedule.
5. **Repetition:** 5 trials. Configuration order is **randomised per trial** from
a seed, with a cooldown between configurations, so a slow drift over the run
cannot systematically favour whichever configuration went first.
6. **Memory:** RSS sampled every 5 ms by a dedicated thread, because the
inference call blocks the main thread for hundreds of milliseconds and a peak
sampled only between inferences misses the peak entirely. `ru_maxrss` is
normalised between macOS (bytes) and Linux (KiB).
7. **Statistics:** per configuration, the median of trial medians with the
min–max range, and a 95% bootstrap interval over the p95 with a fixed seed so
the interval is reproducible.
8. **Raw samples** go to `data/reports/raw/`, one JSON file per trial. Every
reported number is read from one of those files.
## Stability rule
A configuration is marked **unstable** and excluded from every comparison when
either:
- the coefficient of variation of its trial throughput exceeds **5%**, or
- throughput drifts more than **10%** from the first trial to the last.
The report prints the reasons. An unstable measurement quoted as a result is
worse than not quoting it, so the comparison sentences refuse to mention it.
## Latency definitions
Both are always reported, always labelled, never collapsed into one number:
| term | definition | includes the queue wait |
|---|---|---|
| queue-inclusive | `received → persisted` | **yes** |
| service | `claimed → persisted` | no |
| batch wait | `preprocess done → inference start` | n/a |
| preprocess / infer / postprocess / persist | in-process monotonic clock | n/a |
Inference time is attributed to every item in a batch and is never divided
across them.
## Quality metrics
- COCO mAP@[.5:.95] and mAP@.5, per-class AP, from `pycocotools`, with detections
scored from 0.05. The arithmetic is pinned against hand-computed values in
`tests/unit/test_eval.py` (AT-19); a metric nobody has checked against a known
answer is exactly the component that quietly inflates a report.
- The operating threshold is chosen to maximise F1 at IoU 0.5 **on the
calibration split only**, then frozen. Selecting it on the pages the gate is
measured on would be measuring the threshold search as if it were model
quality.
- **Detection agreement** between two runs on the same page:
`2·matched / (|a| + |b|)`, where a match is the same class at IoU ≥ 0.9, greedily
in descending score order. Greedy rather than optimal, because it is stable
under a change in the number of detections, which an optimal matching is not.
- **Raw-output parity**, max and mean absolute difference of logits and boxes
between runtimes on an identical input tensor, which isolates the runtime from
preprocessing.
## Configuration matrix
**Tier 1**, 12 configurations: {heron, egret-medium} × {torch fp32, ORT fp32, ORT
int8} × batch {1, 4}, 4 threads, reference preprocessing, no tiling.
**Tier 2**, if time allows: batch {2, 8}, 8 threads, the `cv2_linear` and `cv2_area`
preprocessing variants, and tiling on versus off for the two-up subset.
## Report rules
Every number states its unit, its configuration, its statistic and its n.
A speedup sentence is emitted only between stable configurations measured in
the same run with the same environment manifest, and always carries its interval.
If a comparison is not permitted, the report says so rather than omitting it
silently. A reader can tell the difference between "no difference was found" and
"no comparison was made".
No number is carried over from a model card, a paper or an issue report.