perf(prover-ray): 5x faster R5 prove - #3736
Open
gbotrel wants to merge 3 commits into
Open
Conversation
BenchmarkR5Prove: 25.5s -> 4.9s per op on 18 cores. - compilers/global: bind each vanishing expression once (leaves resolve to coset slices and scalars, removing per-point map lookups and runtime access), keep base subtrees in base-field arithmetic, and chunk the coset-point accumulation across CPUs. Bit-identical output. - wiop: LagrangeEval evaluates its polynomial batch in parallel. - fri: NewTree hashes each Merkle level in parallel. - zkcdriver: add R5 benchmarks (trace, check, assign, compile, prove, verify, cold end-to-end). Signed-off-by: Gautam Botrel <gautam.botrel@gmail.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds R5 (RISC‑V) benchmark coverage and introduces CPU-parallel execution for several hot kernels to improve prover performance.
Changes:
- Add an R5 benchmark suite covering trace, constraint checking, witness assignment, system/ZKC compilation, prove, verify, and cold end-to-end.
- Parallelize per-polynomial Lagrange evaluations and per-coset-point quotient accumulation using
utils/parallel. - Parallelize FRI Merkle tree level hashing.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| prover-ray/zkcdriver/r5_benchmark_test.go | Adds benchmarks for R5 trace/prove/verify and compilation stages. |
| prover-ray/wiop/query_lagrange_eval.go | Parallelizes polynomial evaluation batch in LagrangeEval. |
| prover-ray/wiop/compilers/global/global.go | Refactors vanishing evaluation into bound expressions and parallelizes per-point accumulation. |
| prover-ray/crypto/koalabear/fri/tree.go | Parallelizes hashing within each Merkle tree level. |
Comment on lines
+571
to
593
| var bound []boundEntry | ||
| if bkt.entries != nil { | ||
| // Static module: use precomputed cancellation cosets. | ||
| for _, entry := range bkt.entries { | ||
| accumulateOnCoset( | ||
| rt, entry.v.Expression, cosetEvals, cosetEvalsExt, selectorCosets, | ||
| entry.cancellationCoset, &coinPow, aggregate, ratio, N, | ||
| ) | ||
| bound = append(bound, boundEntry{ | ||
| expr: bindExpr(rt, entry.v.Expression, cosetEvals, cosetEvalsExt, selectorCosets, ratio, N), | ||
| cancellation: entry.cancellationCoset, | ||
| coinPow: coinPow, | ||
| }) | ||
| // advance coinPow: coinPow *= coinExt | ||
| coinPow.Mul(&coinPow, &coinExt) | ||
| } | ||
| } else { | ||
| // Dynamic module: compute cancellation cosets at runtime. | ||
| for _, v := range bkt.vanishings { | ||
| cancellationCoset := computeCancellationCoset(v.CancelledPositions, n, N) | ||
| accumulateOnCoset( | ||
| rt, v.Expression, cosetEvals, cosetEvalsExt, selectorCosets, | ||
| cancellationCoset, &coinPow, aggregate, ratio, N, | ||
| ) | ||
| bound = append(bound, boundEntry{ | ||
| expr: bindExpr(rt, v.Expression, cosetEvals, cosetEvalsExt, selectorCosets, ratio, N), | ||
| cancellation: computeCancellationCoset(v.CancelledPositions, n, N), | ||
| coinPow: coinPow, | ||
| }) | ||
| coinPow.Mul(&coinPow, &coinExt) | ||
| } | ||
| } |
Comment on lines
+1020
to
+1030
| func (e *boundExpr) evalBase(j int) field.Element { | ||
| switch e.kind { | ||
| case boundVecBase: | ||
| idx := j + e.offset | ||
| if idx >= len(e.vecBase) { | ||
| idx -= len(e.vecBase) | ||
| } | ||
| return field.Lift(cosetEvals[e.Column.Context.ID][idx]) | ||
| case *wiop.LagrangeSelector: | ||
| // Selectors are base-field and unshifted: lift L_pos(coset_j) into 𝔽_{p^6}. | ||
| return field.Lift(selectorCosets[e.Position][j]) | ||
| case *wiop.Cell: | ||
| return rt.GetCellValue(e).AsExt() | ||
| case *wiop.CoinField: | ||
| return rt.GetCoinValue(e).AsExt() | ||
| case *wiop.Constant: | ||
| return field.Lift(e.Value) | ||
| case *wiop.ArithmeticOperation: | ||
| eval := func(i int) field.Ext { | ||
| return evalExprOnCosetExt(rt, e.Operands[i], cosetEvals, cosetEvalsExt, selectorCosets, j, ratio, N) | ||
| return e.vecBase[idx] | ||
| case boundScalarBase: | ||
| return e.scalarBase | ||
| } |
Compressx16Columns now dispatches to a NEON kernel on arm64 (Consensys/gnark-crypto#870) instead of falling back to scalar Go. BenchmarkR5Prove: 4.9s -> 4.1s per op. Signed-off-by: Gautam Botrel <gautam.botrel@gmail.com>
Contributor
Author
|
AlexandreBelling
approved these changes
Aug 7, 2026
| // will not go OOB. | ||
| aux[k] = &leaves[i][j] | ||
| } | ||
| if len(leaves[i]) > 0 { |
gbotrel
enabled auto-merge (squash)
August 10, 2026 21:15
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
BenchmarkR5Prove: 25.5s → 4.1s per op (Apple M5 Max, 18 cores). Proofs are bit-identical.
Profiling showed the prover ran on ~1.4 of 18 cores: 70% of wall time was a serial per-coset-point expression tree walk with a map lookup per column leaf per point.
compilers/global: bind each vanishing expression once (leaves → coset slices / resolved scalars), then accumulate coset points in parallel. Base subtrees stay in base-field arithmetic; a Mul with one base operand usesMulByElementinstead of a full E6 mul. (25.5s → 6.3s)wiop: LagrangeEval evaluates its polynomial batch in parallel. (→ 5.6s)fri: NewTree hashes each Merkle level in parallel. (→ 4.9s)go.mod: bump gnark-crypto so Poseidon2Compressx16Columnsuses the new NEON kernel on arm64 (perf(koalabear): NEON kernel for Compressx16Columns (arm64) Consensys/gnark-crypto#870) instead of scalar Go. (→ 4.1s)zkcdriver: add R5 benchmarks (trace, check, assign, compile, prove, verify, cold e2e).