Skip to content

perf: MSM/FFT prover optimizations, build flags, C API use-after-free fix - #49

Open
OBrezhniev wants to merge 8 commits into
mainfrom
perf/build-flags-and-capi-fix
Open

perf: MSM/FFT prover optimizations, build flags, C API use-after-free fix#49
OBrezhniev wants to merge 8 commits into
mainfrom
perf/build-flags-and-capi-fix

Conversation

@OBrezhniev

Copy link
Copy Markdown
Member

Groth16 prover performance work plus two smaller items that were already on this branch.

Prover optimizations (bumps ffiasm to iden3/ffiasm#7)

Four waves, each committed and benchmarked separately (interleaved old/new binaries, thermally controlled, 20-core x86-64):

  1. Scalar-size partitioned MSM (ee122b8) — witness scalars classified by bit-length; 0/1 wires cost ~one addition instead of ~16 window additions. Adds test_msm, a correctness/benchmark harness (ctest target) comparing against the independent ParallelMultiexp implementation.
  2. Batched witness MSMs (184a656) — on pools of 12+ threads, the A/B1/B2/C multiexponentiations execute as one task set in a single parallel region (G2 first), removing four sequential barriers and straggler tails. Below 12 threads the sequential path is kept (mobile memory).
  3. Batch-affine buckets (0f2412b) — ~5M+1S per bucket addition instead of 8M+2S, batches of ≤512 additions per field inversion.
  4. Bit-reversal-free h pipeline (98edc5c) — DIF-iFFT → fused coset pass → DIT-FFT with a per-prover precomputed bit-reverse-indexed coset table; eliminates six permutation passes and three scaling passes per proof. The coset stays the odd powers of ω_2n baked into snarkjs zkeys, so zkey compatibility is unchanged (unit test asserts exact equivalence with the old ifft/shift/fft).

Measured end-to-end (median of interleaved rounds)

circuit before after
sha256_test (2M constraints, 1.1 GB zkey) 2.41 s 1.38 s (−43%)
credentialAtomicQuery*/authV2/V3 (Privado ID) 0.15–0.34 s −11…−18%

Every wave was verified by proving and verifying on nine production circuits plus the test_msm suite (scalar distributions, boundary values, infinity bases, duplicate points, mixed-curve batching, FFT equivalence).

Also on this branch

  • 4af8b85 — opt-in -march=native/LTO/PGO build targets and mobile-LTO targets
  • 212bff8 — fix use-after-free in groth16_prover_create_zkey_file

🤖 Generated with Claude Code

https://claude.ai/code/session_01JpMJeCdh75Shs3hBRFFLkq

OBrezhniev and others added 8 commits July 7, 2026 17:11
groth16_prover_create_zkey_file loaded the zkey into a local FileLoader,
passed its mmap'd buffer to groth16_prover_create, then returned -- running
the FileLoader destructor and munmap-ing the buffer. BinFile stores the
pointer without copying (addr = fileData), so every zkey section pointer held
by the Prover dangled, causing a segfault on the first prove() call.

Add a file-path constructor to Groth16Prover that builds its BinFile from the
path directly. BinFile's filename ctor owns its FileLoader member for the
object's lifetime, so the zkey stays mapped across every prove(). The buffer
and file-path ctors now share a common init() helper. Route
groth16_prover_create_zkey_file through the new ctor with matching null-arg
and exception handling.

Verified: create_zkey_file + two proves + snarkjs verify OK (previously
crashed with SIGSEGV on the first prove).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add portable build-optimization knobs (all opt-in; default `make host` and
direct cmake builds are unchanged). Measured on a 1.1GB sha256 zkey (C-API
steady-state per-prove), identical RSS across configs and proofs verifying OK:

  host_lto         -5.7%  portable
  host_march_lto   -8.3%  host-CPU-locked
  host_pgo         +2% vs march_lto (worse on this ASM-heavy workload)

- CMake: USE_MARCH_NATIVE / USE_LTO options (default OFF) + PGO=generate|use
  two-phase support. -march=native bakes in host CPU features (SIGILL risk on
  older CPUs) so it is opt-in; LTO is portable.
- Makefile: host_march / host_lto / host_march_lto / host_pgo targets, plus
  *_lto variants for android / android_x86_64 / ios. Expanded `clean` to cover
  the new build/package dirs.
- PGO is wired but measured a ~2% regression here (the compute hot path is
  hand-written NASM, leaving little branchy C++ for PGO); kept as documented
  opt-in. Mobile LTO targets are config-validated for the cross toolchain;
  on-device build/benchmark still pending (no NDK/Xcode in this environment).
- binfile_utils.hpp: add #include <cstdint> (GCC 16 build fix; the header uses
  uint32_t/uint64_t without including it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bumps ffiasm to the scalar-size partitioned MSM with wall-clock-aware
window selection, and adds test_msm: correctness of multiMulByScalarMSM
against the independent ParallelMultiexp across witness-like scalar
distributions and boundary values, plus a benchmark mode (test_msm bench).

Measured G1 n=2^20 on 20 cores: uniform -14%, iden3-realistic witness
-14%, all-binary -87%. End to end: sha256_test proof 2.41s -> 1.85s
(-23%); small identity circuits unchanged (dominated by zkey load).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JpMJeCdh75Shs3hBRFFLkq
Bumps ffiasm to the task-based MSM and, on pools of 12+ threads,
prepares the A, B1, B2 and C multiexponentiations up front and runs
all their bucket tasks in a single parallel region (G1 and G2 tasks
mixed, G2 first as the heaviest), so no MSM's straggler tail leaves
cores idle. Below 12 threads the sequential path is kept: the batch
holds every MSM's scratch at once, a poor trade on mobile.

test_msm gains a batch correctness test mirroring the prover phase.

Measured on 20 cores, interleaved, thermally controlled: identity
circuits (authV3, sigV2, mtpV2, OnChain variants, V3) -5..-9% proof
time end to end; sha256 unchanged on top of the previous -23%.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JpMJeCdh75Shs3hBRFFLkq
Bumps ffiasm to batch-affine bucket accumulation and adapts the
prover's batched MSM phase and test harness to the byte-based task
arena. test_msm gains infinity-base and duplicate-point-pool tests
covering the new accumulator's special-case paths.

Measured on 20 cores, interleaved: sha256 proof 1.89 -> 1.59 s
(-16%), OnChain identity circuits -9..-10%, others -6%. Cumulative
over the three MSM waves: sha256 2.41 -> 1.59 s, identity circuits
-10..-18%.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JpMJeCdh75Shs3hBRFFLkq
Replaces the ifft/shift/fft trio with DIF-iFFT -> one fused coset
pass -> DIT-FFT. The coset table (omega_2n^BR(i) with 1/n folded in,
bit-reverse indexed) is precomputed once in the Prover constructor
and reused by every proof. The coset stays the odd powers of
omega_2n that snarkjs bakes into the zkey's H points, so zkey
compatibility is untouched and the evaluations return in natural
order for the H MSM. Eliminates six bit-reversal permutation passes
and three reindex/scale passes per proof.

test_msm gains a unit test asserting the new pipeline reproduces
ifft+shift+fft exactly.

Measured interleaved on 20 cores: sha256 -8.6%, sigV2/v3/authV2 -6%.
Cumulative over the four optimization waves: sha256 2.41 -> 1.38 s
(-43%), identity circuits -11..-18%.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JpMJeCdh75Shs3hBRFFLkq
Fixes the iOS simulator Debug dylib link on CI (undefined BATCH_SIZE
for arm64): the MSM class constants are now a typed enum, never
ODR-used.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JpMJeCdh75Shs3hBRFFLkq
Bumps ffiasm to the int16-digit / indexed-partition / halved-roots
MSM+FFT, and:

- the batched A/B1/B2/C phase uses one shared bucket arena for both
  curves (a thread runs one task at a time; a common stride keeps the
  per-thread rows disjoint) instead of separate G1 and G2 arenas;
- the Prover builds FFT(domainSize) instead of FFT(domainSize*2),
  deriving omega_2n via FFT::higherRootOfUnity and computing the
  bit-reverse-indexed coset table from a chunked power scan.

Measured interleaved on 20 cores: peak RSS -15..-25% on the identity
circuits (authV2 84 -> 63 MB), -9.7% on sha256 (1444 -> 1304 MB);
proof times unchanged or slightly better (sha256 -1.4%,
OnChain circuits -3.4/-3.8%).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JpMJeCdh75Shs3hBRFFLkq
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