diff --git a/v0-parameters/Cargo.toml b/v0-parameters/Cargo.toml index 4cc903ff8..82a1c39de 100644 --- a/v0-parameters/Cargo.toml +++ b/v0-parameters/Cargo.toml @@ -13,6 +13,17 @@ rayon-cond = "0.2" # to avoid rayon code coloring rayon = "1.5.1" text-diff = "0.4.0" +[dev-dependencies] +criterion = "0.3" + +[lib] +crate-type= [ + "lib", # rust +] [[bin]] name = "v0-parameters" + +[[bench]] +name = "benchmark" +harness = false diff --git a/v0-parameters/benches/benchmark.rs b/v0-parameters/benches/benchmark.rs new file mode 100644 index 000000000..2a4a0b2df --- /dev/null +++ b/v0-parameters/benches/benchmark.rs @@ -0,0 +1,49 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use v0_parameters::{all_results, Args, MAX_LWE_DIM, MIN_LWE_DIM, _4_SIGMA}; + +fn pbs_benchmark(c: &mut Criterion) { + let args: Args = Args { + min_precision: 1, + max_precision: 8, + p_error: _4_SIGMA, + security_level: 128, + min_log_poly_size: 8, + max_log_poly_size: 16, + min_glwe_dim: 1, + max_glwe_dim: 1, + min_intern_lwe_dim: MIN_LWE_DIM, + max_intern_lwe_dim: MAX_LWE_DIM, + sum_size: 4096, + no_parallelize: true, + wop_pbs: false, + }; + + c.bench_function("PBS table generation", |b| { + b.iter(|| black_box(all_results(&args))) + }); +} + +fn wop_pbs_benchmark(c: &mut Criterion) { + let args = Args { + min_precision: 1, + max_precision: 16, + p_error: _4_SIGMA, + security_level: 128, + min_log_poly_size: 10, + max_log_poly_size: 11, + min_glwe_dim: 1, + max_glwe_dim: 2, + min_intern_lwe_dim: 450, + max_intern_lwe_dim: 600, + sum_size: 4096, + no_parallelize: true, + wop_pbs: true, + }; + + c.bench_function("WoP-PBS table generation", |b| { + b.iter(|| black_box(all_results(&args))) + }); +} + +criterion_group!(benches, pbs_benchmark, wop_pbs_benchmark); +criterion_main!(benches); diff --git a/v0-parameters/src/bin/v0-parameters.rs b/v0-parameters/src/bin/v0-parameters.rs new file mode 100644 index 000000000..460451bd0 --- /dev/null +++ b/v0-parameters/src/bin/v0-parameters.rs @@ -0,0 +1,12 @@ +#![warn(clippy::nursery)] +#![warn(clippy::pedantic)] +#![warn(clippy::style)] + +use clap::Parser; +use v0_parameters::{compute_print_results, Args}; + +fn main() { + let args = Args::parse(); + + compute_print_results(std::io::stdout(), &args).unwrap(); +} diff --git a/v0-parameters/src/lib.rs b/v0-parameters/src/lib.rs new file mode 100644 index 000000000..1d28d0c26 --- /dev/null +++ b/v0-parameters/src/lib.rs @@ -0,0 +1,243 @@ +#![warn(clippy::nursery)] +#![warn(clippy::pedantic)] +#![warn(clippy::style)] +#![allow(clippy::cast_precision_loss)] // u64 to f64 +#![allow(clippy::cast_possible_truncation)] // u64 to usize +#![allow(clippy::cast_sign_loss)] +#![allow(clippy::must_use_candidate)] +#![allow(clippy::missing_panics_doc)] +#![allow(clippy::missing_errors_doc)] + +use clap::Parser; +use concrete_optimizer::global_parameters::DEFAUT_DOMAINS; +use concrete_optimizer::optimization::atomic_pattern::{ + self as optimize_atomic_pattern, OptimizationState, +}; +use concrete_optimizer::optimization::wop_atomic_pattern::optimize as optimize_wop_atomic_pattern; +use rayon_cond::CondIterator; +use std::io::Write; + +pub const _4_SIGMA: f64 = 1.0 - 0.999_936_657_516; +const MIN_LOG_POLY_SIZE: u64 = DEFAUT_DOMAINS + .glwe_pbs_constrained + .log2_polynomial_size + .start as u64; +const MAX_LOG_POLY_SIZE: u64 = + DEFAUT_DOMAINS.glwe_pbs_constrained.log2_polynomial_size.end as u64 - 1; +pub const MAX_GLWE_DIM: u64 = DEFAUT_DOMAINS.glwe_pbs_constrained.glwe_dimension.end - 1; +pub const MIN_LWE_DIM: u64 = DEFAUT_DOMAINS.free_glwe.glwe_dimension.start as u64; +pub const MAX_LWE_DIM: u64 = DEFAUT_DOMAINS.free_glwe.glwe_dimension.end as u64 - 1; + +/// Simple program to greet a person +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +pub struct Args { + #[clap(long, default_value_t = 1, help = "1..16")] + pub min_precision: u64, + + #[clap(long, default_value_t = 8, help = "1..16")] + pub max_precision: u64, + + #[clap(long, default_value_t = _4_SIGMA)] + pub p_error: f64, + + #[clap(long, default_value_t = 128, help = "Only 128 is supported")] + pub security_level: u64, + + #[clap(long, default_value_t = MIN_LOG_POLY_SIZE, help = "8..16")] + pub min_log_poly_size: u64, + + #[clap(long, default_value_t = MAX_LOG_POLY_SIZE, help = "8..16")] + pub max_log_poly_size: u64, + + #[clap(long, default_value_t = 1, help = "EXPERIMENTAL")] + pub min_glwe_dim: u64, + + #[clap(long, default_value_t = MAX_GLWE_DIM, help = "EXPERIMENTAL")] + pub max_glwe_dim: u64, + + #[clap(long, default_value_t = MIN_LWE_DIM)] + pub min_intern_lwe_dim: u64, + + #[clap(long, default_value_t = MAX_LWE_DIM)] + pub max_intern_lwe_dim: u64, // 16bits needs around 1300 + + #[clap(long, default_value_t = 4096)] + pub sum_size: u64, + + #[clap(long)] + pub no_parallelize: bool, + + #[clap(long)] + pub wop_pbs: bool, +} + +pub fn all_results(args: &Args) -> Vec> { + let sum_size = args.sum_size; + let p_error = args.p_error; + let security_level = args.security_level; + assert_eq!(security_level, 128, "Only 128bits of security is supported"); + + let glwe_log_polynomial_sizes: Vec<_> = + (args.min_log_poly_size..=args.max_log_poly_size).collect(); + let glwe_dimensions: Vec<_> = (args.min_glwe_dim..=args.max_glwe_dim).collect(); + let internal_lwe_dimensions: Vec<_> = + (args.min_intern_lwe_dim..=args.max_intern_lwe_dim).collect(); + + let precisions = args.min_precision..=args.max_precision; + let manps: Vec<_> = (0..=31).collect(); + + // let guard = pprof::ProfilerGuard::new(100).unwrap(); + + let precisions_iter = CondIterator::new(precisions, !args.no_parallelize); + + precisions_iter + .map(|precision| { + let mut last_solution = None; + manps + .iter() + .map(|&manp| { + let noise_scale = 2_f64.powi(manp); + let result = if args.wop_pbs { + optimize_wop_atomic_pattern::optimize_one::( + sum_size, + precision, + security_level, + noise_scale, + p_error, + &glwe_log_polynomial_sizes, + &glwe_dimensions, + &internal_lwe_dimensions, + ) + } else { + optimize_atomic_pattern::optimize_one::( + sum_size, + precision, + security_level, + noise_scale, + p_error, + &glwe_log_polynomial_sizes, + &glwe_dimensions, + &internal_lwe_dimensions, + last_solution, // 33% gains + ) + }; + last_solution = result.best_solution; + result + }) + .collect::>() + }) + .collect::>() +} + +pub fn compute_print_results(mut writer: impl Write, args: &Args) -> Result<(), std::io::Error> { + let all_results = all_results(args); + + let p_error = args.p_error; + let security_level = args.security_level; + assert_eq!(security_level, 128, "Only 128bits of security is supported"); + + let precisions = args.min_precision..=args.max_precision; + let manps: Vec<_> = (0..=31).collect(); + + writeln!(writer, "{{ /* {:1.1e} errors */", p_error)?; + + for (precision_i, precision) in precisions.enumerate() { + writeln!(writer, "{{ /* precision {:2} */", precision)?; + + for (manp_i, manp) in manps.clone().iter().enumerate() { + if let Some(solution) = all_results[precision_i][manp_i].best_solution { + writeln!( writer, + " /* {:2} */ V0Parameter({:2}, {:2}, {:4}, {:2}, {:2}, {:2}, {:2}), \t\t // {:4} mops, {:1.1e} errors", + manp, solution.glwe_dimension, (solution.glwe_polynomial_size as f64).log2() as u64, + solution.internal_ks_output_lwe_dimension, + solution.br_decomposition_level_count, solution.br_decomposition_base_log, + solution.ks_decomposition_level_count, solution.ks_decomposition_base_log, + (solution.complexity / (1024.0 * 1024.0)) as u64, + solution.p_error + )?; + } else { + writeln!( + writer, + " /* {:2} : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0),", + manp, + )?; + } + } + writeln!(writer, " }},")?; + } + writeln!(writer, "}}")?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_reference_output() { + const REF_FILE: &str = "src/v0_parameters.ref-20-06-2022"; + const CMP_LINES: &str = "\n"; + const EXACT_EQUALITY: i32 = 0; + + let args: Args = Args { + min_precision: 1, + max_precision: 8, + p_error: _4_SIGMA, + security_level: 128, + min_log_poly_size: MIN_LOG_POLY_SIZE, + max_log_poly_size: MAX_LOG_POLY_SIZE, + min_glwe_dim: 1, + max_glwe_dim: MAX_GLWE_DIM, + min_intern_lwe_dim: MIN_LWE_DIM, + max_intern_lwe_dim: MAX_LWE_DIM, + sum_size: 4096, + no_parallelize: false, + wop_pbs: false, + }; + + let mut actual_output = Vec::::new(); + + compute_print_results(&mut actual_output, &args).unwrap(); + + let actual_output = std::str::from_utf8(&actual_output).expect("Bad content"); + + let expected_output = std::fs::read_to_string(REF_FILE).expect("Can't read reference file"); + + text_diff::assert_diff(&expected_output, actual_output, CMP_LINES, EXACT_EQUALITY); + } + + #[test] + fn test_reference_wop_output() { + const REF_FILE: &str = "src/wop_parameters.ref-20-06-2022"; + const CMP_LINES: &str = "\n"; + const EXACT_EQUALITY: i32 = 0; + + let args = Args { + min_precision: 1, + max_precision: 16, + p_error: _4_SIGMA, + security_level: 128, + min_log_poly_size: 10, + max_log_poly_size: 11, + min_glwe_dim: 1, + max_glwe_dim: MAX_GLWE_DIM, + min_intern_lwe_dim: 450, + max_intern_lwe_dim: 600, + sum_size: 4096, + no_parallelize: false, + wop_pbs: true, + }; + + let mut actual_output = Vec::::new(); + + compute_print_results(&mut actual_output, &args).unwrap(); + + let actual_output = std::str::from_utf8(&actual_output).expect("Bad content"); + + let expected_output = std::fs::read_to_string(REF_FILE).expect("Can't read reference file"); + + text_diff::assert_diff(&expected_output, actual_output, CMP_LINES, EXACT_EQUALITY); + } +} diff --git a/v0-parameters/src/main.rs b/v0-parameters/src/main.rs deleted file mode 100644 index e3304ecea..000000000 --- a/v0-parameters/src/main.rs +++ /dev/null @@ -1,229 +0,0 @@ -#![warn(clippy::nursery)] -#![warn(clippy::pedantic)] -#![warn(clippy::style)] -#![allow(clippy::cast_precision_loss)] // u64 to f64 -#![allow(clippy::cast_possible_truncation)] // u64 to usize -#![allow(clippy::cast_sign_loss)] - -use clap::Parser; -use concrete_optimizer::global_parameters::DEFAUT_DOMAINS; -use concrete_optimizer::optimization::atomic_pattern as optimize_atomic_pattern; -use concrete_optimizer::optimization::wop_atomic_pattern::optimize as optimize_wop_atomic_pattern; -use rayon_cond::CondIterator; - -const _4_SIGMA: f64 = 1.0 - 0.999_936_657_516; -const MIN_LOG_POLY_SIZE: u64 = DEFAUT_DOMAINS - .glwe_pbs_constrained - .log2_polynomial_size - .start as u64; -const MAX_LOG_POLY_SIZE: u64 = - DEFAUT_DOMAINS.glwe_pbs_constrained.log2_polynomial_size.end as u64 - 1; -const MAX_GLWE_DIM: u64 = DEFAUT_DOMAINS.glwe_pbs_constrained.glwe_dimension.end - 1; -const MIN_LWE_DIM: u64 = DEFAUT_DOMAINS.free_glwe.glwe_dimension.start as u64; -const MAX_LWE_DIM: u64 = DEFAUT_DOMAINS.free_glwe.glwe_dimension.end as u64 - 1; - -/// Simple program to greet a person -#[derive(Parser, Debug)] -#[clap(author, version, about, long_about = None)] -struct Args { - #[clap(long, default_value_t = 1, help = "1..16")] - min_precision: u64, - - #[clap(long, default_value_t = 8, help = "1..16")] - max_precision: u64, - - #[clap(long, default_value_t = _4_SIGMA)] - p_error: f64, - - #[clap(long, default_value_t = 128, help = "Only 128 is supported")] - security_level: u64, - - #[clap(long, default_value_t = MIN_LOG_POLY_SIZE, help = "8..16")] - min_log_poly_size: u64, - - #[clap(long, default_value_t = MAX_LOG_POLY_SIZE, help = "8..16")] - max_log_poly_size: u64, - - #[clap(long, default_value_t = 1, help = "EXPERIMENTAL")] - min_glwe_dim: u64, - - #[clap(long, default_value_t = MAX_GLWE_DIM, help = "EXPERIMENTAL")] - max_glwe_dim: u64, - - #[clap(long, default_value_t = MIN_LWE_DIM)] - min_intern_lwe_dim: u64, - - #[clap(long, default_value_t = MAX_LWE_DIM)] - max_intern_lwe_dim: u64, // 16bits needs around 1300 - - #[clap(long, default_value_t = 4096)] - sum_size: u64, - - #[clap(long)] - no_parallelize: bool, - - #[clap(long)] - wop_pbs: bool, -} - -fn main() { - let args = Args::parse(); - let sum_size = args.sum_size; - let p_error = args.p_error; - let security_level = args.security_level; - assert!( - security_level == 128, - "Only 128bits of security is supported" - ); - - let glwe_log_polynomial_sizes: Vec<_> = - (args.min_log_poly_size..=args.max_log_poly_size).collect(); - let glwe_dimensions: Vec<_> = (args.min_glwe_dim..=args.max_glwe_dim).collect(); - let internal_lwe_dimensions: Vec<_> = - (args.min_intern_lwe_dim..=args.max_intern_lwe_dim).collect(); - - let precisions = args.min_precision..=args.max_precision; - let manps: Vec<_> = (0..=31).collect(); - - // let guard = pprof::ProfilerGuard::new(100).unwrap(); - - let precisions_iter = CondIterator::new(precisions.clone(), !args.no_parallelize); - - #[rustfmt::skip] - let all_results = precisions_iter.map(|precision| { - let mut last_solution = None; - manps.iter().map(|&manp| { - let noise_scale = 2_f64.powi(manp); - let result = if args.wop_pbs { - optimize_wop_atomic_pattern::optimize_one::( - sum_size, - precision, - security_level, - noise_scale, - p_error, - &glwe_log_polynomial_sizes, - &glwe_dimensions, - &internal_lwe_dimensions, - - ) - } else { - optimize_atomic_pattern::optimize_one::( - sum_size, - precision, - security_level, - noise_scale, - p_error, - &glwe_log_polynomial_sizes, - &glwe_dimensions, - &internal_lwe_dimensions, - last_solution, // 33% gains - ) - }; - last_solution = result.best_solution; - result - }) - .collect::>() - }) - .collect::>(); - - /* - if let Ok(report) = guard.report().build() { - let file = std::fs::File::create("flamegraph.svg").unwrap(); - let mut options = pprof::flamegraph::Options::default(); - options.image_width = Some(32000); - report.flamegraph_with_options(file, &mut options).unwrap(); - }; - */ - - println!("{{ /* {:1.1e} errors */", p_error); - for (precision_i, precision) in precisions.enumerate() { - println!("{{ /* precision {:2} */", precision); - for (manp_i, manp) in manps.clone().iter().enumerate() { - let solution = all_results[precision_i][manp_i].best_solution; - match solution { - Some(solution) => { - println!(" /* {:2} */ V0Parameter({:2}, {:2}, {:4}, {:2}, {:2}, {:2}, {:2}), \t\t // {:4} mops, {:1.1e} errors", - manp, solution.glwe_dimension, (solution.glwe_polynomial_size as f64).log2() as u64, - solution.internal_ks_output_lwe_dimension, - solution.br_decomposition_level_count, solution.br_decomposition_base_log, - solution.ks_decomposition_level_count, solution.ks_decomposition_base_log, - (solution.complexity / (1024.0 * 1024.0)) as u64, - solution.p_error - ); - } - None => { - println!( - " /* {:2} : NO SOLUTION */ V0Parameter(0,0,0,0,0,0,0),", - manp - ); - } - } - } - println!(" }},"); - } - println!("}}"); -} - -#[cfg(test)] -mod tests { - #[test] - fn test_reference_output() { - const REF_FILE: &str = "src/v0_parameters.ref-20-06-2022"; - const V0_PARAMETERS_EXE: &str = "../target/debug/v0-parameters"; - const CMP_LINES: &str = "\n"; - const EXACT_EQUALITY: i32 = 0; - let _ = std::process::Command::new("cargo") - .args(["build", "-q"]) - .status() - .expect("Can't build"); - assert!(std::path::Path::new(V0_PARAMETERS_EXE).exists()); - - let output = std::process::Command::new(V0_PARAMETERS_EXE) - .output() - .expect("failed to execute process"); - - let actual_output = std::str::from_utf8(&output.stdout).expect("Bad content"); - let error_output = std::str::from_utf8(&output.stderr).expect("Bad content"); - assert_eq!(error_output, ""); - - let expected_output = std::fs::read_to_string(REF_FILE).expect("Can't read reference file"); - - text_diff::assert_diff(&expected_output, actual_output, CMP_LINES, EXACT_EQUALITY); - } - - #[test] - fn test_reference_wop_output() { - const REF_FILE: &str = "src/wop_parameters.ref-20-06-2022"; - const V0_PARAMETERS_EXE: &str = "../target/release/v0-parameters"; - const CMP_LINES: &str = "\n"; - const EXACT_EQUALITY: i32 = 0; - let _ = std::process::Command::new("cargo") - .args(["build", "--release", "-q"]) - .status() - .expect("Can't build"); - assert!(std::path::Path::new(V0_PARAMETERS_EXE).exists()); - - #[rustfmt::skip] - let output = std::process::Command::new(V0_PARAMETERS_EXE) - .args([ - "--wop-pbs", - "--min-intern-lwe-dim", "450", - "--max-intern-lwe-dim", "600", - "--min-precision", "1", - "--max-precision", "16", - "--min-log-poly-size", "10", - "--max-log-poly-size", "11", - "--" - ]) - .output().expect("Failed") - ; - let actual_output = std::str::from_utf8(&output.stdout).expect("Bad content"); - let error_output = std::str::from_utf8(&output.stderr).expect("Bad content"); - - assert_eq!(error_output, ""); - - let expected_output = std::fs::read_to_string(REF_FILE).expect("Can't read reference file"); - - text_diff::assert_diff(&expected_output, actual_output, CMP_LINES, EXACT_EQUALITY); - } -}