Calibrate the orbits stack: thread-gated prepared paths, planner re-fit, prover integration - #217
Merged
Conversation
ebfull
force-pushed
the
orbits-calibration
branch
3 times, most recently
from
August 26, 2026 22:30
9b24a3d to
ed9d4f0
Compare
The prepared evaluation stops scaling past ~8 workers (fewer window tasks than the unprepared orbit backend; reduction work inflates under contention) while the unprepared planner keeps scaling, so on full pools the armed identity test measured slower end-to-end: +14-16% at a 16-thread pool (Apple M4 Max) and +22-27% at a 32-thread pool (Skylake-X) at k = 11, against wins of 8-22% at 4-8 threads on both machines. eval now routes through the prepared check only on pools of at most PREPARED_ZERO_CHECK_MAX_THREADS (8) effective threads and falls back to the plain planned multiexp above it, so arming is never a pessimization. Validated within-process on the Orchard-shaped verifier bench: at the 32-thread pool armed is now parity with unarmed (was +22-27%); at an 8-thread pool armed wins -20/-21/-9/-1% at 1/2/16/64 actions. The placement test pins the prepared path inside a capped pool so it keeps testing the preparation on hosts of any width. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wide pools divide per-worker work but not total memory traffic, so past window saturation the parallel estimates under-priced total data movement: the planner picked orbit width 5 where width 6 measured 5-13% faster at 65,536 terms on 16-32 workers (both curves, both grids — the per-MSM face of the end-to-end k=16 verifier regression) and width 5 where width 4 measured up to 28% faster at 512-2,048 terms on 32 workers. Each backend's parallel estimate is now floored at 8% of its total group-operation count. The floor shapes the orbit width choice on any parallel pool and joins the backend-versus-backend comparison only past 16 workers: at 16 workers the mid-size Booth/orbit boundary measures in opposite directions on 16-core/SMT x86 and Apple M4 Max, so it deliberately stays at the unfloored boundary there pending per-architecture calibration. Inert at 8 or fewer workers by construction; serial and low-worker plans are byte-identical. Fit offline against the 2026-08-26 interleaved msm_backend_timings grids (portable and x86_64-asm): summed planner cell losses drop 268.7pp to 136.3pp (asm) and 352.8pp to 176.2pp (portable) on the fit data, with every plan change either a measured win (up to -28% per cell) or within noise; a fresh grid run confirms the Rust planner reproduces the fitted model's plan in all 100 cells. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The bandwidth floor moves the 16-worker width-5/6 boundary to ~28,672
terms, between grid points, so the k = 15 verifier's 32,770-term final
check plans width 6: plan(32_768, 16) = Orbit{6} is now pinned. Measured
end to end on M4 Max as that verifier's ~5% orbit loss becoming parity
(and k = 16 an 8% win), with 16,384/24,576 staying at width 5.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pasta: PreparedZeroCheck (and PreparedZeroMsm) gain multiexp_with_terms_vartime — the exact multiscalar multiplication the zero-check already evaluates, returning the group element instead of comparing it against the identity. The zero-check becomes a thin wrapper and the naive defensive fallback returns the point. halo2: Params::prepare_commitments builds prepared tables over [g..., w, u] (shared with prepare_zero_checks — commit's bases are a subset with the blind riding w and u unused) and [g_lagrange..., w, u]; when armed, Params::commit and Params::commit_lagrange evaluate through them behind the same eight-thread gate as MSM::eval, so arming is never a pessimization. Measured 1.2-1.8x per commitment at 1-8 threads on x86-64 and Apple silicon across full-width and witness-like coefficient distributions, +3.5-11% on end-to-end proving at an 8-thread pool for k <= 14. (An earlier version of this change also parallelized the advice and h(X) commitments; #212 landed that independently and its version is kept.) orchard: ProvingKey::prepare_proving exposes arming for long-lived provers, symmetric to VerifyingKey::prepare_batch_validation. Tests: pasta pins the point-returning evaluation against the generic MSM with and without extras; halo2 pins armed commit/commit_lagrange against independent unarmed params on witness-like polynomials inside a capped pool (and on the ambient pool for the fall-through), and the opening proof round trip runs armed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
End-to-end calibration showed the stale parallel model over-selecting the orbit backend on wide pools (verifier at k = 16 measured ~8% slower orbits-on at 32 threads), so the series ships default-off until the planner's thread-awareness settles. Runtime CI coverage of the prepared zero-check path is unchanged: the Linux test job already lists orbits explicitly, and the 32-bit and platform-smoke jobs cover what is now the default orbits-off fallback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Test-gate the estimated_work shims (orbit and Booth): production goes through estimated_costs, so the wrappers were dead code in non-test builds, and the primary model docs now live on the costs functions. - Fold the planner's inline generic-traffic formula into estimated_signed_booth_costs so the bandwidth floor's traffic input is definitionally in sync with the work model instead of silently mirroring it. - Say precisely what the <=16-worker backend comparison does (unfloored work values, orbit at its floor-picked width). - Refer to the calibration grids' field backends generically in the planner docs and changelog: the x86-64 assembly backend the grids ran on is PR'd separately now. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Rename PREPARED_ZERO_CHECK_MAX_THREADS to PREPARED_MSM_MAX_THREADS and document all three routes it gates (MSM::eval's identity test and the commit/commit_lagrange prepared tables), and have the public rustdoc name the constant instead of hardcoding "eight" in four places. - Harden Params::prepare_commitments: return early when the coefficient arm declines (no doomed Lagrange build, no partial-arm state the return-value doc misdescribes) and keep already-armed tables instead of rebuilding them, so repeat arming is free. - Rename the Lagrange cache field/accessor to lagrange_table_cache / lagrange_table (it backs prover commitments, never a zero-check) and make ZeroCheckCache's type doc basis-neutral. - Pin the armed wide-pool fall-through deterministically: the gate tests now run under capped pools at the gate width and one past it, instead of relying on wide hosts (CI runners are 2-4 cores and never covered the fall-through). - Fold the standalone thread-aware-eval changelog entry into the unreleased prepare_zero_checks entry, whose old "up to ~2.4x on 8-16 workers" claim advertised a regime the gate now refuses. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ding The "documents halo2's new thread-aware routing" entry described a doc change to an API that is itself still unreleased — its substance now lives in the prepare_batch_validation entry. The arming rustdocs phrase the gate as "bounded-width pools (currently eight effective threads)" so they read as point-in-time when halo2's per-arch recalibration moves the constant. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ebfull
force-pushed
the
orbits-calibration
branch
from
August 26, 2026 23:02
60e735d to
7206379
Compare
Contributor
Author
|
Per review: the x86-64 assembly field backend is now split out to #222 (so it can coordinate with #218), and the branch is rebased onto current main. That's what the force-push is — the halo2/orchard/glv content is byte-identical to the previously reviewed tip apart from the rebase and two doc lines in |
ValarDragon
approved these changes
Aug 26, 2026
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.
Re-calibration of the orbits stack against fresh interleaved grids on Skylake-X (16C/32T) and Apple M4 Max (12P+4E), and prover-side consumption of the prepared fixed-base tables. Every behavioral change is measured on both hosts. Two posture decisions frame the series: the
orbitscargo feature is now opt-in (default-off) until the planner's remaining per-arch conflict zones settle, and every prepared route is thread-gated so arming is never a pessimization anywhere we measured — the shape we want it in before it ever becomes default-on.What changed
Planner bandwidth floor (pasta)
Parallel backend estimates are floored at 8% of their total group-operation count: wide pools divide per-worker work but not memory traffic. The floor shapes the orbit backend's window width on any parallel pool and joins the backend-versus-backend comparison only past 16 workers (at 16 the mid-size Booth/orbit boundary measures in opposite directions on the two hosts, so the comparison deliberately stays unfloored there — evaluated at the floor-picked orbit width). Summed planner cell losses: 269 → 136 pp (grid with the #222 assembly field backend), 353 → 176 pp (portable). End to end this fixed both shipped verifier regressions: x86 k=16 (−8% → +8%, measured with the assembly backend enabled) and M4 k=15 (−4..−11% → parity, via the width-5/6 boundary landing at ~28,672 terms). Pinned in
multiexp_plan_selection.Thread-gated prepared routing (halo2)
MSM::evaluses an armed prepared zero-check only on pools of at mostPREPARED_MSM_MAX_THREADS(8) effective threads. The prepared evaluation stops scaling past that width while the unprepared planner keeps scaling: armed verification measured +14–16% slower at 16T (M4) and +22–27% at 32T (Skylake-X) before the gate, and is parity there now, while keeping the 8–22% wins at 4–8T. Validated within-process on both hosts. The gate is one constant, deliberately shared with the prover routes below.Prover integration (pasta/halo2/orchard)
PreparedZeroCheck::multiexp_with_terms_vartimereturns the exact MSM the zero-check already evaluates;Params::prepare_commitmentsarms it forcommit([g…, w, u], shared with the verifier's table; scalars[poly…, r, 0]) andcommit_lagrange(a second[g_lagrange…, w, u]table) behind the same 8-thread gate;orchard::ProvingKey::prepare_provingexposes arming. Measured 1.2–1.8x per commitment at 1–8 threads on both hosts across full-width and witness-like scalar mixes, and +3.5–11% end-to-end proving at 8T for k≤14. One noisy cell: k=15 armed at 8T read −5.6% (lean budget mode at 32,770 bases; single run near the ±3% noise floor) — worth a cross-check before anyone adds a size guard. (This commit originally also parallelized the advice/h(X) commitments; #212 landed that independently while this PR was open, and the rebase keeps #212's version.)orbitsnow default-off (halo2)Removed from
halo2_proofs's default features (it was never in pasta's own defaults). Downstream builds on default features get the pre-series Signed-Booth planner and plain final identity test, byte-for-byte the old behavior.Params::prepare_zero_checks/prepare_commitmentsand orchard'sprepare_batch_validation/prepare_provingstay present as no-ops returningfalse, so callers never need feature-gating. CI's Linux stage job enablesorbitsexplicitly for runtime coverage; the 32-bit and platform-smoke jobs cover the (now default) off state.Post-review cleanup (final three commits)
A dead test shim that warned on every non-test
orbitsbuild; the planner's generic-traffic formula folded into the Booth cost model instead of silently mirroring it;prepare_commitmentsmade idempotent with no partial-arm states; the prepared-gate constant renamed to cover all three routes it now gates, with rustdoc naming the constant instead of hardcoding "eight" in four places; the gate tests pinned under capped pools at the gate width and one past it (CI runners are 2–4 cores and never exercised the armed fall-through before); changelog entries that narrated the branch's history merged into their unreleased base entries.Design posture
orbits(pasta forwards it asorbits = ["glv"], halo2 forwards pasta's); the arming APIs are stable no-ops without it.Deliberately deferred
Per-arch conflict zones left at today's boundaries: the 16-worker mid-size backend choice and witness-shaped selection at 8–16 workers (opposite signs on the two hosts), and the x86 k=16 one-shot residual (isolated grids show orbit winning that cell; in-verifier it doesn't — steady-state vs cold-context divergence). The commit gate reads pool size, not the effective per-commit share under concurrent commits; an effective-threads hint would let wide pools use prepared commits too.
Validation
Full suites green in every feature state on both hosts before the split (pasta 200/196/190/142, halo2 91 + plonk_api with and without orbits, orchard 163/204); after the split and rebase, re-run on the x86 host: pasta 196 (
multicore,orbits) and 218 (--all-features, which no longer includes the assembly), halo2 full suite plus the commitment/msm lanes withbatch,orbits, rustfmt clean. Planner: an offline replica reproduces the Rust planner 100/100 on grid cells pre- and post-fit; per-cell regression checks on both grids; M4 plan diff byte-identical at ≤16T except the two intended width changes; the costs-model refactor is pinned by the measured-cell plan tests. Gates and prover effects validated within-process or with alternated rounds; the armed fall-through is now pinned by capped pools rather than host width. The halo2/orchard/glv trees are byte-identical to the previously validated branch tip apart from the rebase and two doc rewordings inglv.rs.🤖 Generated with Claude Code