Add GPU-accelerated PLONK prover via ICICLE - #1800
Open
martun wants to merge 5 commits into
Open
Conversation
martun
force-pushed
the
neon/gpu-plonk
branch
3 times, most recently
from
July 27, 2026 14:07
9cfa352 to
355bc51
Compare
…381, bw6-761) Ports the Gnark_plonk_on_GPU work onto current gnark master: - New backend/accelerated/icicle/plonk/ package: full PLONK prover on GPU (NTT, MSM, quotient, linearization, KZG openings via ICICLE), selected with -tags=icicle. Like the accelerated Groth16 backend, the per-curve packages are generated from templates by backend/accelerated/icicle/internal/generator for all four supported curves. Stage-timing breakdown under ICICLE_STEP_PROFILE. - Solver output caching for the accelerated backends: raw wire-value cache for the ICICLE PLONK prover (GNARK_RAW_SOLVER_CACHE) and a WithSolutionCachePath prover option for ICICLE Groth16 (now part of the groth16 template, all curves). Cached data is validated against the circuit's wire count before use. Helpers are generated into constraint/<curve>/solution_cache.go; no gnark-generator files are modified. - Blinding is on by default, matching the native prover. Setting GNARK_DISABLE_BLINDING trades zero-knowledge for a faster, deterministic GPU prover. - Tests: KZG-MSM and NTT GPU/CPU parity, proving-key marshal roundtrip, accelerated setup, and a 2^12-constraint end-to-end prove/verify with a BSB22 commitment on every supported curve (all behind -tags=icicle). - Depends on the extended icicle-gnark fork (batch inverse, poly eval, permutation supports, shard merge - see ingonyama-zk/icicle-gnark#4) via a local replace directive to ../icicle-gnark-extended.
Zero-knowledge: disable the raw solver cache (GNARK_RAW_SOLVER_CACHE) for circuits with BSB22 commitments unless GNARK_DISABLE_BLINDING is set. Replaying the cached commitment polynomials reuses their random blinding rows across proofs, making Bsb22Commitments linkable and leaking linear relations over the committed private wires once the polynomial has been opened at more than two distinct zetas. Re-randomizing on load is not possible because the commitment hash feeds back into a witness wire. GPU resource lifecycle: - Pair every CUDA stream in the NTT fan-outs with synchronize + destroy on all paths (previously ~50+ streams leaked per prove). - Drain all NTT worker channels before returning on error so the caller cannot free device slices sibling workers are still writing to; return the scaling-vector pool slices on every exit path. - Track outstanding pool slices (keyed by device pointer; CopyToDevice mutates the slice length field) and reclaim buffers abandoned by error paths in a new pool Shutdown at Prove teardown; mid-prove FreeAll keeps its idle-only semantics. Backstop hGPU / linearizedPolynomialGPU / blindedZCanonicalGPU / polyZLagrangeGPU there as well. - Return the linearized polynomial to the pool instead of direct-freeing it (it is a pool buffer; the bypass surfaced as a double free once outstanding tracking existed). - Convert device-goroutine and prover-stage panics (GPU allocation failure is the expected failure mode) into Prove errors via recover boundaries: devicePanicToError on error-channel closures, inline recover in runErr defers, a goStage wrapper around the errgroup stages, and error returns from setupDevicePointers. - gpuEvaluateConstraints frees all temporaries via a cleanup defer on every path; blinding vectors now come from the temp pool. Adds icicle-tagged regression tests: commitment-free cache round-trip, cache skipped for BSB22 circuits with fresh commitments per prove, and BSB22 replay under GNARK_DISABLE_BLINDING. Verified on RTX PRO 6000 / CUDA 13: full -tags=icicle plonk suite passes on all four curves; gofmt/go vet clean with and without the tag; the checked-in generated files match the generator output. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
Reviewed by Cursor Bugbot for commit b1d7c37. Configure here.
- annotate the atomic cache writes with #nosec G703: the temp file comes from os.CreateTemp in the caller-chosen cache directory and the rename target is the caller-provided cache location (gosec taint analysis flags them as path traversal) - replace deprecated fft.BitReverse with gnark-crypto utils.BitReverse - merge a variable declaration with its assignment (S1021) golangci-lint v2.10.1 (CI's version/config) now reports 0 issues on the default build and 0 issues on ./backend/accelerated/icicle/plonk/... + ./constraint/... with -tags=icicle. Remaining icicle-tagged findings are in the pre-existing accelerated groth16 code, untouched by this PR. Full icicle plonk suite re-verified on GPU after the BitReverse swap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
devicePanicToError converts GPU allocation panics into Prove errors, but closures that create a stream and only release it inside their finish / finalize callback skipped that cleanup when the panic hit between CreateStream and the callback — leaking one stream per recovered OOM, which in a long-lived process eventually exhausts streams and fails later proves. makeFinisher now also returns a guard to defer right after stream creation: if finish never ran, the guard synchronizes and destroys the stream (a no-op on all non-panic paths). The four download closures that released their stream at the tail are converted to the same finish/guard pattern, and openZ's finalize and the eval closure's local finish get equivalent flag-based guards (the latter also releasing its owned eval buffer). Verified on GPU: full -tags=icicle plonk suite 6/6 consecutive runs plus the GNARK_DISABLE_BLINDING replay test; golangci-lint clean (default and icicle-tagged); regenerated files match the generator output. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The cached R1CSSolution was accepted after checking only the wire count, but a circuit edit can keep the wire count while changing the constraint count, leaving A/B/C the wrong size. That reaches computeH, whose padding of Domain.Cardinality-len(a) panics when the stale vectors are larger than the domain, or silently proves a stale assignment otherwise. The load path now also requires len(A) == len(B) == len(C) == GetNbConstraints() and falls back to solving with a warning on any mismatch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

