Repository navigation
Phase 2 release prep: pin to whestbench 0.16.0, add a rounds reference - #37
Merged
Merged
Conversation
josepha-mayo
approved these changes
Aug 20, 2026
… seed baselines at float32
Re-states the starter kit for the Phase 2 round and makes every FLOP figure in
the docs a measured one.
Phase 2 changes four things a participant plans around, and the kit asserted the
Phase 1 value for each:
shape 256 x 32 -> 1024 x 16
per-MLP budget 2.72e11 -> 2**41 = 2,199,023,255,552
effective cost C_m = F_m + l*R_m -> C_m = F_m
residual time priced at l=1e11 -> capped at 400 ms, not priced
Covered across the concepts pages, the getting-started stages, the how-to
guides, the reference pages and troubleshooting. Placeholder tables are replaced
with measured values, and stage-3 now says plainly that the dataset is the only
route to the Phase 2 shape.
New: docs/concepts/allowed-code.md
Phase 2 replaces PRICING computation outside flopscope with PROHIBITING it,
which is a different kind of rule and needed its own page. It states the three
things a submission may use - the grader's interpreter, the flopscope client
API, the pure-Python stdlib - and the prohibition list, and is explicit that
this is a rule of the round rather than a performance tip: code outside it is
disqualifiable, not merely expensive, and enforcement can be retrospective. It
also draws the line residual time sits on - plumbing, not computation - while
keeping the permission for shipped weights and lookup tables.
Examples reseeded at float32
examples/02 and examples/03 seeded their state at float64. flopscope bills
float64 at 2x, and fnp.zeros/fnp.eye default to it, so NumPy promotion carried
that rate through the O(width^3) einsum that is 99.7% of covariance
propagation's cost. Measured at 1024x16:
02_mean_propagation 153,913,344 -> 86,639,616
03_covariance_propagation 103,407,495,614 -> 51,709,240,799
Final-layer MSE is unchanged to five significant figures. 03 also drops a
float64 round-trip through stats.norm whose results were re-infecting the loop
at the 2x rate.
This is independent of the weight dtype. whestbench now hands estimators fp32
weights, which retired the kit's manual weight cast, but neither remaining
source originates at the read path: fnp.zeros/fnp.eye default to float64 and the
STATE wins the promotion in fp32 x fp64, and stats.norm promotes to float64 to
match scipy whatever the weights are. Measured with fp32 weights held constant,
reverting only the state seeding costs 2.000x and dropping only the cast-back
costs 1.937x.
The replacement carries a version-stability trap worth naming: substituting a
plain 0.0 for the round-trip made the gain term's dtype depend on NEP 50 weak
promotion, billing 131,072 FLOPs under flopscope 0.11.0 and 65,536 under 0.12.0.
An explicit float32 zero pins it, and both totals now reproduce identically
under 0.11.0, 0.12.0 and main at 0812afe9.
tests/test_flopscope_cost_docs.py pins them so a future reseed cannot drift the
documented numbers silently.
Pinned to the meter the grader charges
flopscope>=0.11.0,<0.12.0 and whestbench>=0.15.0,<0.16.0, both from PyPI. 0.11.0
is deliberate, not lag: the evaluator pins flopscope[server]==0.11.0, so that is
the accounting a submission is scored under today. 0.12.0 is released, but
whestbench 0.15.0 declares flopscope<0.12.0, so the three move together -
whestbench cuts a release pinning 0.12.0, the evaluator re-pins to that pair,
then the kit follows. Pinning the kit AHEAD of the grader reports FLOP counts a
submission is not scored on, which is the same defect as lagging, pointing the
other way.
Corrections
A conformance pass against the official rules found five factual errors, and
verifying those fixes found five more in the same family. All ten are measured:
- problem-setup described the inputs as "uniformly random"; they are standard
normal N(0,1), which the rectified-Gaussian derivation on the same page
already assumed.
- code-patterns cited 103,062,421,504 for the einsums (exactly 2x the truth)
and called as_symmetric's billing dtype float64. Measured: einsum
51,531,210,752 over 16 calls, as_symmetric 7,340,031 per call at float32.
- algorithm-ideas quoted 4.7% of budget two lines after quoting 2.351% for the
same example.
- inspect-mlp-structure said matmul is 87% of mean propagation; measured
67,076,096 of 86,639,616 = 77.42%.
- the CHANGELOG announced that the kit tracks flopscope main, contradicting
the pin it ships.
- examples/03 called the float32 saving "an exact halving". It cannot be: the
32 stats.norm calls bill float64 in BOTH runs, so they cancel. 49.99%.
- examples/03's Step 7b understated its own argument - the untagged
contraction bills 4,292,870,144, not 4,225,859,552, so the symmetry tag
saves 24.98% per layer and repays its re-validation ~146x, not ~137x.
- a float64 counterfactual quoted as measured in three files did not
reproduce (103,407,495,614, not 103,407,544,766).
- a per-sample forward cost was off by one FLOP, and two provenance labels had
gone stale against the 0.11.0 pin.
Nearly all share one cause - the float32 reseed above - so each stale figure was
almost exactly double the correct one.
Three hedges here are deliberate and collapse once the kit repins to whestbench
0.16.0: --setup-timeout and run_config.setup_timeout_s do not exist in 0.15.0;
run_config.wall_time_limit_s reports 60 under 0.15.0; and local effective_compute
is still inflated by the Phase 1 lambda term, which
troubleshooting/common-participant-errors.md states outright rather than papering
over.
Verified: 27 tests pass, ruff clean, examples 01-04 all run, whest validate
accepts 4 estimators, and the documented FLOP totals reproduce exactly under
flopscope 0.11.0, 0.12.0 and main.
spMohanty
force-pushed
the
chore/flopscope-main-pin
branch
from
August 21, 2026 11:09
16fb366 to
ce4b84a
Compare
whestbench 0.16.0 is the first release whose defaults are the Phase 2 round: 1024x16, a 2**41 per-MLP budget, a 120 s predict cap, a 5 s setup() cap, a 0.4 s residual cap, and lambda = 0. Under 0.15.0 the CLI applied Phase 1 limits while the documentation described Phase 2. flopscope 0.12.0 reprices approximately 40 operations relative to 0.11.0, in both directions. None are operations the bundled examples use (matmul, tagged einsum, stats.norm, astype, pointwise), so every documented FLOP figure is unchanged. tests/test_flopscope_cost_docs.py asserts this. The specifier strings are character-identical to whestbench's own, as scripts/bump_starterkit_pin.py --check-deps requires.
…ball whest package reads .gitignore and .whestignore as a single pattern list with no `!` negation, so the kit's own `*.npz` rule excluded weights.npz from folder submissions. The command reported success and the archive contained only estimator.py and manifest.json. The omission first surfaced at grading time as a setup() error, which fails the whole submission rather than one MLP. No file in this repository is an .npz; examples/04_shipped_weights.py writes to a temporary directory. The pattern therefore excluded only participant files, including the workflow documented in docs/how-to/ship-weights.md. A folder submission now packages estimator.py, weights.npz and manifest.json.
The kit did not state which round it describes, so a Phase 1 figure encountered elsewhere could not be placed. rounds.md lists v1-warmup, v1-phase1 and v2-phase2 side by side: dataset tag, shape, predict() return shape, forward-pass cost, budget, lambda, residual cap, wall cap and residual mode. It also covers reproducing a score from an earlier round, and why pre-Phase-2 local numbers are not comparable. Each round's budget corresponds to approximately 65,000 forward passes: 64,850 for v1-warmup and v1-phase1, 65,536 for v2-phase2. tests/test_rounds_doc.py asserts every cell against whestbench.budget.ROUNDS at runtime, so a round change upstream fails the build. The two forward-pass rows are derived rather than read, since RoundConfig has no such field; the page states this, and a test pins the metered figure it quotes to the one in local-engine-api.md.
Stale 0.15.0 statements. score-report-fields.md carried a "Local reports still apply lambda" section advising readers to distrust effective_compute, mean_compute_utilization and combined_budget_exhausted. Under 0.16.0 lambda defaults to 0.0, it is recorded in run_config, and effective_compute equals flops_used. The section and its five referrers are removed. stage-3 stated that a dataset-less suite is fixed at 256x32; run_config reports 1024x16. setup() does not run once per submission. It runs once per worker process, a submission is served by several workers, and a worker that dies mid-suite is replaced and re-runs it, approximately 5-15 times in total. Corrected in six places. faq.md stated that Stage 4 and the grader run each MLP in a fresh process. Both local runners use a single worker process: one pid, one import, one setup(), with state carried across MLPs. The remedy the entry recommended, caching through SetupContext.scratch_dir, raises inside setup() because that field is None on every 0.16.0 path, and a setup() exception fails the whole submission. The entry now describes worker-level state instead. Also corrected: defaults described as flags that must be passed (--wall-time-limit, --residual-wall-time-limit and --setup-timeout are already the Phase 2 values); reduction and dtype costs; four report fields that are emitted but were undocumented; fenced whest commands now use uv run; one dead URL; and the Phase 1 example cost ratio, quoted as ~160x, which compared a float64 figure against a float32 one. Like-for-like it is 137.6x. The accuracy table in concepts/scoring-model.md is unchanged. The sentence above it, describing those numbers as a Phase 1 measurement, was incorrect and is removed. Prose throughout is tightened, with no change to any number, flag, path, field name or command.
CI ran the Stage 1-3 smokes but not the subprocess runner or the packaging path, which is where the 0.16.0 changes apply. Both are added, measured at approximately 33 s and 1 s against a 15-minute timeout. The Makefile help lists SOURCE=. for the headless recorder, and demo-cast records that it clones GitHub main and cannot capture an unmerged release.
local_engine.py did not model two limits the grader applies. It now enforces the 120 s predict() wall cap and the 0.4 s residual gate, calls setup() outside the budget context, and calls teardown() after the sweep, matching runner.py. Its docstring lists what it still does not model: the 5 s setup timeout, the 8 GB memory cap, and zero-and-continue on failure. The convergence table prints all_layers_mse and final_layer_mse, ranked metric last. estimator.py registers the module in sys.modules before exec_module, so the diagnostic reports the module path rather than <unknown>, and forwards --seed. examples/04 uses fnp.zeros((), dtype=fnp.float32) in place of fnp.ones(()), which bills 2 FLOPs at the float64 default and contradicted ship-weights.md. examples/03 lowers its rescale guard threshold from 1e100 to 1e30, and its docstring gives the guard's measured cost, 1,023 FLOPs per layer and 16,368 across the suite, in place of a claim that it is free. examples/01 and 02 no longer describe the random baseline as a propagation example, nor state that every bundled example carries the seeding scaffold; examples/04 does not. Tests use measured bands in place of placeholder caps, add a parity test for the rng/seed distinction, and use paths that work when pytest runs from inside tests/.
… checkout The two recorders had diverged: record-demo.sh pinned @v1-phase1 and record-demo-headless.py pinned no revision, tracking main. The committed cast therefore showed Phase 1's 256x32 after the kit moved to 1024x16. Both now pin @v2-phase2, with a comment recording why an unpinned demo goes stale without notice. --source clones a local checkout instead of GitHub main, which is required to record an asset for a release that has not merged. It reads committed state only, and must be an absolute path because the clone runs from a temporary directory. make demo-cast-headless SOURCE=. supplies one.
The committed cast was recorded under Phase 1: width=256 depth=32, a 2.72e+11 budget, memory_limit_mb=65536, and an unpinned dataset resolving to main. It is embedded at the top of README.md. Re-recorded against this branch at @v2-phase2. The cast shows width 1024, depth 16, a 2.20e+12 budget and memory_limit_mb=8192.
The kit stated the rule, C_m = F_m with residual time capped at 400 ms rather than charged, in three places without stating the reason. rounds.md now gives it, and the other two sites link there. Three properties of the priced model motivated the change: - Pricing permits the purchase of unmetered compute. Under lambda, arithmetic performed outside flopscope was billable rather than disallowed. Phase 2 prohibits it, so there is no transaction for a rate to price. - The priced score depended on the grading machine. R_m is wall-clock time, so under lambda > 0 an identical submission produced different values of C_m on different hardware. F_m is derived from tensor shapes and dtypes and does not vary by machine. - A combined budget did not state a FLOP limit. Under lambda, B bounded a sum of FLOPs and priced seconds, so the FLOPs available depended on how much residual time a submission spent. Also records that the 0.4 s cap is a limit rather than an allowance: residual time below it does not affect the score, and exceeding it zeroes that MLP's predictions.
spMohanty
force-pushed
the
chore/flopscope-main-pin
branch
from
August 22, 2026 05:46
11a96d8 to
86674ed
Compare
Applies https://developers.google.com/style across the documentation, examples and scripts. Headings and titles move to sentence case. Capitalization only: GitHub lowercases anchors, so the wording is unchanged and every existing link still resolves. The emoji prefixes are kept. Prose moves to second person and active voice, with the actor named. Figurative language is replaced by the mechanism it stood for, so "wall time is a gate, not a currency" becomes "wall time is a limit, not part of the score", and "you pay the ground-truth tax on every run" becomes "you regenerate the ground truth on every run". Word-level changes follow the guide's word list: "e.g." becomes "for example", "i.e." becomes "that is", "etc." is replaced by naming the remaining items, and "simply", "easy" and filler uses of "just" are removed. Occurrences of "just" that mean "only" are kept, as in "Stage 1: just `python`". Conditions now precede instructions, link text describes its destination, and rhetorical questions addressed to the reader are replaced by statements. No number, flag, path, field name, command or error string was changed, and no fenced content was edited.
spMohanty
force-pushed
the
chore/flopscope-main-pin
branch
from
August 22, 2026 06:18
86674ed to
8351087
Compare
Six places claimed that an unflagged local run meters "exactly as the grader does". The verifiable claim is narrower: since whestbench 0.16.0 the CLI defaults are the Phase 2 round's parameters. What a particular deployment applies at a given moment is not something the kit can assert, and stating it as fact invites a participant to trust a local number for something it does not cover. Each site now says what the defaults are rather than what another system does with them.
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.
Readies the kit for the Phase 2 public release. The enabling change is one dependency pin; most of the rest of the diff follows from it.
Why the pin moved
whestbench>=0.15.0,<0.16.0>=0.16.0,<0.17.0flopscope>=0.11.0,<0.12.0>=0.12.0,<0.13.0whestbench 0.16.0 is the first release whose defaults are the Phase 2 round. Under 0.15.0 the CLI applied Phase 1 limits while the documentation described Phase 2:
whest rundefaultflop_budgetwall_time_limit_ssetup_timeout_sresidual_wall_time_limit_slambda_flops_per_secondC_m = F_m--n-samplesEvery one of those is now the CLI default, so a plain
uv run whest runrehearses the Phase 2 parameters with no flags at all. Several pages carried explicit "this becomes true at 0.16.0" hedges, written when it was not; they are gone.flopscope 0.12.0 reprices ~40 operations relative to 0.11.0, in both directions, but none are operations the bundled examples use (matmul, tagged einsum,
stats.norm,astype, pointwise). Every documented FLOP figure is therefore unchanged, andtests/test_flopscope_cost_docs.pyis the gate that keeps it so.pyproject.tomlrecords the ordering rule it is following: the kit should never report FLOP counts a submission is not scored on, so it tracks the graded meter rather than the newest release.New:
docs/reference/rounds.mdThe kit had no page describing the round it is in, so a participant reading a Phase 1 number somewhere had no way to place it. The new page puts all three rounds side by side — dataset tag, shape,
predict()return shape, forward-pass cost, budget, λ, residual cap, wall cap, residual model — plus how to reproduce a score from an earlier round and why pre-Phase-2 local numbers do not compare.It also records the ratio of budget to forward-pass cost:
v1-warmupv1-phase1v2-phase2Each round's budget corresponds to approximately 65,000 forward passes. Phase 2's budget is 8.08× Phase 1's, and one Phase 2 forward pass costs 8× one Phase 1 forward pass, so the number of passes a submission can afford is unchanged across rounds.
tests/test_rounds_doc.py(19 tests) asserts every cell of that table againstwhestbench.budget.ROUNDSat runtime, so a round change upstream fails the build. The two forward-pass rows are derived rather than read, sinceRoundConfighas no such field, and the page states this.Why Phase 2 drops the residual price
The kit asserted
C_m = F_min three places and explained it in none, which leaves anyone who remembers Phase 1's λ unable to tell whether the rate was removed deliberately or is simply missing.rounds.mdnow carries the reasoning and the other two sites link to it:R_mis wall-clock time, so under λ > 0 an identical submission produced different values ofC_mon different hardware and under different load.F_mis derived analytically from tensor shapes and dtypes and does not vary by machine.Bbounded a sum of FLOPs and priced seconds, so the FLOPs available to a submission depended on how much residual time it spent.The page also states that the 0.4 s cap is a limit rather than an allowance: residual time below it does not affect the score, and exceeding it zeroes that MLP's predictions.
Fixed:
whest packagesilently dropped shipped weightsThe kit's own
.gitignorecontained*.npz, andwhest packagereads.gitignoreand.whestignoreas one pattern list with no!negation. Followingdocs/how-to/ship-weights.mdproduced:…and a tarball containing
estimator.pyandmanifest.json, withweights.npzabsent and no warning in the output. The omission first surfaced at grading time as asetup()error, which fails the whole submission rather than one MLP.No file in this repository is an
.npz;examples/04_shipped_weights.pywrites to a temporary directory. The pattern therefore excluded only participant files, including the workflow documented inship-weights.md. Removed, with a comment recording why it should not return. Verified before and after.Fixed: an incorrect FAQ entry on process isolation
faq.mdsaid Stage 4 and the grader "run each MLP in a fresh process — that state is gone." Measured under both runners: one pid, one module import, onesetup(), one instance, state carried across MLPs.--runner subprocessis identical to--runner localhere.The remedy it recommended, caching through
SetupContext.scratch_dir, raises insidesetup(): that field isNoneon every 0.16.0 path, and asetup()exception fails the whole submission.The entry now describes worker-level state, which the kit did not cover: state carries between calls, but a submission is served by several worker processes (approximately 5-15
setup()runs), each handling only the MLPs assigned to it, and a replaced worker starts from an empty state. A cache built across MLPs is therefore populated locally and empty on several grader workers.Corrections
Corrections made against the shipped 0.16.0 harness, by theme:
setup()does not run once per submission — once per worker process, several workers per submission, and a replaced worker re-runs it. Corrected in six places. It changes the advice for anyone amortising an expensive setup across the suite.--wall-time-limit,--residual-wall-time-limitand the new--setup-timeoutare already the Phase 2 values.varbills 4·N),fnp.eye(1024)is 2,048 at its float64 default,stats.normper-element costs, the einsum symmetry saving. The Phase 1 example ratio was quoted as ~160×, a float64-era figure compared against a float32 one; measured like-for-like it is 137.6×.whestbench_version,results.per_layer_mse,per_mlp[].mlp_name,per_mlp[].per_layer_mse);tracebackwas listed as error-only when it is always present;error_codeis not stable across runners.whest …is nowuv run whest …; a barewhest versioncan resolve to a different install entirely./participants/me/customize→/participants/me/edit); 141 internal links checked, 0 broken.local_engine.pynow models the 120 s wall cap and the 0.4 s residual gate, callssetup()outside the budget context andteardown()after the sweep, and states in its docstring what it still does not model.Style guide
The kit adopts the Google developer documentation style guide.
Headings and titles move to sentence case. Capitalization only: GitHub lowercases anchors, so the wording is unchanged and every existing link still resolves. The emoji prefixes are kept.
Prose moves to second person and active voice with the actor named, figurative language is replaced by the mechanism it stood for, conditions precede instructions, and link text describes its destination. Word-level changes follow the guide's word list:
e.g.becomes "for example",i.e.becomes "that is",etc.is replaced by naming the remaining items, andsimply,easyand filler uses ofjustare removed. Occurrences ofjustmeaning "only" are kept, as in "Stage 1: justpython".No number, flag, path, field name, command or error string changed, and no fenced content was edited.
Refreshed demo
assets/demo.castwas recorded under Phase 1:width=256 depth=32,FLOP Budget 2.72e+11,memory_limit_mb=65536, and an unpinned dataset resolving tomain. Re-recorded at the Phase 2 shape.The two recorders had drifted apart — one pinned
@v1-phase1, the other pinned nothing — which is how the cast went stale unnoticed. Both now pin@v2-phase2, andmake demo-cast-headless SOURCE=.can record a local checkout so a pre-merge release can be captured accurately.Verification
uv run pytest tests/ -quv run ruff check .estimator.py,--baselinewhest validateBaseline scores over the full 100-MLP
minisplit at@v2-phase2:final_layer_mseall_layers_mse01_random02_mean_propagation03_covariance_propagationThese match the figures already published in
concepts/scoring-model.md, so that table is unchanged. The sentence above it, describing the numbers as a Phase 1 measurement, was incorrect and is removed.