Skip to content

perf(prover-ray): 5x faster R5 prove - #3736

Open
gbotrel wants to merge 3 commits into
mainfrom
perf/prover1
Open

perf(prover-ray): 5x faster R5 prove#3736
gbotrel wants to merge 3 commits into
mainfrom
perf/prover1

Conversation

@gbotrel

@gbotrel gbotrel commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

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 uses MulByElement instead 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 Poseidon2 Compressx16Columns uses 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).

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>
@gbotrel
gbotrel requested review from AlexandreBelling and a lite review from Copilot August 6, 2026 16:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@gbotrel

gbotrel commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author
  • the NEON kernel for P2 speed up the PCS commit x4 on Mac , good for dev.

// will not go OOB.
aux[k] = &leaves[i][j]
}
if len(leaves[i]) > 0 {
@gbotrel
gbotrel enabled auto-merge (squash) August 10, 2026 21:15
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.

3 participants