Skip to content

Commit 2e181bf

Browse files
committed
test(shortint): add compression atomic pattern for noise checks
- noise checks and pfail based on expected noise have been added - compatible with KS PBS and KS32 PBS
1 parent e7fc6e0 commit 2e181bf

File tree

13 files changed

+1161
-79
lines changed

13 files changed

+1161
-79
lines changed

scripts/pfail_estimate.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import scipy.stats as stats
2+
from scipy.special import erfcinv, erfc
3+
import math
4+
5+
# utilities
6+
t = 1 / (2 ** (4 + 2)) # noise bound
7+
standard_score = lambda p_fail: math.sqrt(2) * erfcinv(p_fail) # standard score
8+
9+
pfail = lambda z: erfc(z / math.sqrt(2))
10+
11+
# Noise squashing after compression
12+
# measured_variance = 7.598561171474912e-35
13+
# variance_after_flood = measured_variance * (2**40 * 100) ** 2
14+
15+
# measured_std_dev = math.sqrt(variance_after_flood)
16+
17+
# New params GPU before MS 128
18+
# measured_variance = 1.438540449823688e-6
19+
# Rerand noise
20+
# measured_variance = 1.4064222454361346e-6
21+
# measured_variance = 1.408401059719539e-6
22+
23+
# measured_variance = 1.4120971218065554e-6 #KS32
24+
measured_variance = 1.4150031500067098e-6
25+
measured_std_dev = math.sqrt(measured_variance)
26+
27+
measured_std_score = t / measured_std_dev
28+
29+
estimated_pfail = pfail(measured_std_score)
30+
31+
print(estimated_pfail, math.log2(estimated_pfail))
32+
33+
34+
# Compression encoding for 2_2
35+
t_compression = 1 / (2 ** (2 + 2))
36+
measured_variance = 1.0216297411906617e-5
37+
measured_std_dev = math.sqrt(measured_variance)
38+
39+
measured_std_score = t_compression / measured_std_dev
40+
41+
estimated_pfail = pfail(measured_std_score)
42+
print(estimated_pfail, math.log2(estimated_pfail))

tfhe/src/core_crypto/algorithms/modulus_switch.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ use crate::core_crypto::commons::noise_formulas::noise_simulation::traits::{
106106
AllocateCenteredBinaryShiftedStandardModSwitchResult, AllocateStandardModSwitchResult,
107107
CenteredBinaryShiftedStandardModSwitch, StandardModSwitch,
108108
};
109+
use crate::core_crypto::entities::glwe_ciphertext::{GlweCiphertext, GlweCiphertextOwned};
109110

110111
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> AllocateStandardModSwitchResult
111112
for LweCiphertext<C>
@@ -206,6 +207,58 @@ impl<
206207
}
207208
}
208209

