From 585c17a163b04b70efd94e660408a943513f0392 Mon Sep 17 00:00:00 2001 From: "Mayeul@Zama" Date: Wed, 15 Jun 2022 18:58:43 +0200 Subject: [PATCH] store precomputation in the macro-parameters loops as vecs --- .../wop_atomic_pattern/optimize.rs | 346 ++++++------------ concrete-optimizer/src/parameters.rs | 2 +- v0-parameters/src/main.rs | 3 +- 3 files changed, 123 insertions(+), 228 deletions(-) diff --git a/concrete-optimizer/src/optimization/wop_atomic_pattern/optimize.rs b/concrete-optimizer/src/optimization/wop_atomic_pattern/optimize.rs index d35f83e33..bee88b3e8 100644 --- a/concrete-optimizer/src/optimization/wop_atomic_pattern/optimize.rs +++ b/concrete-optimizer/src/optimization/wop_atomic_pattern/optimize.rs @@ -22,7 +22,6 @@ use crate::utils::square; use complexity_atomic_pattern::DEFAULT as DEFAULT_COMPLEXITY; use concrete_commons::dispersion::{DispersionParameter, Variance}; use concrete_commons::numeric::UnsignedInteger; -use std::collections::HashMap; pub fn find_p_error(kappa: f64, variance_max: f64, current_maximum_noise: f64) -> f64 { let sigma = Variance(variance_max).get_standard_dev() * kappa; @@ -84,175 +83,111 @@ impl Solution { #[allow(clippy::type_complexity)] #[derive(Debug)] -pub struct Tab { - pbs: HashMap<(u64, u64, u64, u64, u64), (f64, Complexity)>, - modulus_switching: HashMap<(u64, u64), f64>, - key_switching: HashMap<(u64, u64, u64), Vec<(KsDecompositionParameters, (f64, Complexity))>>, - // NEW VALUE MEMOIZED - pp_switching: HashMap<(u64, u64, u64, u64), (f64, Complexity)>, +struct NoiseCostByMicroParam { + pbs: Vec<(f64, Complexity)>, + key_switching: Vec<(f64, Complexity)>, + pp_switching: Vec<(f64, Complexity)>, } #[allow(clippy::too_many_lines)] -pub fn tabulate_circuit_bootstrap( +fn compute_noise_cost_by_micro_param( security_level: u64, - maximum_acceptable_error_probability: f64, - glwe_log_polynomial_sizes: &[u64], - glwe_dimensions: &[u64], - internal_lwe_dimensions: &[u64], -) -> Tab { - assert_eq!(security_level, 128); - assert!(0.0 < maximum_acceptable_error_probability); - assert!(maximum_acceptable_error_probability < 1.0); + glwe_params: GlweParameters, + + internal_dim: u64, +) -> NoiseCostByMicroParam { + assert!(256 < internal_dim); + + let mut noise_cost_pbs = Vec::new(); + let mut noise_cost_key_switching = Vec::new(); + let mut noise_cost_pp_switching = Vec::new(); let ciphertext_modulus_log = W::BITS as u64; - let mut noise_cost_pbs = HashMap::new(); - let mut noise_cost_modulus_switching = HashMap::new(); - let mut noise_cost_key_switching = HashMap::new(); - let mut noise_cost_pp_switching = HashMap::new(); - for &glwe_dim in glwe_dimensions { - for &glwe_log_poly_size in glwe_log_polynomial_sizes { - assert!(8 <= glwe_log_poly_size); - assert!(glwe_log_poly_size < 18); - let glwe_poly_size = 1 << glwe_log_poly_size; + let variance_bsk = + security::glwe::minimal_variance(glwe_params, ciphertext_modulus_log, security_level); - if glwe_dim * glwe_poly_size <= 1 << 13 { - let glwe_params = GlweParameters { - log2_polynomial_size: glwe_log_poly_size, - glwe_dimension: glwe_dim, - }; - let variance_bsk = security::glwe::minimal_variance( - glwe_params, - ciphertext_modulus_log, - security_level, - ); + let variance_ksk = + noise_atomic_pattern::variance_ksk(internal_dim, ciphertext_modulus_log, security_level); - for &internal_dim in internal_lwe_dimensions { - assert!(256 < internal_dim); + for &br_decomposition_parameter in BR_BL.iter() { + let pbs_parameters = PbsParameters { + internal_lwe_dimension: LweDimension(internal_dim), + br_decomposition_parameter, + output_glwe_params: glwe_params, + }; - let macro_key = (glwe_dim, glwe_poly_size, internal_dim); + let complexity_pbs = DEFAULT_COMPLEXITY + .pbs + .complexity(pbs_parameters, ciphertext_modulus_log); - let variance_ksk = noise_atomic_pattern::variance_ksk( - internal_dim, - ciphertext_modulus_log, - security_level, - ); - let noise_modulus_switching = - noise_atomic_pattern::estimate_modulus_switching_noise_with_binary_key::( - internal_dim, - glwe_params.polynomial_size(), - ) - .get_variance(); - let _ = noise_cost_modulus_switching - .insert((internal_dim, glwe_poly_size), noise_modulus_switching); + // let complexity_cmux = + // complexity_pbs / (pbs_parameters.internal_lwe_dimension.0 as f64); - for &br_decomposition_parameter in BR_BL.iter() { - let pbs_parameters = PbsParameters { - internal_lwe_dimension: LweDimension(internal_dim), - br_decomposition_parameter, - output_glwe_params: glwe_params, - }; + // PBS of the first layer of the CB + let base_noise = noise_atomic_pattern::variance_bootstrap::( + pbs_parameters, + ciphertext_modulus_log, + variance_bsk, + ) + .get_variance(); - let complexity_pbs = DEFAULT_COMPLEXITY - .pbs - .complexity(pbs_parameters, ciphertext_modulus_log); - - // let complexity_cmux = - // complexity_pbs / (pbs_parameters.internal_lwe_dimension.0 as f64); - - // PBS of the first layer of the CB - let base_noise = noise_atomic_pattern::variance_bootstrap::( - pbs_parameters, - ciphertext_modulus_log, - variance_bsk, - ) - .get_variance(); - - let _ = noise_cost_pbs.insert( - ( - glwe_dim, - glwe_poly_size, - internal_dim, - br_decomposition_parameter.log2_base, - br_decomposition_parameter.level, - ), - (base_noise, complexity_pbs), - ); - } - - let mut ks_seq = Vec::with_capacity(KS_BL_FOR_CB.len()); - for &ks_decomposition_parameter in KS_BL_FOR_CB.iter() { - let keyswitch_parameter = KeyswitchParameters { - input_lwe_dimension: LweDimension(glwe_poly_size * glwe_dim), - output_lwe_dimension: LweDimension(internal_dim), - ks_decomposition_parameter, - }; - let complexity_keyswitch = DEFAULT_COMPLEXITY - .ks_lwe - .complexity(keyswitch_parameter, ciphertext_modulus_log); - // Keyswitch before bootstrap - let noise_keyswitch = noise_atomic_pattern::variance_keyswitch::( - keyswitch_parameter, - ciphertext_modulus_log, - variance_ksk, - ) - .get_variance(); - ks_seq.push(( - ks_decomposition_parameter, - (noise_keyswitch, complexity_keyswitch), - )); - } - std::mem::drop(noise_cost_key_switching.insert(macro_key, ks_seq)); - - for &pp_ks_decomposition_parameter in BR_BL.iter() { - let ppks_parameter = PbsParameters { - internal_lwe_dimension: LweDimension( - glwe_params.glwe_dimension * glwe_params.polynomial_size(), - ), - br_decomposition_parameter: pp_ks_decomposition_parameter, - output_glwe_params: glwe_params, - }; - // We assume the packing KS and theexternal product in a PBSto have - // the same parameters (base, level) - let noise_private_packing_ks = estimate_packing_private_keyswitch::( - Variance(0.), - variance_bsk, - ppks_parameter, - ) - .get_variance(); - - let ppks_parameter_complexity = KeyswitchParameters { - input_lwe_dimension: LweDimension( - glwe_params.glwe_dimension * glwe_params.polynomial_size(), - ), - output_lwe_dimension: LweDimension( - glwe_params.glwe_dimension * glwe_params.polynomial_size(), - ), - ks_decomposition_parameter: KsDecompositionParameters { - level: pp_ks_decomposition_parameter.level, - log2_base: pp_ks_decomposition_parameter.log2_base, - }, - }; - let complexity_ppks = DEFAULT_COMPLEXITY - .ks_lwe - .complexity(ppks_parameter_complexity, ciphertext_modulus_log); - let _ = noise_cost_pp_switching.insert( - ( - glwe_dim, - glwe_poly_size, - pp_ks_decomposition_parameter.log2_base, - pp_ks_decomposition_parameter.level, - ), - (noise_private_packing_ks, complexity_ppks), - ); - } - } - } - } + noise_cost_pbs.push((base_noise, complexity_pbs)); } - Tab { + + for &ks_decomposition_parameter in KS_BL_FOR_CB.iter() { + let keyswitch_parameter = KeyswitchParameters { + input_lwe_dimension: LweDimension(glwe_params.sample_extract_lwe_dimension()), + output_lwe_dimension: LweDimension(internal_dim), + ks_decomposition_parameter, + }; + let complexity_keyswitch = DEFAULT_COMPLEXITY + .ks_lwe + .complexity(keyswitch_parameter, ciphertext_modulus_log); + // Keyswitch before bootstrap + let noise_keyswitch = noise_atomic_pattern::variance_keyswitch::( + keyswitch_parameter, + ciphertext_modulus_log, + variance_ksk, + ) + .get_variance(); + noise_cost_key_switching.push((noise_keyswitch, complexity_keyswitch)); + } + + for &pp_ks_decomposition_parameter in BR_BL.iter() { + let ppks_parameter = PbsParameters { + internal_lwe_dimension: LweDimension( + glwe_params.glwe_dimension * glwe_params.polynomial_size(), + ), + br_decomposition_parameter: pp_ks_decomposition_parameter, + output_glwe_params: glwe_params, + }; + // We assume the packing KS and theexternal product in a PBSto have + // the same parameters (base, level) + let noise_private_packing_ks = + estimate_packing_private_keyswitch::(Variance(0.), variance_bsk, ppks_parameter) + .get_variance(); + + let ppks_parameter_complexity = KeyswitchParameters { + input_lwe_dimension: LweDimension( + glwe_params.glwe_dimension * glwe_params.polynomial_size(), + ), + output_lwe_dimension: LweDimension( + glwe_params.glwe_dimension * glwe_params.polynomial_size(), + ), + ks_decomposition_parameter: KsDecompositionParameters { + level: pp_ks_decomposition_parameter.level, + log2_base: pp_ks_decomposition_parameter.log2_base, + }, + }; + let complexity_ppks = DEFAULT_COMPLEXITY + .ks_lwe + .complexity(ppks_parameter_complexity, ciphertext_modulus_log); + noise_cost_pp_switching.push((noise_private_packing_ks, complexity_ppks)); + } + + NoiseCostByMicroParam { pbs: noise_cost_pbs, - modulus_switching: noise_cost_modulus_switching, key_switching: noise_cost_key_switching, pp_switching: noise_cost_pp_switching, } @@ -263,17 +198,16 @@ const BITS_PADDING_WITHOUT_NOISE: u64 = 1; #[allow(clippy::expect_fun_call)] #[allow(clippy::identity_op)] #[allow(clippy::too_many_lines)] -pub fn optimise_one_with_memo( +pub fn optimise_one( precision: u64, // max precision of a word log_norm: f64, // ?? norm2 of noise multisum, complexity of multisum is neglected - _security_level: u64, + security_level: u64, maximum_acceptable_error_probability: f64, glwe_log_polynomial_sizes: &[u64], glwe_dimensions: &[u64], internal_lwe_dimensions: &[u64], n_functions: u64, // Many functions at the same time, stay at 1 for start - memo: &Tab, - n_inputs: u64, // Tau (nb blocks) + n_inputs: u64, // Tau (nb blocks) ) -> OptimizationState { assert!(0.0 < maximum_acceptable_error_probability); assert!(maximum_acceptable_error_probability < 1.0); @@ -313,25 +247,30 @@ pub fn optimise_one_with_memo( glwe_dimension: glwe_dim, }; - let input_lwe_dimension = glwe_params.lwe_dimension(); + let input_lwe_dimension = glwe_params.sample_extract_lwe_dimension(); for &internal_dim in internal_lwe_dimensions { - let &noise_modulus_switching = memo - .modulus_switching - .get(&(internal_dim, glwe_poly_size)) - .expect(&format!( - "Internal_dim : {} ; glwe poly size: {}", - internal_dim, glwe_poly_size - )); + let micro_tab = compute_noise_cost_by_micro_param::( + security_level, + glwe_params, + internal_dim, + ); + + let noise_modulus_switching = + noise_atomic_pattern::estimate_modulus_switching_noise_with_binary_key::( + internal_dim, + glwe_params.polynomial_size(), + ) + .get_variance(); if noise_modulus_switching > variance_max { continue; } - let macro_key = (glwe_dim, glwe_poly_size, internal_dim); - // BlindRotate dans Circuit BS - for &br_decomposition_parameter in BR_BL_FOR_CB.iter() { + for (br_dp_index, &br_decomposition_parameter) in + BR_BL_FOR_CB.iter().enumerate() + { // Pbs dans BitExtract et Circuit BS et FP-KS (partagés) // TODO: choisir indépendemment(separate FP-KS) let pbs_parameters = PbsParameters { @@ -340,30 +279,13 @@ pub fn optimise_one_with_memo( output_glwe_params: glwe_params, }; - let &(base_noise, complexity_pbs) = memo - .pbs - .get(&( - glwe_dim, - glwe_poly_size, - internal_dim, - br_decomposition_parameter.log2_base, - br_decomposition_parameter.level, - )) - .unwrap(); + let (base_noise, complexity_pbs) = micro_tab.pbs[br_dp_index]; // new pbs key for the bit extract pbs, shared - let bit_extract_decomposition_parameter = br_decomposition_parameter; + let bit_extract_dp_index = br_dp_index; - let &(_bit_extract_base_noise, complexity_bit_extract_pbs) = memo - .pbs - .get(&( - glwe_dim, - glwe_poly_size, - internal_dim, - bit_extract_decomposition_parameter.log2_base, - bit_extract_decomposition_parameter.level, - )) - .unwrap(); + let (_bit_extract_base_noise, complexity_bit_extract_pbs) = + micro_tab.pbs[bit_extract_dp_index]; let complexity_bit_extract_wo_ks = (n_inputs * (precision - 1)) as f64 * complexity_bit_extract_pbs; @@ -373,25 +295,11 @@ pub fn optimise_one_with_memo( } // private packing keyswitch, <=> FP-KS (Circuit Boostrap) - let pp_ks_decomposition_parameter = - pbs_parameters.br_decomposition_parameter; + let pp_ks_dp_index = br_dp_index; // Circuit Boostrap - let &(base_noise_private_packing_ks, complexity_ppks) = memo - .pp_switching - .get(&( - glwe_dim, - glwe_poly_size, - pp_ks_decomposition_parameter.log2_base, - pp_ks_decomposition_parameter.level, - )) - .expect(&format!( - "{}, {}, {}, {}", - glwe_dim, - glwe_poly_size, - pp_ks_decomposition_parameter.log2_base, - pp_ks_decomposition_parameter.level, - )); + let (base_noise_private_packing_ks, complexity_ppks) = + micro_tab.pp_switching[pp_ks_dp_index]; // CircuitBootstrap: new parameters l,b for &circuit_pbs_decomposition_parameter in CB_V1_BL.iter() { @@ -489,12 +397,11 @@ pub fn optimise_one_with_memo( let noise_max = noise_ggsw_reencoding.max(noise_hybrid_packing); // Shared by all pbs (like brs) - let key_switching_q = memo.key_switching.get(¯o_key).unwrap(); - for &( - ks_decomposition_parameter, - (noise_keyswitch, complexity_keyswitch), - ) in key_switching_q + for (ks_dp_index, &ks_decomposition_parameter) in + KS_BL_FOR_CB.iter().enumerate() { + let (noise_keyswitch, complexity_keyswitch) = + micro_tab.key_switching[ks_dp_index]; let noise_max = noise_max + noise_keyswitch; if noise_max > variance_max { continue; @@ -588,23 +495,13 @@ pub fn optimize_one( glwe_log_polynomial_sizes: &[u64], glwe_dimensions: &[u64], internal_lwe_dimensions: &[u64], - memo_opt: &mut Option, ) -> atomic_pattern::OptimizationState { let partitionning = default_partitionning(precision); let nb_words = partitionning.len() as u64; let max_word_precision = *partitionning.iter().max().unwrap() as u64; let log_norm = noise_factor.log2(); let n_functions = 1; - let memo = memo_opt.get_or_insert_with(|| { - tabulate_circuit_bootstrap::( - security_level, - maximum_acceptable_error_probability, - glwe_log_polynomial_sizes, - glwe_dimensions, - internal_lwe_dimensions, - ) - }); - let result = optimise_one_with_memo::( + let result = optimise_one::( max_word_precision, log_norm, security_level, @@ -613,7 +510,6 @@ pub fn optimize_one( glwe_dimensions, internal_lwe_dimensions, n_functions, - memo, nb_words, // Tau ); let best_solution = result.best_solution.map(|sol| atomic_pattern::Solution { diff --git a/concrete-optimizer/src/parameters.rs b/concrete-optimizer/src/parameters.rs index b348b95d0..4750862bf 100644 --- a/concrete-optimizer/src/parameters.rs +++ b/concrete-optimizer/src/parameters.rs @@ -26,7 +26,7 @@ mod individual { pub fn polynomial_size(self) -> u64 { 1 << self.log2_polynomial_size } - pub fn lwe_dimension(self) -> u64 { + pub fn sample_extract_lwe_dimension(self) -> u64 { self.glwe_dimension << self.log2_polynomial_size } } diff --git a/v0-parameters/src/main.rs b/v0-parameters/src/main.rs index 819dc83d3..22cd2b01f 100644 --- a/v0-parameters/src/main.rs +++ b/v0-parameters/src/main.rs @@ -92,7 +92,6 @@ fn main() { #[rustfmt::skip] let all_results = precisions_iter.map(|precision| { let mut last_solution = None; - let mut memo = None; manps.iter().map(|&manp| { let noise_scale = 2_f64.powi(manp); let result = if args.wop_pbs { @@ -105,7 +104,7 @@ fn main() { &glwe_log_polynomial_sizes, &glwe_dimensions, &internal_lwe_dimensions, - &mut memo, + ) } else { optimize_atomic_pattern::optimize_one::(