feat(ppsnark): improve ppsnark memory-check with Logup-GKR#501
Conversation
Replace the ppSNARK inverse-logup memory-check with a Logup-GKR fractional-sum argument, selectable by cargo feature. - Default: Logup-GKR. A fractional-sum GKR argument (src/spartan/logup_gkr/) folds the four row/col x table/access sub-instances into per-instance trees, with Horner lambda-batching (distinct powers per num/den) and MSB-first fold. - logup-no-gkr: the original inverse-logup memory-check (main's behaviour). The two paths are mutually exclusive and each compiles only its own code, so the GKR default drops the four inverse-oracle commitments and the six-route sumcheck. - Host bridge (src/spartan/mem_check_logup_gkr.rs) reconciles the claimed fingerprint columns against the GKR-reduced input claims (component-wise equality), runs the row/col balance check with a nonzero-denominator guard, and carries the seven reconcile columns into the inner batched sumcheck via a RerandomizeSumcheckInstance landing at r_inner_batched. - Verifier binds GKR depth to log N (rejects mismatched depth with NovaError instead of panicking) and uses exact fraction equality at the root gate and host reconcile to close the (0,0) projective-claim bypass. test_ivc_nontrivial_with_compression passes on both feature paths.
clippy 1.97 adds `useless_borrows_in_formatting` and `manual_clear`: - drop redundant `&` in write!/writeln! args (test_shape_cs pretty_print) - use Vec::clear() instead of truncate(0) (num gadget)
Proof Size Change
Methodology
Notation & parameters
All outer/inner sumcheck round-polys and GKR layer-polys are cubic (3 1. Overall proof layout (
|
| log num_cons | (c, d) | Proof Baseline (FG) | Baseline (KB) | Proof GKR (FG) | GKR (KB) | Δ (KB, ×) |
|---|---|---|---|---|---|---|
| 10 | (11,12) | 20 G + 124 F | 4.71 | 16 G + 525 F | 17.84 | +13.12 (3.8×) |
| 12 | (13,14) | 22 G + 142 F | 5.37 | 18 G + 650 F | 22.06 | +16.70 (4.1×) |
| 14 | (15,16) | 24 G + 160 F | 6.02 | 20 G + 787 F | 26.70 | +20.67 (4.4×) |
| 16 | (17,18) | 26 G + 178 F | 6.68 | 22 G + 936 F | 31.73 | +25.05 (4.8×) |
| 18 | (19,20) | 28 G + 196 F | 7.34 | 24 G + 1097 F | 37.18 | +29.84 (5.1×) |
3b. MemCheck payload — Baseline vs GKR (with delta)
| log num_cons | (c, d) | MemCheck Baseline (FG) | Base (KB) | MemCheck GKR (FG) | GKR (KB) | Δ GKR−Base (FG / KB / ×) |
|---|---|---|---|---|---|---|
| 10 | (11,12) | 4 G + 4 F | 0.25 | 405 F | 13.38 | −4 G + 401 F / +13.12 / 53.5× |
| 12 | (13,14) | 4 G + 4 F | 0.25 | 512 F | 16.95 | −4 G + 508 F / +16.70 / 67.8× |
| 14 | (15,16) | 4 G + 4 F | 0.25 | 631 F | 20.92 | −4 G + 627 F / +20.67 / 83.7× |
| 16 | (17,18) | 4 G + 4 F | 0.25 | 762 F | 25.30 | −4 G + 758 F / +25.05 / 101.2× |
| 18 | (19,20) | 4 G + 4 F | 0.25 | 905 F | 30.09 | −4 G + 901 F / +29.84 / 120.4× |
The total-proof Δ (KB) equals the MemCheck Δ (KB) exactly — the shared skeleton is
byte-identical, so swapping the memory-check is the only difference. The two ratio
columns differ (total 3.8×–5.1× vs memcheck 53×–120×) because the shared part dilutes
the total ratio. GKR drops 4 G (the four inverse-oracle commitments) and pays it back
many times over in F.
Raw measured bytes (for reference):
| log num_cons | Proof Baseline (B) | Proof GKR (B) | MemCheck Baseline (B) | MemCheck GKR (B) | Witness (num_vars) | Witness size |
|---|---|---|---|---|---|---|
| 10 | 4824 | 18264 | 256 | 13696 | 2048 | 64 KB |
| 12 | 5496 | 22592 | 256 | 17352 | 8192 | 256 KB |
| 14 | 6168 | 27336 | 256 | 21424 | 32768 | 1024 KB |
| 16 | 6840 | 32496 | 256 | 25912 | 131072 | 4096 KB |
| 18 | 7512 | 38072 | 256 | 30816 | 524288 | 16384 KB |
Witness size = num_vars * 32 B — the raw R1CS witness the ppSNARK compresses.
Reuse preceding-layer left final claims as the first sumcheck round's t(0), so the cached-delta pass only evaluates t(inf). The verifier and proof format are unchanged. At 20 variables, paired GKR prove benchmarks reduce median time by about 15 ms (~8%).
Build the outer sum-check third input uCz_E = u*Cz + E with a parallel iterator instead of a serial one. The map is memory-parallel and was needlessly serial: it shows a 4.2x-8.9x speedup on the construction itself (1.06ms -> 0.26ms at 2^16, 15.6ms -> 1.75ms at 2^20). The outer sum-check and all downstream logic are unchanged, so this is a zero-risk net win. End-to-end prover impact is ~0.1% (below the benchmark noise floor).
Summary
This PR improves preprocessing Spartan (ppSNARK), primarily by replacing its inverse-logup memory-check with a Logup-GKR argument. The new default avoids committing to four inverse-oracle polynomials and reduces the final batched PCS opening from 15 polynomials to 11.
The trade-off is proof size: Logup-GKR adds a field-only proof whose size is quadratic in
log N. In return, the measured end-to-end prover is about 1.3x faster at medium and large circuit sizes, corresponding to roughly 23-24% less wall time in the current A/B benchmark.The original inverse-logup implementation remains available behind the
logup-no-gkrfeature for compatibility, comparison, and deployments that prefer the smaller proof.Motivation
The previous ppSNARK memory-check proves, independently for the row and column memories,
Its prover materializes four inverse-oracle vectors through two batch inversions, commits to all four vectors, proves four inverse-validity claims in addition to the two balance claims, and includes the four inverse polynomials in the final batched PCS opening. At large
N, the four length-Ncommitments dominate the memory-check wall time.Logup-GKR keeps the fractions in projective form and proves their reduction through fractional-add trees. It therefore removes the need to materialize or commit to the inverse polynomials.
Protocol changes
Logup-GKR memory-check
The new memory-check builds four equal-height input layers:
Each layer is folded with the projective fractional-add gate
The prover and verifier batch the four trees at every layer. The verifier then:
eval_pointand reconciles them with the GKR result;The GKR reduction adds
O(log N)layer sumchecks, with a decreasing number of rounds per layer. It contributesO(log^2 N)field elements to the proof.Opening-point reduction
GKR finishes at its own
eval_point, while the existing ppSNARK inner sumcheck finishes atr_inner_batched. A rerandomization sumcheck carries the seven columns needed by the host reconciliation fromeval_pointtor_inner_batched:This rerandomization claim is included in the existing batched inner sumcheck, so all committed polynomials are still opened once at
r_inner_batched.What is removed and added
Removed from the default path:
Ninverse-oracle commitments;15 -> 11polynomials).Added to the default path:
O(log N)layer sumchecks;eval_point;r_inner_batched.The evaluation engine still receives one RLC-batched length-
Npolynomial, so the IPA, HyperKZG, or Mercury evaluation argument itself does not become smaller. The15 -> 11reduction saves construction work beforeEvaluationEngine::prove; the main wall-time saving comes from removing the four inverse-oracle commitments.Feature gate
Logup-GKR is the default memory-check. The original inverse-logup protocol can be selected at compile time:
The two configurations have different ppSNARK proof layouts. A proof must be verified with the same feature configuration that produced it.
Sumcheck optimizations
Reuse cached multilinear deltas
For a multilinear polynomial split into low and high halves, both round evaluation and binding need the slope
The evaluation pass now stores
deltain the high half. After the transcript produces the round challenge, binding reuses it directly:This avoids recomputing the subtraction during the bind pass without changing the round polynomial, transcript, or proof. The optimization is applied to:
L_row * L_col * val) and itsEevaluation claim;The default Logup-GKR path fuses the rerandomization columns as described below, so that path binds one combined polynomial instead of seven cached polynomials.
Fuse the seven rerandomization columns
Once
prove_helpersamples the inner-batch coefficients, the seven rerandomization columns and claims are collapsed into one random linear combination. This reduces the rerandomization state from approximately7Nfield elements toNand changes each inner-sumcheck round from seven column scans/binds to one.The fusion is linear in the same coefficients already used by the batched sumcheck. It preserves the transcript and proof bytes. In the focused A/B measurement, it reduced the inner-batch phase by approximately 25-33% at
2^18and2^20constraints.Optimize GKR layer sumchecks
The GKR prover also:
EqSumCheckInstanceinstead of materializing and rebinding a full equality MLE;N-scaling sums instead of three in the common case;lambdabatching.Memory management
This PR shortens the lifetime of large prover buffers before later ppSNARK phases allocate their own length-
Ndata:Here,
mis the constraint count used by the shortened outer sumcheck andNis the padded inner-domain size.poly_Az,poly_Bz,poly_uCz_E, andCzafter the outer claims are computed (approximately4mfield elements);NEandWvectors are materialized (approximately2mfield elements);zafterevaluation_oracles;These lifetime changes do not affect the transcript or proof.
Performance
The following numbers compare the current default Logup-GKR path with
--features logup-no-gkrusingBn256EngineKZG+ HyperKZG on an Apple M-series machine with 14 cores. Each row is the median of 10 sequential repetitions after one warm-up.2^162^182^20At
2^20, the isolated memory-check phase improves from 3.649 s to 0.834 s, or approximately 4.4x. This phase accounts for essentially the entire end-to-end difference; the twoLcommitments and the final HyperKZG opening are shared costs.The same
2^20run measured peak RSS at 9.09 GB for Logup-GKR and 10.92 GB for inverse-logup. RSS remains high because ppSNARK and HyperKZG retain several length-Nbuffers; this PR reduces it but does not claim to solve the full prover-memory problem.Proof-size trade-off
Logup-GKR deliberately exchanges proof size for prover time. Let
d = log2(N). For four GKR sub-instances, its memory-check-specific payload is approximatelyAt
N = 2^20, this is 905 field elements, or about 28 KiB for 32-byte field elements, before container and serialization metadata. The inverse-logup memory-check instead carries four commitments and four field evaluations.The shared evaluation argument has the same size in both modes because all PCS inputs are RLC-batched before the evaluation engine is invoked. Users for whom proof bytes, network transfer, or calldata dominate can retain inverse-logup through
logup-no-gkr.Correctness and review notes
num_varsand does not add generic padding machinery.lambda; per-instance batching uses distinct Horner powers.logup-no-gkris the explicit legacy fallback.Validation
Validated during development under both memory-check configurations:
cargo fmt --all -- --checkcargo clippy --all-targets -- -D warningscargo clippy --all-targets --features logup-no-gkr -- -D warningscargo test --release test_ivc_nontrivial_with_compressioncargo test --release --features logup-no-gkr test_ivc_nontrivial_with_compression