Reduce memory in SumCheck::prove via move semantics#326
Conversation
Summary of ChangesHello @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 Highlights
🧠 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
Activity
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this comment.
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.
| 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()); | ||
| }); |
| 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, | ||
| } |
There was a problem hiding this comment.
| 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); |
| /// Takes ownership of poly_list to avoid cloning internally. | ||
| pub fn prove( | ||
| poly_list: &SumOfProductsPoly<F>, | ||
| poly_list: SumOfProductsPoly<F>, |
There was a problem hiding this comment.
| 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); |
| /// Initialize the prover state by taking ownership of the polynomials, | ||
| /// avoiding the clone in `prover_init`. |
| 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); |
| // 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); |
| 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()); |
| }; | ||
|
|
||
| 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()); |
…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>
f4c9a82 to
71777e7
Compare
Summary
prover_init_owned()toIOPProverStatethat takesSumOfProductsPolyby value, eliminating theclone()inprover_init()SumCheck::prove()signature from&SumOfProductsPoly<F>toSumOfProductsPoly<F>(ownership)prover_merge_pointsin batching to passsumcheck_polyby moveprover_init()(borrow + clone) is preserved for callers that need itMotivation
During batched polynomial commitment opening,
SumCheck::proveinternally clones the entire polynomial list viaprover_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