Skip to content

Reduce memory in SumCheck::prove via move semantics#326

Merged
niconiconi merged 1 commit into
mainfrom
hc/sumcheck_move_semantics
Mar 12, 2026
Merged

Reduce memory in SumCheck::prove via move semantics#326
niconiconi merged 1 commit into
mainfrom
hc/sumcheck_move_semantics

Conversation

@hczphn
Copy link
Copy Markdown
Contributor

@hczphn hczphn commented Feb 22, 2026

Summary

  • Add prover_init_owned() to IOPProverState that takes SumOfProductsPoly by value, eliminating the clone() in prover_init()
  • Change SumCheck::prove() signature from &SumOfProductsPoly<F> to SumOfProductsPoly<F> (ownership)
  • Update prover_merge_points in batching to pass sumcheck_poly by move
  • The original prover_init() (borrow + clone) is preserved for callers that need it

Motivation

During batched polynomial commitment opening, SumCheck::prove internally clones the entire polynomial list via prover_init. For large proving workloads (e.g., ZKML with many kernels), this clone can consume significant memory. By accepting ownership, we eliminate one full copy of the polynomial data.

🤖 Generated with Claude Code

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @hczphn, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly reduces memory consumption during batched polynomial commitment opening by refactoring the SumCheck::prove function to leverage Rust's move semantics. By accepting ownership of polynomial data instead of cloning it, the change eliminates a substantial memory overhead, particularly beneficial for large proving workloads. Additionally, it introduces buffered I/O and parallel deserialization for Structured Reference Strings (SRS) to improve performance during setup and adds a new utility for PCS initialization with optional SRS caching.

Highlights

  • Memory Optimization: Introduced prover_init_owned() in IOPProverState to accept SumOfProductsPoly by value, removing an internal clone.
  • API Change: Updated SumCheck::prove() signature to take ownership of SumOfProductsPoly<F>.
  • Batching Integration: Modified prover_merge_points in batching to use move semantics for sumcheck_poly.
  • Backward Compatibility: Retained the original prover_init() for compatibility where borrowing is required.
  • SRS Serialization Performance: Implemented custom parallel serialization and deserialization for KZG SRS using uncompressed points and buffered I/O.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • gkr_engine/src/poly_commit/definition.rs
    • Imported BufReader and BufWriter for efficient file I/O.
    • Defined SRS_IO_BUFFER_CAPACITY constant for buffered operations.
    • Modified gen_or_load_srs_for_testing to use BufReader and BufWriter when loading or saving SRS.
  • poly_commit/src/batching.rs
    • Changed the call to SumCheck::prove to pass sumcheck_poly by value, utilizing the new ownership-based API.
  • poly_commit/src/kzg/uni_kzg/structs_kzg.rs
    • Added necessary imports for PrimeCurveAffine, UncompressedEncoding, rayon::prelude, std::io::{Read, Write}.
    • Removed the ExpSerde derive macro from CoefFormUniKZGSRS.
    • Implemented a custom ExpSerde trait for CoefFormUniKZGSRS to enable parallel serialization and deserialization of G1 affine points using uncompressed format, improving performance.
  • poly_commit/src/utils.rs
    • Added a new public function expander_pcs_init_with_srs_path that allows specifying an optional path for SRS caching.
    • Updated expander_pcs_init_testing_only to call the new expander_pcs_init_with_srs_path with a temporary SRS file path.
  • serdes/src/serdes.rs
    • Optimized Vec<V>::deserialize_from by pre-allocating vector capacity based on the deserialized length.
  • sumcheck/src/sumcheck_generic.rs
    • Updated the SumCheck::prove function signature to accept SumOfProductsPoly<F> by value.
    • Modified the implementation to call IOPProverState::prover_init_owned instead of prover_init.
  • sumcheck/src/sumcheck_generic/prover.rs
    • Introduced prover_init_owned function to IOPProverState which initializes the prover state by taking ownership of the SumOfProductsPoly, thereby avoiding an internal clone.
  • sumcheck/src/sumcheck_generic/tests.rs
    • Adjusted calls to SumCheck::prove in tests to either clone the mle_list explicitly or pass padded_mle_list by value, aligning with the updated prove signature.
Activity
  • The author implemented prover_init_owned() and updated SumCheck::prove() to use move semantics, addressing memory consumption.
  • Test calls to SumCheck::prove were updated to handle the change in ownership.
  • Existing sumcheck tests were confirmed to pass.
  • cargo test -p sumcheck and cargo test -p poly_commit were run as part of the test plan.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces significant memory and performance optimizations, primarily by refactoring the SumCheck::prove function to leverage move semantics for SumOfProductsPoly, thereby eliminating an unnecessary clone. Additionally, it enhances SRS (Structured Reference String) handling by implementing buffered I/O for serialization/deserialization and parallel processing for KZG SRS, which will lead to faster loading times for large SRS files. The changes also include minor performance improvements like pre-allocating vector capacity during deserialization and better code organization for PCS initialization functions.

Comment on lines +77 to +83
buffer
.par_chunks_mut(point_size)
.zip(self.powers_of_tau.par_iter())
.for_each(|(chunk, point)| {
let uncompressed = point.to_uncompressed();
chunk.copy_from_slice(uncompressed.as_ref());
});
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.

