Skip to content

perf(verifier): amortize per-table/per-query verifier work (Tier 0 + Tier 1)#830

Open
diegokingston wants to merge 1 commit into
perf/verifier-step2-deep-arithfrom
perf/verifier-amortize-tier01
Open

perf(verifier): amortize per-table/per-query verifier work (Tier 0 + Tier 1)#830
diegokingston wants to merge 1 commit into
perf/verifier-step2-deep-arithfrom
perf/verifier-amortize-tier01

Conversation

@diegokingston

@diegokingston diegokingston commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #829. Removes redundancies from the per-table verify path. Each runs T times for the ~100–140 un-batched sub-proofs of an ethrex proof, and verifier.rs doubles as the recursion guest, so cycles count as well as wall-clock.

Tier 0 (micro)

  • FRI-layer leaf hashing — hash the two folded evaluations directly from element slices (hash_data_from_slices + verify_merkle_path_from_leaf_hash, mirroring verify_opening_pair) instead of allocating a vec![a, b] per layer per query.
  • FRI fold twiddles — produce the per-layer inverse points 𝜐^(-2ⁱ) lazily and zip them into the fold, dropping the pre-collected evaluation_point_vec.
  • Redundant primitive root — the DEEP driver recomputed g via get_primitive_root_of_unity(root_order) (~log₂N squarings/table) although the verifier domain already stores it as trace_primitive_root. Use that; drop the now-dead VerifierDomain::root_order field.
  • DEEP eval points — compute the Q primary FRI points once in step 3 and reuse them for both the DEEP denominators and the FRI eval-point inverses. The symmetric point is the primary's negation (sym_index = primary + N/2, lde_root^(N/2) = −1), so no second pow per point.

Tier 1 (structural)

  • Zerofier denominator — fold the transition zerofier's shared 1/(zᴺ − 1) into the boundary-constraint batch inverse as one trailing element (~3 muls) instead of a separate extension inversion (~70 base muls) per table.
  • OOD grid — reconstruct the full current+next-row OOD grid once per table in verify_rounds_2_to_4 (with the soundness-I3 shape validation, now in reconstruct_ood_grid) and thread it into both step 2 and step 3 instead of each rebuilding it.

Tier 2 (per-query DEEP arithmetic)

  • DEEP trace-term denominator factoring — the per-query trace-term sum Σ_col Σ_row (t_col(x) − ood[row][col])·inv_den[row]·γ multiplied the shared row denominator 1/(x − z·gʳᵒʷ) in per (col,row) element. Factor it out of the column sum (Σ_row inv_den[row]·Σ_col (…)·γ): one extension multiply per row instead of per cell, ~halving the trace-term extension muls over 2·Q queries × T tables. Byte-identical (field add/mul are associative + commutative); the g·z pruning skip is preserved and the trace_term_coeffs shape guard is strengthened to exact per-dimension equality so the row-major indexing is panic-safe.

Soundness

No checks removed. The I3 OOD-shape validation still runs (moved into reconstruct_ood_grid, returns None → reject). The folded 1/(zᴺ − 1) still fails closed: a zero denominator (z on a boundary step or on the trace domain) fails the batch inverse and rejects. The sym = −primary identity is the same one the openings already rely on. The DEEP factoring reorders a finite-field sum/product, so it is exact.

Testing

  • All 198 stark tests pass — the test_prove_verify_* suite generates real proofs and runs them through the full multi_verify_views → verify_rounds_2_to_4 → step_2/3/4 path, exercising every changed line; any DEEP drift would desync the codeword from the FRI folding.
  • cargo clippy -p stark clean; no new warnings.
  • Prover-crate ELF/recursion roundtrip tests are unrun locally (need the RISC-V toolchain to compile guest artifacts — the 139 local failures are all missing-ELF, zero verification-arithmetic). GPU and cross-verify validation to run on the server.

Considered but deferred

  • Cross-table domain/layout sharing in multi_verify_views — unsafe when tables have different trace lengths (table splitting), and structural; belongs with the batched-FRI lane.
  • Merkle-path Keccak (~85% of native wall-clock) — only shrinks by batching commitments/FRI across tables, out of scope here.
  • Double-build of boundary_constraints per table (counted in replay, rebuilt in step 2) — a small per-table alloc, not arithmetic; avoiding it needs a new AIR-trait count method or Challenges plumbing not justified by the saving.

@diegokingston

