feat(bench): add optimization benches

refactor v0-parameters
This commit is contained in:
Mayeul@Zama
2022-06-16 15:36:58 +02:00
committed by mayeul-zama
parent 8775cc0be3
commit fd498a5286
5 changed files with 315 additions and 229 deletions

View File

@@ -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

View File

@@ -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);

View File

@@ -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();
}

243
v0-parameters/src/lib.rs Normal file
View File

@@ -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<Vec<OptimizationState>> {
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::<u64>(
sum_size,
precision,
security_level,
noise_scale,
p_error,
&glwe_log_polynomial_sizes,
&glwe_dimensions,
&internal_lwe_dimensions,
)
} else {
optimize_atomic_pattern::optimize_one::<u64>(
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::<Vec<_>>()
})
.collect::<Vec<_>>()
}
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::<u8>::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::<u8>::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);
}
}

View File

@@ -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::<u64>(
sum_size,
precision,
security_level,
noise_scale,
p_error,
&glwe_log_polynomial_sizes,
&glwe_dimensions,
&internal_lwe_dimensions,
)
} else {
optimize_atomic_pattern::optimize_one::<u64>(
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::<Vec<_>>()
})
.collect::<Vec<_>>();
/*
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);
}
}