Skip to content

bench(recursion): measure the verifier at real query counts, over real blocks#846

Merged
Oppen merged 13 commits into
mainfrom
bench/recursion-bench-infra
Jul 21, 2026
Merged

bench(recursion): measure the verifier at real query counts, over real blocks#846
Oppen merged 13 commits into
mainfrom
bench/recursion-bench-infra

Conversation

@Oppen

@Oppen Oppen commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

Stacked on #845. 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:

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, --release test invocations, per-preset regime labels; /bench-verify now posts min + blowup2 + blowup4 tables.
  • scripts/bench_recursion_scaling.sh: block-size × preset ladder over the committed ethrex fixtures (1/4/8/16 transfers).
  • A real-block detailed profile test (blowup=4, 4 transfers) plus a make test-profile-recursion-block target, since cycle counts across every preset/block size are already covered by the scripts above.

Note: this branch currently includes a cherry-picked copy of #843's commit (fix(syscalls): raise the private-input clamp to 512 MiB) so the real-block tests are runnable now (real blocks exceed the old 64 MiB cap). That commit will disappear from this diff once #843 merges and this branch is rebased onto the updated base.

Test plan

  • cargo test -p lambda-vm-prover --lib (516 passed)
  • test_recursion_profile_blowup4_block run end-to-end against a real 4-tx ethrex block (not just compiled) — produced the full cycle/step/function breakdown
  • cargo clippy --all-targets -- -D warnings / cargo fmt --check
  • make -n test-profile-recursion-block resolves the full build graph correctly

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown

Benchmark Results for modified programs 🚀

Command Mean [ms] Min [ms] Max [ms] Relative
head hashmap 128.4 ± 4.6 122.2 134.6 1.00

@Oppen

Oppen commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator Author

/ai-review

@github-actions

Copy link
Copy Markdown

Codex Code Review

  • Highmemory.rs: Raising the private-input cap to 512 MiB allows store_private_inputs to expand one input into roughly 134 million HashMap<u64, [u8; 4]> entries. This can consume many gigabytes and crash the executor/prover through host-memory exhaustion. The cap should reflect the sparse-memory representation’s safe limit, or private input should use a compact/page-backed representation before accepting inputs this large.

  • Mediumflamegraph.rs: The oversized-input test now allocates over 512 MiB merely to exercise rejection, making an ordinary test prone to OOM. Test the boundary without constructing such a large buffer, or move this into an explicitly ignored/resource-heavy test.

@claude

claude Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Reviewed as a static diff review (working tree is at main, so gh pr diff 846 was the source of truth). This is largely benchmarking infrastructure — new verifier presets, a continuation-guest benchmark path, scripts, and Makefile plumbing. It is careful and well-documented. No blocking issues found.

Verified correct

  • New presets Blowup2/Blowup4: all three match self arms in recursion.rs (options/artifact_stem/name) are updated exhaustively, Preset::ALL lists all four, and there are no other Preset match sites to break. The compile_error! mutual-exclusion guard in main.rs correctly enumerates all six preset pairs, and --all-features (which also enables continuation) still trips it.
  • ContinuationGuestInput / encode_continuation_guest_input: pure relocation earlier in recursion.rs, no behavior change.
  • Makefile continuation rules: --features "continuation $(1)" builds both the cont and non-cont bins but copies the correctly-named artifact; race-free under -j via distinct bin names.
  • flamegraph.rs test now derives the oversized-input size from MAX_PRIVATE_INPUT_SIZE instead of a hardcoded 64 MiB — no drift.

FYI (not blocking, out of scope for this PR)
The MAX_PRIVATE_INPUT_SIZE 64->512 MiB bump is the cherry-picked commit you flagged. Worth calling out for whoever reviews it: beyond the 8x host allocation, it widens the verifier honest page-count bound (page::max_private_input_pages, 257->2049) that guards num_private_input_pages against a malicious prover (prover/src/lib.rs:1350, continuation.rs:933). Both copies of the constant stay in sync and the bound remains enforced and pinned by test_max_private_input_pages_is_tight, so it is sound — just a security-relevant surface that belongs to the other PR review, and drops out of this diff on rebase as noted.