Copy link
Copy Markdown
Collaborator Author

/bench-verify

@github-actions

Copy link
Copy Markdown

Benchmark started on the bench server. The verifier bench takes ~5 min; the recursion-guest cycle comparison then adds guest builds — a few minutes when cached, up to ~1h on a cold run. The bench server is occupied until it finishes.

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown

Verifier benchmark — 1ea7876979 vs main (20 pairs)

=== Verify ABBA result ===

Metric main PR Δ
Verify time 3.582s 3.581s -0.01% ⚪
Proof size 204.30 MiB 204.30 MiB +0.00% ⚪
  pairs: 20   mean A (PR): 3.581s   mean B (main): 3.582s
  [parametric] paired-t   mean -0.01%   sd 0.65%   se 0.15%
               95% CI: [-0.32%, +0.30%]   (t df=19 = 2.093)
  [robust]     median +0.10%   Wilcoxon W+=132 W-=78  p(exact)=0.3300  (z=+0.99)

  run-to-run jitter:    A CV 0.30%   B CV 0.48%        (lower = steadier)
  within-session drift: +0.12% over the run, 1st->2nd half +0.14%

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

Drift-free interleaved A/B/B/A measurement. - = PR faster. Trust the verdict when paired-t and Wilcoxon agree.


Recursion guest cycles (main vs PR)

=== Recursion-guest cycle comparison — single query (blowup=2, 1 query) — deterministic to ~±100k cycles ===
REF_B (baseline) origin/main a864832 guest=recursion-min.elf
REF_A (PR) 1ea7876 1ea7876 guest=recursion-min.elf

Metric REF_B (baseline) REF_A (PR) Δ (A-B)
Guest cycles 43.6M 38.3M -5.3M (-12.21%)
Keccak calls 3025 3025 0

note: cycles reproduce to ~±100k (build codegen + proof nondeterminism); treat sub-100k deltas as noise, not signal.

raw (exact integer counts)
ref_b_sha=a8648320867f7f4242fe286a5b266ffed1fb5519 ref_b_elf=recursion-min.elf ref_b_cycles=43608950 ref_b_keccak=3025 ref_b_execute_wall_s=1
ref_a_sha=1ea787697917bf6e4b85620c8913907b5b5a3683 ref_a_elf=recursion-min.elf ref_a_cycles=38284840 ref_a_keccak=3025 ref_a_execute_wall_s=1
delta_cycles=-5324110 delta_keccak=0

@diegokingston

Copy link
Copy Markdown
Collaborator Author

/bench-verify

@github-actions

Copy link
Copy Markdown

Benchmark started on the bench server. The verifier bench takes ~5 min; the recursion-guest cycle comparison then adds guest builds — a few minutes when cached, up to ~1h on a cold run. The bench server is occupied until it finishes.

@diegokingston

Copy link
Copy Markdown
Collaborator Author

/bench-verify

@github-actions

Copy link
Copy Markdown

Benchmark started on the bench server. The verifier bench takes ~5 min; the recursion-guest cycle comparison then adds guest builds — a few minutes when cached, up to ~1h on a cold run. The bench server is occupied until it finishes.

Two recursion-cycle savings in the verifier (= serial recursion guest):

1. Drop VerifierDomain::root_order. The DEEP driver recomputed the trace
   primitive root via get_primitive_root_of_unity (repeated squarings)
   every proof; reuse the precomputed domain.trace_primitive_root instead.

2. Compute the Q primary FRI evaluation points once in step_3 and share
   them between the DEEP reconstruction and the FRI fold. The DEEP driver
   previously recomputed all 2Q (primary+symmetric) points and the fold
   recomputed Q more. Now: Q pow() total; each symmetric point is -primary
   (bit-reversed index primary+N/2, lde_root^(N/2)=-1), so negate instead
   of a second pow(); the fold moves the same Q points into its inverses.

Byte-identical. 204 stark tests pass, clippy clean.
@diegokingston
diegokingston force-pushed the perf/verifier-amortize-tier01 branch from 2084def to 1ea7876 Compare July 16, 2026 20:38
@diegokingston

Copy link
Copy Markdown
Collaborator Author

/bench-verify

@github-actions

Copy link
Copy Markdown

Benchmark started on the bench server. The verifier bench takes ~5 min; the recursion-guest cycle comparison then adds guest builds — a few minutes when cached, up to ~1h on a cold run. The bench server is occupied until it finishes.

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