feat(dep): concrete-cpu-noise-model

This commit is contained in:
Mayeul@Zama
2022-11-09 15:39:15 +01:00
committed by mayeul-zama
parent d0654c0fa7
commit ebb9c86048
19 changed files with 130 additions and 668 deletions

View File

@@ -6,8 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
concrete-commons = { git = "ssh://git@github.com/zama-ai/concrete-core.git", rev = "715d4a18a59d13a5a51fee14bc1f6578de665c20" }
concrete-npe = { git = "ssh://git@github.com/zama-ai/concrete-core.git", rev = "715d4a18a59d13a5a51fee14bc1f6578de665c20" }
concrete-cpu-noise-model = { git = "ssh://git@github.com/zama-ai/concrete-cpu.git", rev = "c185266e55f31b7d4c6e3264d9569eb03b4a785e" }
file-lock = "2.1.6"
serde = { version = "1.0", features = ["derive"] }
bincode = "1.3"

View File

@@ -24,6 +24,7 @@ pub mod global_parameters;
pub mod noise_estimator;
pub mod optimization;
pub mod parameters;
pub mod security;
pub mod utils;
pub mod weight;
pub use concrete_cpu_noise_model::gaussian_noise::security::supported_security_levels;

View File

@@ -1,4 +1,4 @@
use concrete_commons::dispersion::{DispersionParameter, Variance};
use concrete_cpu_noise_model::gaussian_noise::conversion::modular_variance_to_variance;
use crate::utils::square;
@@ -33,7 +33,7 @@ fn safe_variance_bound_from_p_error(
let safe_sigma = fatal_noise_limit / kappa;
let modular_variance = square(safe_sigma);
Variance::from_modular_variance(modular_variance, ciphertext_modulus_log).get_variance()
modular_variance_to_variance(modular_variance, ciphertext_modulus_log)
}
pub fn safe_variance_bound_2padbits(

View File

@@ -1,152 +1,20 @@
use crate::global_parameters::DEFAUT_DOMAINS;
use crate::parameters::{
AtomicPatternParameters, BrDecompositionParameters, CmuxParameters, GlweParameters,
KeyswitchParameters, PbsParameters,
};
use crate::{parameters, security};
use concrete_commons::dispersion::{DispersionParameter, Variance};
use concrete_commons::key_kinds::BinaryKeyKind;
use concrete_commons::numeric::UnsignedInteger;
use concrete_commons::parameters::{
DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
};
use crate::parameters::AtomicPatternParameters;
use concrete_cpu_noise_model::gaussian_noise::noise::keyswitch::variance_keyswitch;
use concrete_cpu_noise_model::gaussian_noise::noise::modulus_switching::estimate_modulus_switching_noise_with_binary_key;
use concrete_cpu_noise_model::gaussian_noise::security::minimal_variance_lwe;
const FFT_SCALING_WEIGHT: f64 = -2.577_224_94;
/// Additional noise generated by the keyswitch step.
pub fn variance_keyswitch(
param: KeyswitchParameters,
ciphertext_modulus_log: u32,
variance_ksk: Variance,
) -> Variance {
concrete_npe::estimate_keyswitch_noise_lwe_to_glwe_with_constant_terms::<
Variance,
Variance,
BinaryKeyKind,
>(
LweDimension(param.input_lwe_dimension.0 as usize),
Variance(0.0),
variance_ksk,
DecompositionBaseLog(param.ks_decomposition_parameter.log2_base as usize),
DecompositionLevelCount(param.ks_decomposition_parameter.level as usize),
ciphertext_modulus_log,
)
}
/// Compute the variance parameter of the keyswitch key.
pub fn variance_ksk(
internal_ks_output_lwe_dimension: u64,
ciphertext_modulus_log: u32,
security_level: u64,
) -> Variance {
let glwe_params = GlweParameters {
log2_polynomial_size: 0,
glwe_dimension: internal_ks_output_lwe_dimension,
};
// https://github.com/zama-ai/concrete-optimizer/blob/prototype/python/optimizer/noise_formulas/keyswitch.py#L13
security::glwe::minimal_variance(glwe_params, ciphertext_modulus_log, security_level)
}
/// Additional noise generated by fft computation
pub fn fft_noise(
internal_ks_output_lwe_dimension: u64, //n_small
glwe_params: GlweParameters,
br_decomposition_parameter: BrDecompositionParameters,
ciphertext_modulus_log: u32,
) -> Variance {
// https://github.com/zama-ai/concrete-optimizer/blob/prototype/python/optimizer/noise_formulas/bootstrap.py#L25
let n = internal_ks_output_lwe_dimension as f64;
let b = 2_f64.powi(br_decomposition_parameter.log2_base as i32);
let l = br_decomposition_parameter.level as f64;
let big_n = glwe_params.polynomial_size() as f64;
let k = glwe_params.glwe_dimension;
assert!(k > 0, "k = {k}");
assert!(
DEFAUT_DOMAINS.glwe_pbs_constrained.glwe_dimension.end > k,
"k={} and bound={}",
k,
DEFAUT_DOMAINS.glwe_pbs_constrained.glwe_dimension.end
);
// 22 = 2 x 11, 11 = 64 -53
let scale_margin = (1_u64 << 22) as f64;
let res = n
* f64::exp2(FFT_SCALING_WEIGHT)
* scale_margin
* l
* b
* b
* big_n.powf(2.)
* (k as f64 + 1.);
Variance::from_modular_variance(res, ciphertext_modulus_log)
}
/// Final reduced noise generated by the final bootstrap step.
/// Note that it does not depends from input noise, assuming the bootstrap is successful
pub fn variance_bootstrap(
param: PbsParameters,
ciphertext_modulus_log: u32,
variance_bsk: Variance,
) -> Variance {
let out_variance_pbs = concrete_npe::estimate_pbs_noise::<Variance, BinaryKeyKind>(
LweDimension(param.internal_lwe_dimension.0 as usize),
PolynomialSize(param.output_glwe_params.polynomial_size() as usize),
GlweDimension(param.output_glwe_params.glwe_dimension as usize),
DecompositionBaseLog(param.br_decomposition_parameter.log2_base as usize),
DecompositionLevelCount(param.br_decomposition_parameter.level as usize),
variance_bsk,
ciphertext_modulus_log,
);
let additional_fft_noise = fft_noise(
param.internal_lwe_dimension.0,
param.output_glwe_params,
param.br_decomposition_parameter,
ciphertext_modulus_log,
);
Variance(out_variance_pbs.get_variance() + additional_fft_noise.get_variance())
}
pub fn variance_cmux(
cmux_params: CmuxParameters,
ciphertext_modulus_log: u32,
variance_bsk: Variance,
) -> Variance {
let pbs_params = PbsParameters {
internal_lwe_dimension: parameters::LweDimension(1),
br_decomposition_parameter: cmux_params.br_decomposition_parameter,
output_glwe_params: cmux_params.output_glwe_params,
};
variance_bootstrap(pbs_params, ciphertext_modulus_log, variance_bsk)
}
pub fn estimate_modulus_switching_noise_with_binary_key(
internal_ks_output_lwe_dimension: u64,
glwe_polynomial_size: u64,
ciphertext_modulus_log: u32,
) -> Variance {
#[allow(clippy::cast_sign_loss)]
let nb_msb = (f64::log2(glwe_polynomial_size as f64) as usize) + 1;
concrete_npe::estimate_modulus_switching_noise_with_binary_key::<Variance>(
LweDimension(internal_ks_output_lwe_dimension as usize),
nb_msb,
Variance(0.0),
ciphertext_modulus_log,
)
}
pub fn maximal_noise<D>(
input_variance: Variance,
pub fn maximal_noise(
input_variance: f64,
param: AtomicPatternParameters,
ciphertext_modulus_log: u32, //log(q)
security_level: u64,
) -> Variance
where
D: DispersionParameter,
{
) -> f64 {
let v_keyswitch = variance_keyswitch(
param.ks_parameters(),
param.input_lwe_dimension.0,
param.ks_decomposition_parameter.log2_base,
param.ks_decomposition_parameter.level,
ciphertext_modulus_log,
variance_ksk(
minimal_variance_lwe(
param.internal_lwe_dimension.0,
ciphertext_modulus_log,
security_level,
@@ -154,182 +22,8 @@ where
);
let v_modulus_switch = estimate_modulus_switching_noise_with_binary_key(
param.internal_lwe_dimension.0,
param.output_glwe_params.polynomial_size(),
param.output_glwe_params.log2_polynomial_size,
ciphertext_modulus_log,
);
Variance(
input_variance.get_variance()
+ v_keyswitch.get_variance()
+ v_modulus_switch.get_variance(),
)
}
/// The maximal noise is attained at the end of the modulus switch.
pub fn maximal_noise_multi_sum<D, W, Ignored>(
dispersions: &[D],
weights_tuples: &[(W, Ignored)],
param: AtomicPatternParameters,
ciphertext_modulus_log: u32,
security_level: u64,
) -> Variance
where
D: DispersionParameter,
W: UnsignedInteger,
{
let v_out_multi_sum = if dispersions.is_empty() {
let mut weights = vec![];
for (weight, _) in weights_tuples.iter() {
weights.push(*weight);
}
concrete_npe::estimate_weighted_sum_noise(dispersions, weights.as_slice())
} else {
Variance(0.0)
};
maximal_noise::<D>(
v_out_multi_sum,
param,
ciphertext_modulus_log,
security_level,
)
}
/// The output noise is the variance boostrap.
pub fn output_noise<D, W>(
param: AtomicPatternParameters,
ciphertext_modulus_log: u32,
security_level: u64,
) -> Variance
where
D: DispersionParameter,
W: UnsignedInteger,
{
// https://github.com/zama-ai/concrete-optimizer/blob/prototype/python/optimizer/noise_formulas/bootstrap.py#L66
let variance_bsk = security::glwe::minimal_variance(
param.output_glwe_params,
ciphertext_modulus_log,
security_level,
);
variance_bootstrap(param.pbs_parameters(), ciphertext_modulus_log, variance_bsk)
}
#[cfg(test)]
mod tests {
use crate::parameters::{
BrDecompositionParameters, GlweParameters, KsDecompositionParameters, LweDimension,
};
use super::*;
#[test]
fn golden_python_prototype_security_variance_keyswitch_1() {
let golden_modular_variance = 5.997_880_135_602_194e68;
let internal_ks_output_lwe_dimension = 1024;
let ciphertext_modulus_log = 128;
let security = 128;
let param = KeyswitchParameters {
input_lwe_dimension: LweDimension(4096),
output_lwe_dimension: LweDimension(internal_ks_output_lwe_dimension),
ks_decomposition_parameter: KsDecompositionParameters {
level: 9,
log2_base: 5,
},
};
let actual = variance_keyswitch(
param,
ciphertext_modulus_log,
variance_ksk(
internal_ks_output_lwe_dimension,
ciphertext_modulus_log,
security,
),
)
.get_modular_variance(128);
approx::assert_relative_eq!(actual, golden_modular_variance, max_relative = 1e-8);
}
#[test]
fn golden_python_prototype_security_variance_keyswitch_2() {
// let golden_modular_variance = 8.580795457940938e+66;
// the full npe implements a part of the full estimation
let golden_modular_variance = 7.407_691_550_271_225e48; // full estimation
let internal_ks_output_lwe_dimension = 512;
let ciphertext_modulus_log = 64;
let security = 128;
let param = KeyswitchParameters {
input_lwe_dimension: LweDimension(2048),
output_lwe_dimension: LweDimension(internal_ks_output_lwe_dimension),
ks_decomposition_parameter: KsDecompositionParameters {
level: 2,
log2_base: 24,
},
};
let actual = variance_keyswitch(
param,
ciphertext_modulus_log,
variance_ksk(
internal_ks_output_lwe_dimension,
ciphertext_modulus_log,
security,
),
)
.get_modular_variance(64);
approx::assert_relative_eq!(actual, golden_modular_variance, max_relative = 1e-8);
}
#[test]
fn security_variance_bootstrap_1() {
let ref_modular_variance = 4.078_296_369_990_673e31;
let glwe_params = GlweParameters {
log2_polynomial_size: 12,
glwe_dimension: 2,
};
let ciphertext_modulus_log = 64;
let security = 128;
let variance_bsk =
security::glwe::minimal_variance(glwe_params, ciphertext_modulus_log, security);
let param = PbsParameters {
internal_lwe_dimension: LweDimension(2048),
br_decomposition_parameter: BrDecompositionParameters {
level: 2,
log2_base: 24,
},
output_glwe_params: glwe_params,
};
let actual = variance_bootstrap(param, ciphertext_modulus_log, variance_bsk)
.get_modular_variance(64);
approx::assert_relative_eq!(actual, ref_modular_variance, max_relative = 1e-8);
}
#[test]
fn golden_python_prototype_security_variance_bootstrap_2() {
// golden value include fft correction
let golden_modular_variance = 3.269_722_907_894_341e55;
let glwe_params = GlweParameters {
log2_polynomial_size: 12,
glwe_dimension: 4,
};
let ciphertext_modulus_log = 128;
let security = 128;
let variance_bsk =
security::glwe::minimal_variance(glwe_params, ciphertext_modulus_log, security);
let param = PbsParameters {
internal_lwe_dimension: LweDimension(1024),
br_decomposition_parameter: BrDecompositionParameters {
level: 9,
log2_base: 5,
},
output_glwe_params: glwe_params,
};
let actual = variance_bootstrap(param, ciphertext_modulus_log, variance_bsk)
.get_modular_variance(128);
approx::assert_relative_eq!(actual, golden_modular_variance, max_relative = 1e-8);
}
input_variance + v_keyswitch + v_modulus_switch
}

View File

@@ -1,42 +1 @@
use concrete_commons::dispersion::{DispersionParameter, Variance};
use concrete_commons::key_kinds::BinaryKeyKind;
use concrete_npe::KeyDispersion;
use crate::parameters::CmuxParameters;
use crate::utils::square;
pub fn estimate_packing_private_keyswitch<T>(
var_glwe: Variance,
var_ggsw: Variance,
param: CmuxParameters,
ciphertext_modulus_log: u32,
) -> Variance {
type K = BinaryKeyKind;
let l = param.br_decomposition_parameter.level as f64;
let b = (1 << param.br_decomposition_parameter.log2_base) as f64;
let n = (param.output_glwe_params.glwe_dimension * param.output_glwe_params.polynomial_size())
as f64; // param.internal_lwe_dimension.0 as f64;
let b2l = f64::powf(b, 2. * l);
let var_s_w = 1. / 4.;
let mean_s_w = 1. / 2.;
// println!("n = {}", n);
let res_1 = (l * (n + 1.) * var_ggsw.get_modular_variance(ciphertext_modulus_log))
* (square(b) + 2.)
/ 12.;
#[allow(clippy::cast_possible_wrap)]
let res_3 = ((square(f64::powi(2., ciphertext_modulus_log as i32)) - b2l) / (12. * b2l)
* (1.
+ n * (K::variance_key_coefficient(ciphertext_modulus_log)
.get_modular_variance(ciphertext_modulus_log)
+ square(K::expectation_key_coefficient())))
+ n / 4.
* K::variance_key_coefficient(ciphertext_modulus_log)
.get_modular_variance(ciphertext_modulus_log)
+ var_glwe.get_modular_variance(ciphertext_modulus_log))
* (var_s_w + square(mean_s_w));
let res_5 = var_s_w * (1. / 4. * square(1. - n * K::expectation_key_coefficient()));
Variance::from_modular_variance(res_1 + res_3 + res_5, ciphertext_modulus_log)
}

View File

@@ -1,9 +1,10 @@
use concrete_cpu_noise_model::gaussian_noise::noise::modulus_switching::estimate_modulus_switching_noise_with_binary_key;
use super::config::{Config, SearchSpace};
use super::wop_atomic_pattern::optimize::find_p_error;
use crate::noise_estimator::error;
use crate::noise_estimator::operators::atomic_pattern as noise_atomic_pattern;
use crate::parameters::{BrDecompositionParameters, GlweParameters, KsDecompositionParameters};
use crate::utils::square;
use concrete_commons::dispersion::{DispersionParameter, Variance};
use super::decomposition::{
blind_rotate, circuit_bootstrap, keyswitch, pp_switch, DecompCaches, PersistDecompCaches,
@@ -58,13 +59,11 @@ fn update_state_with_best_decompositions(
) {
let glwe_poly_size = glwe_params.polynomial_size();
let input_lwe_dimension = glwe_params.glwe_dimension * glwe_poly_size;
let noise_modulus_switching =
noise_atomic_pattern::estimate_modulus_switching_noise_with_binary_key(
internal_dim,
glwe_poly_size,
consts.config.ciphertext_modulus_log,
)
.get_variance();
let noise_modulus_switching = estimate_modulus_switching_noise_with_binary_key(
internal_dim,
glwe_params.log2_polynomial_size,
consts.config.ciphertext_modulus_log,
);
let safe_variance = consts.safe_variance;
if CUTS && noise_modulus_switching > safe_variance {
return;
@@ -111,9 +110,7 @@ fn update_state_with_best_decompositions(
}
// feasible and at least as good complexity
if complexity < best_complexity || noise_max < best_variance {
let sigma = Variance(safe_variance).get_standard_dev() * consts.kappa;
let sigma_scale = sigma / Variance(noise_max).get_standard_dev();
let p_error = error::error_probability_of_sigma_scale(sigma_scale);
let p_error = find_p_error(consts.kappa, safe_variance, noise_max);
let BrDecompositionParameters {
level: br_l,
@@ -188,15 +185,13 @@ pub fn optimize_one(
// cut only on glwe_poly_size based of modulus switching noise
// assume this noise is increasing with lwe_intern_dim
let min_internal_lwe_dimensions = search_space.internal_lwe_dimensions[0];
let lower_bound_cut = |glwe_poly_size| {
let lower_bound_cut = |glwe_log_poly_size| {
// TODO: cut if min complexity is higher than current best
CUTS && noise_atomic_pattern::estimate_modulus_switching_noise_with_binary_key(
CUTS && estimate_modulus_switching_noise_with_binary_key(
min_internal_lwe_dimensions,
glwe_poly_size,
glwe_log_poly_size,
ciphertext_modulus_log,
)
.get_variance()
> consts.safe_variance
) > consts.safe_variance
};
let mut caches = persistent_caches.caches();
@@ -205,8 +200,7 @@ pub fn optimize_one(
for &glwe_log_poly_size in &search_space.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;
if lower_bound_cut(glwe_poly_size) {
if lower_bound_cut(glwe_log_poly_size) {
continue;
}

View File

@@ -1,17 +1,15 @@
use concrete_cpu_noise_model::gaussian_noise::noise::modulus_switching::estimate_modulus_switching_noise_with_binary_key;
use super::analyze;
use crate::dag::operator::{LevelledComplexity, Precision};
use crate::dag::unparametrized;
use crate::noise_estimator::error;
use crate::noise_estimator::operators::atomic_pattern as noise_atomic_pattern;
use crate::optimization::atomic_pattern::{
OptimizationDecompositionsConsts, OptimizationState, Solution,
};
use crate::optimization::config::{Config, NoiseBoundConfig, SearchSpace};
use crate::optimization::decomposition::{DecompCaches, PersistDecompCaches};
use crate::parameters::GlweParameters;
use crate::security;
use concrete_commons::dispersion::DispersionParameter;
#[allow(clippy::too_many_lines)]
fn update_best_solution_with_best_decompositions(
@@ -195,12 +193,7 @@ fn update_no_luts_solution(
}
fn minimal_variance(config: &Config, glwe_params: GlweParameters) -> f64 {
security::glwe::minimal_variance(
glwe_params,
config.ciphertext_modulus_log,
config.security_level,
)
.get_variance()
glwe_params.minimal_variance(config.ciphertext_modulus_log, config.security_level)
}
fn optimize_no_luts(
@@ -269,13 +262,12 @@ pub fn optimize(
}
let mut caches = persistent_caches.caches();
let noise_modulus_switching = |glwe_poly_size, internal_lwe_dimensions| {
noise_atomic_pattern::estimate_modulus_switching_noise_with_binary_key(
let noise_modulus_switching = |glwe_log2_poly_size, internal_lwe_dimensions| {
estimate_modulus_switching_noise_with_binary_key(
internal_lwe_dimensions,
glwe_poly_size,
glwe_log2_poly_size,
ciphertext_modulus_log,
)
.get_variance()
};
let not_feasible = |input_noise_out, noise_modulus_switching| {
@@ -290,8 +282,8 @@ pub fn optimize(
};
let input_noise_out = minimal_variance(&config, glwe_params);
for &internal_dim in &search_space.internal_lwe_dimensions {
let glwe_poly_size = 1 << glwe_log_poly_size;
let noise_modulus_switching = noise_modulus_switching(glwe_poly_size, internal_dim);
let noise_modulus_switching =
noise_modulus_switching(glwe_log_poly_size, internal_dim);
if not_feasible(input_noise_out, noise_modulus_switching) {
// noise_modulus_switching is increasing with internal_dim
break;

View File

@@ -1,15 +1,11 @@
use std::sync::Arc;
use serde::{Deserialize, Serialize};
use concrete_commons::dispersion::DispersionParameter;
use crate::computing_cost::complexity_model::ComplexityModel;
use crate::noise_estimator::operators::atomic_pattern as noise_atomic_pattern;
use crate::config;
use crate::parameters::{BrDecompositionParameters, GlweParameters, LweDimension, PbsParameters};
use crate::utils::cache::ephemeral::{CacheHashMap, EphemeralCache};
use crate::utils::cache::persistent::{default_cache_dir, PersistentCacheHashMap};
use crate::{config, security};
use concrete_cpu_noise_model::gaussian_noise::noise::blind_rotate::variance_blind_rotate;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use super::common::{MacroParam, VERSION};
@@ -30,16 +26,8 @@ pub fn pareto_quantities(
max_log2_base: u64,
) -> Vec<BrComplexityNoise> {
assert!(ciphertext_modulus_log == 64);
let pbs_param = |level, log2_base| {
let br_decomposition_parameter = BrDecompositionParameters { level, log2_base };
PbsParameters {
internal_lwe_dimension: LweDimension(internal_dim),
br_decomposition_parameter,
output_glwe_params: glwe_params,
}
};
let variance_bsk =
security::glwe::minimal_variance(glwe_params, ciphertext_modulus_log, security_level);
let variance_bsk = glwe_params.minimal_variance(ciphertext_modulus_log, security_level);
let mut quantities = Vec::with_capacity(max_log2_base as usize);
let mut increasing_complexity = 0.0;
@@ -58,12 +46,15 @@ pub fn pareto_quantities(
let range = (1..=prev_best_log2_base).rev();
for log2_base in range {
let base_noise = noise_atomic_pattern::variance_bootstrap(
pbs_param(level, log2_base),
let base_noise = variance_blind_rotate(
internal_dim,
glwe_params.glwe_dimension,
glwe_params.polynomial_size(),
log2_base,
level,
ciphertext_modulus_log,
variance_bsk,
)
.get_variance();
);
if base_noise > level_decreasing_base_noise {
break;
}
@@ -81,7 +72,17 @@ pub fn pareto_quantities(
}
continue;
}
let params = pbs_param(level, best_log2_base);
let br_decomposition_parameter = BrDecompositionParameters {
level,
log2_base: best_log2_base,
};
let params = PbsParameters {
internal_lwe_dimension: LweDimension(internal_dim),
br_decomposition_parameter,
output_glwe_params: glwe_params,
};
let complexity_pbs = complexity_model.pbs_complexity(params, ciphertext_modulus_log);
quantities.push(BrComplexityNoise {

View File

@@ -1,11 +1,10 @@
use std::sync::Arc;
use concrete_commons::dispersion::{DispersionParameter, Variance};
use concrete_cpu_noise_model::gaussian_noise::noise::cmux::variance_cmux;
use serde::{Deserialize, Serialize};
use crate::computing_cost::complexity_model::ComplexityModel;
use crate::config;
use crate::noise_estimator::operators::atomic_pattern::variance_cmux;
use crate::parameters::{BrDecompositionParameters, CmuxParameters, GlweParameters};
use crate::utils::cache::ephemeral::{CacheHashMap, EphemeralCache};
use crate::utils::cache::persistent::{default_cache_dir, PersistentCacheHashMap};
@@ -62,8 +61,14 @@ pub fn pareto_quantities(
// Compute bias and slove for variance_one_external_product_for_cmux_tree_bias
let variance = |variance_bsk| {
let variance_bsk = Variance::from_variance(variance_bsk);
variance_cmux(params, ciphertext_modulus_log, variance_bsk).get_variance()
variance_cmux(
glwe_params.glwe_dimension,
glwe_params.polynomial_size(),
log2_base,
level,
ciphertext_modulus_log,
variance_bsk,
)
};
let variance_at_0 = variance(0.0);
let variance_at_1 = variance(1.0);

View File

@@ -1,19 +1,15 @@
use std::sync::Arc;
use serde::{Deserialize, Serialize};
use concrete_commons::dispersion::DispersionParameter;
use super::common::{MacroParam, VERSION};
use crate::computing_cost::complexity_model::ComplexityModel;
use crate::config;
use crate::noise_estimator::operators::atomic_pattern as noise_atomic_pattern;
use crate::parameters::{
GlweParameters, KeyswitchParameters, KsDecompositionParameters, LweDimension,
};
use crate::utils::cache::ephemeral::{CacheHashMap, EphemeralCache};
use crate::utils::cache::persistent::{default_cache_dir, PersistentCacheHashMap};
use super::common::{MacroParam, VERSION};
use concrete_cpu_noise_model::gaussian_noise::noise::keyswitch::variance_keyswitch;
use concrete_cpu_noise_model::gaussian_noise::security::minimal_variance_lwe;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct KsComplexityNoise {
@@ -41,8 +37,7 @@ pub fn pareto_quantities(
ks_decomposition_parameter,
}
};
let variance_ksk =
noise_atomic_pattern::variance_ksk(internal_dim, ciphertext_modulus_log, security_level);
let variance_ksk = minimal_variance_lwe(internal_dim, ciphertext_modulus_log, security_level);
let mut quantities = Vec::with_capacity(64);
let mut increasing_complexity = 0.0;
@@ -61,12 +56,13 @@ pub fn pareto_quantities(
let range = (1..=prev_best_log2_base).rev();
for log2_base in range {
let noise_keyswitch = noise_atomic_pattern::variance_keyswitch(
ks_param(level, log2_base),
let noise_keyswitch = variance_keyswitch(
input_lwe_dimension,
log2_base,
level,
ciphertext_modulus_log,
variance_ksk,
)
.get_variance();
);
if noise_keyswitch > level_decreasing_base_noise {
break;
}

View File

@@ -1,21 +1,16 @@
use std::sync::Arc;
use concrete_commons::dispersion::{DispersionParameter, Variance};
use serde::{Deserialize, Serialize};
use super::blind_rotate;
use super::common::{MacroParam, VERSION};
use crate::computing_cost::complexity_model::ComplexityModel;
use crate::noise_estimator::operators::wop_atomic_pattern::estimate_packing_private_keyswitch;
use crate::config;
use crate::parameters::{
BrDecompositionParameters, CmuxParameters, GlweParameters, KeyswitchParameters,
KsDecompositionParameters, LweDimension,
BrDecompositionParameters, GlweParameters, KeyswitchParameters, KsDecompositionParameters,
LweDimension,
};
use crate::utils::cache::ephemeral::{CacheHashMap, EphemeralCache};
use crate::utils::cache::persistent::{default_cache_dir, PersistentCacheHashMap};
use crate::{config, security};
use super::blind_rotate;
use super::common::{MacroParam, VERSION};
use concrete_cpu_noise_model::gaussian_noise::noise::private_packing_keyswitch::estimate_packing_private_keyswitch;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
pub struct PpSwitchComplexityNoise {
@@ -46,24 +41,22 @@ pub fn pareto_quantities(
glwe_params: GlweParameters,
br_quantities: &[blind_rotate::BrComplexityNoise],
) -> Vec<PpSwitchComplexityNoise> {
let variance_bsk =
security::glwe::minimal_variance(glwe_params, ciphertext_modulus_log, security_level);
let variance_bsk = glwe_params.minimal_variance(ciphertext_modulus_log, security_level);
let mut result = Vec::with_capacity(br_quantities.len());
for br in br_quantities {
let pp_ks_decomposition_parameter = br.decomp;
let ppks_parameter = CmuxParameters {
br_decomposition_parameter: pp_ks_decomposition_parameter,
output_glwe_params: glwe_params,
};
// We assume the packing KS and the external product in a PBSto have
// the same parameters (base, level)
let variance_private_packing_ks = estimate_packing_private_keyswitch::<u64>(
Variance(0.),
let variance_private_packing_ks = estimate_packing_private_keyswitch(
0.,
variance_bsk,
ppks_parameter,
pp_ks_decomposition_parameter.log2_base,
pp_ks_decomposition_parameter.level,
glwe_params.glwe_dimension,
glwe_params.polynomial_size(),
ciphertext_modulus_log,
)
.get_variance();
);
let sample_extract_lwe_dimension = LweDimension(glwe_params.sample_extract_lwe_dimension());
let ppks_parameter_complexity = KeyswitchParameters {

View File

@@ -1,10 +1,12 @@
use concrete_cpu_noise_model::gaussian_noise::conversion::variance_to_std_dev;
use concrete_cpu_noise_model::gaussian_noise::noise::modulus_switching::estimate_modulus_switching_noise_with_binary_key;
use super::crt_decomposition;
use crate::dag::operator::Precision;
use crate::noise_estimator::error::{
error_probability_of_sigma_scale, safe_variance_bound_product_1padbit,
sigma_scale_of_error_probability,
};
use crate::noise_estimator::operators::atomic_pattern as noise_atomic_pattern;
use crate::optimization::atomic_pattern;
use crate::optimization::atomic_pattern::OptimizationDecompositionsConsts;
@@ -12,11 +14,11 @@ use crate::optimization::config::{Config, SearchSpace};
use crate::optimization::decomposition::circuit_bootstrap::CbComplexityNoise;
use crate::optimization::decomposition::{DecompCaches, PersistDecompCaches};
use crate::parameters::{BrDecompositionParameters, GlweParameters};
use concrete_commons::dispersion::{DispersionParameter, Variance};
use crate::utils::square;
pub fn find_p_error(kappa: f64, variance_bound: f64, current_maximum_noise: f64) -> f64 {
let sigma = Variance(variance_bound).get_standard_dev() * kappa;
let sigma_scale = sigma / Variance(current_maximum_noise).get_standard_dev();
let sigma = variance_to_std_dev(variance_bound) * kappa;
let sigma_scale = sigma / variance_to_std_dev(current_maximum_noise);
error_probability_of_sigma_scale(sigma_scale)
}
@@ -104,14 +106,14 @@ fn estimate_variance(
cb_decomp: &CbComplexityNoise,
ks_variance: f64,
variance_modulus_switching: f64,
log_norm: f64,
norm: f64,
precisions_sum: u64,
max_precision: u64,
) -> f64 {
assert!(max_precision <= precisions_sum);
let variance_ggsw = pp_variance + br_variance / 2.;
let variance_coeff_1_cmux_tree =
2_f64.powf(2. * log_norm) // variance_coeff for the multisum
square(norm) // variance_coeff for the multisum
* (precisions_sum // for hybrid packing
<< (2 * (max_precision - 1))) as f64 // for left shift
;
@@ -197,15 +199,13 @@ fn update_state_with_best_decompositions(
let nb_blocks = partitionning.len() as u64;
let safe_variance_bound = consts.safe_variance;
let log_norm = consts.noise_factor.log2();
let norm = consts.noise_factor;
let variance_modulus_switching =
noise_atomic_pattern::estimate_modulus_switching_noise_with_binary_key(
internal_dim,
glwe_params.polynomial_size(),
ciphertext_modulus_log,
)
.get_variance();
let variance_modulus_switching = estimate_modulus_switching_noise_with_binary_key(
internal_dim,
glwe_params.log2_polynomial_size,
ciphertext_modulus_log,
);
if variance_modulus_switching > consts.safe_variance {
return;
@@ -285,7 +285,7 @@ fn update_state_with_best_decompositions(
cb_decomp,
ks_variance,
variance_modulus_switching,
log_norm,
norm,
precisions_sum,
max_precision,
)

View File

@@ -3,6 +3,7 @@ pub use individual::*;
pub use range::*;
mod individual {
use concrete_cpu_noise_model::gaussian_noise::security::minimal_variance_glwe;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize)]
@@ -30,6 +31,15 @@ mod individual {
pub fn sample_extract_lwe_dimension(self) -> u64 {
self.glwe_dimension << self.log2_polynomial_size
}
pub fn minimal_variance(self, ciphertext_modulus_log: u32, security_level: u64) -> f64 {
minimal_variance_glwe(
self.glwe_dimension,
self.polynomial_size(),
ciphertext_modulus_log,
security_level,
)
}
}
#[derive(Clone, Copy)]

View File

@@ -1,62 +0,0 @@
use super::security_weights::security_weight;
use crate::parameters::GlweParameters;
use concrete_commons::dispersion::Variance;
/// Noise ensuring security
pub fn minimal_variance(
glwe_params: GlweParameters,
ciphertext_modulus_log: u32,
security_level: u64,
) -> Variance {
let equiv_lwe_dimension = glwe_params.glwe_dimension * glwe_params.polynomial_size();
let security_weights = security_weight(security_level)
.unwrap_or_else(|| panic!("{security_level} bits of security is not supported"));
let secure_log2_std =
security_weights.secure_log2_std(equiv_lwe_dimension, ciphertext_modulus_log as f64);
let log2_var = 2.0 * secure_log2_std;
Variance(f64::exp2(log2_var))
}
#[cfg(test)]
mod tests {
use super::*;
use concrete_commons::dispersion::DispersionParameter;
#[test]
fn golden_python_prototype_security_security_glwe_variance_low() {
// python securityFunc(10,14,64)= 0.3120089883926036
let integer_size = 64;
let golden_std_dev = 2.168_404_344_971_009e-19;
let security_level = 128;
let glwe_params = GlweParameters {
log2_polynomial_size: 14,
glwe_dimension: 10,
};
let actual = minimal_variance(glwe_params, integer_size, security_level);
approx::assert_relative_eq!(
golden_std_dev,
actual.get_standard_dev(),
epsilon = f64::EPSILON
);
}
#[test]
fn golden_python_prototype_security_security_glwe_variance_high() {
// python securityFunc(3,8,32)= 2.6011445832514504
let integer_size = 32;
let golden_std_dev = 4.392_824_146_816_922_4e-6;
let security_level = 128;
let glwe_params = GlweParameters {
log2_polynomial_size: 8,
glwe_dimension: 3,
};
let actual = minimal_variance(glwe_params, integer_size, security_level);
approx::assert_relative_eq!(
golden_std_dev,
actual.get_standard_dev(),
epsilon = f64::EPSILON
);
}
}

View File

@@ -1,2 +0,0 @@
pub mod glwe;
pub mod security_weights;

View File

@@ -1,116 +0,0 @@
#[derive(Clone, Copy)]
pub struct SecurityWeights {
slope: f64,
bias: f64,
minimal_lwe_dimension: u64,
}
impl SecurityWeights {
pub fn secure_log2_std(&self, lwe_dimension: u64, ciphertext_modulus_log: f64) -> f64 {
// ensure to have a minimal on std deviation covering the 2 lowest bits on modular scale
let epsilon_log2_std_modular = 2.0;
let epsilon_log2_std = epsilon_log2_std_modular - (ciphertext_modulus_log);
// ensure the requested lwe_dimension is bigger than the minimal lwe dimension
if self.minimal_lwe_dimension <= lwe_dimension {
f64::max(
self.slope * lwe_dimension as f64 + self.bias,
epsilon_log2_std,
)
} else {
ciphertext_modulus_log
}
}
}
// Security curves generated using the lattice-estimator
// (https://github.com/malb/lattice-estimator) on the 24th of June 2022
const SECURITY_WEIGHTS_ARRAY: [(u64, SecurityWeights); 9] = [
(
80,
SecurityWeights {
slope: -0.040_426_331_193_645_89,
bias: 1.660_978_864_143_672_2,
minimal_lwe_dimension: 450,
},
),
(
96,
SecurityWeights {
slope: -0.034_147_803_608_670_51,
bias: 2.017_310_258_660_345,
minimal_lwe_dimension: 450,
},
),
(
112,
SecurityWeights {
slope: -0.029_670_137_081_135_885,
bias: 2.162_463_714_083_856,
minimal_lwe_dimension: 450,
},
),
(
128,
SecurityWeights {
slope: -0.026_405_028_765_226_22,
bias: 2.482_642_269_104_317_7,
minimal_lwe_dimension: 450,
},
),
(
144,
SecurityWeights {
slope: -0.023_821_437_305_989_134,
bias: 2.717_778_944_063_667_3,
minimal_lwe_dimension: 450,
},
),
(
160,
SecurityWeights {
slope: -0.021_743_582_187_160_36,
bias: 2.938_810_548_493_322,
minimal_lwe_dimension: 498,
},
),
(
176,
SecurityWeights {
slope: -0.019_904_056_582_117_684,
bias: 2.816_125_280_154_224_7,
minimal_lwe_dimension: 551,
},
),
(
192,
SecurityWeights {
slope: -0.018_610_403_247_590_085,
bias: 3.299_623_684_839_900_8,
minimal_lwe_dimension: 606,
},
),
(
256,
SecurityWeights {
slope: -0.014_606_812_351_714_953,
bias: 3.849_362_923_469_300_3,
minimal_lwe_dimension: 826,
},
),
];
pub fn supported_security_levels() -> impl std::iter::Iterator<Item = u64> {
SECURITY_WEIGHTS_ARRAY
.iter()
.map(|(security_level, _)| *security_level)
}
pub fn security_weight(security_level: u64) -> Option<SecurityWeights> {
let index = SECURITY_WEIGHTS_ARRAY
.binary_search_by_key(&security_level, |(security_level, _weights)| {
*security_level
})
.ok()?;
Some(SECURITY_WEIGHTS_ARRAY[index].1)
}

View File

@@ -1,16 +1,15 @@
pub mod cache;
use std::ops::Mul;
pub fn square<V>(v: V) -> V
where
V: Mul<Output = V> + Copy,
V: std::ops::Mul<Output = V> + Copy,
{
v * v
}
pub fn square_ref<V>(v: &V) -> V
where
V: Mul<Output = V> + Copy,
V: std::ops::Mul<Output = V> + Copy,
{
square(*v)
}

View File

@@ -4,7 +4,7 @@
use chrono::{Datelike, Utc};
use clap::Parser;
use concrete_optimizer::security::security_weights::supported_security_levels;
use concrete_optimizer::supported_security_levels;
use std::fs::File;
use v0_parameters::{compute_print_results, Args};

View File

@@ -240,9 +240,8 @@ pub fn compute_print_results(mut writer: impl Write, args: &Args) -> Result<(),
#[cfg(test)]
mod tests {
use concrete_optimizer::security::security_weights::supported_security_levels;
use super::*;
use concrete_optimizer::supported_security_levels;
#[test]
fn test_reference_output() {