Research: seven-component aggregation performance stack - #5
Conversation
# Conflicts: # crates/pcs/src/stack_open.rs
|
@TomWambsgans We’re pausing new optimization work and would appreciate a decision-level review rather than a line-by-line review of this research stack. Could you please help with four decisions?
The latest current-protocol shared-owner campaign was favorable in 20/20 pairs: median 69.54 ms reduction at the complete N2 boundary and 52.52 ms in Bus, with 44/44 byte-identical verified proofs. The timing evidence binds to experimental commit If the design is acceptable, we’ll submit only the shared-owner extraction as a fresh small PR and leave this draft as the research/evidence index. |
|
Hi, looking into it |
The L0 induction picks between a dense per-query basis expansion and a sparse transposed NTT. Both return bit-identical output, verified by running them side by side on every call site of both benchmarks, so the choice is only ever about speed and cannot affect a proof's validity. The model charged the NTT log_block passes over the codeword domain and dispatched on n_queries > 4 * 2^rate * log_block. That factor is not there in wall time. Timing both paths on AVX512 at the shapes this repository proves: cols=20 rate=1 q=226 dense 643 ms NTT 25 ms 25.2x cols=19 rate=2 q=113 dense 167 ms NTT 26 ms 6.4x cols=20 rate=2 q=113 dense 344 ms NTT 80 ms 4.3x cols=19 rate=3 q=75 dense 120 ms NTT 66 ms 1.8x cols=20 rate=3 q=75 dense 248 ms NTT 127 ms 2.0x Each row implies a crossover at queries / ratio, and dividing by the blowup gives 4.5, 4.4, 6.5, 5.1 and 4.7 queries per unit of 2^rate: one constant, holding across three blowups, with no size term. Both paths scale with the message, so the ratio between them cannot depend on it, which is what the log_block factor was asserting. Its thresholds ran from 168 to 736, an order of magnitude high, and every rate-2 and rate-3 shape was on the slow path because of it. So the fix is the model, not an exception list for the shapes someone measured. The threshold becomes n_queries > 8 * 2^rate. Since a threshold above the true crossover can only forgo a win and can never mis-pick the NTT, 8 is the safe side of every measurement below. Query counts are set by the rate, so the tested quantity only ever takes the values 112, 28 and 9.4, and rate 3 clears the threshold by the least. It was measured rather than left to the margin: faster at cols 16, 19 and 20 in a real proof and at 12 and 14 standalone. pcs_throughput disagrees at exactly one shape, cols 16 rate 3, by +2.6% over 5 paired rounds. That benchmark opens no arena phase, so ArenaVec falls back to the system allocator and the NTT's scratch is charged an allocation it never pays inside cpu::prove. Driving the same shape through the prover (xmss --n-signatures 30 --log-inv-rate 3) puts the NTT 3.9% ahead over 6 rounds, so the benchmark is what is unrepresentative there, not the dispatch. The >= 12 floor is the original's and stays: the ratio argument is size-free, but nothing here measured a smaller message, and the floor is what keeps the log_n = 16 roundtrip on the dense path. Re-measured on Apple M4 Max (NEON, 12P + 4E), an arena phase open per call so the allocator behaves as it does inside cpu::prove, minimum of 3: cols=19 rate=1 q=226 dense 43.7 ms NTT 1.8 ms 24.0x crossover 4.7 cols=19 rate=2 q=113 dense 23.7 ms NTT 3.2 ms 7.5x crossover 3.8 cols=19 rate=3 q=75 dense 17.8 ms NTT 7.9 ms 2.3x crossover 4.2 cols=20 rate=1 q=226 dense 89.4 ms NTT 3.9 ms 23.0x crossover 4.9 cols=20 rate=2 q=113 dense 49.5 ms NTT 8.0 ms 6.2x crossover 4.6 cols=20 rate=3 q=75 dense 35.9 ms NTT 19.0 ms 1.9x crossover 5.0 3.8 to 5.0 against AVX512's 4.4 to 6.5: the size-free ratio holds on a second microarchitecture, and the NTT wins at every shape the change moves. Neither arm has an architecture-specific kernel here, transpose_layers_ext is scalar on both and the AVX512 butterflies serve the commit and fold NTT instead, so what differs between the machines is the balance of bandwidth against cores. On a single cold-page call the crossover rises to between 4.6 and 7.8, still under 8 but leaving rate 3 a margin of 1.3x rather than the 2x the warm numbers imply. Below the floor the expansion is the right call by a wide margin: at cols 10 it is 2x to 8x faster than the NTT. End to end, interleaved, the baseline arm reproducing the old dispatch through a temporary threshold override so both arms are one binary: AVX512, 3 passes per arm per round, measured against 6203daf recursion --n 2 --log-inv-rate 2 1.590 s -> 1.452 s -138 ms (-8.68%), 5/5 rounds favorable recursion --n 2 --log-inv-rate 3 1.883 s -> 1.829 s -54 ms (-2.87%), 4/4 rounds favorable xmss --n-signatures 890 ... unchanged, 2/4 rounds favorable, no consistent sign M4 Max, --repeat 5 --cooldown 6, 5 rounds per arm recursion --n 2 --log-inv-rate 2 0.5412 s -> 0.5224 s -18.8 ms (-3.47%), 5/5 rounds favorable recursion --n 2 --log-inv-rate 3 0.6468 s -> 0.6448 s -2.0 ms (-0.31%), 3/5 rounds favorable Rate 3 on the M4 sits inside the run-to-run band of +-2%, so that machine's recursion benchmark does not resolve it either way. The arithmetic says why: the outer L0 saves about 5 ms of 645 ms and the two inner proofs about 9 ms of a 4.5 s command. It is not a regression, and the microbenchmark is unambiguous at 1.9x to 2.3x, but the -2.87% above is an AVX512 result and does not generalize. Rate 2 does: -8.68% there, -3.47% here, every round favorable on both. Peak memory drops with it, 14.19 GiB -> 13.89 GiB at rate 2, since the dense path holds two 2^cols F192 buffers per thread and the NTT path does not. xmss is untouched because at rate 1 it already cleared the old threshold, so both arms take the same path and the two are the same binary. The benchmark reports outer proving only, which understates the rate-2 result: the whole recursion --n 2 --log-inv-rate 2 command goes 13.41 s -> 12.59 s on AVX512, the two inner proofs each dropping an L0 call from 344 ms to about 70 ms. The idea of moving these shapes onto the NTT path is from #5, which allowlisted rate 2 with 113 queries at columns 19 to 21. This generalizes it to the model, which is what also reaches rate 3. Co-Authored-By: Adam Mohammed A Latif <latifkasuli@gmail.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The leaf vector is the largest buffer a proof allocates, one E element per bus row, and it was built with `ArenaVec::filled(ONE, explicit)` before any leaf was computed. Nothing reads those identities. `stack_offsets` packs the blocks contiguously from zero, so the blocks tile `0..explicit` and every slot is written by exactly one block fill; the implicit identity padding that GKR does rely on lives beyond `explicit`, up to `2^mu`, and was never materialized in the first place. The `covered == explicit` test is what licenses skipping the fill, rather than an appeal to how `stack_offsets` currently packs. If a layout ever left a hole, the sum of the block sizes falls short of `explicit` and the identity fill comes back, so the uninitialized path cannot outlive the invariant that justifies it. Capacity is also rounded up to whole four-tuples: `gkr::QuaternaryLayerState` pads whatever level it is handed to a multiple of four before the sumcheck reads it, and for level zero that level is this vector, so without the reservation the pad reallocates and copies all of it. Measured against f3f4f7d, interleaved A/B, 3 passes per arm per round: xmss --n-signatures 890 --log-inv-rate 1 3.700 s -> 3.646 s -54 ms (-1.46%), 5/5 rounds favorable recursion --n 2 --log-inv-rate 2 1.445 s -> 1.418 s -22 ms (-1.5%), 8/9 rounds favorable Picked first out of the remaining candidates on saving per line added: 76 ms across the two benchmarks for 19 lines, about four times the ratio of anything else measured, and the only one needing no new API and no new test. Idea from #5. Co-Authored-By: Adam Mohammed A Latif <latifkasuli@gmail.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Hi, thanks for your contribution. I like to keep 1 commit = 1 optimisation, so I did:
I have included what seemed the best ROI in terms of perf / LoC. Feel free to keep this PR open, will come back to remaining optimisations later. For now my focus is cleaning / doc / soundness etc |
|
Thanks, that approach makes sense. We’ll keep #5 open as the research and evidence reference, and pause further optimization work while you focus on cleanup, documentation, and soundness. It’s great to see the L0 dispatch and Bus leaf work upstream, and the constraints endpoint optimization isolated in #6. When you return to the remaining items, we can extract whichever mechanism you select onto current main and run only the validation you need. |
Summary
This draft preserves an evidence-backed aggregation-performance research stack, integrates upstream
mainat4982d7eb9972c6ea9fdeba52787c6af0401c4824, and adds a production-only extraction of the validated public-MLE shared-owner path.It remains a research handoff, not a merge-ready production patch. The older measured mechanisms retain replay selectors and measurement seams for reproducibility. The new shared-owner extraction itself has no runtime research selector or legacy duplication path.
Source and evidence boundary
b61a0ee64a9e5f41c368f937c56cdbb74fd3908bon the older84fbd3efreeze.256928f9127c812749e40b08b4cf9744185c6b61on that same freeze.a5477b369ee44ef7aea91b0799f7b920b349632fon the earliere9cd16dintegration.e5d33ab1e756166daa88c283cd5e089bc1668693, tree25175fd1b1edfbeb8c94cb4c0768d367ad9a9c2d, with upstream4982d7eas an ancestor.7ed548795bc9f04a65010d9705f2a5c980828c61, integrates upstream through72f2a77574ddd9dad62518c554d4a5a9830e7e2f, and is finalized by04ba272b14f406b5b629b58de4476983dd8d824b.b7625ef55d40c3dd9150fd4f1e2727bf8c94ce4frecords the result and evidence boundary without rewriting any measured commit.Historical proof hashes, fixtures, geometry, and timings remain evidence only for their named source commits. Wire version 2 identifies the artifact encoding, not protocol compatibility; fixtures and proofs must be regenerated after transcript or recursion-guest changes.
Earlier seven-component result
On the older measured freeze:
After seven components, the N2 phase cluster was Bus 0.687050 s, PCS opening 0.527055 s, constraints 0.518037 s, and Flock reduction 0.336672 s. This moved the previous PCS bottleneck; it did not establish that aggregation meets a production budget.
Two-kernel result on
e9cd16dTwo default-off, fail-closed experiments were measured on the earlier integrated stack:
LEANVM_CONSTRAINT_NODE_SKIP=1derives one Boolean constraints-sumcheck endpoint from the running claim, including the exactzeta == 1branch.FLOCK_PACKED_128_PARALLEL=1serializes the three live packed witnesses concurrently into disjoint, completely initialized outputs.The canonical N2 campaign used one AMD EPYC 9354 NUMA domain, CPUs 8–15, four pilots, and 32 measured fresh processes in a repeated 2×2 Williams-square design.
All 36 proofs were byte-identical and passed inspection. The direct mechanisms were repeatedly favorable, but the stated -350 ms system-materiality gate failed. The system deltas are descriptive rather than a confirmatory production-acceptance result.
Current upstream integration
The branch now incorporates upstream through
4982d7e, including the changes that:The integration retains upstream transcript and pre-bound ring-switch semantics while preserving the research timing spans, direct-fold path, and hardened L0 induction policy. Proofs and fixtures predating these protocol changes remain historical artifacts only.
Production shared-owner extraction
The production path builds the nine public bytecode columns once as immutable
Arc<[F64]>owners and shares them across the Bus push and pull layouts.Reuse is authorized only when all relevant invariants agree:
If authorization fails, proving falls back to the original scalar public-MLE evaluations. The verifier, transcript, and proof format are unchanged. The experimental ownership selector, legacy-owner duplication, diagnostics, CLI option, and campaign plumbing are absent from this production extraction.
Current-head public-MLE shared-owner result
A current-protocol N2 system campaign measured the mechanism on composed experimental commit
e5d33ab. The shared arm reused nine public-column evaluations across both Bus decompositions and the bytecode claim; the control arm performed 27 evaluations.The canonical workload was two children, eight BLAKE3 hashes per child, 64,000 iterations per child, and inverse-rate log 2. It ran on an AMD EPYC 9534 with CPUs 8–15 pinned to NUMA node 0 under inherited memory policy.
One excluded
CHpilot preceded the fixed measured order(CHHC HCCH) × 5, giving 40 fresh measured processes and 20 adjacent pairs. There were no retries, replacements, adaptive extensions, or outlier deletions.All eight preregistered promotion gates passed. The final campaign seal covers 1,110 entries with SHA-256
aa36dc23b4a0812cf8436bd800188dc54aec2aa89a4128012eb3fcdcb2c3b668.The original post-run validator exposed a projection bug after validating a new PATH-binding field. An immutable repaired validator and a separate raw reconstruction both reproduced source identity, proof checks, sampler evidence, pair statistics, decision gates, and seals without changing campaign data, ordering, thresholds, or rules.
Evidence boundary and limits
The timing result belongs to experimental commit
e5d33ab, not bare upstream4982d7eand not automatically to the selector-free extraction at this PR head.The experiment's flag-absent arm exercised the intended production shared-owner path, making the campaign strong mechanism-transfer evidence. Removing the A/B harness nevertheless changes the built source, so the EPYC numbers above are not presented as a direct benchmark of
b7625ef. No separate production-head performance rerun has been performed.This is a single-process N2 result on one EPYC 9534 placement. It does not establish N4/N8 scaling, concurrent-prover admission, cross-host portability, or completion of the broader aggregation objective. The memory endpoint is a 25 ms whole-cgroup sample rather than an exact per-process physical peak, and CPU placement was pinned without a hard memory bind.
Local validation on
b7625efThe following passed locally on the final code tree;
b7625efis documentation-only relative to that tree:cargo fmt --all -- --check.cargo check -p lean_vm --tests.cargo test --release -p lean_vm: 31 passed, 0 failed.cargo testall: zero failures, including the ordinaryrecursion_2to1andrecursion_2to1_mixedtests.cargo clippyall.cargo docall.ruff check python-verifier/verifier.py.latexmk -pdf -interaction=nonstopmode main.texfromdoc/: successful 43-page build.These are unsealed compatibility and regression checks, not a replacement performance campaign.
The ignored soundness oracle and a separate
--all-featuressuite were not run in this final validation pass.ruff format --checkis also not claimed: the mergedpython-verifier/verifier.pywould currently be reformatted, and this patch deliberately does not include an unrelated whole-file formatting change.Hosted CI
GitHub created Rust and Documentation PDF workflow runs for prior pushes, but they stopped with
action_requiredbefore starting any jobs. AleanEthereummaintainer must approve workflow execution for this fork PR. No hosted CI result is claimed until those jobs are approved and complete.Fast-upstream procedure
Immediately before review, fetch
origin/mainand inspect:If the branch is behind, inspect the changed files, integrate the exact new upstream head, and rerun the applicable validation commands. Do not rewrite evidence-bound commits
256928f,7be45de,a2c1024,a5477b3, ore5d33ab.Feedback requested
mainthe intended architecture for splitting and productionizing the retained research mechanisms?