Skip to content

Perf/verifier per air work - #989

Closed
jotabulacios wants to merge 3 commits into
mainfrom
perf/verifier-per-air-work
Closed

jotabulacios wants to merge 3 commits into
mainfrom
perf/verifier-per-air-work

Conversation

@jotabulacios

Copy link
Copy Markdown
Collaborator

No description provided.

…pying them.

An AirWithBuses held the interaction list twice — once in auxiliary_trace_build_data
and again inside the LogUpLayout built from it — and Clone copied both, so every
clone duplicated the same data twice over. The in-VM verifier clones one AIR per
table per epoch, which turns that into real work: profiling the recursion guest put
clone+drop of Vec<BusInteraction> at 9.2% of its cycles without computing anything.

Both now sit behind an Arc, so a clone bumps two refcounts. This is the pattern the
struct already uses one field up for the captured constraint IR, and for the same
reason. Nothing else changes: every use of these two fields is a read, so Arc's Deref
covers them, and no construction site had to move.

Measured on the recursion guest verifying a real mainnet block (30 epochs), same
input blob on both sides: 1,546,877,064 -> 1,485,030,675 cycles, -4.00%. The guest's
keccak-permutation count is identical on both sides (2,009,340), so the verifier did
the same work.
…onstraint.

evaluate_zerofier computed that inverse itself, but the caller evaluates every
constraint of an AIR at the same z and trace_length, so the value is identical for
all of them — the loop was redoing one extension-field pow and one extension-field
inv per constraint, hundreds of times per table, always with the same result. The
constraint-dependent half (the end-exemptions product) is the only part that had to
be inside.

zerofier_base_inv now computes the shared half and the callers hoist it out of the
loop. Both call sites had the identical loop, so both get it: the verifier's OOD
transition evaluation and the prover's.

Measured on the recursion guest verifying a real mainnet block (30 epochs), same
input blob on both sides: 1,485,030,675 -> 1,311,599,244 cycles, -11.21%. Combined
with the preceding commit that is -15.21% off main, or 235,277,820 fewer cycles —
and since the outer trace carries one CPU row per guest cycle, the same number of
rows off whatever has to prove that verifier. Identical keccak-permutation count on
both sides (2,009,340).

The win is specific to the in-VM verifier. Verifying the same 1.6 GB continuation
proof natively shows no measurable change: three interleaved runs per side averaged
27.173s before and 27.378s after, a +0.75% difference against a within-arm spread of
up to 2.08%. That is expected — in the VM every instruction costs one cycle and there
is no SIMD, so a cubic-extension inversion (an addition chain of dozens of
multiplications) dominates in a way it never does natively, where the 27 seconds go
to deserializing 1.6 GB, Merkle paths and FRI. The prover side is not measured either
and is expected neutral for the same reason: there the zerofier is evaluated once per
proof, not per row.
@jotabulacios

Copy link
Copy Markdown
Collaborator Author

/bench-verify

@github-actions

Copy link
Copy Markdown

Benchmark started on the bench server. Two verifier arms (monolithic + continuations over an ethrex 20-tx block), then the recursion-guest cycle comparison, which adds guest builds on top — longer on a cold runner. The bench server is occupied until it finishes.

@github-actions

Copy link
Copy Markdown

Verifier benchmark — 9f2dcc7d4e vs main (20 pairs, monolithic + continuations)

ethrex 20-tx block · monolithic · blowup=2, 219 queries

Metric main PR Δ
Verify time (ABBA, 20 pairs) 2.514s 2.518s +0.18% ⚪
Proof size (exact, 1 reading) 102.33 MiB 102.33 MiB +0.00% ⚪
  pairs: 20   mean A (PR): 2.518s   mean B (main): 2.514s
  [parametric] paired-t   mean +0.18%   sd 0.76%   se 0.17%
               95% CI: [-0.18%, +0.54%]   (t df=19 = 2.093)
  [robust]     median +0.30%   Wilcoxon W+=135 W-=75  p(exact)=0.2774  (z=+1.10)

  run-to-run jitter:    A CV 0.44%   B CV 0.58%        (lower = steadier)
  within-session drift: -0.32% over the run, 1st->2nd half -0.23%

