diff --git a/src/io/aky24_io/bench_estimator.rs b/src/io/aky24_io/bench_estimator.rs index ec7ec87f..1bcd248d 100644 --- a/src/io/aky24_io/bench_estimator.rs +++ b/src/io/aky24_io/bench_estimator.rs @@ -558,6 +558,20 @@ impl<'a, PKBE, EncBE, NBE> Aky24IOBenchEstimator<'a, PKBE, EncBE, NBE> { repeat_sequential_summary(refresh_decoder_work_per_round, shape.prf_round_count) } }; + let homomorphic_prf = sequential_summaries(&[round_summary.clone(), final_prg.clone()]); + tracing::info!( + ?mode, + homomorphic_prf_total_time_nanos = %homomorphic_prf.total_time, + homomorphic_prf_latency = homomorphic_prf.latency, + homomorphic_prf_max_parallelism = %homomorphic_prf.max_parallelism, + final_mask_total_time_nanos = %final_mask_decrypt.total_time, + final_mask_latency = final_mask_decrypt.latency, + final_mask_max_parallelism = %final_mask_decrypt.max_parallelism, + function_outputs_total_time_nanos = %final_function_decrypt.total_time, + function_outputs_latency = final_function_decrypt.latency, + function_outputs_max_parallelism = %final_function_decrypt.max_parallelism, + "estimated AKY24IO PRF final-FE 3-way breakdown" + ); let final_summary = sequential_summaries(&[final_prg.clone(), final_output_decrypt]); let compute_without_refresh_decoder = sequential_summaries(&[round_compute, final_summary.clone()]); diff --git a/src/io/diamond_io/bench_estimator.rs b/src/io/diamond_io/bench_estimator.rs index 2a790b39..5ae92da2 100644 --- a/src/io/diamond_io/bench_estimator.rs +++ b/src/io/diamond_io/bench_estimator.rs @@ -1080,6 +1080,20 @@ where shape.prf_round_count, ), }; + let homomorphic_prf = sequential_summaries(&[round_summary.clone(), final_prg.clone()]); + info!( + ?mode, + homomorphic_prf_total_time_nanos = %homomorphic_prf.total_time, + homomorphic_prf_latency = homomorphic_prf.latency, + homomorphic_prf_max_parallelism = %homomorphic_prf.max_parallelism, + final_mask_total_time_nanos = %final_mask_decrypt.total_time, + final_mask_latency = final_mask_decrypt.latency, + final_mask_max_parallelism = %final_mask_decrypt.max_parallelism, + function_outputs_total_time_nanos = %final_function_decrypt.total_time, + function_outputs_latency = final_function_decrypt.latency, + function_outputs_max_parallelism = %final_function_decrypt.max_parallelism, + "estimated DiamondIO PRF final-FE 3-way breakdown" + ); let final_summary = sequential_summaries(&[final_prg.clone(), final_output_decrypt.clone()]); let compute_without_refresh_decoder = diff --git a/tests/test_gpu_diamond_we.rs b/tests/test_gpu_diamond_we.rs index 9393e141..b8a5da9e 100644 --- a/tests/test_gpu_diamond_we.rs +++ b/tests/test_gpu_diamond_we.rs @@ -4,15 +4,22 @@ use keccak_asm::Keccak256; use mxx::{ bench_estimator::{ BggEncodingBenchEstimator, BggPublicKeyBenchEstimator, BggPublicKeyBenchSamples, + PublicLutSampleAuxBenchEstimator, + }, + bgg::{ + public_key::BggPublicKey, + sampler::{BGGEncodingSampler, BGGPublicKeySampler}, }, - bgg::{public_key::BggPublicKey, sampler::BGGPublicKeySampler}, circuit::{PolyCircuit, gate::GateId}, element::PolyElem, func_enc::NoCircuitEvaluator, input_injector::DiamondInjector, io::diamond_io::GpuDCRTPolyMatrixNativeBenchEstimator, - lookup::{PublicLut, lwe::LWEBGGPubKeyPltEvaluator}, - matrix::{dcrt_poly::DCRTPolyMatrix, gpu_dcrt_poly::GpuDCRTPolyMatrix}, + lookup::{ + PltEvaluator, PublicLut, + lwe::{LWEBGGEncodingPltEvaluator, LWEBGGPubKeyPltEvaluator}, + }, + matrix::{PolyMatrix, dcrt_poly::DCRTPolyMatrix, gpu_dcrt_poly::GpuDCRTPolyMatrix}, poly::{ Poly, PolyParams, dcrt::{ @@ -22,14 +29,14 @@ use mxx::{ }, }, sampler::{ - PolyTrapdoorSampler, + DistType, PolyTrapdoorSampler, PolyUniformSampler, gpu::{GpuDCRTPolyHashSampler, GpuDCRTPolyUniformSampler}, hash::DCRTPolyHashSampler, trapdoor::{DCRTPolyTrapdoorSampler, GpuDCRTPolyTrapdoorSampler}, uniform::DCRTPolyUniformSampler, }, slot_transfer::bgg_pubkey::BggPublicKeySTEvaluator, - storage::write::init_storage_system, + storage::write::{init_storage_system, wait_for_all_writes}, utils::bigdecimal_bits_ceil, we::{ DiamondWE, WitnessEnc, @@ -95,6 +102,7 @@ type GpuDiamondWE = DiamondWE< >; type GpuPubKeyPltEvaluator = LWEBGGPubKeyPltEvaluator; +type GpuEncodingPltEvaluator = LWEBGGEncodingPltEvaluator; type GpuPubKeySlotEvaluator = BggPublicKeySTEvaluator< GpuMatrix, GpuDCRTPolyUniformSampler, @@ -541,6 +549,67 @@ fn build_public_key_bench_estimator( ) } +async fn build_encoding_bench_estimator( + params: &GpuDCRTPolyParams, + cfg: &DiamondWEGpuBenchConfig, + bench_dir: PathBuf, +) -> BggEncodingBenchEstimator { + if bench_dir.exists() { + fs::remove_dir_all(&bench_dir).expect("failed to clear DiamondWE encoding bench directory"); + } + ensure_dir(&bench_dir); + init_storage_system(bench_dir.clone()); + + let (public_lut, public_lut_id, public_lut_gate_id) = + build_benchmark_public_lookup_gate(params); + let public_key_sampler = + BGGPublicKeySampler::<_, GpuHashSampler>::new(DEFAULT_HASH_KEY, cfg.d_secret); + let uniform_sampler = GpuDCRTPolyUniformSampler::new(); + let secrets = + uniform_sampler.sample_uniform(params, 1, cfg.d_secret, DistType::TernaryDist).get_row(0); + let plaintexts = vec![GpuDCRTPoly::from_usize_to_constant(params, 2)]; + let public_keys = + public_key_sampler.sample(params, b"test_gpu_diamond_we_encoding_bench", &[true]); + let encoding_sampler = + BGGEncodingSampler::::new(params, &secrets, None); + let encodings = encoding_sampler.sample(params, &public_keys, &plaintexts); + + let public_lut_aux_writer = build_lwe_scalar_pubkey_plt_evaluator( + params, + DEFAULT_HASH_KEY, + cfg.d_secret, + bench_dir.clone(), + ); + let lookup_base_matrix = Arc::clone(&public_lut_aux_writer.pub_matrix); + public_lut_aux_writer.write_dummy_aux_for_poly_encode_bench( + params, + &public_lut, + &[plaintexts[0].const_coeff_u64()], + public_lut_id, + public_lut_gate_id, + cfg.error_sigma, + ); + wait_for_all_writes(bench_dir.clone()) + .await + .expect("DiamondWE encoding public lookup benchmark aux writes must flush"); + + let secret_vec = GpuMatrix::from_poly_vec_row(params, secrets); + let c_b = secret_vec * lookup_base_matrix.as_ref(); + let encoding_evaluator = GpuEncodingPltEvaluator::new(DEFAULT_HASH_KEY, bench_dir, c_b); + let one = encodings[0].clone(); + let input = encodings[1].clone(); + BggEncodingBenchEstimator::::benchmark(params, cfg.bench_iterations, || { + encoding_evaluator.public_lookup( + params, + &public_lut, + &one, + &input, + public_lut_gate_id, + public_lut_id, + ) + }) +} + #[tokio::test] #[sequential_test::sequential] async fn test_gpu_diamond_we_error_search_bench_estimate_and_round_trip() { @@ -646,7 +715,7 @@ async fn test_gpu_diamond_we_error_search_bench_estimate_and_round_trip() { build_public_key_bench_estimator(&gpu_params, &cfg, final_dir.join("pubkey_bench")); info!("starting DiamondWE GPU encoding bench estimator construction"); let encoding_estimator = - BggEncodingBenchEstimator::::benchmark(&gpu_params, cfg.bench_iterations, || ()); + build_encoding_bench_estimator(&gpu_params, &cfg, final_dir.join("encoding_bench")).await; info!("starting DiamondWE GPU native bench estimator construction"); let native_estimator = GpuDCRTPolyMatrixNativeBenchEstimator::new(gpu_params.clone(), cfg.bench_iterations); diff --git a/tests/test_gpu_lwe_nested_rns_modq_arith.rs b/tests/test_gpu_lwe_nested_rns_modq_arith.rs index 1e186137..1d68b1f3 100644 --- a/tests/test_gpu_lwe_nested_rns_modq_arith.rs +++ b/tests/test_gpu_lwe_nested_rns_modq_arith.rs @@ -17,7 +17,7 @@ use mxx::{ DEFAULT_MAX_UNREDUCED_MULS, NestedRnsPoly, NestedRnsPolyContext, encode_nested_rns_poly, }, lookup::{ - PublicLut, + PltEvaluator, PublicLut, lwe::{LWEBGGEncodingPltEvaluator, LWEBGGPubKeyPltEvaluator}, poly::PolyPltEvaluator, }, @@ -38,7 +38,11 @@ use mxx::{ gpu::{GpuDCRTPolyHashSampler, GpuDCRTPolyUniformSampler}, trapdoor::GpuDCRTPolyTrapdoorSampler, }, - simulator::{SimulatorContext, error_norm::NormPltLWEEvaluator}, + simulator::{ + SimulatorContext, + error_norm::NormPltLWEEvaluator, + lattice_estimator::{Distribution, run_lattice_estimator_cli_with_timeout}, + }, slot_transfer::bgg_pubkey::BggPublicKeySTEvaluator, storage::write::{init_storage_system, wait_for_all_writes}, utils::bigdecimal_bits_ceil, @@ -48,7 +52,7 @@ use std::{ env, fs, path::{Path, PathBuf}, sync::Arc, - time::Instant, + time::{Duration, Instant}, }; use tracing::{debug, info}; @@ -66,6 +70,7 @@ const DEFAULT_BENCH_ITERATIONS: usize = 1; const DEFAULT_BENCH_SEED: [u8; 32] = [0u8; 32]; const TRAPDOOR_SIGMA: f64 = 4.578; const ERROR_SIM_ACTIVE_LEVELS: usize = 1; +const LATTICE_ESTIMATOR_TIMEOUT: Duration = Duration::from_secs(60 * 60); const ACTUAL_HASH_KEY_CHECKPOINT_FILE: &str = "actual_lwe_hash_key.bin"; const ACTUAL_TRAPDOOR_CHECKPOINT_FILE: &str = "actual_lwe_trapdoor.bin"; const ACTUAL_PUB_MATRIX_CHECKPOINT_FILE: &str = "actual_lwe_pub_matrix.bin"; @@ -74,6 +79,7 @@ type GpuMatrix = GpuDCRTPolyMatrix; type GpuHashSampler = GpuDCRTPolyHashSampler; type GpuScalarPubKeyPltEvaluator = LWEBGGPubKeyPltEvaluator; +type GpuEncodingPltEvaluator = LWEBGGEncodingPltEvaluator; type GpuPubKeySlotEvaluator = BggPublicKeySTEvaluator< GpuMatrix, GpuDCRTPolyUniformSampler, @@ -487,6 +493,46 @@ fn find_crt_depth_for_modq_arith( ); } +fn log_lattice_estimator_security_for_selected_crt_depth( + params: &DCRTPolyParams, + cfg: &ModqArithConfig, + crt_depth: usize, +) { + let q = params.modulus(); + let ring_dim = BigUint::from(params.ring_dimension()); + let s_dist = Distribution::Ternary; + let e_dist = + Distribution::DiscreteGaussian { stddev: cfg.error_sigma.to_string(), mean: None, n: None }; + info!( + crt_depth, + ring_dim = params.ring_dimension(), + modulus_bits = q.bits(), + error_sigma = cfg.error_sigma, + "running lattice-estimator security check for selected nested-RNS modq arithmetic parameters" + ); + let achieved_secpar_for_gauss = run_lattice_estimator_cli_with_timeout( + &ring_dim, + q.as_ref(), + &s_dist, + &e_dist, + None, + false, + LATTICE_ESTIMATOR_TIMEOUT, + ) + .unwrap_or_else(|err| { + panic!( + "nested-RNS modq arithmetic benchmark requires a working lattice-estimator-cli on PATH to log the selected CRT-depth security level; failed: {err}" + ) + }); + info!( + crt_depth, + ring_dim = params.ring_dimension(), + modulus_bits = q.bits(), + achieved_secpar_for_gauss, + "nested-RNS modq arithmetic selected parameter lattice-estimator security" + ); +} + fn build_lwe_scalar_pubkey_plt_evaluator( params: &GpuDCRTPolyParams, hash_key: [u8; 32], @@ -565,6 +611,69 @@ fn constant_and_shared_benchmark_pubkeys( (&pubkeys[0], &pubkeys[1]) } +async fn build_encoding_bench_estimator( + params: &GpuDCRTPolyParams, + cfg: &ModqArithConfig, + bench_dir: PathBuf, +) -> BggEncodingBenchEstimator { + if bench_dir.exists() { + fs::remove_dir_all(&bench_dir) + .expect("failed to clear modq arithmetic encoding bench directory"); + } + fs::create_dir_all(&bench_dir) + .expect("failed to create modq arithmetic encoding bench directory"); + init_storage_system(bench_dir.clone()); + + let (public_lut, public_lut_id, public_lut_gate_id) = + build_benchmark_public_lookup_gate(params); + let public_key_sampler = + BGGPublicKeySampler::<_, GpuHashSampler>::new(DEFAULT_BENCH_SEED, cfg.d_secret); + let uniform_sampler = GpuDCRTPolyUniformSampler::new(); + let secrets = + uniform_sampler.sample_uniform(params, 1, cfg.d_secret, DistType::TernaryDist).get_row(0); + let plaintexts = vec![GpuDCRTPoly::from_usize_to_constant(params, 2)]; + let public_keys = + public_key_sampler.sample(params, b"LWE_NESTED_RNS_MODQ_ARITH_ENCODING_BENCH", &[true]); + let encoding_sampler = + BGGEncodingSampler::::new(params, &secrets, None); + let encodings = encoding_sampler.sample(params, &public_keys, &plaintexts); + + let public_lut_aux_writer = build_lwe_scalar_pubkey_plt_evaluator( + params, + DEFAULT_BENCH_SEED, + cfg.d_secret, + bench_dir.clone(), + ); + let lookup_base_matrix = Arc::clone(&public_lut_aux_writer.pub_matrix); + public_lut_aux_writer.write_dummy_aux_for_poly_encode_bench( + params, + &public_lut, + &[plaintexts[0].const_coeff_u64()], + public_lut_id, + public_lut_gate_id, + cfg.error_sigma, + ); + wait_for_all_writes(bench_dir.clone()) + .await + .expect("modq arithmetic encoding public lookup benchmark aux writes must flush"); + + let secret_vec = GpuMatrix::from_poly_vec_row(params, secrets); + let c_b = secret_vec * lookup_base_matrix.as_ref(); + let encoding_evaluator = GpuEncodingPltEvaluator::new(DEFAULT_BENCH_SEED, bench_dir, c_b); + let one = encodings[0].clone(); + let input = encodings[1].clone(); + BggEncodingBenchEstimator::::benchmark(params, cfg.bench_iterations, || { + encoding_evaluator.public_lookup( + params, + &public_lut, + &one, + &input, + public_lut_gate_id, + public_lut_id, + ) + }) +} + fn round_div_biguint(value: &BigUint, divisor: &BigUint) -> BigUint { let half = divisor / BigUint::from(2u64); (value + half) / divisor @@ -582,6 +691,7 @@ async fn test_gpu_lwe_nested_rns_modq_arith() { .checked_shl(cfg.height as u32) .expect("LWE_NESTED_RNS_MODQ_ARITH_HEIGHT is too large"); let (crt_depth, cpu_params) = find_crt_depth_for_modq_arith(&cfg, q_level); + log_lattice_estimator_security_for_selected_crt_depth(&cpu_params, &cfg, crt_depth); let (moduli, _, _) = cpu_params.to_crt(); let detected_gpu_ids = detected_gpu_device_ids(); let detected_gpu_count = detected_gpu_device_count(); @@ -752,7 +862,7 @@ async fn test_gpu_lwe_nested_rns_modq_arith() { ); let encoding_scalar_bench = - BggEncodingBenchEstimator::::benchmark(¶ms, cfg.bench_iterations, || ()); + build_encoding_bench_estimator(¶ms, &cfg, dir.join("encoding_bench")).await; let encoding_circuit_bench = encoding_scalar_bench.estimate_circuit_bench(&circuit); info!( "modq_arith scalar bgg encoding circuit bench estimate: total_time={:.6} latency={:.6} max_parallelism={} peak_vram={}", diff --git a/tests/test_gpu_lwe_nested_rns_ring_gsw_mul_bench.rs b/tests/test_gpu_lwe_nested_rns_ring_gsw_mul_bench.rs index c11f0c61..6e88e363 100644 --- a/tests/test_gpu_lwe_nested_rns_ring_gsw_mul_bench.rs +++ b/tests/test_gpu_lwe_nested_rns_ring_gsw_mul_bench.rs @@ -14,7 +14,7 @@ use mxx::{ NaiveBGGPublicKeyVecSampler, }, public_key::BggPublicKey, - sampler::BGGPublicKeySampler, + sampler::{BGGEncodingSampler, BGGPublicKeySampler}, }, circuit::{PolyCircuit, PolyGateKind, evaluable::PolyVec, gate::GateId}, element::PolyElem, @@ -27,7 +27,7 @@ use mxx::{ }, }, lookup::{ - PublicLut, + PltEvaluator, PublicLut, lwe::{ LWEBGGEncodingPltEvaluator, LWEBGGPubKeyPltEvaluator, NaiveLWEBGGEncodingVecPltEvaluator, NaiveLWEBGGPublicKeyVecPltEvaluator, @@ -55,6 +55,7 @@ use mxx::{ simulator::{ SimulatorContext, error_norm::{NormNaiveBggEncodingVecSTEvaluator, NormPltLWEEvaluator}, + lattice_estimator::{Distribution, run_lattice_estimator_cli_with_timeout}, }, slot_transfer::{ NaiveBGGVecSlotTransferEvaluator, PolyVecSlotTransferEvaluator, @@ -69,7 +70,7 @@ use std::{ env, fs, path::{Path, PathBuf}, sync::Arc, - time::Instant, + time::{Duration, Instant}, }; use tracing::{debug, info}; @@ -87,6 +88,7 @@ const DEFAULT_MUL_DEPTH: usize = 1; const DEFAULT_BENCH_SEED: [u8; 32] = [0u8; 32]; const TRAPDOOR_SIGMA: f64 = 4.578; const ERROR_SIM_ACTIVE_LEVELS: usize = 1; +const LATTICE_ESTIMATOR_TIMEOUT: Duration = Duration::from_secs(60 * 60); type GpuMatrix = GpuDCRTPolyMatrix; type GpuHashSampler = GpuDCRTPolyHashSampler; @@ -115,6 +117,7 @@ struct RingGswMulBenchConfig { d_secret: usize, bench_iterations: usize, mul_depth: usize, + skip_lattice_estimator: bool, active_levels_override: Option, dir_name_override: Option, } @@ -149,6 +152,17 @@ fn env_or_parse_f64(key: &str, default: f64) -> f64 { } } +fn env_or_parse_bool(key: &str, default: bool) -> bool { + env::var(key) + .ok() + .map(|raw| match raw.as_str() { + "1" | "true" | "TRUE" | "yes" | "YES" => true, + "0" | "false" | "FALSE" | "no" | "NO" => false, + _ => panic!("{key} must be a bool-like value: 1/0, true/false, or yes/no"), + }) + .unwrap_or(default) +} + impl RingGswMulBenchConfig { fn from_env() -> Self { let ring_dim = @@ -180,6 +194,8 @@ impl RingGswMulBenchConfig { ); let mul_depth = env_or_parse_usize("LWE_NESTED_RNS_RING_GSW_MUL_BENCH_MUL_DEPTH", DEFAULT_MUL_DEPTH); + let skip_lattice_estimator = + env_or_parse_bool("LWE_NESTED_RNS_RING_GSW_MUL_BENCH_SKIP_LATTICE_ESTIMATOR", false); let active_levels_override = env::var("LWE_NESTED_RNS_RING_GSW_MUL_BENCH_ACTIVE_LEVELS") .ok() .map(|value| value.trim().to_string()) @@ -226,6 +242,7 @@ impl RingGswMulBenchConfig { d_secret, bench_iterations, mul_depth, + skip_lattice_estimator, active_levels_override, dir_name_override, } @@ -538,6 +555,64 @@ fn find_crt_depth_for_ring_gsw_mul_bench(cfg: &RingGswMulBenchConfig) -> (usize, ); } +fn sample_aux_max_parallelism(total_time: f64, latency: f64) -> BigUint { + if total_time <= 0.0 || latency <= 0.0 { + BigUint::from(0u8) + } else { + BigUint::from((total_time / latency).ceil() as u128) + } +} + +fn log_lattice_estimator_security_for_selected_crt_depth( + params: &DCRTPolyParams, + cfg: &RingGswMulBenchConfig, + crt_depth: usize, +) { + if cfg.skip_lattice_estimator { + info!( + crt_depth, + ring_dim = params.ring_dimension(), + modulus_bits = params.modulus_bits(), + "skipping lattice-estimator security check because LWE_NESTED_RNS_RING_GSW_MUL_BENCH_SKIP_LATTICE_ESTIMATOR was requested" + ); + return; + } + + let q = params.modulus(); + let ring_dim = BigUint::from(params.ring_dimension()); + let s_dist = Distribution::Ternary; + let e_dist = + Distribution::DiscreteGaussian { stddev: cfg.error_sigma.to_string(), mean: None, n: None }; + info!( + crt_depth, + ring_dim = params.ring_dimension(), + modulus_bits = q.bits(), + error_sigma = cfg.error_sigma, + "running lattice-estimator security check for selected nested-RNS Ring-GSW multiplication parameters" + ); + let achieved_secpar_for_gauss = run_lattice_estimator_cli_with_timeout( + &ring_dim, + q.as_ref(), + &s_dist, + &e_dist, + None, + false, + LATTICE_ESTIMATOR_TIMEOUT, + ) + .unwrap_or_else(|err| { + panic!( + "nested-RNS Ring-GSW multiplication benchmark requires a working lattice-estimator-cli on PATH to log the selected CRT-depth security level; failed: {err}" + ) + }); + info!( + crt_depth, + ring_dim = params.ring_dimension(), + modulus_bits = q.bits(), + achieved_secpar_for_gauss, + "nested-RNS Ring-GSW multiplication selected parameter lattice-estimator security" + ); +} + fn build_lwe_pubkey_vec_plt_evaluator( params: &GpuDCRTPolyParams, hash_key: [u8; 32], @@ -637,6 +712,74 @@ fn constant_and_shared_benchmark_pubkeys( (&pubkeys[0], &pubkeys[1]) } +async fn build_naive_vec_encoding_bench_estimator( + params: &GpuDCRTPolyParams, + cfg: &RingGswMulBenchConfig, + bench_dir: PathBuf, +) -> NaiveBGGVecBenchEstimator> { + if bench_dir.exists() { + fs::remove_dir_all(&bench_dir).expect("failed to clear encoding benchmark directory"); + } + fs::create_dir_all(&bench_dir).expect("failed to create encoding benchmark directory"); + init_storage_system(bench_dir.clone()); + + let (public_lut, public_lut_id, public_lut_gate_id) = + build_benchmark_public_lookup_gate(params); + let public_key_sampler = + BGGPublicKeySampler::<_, GpuHashSampler>::new(DEFAULT_BENCH_SEED, cfg.d_secret); + let uniform_sampler = GpuDCRTPolyUniformSampler::new(); + let secrets = + uniform_sampler.sample_uniform(params, 1, cfg.d_secret, DistType::TernaryDist).get_row(0); + let plaintexts = vec![GpuDCRTPoly::from_usize_to_constant(params, 2)]; + let public_keys = + public_key_sampler.sample(params, b"LWE_NESTED_RNS_RING_GSW_MUL_ENCODING_BENCH", &[true]); + let encoding_sampler = + BGGEncodingSampler::::new(params, &secrets, None); + let encodings = encoding_sampler.sample(params, &public_keys, &plaintexts); + + let public_lut_aux_writer = build_lwe_pubkey_vec_plt_evaluator( + params, + DEFAULT_BENCH_SEED, + cfg.d_secret, + bench_dir.clone(), + ); + let lookup_base_matrix = Arc::clone(&public_lut_aux_writer.inner.pub_matrix); + public_lut_aux_writer.write_dummy_aux_for_poly_encode_bench( + params, + &public_lut, + &[plaintexts[0].const_coeff_u64()], + public_lut_id, + public_lut_gate_id, + cfg.error_sigma, + ); + wait_for_all_writes(bench_dir.clone()) + .await + .expect("Ring-GSW multiplication encoding public lookup benchmark aux writes must flush"); + + let secret_vec = GpuMatrix::from_poly_vec_row(params, secrets); + let c_b = secret_vec * lookup_base_matrix.as_ref(); + let encoding_evaluator = GpuEncodingPltEvaluator::new(LWEBGGEncodingPltEvaluator::< + GpuMatrix, + GpuHashSampler, + >::new( + DEFAULT_BENCH_SEED, bench_dir, c_b + )); + let one = NaiveBGGEncodingVec::new(params, vec![encodings[0].clone()]); + let input = NaiveBGGEncodingVec::new(params, vec![encodings[1].clone()]); + let scalar_estimator = + BggEncodingBenchEstimator::::benchmark(params, cfg.bench_iterations, || { + encoding_evaluator.public_lookup( + params, + &public_lut, + &one, + &input, + public_lut_gate_id, + public_lut_id, + ) + }); + NaiveBGGVecBenchEstimator::new(scalar_estimator, cfg.num_slots()) +} + fn round_div_biguint(value: &BigUint, divisor: &BigUint) -> BigUint { let half = divisor / BigUint::from(2u64); (value + half) / divisor @@ -700,6 +843,7 @@ async fn test_gpu_lwe_nested_rns_ring_gsw_mul_bench() { info!("nested-RNS ring_gsw mul bench config: {:?}", cfg); let (crt_depth, cpu_params) = find_crt_depth_for_ring_gsw_mul_bench(&cfg); + log_lattice_estimator_security_for_selected_crt_depth(&cpu_params, &cfg, crt_depth); let (moduli, _, _) = cpu_params.to_crt(); let detected_gpu_ids = detected_gpu_device_ids(); let detected_gpu_count = detected_gpu_device_count(); @@ -790,6 +934,12 @@ async fn test_gpu_lwe_nested_rns_ring_gsw_mul_bench() { bigdecimal_bits_ceil(&BigDecimal::from_biguint(ring_gsw_threshold, 0)), selected_decryption_ok ); + assert!( + selected_decryption_ok, + "selected Ring-GSW multiplication ciphertext decryption error exceeds q/2: error_bits={}, threshold_bits={}", + bigdecimal_bits_ceil(&max_selected_decryption_error), + bigdecimal_bits_ceil(&ring_gsw_threshold_bd) + ); let mul_bench_dir = Path::new(&cfg.bench_dir_base(crt_depth)).join("ring_gsw_mul"); let pubkey_bench_dir = mul_bench_dir.join("pubkey"); @@ -904,11 +1054,30 @@ async fn test_gpu_lwe_nested_rns_ring_gsw_mul_bench() { pubkey_circuit_bench.max_parallelism, pubkey_circuit_bench.peak_vram ); + let pubkey_slot_transfer_aux_total_time = pubkey_slot_transfer_aux_slot_time.total_time * + cfg.num_slots() as f64 + + pubkey_slot_transfer_aux_gate_time.total_time * total_slot_transfer_gates as f64; + let pubkey_slot_transfer_aux_latency = + pubkey_slot_transfer_aux_slot_time.latency + pubkey_slot_transfer_aux_gate_time.latency; + let pubkey_sample_aux_total_time = + pubkey_public_lut_aux_estimate.total_time + pubkey_slot_transfer_aux_total_time; + let pubkey_sample_aux_latency = + pubkey_public_lut_aux_estimate.latency + pubkey_slot_transfer_aux_latency; + let pubkey_sample_aux_max_parallelism = + sample_aux_max_parallelism(pubkey_sample_aux_total_time, pubkey_sample_aux_latency); + let pubkey_preprocess_total_time = + pubkey_circuit_bench.total_time_seconds() + pubkey_sample_aux_total_time; + let pubkey_preprocess_latency = pubkey_circuit_bench.latency + pubkey_sample_aux_latency; + let pubkey_preprocess_max_parallelism = + pubkey_circuit_bench.max_parallelism.clone().max(pubkey_sample_aux_max_parallelism); + info!( + "ring_gsw_mul naive bgg pubkey vec preprocess bench estimate: total_time_sec={:.6} latency_sec={:.6} max_parallelism={}", + pubkey_preprocess_total_time, pubkey_preprocess_latency, pubkey_preprocess_max_parallelism + ); - let encoding_scalar_bench = - BggEncodingBenchEstimator::::benchmark(¶ms, cfg.bench_iterations, || ()); let encoding_vec_bench_estimator = - NaiveBGGVecBenchEstimator::new(encoding_scalar_bench, cfg.num_slots()); + build_naive_vec_encoding_bench_estimator(¶ms, &cfg, mul_bench_dir.join("encoding")) + .await; let poly_circuit_bench = encoding_vec_bench_estimator.estimate_circuit_bench(&circuit); info!( "ring_gsw_mul naive bgg encoding vec circuit bench estimate: total_time={:.6} latency={:.6} max_parallelism={} peak_vram={}",