Skip to content

Commit 16ac2a6

Browse files
feat(sumcheck): build h1(x) without fix mles at r0 from first round (#29)
* build round 2 univariate poly from original evaluations * refactor * misc: support runtime mode and falleback with legacy feature (#30) * bug fix: sumcheck phase 2 always use legacy mode * add memory reduction benchmark * cargo fmt * revise the build_uni_poly_round2 algorithm to reuse fi(r0,0,b) and fi(r0,1,b) among block (b,fi) * add new feature s.t. sumcheck prover follows reduced peak memory flow and fix a bug --------- Co-authored-by: Ming <hero78119@gmail.com>
1 parent df01e24 commit 16ac2a6

7 files changed

Lines changed: 829 additions & 27 deletions

File tree

crates/multilinear_extensions/src/mle.rs

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{any::TypeId, borrow::Cow, mem, sync::Arc};
1+
use std::{any::TypeId, borrow::Cow, mem, ops::Range, sync::Arc};
22

33
use crate::{
44
field_type_mut_map,
@@ -231,6 +231,14 @@ impl<'a, E: ExtensionField> FieldType<'a, E> {
231231
FieldType::Unreachable => unreachable!(),
232232
}
233233
}
234+
235+
pub fn as_slice(&self, range: Range<usize>) -> Either<&[E::BaseField], &[E]> {
236+
match self {
237+
FieldType::Base(slice) => Either::Left(&slice[range]),
238+
FieldType::Ext(slice) => Either::Right(&slice[range]),
239+
FieldType::Unreachable => unreachable!(),
240+
}
241+
}
234242
}
235243

236244
impl<'a, E: ExtensionField> PartialEq for FieldType<'a, E> {
@@ -567,6 +575,85 @@ impl<'a, E: ExtensionField> MultilinearExtension<'a, E> {
567575
self.num_vars = nv - partial_point.len();
568576
}
569577

578+
// compute f(r0,r1,b) from block { f(0,0,b), f(1,0,b), f(0,1,b), f(1,1,b) }
579+
#[inline(always)]
580+
fn eval_block_2_vars_base(block: &[E::BaseField], r0: E, r1: E) -> E {
581+
// f(r0,r1,b) = (1 - r1) * f(r0,0,b) + r1 * f(r0,1,b)
582+
// f(r0,0,b) = (1 - r0) * f(0,0,b) + r0 * f(1,0,b)
583+
// f(r0,1,b) = (1 - r0) * f(0,1,b) + r0 * f(1,1,b)
584+
let y0: E = r0 * (block[1] - block[0]) + block[0];
585+
let y1: E = r0 * (block[3] - block[2]) + block[2];
586+
y0 + (y1 - y0) * r1
587+
}
588+
589+
#[inline(always)]
590+
fn eval_block_2_vars_ext(block: &[E], r0: E, r1: E) -> E {
591+
// f(r0,r1,b) = (1 - r1) * f(r0,0,b) + r1 * f(r0,1,b)
592+
// f(r0,0,b) = (1 - r0) * f(0,0,b) + r0 * f(1,0,b)
593+
// f(r0,1,b) = (1 - r0) * f(0,1,b) + r0 * f(1,1,b)
594+
let y0: E = block[0] + (block[1] - block[0]) * r0;
595+
let y1: E = block[2] + (block[3] - block[2]) * r0;
596+
y0 + (y1 - y0) * r1
597+
}
598+
599+
/// Reduce the number of variables by 2 in one pass.
600+
///
601+
/// This avoids calling `fix_variables` twice and directly computes
602+
/// `f(r0, r1, ..)` from 4-point blocks.
603+
pub fn fix_two_variables(&self, r0: E, r1: E) -> Self {
604+
assert!(self.num_vars() >= 2, "num_vars {} < 2", self.num_vars());
605+
let nv = self.num_vars();
606+
match self.evaluations() {
607+
FieldType::Base(slice) => MultilinearExtension::from_evaluations_ext_vec(
608+
nv - 2,
609+
slice
610+
.chunks(4)
611+
.map(|buf| Self::eval_block_2_vars_base(buf, r0, r1))
612+
.collect(),
613+
),
614+
FieldType::Ext(slice) => MultilinearExtension::from_evaluations_ext_vec(
615+
nv - 2,
616+
slice
617+
.chunks(4)
618+
.map(|buf| Self::eval_block_2_vars_ext(buf, r0, r1))
619+
.collect(),
620+
),
621+
FieldType::Unreachable => unreachable!(),
622+
}
623+
}
624+
625+
/// In-place variant of `fix_two_variables`.
626+
pub fn fix_two_variables_in_place(&mut self, r0: E, r1: E) {
627+
assert!(self.is_mut());
628+
assert!(self.num_vars() >= 2, "num_vars {} < 2", self.num_vars());
629+
let nv = self.num_vars();
630+
631+
match &mut self.evaluations {
632+
FieldType::Base(slice) => {
633+
let ext_vec = slice
634+
.chunks(4)
635+
.map(|buf| Self::eval_block_2_vars_base(buf, r0, r1))
636+
.collect();
637+
let _ = std::mem::replace(
638+
&mut self.evaluations,
639+
FieldType::Ext(SmartSlice::Owned(ext_vec)),
640+
);
641+
}
642+
FieldType::Ext(slice) => {
643+
let slice_mut = slice.to_mut();
644+
let new_len = 1 << (nv - 2);
645+
for i in 0..new_len {
646+
let b = i << 2;
647+
slice_mut[i] = Self::eval_block_2_vars_ext(&slice_mut[b..b + 4], r0, r1);
648+
}
649+
slice.truncate_mut(new_len);
650+
}
651+
FieldType::Unreachable => unreachable!(),
652+
}
653+
654+
self.num_vars = nv - 2;
655+
}
656+
570657
/// Evaluate the MLE at a give point.
571658
/// Returns an error if the MLE length does not match the point.
572659
pub fn evaluate(&self, point: &[E]) -> E {

crates/sumcheck/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,13 @@ rand.workspace = true
3131
[features]
3232
nightly-features = ["ff_ext/nightly-features"]
3333
parallel = ["p3/parallel", "multilinear_extensions/parallel"]
34+
reduce-peak-memory = []
3435

3536

3637
[[bench]]
3738
harness = false
3839
name = "devirgo_sumcheck"
40+
41+
[[bench]]
42+
harness = false
43+
name = "memory_usage"

crates/sumcheck/benches/devirgo_sumcheck.rs

Lines changed: 142 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,20 @@ use ff_ext::{ExtensionField, GoldilocksExt2};
99
use itertools::Itertools;
1010
use p3::field::FieldAlgebra;
1111
use rand::thread_rng;
12-
use sumcheck::structs::IOPProverState;
12+
use sumcheck::structs::{IOPProverState, SumcheckProverMode};
1313

1414
use multilinear_extensions::{
1515
mle::MultilinearExtension, monomial::Term, op_mle, util::max_usable_threads,
1616
virtual_poly::VirtualPolynomial, virtual_polys::VirtualPolynomials,
1717
};
1818
use transcript::BasicTranscript as Transcript;
1919

20-
criterion_group!(benches, sumcheck_fn, devirgo_sumcheck_fn,);
20+
criterion_group!(
21+
benches,
22+
sumcheck_fn,
23+
devirgo_sumcheck_fn,
24+
devirgo_sumcheck_reduced_peak_memory_fn,
25+
);
2126
criterion_main!(benches);
2227

2328
const NUM_SAMPLES: usize = 10;
@@ -223,3 +228,138 @@ fn devirgo_sumcheck_fn(c: &mut Criterion) {
223228
group.finish();
224229
}
225230
}
231+
232+
fn devirgo_sumcheck_reduced_peak_memory_fn(c: &mut Criterion) {
233+
type E = GoldilocksExt2;
234+
235+
let threads = max_usable_threads();
236+
for nv in NV {
237+
let mut group = c.benchmark_group(format!("devirgo_reduced_peak_memory_nv_{}", nv));
238+
group.sample_size(NUM_SAMPLES);
239+
240+
group.bench_function(
241+
BenchmarkId::new(
242+
"prove_sumcheck",
243+
format!("devirgo_reduced_peak_memory_nv_{}", nv),
244+
),
245+
|b| {
246+
b.iter_custom(|iters| {
247+
let mut time = Duration::new(0, 0);
248+
for _ in 0..iters {
249+
let mut prover_transcript = Transcript::new(b"test");
250+
let (_, fs) = { prepare_input(nv) };
251+
252+
let virtual_poly = VirtualPolynomials::new_from_monimials(
253+
threads,
254+
nv,
255+
vec![Term {
256+
scalar: Either::Right(E::ONE),
257+
product: fs.iter().map(Either::Left).collect_vec(),
258+
}],
259+
);
260+
let instant = std::time::Instant::now();
261+
let _ = IOPProverState::<E>::prove_with_mode(
262+
virtual_poly,
263+
&mut prover_transcript,
264+
SumcheckProverMode::ReducedPeakMemory,
265+
);
266+
time += instant.elapsed();
267+
}
268+
time
269+
});
270+
},
271+
);
272+
273+
group.bench_function(
274+
BenchmarkId::new(
275+
"prove_sumcheck_ext",
276+
format!("devirgo_reduced_peak_memory_nv_{}", nv),
277+
),
278+
|b| {
279+
b.iter_custom(|iters| {
280+
let mut time = Duration::new(0, 0);
281+
for _ in 0..iters {
282+
let mut prover_transcript = Transcript::new(b"test");
283+
let (_, fs) = { prepare_input(nv) };
284+
let fs = fs
285+
.into_iter()
286+
.map(|mle: MultilinearExtension<'_, E>| {
287+
MultilinearExtension::from_evaluation_vec_smart(
288+
mle.num_vars(),
289+
mle.get_base_field_vec()
290+
.iter()
291+
.map(E::from_ref_base)
292+
.collect_vec(),
293+
)
294+
})
295+
.collect_vec();
296+
297+
let virtual_poly = VirtualPolynomials::new_from_monimials(
298+
threads,
299+
nv,
300+
vec![Term {
301+
scalar: Either::Right(E::ONE),
302+
product: fs.iter().map(Either::Left).collect_vec(),
303+
}],
304+
);
305+
let instant = std::time::Instant::now();
306+
let _ = IOPProverState::<E>::prove_with_mode(
307+
virtual_poly,
308+
&mut prover_transcript,
309+
SumcheckProverMode::ReducedPeakMemory,
310+
);
311+
time += instant.elapsed();
312+
}
313+
time
314+
});
315+
},
316+
);
317+
318+
group.bench_function(
319+
BenchmarkId::new(
320+
"prove_sumcheck_ext_in_place",
321+
format!("devirgo_reduced_peak_memory_nv_{}", nv),
322+
),
323+
|b| {
324+
b.iter_custom(|iters| {
325+
let mut time = Duration::new(0, 0);
326+
for _ in 0..iters {
327+
let mut prover_transcript = Transcript::new(b"test");
328+
let (_, fs) = { prepare_input(nv) };
329+
let mut fs = fs
330+
.into_iter()
331+
.map(|mle: MultilinearExtension<'_, E>| {
332+
MultilinearExtension::from_evaluation_vec_smart(
333+
mle.num_vars(),
334+
mle.get_base_field_vec()
335+
.iter()
336+
.map(E::from_ref_base)
337+
.collect_vec(),
338+
)
339+
})
340+
.collect_vec();
341+
342+
let virtual_poly = VirtualPolynomials::new_from_monimials(
343+
threads,
344+
nv,
345+
vec![Term {
346+
scalar: Either::Right(E::ONE),
347+
product: fs.iter_mut().map(Either::Right).collect_vec(),
348+
}],
349+
);
350+
let instant = std::time::Instant::now();
351+
let _ = IOPProverState::<E>::prove_with_mode(
352+
virtual_poly,
353+
&mut prover_transcript,
354+
SumcheckProverMode::ReducedPeakMemory,
355+
);
356+
time += instant.elapsed();
357+
}
358+
time
359+
});
360+
},
361+
);
362+
363+
group.finish();
364+
}
365+
}

0 commit comments

Comments
 (0)