Files
tfhe-rs/tfhe/benches/utilities.rs
2025-02-18 13:19:28 +01:00

464 lines
16 KiB
Rust

use serde::Serialize;
use std::path::PathBuf;
use std::{env, fs};
use tfhe::core_crypto::prelude::*;
#[cfg(feature = "boolean")]
pub mod boolean_utils {
use super::*;
use tfhe::boolean::parameters::BooleanParameters;
impl From<BooleanParameters> for CryptoParametersRecord<u32> {
fn from(params: BooleanParameters) -> Self {
CryptoParametersRecord {
lwe_dimension: Some(params.lwe_dimension),
glwe_dimension: Some(params.glwe_dimension),
polynomial_size: Some(params.polynomial_size),
lwe_noise_distribution: Some(params.lwe_noise_distribution),
glwe_noise_distribution: Some(params.glwe_noise_distribution),
pbs_base_log: Some(params.pbs_base_log),
pbs_level: Some(params.pbs_level),
ks_base_log: Some(params.ks_base_log),
ks_level: Some(params.ks_level),
ciphertext_modulus: Some(CiphertextModulus::<u32>::new_native()),
..Default::default()
}
}
}
}
#[allow(unused_imports)]
#[cfg(feature = "boolean")]
pub use boolean_utils::*;
#[cfg(feature = "shortint")]
pub mod shortint_utils {
use super::*;
use itertools::iproduct;
use std::vec::IntoIter;
use tfhe::shortint::parameters::compact_public_key_only::CompactPublicKeyEncryptionParameters;
#[cfg(not(feature = "gpu"))]
use tfhe::shortint::parameters::current_params::V1_0_PARAM_MULTI_BIT_GROUP_2_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M64;
use tfhe::shortint::parameters::list_compression::CompressionParameters;
#[cfg(feature = "gpu")]
use tfhe::shortint::parameters::PARAM_GPU_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_3_KS_PBS;
use tfhe::shortint::parameters::{
ShortintKeySwitchingParameters, PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
};
use tfhe::shortint::{
ClassicPBSParameters, MultiBitPBSParameters, PBSParameters, ShortintParameterSet,
};
/// An iterator that yields a succession of combinations
/// of parameters and a num_block to achieve a certain bit_size ciphertext
/// in radix decomposition
pub struct ParamsAndNumBlocksIter {
params_and_bit_sizes:
itertools::Product<IntoIter<tfhe::shortint::PBSParameters>, IntoIter<usize>>,
}
impl Default for ParamsAndNumBlocksIter {
fn default() -> Self {
let env_config = EnvConfig::new();
if env_config.is_multi_bit {
#[cfg(feature = "gpu")]
let params = vec![PARAM_GPU_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_3_KS_PBS.into()];
#[cfg(not(feature = "gpu"))]
let params = vec![
V1_0_PARAM_MULTI_BIT_GROUP_2_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M64.into(),
];
let params_and_bit_sizes = iproduct!(params, env_config.bit_sizes());
Self {
params_and_bit_sizes,
}
} else {
// FIXME One set of parameter is tested since we want to benchmark only quickest
// operations.
let params = vec![PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128.into()];
let params_and_bit_sizes = iproduct!(params, env_config.bit_sizes());
Self {
params_and_bit_sizes,
}
}
}
}
impl Iterator for ParamsAndNumBlocksIter {
type Item = (tfhe::shortint::PBSParameters, usize, usize);
fn next(&mut self) -> Option<Self::Item> {
let (param, bit_size) = self.params_and_bit_sizes.next()?;
let num_block =
(bit_size as f64 / (param.message_modulus().0 as f64).log(2.0)).ceil() as usize;
Some((param, num_block, bit_size))
}
}
impl From<PBSParameters> for CryptoParametersRecord<u64> {
fn from(params: PBSParameters) -> Self {
CryptoParametersRecord {
lwe_dimension: Some(params.lwe_dimension()),
glwe_dimension: Some(params.glwe_dimension()),
polynomial_size: Some(params.polynomial_size()),
lwe_noise_distribution: Some(params.lwe_noise_distribution()),
glwe_noise_distribution: Some(params.glwe_noise_distribution()),
pbs_base_log: Some(params.pbs_base_log()),
pbs_level: Some(params.pbs_level()),
ks_base_log: Some(params.ks_base_log()),
ks_level: Some(params.ks_level()),
message_modulus: Some(params.message_modulus().0),
carry_modulus: Some(params.carry_modulus().0),
ciphertext_modulus: Some(
params
.ciphertext_modulus()
.try_to()
.expect("failed to convert ciphertext modulus"),
),
..Default::default()
}
}
}
impl From<ShortintKeySwitchingParameters> for CryptoParametersRecord<u64> {
fn from(params: ShortintKeySwitchingParameters) -> Self {
CryptoParametersRecord {
ks_base_log: Some(params.ks_base_log),
ks_level: Some(params.ks_level),
..Default::default()
}
}
}
impl From<CompactPublicKeyEncryptionParameters> for CryptoParametersRecord<u64> {
fn from(params: CompactPublicKeyEncryptionParameters) -> Self {
CryptoParametersRecord {
message_modulus: Some(params.message_modulus.0),
carry_modulus: Some(params.carry_modulus.0),
ciphertext_modulus: Some(params.ciphertext_modulus),
..Default::default()
}
}
}
impl From<(CompressionParameters, ClassicPBSParameters)> for CryptoParametersRecord<u64> {
fn from((comp_params, pbs_params): (CompressionParameters, ClassicPBSParameters)) -> Self {
(comp_params, PBSParameters::PBS(pbs_params)).into()
}
}
impl From<(CompressionParameters, MultiBitPBSParameters)> for CryptoParametersRecord<u64> {
fn from(
(comp_params, multi_bit_pbs_params): (CompressionParameters, MultiBitPBSParameters),
) -> Self {
(
comp_params,
PBSParameters::MultiBitPBS(multi_bit_pbs_params),
)
.into()
}
}
impl From<(CompressionParameters, PBSParameters)> for CryptoParametersRecord<u64> {
fn from((comp_params, pbs_params): (CompressionParameters, PBSParameters)) -> Self {
let pbs_params = ShortintParameterSet::new_pbs_param_set(pbs_params);
let lwe_dimension = pbs_params.encryption_lwe_dimension();
CryptoParametersRecord {
lwe_dimension: Some(lwe_dimension),
br_level: Some(comp_params.br_level),
br_base_log: Some(comp_params.br_base_log),
packing_ks_level: Some(comp_params.packing_ks_level),
packing_ks_base_log: Some(comp_params.packing_ks_base_log),
packing_ks_polynomial_size: Some(comp_params.packing_ks_polynomial_size),
packing_ks_glwe_dimension: Some(comp_params.packing_ks_glwe_dimension),
lwe_per_glwe: Some(comp_params.lwe_per_glwe),
storage_log_modulus: Some(comp_params.storage_log_modulus),
lwe_noise_distribution: Some(pbs_params.encryption_noise_distribution()),
packing_ks_key_noise_distribution: Some(
comp_params.packing_ks_key_noise_distribution,
),
ciphertext_modulus: Some(pbs_params.ciphertext_modulus()),
..Default::default()
}
}
}
}
#[allow(unused_imports)]
#[cfg(feature = "shortint")]
pub use shortint_utils::*;
#[derive(Clone, Copy, Default, Serialize)]
pub struct CryptoParametersRecord<Scalar: UnsignedInteger> {
pub lwe_dimension: Option<LweDimension>,
pub glwe_dimension: Option<GlweDimension>,
pub packing_ks_glwe_dimension: Option<GlweDimension>,
pub polynomial_size: Option<PolynomialSize>,
pub packing_ks_polynomial_size: Option<PolynomialSize>,
#[serde(serialize_with = "CryptoParametersRecord::serialize_distribution")]
pub lwe_noise_distribution: Option<DynamicDistribution<Scalar>>,
#[serde(serialize_with = "CryptoParametersRecord::serialize_distribution")]
pub glwe_noise_distribution: Option<DynamicDistribution<Scalar>>,
#[serde(serialize_with = "CryptoParametersRecord::serialize_distribution")]
pub packing_ks_key_noise_distribution: Option<DynamicDistribution<Scalar>>,
pub pbs_base_log: Option<DecompositionBaseLog>,
pub pbs_level: Option<DecompositionLevelCount>,
pub ks_base_log: Option<DecompositionBaseLog>,
pub ks_level: Option<DecompositionLevelCount>,
pub pfks_level: Option<DecompositionLevelCount>,
pub pfks_base_log: Option<DecompositionBaseLog>,
pub pfks_std_dev: Option<StandardDev>,
pub cbs_level: Option<DecompositionLevelCount>,
pub cbs_base_log: Option<DecompositionBaseLog>,
pub br_level: Option<DecompositionLevelCount>,
pub br_base_log: Option<DecompositionBaseLog>,
pub packing_ks_level: Option<DecompositionLevelCount>,
pub packing_ks_base_log: Option<DecompositionBaseLog>,
pub message_modulus: Option<u64>,
pub carry_modulus: Option<u64>,
pub ciphertext_modulus: Option<CiphertextModulus<Scalar>>,
pub lwe_per_glwe: Option<LweCiphertextCount>,
pub storage_log_modulus: Option<CiphertextModulusLog>,
}
impl<Scalar: UnsignedInteger> CryptoParametersRecord<Scalar> {
pub fn noise_distribution_as_string(noise_distribution: DynamicDistribution<Scalar>) -> String {
match noise_distribution {
DynamicDistribution::Gaussian(g) => format!("Gaussian({}, {})", g.std, g.mean),
DynamicDistribution::TUniform(t) => format!("TUniform({})", t.bound_log2()),
}
}
pub fn serialize_distribution<S>(
noise_distribution: &Option<DynamicDistribution<Scalar>>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match noise_distribution {
Some(d) => serializer.serialize_some(&Self::noise_distribution_as_string(*d)),
None => serializer.serialize_none(),
}
}
}
#[derive(Serialize)]
enum PolynomialMultiplication {
Fft,
// Ntt,
}
#[derive(Serialize)]
enum IntegerRepresentation {
Radix,
// Crt,
// Hybrid,
}
#[derive(Serialize)]
enum ExecutionType {
Sequential,
Parallel,
}
#[derive(Serialize)]
enum KeySetType {
Single,
// Multi,
}
#[derive(Serialize)]
enum OperandType {
CipherText,
PlainText,
}
#[derive(Clone, Serialize)]
pub enum OperatorType {
Atomic,
// AtomicPattern,
}
#[derive(Serialize)]
struct BenchmarkParametersRecord<Scalar: UnsignedInteger> {
display_name: String,
crypto_parameters_alias: String,
crypto_parameters: CryptoParametersRecord<Scalar>,
message_modulus: Option<u64>,
carry_modulus: Option<u64>,
ciphertext_modulus: usize,
bit_size: u32,
polynomial_multiplication: PolynomialMultiplication,
precision: u32,
error_probability: f64,
integer_representation: IntegerRepresentation,
decomposition_basis: Vec<u32>,
pbs_algorithm: Option<String>,
execution_type: ExecutionType,
key_set_type: KeySetType,
operand_type: OperandType,
operator_type: OperatorType,
}
/// Writes benchmarks parameters to disk in JSON format.
pub fn write_to_json<
Scalar: UnsignedInteger + Serialize,
T: Into<CryptoParametersRecord<Scalar>>,
>(
bench_id: &str,
params: T,
params_alias: impl Into<String>,
display_name: impl Into<String>,
operator_type: &OperatorType,
bit_size: u32,
decomposition_basis: Vec<u32>,
) {
let params = params.into();
let execution_type = match bench_id.contains("parallelized") {
true => ExecutionType::Parallel,
false => ExecutionType::Sequential,
};
let operand_type = match bench_id.contains("scalar") {
true => OperandType::PlainText,
false => OperandType::CipherText,
};
let record = BenchmarkParametersRecord {
display_name: display_name.into(),
crypto_parameters_alias: params_alias.into(),
crypto_parameters: params.to_owned(),
message_modulus: params.message_modulus,
carry_modulus: params.carry_modulus,
ciphertext_modulus: 64,
bit_size,
polynomial_multiplication: PolynomialMultiplication::Fft,
precision: (params.message_modulus.unwrap_or(2) as u32).ilog2(),
error_probability: 2f64.powf(-41.0),
integer_representation: IntegerRepresentation::Radix,
decomposition_basis,
pbs_algorithm: None, // To be added in future version
execution_type,
key_set_type: KeySetType::Single,
operand_type,
operator_type: operator_type.to_owned(),
};
let mut params_directory = ["benchmarks_parameters", bench_id]
.iter()
.collect::<PathBuf>();
fs::create_dir_all(&params_directory).unwrap();
params_directory.push("parameters.json");
fs::write(params_directory, serde_json::to_string(&record).unwrap()).unwrap();
}
const FAST_BENCH_BIT_SIZES: [usize; 1] = [64];
const BENCH_BIT_SIZES: [usize; 8] = [4, 8, 16, 32, 40, 64, 128, 256];
const MULTI_BIT_CPU_SIZES: [usize; 6] = [4, 8, 16, 32, 40, 64];
/// User configuration in which benchmarks must be run.
#[derive(Default)]
pub struct EnvConfig {
pub is_multi_bit: bool,
pub is_fast_bench: bool,
}
impl EnvConfig {
#[allow(dead_code)]
pub fn new() -> Self {
let is_multi_bit = match env::var("__TFHE_RS_PARAM_TYPE") {
Ok(val) => val.to_lowercase() == "multi_bit",
Err(_) => false,
};
let is_fast_bench = match env::var("__TFHE_RS_FAST_BENCH") {
Ok(val) => val.to_lowercase() == "true",
Err(_) => false,
};
EnvConfig {
is_multi_bit,
is_fast_bench,
}
}
/// Get precisions values to benchmark.
#[allow(dead_code)]
pub fn bit_sizes(&self) -> Vec<usize> {
if self.is_fast_bench {
FAST_BENCH_BIT_SIZES.to_vec()
} else if self.is_multi_bit {
if cfg!(feature = "gpu") {
BENCH_BIT_SIZES.to_vec()
} else {
MULTI_BIT_CPU_SIZES.to_vec()
}
} else {
BENCH_BIT_SIZES.to_vec()
}
}
}
#[cfg(feature = "integer")]
pub mod integer_utils {
use super::*;
use std::sync::OnceLock;
#[cfg(feature = "gpu")]
use tfhe_cuda_backend::cuda_bind::cuda_get_number_of_gpus;
/// Generate a number of threads to use to saturate current machine for throughput measurements.
#[allow(dead_code)]
pub fn throughput_num_threads(num_block: usize) -> u64 {
let ref_block_count = 32; // Represent a ciphertext of 64 bits for 2_2 parameters set
let block_multiplicator = (ref_block_count as f64 / num_block as f64).ceil();
#[cfg(feature = "gpu")]
{
// This value is for Nvidia H100 GPU
let streaming_multiprocessors = 132;
let num_gpus = unsafe { cuda_get_number_of_gpus() };
((streaming_multiprocessors * num_gpus) as f64 * block_multiplicator) as u64
}
#[cfg(not(feature = "gpu"))]
{
let num_threads = rayon::current_num_threads() as f64;
// Add 20% more to maximum threads available.
((num_threads + (num_threads * 0.2)) * block_multiplicator) as u64
}
}
#[allow(dead_code)]
pub static BENCH_TYPE: OnceLock<BenchmarkType> = OnceLock::new();
#[allow(dead_code)]
pub enum BenchmarkType {
Latency,
Throughput,
}
#[allow(dead_code)]
impl BenchmarkType {
pub fn from_env() -> Result<Self, String> {
let raw_value = env::var("__TFHE_RS_BENCH_TYPE").unwrap_or("latency".to_string());
match raw_value.to_lowercase().as_str() {
"latency" => Ok(BenchmarkType::Latency),
"throughput" => Ok(BenchmarkType::Throughput),
_ => Err(format!("benchmark type '{raw_value}' is not supported")),
}
}
}
}
#[allow(unused_imports)]
#[cfg(feature = "integer")]
pub use integer_utils::*;
// Empty main to please clippy.
#[allow(dead_code)]
pub fn main() {}