Description
Ports the Gnark_plonk_on_GPU work onto current gnark master.
backend/accelerated/icicle/plonk/package: full PLONK prover on GPU (NTT, MSM, quotient, linearization, KZG openings via ICICLE), selected with-tags=icicle. Like the accelerated Groth16 backend, the per-curve packages are generated from templates (backend/accelerated/icicle/internal/generator) for all four supported curves: BN254, BLS12-377, BLS12-381, BW6-761. Stage-timing breakdown available underICICLE_STEP_PROFILE.GNARK_RAW_SOLVER_CACHE) and aWithSolutionCachePathprover option for ICICLE Groth16. Cached data is validated against the circuit's wire count before use. Helpers are generated intoconstraint/<curve>/solution_cache.go; the Groth16 cache hook now lives in the groth16 template (all curves). No files owned by the main gnark generator are modified.GNARK_DISABLE_BLINDINGtrades zero-knowledge for a faster, deterministic GPU prover.../icicle-gnark-extended. This PR should stay in draft until that dependency is merged and released, at which point the replace will be swapped for a released version.Type of change
How has this been tested?
All behind
-tags=icicle, on an RTX PRO 6000 (CUDA 13.0) against the icicle-gnark#4 libraries:TestEndToEndLargeCircuit— 2^12-constraint circuit with a BSB22 commitment: accelerated setup → GPU prove → verify on all four curves (and on bn254: default blinding on, withGNARK_DISABLE_BLINDING, and with the raw solver cache save/load round-trip)TestMarshalBN254— proving-key marshal roundtrip; the same circuit proven natively and on GPU, both proofs verifiedTestSetupBN254,TestKZGCommitmentICICLEMSMParity,TestGpuNTTInverseBatch_matchesCPUToCanonicalgo test -short ./backend/plonk/... ./constraint/bn254/...,go vet,gofmt, builds with and without-tags=icicleHow has this been benchmarked?
Checklist:
golangci-lintdoes not output errors locallyNote
Medium Risk
Stale or mismatched cache files could produce invalid proofs if validation is incomplete; caching is disabled for commitment circuits but raw-cache ZK caveats apply when blinding is off.
Overview
Adds solver output caching for ICICLE accelerated proving so repeated runs on the same circuit can skip the constraint solver.
README documents the feature:
GNARK_RAW_SOLVER_CACHEfor PLONK raw wire values,WithSolutionCachePathfor Groth16 fullR1CSSolution, ZK/BSB22 caveats, and benchmark numbers.ICICLE code generator now also emits PLONK per-curve packages and generates
constraint/<curve>/solution_cache.gofrom a new template (raw wire save/load, LRO derivation,SolveAndSaveRawValues, Groth16 solution save/load).ICICLE Groth16
Prove(all curves, via template): whenSolutionCachePathis set and there are no BSB22 commitments, loads a cachedR1CSSolutionafter validating wire and constraint vector lengths; otherwise solves and atomically writes the cache.Reviewed by Cursor Bugbot for commit 2150115. Bugbot is set up for automated code reviews on this repo. Configure here.