From 0b83e58b30f8ee1e614ac24d798a5e8146e62f9f Mon Sep 17 00:00:00 2001 From: spherel Date: Sat, 9 Jul 2022 00:07:47 +0000 Subject: [PATCH 1/2] Add Mockprover::verify_par() --- halo2_proofs/src/dev.rs | 272 +++++++++++++++++++++++++--------------- 1 file changed, 170 insertions(+), 102 deletions(-) diff --git a/halo2_proofs/src/dev.rs b/halo2_proofs/src/dev.rs index ce832797..f23e343a 100644 --- a/halo2_proofs/src/dev.rs +++ b/halo2_proofs/src/dev.rs @@ -7,6 +7,8 @@ use std::iter; use std::ops::{Add, Mul, Neg, Range}; use ff::Field; +use rayon::iter::IntoParallelRefIterator; +use rayon::iter::ParallelIterator; use crate::plonk::Assigned; use crate::{ @@ -670,6 +672,37 @@ impl Assignment for MockProver { } } +fn home_made_par_iter_flat_map( + row_indexes: &[usize], + f: Fun, +) -> Vec +where + Fun: Fn(i32) -> Vec, +{ + if ISPAR { + row_indexes + .par_iter() + .flat_map(|row| f(*row as i32)) + .collect() + } else { + row_indexes.iter().flat_map(|row| f(*row as i32)).collect() + } +} + +fn home_made_par_iter_map( + row_indexes: &[usize], + f: Fun, +) -> Vec +where + Fun: Fn(i32) -> U, +{ + if ISPAR { + row_indexes.par_iter().map(|row| f(*row as i32)).collect() + } else { + row_indexes.iter().map(|row| f(*row as i32)).collect() + } +} + impl MockProver { /// Runs a synthetic keygen-and-prove operation on the given circuit, collecting data /// about the constraints and their assignments. @@ -759,6 +792,12 @@ impl MockProver { self.verify_at_rows(self.usable_rows.clone(), self.usable_rows.clone()) } + /// Returns `Ok(())` if this `MockProver` is satisfied, or a list of errors indicating + /// the reasons that the circuit is not satisfied. Run verify() in parallel. + pub fn verify_par(&self) -> Result<(), Vec> { + self.verify_at_rows_par(self.usable_rows.clone(), self.usable_rows.clone()) + } + /// Returns `Ok(())` if this `MockProver` is satisfied, or a list of errors indicating /// the reasons that the circuit is not satisfied. /// Constraints are only checked at `gate_row_ids`, @@ -767,6 +806,27 @@ impl MockProver { &self, gate_row_ids: I, lookup_input_row_ids: I, + ) -> Result<(), Vec> { + self.verify_at_rows_internal::<_, false>(gate_row_ids, lookup_input_row_ids) + } + + /// Returns `Ok(())` if this `MockProver` is satisfied, or a list of errors indicating + /// the reasons that the circuit is not satisfied. + /// Constraints are only checked at `gate_row_ids`, + /// and lookup inputs are only checked at `lookup_input_row_ids` + /// Run verify_at_rows() in parallel. + pub fn verify_at_rows_par>( + &self, + gate_row_ids: I, + lookup_input_row_ids: I, + ) -> Result<(), Vec> { + self.verify_at_rows_internal::<_, true>(gate_row_ids, lookup_input_row_ids) + } + + fn verify_at_rows_internal, const ISPAR: bool>( + &self, + gate_row_ids: I, + lookup_input_row_ids: I, ) -> Result<(), Vec> { let n = self.n as i32; @@ -800,75 +860,75 @@ impl MockProver { .enumerate() .filter(move |(_, g)| g.queried_selectors().contains(selector)) .flat_map(move |(gate_index, gate)| { - at.iter().flat_map(move |selector_row| { - // Selectors are queried with no rotation. - let gate_row = *selector_row as i32; + home_made_par_iter_flat_map::<_, _, ISPAR>(at, |gate_row| { + let ret: Vec = gate + .queried_cells() + .iter() + .filter_map(move |cell| { + // Determine where this cell should have been assigned. + let cell_row = ((gate_row + n + cell.rotation.0) % n) as usize; - gate.queried_cells().iter().filter_map(move |cell| { - // Determine where this cell should have been assigned. - let cell_row = ((gate_row + n + cell.rotation.0) % n) as usize; - - // Check that it was assigned! - if r.is_assigned(cell.column, cell_row) { - None - } else { - Some(VerifyFailure::CellNotAssigned { - gate: (gate_index, gate.name()).into(), - region: (r_i, r.name.clone()).into(), - column: cell.column, - offset: cell_row as isize - r.rows.unwrap().0 as isize, - }) - } - }) + // Check that it was assigned! + if r.is_assigned(cell.column, cell_row) { + None + } else { + Some(VerifyFailure::CellNotAssigned { + gate: (gate_index, gate.name()).into(), + region: (r_i, r.name.clone()).into(), + column: cell.column, + offset: cell_row as isize - r.rows.unwrap().0 as isize, + }) + } + }) + .collect(); + ret }) }) }) }); // Check that all gates are satisfied for all rows. - let gate_errors = - self.cs - .gates - .iter() - .enumerate() - .flat_map(|(gate_index, gate)| { - let blinding_rows = - (self.n as usize - (self.cs.blinding_factors() + 1))..(self.n as usize); - (gate_row_ids - .clone() - .into_iter() - .chain(blinding_rows.into_iter())) - .flat_map(move |row| { - fn load_instance<'a, F: FieldExt, T: ColumnType>( - n: i32, - row: i32, - queries: &'a [(Column, Rotation)], - cells: &'a [Vec], - ) -> impl Fn(usize, usize, Rotation) -> Value + 'a - { - move |index, _, _| { - let (column, at) = &queries[index]; - let resolved_row = (row + n + at.0) % n; - Value::Real(cells[column.index()][resolved_row as usize]) - } - } + let blinding_rows = (self.n as usize - (self.cs.blinding_factors() + 1))..(self.n as usize); + let indexes: Vec = + (gate_row_ids.into_iter().chain(blinding_rows.into_iter())).collect(); + let gate_errors = self + .cs + .gates + .iter() + .enumerate() + .flat_map(|(gate_index, gate)| { + fn load_instance<'a, F: FieldExt, T: ColumnType>( + n: i32, + row: i32, + queries: &'a [(Column, Rotation)], + cells: &'a [Vec], + ) -> impl Fn(usize, usize, Rotation) -> Value + 'a { + move |index, _, _| { + let (column, at) = &queries[index]; + let resolved_row = (row + n + at.0) % n; + Value::Real(cells[column.index()][resolved_row as usize]) + } + } - fn load<'a, F: FieldExt, T: ColumnType>( - n: i32, - row: i32, - queries: &'a [(Column, Rotation)], - cells: &'a [Vec>], - ) -> impl Fn(usize, usize, Rotation) -> Value + 'a - { - move |index, _, _| { - let (column, at) = &queries[index]; - let resolved_row = (row + n + at.0) % n; - cells[column.index()][resolved_row as usize].into() - } - } - let row = row as i32; - gate.polynomials().iter().enumerate().filter_map( - move |(poly_index, poly)| match poly.evaluate_lazy( + fn load<'a, F: FieldExt, T: ColumnType>( + n: i32, + row: i32, + queries: &'a [(Column, Rotation)], + cells: &'a [Vec>], + ) -> impl Fn(usize, usize, Rotation) -> Value + 'a { + move |index, _, _| { + let (column, at) = &queries[index]; + let resolved_row = (row + n + at.0) % n; + cells[column.index()][resolved_row as usize].into() + } + } + home_made_par_iter_flat_map::<_, _, ISPAR>(&indexes, |row| { + let ret: Vec = gate + .polynomials() + .iter() + .enumerate() + .filter_map(move |(poly_index, poly)| { + match poly.evaluate_lazy( &|scalar| Value::Real(scalar), &|_| panic!("virtual selectors are removed during optimization"), &load(n, row, &self.cs.fixed_queries, &self.fixed), @@ -915,10 +975,12 @@ impl MockProver { ) .into(), }), - }, - ) - }) - }); + } + }) + .collect(); + ret + }) + }); // Check that all lookups exist in their respective tables. let lookup_errors = @@ -927,7 +989,7 @@ impl MockProver { .iter() .enumerate() .flat_map(|(lookup_index, lookup)| { - let load = |expression: &Expression, row| { + let load = |expression: &Expression, row: i32| { expression.evaluate_lazy( &|scalar| Value::Real(scalar), &|_| panic!("virtual selectors are removed during optimization"), @@ -935,8 +997,7 @@ impl MockProver { let query = self.cs.fixed_queries[index]; let column_index = query.0.index(); let rotation = query.1 .0; - self.fixed[column_index] - [(row as i32 + n + rotation) as usize % n as usize] + self.fixed[column_index][(row + n + rotation) as usize % n as usize] .into() }, &|index, _, _| { @@ -944,7 +1005,7 @@ impl MockProver { let column_index = query.0.index(); let rotation = query.1 .0; self.advice[column_index] - [(row as i32 + n + rotation) as usize % n as usize] + [(row + n + rotation) as usize % n as usize] .into() }, &|index, _, _| { @@ -966,42 +1027,40 @@ impl MockProver { // In the real prover, the lookup expressions are never enforced on // unusable rows, due to the (1 - (l_last(X) + l_blind(X))) term. - let table: std::collections::BTreeSet> = self - .usable_rows - .clone() - .map(|table_row| { + let usable_row_vec: Vec<_> = self.usable_rows.clone().into_iter().collect(); + let table = + home_made_par_iter_map::<_, _, ISPAR>(&usable_row_vec, |table_row| { lookup .table_expressions .iter() .map(move |c| load(c, table_row)) .collect::>() - }) - .collect(); - lookup_input_row_ids - .clone() - .into_iter() - .filter_map(move |input_row| { - let inputs: Vec<_> = lookup - .input_expressions - .iter() - .map(|c| load(c, input_row)) - .collect(); - let lookup_passes = table.contains(&inputs); - if lookup_passes { - None - } else { - Some(VerifyFailure::Lookup { - name: lookup.name, - lookup_index, - location: FailureLocation::find_expressions( - &self.cs, - &self.regions, - input_row, - lookup.input_expressions.iter(), - ), - }) - } - }) + }); + let lookup_input_row_id_vec: Vec<_> = lookup_input_row_ids.clone().collect(); + home_made_par_iter_map::<_, _, ISPAR>(&lookup_input_row_id_vec, |input_row| { + let inputs: Vec<_> = lookup + .input_expressions + .iter() + .map(|c| load(c, input_row)) + .collect(); + let lookup_passes = table.contains(&inputs); + if lookup_passes { + None + } else { + Some(VerifyFailure::Lookup { + name: lookup.name, + lookup_index, + location: FailureLocation::find_expressions( + &self.cs, + &self.regions, + input_row as usize, + lookup.input_expressions.iter(), + ), + }) + } + }) + .into_iter() + .flatten() }); // Check that permutations preserve the original values of the cells. @@ -1028,7 +1087,7 @@ impl MockProver { .flat_map(move |(column, values)| { // Iterate over each row of the column to check that the cell's // value is preserved by the mapping. - values.iter().enumerate().filter_map(move |(row, cell)| { + let f = move |row, cell: (usize, usize)| -> Option { let original_cell = original(column, row); let permuted_cell = original(cell.0, cell.1); if original_cell == permuted_cell { @@ -1040,7 +1099,16 @@ impl MockProver { row, }) } - }) + }; + let indexes: Vec = (0..values.len()).into_iter().collect(); + let ret: Vec = indexes + .par_iter() + .filter_map(|row| { + let cell = values[*row]; + f(*row, cell) + }) + .collect(); + ret }) }; From 55bae7c6872692649dd1290716045eed0c5c5941 Mon Sep 17 00:00:00 2001 From: spherel Date: Sun, 7 Aug 2022 20:37:27 +0000 Subject: [PATCH 2/2] Fix parallel function signature. --- halo2_proofs/src/dev.rs | 91 +++++++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 39 deletions(-) diff --git a/halo2_proofs/src/dev.rs b/halo2_proofs/src/dev.rs index f23e343a..8439310b 100644 --- a/halo2_proofs/src/dev.rs +++ b/halo2_proofs/src/dev.rs @@ -672,34 +672,45 @@ impl Assignment for MockProver { } } -fn home_made_par_iter_flat_map( +fn parallelizable_iter_flat_map( row_indexes: &[usize], f: Fun, ) -> Vec where - Fun: Fn(i32) -> Vec, + Fun: Fn(usize) -> Vec, { if ISPAR { - row_indexes - .par_iter() - .flat_map(|row| f(*row as i32)) - .collect() + row_indexes.par_iter().flat_map(|row| f(*row)).collect() } else { - row_indexes.iter().flat_map(|row| f(*row as i32)).collect() + row_indexes.iter().flat_map(|row| f(*row)).collect() } } -fn home_made_par_iter_map( +fn parallelizable_iter_map( row_indexes: &[usize], f: Fun, ) -> Vec where - Fun: Fn(i32) -> U, + Fun: Fn(usize) -> U, { if ISPAR { - row_indexes.par_iter().map(|row| f(*row as i32)).collect() + row_indexes.par_iter().map(|row| f(*row)).collect() } else { - row_indexes.iter().map(|row| f(*row as i32)).collect() + row_indexes.iter().map(|row| f(*row)).collect() + } +} + +fn parallelizable_iter_filter_map( + row_indexes: &[usize], + f: Fun, +) -> Vec +where + Fun: Fn(usize) -> Option, +{ + if ISPAR { + row_indexes.par_iter().filter_map(|row| f(*row)).collect() + } else { + row_indexes.iter().filter_map(|row| f(*row)).collect() } } @@ -860,13 +871,14 @@ impl MockProver { .enumerate() .filter(move |(_, g)| g.queried_selectors().contains(selector)) .flat_map(move |(gate_index, gate)| { - home_made_par_iter_flat_map::<_, _, ISPAR>(at, |gate_row| { + parallelizable_iter_flat_map::<_, _, ISPAR>(at, |gate_row| { let ret: Vec = gate .queried_cells() .iter() .filter_map(move |cell| { // Determine where this cell should have been assigned. - let cell_row = ((gate_row + n + cell.rotation.0) % n) as usize; + let cell_row = + (((gate_row as i32) + n + cell.rotation.0) % n) as usize; // Check that it was assigned! if r.is_assigned(cell.column, cell_row) { @@ -922,7 +934,8 @@ impl MockProver { cells[column.index()][resolved_row as usize].into() } } - home_made_par_iter_flat_map::<_, _, ISPAR>(&indexes, |row| { + parallelizable_iter_flat_map::<_, _, ISPAR>(&indexes, |row| { + let row = row as i32; let ret: Vec = gate .polynomials() .iter() @@ -1014,7 +1027,7 @@ impl MockProver { let rotation = query.1 .0; Value::Real( self.instance[column_index] - [(row as i32 + n + rotation) as usize % n as usize], + [(row + n + rotation) as usize % n as usize], ) }, &|a| -a, @@ -1029,19 +1042,19 @@ impl MockProver { // unusable rows, due to the (1 - (l_last(X) + l_blind(X))) term. let usable_row_vec: Vec<_> = self.usable_rows.clone().into_iter().collect(); let table = - home_made_par_iter_map::<_, _, ISPAR>(&usable_row_vec, |table_row| { + parallelizable_iter_map::<_, _, ISPAR>(&usable_row_vec, |table_row| { lookup .table_expressions .iter() - .map(move |c| load(c, table_row)) + .map(move |c| load(c, table_row as i32)) .collect::>() }); let lookup_input_row_id_vec: Vec<_> = lookup_input_row_ids.clone().collect(); - home_made_par_iter_map::<_, _, ISPAR>(&lookup_input_row_id_vec, |input_row| { + parallelizable_iter_map::<_, _, ISPAR>(&lookup_input_row_id_vec, |input_row| { let inputs: Vec<_> = lookup .input_expressions .iter() - .map(|c| load(c, input_row)) + .map(|c| load(c, input_row as i32)) .collect(); let lookup_passes = table.contains(&inputs); if lookup_passes { @@ -1087,27 +1100,27 @@ impl MockProver { .flat_map(move |(column, values)| { // Iterate over each row of the column to check that the cell's // value is preserved by the mapping. - let f = move |row, cell: (usize, usize)| -> Option { - let original_cell = original(column, row); - let permuted_cell = original(cell.0, cell.1); - if original_cell == permuted_cell { - None - } else { - Some(VerifyFailure::Permutation { - column: (*self.cs.permutation.get_columns().get(column).unwrap()) - .into(), - row, - }) - } - }; let indexes: Vec = (0..values.len()).into_iter().collect(); - let ret: Vec = indexes - .par_iter() - .filter_map(|row| { - let cell = values[*row]; - f(*row, cell) - }) - .collect(); + let ret: Vec = + parallelizable_iter_filter_map::<_, _, ISPAR>(&indexes, move |row| { + let cell = values[row]; + let original_cell = original(column, row); + let permuted_cell = original(cell.0, cell.1); + if original_cell == permuted_cell { + None + } else { + Some(VerifyFailure::Permutation { + column: (*self + .cs + .permutation + .get_columns() + .get(column) + .unwrap()) + .into(), + row, + }) + } + }); ret }) };