Repository navigation
judge: four edge-case fixes (budget ties, converged rerun, 16-bit pages, failed counts on the sample) - #93
Open
davanstrien wants to merge 10 commits into
Open
davanstrien wants to merge 10 commits into
davanstrien wants to merge 10 commits into
Conversation
A --max-comparisons budget dropped the judged comparisons over the budget but kept every auto-tie (identical outputs, no judge call) that came after them. With --no-adaptive the trimmed list is the whole sample, so a pair of models with often-identical outputs got ties from pages where no other pair was judged. Its tie share was inflated and its two models were ranked on more pages than the others. In adaptive mode the same happened within the last batch of five pages. _trim_to_budget now cuts the list after the last judged comparison that fits the budget. Every comparison before the cut is kept, auto-ties included, and every comparison after it is dropped, auto-ties included. The adaptive loop counts the auto-ties of a batch after the trim, so the auto_tied count in the metadata matches what is stored. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
In --adaptive-strategy balanced (the default), a run on a results repo that already met the adaptive stopping rule still judged the next batch of five pages. The loop checked the rule only after it had judged a batch, so each rerun of a converged board added comparisons. Targeted mode already checked the rule on the stored comparisons first. Balanced mode now checks the rule on the stored comparisons before the first batch, once there are enough of them for the loop to check. When no adjacent pair is unresolved, the run makes no judge call and prints the message targeted mode prints. The check before the first batch, the targeted resume and the check after each batch call one helper, _stopping_decisions, so they apply the same rule. A run with a new model still judges, because a model with no comparisons has no interval. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
image_to_base64 called convert("RGB") on every image that was not RGB.
For the PIL modes I;16, I;16L, I;16B, I and F that conversion clips
every value above 255, so a 16-bit greyscale scan reached the judge as
a white JPEG with no text on it.
A helper, _wide_grey_to_8bit, runs before the conversion. When an image
in one of these modes holds a value above 255, it maps the image's own
range, from its minimum to its maximum, linearly onto 0 to 255. An image
whose values are all 255 or below, a flat image and an image in any
other mode are passed on unchanged, so their JPEG bytes do not change.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
judge computed the per-model error-sentinel counts over every row of the dataset, but it judges only the rows of the sample. With --max-samples below the dataset length, a model with a sentinel on every sampled row and text on another row was not marked FAILED. It was published with rating 1500, no interval and no comparisons, in rank among the other models, and adaptive mode could not stop because a model with no interval is never resolved. A model with sentinels only outside the sample was published as degraded. compute_model_stats takes the rows to count, and judge passes the judged sample. A model is FAILED when every sampled output is a sentinel, and failed_outputs is the count in the sample, equal to sentinel_pages in the coverage report. A run without --max-samples gives the same counts. The judge warning and the results card say that the count is for the judged sample. audit still reports on the whole dataset. The metadata row records failed_outputs_scope as "sample". share reads it and writes failed_outputs_scope to board.json as "judged sample", or as "source dataset" when the row has no value, and words the card sentence from it. A board shared from results judged by an earlier version keeps its counts, its statuses and the "source dataset" wording. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Image scaling: - The values of a 16-bit, 32-bit or float greyscale image are divided by the largest value, so zero stays black. Stretching the darkest pixel to black gave a blank page a mid-grey look and made show-through look like ink. - The range is read from the pixel values with numpy, exact for 32-bit integers, and `I;16N` is covered. A pixel that is not a number, or infinite, becomes black or white. Stopping rule before the first batch: - Not checked after a run that stopped at its budget. That run stopped in the middle of a batch, and a check at every budget stop looks at the rule more often than one run with no budget does. - The message says how to judge more pages on a converged board. Also: the FAILED sentence on the shared card names the judged sample when the counts do; tests for a chain of budgeted runs with auto-ties, for the 20-comparison minimum on a two-model board, and for the image cases above; CHANGELOG wording. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Stopping rule before the first batch: - The check no longer reads the latest metadata row. A checkpoint writes comparisons and no metadata, so that row can describe an earlier run. The check is made when the first batch with work left has no stored comparison on its pages, which means the stored comparisons end at the end of a batch. A batch that is partly judged is finished first and the rule is checked at its end. - Runs that each stop at a budget stop where one run with no budget stops, with or without metadata from the stopped run. Image scaling: - The page is scaled in strips of rows into one 8-bit array, so the extra memory is the pixel values and the result, not several full-size float copies. The range of a float page leaves out pixels that are not finite. Tests: budget chains on a board that converges, with the metadata each run published; a run stopped one comparison short of a batch; a stale metadata row; exact scaling of 32-bit values checked on the array, not through the JPEG; a page taller than one strip. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
- Budget chains on the converging board also use budgets that end at the end of a batch (3, 5, 15) and one that is used up when the rule is met (30). With a check that read the latest metadata row these chains ran on to the end of the sample. - Image scaling: exact expected values for a signed page (zero is mid-grey), for rounding, and for pixels that are not finite; a page with no finite pixel. - The "already meet the stopping criteria" message is the same in targeted mode, with the --no-adaptive hint. - CHANGELOG: the budget-chain statement holds for --no-adaptive and balanced, not for targeted. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…ng order The check before the first batch with work left ran when none of that batch's pages had a stored comparison. That is not enough: when the judge calls of an earlier batch all failed, that batch has no stored comparison either, while later batches are stored and the last of them can be unfinished. The run then stopped with no judge call in the middle of a logical batch. The check now runs only when every stored comparison is on a page of the batches before the first batch with work left. Then the stored comparisons are exactly the first batches of the visiting order. Results stored in another visiting order (before 0.0.7) get no early check, as before this branch. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016isSPrQfEr21vMuVwsfJxz
From the third review. No change to the stopping check. Tests for conditions that mutants of the check survived: - stored comparisons that end at the end of a page inside a batch (the first, second and last page of the batch); - whole stored batches of a two-model board, below the 20 comparisons the rule needs; - rows of a model outside the current grid, on later pages; - a float page whose range spans several strips. The hint about --no-adaptive is printed in balanced mode only: it cannot be combined with --adaptive-strategy targeted. CHANGELOG: a rerun still judges a batch each time when the stored comparisons are not the first batches of the visiting order (a model added later, a targeted board, judge calls that keep failing, results from before 0.0.7); the budget-chain statement holds when no judge call fails; a page with values below zero uses its lowest value as black. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016isSPrQfEr21vMuVwsfJxz
No change to the code. Both reviewers of the fourth round found no defect in the stopping check. - A test that the stored comparisons are looked at once: a board that meets the rule inside a batch and not at its end must run on to its budget, not stop at the next batch. - A test that exactly 20 stored comparisons are enough for the check (the minimum is "at least"). - CHANGELOG: while the stored comparisons are not a finished prefix, a rerun still makes judge calls; it does not always add a batch. After a model is added, the reruns fill its comparisons and the check then applies. Failed calls are repeated and store no row. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016isSPrQfEr21vMuVwsfJxz
davanstrien
marked this pull request as ready for review
November 11, 2026 07:09
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four separate fixes from a correctness review of the
judgepath, one commit each, plus six commits of review follow-ups. Each has tests that fail without it.1. A budget stop kept ties from pages the run did not reach
_trim_to_budgetdropped the judged comparisons over the budget but kept every auto-tie (identical outputs, no judge call) after them. With--no-adaptive --max-comparisonsthat was the rest of the sample.Reproduction (300 pages, two models identical on 20% of them, budget 60): the pair had 60 ties in 78 rows (77%), and its two models were ranked on 78 pages against 21 for the third. After the fix: 3 ties in 21 rows, 21 pages each.
The run now stops at the last judged comparison that fits. A later run records the dropped ties. With
--no-adaptiveorbalanced, and when no judge call fails, runs that each stop at a budget end with the comparisons of one run with no budget. A reviewer checked 48 chains on a board that never converges (both modes, 12 budgets): same rows, same order, no duplicate. The test suite checks 9 budgets on a board that converges.targetedchooses its pairs again at each start, so its chains can differ; that is not changed here.2. A rerun of a converged board judged one more batch
--adaptive-strategy balancedchecked the stopping rule only after a batch. It now checks the stored comparisons before the first batch, astargetedalready did. A converged board makes no judge call, and the message says how to judge more (--no-adaptive).The check is made only when the stored comparisons are exactly the first batches of the visiting order: every stored comparison is on a page of the batches before the first batch with work left. A batch that is partly judged (a budget stop, a stopped Job) is finished first, and the rule is checked at its end. So a chain of stopped runs looks at the rule where one uninterrupted run does. The check reads the stored comparisons, not the metadata: a checkpoint writes comparisons and no metadata row, so the latest row can describe an earlier run.
Two reviewers ran the same harness of 11,962 sequences on 25 boards (every budget from 1 to 35, mixed budgets, a kill at every stored prefix, blank pages, all-tie pages, error outputs), the second time on the final code: 0 differences in rows, order and verdicts against one uninterrupted run, 0 judge calls on the reruns after each, and no sequence that stopped earlier than the uninterrupted run. On 1,500 random stored states the code matched an independent implementation of the documented rule in every one.
While the stored comparisons are not such a prefix, the run judges before it checks, as on
main, so a rerun still makes judge calls: a model added after the other models were judged on later pages (until its comparisons fill the earlier batches), atargetedboard, judge calls that keep failing (each rerun repeats them), and results stored before 0.0.7 in another visiting order.3. 16-bit greyscale pages reached the judge as a white image
convert("RGB")clips PIL modesI;16(andI;16L,I;16B,I;16N),IandFat 255. The values are now divided by the image's largest value: the lightest pixel is white and zero stays black. The page is scaled in strips of rows; on a 5000×7000 page the scaling step takes about 0.2 s and 134 MiB of extra memory forI;16(267 MiB forIandF); most of that is the pixel array that numpy reads from the image. A reviewer compared 2,684 images with a whole-array reference: 0 differences.The first version stretched the darkest pixel to black. A reviewer measured that this turned a blank page mid-grey and gave show-through the contrast of ink, so the follow-up keeps zero as black. A second reviewer compared three mappings on real 16-bit TIFF and PNG pages (paper / ink as the judge receives them):
A high percentile in place of the maximum was not clearly better: it fixes one hot pixel, and it breaks an inverted page with little ink.
4. Failed-output counts used every dataset row, not the judged sample
With
--max-samplesbelow the dataset length, a model with an error output on every sampled page and text on some unsampled row was published with rating 1500, no interval and no comparisons, and adaptive mode could not stop. A model with error outputs only outside the sample was marked degraded.judgenow counts over the judged sample. The metadata recordsfailed_outputs_scope: sample.sharewords its card from that field and does not recompute stored boards: results judged by an earlier version keep the "source dataset" wording.FAILED still means only that every output of the model in the judged sample is an error message. A model that ends with no ranked page for another reason (a small budget stops before any page where it has text) is published as before.
Tests
ruff,tyandrumdlpass.>for the minimum; pages of out-of-grid rows counted; never check. One mutant is not covered on purpose: making the same check intargetedmode differs only on atargetedboard with a pair never compared, which behaves as onmain.Review before this PR
Four rounds, each by Codex and by an independent agent.
I;16N; min–max stretching made blank pages grey; checking the rule at every budget stop made budget chains stop early (451 to 472 rows where one run gave 480).mainand concluded that the condition cannot stop a run earlier than an uninterrupted run; where it does not apply, the run does extra work as onmain. The agent re-ran the sequence harness (numbers above), ran 60 failed-batch cases (the run judges on the first resume in 60 of 60, where the round-3 code made no call in 13 of 15), and 56 clean converged states (0 calls on every rerun). Its three nits, two missing tests and one sentence, are in the last commit.Known, not changed here
auditcounts error outputs over the whole dataset;judgecounts over the sample.sharetakes its "Judged" date from the last row.mainsends white for all of these.budget_exhausted: true.repo_existscall from the judge test harness and one public file download).🤖 Generated with Claude Code