feat(ppsnark): Batch the Logup-GKR last layer with the Inner sumcheck batch#502
Open
Sun-Jc wants to merge 5 commits into
Open
feat(ppsnark): Batch the Logup-GKR last layer with the Inner sumcheck batch#502Sun-Jc wants to merge 5 commits into
Sun-Jc wants to merge 5 commits into
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)
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).
Replace the default GKR memory-check's independent opening-point reduction (the Logup-GKR argument to its own point, then a seven-column rerandomize sumcheck carrying the reconcile columns into the Inner sumcheck) with a single fused reduction: the GKR argument runs only to its input layer (the prefix), and its last layer shares the n-1 suffix rounds and the final MSB fold f with the Inner sumcheck's three relations, landing both on one shared point r_shared = f || s. This drops the seven-column rerandomize entirely and keeps a single PCS opening. Protocol: - Stage the Logup-GKR argument into prefix + last layer (shared per-round reduction body; standalone byte-equal to the prior monolithic loop). - A fresh beta (sampled after the four initial claims C_G/C_A/C_E/C_W are bound) batches the GKR last layer with the ABC / E / W Inner relations. n-1 shared suffix rounds send one fused cubic; the boundary uses h_sum = e_suffix - G_end (G_end reconstructed by the verifier from the input splits, never absorbed), then one shared fold f closes both. - Verifier checks the Inner endpoint h(f) == inner_expected and the GKR input-layer reconcile + balance separately (no RLC merge); component-wise exact fraction equality with four root-den != 0 guards; a forged GKR depth is rejected with Err. - n = 1 keeps the exact component-wise root-gate check. Proof size: -(3d+4) field elements vs the un-fused GKR path (drop the standalone inner-batched sumcheck's 3d coefficients + the 4 rerandomize columns). Prover time is neutral (memory-check is field-bound and dwarfed by the shared MSM stages); the fusion's win is proof size + memory. Two memory-check implementations remain mutually exclusive behind a cargo feature: default = fused Logup-GKR; logup-no-gkr = the original inverse-logup path. Both build and test.
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.
Dependency: Should merge after #501
Summary
The default GKR memory-check currently runs the Logup-GKR fractional-sum argument to its own evaluation point, then a seven-column rerandomize sumcheck carries the reconcile columns into the Inner batched sumcheck's point — two independent
O(N)opening-point reductions of the same length. This PR fuses them: the GKR argument runs only to its input layer (the prefix), and its last layer shares then-1suffix rounds and the final MSB foldfwith the Inner sumcheck's three relations (ABC / E / W), landing both on one shared pointr_shared = f ∥ s. The seven-column rerandomize is deleted and a single PCS opening remains.beta(sampled after the four initial claimsC_G/C_A/C_E/C_Ware bound) batches the GKR last layer with the three Inner relations.n-1shared suffix rounds send one fused cubic; the boundary usesh_sum = e_suffix − G_end, withG_endreconstructed by the verifier from the input splits (never absorbed), then a single foldfcloses both protocols.h(f) == inner_expectedand the GKR input-layer reconcile + balance separately (no RLC merge); component-wise exact fraction equality with four root-denominator≠ 0guards; a forged GKR depth is rejected withErr.n = 1keeps the exact component-wise root gate.Two memory-check implementations remain mutually exclusive behind a cargo feature: default = fused Logup-GKR,
logup-no-gkr= the original inverse-logup path. Both build and test.What this is (and is not)
−(3d+4)field-element win over the un-fused GKR path — drop the standalone inner-batched sumcheck (3dcoefficients) plus the four rerandomize columns.L+ PCS open); fusing the field band does not move end-to-end wall time. The win is proof size + prover memory (one fewer resident/committed column set).O(d²), field-only (0 G) memory-check payload.Benchmark 1 — prover time, canonical 11-stage breakdown (3-way)
num_cons = 2²⁰(N_mem = 2²²), BN254 / HyperKZG,test-utils,DirectSNARK::proveonNonTrivialCircuit. 20 reps per config,PPS_STAGEprobe. ② (un-fused) was measured in afeat/ppsnark/gkrworktree; its GKR runs inside arayon::join, so the probe records sub-stages through a global-Mutexspan buffer to survive work-stealing. Stages 0/1/2/9/10 are config-independent (identicalz+SpMV, outer sumcheck,commit L_row/L_col, extra evals, and the single-polyEE::proveopen), so they carry one pooled value across the three columns — their per-config spread is pure run-to-run noise (the PCS within-config spread alone is4.8–6.4 s, dwarfing the mean gaps). The memory-check band (#3–#8) is the only real difference. This is the taxonomy fromjcbase/bench/2026-07-23-ppsnark-breakdown.md; the sub-stage cell values are each config's own 20-rep mean (ms).logup-no-gkr)feat/ppsnark/gkr)z+ SpMV)Lbuild_input_layers)j = d…2)j = 1)* Both paths also do the inner-instance prep (
val = val_A + c·val_B + c²·val_C,masked_eq, the sumcheck-slot build), which is not counted in the band above: ② overlaps itsbuild_innerwith the GKR viarayon::join(hidden in wall-clock), while ③ currently runs it sequentially before the GKR (~147 ms). It is the same work in both — a scheduling difference, not a fusion cost — so the measured top-level memcheck region is a touch higher than the band sum (② ~1 154, ③ ~1 189 ms). ③ could overlap it too.Reading the table
batch_invert) and committing them (4 × length-NMSM, pure group work) — plus Add support for using bellperson to generate R1CS. #8 ≈ 0.39 s for the six-route inverse-logup sumcheck. ②/③ commit nothing for the memory-check (Make traits public. #3 empty): Support Pasta curves. #4–Add support for using bellperson to generate R1CS. #8 are all field arithmetic (0 G). So switching inverse-logup → GKR removes ~3.5 s of MSM and makes the prover field-bound there: band 4.36 s → ~1.03 s.Nsumchecks (492 ms); ③ carries the Inner relations on the last layer's shared suffix, so Derive more traits. #7 grows to 473 ms and Add support for using bellperson to generate R1CS. #8 → 0 (473 ms). Net for that pair: 492 → 473 ms (−19 ms), so ③'s band (1 025 ms) is marginally under ②'s (1 033 ms) — the algebraic merge is a small field-domain win, and every other GKR sub-stage (Support Pasta curves. #4–Add InputOutputMismatch error. #6) matches within noise.commit L+ PCS = ~86 % of the GKR configs' total), the whole ~1 s field band — and the ±19 ms the fusion moves inside it — is end-to-end noise. ② and ③ have the same prove time; the fusion is a strict improvement on size + memory at ≈zero prove-time cost, not a speedup.pcs(Add a test case that uses the bellperson adapter to generate R1CS #10) is identical across all three by construction —EE::proveopens one length-NRLC polynomial, independent of column count or memory-check; do not attribute any Add a test case that uses the bellperson adapter to generate R1CS #10 difference to fusion.Benchmark 2 — proof size (3-way, theoretical + measured)
Engine
Bn256EngineKZG+ HyperKZG (BN254);G= compressed G1 = 32 B,F= BN254 scalar = 32 B.c= outer rounds =log₂(num_cons)+1;d= inner / memory-check rounds =log₂(num_cons)+2. "Proof" = the ppSNARKRelaxedR1CSSNARKobject; actual =bincode(legacy) serialized length in bytes of a real prove output. Proof structure is byte-identical on this branch — the post-squash quality refactors are transcript-equivalent and touch no serialized type.Measured total bytes
Theoretical composition (crypto elements, then closed-form bytes incl. bincode framing)
(d+8) G + (3c+6d+19) F4 G + 4 F= 256 B(1.5·d(d−1) + 16d + 15) F,0 G52 d² + 476 d + 496(1.5·d(d−1) + 16d + 11) F,0 G52 d² + 476 d + 36852 d² + 708 d + 896−(3d + 4) F−(104 d + 136)(incl. framing)Theory-vs-measured (fused total, closed form
52 d² + 708 d + 896):Where the win comes from. The memory-check field itself shrinks by a flat 128 B (= 4 F) (drop the 7 rerandomize columns, net against the fused suffix's extra split/MSB data). The growing part of the win is the removed standalone
sc_inner_batched(dcubic rounds = 3d F), so the total delta is−(3d+4) F— reproducing the hand-derived bound exactly. Fusing shaves ~6–8 % off the GKR proof but does not close the gap to the flat4 G + 4 Finverse-logup baseline: the GKR memory-check is anO(d²), field-only payload that keeps config ③ 3.5×–4.8× larger than baseline, widening with size.Soundness & tests
crypto-security-reviewer) confirm no false-accept path: the fused MSB-fold boundary red-lines R1–R7 hold (G_endrecomputed from splits, endpoint/reconcile checked separately, splits+hbound beforef, component-wise exact equality + root-den≠ 0,betafresh after the four initial claims,h_sum = e_suffix − G_end, exactn=1root gate). Net added soundness error vs the un-fused path is the freshbeta's3/|F|.Err, not panic),reconcile_and_balancereject paths (mismatched column / unbalanced roots / zero root denominator), and a differential lock tying the fused last-layer gate algebra to the standalone GKR reconstruction.fmt/clippy -D warnings/doc --document-private-items;logup_gkr(28 tests) + fused module (8 tests);test_ivc_nontrivial_with_compressionon both the default (fused) andlogup-no-gkrfeatures.CI note
Both feature configs must build + test — the matrix needs
--features logup-no-gkr(and--features "logup-no-gkr,test-utils") alongside the default (which covers fused Logup-GKR).