Minor

  • The two MAX_PRIVATE_INPUT_SIZE definitions (executor + syscalls) are kept consistent only by a doc comment ("Must match"), with no compile-time cross-check. Pre-existing, not introduced here, but the bump is a reminder it would be nice to enforce.

@Oppen

Oppen commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

Note: MAX_PRIVATE_INPUT_SIZE findings come from and are handled in #843

@github-actions

Copy link
Copy Markdown

Benchmark Results for unmodified programs 🚀

Command Mean [ms] Min [ms] Max [ms] Relative
base binary_search 41.1 ± 1.4 38.8 42.6 1.03 ± 0.04
head binary_search 39.8 ± 1.0 38.5 41.6 1.00
Command Mean [ms] Min [ms] Max [ms] Relative
base bitwise_ops 41.1 ± 1.4 38.8 43.1 1.05 ± 0.05
head bitwise_ops 39.0 ± 1.1 38.5 41.5 1.00
Command Mean [ms] Min [ms] Max [ms] Relative
base ecsm 2.1 ± 0.0 2.1 2.1 1.03 ± 0.03
head ecsm 2.0 ± 0.0 2.0 2.2 1.00
Command Mean [ms] Min [ms] Max [ms] Relative
base fibonacci_26 42.2 ± 1.4 40.3 44.0 1.03 ± 0.04
head fibonacci_26 41.2 ± 1.1 40.2 43.3 1.00
Command Mean [ms] Min [ms] Max [ms] Relative
base keccak 78.1 ± 2.0 75.4 80.6 1.03 ± 0.03
head keccak 75.8 ± 1.6 74.7 78.5 1.00
Command Mean [ms] Min [ms] Max [ms] Relative
base matrix_multiply 45.7 ± 2.9 41.4 48.6 1.00
head matrix_multiply 47.3 ± 3.2 41.2 54.2 1.04 ± 0.10
Command Mean [ms] Min [ms] Max [ms] Relative
base modular_exp 40.1 ± 1.1 38.9 41.8 1.01 ± 0.04
head modular_exp 39.9 ± 1.3 38.8 41.7 1.00
Command Mean [ms] Min [ms] Max [ms] Relative
base quicksort 42.5 ± 1.0 41.1 44.0 1.00
head quicksort 42.5 ± 1.3 41.4 44.3 1.00 ± 0.04
Command Mean [ms] Min [ms] Max [ms] Relative
base sieve 44.2 ± 1.2 42.3 45.8 1.02 ± 0.03
head sieve 43.2 ± 0.9 42.2 44.7 1.00
Command Mean [ms] Min [ms] Max [ms] Relative
base sum_array 49.3 ± 1.2 47.4 51.0 1.00 ± 0.03
head sum_array 49.2 ± 1.0 47.8 51.3 1.00
Command Mean [ms] Min [ms] Max [ms] Relative
base syscall_commit 59.1 ± 1.7 57.4 61.6 1.00
head syscall_commit 66.4 ± 2.2 64.7 70.4 1.12 ± 0.05

@Oppen
Oppen force-pushed the perf/continuation-rkyv-zero-copy branch 3 times, most recently from 9e9f790 to 995f971 Compare July 17, 2026 22:17
@Oppen
Oppen force-pushed the bench/recursion-bench-infra branch from 6d8c4dd to 23a96ae Compare July 17, 2026 22:38
@Oppen

Oppen commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

/ai-review

@github-actions

Copy link
Copy Markdown