210+
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> AllocateStandardModSwitchResult
211+
for GlweCiphertext<C>
212+
{
213+
type Output = GlweCiphertextOwned<Scalar>;
214+
type SideResources = ();
215+
216+
fn allocate_standard_mod_switch_result(
217+
&self,
218+
_side_resources: &mut Self::SideResources,
219+
) -> Self::Output {
220+
// We will mod switch but we keep the current modulus as the noise is interesting in the
221+
// context of the input modulus
222+
Self::Output::new(
223+
Scalar::ZERO,
224+
self.glwe_size(),
225+
self.polynomial_size(),
226+
self.ciphertext_modulus(),
227+
)
228+
}
229+
}
230+
231+
impl<
232+
Scalar: UnsignedInteger,
233+
InputCont: Container<Element = Scalar>,
234+
OutputCont: ContainerMut<Element = Scalar>,
235+
> StandardModSwitch<GlweCiphertext<OutputCont>> for GlweCiphertext<InputCont>
236+
{
237+
type SideResources = ();
238+
239+
fn standard_mod_switch(
240+
&self,
241+
output_modulus_log: CiphertextModulusLog,
242+
output: &mut GlweCiphertext<OutputCont>,
243+
_side_resources: &mut Self::SideResources,
244+
) {
245+
assert!(self
246+
.ciphertext_modulus()
247+
.is_compatible_with_native_modulus());
248+
assert_eq!(self.glwe_size(), output.glwe_size());
249+
assert_eq!(self.polynomial_size(), output.polynomial_size());
250+
// Mod switched but the noise is to be interpreted with respect to the input modulus, as
251+
// strictly the operation adding the noise is the rounding under the original modulus
252+
assert_eq!(self.ciphertext_modulus(), output.ciphertext_modulus());
253+
254+
for (inp, out) in self.as_ref().iter().zip(output.as_mut().iter_mut()) {
255+
let msed = modulus_switch(*inp, output_modulus_log);
256+
// Shift in MSBs to match the power of 2 encoding in core
257+
*out = msed << (Scalar::BITS - output_modulus_log.0);
258+
}
259+
}
260+
}
261+
209262
#[cfg(test)]
210263
mod tests {
211264
use super::*;

tfhe/src/core_crypto/commons/noise_formulas/noise_simulation/lwe_packing_keyswitch.rs

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ use crate::core_crypto::commons::noise_formulas::noise_simulation::traits::{
88
};
99
use crate::core_crypto::commons::noise_formulas::noise_simulation::{
1010
NoiseSimulationGlwe, NoiseSimulationLwe, NoiseSimulationModulus,
11+
NoiseSimulationNoiseDistribution, NoiseSimulationNoiseDistributionKind,
1112
};
1213
use crate::core_crypto::commons::numeric::UnsignedInteger;
1314
use crate::core_crypto::commons::parameters::{
14-
DecompositionBaseLog, DecompositionLevelCount, DynamicDistribution, GlweSize, LweDimension,
15-
PolynomialSize,
15+
DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
1616
};
1717
use crate::core_crypto::commons::traits::container::Container;
1818
use crate::core_crypto::entities::lwe_packing_keyswitch_key::LwePackingKeyswitchKey;
@@ -22,9 +22,9 @@ pub struct NoiseSimulationLwePackingKeyswitchKey {
2222
input_lwe_dimension: LweDimension,
2323
decomp_base_log: DecompositionBaseLog,
2424
decomp_level_count: DecompositionLevelCount,
25-
output_glwe_size: GlweSize,
25+
output_glwe_dimension: GlweDimension,
2626
output_polynomial_size: PolynomialSize,
27-
noise_distribution: DynamicDistribution<u128>,
27+
noise_distribution: NoiseSimulationNoiseDistribution,
2828
modulus: NoiseSimulationModulus,
2929
}
3030

@@ -33,16 +33,16 @@ impl NoiseSimulationLwePackingKeyswitchKey {
3333
input_lwe_dimension: LweDimension,
3434
decomp_base_log: DecompositionBaseLog,
3535
decomp_level_count: DecompositionLevelCount,
36-
output_glwe_size: GlweSize,
36+
output_glwe_dimension: GlweDimension,
3737
output_polynomial_size: PolynomialSize,
38-
noise_distribution: DynamicDistribution<u128>,
38+
noise_distribution: NoiseSimulationNoiseDistribution,
3939
modulus: NoiseSimulationModulus,
4040
) -> Self {
4141
Self {
4242
input_lwe_dimension,
4343
decomp_base_log,
4444
decomp_level_count,
45-
output_glwe_size,
45+
output_glwe_dimension,
4646
output_polynomial_size,
4747
noise_distribution,
4848
modulus,
@@ -57,7 +57,7 @@ impl NoiseSimulationLwePackingKeyswitchKey {
5757
input_lwe_dimension,
5858
decomp_base_log,
5959
decomp_level_count,
60-
output_glwe_size,
60+
output_glwe_dimension,
6161
output_polynomial_size,
6262
noise_distribution: _,
6363
modulus,
@@ -66,15 +66,15 @@ impl NoiseSimulationLwePackingKeyswitchKey {
6666
let pksk_input_lwe_dimension = pksk.input_key_lwe_dimension();
6767
let pksk_decomp_base_log = pksk.decomposition_base_log();
6868
let pksk_decomp_level_count = pksk.decomposition_level_count();
69-
let pksk_output_glwe_size = pksk.output_glwe_size();
69+
let pksk_output_glwe_dimension = pksk.output_key_glwe_dimension();
7070
let pksk_output_polynomial_size = pksk.output_key_polynomial_size();
7171
let pksk_modulus =
7272
NoiseSimulationModulus::from_ciphertext_modulus(pksk.ciphertext_modulus());
7373

7474
input_lwe_dimension == pksk_input_lwe_dimension
7575
&& decomp_base_log == pksk_decomp_base_log
7676
&& decomp_level_count == pksk_decomp_level_count
77-
&& output_glwe_size == pksk_output_glwe_size
77+
&& output_glwe_dimension == pksk_output_glwe_dimension
7878
&& output_polynomial_size == pksk_output_polynomial_size
7979
&& modulus == pksk_modulus
8080
}
@@ -91,15 +91,15 @@ impl NoiseSimulationLwePackingKeyswitchKey {
9191
self.decomp_level_count
9292
}
9393

94-
pub fn output_glwe_size(&self) -> GlweSize {
95-
self.output_glwe_size
94+
pub fn output_glwe_dimension(&self) -> GlweDimension {
95+
self.output_glwe_dimension
9696
}
9797

9898
pub fn output_polynomial_size(&self) -> PolynomialSize {
9999
self.output_polynomial_size
100100
}
101101

102-
pub fn noise_distribution(&self) -> DynamicDistribution<u128> {
102+
pub fn noise_distribution(&self) -> NoiseSimulationNoiseDistribution {
103103
self.noise_distribution
104104
}
105105

@@ -117,7 +117,7 @@ impl AllocateLwePackingKeyswitchResult for NoiseSimulationLwePackingKeyswitchKey
117117
_side_resources: &mut Self::SideResources,
118118
) -> Self::Output {
119119
Self::Output::new(
120-
self.output_glwe_size().to_glwe_dimension(),
120+
self.output_glwe_dimension(),
121121
self.output_polynomial_size(),
122122
Variance(f64::NAN),
123123
self.modulus,
@@ -137,43 +137,44 @@ impl LwePackingKeyswitch<[&NoiseSimulationLwe], NoiseSimulationGlwe>
137137
_side_resources: &mut Self::SideResources,
138138
) {
139139
let mut input_iter = input.iter();
140-
let input = input_iter.next().unwrap();
140+
let first_input = input_iter.next().unwrap();
141141

142-
let mut lwe_to_pack = 1;
142+
// Check first input is compatible with us
143+
assert_eq!(first_input.lwe_dimension(), self.input_lwe_dimension());
144+
// Check all inputs are the same as first input
145+
assert!(input_iter.all(|x| x == first_input));
143146

144-
assert!(input_iter.inspect(|_| lwe_to_pack += 1).all(|x| x == input));
147+
let lwe_to_pack = input.len() as f64;
145148

146-
assert_eq!(input.lwe_dimension(), self.input_lwe_dimension());
147-
148-
let packing_ks_additive_var = match self.noise_distribution() {
149-
DynamicDistribution::Gaussian(_) => {
149+
let packing_ks_additive_var = match self.noise_distribution().kind() {
150+
NoiseSimulationNoiseDistributionKind::Gaussian => {
150151
packing_keyswitch_additive_variance_132_bits_security_gaussian(
151152
self.input_lwe_dimension(),
152-
self.output_glwe_size().to_glwe_dimension(),
153+
self.output_glwe_dimension(),
153154
self.output_polynomial_size(),
154155
self.decomp_base_log(),
155156
self.decomp_level_count(),
156-
lwe_to_pack.into(),
157+
lwe_to_pack,
157158
self.modulus().as_f64(),
158159
)
159160
}
160-
DynamicDistribution::TUniform(_) => {
161+
NoiseSimulationNoiseDistributionKind::TUniform => {
161162
packing_keyswitch_additive_variance_132_bits_security_tuniform(
162163
self.input_lwe_dimension(),
163-
self.output_glwe_size().to_glwe_dimension(),
164+
self.output_glwe_dimension(),
164165
self.output_polynomial_size(),
165166
self.decomp_base_log(),
166167
self.decomp_level_count(),
167-
lwe_to_pack.into(),
168+
lwe_to_pack,
168169
self.modulus().as_f64(),
169170
)
170171
}
171172
};
172173

173174
*output = NoiseSimulationGlwe::new(
174-
self.output_glwe_size().to_glwe_dimension(),
175+
self.output_glwe_dimension(),
175176
self.output_polynomial_size(),
176-
Variance(input.variance().0 + packing_ks_additive_var.0),
177+
Variance(first_input.variance().0 + packing_ks_additive_var.0),
177178
self.modulus(),
178179
);
179180
}

tfhe/src/core_crypto/commons/noise_formulas/noise_simulation/mod.rs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@ pub use lwe_programmable_bootstrap::{
1313

1414
use crate::core_crypto::commons::ciphertext_modulus::CiphertextModulus;
1515
use crate::core_crypto::commons::dispersion::Variance;
16+
use crate::core_crypto::commons::math::random::DynamicDistribution;
1617
use crate::core_crypto::commons::noise_formulas::noise_simulation::traits::{
1718
AllocateLweBootstrapResult, AllocateLweMultiBitBlindRotateResult, LweUncorrelatedAdd,
1819
LweUncorrelatedSub, ScalarMul, ScalarMulAssign,
1920
};
2021
use crate::core_crypto::commons::numeric::{CastInto, UnsignedInteger};
2122
use crate::core_crypto::commons::parameters::{
22-
CiphertextModulusLog, GlweDimension, LweDimension, PolynomialSize,
23+
CiphertextModulusLog, GlweDimension, GlweSize, LweDimension, PolynomialSize,
2324
};
2425

2526
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -71,6 +72,40 @@ impl NoiseSimulationModulus {
7172
}
7273
}
7374

75+
#[derive(Clone, Copy, Debug, PartialEq)]
76+
pub enum NoiseSimulationNoiseDistribution {
77+
U32(DynamicDistribution<u32>),
78+
U64(DynamicDistribution<u64>),
79+
U128(DynamicDistribution<u128>),
80+
}
81+
82+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
83+
pub enum NoiseSimulationNoiseDistributionKind {
84+
Gaussian,
85+
TUniform,
86+
}
87+
88+
impl NoiseSimulationNoiseDistribution {
89+
pub fn kind(&self) -> NoiseSimulationNoiseDistributionKind {
90+
match self {
91+
Self::U32(dynamic_distribution) => dynamic_distribution.into(),
92+
Self::U64(dynamic_distribution) => dynamic_distribution.into(),
93+
Self::U128(dynamic_distribution) => dynamic_distribution.into(),
94+
}
95+
}
96+
}
97+
98+
impl<Scalar: UnsignedInteger> From<&DynamicDistribution<Scalar>>
99+
for NoiseSimulationNoiseDistributionKind
100+
{
101+
fn from(value: &DynamicDistribution<Scalar>) -> Self {
102+
match value {
103+
DynamicDistribution::Gaussian(_) => Self::Gaussian,
104+
DynamicDistribution::TUniform(_) => Self::TUniform,
105+
}
106+
}
107+
}
108+
74109
// Avoids fields to be public/accessible in the noise_simulation module to make sure all functions
75110
// use constructors
76111
mod simulation_ciphertexts {
@@ -211,6 +246,10 @@ mod simulation_ciphertexts {
211246
self.glwe_dimension
212247
}
213248

249+
pub fn glwe_size(&self) -> GlweSize {
250+
self.glwe_dimension().to_glwe_size()
251+
}
252+
214253
pub fn polynomial_size(&self) -> PolynomialSize {
215254
self.polynomial_size
216255
}

0 commit comments

Comments
 (0)