high

Parallelizing the conversion of points to uncompressed format using par_chunks_mut and par_iter from rayon is a significant performance optimization for serializing large SRS. This will greatly reduce the time taken for SRS serialization.

Comment on lines +36 to +57
pub fn prover_init_owned(polynomials: SumOfProductsPoly<F>) -> Self {
let num_vars = polynomials.num_vars();
let init_sum_of_vals: Vec<F> = polynomials
.f_and_g_pairs
.par_iter()
.map(|(f, g)| {
f.coeffs
.iter()
.zip(g.coeffs.iter())
.map(|(&f, &g)| f * g)
.sum::<F>()
})
.collect();
let eq_prefix = vec![F::one(); polynomials.f_and_g_pairs.len()];
Self {
challenges: Vec::with_capacity(num_vars),
round: 0,
init_num_vars: num_vars,
mle_list: polynomials,
init_sum_of_vals,
eq_prefix,
}
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.

high

The prover_init_owned function is correctly implemented to accept polynomials by value, directly moving it into mle_list. This is the key mechanism for the memory optimization, as it prevents the SumOfProductsPoly from being cloned, which was identified as a significant memory consumer.

let num_vars = poly_list.num_vars();

let mut prover_state = IOPProverState::prover_init(poly_list);
let mut prover_state = IOPProverState::prover_init_owned(poly_list);
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.

high

Calling prover_init_owned instead of prover_init ensures that the SumOfProductsPoly is moved into the prover state, avoiding a costly clone operation. This is a direct implementation of the PR's memory optimization objective.

/// Takes ownership of poly_list to avoid cloning internally.
pub fn prove(
poly_list: &SumOfProductsPoly<F>,
poly_list: SumOfProductsPoly<F>,
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.

high

Changing the poly_list parameter from a reference (&SumOfProductsPoly<F>) to a value (SumOfProductsPoly<F>) is the core change enabling move semantics. This directly addresses the memory optimization goal by transferring ownership and preventing an implicit clone within the prove function.

sumcheck_poly.add_pair(tilde_g.clone(), tilde_eq);
}
let proof = SumCheck::<C::Scalar>::prove(&sumcheck_poly, transcript);
let proof = SumCheck::<C::Scalar>::prove(sumcheck_poly, transcript);
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.

high

Changing SumCheck::<C::Scalar>::prove to take sumcheck_poly by value (move semantics) directly addresses the PR's goal of reducing memory consumption by avoiding an implicit clone. This is a crucial optimization for large polynomial workloads.

Comment on lines +34 to +35
/// Initialize the prover state by taking ownership of the polynomials,
/// avoiding the clone in `prover_init`.
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.

medium

The doc comment for prover_init_owned clearly explains its purpose: to initialize the prover state by taking ownership of polynomials and avoiding an internal clone. This is good documentation for a new, optimized function.

Ok(file) => {
// file exists; deserialize SRS from file
Self::SRS::deserialize_from(&mut file).unwrap_or_else(|_| {
let mut reader = BufReader::with_capacity(SRS_IO_BUFFER_CAPACITY, file);
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.

medium

Introducing BufReader with a specified capacity is a good performance optimization for reading large SRS files. It reduces the number of system calls and can significantly speed up deserialization.

// prover
let mut transcript = BytesHashTranscript::<Keccak256hasher>::new();
let proof = SumCheck::<Fr>::prove(&mle_list, &mut transcript);
let proof = SumCheck::<Fr>::prove(mle_list.clone(), &mut transcript);
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.

medium

The addition of .clone() here is necessary because mle_list is used again later in the test for evaluation (mle_list.evaluate(&subclaim.point)). This correctly adapts the test to the new prove signature while preserving the original test logic.

let claimed_sum = mle_list.sum();

let proof = SumCheck::prove(&mle_list, &mut T::new());
let proof = SumCheck::prove(mle_list.clone(), &mut T::new());
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.

medium

Similar to the previous comment, mle_list.clone() is correctly added here because mle_list is needed for subsequent operations in the test, such as creating padded_mle_list and evaluating against subclaim.point.

};

let proof_with_padded_mle_list = SumCheck::prove(&padded_mle_list, &mut T::new());
let proof_with_padded_mle_list = SumCheck::prove(padded_mle_list, &mut T::new());
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.

medium

Passing padded_mle_list directly (by value) is correct here because padded_mle_list is not used after this call. This demonstrates the intended use of the new prove signature, where ownership is transferred, and no unnecessary cloning occurs.

…oning

Add `prover_init_owned()` that takes `SumOfProductsPoly` by value,
avoiding the expensive clone inside `prover_init()`. Change
`SumCheck::prove()` to accept ownership and use the new init path.

This eliminates one full copy of the polynomial list during proving,
which can be significant for large batched polynomial commitments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@niconiconi niconiconi force-pushed the hc/sumcheck_move_semantics branch from f4c9a82 to 71777e7 Compare March 12, 2026 02:42
@niconiconi niconiconi merged commit b21233b into main Mar 12, 2026
0 of 9 checks passed
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.

2 participants