Skip to content

feat(ppsnark): improve ppsnark memory-check with Logup-GKR#501

Open
Sun-Jc wants to merge 2 commits into
microsoft:mainfrom
Sun-Jc:feat/ppsnark/gkr
Open

feat(ppsnark): improve ppsnark memory-check with Logup-GKR#501
Sun-Jc wants to merge 2 commits into
microsoft:mainfrom
Sun-Jc:feat/ppsnark/gkr

Conversation

@Sun-Jc

@Sun-Jc Sun-Jc commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

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-gkr feature for compatibility, comparison, and deployments that prefer the smaller proof.

Motivation

The previous ppSNARK memory-check proves, independently for the row and column memories,

sum_i ts[i] / (T[i] + r) = sum_i 1 / (W[i] + r).

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-N commitments 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:

row table:   ts_row / (T_row + r)
row access:       -1 / (W_row + r)
col table:   ts_col / (T_col + r)
col access:       -1 / (W_col + r)

Each layer is folded with the projective fractional-add gate

(n_l, d_l) + (n_r, d_r)
  = (n_l * d_r + n_r * d_l, d_l * d_r).

The prover and verifier batch the four trees at every layer. The verifier then:

  1. verifies the GKR layer reductions;
  2. reconstructs the four input fractions from the claimed column evaluations at the shared eval_point and reconciles them with the GKR result;
  3. checks that the row and column table/access roots each sum to zero; and
  4. binds the claimed columns to the committed ppSNARK polynomials through the batched inner sumcheck and final PCS opening.

The GKR reduction adds O(log N) layer sumchecks, with a decreasing number of rounds per layer. It contributes O(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 at r_inner_batched. A rerandomization sumcheck carries the seven columns needed by the host reconciliation from eval_point to r_inner_batched:

L_row, L_col, row, col, ts_row, ts_col, mem_col.

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:

  • two batch inversions that produce four inverse-oracle vectors;
  • four length-N inverse-oracle commitments;
  • the old six-claim memory sumcheck, including four inverse-validity claims;
  • four inverse polynomials and evaluations from the batched PCS input (15 -> 11 polynomials).

Added to the default path:

  • four projective fractional-sum trees;
  • one batched GKR argument across the four trees, with O(log N) layer sumchecks;
  • seven claimed column evaluations at the GKR eval_point;
  • a rerandomization sumcheck that aligns the GKR final claim with r_inner_batched.

The evaluation engine still receives one RLC-batched length-N polynomial, so the IPA, HyperKZG, or Mercury evaluation argument itself does not become smaller. The 15 -> 11 reduction saves construction work before EvaluationEngine::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:

# Default: Logup-GKR
cargo test --release

# Legacy inverse-logup memory-check
cargo test --release --features logup-no-gkr

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

delta = high - low.

The evaluation pass now stores delta in the high half. After the transcript produces the round challenge, binding reuses it directly:

low <- low + r * cached_delta.

This avoids recomputing the subtraction during the bind pass without changing the round polynomial, transcript, or proof. The optimization is applied to:

  • the ppSNARK batched inner sumcheck's cubic ABC claim (L_row * L_col * val) and its E evaluation claim;
  • the four-instance Logup-GKR fractional-add layer sumcheck; and
  • the multi-column rerandomization implementation when it is used without coefficient fusion.

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_helper samples the inner-batch coefficients, the seven rerandomization columns and claims are collapsed into one random linear combination. This reduces the rerandomization state from approximately 7N field elements to N and 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^18 and 2^20 constraints.

Optimize GKR layer sumchecks

The GKR prover also:

  • reuses EqSumCheckInstance instead of materializing and rebinding a full equality MLE;
  • uses Gruen equality factoring so the equality-polynomial bind is constant time per round;
  • uses BDDT claim derivation to compute two N-scaling sums instead of three in the common case;
  • evaluates the four ppSNARK sub-instances in one parallel pass, hoisting the shared equality factor out of the instance loop;
  • computes round data directly and parallelizes only above measured crossover thresholds; and
  • uses Horner-style powers for per-instance lambda batching.

Memory management

This PR shortens the lifetime of large prover buffers before later ppSNARK phases allocate their own length-N data:

Here, m is the constraint count used by the shortened outer sumcheck and N is the padded inner-domain size.

  • release poly_Az, poly_Bz, poly_uCz_E, and Cz after the outer claims are computed (approximately 4m field elements);
  • release the padded relaxed witness after the length-N E and W vectors are materialized (approximately 2m field elements);
  • release the local padded R1CS shape, its lazy sparse-matrix caches, and the assignment z after evaluation_oracles;
  • move, rather than clone, GKR child buffers into each layer sumcheck; and
  • consume and drop every GKR tree layer immediately after proving it.

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-gkr using Bn256EngineKZG + HyperKZG on an Apple M-series machine with 14 cores. Each row is the median of 10 sequential repetitions after one warm-up.

Constraints Logup-GKR Inverse-logup Wall-time reduction Speedup
2^16 0.667 s 0.875 s 23.7% 1.31x
2^18 2.387 s 3.145 s 24.1% 1.32x
2^20 8.789 s 11.405 s 22.9% 1.30x

At 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 two L commitments and the final HyperKZG opening are shared costs.

The same 2^20 run 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-N buffers; 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 approximately

initial roots:                       8 scalars
per-layer split final claims:      16d scalars
compressed layer round polynomials: 3d(d - 1) / 2 scalars
rerandomization claims:              7 scalars
total:                 15 + 16d + 3d(d - 1) / 2 scalars.

At 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

  • The Logup-GKR core is specialized to ppSNARK's four equal-height row/column table/access instances. It asserts equal num_vars and does not add generic padding machinery.
  • The verifier defines the transcript order and independently checks every root gate, layer sumcheck, fold-down claim, host reconciliation, and final balance claim.
  • Every layer samples a fresh Fiat-Shamir lambda; per-instance batching uses distinct Horner powers.
  • Performance-only changes preserve the transcript and proof output.
  • The default proof format changes because the memory-check protocol changes; logup-no-gkr is the explicit legacy fallback.

Validation

Validated during development under both memory-check configurations:

  • cargo fmt --all -- --check
  • cargo clippy --all-targets -- -D warnings
  • cargo clippy --all-targets --features logup-no-gkr -- -D warnings
  • Spartan unit tests, including adversarial Logup-GKR verifier tests
  • cargo test --release test_ivc_nontrivial_with_compression
  • cargo test --release --features logup-no-gkr test_ivc_nontrivial_with_compression

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.
@Sun-Jc Sun-Jc changed the title feat(ppsnark): replace the inverse-logup memory-check with Logup-GKR feat(ppsnark): improve ppsnark memory-check with Logup-GKR Jul 14, 2026
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)
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