INCONCLUSIVE — effect not separable from 0 at n=20 (point estimate ~+0.30%). Add pairs to resolve.

ethrex 20-tx block · continuations, epoch 2^20 (3 epochs) · blowup=2, 219 queries

Metric main PR Δ
Verify time (ABBA, 8 pairs) 3.154s 3.175s +0.67% 🔴
Proof size (exact, 1 reading) 177.07 MiB 177.07 MiB +0.00% ⚪
  pairs: 8   mean A (PR): 3.175s   mean B (main): 3.154s
  [parametric] paired-t   mean +0.67%   sd 0.72%   se 0.25%
               95% CI: [+0.08%, +1.27%]   (t df=7 = 2.365)
  [robust]     median +0.57%   Wilcoxon W+=27 W-=1  p(exact)=0.0312  (z=+2.11)

  run-to-run jitter:    A CV 0.62%   B CV 0.16%        (lower = steadier)
  within-session drift: +0.31% over the run, 1st->2nd half +0.21%

🔴 REAL REGRESSION — PR verifies ~0.67% slower (paired-t and Wilcoxon agree).

Verify-time rows only: drift-free interleaved A/B/B/A, with paired-t and exact Wilcoxon — trust the verdict when the two agree. Proof sizes are single exact readings (no averaging). - = PR faster.


Recursion guest cycles — verifier running INSIDE the VM (main vs PR)

empty program · monolithic · blowup=2, 1 query (diagnostic — NOT a real verifier cost)

Single exact reading per ref — no ABBA: guest cycles are deterministic for a fixed
(guest ELF, input blob), so there is no machine drift to cancel.

Metric main PR Δ
Guest cycles 32.1M 25.6M -6.6M (-20.40%)
Keccak calls 3029 3029 0
  baseline  origin/main  99d7567afe  guest=recursion-min.elf
  PR        9f2dcc7d4e0f49dcb5eebea77ba2c0c6fe7678a4  9f2dcc7d4e  guest=recursion-min.elf
  note: cycles reproduce to ~±100k (build codegen + proof nondeterminism);
        treat sub-100k deltas as noise, not signal.
raw (exact integer counts)
ref_b_sha=99d7567afec95c672e78065bc2bacb4416fb7577 ref_b_elf=recursion-min.elf ref_b_cycles=32137866 ref_b_keccak=3029 ref_b_execute_wall_s=1
ref_a_sha=9f2dcc7d4e0f49dcb5eebea77ba2c0c6fe7678a4 ref_a_elf=recursion-min.elf ref_a_cycles=25582694 ref_a_keccak=3029 ref_a_execute_wall_s=0
delta_cycles=-6555172 delta_keccak=0

ethrex 20-tx block · continuations, epoch 2^21 (2 epochs) · blowup=2, 219 queries (128-bit)

Single exact reading per ref — no ABBA: guest cycles are deterministic for a fixed
(guest ELF, input blob), so there is no machine drift to cancel.

Metric main PR Δ
Guest cycles 1910.0M 1877.4M -32.7M (-1.71%)
Keccak calls 3296811 3250507 -46304
  baseline  origin/main  99d7567afe  guest=recursion-cont-blowup2.elf
  PR        9f2dcc7d4e0f49dcb5eebea77ba2c0c6fe7678a4  9f2dcc7d4e  guest=recursion-cont-blowup2.elf
  note: cycles reproduce to ~±100k (build codegen + proof nondeterminism);
        treat sub-100k deltas as noise, not signal.
raw (exact integer counts)
ref_b_sha=99d7567afec95c672e78065bc2bacb4416fb7577 ref_b_elf=recursion-cont-blowup2.elf ref_b_cycles=1910023462 ref_b_keccak=3296811 ref_b_execute_wall_s=31
ref_a_sha=9f2dcc7d4e0f49dcb5eebea77ba2c0c6fe7678a4 ref_a_elf=recursion-cont-blowup2.elf ref_a_cycles=1877350891 ref_a_keccak=3250507 ref_a_execute_wall_s=31
delta_cycles=-32672571 delta_keccak=-46304

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant