Skip to content

feat(vision-suite): make a comparable Apple arm runnable - #199

Merged
glennneuber merged 1 commit into
mainfrom
feat/apple-arm-prereqs
Aug 18, 2026
Merged

glennneuber merged 1 commit into
mainfrom
feat/apple-arm-prereqs

Conversation

@glennneuber

Copy link
Copy Markdown

Stacked on #198 — it retargets to main when that merges. The rung row from #198 is what makes the finetext divergence visible in the first place.

The ROCm side of the qwen3.8 campaign is finished. The Apple side can't be run from here, but two things would block whoever does run it — I hit both rendering the ROCm arms, so this lands the fixes rather than leaving them to be rediscovered.

1. finetext's window could not be pinned

finetext carries its own 32768/4000 and falls back to NUM_CTX/NUM_PREDICT when set. run_engine_compare.sh always sets them (run_engine_compare.sh:199), so:

how the arm is driven finetext window
straight from vision_suite.py, NUM_CTX unset — the ROCm arms 32768 / 4000
via run_engine_compare.sh at NUM_CTX=16384 — any normal Apple arm 16384 / 2200

Same test, same model, different windows for the one metric that is entirely about how much small text survives — with no way to reconcile them. Nothing reported it until #198's rung row started printing (finetext 32768).

FINETEXT_NUM_CTX / FINETEXT_NUM_PREDICT now pin that test alone:

FINETEXT_NUM_CTX=32768 FINETEXT_NUM_PREDICT=4000 ./run_engine_compare.sh …

The existing coupling stays the default and is pinned by its own test, so this is an escape hatch, not a behaviour change.

2. A repeated arm could not be pooled under a name

run_engine_compare.sh writes the rep number as a tag prefix (lt1_…); summarize_reps.py globs its own -rep<N> suffix. They never meet. REPEATS=3 therefore produces three files that pool only via a shell glob — which then becomes the column header of a published table. And an arm pooling two families can't be written as one glob at all: the ROCm n=5 arm was rendered by copying score files to matching names by hand, the manipulation ADR 0012 rule 8 exists to keep out of a record.

An arm can now name itself and list what it pools, and the published table reproduces from the real tags with nothing copied:

python3 summarize_reps.py \
  'think-off=rocm-5d5b7a72-qwen38-thinkoff,rocm-n3-qwen38-thinkoff' \
  'think-on=rocm-5d5b7a72-qwen38-thinkon,rocm-5d5b7a72-qwen38-thinkon-np4400,rocm-n3-qwen38-thinkon-rep*'
| **num_ctx rung** | 16384 (finetext 32768) | 16384 (finetext 32768) |
| **num_predict**  | 2200 (finetext 4000)   | 2200/4400 ⚠ (finetext 4000/4400) |
| scene bbox IoU   | 0.990 [0.988–0.991]    | 0.987 [0.979–1.000] |
| doc name_bbox IoU| 0.571 [0.570–0.571]    | 0.742 [0.631–0.838] |

An = in the label is fatal, not a warning: it doesn't produce an empty pattern, it produces a plausible one matching nothing, so the arm is dropped and the table renders one column short. A record missing an arm reads as a record that only had one.

Verification

mutation result
drop multi-pattern support FAILED
render the spec instead of the label FAILED*
remove the label guard FAILED (2)
ignore the finetext override FAILED

* survived the first pass — parse_arm was covered but the rendered header was not, which is the entire point of the syntax. There is now a test that renders a table and asserts the header carries no *.

29 summarizer + 15 rescore + 43 preflight, all green. README gains the pooling syntax, the label rule, and a worked example of matching the ROCm window from another host.

Not included

The Apple measurement itself. No host reachable from here runs an MLX build — 10.8.0.6 holds the -nvfp4 models but answers MLX not available … /dist/linux-amd64/lib/ollama, and there is no :11436 anywhere. Separately, the published Apple arms are 0.32.5-maxusai-*, which is not running either, so a run today would be a new baseline rather than the n=3 repeat the campaign's gap asks for.

@glennneuber

Copy link
Copy Markdown
Author

Reviewing as consolidator. Both of these are real, and the second one describes something I did today.

finetext's window

The table is the argument:

how the arm is driven finetext window
straight from vision_suite.py, NUM_CTX unset 32768 / 4000
via run_engine_compare.sh at NUM_CTX=16384 16384 / 2200

Same test, same model, different windows, for the one metric that is entirely about how much small text survives — and nothing surfaced it until #198's rung row started printing (finetext 32768). That is a good illustration of why the rung row belongs in the table: it made a two-year-old-shaped ambiguity visible within a day of existing.

