-
Notifications
You must be signed in to change notification settings - Fork 41
Parallel sparse multiplication #125
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 5 commits
3de18cd
d333f8d
b4656d1
c18a743
17aee00
66f8dc7
62462ca
72bb6ea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| //! Standalone executable for profiling noir-r1cs prove operations | ||
| use { | ||
| noir_r1cs::{read, NoirProofScheme}, | ||
| noir_tools::compile_workspace, | ||
| std::path::Path, | ||
| }; | ||
|
|
||
| fn main() { | ||
| println!("Starting prove profiling..."); | ||
|
|
||
| let manifest_path = Path::new(env!("CARGO_MANIFEST_DIR")); | ||
| let poseidon_path = manifest_path.join("benches").join("poseidon-1000.nps"); | ||
| let scheme: NoirProofScheme = read(&poseidon_path).unwrap(); | ||
|
|
||
| let crate_dir = manifest_path.join("../noir-examples/poseidon-rounds"); | ||
|
|
||
| compile_workspace(&crate_dir).expect("Compiling workspace"); | ||
|
|
||
| let witness_path = crate_dir.join("Prover.toml"); | ||
|
|
||
| let input_map = scheme | ||
| .read_witness(&witness_path) | ||
| .expect("Failed reading witness"); | ||
|
|
||
| println!("Setup complete, starting prove operations..."); | ||
|
|
||
| // Run multiple iterations for better profiling data | ||
| for i in 0..1 { | ||
| println!("Prove iteration {}", i + 1); | ||
|
|
||
| let _proof = scheme.prove(&input_map); | ||
| } | ||
|
|
||
| println!("Profiling complete!"); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,12 @@ | ||
| use { | ||
| crate::{FieldElement, InternedFieldElement, Interner}, | ||
| ark_std::Zero, | ||
| rayon::iter::{ | ||
| IndexedParallelIterator, IntoParallelIterator, ParallelBridge, ParallelIterator, | ||
| }, | ||
| serde::{Deserialize, Serialize}, | ||
| std::{ | ||
| cell::UnsafeCell, | ||
| fmt::Debug, | ||
| ops::{Mul, Range}, | ||
| }, | ||
|
|
@@ -146,7 +150,6 @@ impl HydratedSparseMatrix<'_> { | |
| } | ||
|
|
||
| /// Right multiplication by vector | ||
| // OPT: Paralelize | ||
| impl Mul<&[FieldElement]> for HydratedSparseMatrix<'_> { | ||
| type Output = Vec<FieldElement>; | ||
|
|
||
|
|
@@ -156,16 +159,40 @@ impl Mul<&[FieldElement]> for HydratedSparseMatrix<'_> { | |
| rhs.len(), | ||
| "Vector length does not match number of columns." | ||
| ); | ||
|
|
||
| let mut result = vec![FieldElement::zero(); self.matrix.num_rows]; | ||
| for ((i, j), value) in self.iter() { | ||
| result[i] += value * rhs[j]; | ||
| } | ||
|
|
||
| (0..self.matrix.num_rows) | ||
| .into_par_iter() | ||
| .map(|i| { | ||
| self.iter_row(i) | ||
| .fold(FieldElement::zero(), |sum, (j, value)| sum + value * rhs[j]) | ||
| }) | ||
| .collect_into_vec(&mut result); | ||
| result | ||
| } | ||
| } | ||
|
|
||
| // Provide interior mutability where | ||
| struct LockFreeArray<T>(UnsafeCell<Box<[T]>>); | ||
| unsafe impl<T: Sync + Send> Send for LockFreeArray<T> {} | ||
| unsafe impl<T: Sync + Send> Sync for LockFreeArray<T> {} | ||
|
|
||
| impl<T> LockFreeArray<T> { | ||
| fn new(vec: Vec<T>) -> Self { | ||
| let arr = vec.into_boxed_slice(); | ||
| LockFreeArray(UnsafeCell::new(arr)) | ||
| } | ||
|
|
||
| // Requires that only one thread has access to index and that the index is | ||
| // within bounds. | ||
| unsafe fn insert(&self, index: usize, value: T) { | ||
| let vec = { &mut **self.0.get() }; | ||
| vec[index] = value; | ||
| } | ||
| } | ||
|
|
||
| /// Left multiplication by vector | ||
| // OPT: Paralelize | ||
| impl Mul<HydratedSparseMatrix<'_>> for &[FieldElement] { | ||
| type Output = Vec<FieldElement>; | ||
|
|
||
|
|
@@ -175,10 +202,44 @@ impl Mul<HydratedSparseMatrix<'_>> for &[FieldElement] { | |
| rhs.matrix.num_rows, | ||
| "Vector length does not match number of rows." | ||
| ); | ||
|
|
||
| let intermediate_multiplication = | ||
| LockFreeArray::new(vec![(0, FieldElement::zero()); rhs.matrix.num_entries()]); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's unfortunate that it requires allocating a temporary vector larger than the output. |
||
|
|
||
| let intermediate_reference = &intermediate_multiplication; | ||
|
|
||
| // Mapping phase | ||
| // | ||
| // Parallelize the multiplication | ||
| // Use a lock-free array to prevent constant resizing when collecting the | ||
| // iterator as the size is not known to Rayon. Collecting without a | ||
| // preallocating the intermediate vector is >15% slower | ||
| // Other options that have been explored | ||
| // - An IndexedParallelIterator on the values of the sparse matrix also wasn't | ||
| // an option as it requires random access which we can't provide as we | ||
| // wouldn't know the row a value belongs to. That's why the rows drive the | ||
| // iterator below. | ||
| // - Acquiring a mutex per column in the result was too expensive (even with | ||
| // parking_lot) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What I had in mind was splitting the work over the mutable output vector, and creating a |
||
|
|
||
| (0..rhs.matrix.num_rows).into_par_iter().for_each(|row| { | ||
| let range = rhs.matrix.row_range(row); | ||
| rhs.iter_row(row) | ||
| .zip(range) | ||
| .for_each(move |((col, value), ind)| unsafe { | ||
| intermediate_reference.insert(ind, (col, value * self[row])) | ||
| }) | ||
| }); | ||
|
|
||
| let mut result = vec![FieldElement::zero(); rhs.matrix.num_cols]; | ||
| for ((i, j), value) in rhs.iter() { | ||
| result[j] += value * self[i]; | ||
|
|
||
| // Reduce phase | ||
| // Single thread for folding to not have a mutex per column in the result. | ||
|
|
||
| for (j, value) in intermediate_multiplication.0.into_inner() { | ||
| result[j] += value; | ||
| } | ||
|
|
||
| result | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.