bench(recursion): measure the verifier at real query counts, over real blocks#825
bench(recursion): measure the verifier at real query counts, over real blocks#825MauroToscano wants to merge 3 commits into
Conversation
|
Final ladder cells landed (epoch=2^21, deterministic guest cycles):
Both 16tx cells match the linear fits from the first three points to within 2% (predicted ~51B / ~28B): blowup2 ≈ 2.6B + 8.0B/epoch, blowup4 ≈ 1.7B + 4.4B/epoch. The per-epoch verify cost vs the 2.1M-cycle epoch size puts naive in-VM recursion at ~3,800× expansion per layer at blowup2. |
ed9ae73 to
42f98e4
Compare
|
Rebased onto main now that #769 (rkyv in-place verification) landed — the PR is a single commit on top of the squash, with the continuation guest input ported to the LVMR-prefixed rkyv wire format ( Re-measured the blowup2 ladder on the rkyv base (identical parameters, CUDA-proved fixtures):
Per-epoch marginal cost dropped ~8.0B → ~5.6B (per-query ~36M → ~25M cycles). Keccak counts are unchanged (1,721,218 vs 1,721,282 at 1tx) — the win is deserialization/traversal removal, as designed. Both host roundtrip tests pass in release on the rebased tree. |
|
Benchmark Results for modified programs 🚀
|
…l blocks The recursion cycle benchmark only measured MIN_PROOF_OPTIONS (blowup=2, 1 FRI query) verifying a proof of the empty program - a diagnostic floor, not the production verifier cost (blowup=2 at 128-bit is 219 queries, and real inner proofs are multi-epoch continuation bundles of real blocks). Presets and guests: - Preset::Blowup2 (219 q) / Blowup4 (110 q) - the realistic base-layer regimes - alongside the existing Min/Blowup8; one guest ELF per preset. - A 'continuation' guest feature: verify a whole ContinuationProof bundle in-VM (recursion-cont-<preset>.elf) instead of a monolithic VmProof, so the inner prove is memory-bounded via epochs at any block size. The bundle rides the same LVMR-prefixed rkyv wire format as the monolithic input (ContinuationGuestInput + verify_continuation_and_attest_blob: bytecheck + one rkyv::deserialize pass; zero-copy epoch verify over the archived bundle is follow-up). - verify_continuation_with_roots + continuation_precomputed_commitments: supplied DECODE/page-genesis roots skip the in-VM FFT+Merkle recomputes; binding stays with the attestation fold + consumer recompute, exactly like the monolithic guest path. Benchmark plumbing: - test_dump_recursion_input gains env knobs: RECURSION_DUMP_PRESET, RECURSION_DUMP_INNER_ELF/INPUT (any inner program, e.g. ethrex blocks), RECURSION_DUMP_EPOCH_LOG2 (continuation mode). - scripts/bench_recursion_cycles.sh: preset-aware dump (fails loudly on refs that predate it), --release test invocations, per-preset regime labels; /bench-verify now posts min + blowup2 + blowup4 tables. - scripts/bench_recursion_scaling.sh: block-size x preset ladder over the committed ethrex fixtures (1/4/8/16 transfers), preset-major so the headline curve completes first; DUMP_FEATURES=cuda for GPU boxes. - MAX_PRIVATE_INPUT_SIZE 64 MiB -> 512 MiB: real proofs outgrew the cap the constant was sized for (ethrex_20 at production options is ~231 MiB monolithic; continuation bundles carry one proof per epoch). Nothing is mapped above the region until the stack, so the layout is unaffected. Measured (deterministic guest cycles, epoch=2^21, blowup2): verifying an ethrex block costs ~8B cycles/epoch pre-rkyv and ~5.6B post-rkyv (#769, -30%), ~98% of it in the 219 queries' openings - naive in-VM recursion expands work ~2,700-3,800x per layer.
The 64 MiB -> 512 MiB bump this branch claims only ever landed on the
executor half. `executor::vm::memory::MAX_PRIVATE_INPUT_SIZE` is 512 MiB,
but the guest-side twin here stayed at 64 MiB, so
`get_private_input_slice` clamped `len.min(64 MiB)` and handed the guest
the first 64 MiB of any larger blob. A real recursion input (4tx/blowup2
= 143,004,640 bytes) arrived truncated, rkyv rejected the fragment, and
the recursion guest surfaced it as an opaque Panic("PANICKED") ~1s in.
That made the ladder in `scripts/bench_recursion_scaling.sh` unrunnable
on this branch: its own header example (blob=145080513 with a real cycle
count) is only reachable with the guest clamp at 512 MiB, so the value
was 512 MiB when the table was measured and the edit was lost before
commit. The published numbers are unaffected -- re-measuring 4tx/blowup2
at a8a3d87 reproduces 13.36B cycles against the recorded 13.24B at
c30ffe5 (+0.94%, i.e. #815's own cost).
Soundness is unchanged: the clamp exists to bound a forged length prefix,
and it still bounds it -- now at the same cap the host enforces in
`Memory::store_private_inputs`, which is what makes an honest prefix
always `<=` the bound.
5bac9f0 to
59053bb
Compare
What
The recursion cycle benchmark previously measured one regime:
MIN_PROOF_OPTIONS(blowup=2, 1 FRI query) verifying a proof of the empty program. That's a useful diagnostic floor, but the production verifier runs 219 queries at blowup 2, and the real inner object is a multi-epoch continuation bundle of an actual block. This PR makes the benchmark able to measure the real target.Presets & guests
Preset::Blowup2(219 q) andPreset::Blowup4(110 q) — the realistic base-layer regimes — alongsideMin/Blowup8; one guest ELF per preset (RECURSION_VERIFIER_PRESETSin the Makefile).continuationguest feature →recursion-cont-{min,blowup2,blowup4}.elf: verifies a wholeContinuationProofbundle in-VM instead of a monolithicVmProof, so the inner prove is memory-bounded (any block size proves on a bounded-RAM box).verify_continuation_with_roots+continuation_precomputed_commitments: supplied DECODE / page-genesis roots skip the in-VM FFT+Merkle recomputes. Trust model is identical to the monolithic guest: roots are folded into the attestation'sprogram_id; the binding is the consumer's recompute+compare.Benchmark plumbing
test_dump_recursion_inputenv knobs:RECURSION_DUMP_PRESET,RECURSION_DUMP_INNER_ELF/INPUT(any inner program),RECURSION_DUMP_EPOCH_LOG2(continuation mode).bench_recursion_cycles.sh: preset-aware dump (fails loudly for refs predating it),--releasetest invocations, regime labels;/bench-verifynow posts min + blowup2 + blowup4 tables (builds cached; each extra regime ≈ a blob dump + one ~1–2 min execute per ref).bench_recursion_scaling.sh: block-size × preset ladder over committed ethrex fixtures (1/4/8/16 transfers, ~13–30 KB each, un-ignored explicitly); preset-major sweep so the headline curve completes first.MAX_PRIVATE_INPUT_SIZE64 MiB → 512 MiB: the constant is documented as sized to pass "a whole VmProof" to the verifier guest, and real proofs outgrew it (ethrex_20 at production options ≈ 231 MiB monolithic; bundles carry one proof per epoch). Nothing is mapped above the region until the stack, so the layout is unaffected. The pinnedtest_max_private_input_pages_is_tightvalue moves 257 → 2049 accordingly.Measured (ladder, epoch = 2^21, deterministic guest cycles)
Fits: blowup2 ≈ 2.6B + 8.0B/epoch, blowup4 ≈ 1.7B + 4.4B/epoch, min ≈ 1.42B + 0.16B/epoch. ~98% of the blowup2 per-epoch cost is the 219 queries' openings (~36M cycles/query/epoch — of which the keccak accelerator calls are only ~7K cycles, i.e. the cost is VM scaffolding around the crypto, not the hashing). Net: naive in-VM recursion expands work ~4,000× per layer at current parameters; the empty-program 1-query number under-measured the real verifier cost ~180×.
Notes
bench_recursion_scaling.shruns it locally/on the bench box.blowup8has no continuation guest on purpose (final-wrap parameters, not a base-layer regime).