Keeping the existing coupling as the default and pinning it with its own test is right. FINETEXT_NUM_CTX as an escape hatch rather than a behaviour change means every published finetext number keeps its meaning, and a comparable arm becomes expressible for the first time.

Worth noting for whoever runs the Apple side: my MLX/CUDA arms were driven through run_engine_compare.sh at NUM_CTX=16384, so their finetext cells sit at 16384/2200 — the second row. Any Apple comparison should match that or pin both sides explicitly, not assume the ROCm document's 32768.

The tag mismatch — I hit this and worked around it badly

run_engine_compare.sh writes the rep number as a tag prefix (lt1_…); summarize_reps.py globs its own -rep<N> suffix. They never meet.

I rendered today's MLX/CUDA tables by copying score files into a scratch directory under names the summarizer would accept — scores_mlxcuda2_… → scores_gemma4_31b-nvfp4_thinkfalse.json — because summarize_engine_compare.py resolves tags as <model>_think<mode> and cannot see a TAG_PREFIX. It worked, and it is exactly the hand-manipulation ADR 0012 rule 8 exists to keep out of a record. I flagged it at the time as something the generator should absorb; this is that fix, and it is better than the --tag-prefix flag I had in mind because an arm that pools two families cannot be expressed as one glob at all.

So: no objection, and it removes a step I would otherwise have proposed separately.

One thing to confirm before merge

This is stacked on #198 and both edit summarize_reps.py, as do #196 (mean [lo–hi]) and #197 (the rung row). Four PRs on one file in one evening. Whatever lands last should be checked to still carry all four behaviours — the interval rendering, the rung row, the escalation-scoped ⚠, and the arm naming — rather than assuming a clean rebase preserved them. That file is where an accidental revert would be quietest, because every one of these changes is invisible until someone reads a rendered table.

@glennneuber
glennneuber deleted the branch main August 18, 2026 08:01
@glennneuber glennneuber reopened this Aug 18, 2026
@glennneuber
glennneuber changed the base branch from fix/rung-row-scope to main August 18, 2026 08:02
Two things block anyone from producing an Apple arm that can be compared with
the qwen3.8 ROCm campaign. Both were hit head-on rendering that campaign.

FINETEXT'S WINDOW COULD NOT BE PINNED. finetext carries its own 32768/4000 and
falls back to NUM_CTX/NUM_PREDICT when set. run_engine_compare.sh always sets
them, so a runner-driven arm measures finetext at the suite's window, while the
ROCm arms -- driven straight from vision_suite.py with NUM_CTX unset -- measured
it at 32768. Same test, same model, different windows for the one metric that is
entirely about how much small text survives, with no way to reconcile them and
nothing reporting it until the rung row began printing "(finetext 32768)".
FINETEXT_NUM_CTX / FINETEXT_NUM_PREDICT now pin that test alone. The existing
coupling stays the default, and a test pins it so this is an escape hatch rather
than a behaviour change.

A REPEATED ARM COULD NOT BE POOLED UNDER A NAME. run_engine_compare.sh writes
the rep number as a tag PREFIX; summarize_reps globs its own -rep<N> SUFFIX.
They never meet, so REPEATS=3 produces three files that pool only via a shell
glob -- which then becomes the column header of a published table. Worse, an arm
pooling two tag families cannot be written as one glob at all: the ROCm n=5 arm
was rendered by copying score files to matching names by hand, exactly the
manipulation ADR 0012 rule 8 keeps out of a record. An arm may now name itself
and list what it pools, and the published table reproduces from the real tags
with nothing copied.

An "=" in the label is fatal rather than a warning. It does not yield an empty
pattern; it yields a plausible one that matches nothing, so the arm is dropped
and the table renders one column short -- and a record missing an arm reads as a
record that only had one.

Mutation-tested: dropping multi-pattern support, rendering the spec instead of
the label, removing the guard, and ignoring the finetext override each turn the
tests red. The label mutation survived the first pass -- parse_arm was covered
but the rendered header was not, which is the entire point of the syntax -- so
there is now a test that renders a table and asserts the header.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@glennneuber
glennneuber force-pushed the feat/apple-arm-prereqs branch from c95badc to eae4bba Compare August 18, 2026 08:02
@glennneuber
glennneuber merged commit e93c117 into main Aug 18, 2026
1 check passed
@glennneuber
glennneuber deleted the feat/apple-arm-prereqs branch August 18, 2026 08:03
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