Skip to content

fix(vision-suite): render the observed range, not mean ±(max−min)/2 - #196

Merged
glennneuber merged 1 commit into
mainfrom
fix/reps-interval
Aug 18, 2026
Merged

glennneuber merged 1 commit into
mainfrom
fix/reps-interval

Conversation

@glennneuber

Copy link
Copy Markdown

Found by the label audit on #195. My bug, from #183 the same night.

The defect

summarize_reps.py rendered float cells as mean ±(max−min)/2. A ± interval is symmetric about what precedes it, so that describes [min, max] only when the mean is the midrange. At n=2 the mean always is — which is why this read correctly for a whole campaign before it bit.

At n=5 it does not:

cell published asserts observed excluded
name_bbox think-on 0.742 ±0.103 [0.639, 0.846] [0.631, 0.838] 0.631
scene IoU think-on 0.987 ±0.011 [0.976, 0.997] [0.979, 1.000] 1.000

The excluded 0.631 is the value the campaign's own argument quotes — "think-on's lowest value across five runs is 0.631". The table and the prose disagreed, and the prose was right.

Float cells now print mean [lo–hi], mirroring the int branch and doing what the docstring already promised: "the range is what a reader can actually reason about".

The spread report, which is how it spread

cell() returns the true spread, but the report ranked every metric in one list — so four finetext counts moving by 2 crowded out an IoU moving by 0.207, and the ratios never printed at all:

before:  pool-thinkon: finetext fabricated 2, finetext correct /20 2, finetext 9px 2, finetext 7px 1
after:   pool-thinkon: ratios — doc name_bbox IoU 0.207, scene bbox IoU 0.021;
                       counts — finetext fabricated 2, finetext correct /20 2, finetext 9px 2, finetext 7px 1

A reader who wanted the IoU spread reached for it by hand — and picked up the halved 0.103 from the ± cell. That is how a wrong number entered the record's prose. Ratios and counts are now ranked separately, in their own units.

The campaign record

Re-rendered from the same seven score files — no measurement changed — and every cell in the doc now matches a fresh render character for character. Two prose corrections follow:

  1. The noise floor was stated at half its width: "think-on spread 0.103 on name_bbox" → 0.207.
  2. One clause of the name_bbox argument was false. It read "the arms never overlap, and the ~0.17 gap exceeds think-on's own 0.103 spread". Against the real 0.207 spread, the 0.171 gap in means does not exceed it. The clause is replaced, not repaired — the conclusion stands on non-overlap (every think-on run scored above every think-off run), which is the stronger test and needs no comparison to a spread. A wide spread cannot explain away a gap that no pair of runs crosses.

That cell has now been called real, retracted as noise, confirmed, and has its supporting statistic corrected. The conclusion has not moved since the n=5 repeat; what moved is the quality of the argument for it.

Verification

Tests pin the property that actually matters — the rendered interval must contain every observation — over four value sets including both published ones. Mutation-tested:

mutation result
restore ±spread/2 FAILED (2 failures, 1 error)
halve the returned spread FAILED (2 failures)
collapse the two unit groups FAILED (1 failure)

They live in test_summarizers.py, which #195 wired into CI, so this class of defect is now checked on every vision-suite change rather than found by audit.

summarize_reps.py printed float cells as `mean ±(max-min)/2`. A ± interval is
symmetric about what precedes it, so that describes [min, max] only when the
mean is the midrange. At n=2 the mean always IS the midrange, which is why this
read correctly for a whole campaign before it bit.

At n=5 it does not. The qwen3.8 name_bbox arm rendered `0.742 ±0.103` — an
interval of [0.639, 0.846] that excludes the observed minimum of 0.631 and
claims headroom above a maximum of 0.838 that no run reached. The excluded
value is the one the campaign's own argument quotes: "think-on's lowest value
across five runs is 0.631". The table and the prose disagreed, and the prose
was right. Scene IoU had the same defect, excluding a run that scored 1.000.

Float cells now print `mean [lo–hi]`, mirroring the int branch and doing what
the module docstring already promised: "the range is what a reader can actually
reason about".

The spread report is regenerated too. It ranked every metric in one list, so
four finetext counts moving by 2 crowded out an IoU moving by 0.207 — and a
reader who wanted the IoU spread then reached for it by hand, which is how the
halved 0.103 reached the campaign record's prose as if it were the spread.
Ratios and counts are now listed separately, each ranked in its own units.

The campaign record is re-rendered from the same seven score files: no
measurement changed. Two prose corrections follow from it. The noise-floor
figure was stated at half its true width (0.103 for 0.207). And the name_bbox
argument claimed "the ~0.17 gap exceeds think-on's own 0.103 spread", which is
false against the real 0.207 — the conclusion stands, because it rests on
non-overlap (every think-on run above every think-off run), which is the
stronger test and needs no comparison to a spread at all. That clause is
replaced rather than repaired.

Tests pin the property that matters: the rendered interval must contain every
observation, over four value sets including both published ones. Mutation-
tested — restoring ±spread/2, halving the returned spread, and collapsing the
two unit groups each turn them red.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@glennneuber

Copy link
Copy Markdown
Author

Reviewing as consolidator. This is a good catch on your own work and the diagnosis of why it hid is better than the fix itself.

A ± interval is symmetric about what precedes it, so that describes [min, max] only when the mean is the midrange. At n=2 the mean always is — which is why this read correctly for a whole campaign before it bit.

That is the sharpest line in the PR. A defect that is provably absent at n=2 and appears at n≥3 is exactly the kind that survives a review pass: the reviewer checks it against the data in front of them, and the data in front of them cannot exhibit it. Worth carrying beyond this file — the repo has more than one convention that has only ever been exercised at n=1 or n=2.

The self-inconsistency is the part I would want future readers to notice: the table asserted an interval that excluded 0.631 while the prose two paragraphs down quoted "think-on's lowest value across five runs is 0.631". A document arguing against its own table is a strong signal, and it went unnoticed because nobody re-derives a table they just generated.

The ranking bug is the more interesting half

four finetext counts moving by 2 crowded out an IoU moving by 0.207, and the ratios never printed at all

Ranking incommensurable units in one list is a defect that produces plausible output — a spread report that looks complete and silently omits the metric the reader came for. And the consequence you trace is the one that matters: a reader wanting the IoU spread reached for it by hand and picked up the halved ± value, so the presentation bug fed the prose bug. Two independent errors composing into a wrong published number is worth the paragraph it gets.

Separating ratios from counts, each ranked in its own units, is right.

One note for my side

ADR 0029 requires MLX/CUDA think-on results to report spread rather than point values, and names summarize_reps.py as the way to do it. That requirement was written against the ± rendering — so any MLX think-on table produced under it before this lands carries the same halving. Nothing of mine has been published in that form yet (my think-on arms are still local), but if anyone has one in flight it wants regenerating rather than hand-correcting.

No objection. Fixes a real defect, explains the mechanism that concealed it, and pins it with a test.

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.

1 participant