Codex Code Review

  • Medium — .github/workflows/bench-verify.yml:134: Both new full-query comparisons always fail against the current origin/main, because bench_recursion_cycles.sh rejects any non-min baseline lacking RECURSION_DUMP_PRESET. The if then suppresses that failure, so CI posts only “unavailable” notes and never measures blowup2 or blowup4 for this PR. Generate the baseline blob with explicitly matched options or avoid advertising these regimes until both refs support them.

@claude

claude Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review — no significant issues found

Reviewed the diff (safety/security, correctness, performance, readability). This PR is almost entirely benchmark/test/build plumbing on top of the existing recursion verifier, and it's carefully done and well-documented.

Verified as correct:

  • Preset expansion (recursion.rs): Blowup2/Blowup4 added consistently across options()/artifact_stem()/name(), and Preset::ALL matches the 4-variant enum (Preset is Copy, so the into_iter().find(...) name lookup is fine). with_blowup(2|4) mirrors existing with_blowup(8) usage.
  • Guest compile_error! mutual-exclusion (main.rs): the pairwise all(...) guard correctly rejects any two presets enabled at once, and the not(any(...)) guard rejects none — preserving the security invariant that a preset is pinned at build time, not via private input.
  • Makefile --bin fix: the added --bin $(2) correctly defeats the required-features subset-match race described in the comment (a --features "continuation min" build would otherwise also build plain recursion-min-bench into the shared target path, racing make -j). Distinct bin names + --bin pinning is race-free.
  • CI full-query loop (bench-verify.yml): the if scripts/...; then ... else <note> fi wrapper correctly tolerates refs predating the preset-aware dump (the if-condition is exempt from set -e; pipefail still makes the script's exit status authoritative through the tee).
  • Profile test correctness upgrade (recursion_smoke_test.rs): checking the guest's committed split_attestation against a host-side expected_program_id / continuation recompute turns the profile tests from crash-freedom into an actual identity+output oracle — a good addition. The added blob.len() <= MAX_PRIVATE_INPUT_SIZE assert in the dump test is a sensible guard.
  • Scripts: bench_recursion_scaling.sh cleans up each ~100+ MB blob per iteration (no disk blowup), fails fast on missing prereqs, and tolerates per-cell failures without aborting the sweep.

Note (not a finding): the continuation verify path (verify_continuation_and_attest_blob, ContinuationGuestInput) lives in the stacked base (#844/#845), not in this diff — those functions are only moved/referenced here, so I could not re-verify their internals from this PR alone.

Nothing blocking.

@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown

AI Review

PR #846 · 13 changed files

Findings

Status Sev Location Finding Found by
confirmed medium scripts/bench_recursion_cycles.sh:197 bench_recursion_cycles.sh block-mode result cache key ignores BLOCK_TXS/BLOCK_EPOCH_LOG2 kimi
openrouter/moonshotai/kimi-k2.7-code
confirmed low .github/scripts/run_recursion_bench.sh:21 run_recursion_bench.sh else-branch message is misleading moonmath
zro/minimax-m3
confirmed low .github/scripts/run_recursion_bench.sh:27 run_recursion_bench.sh raw-log fallback always uses the min preset log kimi
openrouter/moonshotai/kimi-k2.7-code
confirmed low Makefile:282 test-profile-recursion aggregate target doesn't include block variant moonmath
zro/minimax-m3
confirmed low scripts/bench_recursion_scaling.sh:89 DUMP_FEATURES word-splitting turns space-separated features into positional test filters glm
openrouter/z-ai/glm-5.2

Status column reflects the verdict from the verifier: deepseek-verifier (openrouter/deepseek/deepseek-v4-pro).

AI-003: bench_recursion_cycles.sh block-mode result cache key ignores BLOCK_TXS/BLOCK_EPOCH_LOG2
  • Status: confirmed
  • Severity: medium
  • Location: scripts/bench_recursion_cycles.sh:197
  • Found by: kimi:openrouter/moonshotai/kimi-k2.7-code
  • Verified by: deepseek-verifier:openrouter/deepseek/deepseek-v4-pro
  • Rejected by: -

Claim

In blowup4-block mode the cached blob is keyed on preset + block_txs + block_epoch_log2, but the cached result file is only keyed on preset, MEASURE_CLI SHA, and ref SHA. Changing BLOCK_TXS or BLOCK_EPOCH_LOG2 between runs can reuse a stale result while a fresh blob is generated, producing an invalid comparison.

Evidence

Line 197: result="$WORK/result_${sha8}_${PRESET}_m${HEAD_SHA:0:8}.txt". Lines 201-204: only blob_key includes _txs${block_txs}_epoch${block_epoch_log2}. The result is read/written with no knowledge of those parameters.

Suggested fix

Include the same block parameter suffix in the result cache key, e.g. result="$WORK/result_${sha8}_${blob_key}_m${HEAD_SHA:0:8}.txt", or otherwise derive the result key from blob_key.

AI-008: run_recursion_bench.sh else-branch message is misleading
  • Status: confirmed
  • Severity: low
  • Location: .github/scripts/run_recursion_bench.sh:21
  • Found by: moonmath:zro/minimax-m3
  • Verified by: deepseek-verifier:openrouter/deepseek/deepseek-v4-pro
  • Rejected by: -

Claim

The "unavailable for these refs" message is shown for ANY bench_recursion_cycles.sh failure (build error, missing fixture, MEASURE_CLI failure), not just unsupported preset combos. A build error or missing fixture would be reported as "preset unavailable" instead of surfacing the real cause.

Evidence

run_preset() captures the exit code of the pipeline (bench_recursion_cycles.sh ... | tee) and on failure unconditionally prints "(${preset} regime unavailable for these refs — see the workflow log.)" regardless of whether the script failed because the ref predates a knob, because make failed, because MEASURE_CLI crashed, or because the sysroot wasn't provisioned.

Suggested fix

Inspect the captured log and print a more specific message (e.g., grep the log for the known "predates" / "missing" markers the script already emits, and otherwise fall back to a generic "failed — see workflow log").

AI-010: run_recursion_bench.sh raw-log fallback always uses the min preset log
  • Status: confirmed
  • Severity: low
  • Location: .github/scripts/run_recursion_bench.sh:27
  • Found by: kimi:openrouter/moonshotai/kimi-k2.7-code
  • Verified by: deepseek-verifier:openrouter/deepseek/deepseek-v4-pro
  • Rejected by: -

Claim

Only /tmp/recursion_out_min.txt is copied to /tmp/recursion_out.txt. If blowup2, blowup4, or blowup4-block is the preset that fails, the bench-verify PR-comment fallback will tail the min log, which is misleading.

Evidence

Line 27: cp -f /tmp/recursion_out_min.txt /tmp/recursion_out.txt. bench-verify.yml reads /tmp/recursion_out.txt as the raw fallback when RECURSION_OUTCOME is not success.

Suggested fix

Copy the per-preset log that actually ran last, or update the fallback in bench-verify.yml to read /tmp/recursion_out_${preset}.txt for the most recently attempted preset.

AI-012: test-profile-recursion aggregate target doesn't include block variant
  • Status: confirmed
  • Severity: low
  • Location: Makefile:282
  • Found by: moonmath:zro/minimax-m3
  • Verified by: deepseek-verifier:openrouter/deepseek/deepseek-v4-pro
  • Rejected by: -

Claim

test-profile-recursion: test-profile-recursion-single test-profile-recursion-multi doesn't include test-profile-recursion-block, so a manual make test-profile-recursion won't run the new block profile. The workflow calls the variants directly so CI is fine, but the aggregate is now incomplete.

Evidence

Line 282-283: test-profile-recursion: test-profile-recursion-single test-profile-recursion-multi. Lines 285, 288, 309: each variant is defined individually. The block variant at line 308 is not in the aggregate.

Suggested fix

Add test-profile-recursion-block to the aggregate, or document that the block variant is intentionally opt-in (heavy, requires pre-proved ethrex fixture).

AI-019: DUMP_FEATURES word-splitting turns space-separated features into positional test filters
  • Status: confirmed
  • Severity: low
  • Location: scripts/bench_recursion_scaling.sh:89
  • Found by: glm:openrouter/z-ai/glm-5.2
  • Verified by: deepseek-verifier:openrouter/deepseek/deepseek-v4-pro
  • Rejected by: -

Claim

The expansion ${DUMP_FEATURES:+--features "$DUMP_FEATURES"} is unquoted on the command line, so the inner double-quotes do not survive word-splitting. A single feature (the documented DUMP_FEATURES=cuda) works, but a space-separated multi-feature value like DUMP_FEATURES="cuda test-cuda-faults" word-splits into --features, cuda, and test-cuda-faults, where the last token becomes a positional libtest name filter rather than a feature — silently running a different test set.

Evidence

scripts/bench_recursion_scaling.sh line ~89: cargo test --release -p lambda-vm-prover ${DUMP_FEATURES:+--features "$DUMP_FEATURES"} --lib test_dump_recursion_input -- --ignored --nocapture. Because the ${...:+...} is unquoted, "$DUMP_FEATURES" does not group the value; only comma-separated single-token values parse correctly as cargo features.

Suggested fix

Document/require comma-separated features (e.g. DUMP_FEATURES=cuda,test-cuda-faults, which cargo accepts as one token), or pass via an array built with read -ra so multi-feature values are handled safely.

Reviewer Lanes

Lane Model Prompt Status Findings
glm openrouter/z-ai/glm-5.2 general success 2
kimi openrouter/moonshotai/kimi-k2.7-code general success 4
minimax minimax/MiniMax-M3 general error: opencode failed (provider/auth/runtime error) and no findings were submitted 0
moonmath zro/minimax-m3 general success 8
nemotron openrouter/nvidia/nemotron-3-ultra-550b-a55b general success 5

Verification Lanes

Lane Model Status Confirmed Rejected Uncertain
deepseek-verifier openrouter/deepseek/deepseek-v4-pro success 5 12 0

Native Codex and Claude reviews run separately and post their own comments. They are not included in this structured provenance report.

Discarded candidates (12) — rejected by the verifier
  • Removal of function name truncation in histogram output may break PR comment layout (.github/scripts/aggregate_recursion_histogram.py:77, found by nemotron:openrouter/nvidia/nemotron-3-ultra-550b-a55b, kimi:openrouter/moonshotai/kimi-k2.7-code, moonmath:zro/minimax-m3) — The short() truncation function was indeed removed. However, GitHub markdown tables handle long code-span content gracefully via cell wrapping/scrolling — they are not 'broken' by long content. The claim about 'breaking GitHub's comment rendering' is speculative; mangled symbols may look ugly in tables but that's a cosmetic concern, not a rendering break. This is a design trade-off (full names vs truncated) not a bug.
  • Stale expected sidecar silently breaks test_recursion_profile_blowup4_block (prover/src/tests/recursion_smoke_test.rs:217, found by moonmath:zro/minimax-m3) — The test reads committed binary fixtures (blob + expected sidecar) and asserts the guest's attestation matches. While it's true that changing ContinuationGuestInput or the encoding requires regenerating the sidecar, the test's error messages explicitly direct the user to run make recursion-profile-block-input. The comment in the Makefile says 'built ONCE' documenting this maintenance constraint. The failure mode is not opaque — it tells the developer exactly what to do. This is an explicit design choice with documented regeneration, not a silent trap.
  • bench_recursion_cycles.sh fails loudly for blowup4-block if ethrex_bench_4.bin missing, but fixture generation not attempted (scripts/bench_recursion_cycles.sh:260, found by nemotron:openrouter/nvidia/nemotron-3-ultra-550b-a55b) — Lines 299-303 check for ethrex_bench_${block_txs}.bin and error if missing. However, the default BLOCK_TXS=4 matches the committed fixture (executor/tests/ethrex_bench_4.bin is part of this PR's diff), so this check never fails in the default CI path. For non-default BLOCK_TXS values, the error message is descriptive ('blowup4-block is not measurable for it'). Unlike bench_recursion_scaling.sh which auto-generates, this script's design choice to only support the committed fixture is intentional for CI determinism.
  • bench_recursion_scaling.sh writes generated block fixtures back into the repo tree (scripts/bench_recursion_scaling.sh:68, found by kimi:openrouter/moonshotai/kimi-k2.7-code) — Line 65 of bench_recursion_scaling.sh guards generation with if [ ! -f "$FIX" ]; then. Since ethrex_bench_4.bin IS committed and present in the repo, this branch is never taken for N=4 — the committed file is never overwritten. The .gitignore un-ignores only ethrex_bench_4.bin and ignores ethrex_bench_*.bin for all other sizes, so generated files for N=1,8,16 are properly git-ignored and don't dirty the tree.
  • run_recursion_bench.sh always invokes min preset, even when redundant (.github/scripts/run_recursion_bench.sh:25, found by moonmath:zro/minimax-m3) — Line 25 unconditionally runs run_preset min. The claim is about redundancy — that min always runs even when its result is already cached. However, the PR's design intentionally always runs min as the baseline diagnostic. With internal caching in bench_recursion_cycles.sh, a re-run is effectively instantaneous (the subprocess exits quickly with a cache hit). Running min unconditionally is a design choice, not a bug. The overhead of spawning the subprocess and resolving refs is negligible compared to the actual measurement work.
  • Profile-recursion comment job uploads fragments under mismatched names (.github/workflows/profile-recursion.yml:108, found by moonmath:zro/minimax-m3) — With download-artifact@v4 and merge-multiple: true, files are extracted FLAT into the specified path directory (not nested in per-artifact subdirectories). The assembly loop at lines 142-145 correctly references fragments/fragment-single-query.md etc. The claim acknowledges this works and only raises a hypothetical 'brittle if a maintainer changes one piece' concern. This is not a real bug in the current code.
  • Verbose pairwise compile_error! checks for preset mutual exclusion (bench_vs/lambda/recursion/src/main.rs:18, found by nemotron:openrouter/nvidia/nemotron-3-ultra-550b-a55b) — The compile_error! macro explicitly lists all 6 pairwise combinations of 4 features. While this is verbose, it is correct, exhaustive, and self-documenting. Adding more presets would require updating this list, but that's standard Rust feature-gating. The claim that it's 'error-prone' is speculative — the original code had 2 features with 2 combinations, and the PR extends it to 4 features with 6 combinations correctly. This is a code style preference, not a bug.
  • GoldilocksCubicProofOptions::with_blowup() validity assumption for blowup=2 and blowup=4 (prover/src/recursion.rs:58, found by nemotron:openrouter/nvidia/nemotron-3-ultra-550b-a55b) — GoldilocksCubicProofOptions::with_blowup() validates that the blowup factor is a power of 2 >= 2 and that security_bits (128) > grinding_factor (20). Both blowup=2 and blowup=4 satisfy all these constraints — they are valid powers of two, and 128 > 20. The expect() calls are provably safe and will never panic at runtime. The claim also has evidence from existing tests (proof_options_tests.rs) confirming 2 and 4 are valid.
  • Blob cache key for blowup4-block uses preset name 'blowup4-block' but dump uses RECURSION_DUMP_PRESET=blowup4 (scripts/bench_recursion_cycles.sh:173, found by nemotron:openrouter/nvidia/nemotron-3-ultra-550b-a55b) — The blob cache key uses blowup4-block while the dump test internally uses RECURSION_DUMP_PRESET=blowup4. However, the claim's own evidence acknowledges this is 'functionally correct' because 'blowup4-block' isn't a build preset — it's a benchmark preset that maps to the blowup4 build preset. The naming asymmetry is by design (there is no blowup4-block Cargo feature). The concern is purely about cosmetic naming confusion, not a functional issue.
  • build_guest log filename is preset-agnostic, may collide across presets (scripts/bench_recursion_cycles.sh:238, found by moonmath:zro/minimax-m3) — The build_guest log name build_guest_${sha8}.log is correct because compile-recursion-elfs builds ALL recursion ELFs regardless of preset — the build output IS preset-agnostic. The prune logic on line 119 removes this exact filename (no glob needed since there's only one per SHA). The claim's evidence concedes 'The build IS preset-agnostic ... so overwriting is fine'. The observed asymmetry with dump/measure globs is a cosmetic inconsistency, not a bug.
  • Bench/profile scripts build all 7 recursion guest ELFs even when one preset is measured (scripts/bench_recursion_cycles.sh:239, found by glm:openrouter/z-ai/glm-5.2) — The compile-recursion-elfs target builds all recursion verifier and continuation artifacts. On a cold runner the extra build work does add time, but this is a deliberate trade-off for simplicity — one target builds everything, avoiding the complexity of per-preset build targets. With shared GUEST_TARGET_DIR and build-std, the incremental cost on warm runs is zero. The PR's timeout comments acknowledge the cold-run cost. This is a design choice, not a bug.
  • bench_recursion_scaling.sh recomputes inner cycles per preset (scripts/bench_recursion_scaling.sh:72, found by moonmath:zro/minimax-m3) — The inner cycle measurement (CLI execute --cycles) is run inside the preset loop at lines 72-76. While the inner cycle count depends only on tx_count and not the preset, the comment on line 71 says 'once per block size (cheap; deterministic)'. A single execute --cycles of the ethrex program with 4 transfers is likely milliseconds to seconds. The 8 redundant measurements across 3 presets × 4 sizes is a minor efficiency concern, not a correctness issue. The claim's speculation about 'full guest execution' ignores the comment's assertion that it's cheap.

Raw lane outputs, candidates, final issues, and model metrics are uploaded as workflow artifacts.

Base automatically changed from perf/continuation-rkyv-zero-copy to main July 17, 2026 23:07
Oppen added 5 commits July 17, 2026 20:08
…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.
- bench_vs/lambda/recursion's `continuation` guest feature: verify a
  whole ContinuationProof bundle in-VM (recursion-cont-<preset>.elf),
  built on the continuation supplied-roots verify path and the
  zero-copy continuation guest API from the prior two PRs.

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, --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).
- A real-block detailed profile test (blowup=4, 4 transfers) plus a
  `make test-profile-recursion-block` target, since cycle counts across
  every preset/block size are already covered by the scripts above.
Address PR review feedback:
- build_guest_elf: pass --bin so a continuation build's superset
  features (`continuation min`) don't also rebuild the plain preset's
  bin into the same shared target-dir path a concurrent `make -j` job
  is writing to.
- drop redundant ethrex_bench_{1,8,16}.bin fixtures (only _4 is read
  by tests); bench_recursion_scaling.sh regenerates them on demand.
- bench_recursion_scaling.sh: don't let one failed cycle-count/grep
  abort the whole sweep under `set -e`; clean up its temp dir.
- recursion.rs docs: list blowup4 among the fixed-preset ELFs.
- recursion profile tests: check the guest's actual committed
  attestation against a trusted host recompute, not just that it ran
  without crashing — covers both the monolithic and continuation
  (real ethrex block) profile paths.
@Oppen
Oppen force-pushed the bench/recursion-bench-infra branch from 0bab2ac to ed9d34f Compare July 17, 2026 23:08
Oppen added 7 commits July 20, 2026 14:59
test_recursion_profile_blowup4_block previously re-proved a real ethrex
block's continuation bundle on every run (minutes of prover work) just
to profile the verifier guest. Build the input once via a new
recursion-profile-block-input Makefile target and read it as a fixture,
with an .expected sidecar carrying the id+output ground truth so the
test can verify the guest's attestation without re-deriving it.
…roof

profile-recursion.yml runs test-profile-recursion-block; bench-verify.yml
adds a blowup4-block regime. The dump test's proof blob is now cached by
ref SHA so repeat runs skip re-proving. Also trims verbose comments.
Moves the inline shell for looping presets (min/blowup2/blowup4/
blowup4-block) into .github/scripts/run_recursion_bench.sh, matching the
repo's other .github/scripts/*.sh conventions.
local a=x b=$a expands $a before the assignment lands, so under
set -u this failed as "preset: unbound variable". Split into two
local statements.
Cut generic-monomorphized Rust symbol names to 90 chars, hiding the
type params that usually matter most when reading the profile.
…mplete

continue-on-error on that step exists to protect the already-posted
verifier result, not to hide a broken recursion step behind a green
job. Add a final step, after the result posts, that fails the job on
a non-success recursion outcome.
@Oppen Oppen added the ai-review Trigger the AI review label Jul 20, 2026
@github-actions

Copy link
Copy Markdown

Codex Code Review

  • Mediumscripts/bench_recursion_cycles.sh:197: Block benchmark results are cached without BLOCK_TXS or BLOCK_EPOCH_LOG2, although the blob cache includes both. Changing either variable reuses stale cycle results. Include blob_key in the result filename.

  • Low.github/workflows/bench-verify.yml:79: A nonnumeric workflow_dispatch pair count makes both numeric comparisons error and leaves the invalid value unchanged. Validate with a numeric pattern before range checks.

Comment thread scripts/bench_recursion_cycles.sh Outdated
@claude

claude Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review summary

Benchmark-infrastructure PR: adds Blowup2/Blowup4 verifier presets, a continuation-guest real-block profile path, and preset-aware bench plumbing. The core Rust changes are additive/test-only and the shell + CI wiring is careful. No critical/high safety, security, or correctness issues found.

Strengths

  • The new profile path adds a real correctness oracle (ExpectedAttestation checked against a trusted host recompute of program_id + inner public output), not just crash-freedom — a genuine improvement over the prior execute-only profiler.
  • Preset::ALL is kept in sync with the enum and used for the name→preset lookup; the compile_error! mutual-exclusion guard in main.rs is correctly expanded to all four presets.
  • MAX_PRIVATE_INPUT_SIZE assertion added to the dump test, and the continuation dump verifies the bundle on-host before writing the fixture — the dumped blob can't silently be garbage.
  • Scripts fail loudly up front (missing knobs, missing fixtures, missing guest ELFs) rather than letting the guest reject a blob in-VM; .gitignore and comments are consistent (only ethrex_bench_4.bin committed).

Minor

  • One low-severity cache-key inconsistency in scripts/bench_recursion_cycles.sh (result cache omits the block txs/epoch that the blob cache includes) — inline comment left.

Nothing blocking.

…rs input

Result cache was keyed on $PRESET alone while the blob cache also folds in
BLOCK_TXS/BLOCK_EPOCH_LOG2, so overriding those on a cached ref silently
reused a stale measurement. Also validate the workflow_dispatch pairs
input is numeric before the range check — a non-numeric value made both
comparisons error out and pass through unchanged.
@Oppen
Oppen added this pull request to the merge queue Jul 20, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 20, 2026
@Oppen
Oppen added this pull request to the merge queue Jul 21, 2026
Merged via the queue into main with commit 528a841 Jul 21, 2026
20 checks passed
@Oppen
Oppen deleted the bench/recursion-bench-infra branch July 21, 2026 04:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review Trigger the AI review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants