Skip to content

Have a method to select deterministic FFT algorithms for all supported PolynomialSize #1765

@furkanturan

Description

@furkanturan

Describe the bug

The Fft generation here is based on 10ms duration. Not only duration is not always precise, but also we are worried that this implementation causes excess noise depending on target machine.

When developing and testing the Belfort FHE Accelerator, we need to carefully evaluate the effects of noise. After noticing discrepancies, we used deterministic engine for debug. However, we observed that different bootstrapping keys were generated across executions with same seed. The loss of determinism is rare on AMD EPYC processors (3.5 GHz or 4.1 GHz), occurring roughly 1 in 10,000 executions. In contrast, it happens frequently on Intel Xeon processors (Silver 2.1 GHz and E5 2.3 GHz), where about 1 in 10 of executions produce different bootstrapping keys for the same seed.

The variations are sometimes minor, limited to fractional digits of the coefficients, but occasionally, even integer bits differ, resulting in failed computations. These failures are more concerning than the non-determinism itself, as they probably mean an increase in injected noise, which disrupts the expected computation outcomes.

We suspect that the issue may be related to reduced precision on lower clock frequency CPUs, rather than being specific to processor brands. However, we could not verify this hypothesis, as we lack access to faster Intel CPUs or slower AMD CPUs.

To Reproduce

Steps to reproduce the behaviour

  1. Generate keys
let seed = ...
let mut seeder = DeterministicSeeder::<ActivatedRandomGenerator>::new(Seed(seed as u128));
let shortint_engine = ShortintEngine::new_from_seeder(&mut seeder);
ShortintEngine::replace_thread_local(shortint_engine);

let params: ClassicPBSParameters = tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
let client_key = ClientKey::new(params);
let server_key = ServerKey::new(&client_key);
  1. Print your bootstrapping key. Printing only 10 is enough, as problems appear quickly
let fourier_bsk = match server_key.clone().bootstrapping_key {
    ShortintBootstrappingKey::Classic(flbko) => flbko,
    ShortintBootstrappingKey::MultiBit { .. } => panic!("Ingore this")
  };

let bsk_flat = fourier_bsk.clone().data().to_vec();
for (index, elem) in bsk_flat.iter().take(10).enumerate() {
    println!("Index: {}, Value: {:?}", index, elem);
}
  1. Execute this app many times, and compare the printed results. As mentioned above, even small number of executions are enough to capture it, depending on your CPU. Let's say, 0.0001% chance on some processors but 10% on others.

Expected behaviour

Once the seed is fixed, we expect to get the same exact bootstrapping key for any execution.

Configuration(please complete the following information):

  • OS: Ubuntu 22.04.4 LTS
  • CPU: Intel(R) Xeon(R) Silver 4208 CPU @ 2.10GHz

Additional context

For now, we are using experimental-force_fft_algo_dif4 feature, which fixes the issues:

  • Determinism: Always the same bootstrapping key for the same seed
  • Accuracy: Always correct calculations given different seeds

We tested these with 10'000 executions over a night, not more.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions