chore(hlapi): add example to measure CPK and CCTL sizes

This also includes key generation time in WASM web client side
This commit is contained in:
Arthur Meyre
2023-06-09 16:14:44 +02:00
committed by David Testé
parent f8b497a4b8
commit 9e307a8945
19 changed files with 826 additions and 172 deletions

View File

@@ -0,0 +1,115 @@
# Run WASM client benchmarks on an AWS instance and return parsed results to Slab CI bot.
name: WASM client benchmarks
on:
workflow_dispatch:
inputs:
instance_id:
description: "Instance ID"
type: string
instance_image_id:
description: "Instance AMI ID"
type: string
instance_type:
description: "Instance product type"
type: string
runner_name:
description: "Action runner name"
type: string
request_id:
description: "Slab request ID"
type: string
env:
CARGO_TERM_COLOR: always
RESULTS_FILENAME: parsed_benchmark_results_${{ github.sha }}.json
jobs:
run-wasm-client-benchmarks:
name: Execute WASM client benchmarks in EC2
runs-on: ${{ github.event.inputs.runner_name }}
if: ${{ !cancelled() }}
steps:
- name: Instance configuration used
run: |
echo "IDs: ${{ inputs.instance_id }}"
echo "AMI: ${{ inputs.instance_image_id }}"
echo "Type: ${{ inputs.instance_type }}"
echo "Request ID: ${{ inputs.request_id }}"
- name: Get benchmark date
run: |
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
with:
fetch-depth: 0
- name: Set up home
# "Install rust" step require root user to have a HOME directory which is not set.
run: |
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
- name: Install rust
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
with:
toolchain: nightly
override: true
- name: Run benchmarks
run: |
make install_node
make bench_web_js_api_parallel
- name: Parse results
run: |
make parse_wasm_benchmarks
COMMIT_DATE="$(git --no-pager show -s --format=%cd --date=iso8601-strict ${{ github.sha }})"
COMMIT_HASH="$(git describe --tags --dirty)"
python3 ./ci/benchmark_parser.py tfhe/web_wasm_parallel_tests/public_key_ct_generation_timings.csv ${{ env.RESULTS_FILENAME }} \
--database tfhe_rs \
--hardware ${{ inputs.instance_type }} \
--project-version "${COMMIT_HASH}" \
--branch ${{ github.ref_name }} \
--commit-date "${COMMIT_DATE}" \
--bench-date "${{ env.BENCH_DATE }}" \
--key-gen
- name: Measure public key and ciphertext sizes in HL Api
run: |
make measure_hlapi_compact_pk_ct_sizes
- name: Parse key and ciphertext sizes results
run: |
python3 ./ci/benchmark_parser.py tfhe/hlapi_cpk_and_cctl_sizes.csv ${{ env.RESULTS_FILENAME }} \
--key-gen \
--append-results
- name: Upload parsed results artifact
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
with:
name: ${{ github.sha }}_wasm
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
with:
repository: zama-ai/slab
path: slab
token: ${{ secrets.CONCRETE_ACTIONS_TOKEN }}
- name: Send data to Slab
shell: bash
run: |
echo "Computing HMac on results file"
SIGNATURE="$(slab/scripts/hmac_calculator.sh ${{ env.RESULTS_FILENAME }} '${{ secrets.JOB_SECRET }}')"
echo "Sending results to Slab..."
curl -v -k \
-H "Content-Type: application/json" \
-H "X-Slab-Repository: ${{ github.repository }}" \
-H "X-Slab-Command: store_data_v2" \
-H "X-Hub-Signature-256: sha256=${SIGNATURE}" \
-d @${{ env.RESULTS_FILENAME }} \
${{ secrets.SLAB_URL }}

2
.gitignore vendored
View File

@@ -12,4 +12,4 @@ target/
# Some of our bench outputs
/tfhe/benchmarks_parameters
/tfhe/shortint_key_sizes.csv
**/*.csv

View File

@@ -370,6 +370,10 @@ ci_test_web_js_api_parallel: build_web_js_api_parallel
no_tfhe_typo:
@./scripts/no_tfhe_typo.sh
#
# Benchmarks
#
.PHONY: bench_integer # Run benchmarks for integer
bench_integer: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
@@ -394,23 +398,54 @@ bench_pbs: install_rs_check_toolchain
--bench pbs-bench \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,internal-keycache,$(AVX512_FEATURE) -p tfhe
.PHONY: bench_web_js_api_parallel # Run benchmarks for the web wasm api
bench_web_js_api_parallel: build_web_js_api_parallel
$(MAKE) -C tfhe/web_wasm_parallel_tests bench
.PHONY: ci_bench_web_js_api_parallel # Run benchmarks for the web wasm api
ci_bench_web_js_api_parallel: build_web_js_api_parallel
source ~/.nvm/nvm.sh && \
nvm use node && \
$(MAKE) -C tfhe/web_wasm_parallel_tests bench-ci
#
# Utility tools
#
.PHONY: measure_hlapi_compact_pk_ct_sizes # Measure sizes of public keys and ciphertext for high-level API
measure_hlapi_compact_pk_ct_sizes: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \
--example hlapi_compact_pk_ct_sizes \
--features=$(TARGET_ARCH_FEATURE),integer,internal-keycache
.PHONY: measure_shortint_key_sizes # Measure sizes of bootstrapping and key switching keys for shortint
measure_shortint_key_sizes: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \
--example shortint_key_sizes \
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache
.PHONY: measure_boolean_key_sizes # Measure sizes of bootstrapping and key switching keys for boolean
measure_boolean_key_sizes: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \
--example boolean_key_sizes \
--features=$(TARGET_ARCH_FEATURE),boolean,internal-keycache
.PHONY: parse_integer_benches # Run python parser to output a csv containing integer benches data
parse_integer_benches:
python3 ./ci/parse_integer_benches_to_csv.py \
--criterion-dir target/criterion \
--output-file "$(PARSE_INTEGER_BENCH_CSV_FILE)"
.PHONY: measure_shortint_key_sizes # Measure sizes of bootstrapping and key switching keys for shortint
measure_shortint_key_sizes: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run \
--example shortint_key_sizes \
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache
.PHONY: parse_wasm_benchmarks # Parse benchmarks performed with WASM web client into a CSV file
parse_wasm_benchmarks: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \
--example wasm_benchmarks_parser \
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache \
-- tfhe/web_wasm_parallel_tests/test/benchmark_results
.PHONY: measure_boolean_key_sizes # Measure sizes of bootstrapping and key switching keys for boolean
measure_boolean_key_sizes: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run \
--example boolean_key_sizes \
--features=$(TARGET_ARCH_FEATURE),boolean,internal-keycache
#
# Real use case examples
#
.PHONY: regex_engine # Run regex_engine example
regex_engine: install_rs_check_toolchain

View File

@@ -39,6 +39,8 @@ parser.add_argument('--walk-subdirs', dest='walk_subdirs', action='store_true',
help='Check for results in subdirectories')
parser.add_argument('--key-sizes', dest='key_sizes', action='store_true',
help='Parse only the results regarding keys size measurements')
parser.add_argument('--key-gen', dest='key_gen', action='store_true',
help='Parse only the results regarding keys generation time measurements')
parser.add_argument('--throughput', dest='throughput', action='store_true',
help='Compute and append number of operations per second and'
'operations per dollar')
@@ -177,9 +179,9 @@ def parse_estimate_file(directory):
}
def parse_key_sizes(result_file):
def _parse_key_results(result_file, bench_type):
"""
Parse file containing key sizes results. The file must be formatted as CSV.
Parse file containing results about operation on keys. The file must be formatted as CSV.
:param result_file: results file as :class:`pathlib.Path`
@@ -202,13 +204,35 @@ def parse_key_sizes(result_file):
"test": test_name,
"name": display_name,
"class": "keygen",
"type": "keysize",
"type": bench_type,
"operator": operator,
"params": params})
return result_values, parsing_failures
def parse_key_sizes(result_file):
"""
Parse file containing key sizes results. The file must be formatted as CSV.
:param result_file: results file as :class:`pathlib.Path`
:return: tuple of :class:`list` as (data points, parsing failures)
"""
return _parse_key_results(result_file, "keysize")
def parse_key_gen_time(result_file):
"""
Parse file containing key generation time results. The file must be formatted as CSV.
:param result_file: results file as :class:`pathlib.Path`
:return: tuple of :class:`list` as (data points, parsing failures)
"""
return _parse_key_results(result_file, "latency")
def get_parameters(bench_id):
"""
Get benchmarks parameters recorded for a given benchmark case.
@@ -301,7 +325,7 @@ def check_mandatory_args(input_args):
for arg_name in vars(input_args):
if arg_name in ["results_dir", "output_file", "name_suffix",
"append_results", "walk_subdirs", "key_sizes",
"throughput"]:
"key_gen", "throughput"]:
continue
if not getattr(input_args, arg_name):
missing_args.append(arg_name)
@@ -318,7 +342,15 @@ if __name__ == "__main__":
#failures = []
raw_results = pathlib.Path(args.results)
if not args.key_sizes:
if args.key_sizes or args.key_gen:
if args.key_sizes:
print("Parsing key sizes results... ")
results, failures = parse_key_sizes(raw_results)
if args.key_gen:
print("Parsing key generation time results... ")
results, failures = parse_key_gen_time(raw_results)
else:
print("Parsing benchmark results... ")
hardware_cost = None
if args.throughput:
@@ -334,9 +366,7 @@ if __name__ == "__main__":
results, failures = recursive_parse(raw_results, args.walk_subdirs, args.name_suffix,
args.throughput, hardware_cost)
else:
print("Parsing key sizes results... ")
results, failures = parse_key_sizes(raw_results)
print("Parsing results done")
output_file = pathlib.Path(args.output_file)

View File

@@ -10,7 +10,13 @@ repository = "https://github.com/zama-ai/tfhe-rs"
license = "BSD-3-Clause-Clear"
description = "TFHE-rs is a fully homomorphic encryption (FHE) library that implements Zama's variant of TFHE."
build = "build.rs"
exclude = ["/docs/", "/c_api_tests/", "/CMakeLists.txt", "/js_on_wasm_tests/"]
exclude = [
"/docs/",
"/c_api_tests/",
"/CMakeLists.txt",
"/js_on_wasm_tests/",
"/web_wasm_parallel_tests/",
]
rust-version = "1.67"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -23,7 +29,7 @@ lazy_static = { version = "1.4.0" }
criterion = "0.4.0"
doc-comment = "0.3.3"
serde_json = "1.0.94"
clap = "4.2.7"
clap = { version = "4.2.7", features = ["derive"] }
# Used in user documentation
bincode = "1.3.3"
fs2 = { version = "0.4.3" }
@@ -83,12 +89,7 @@ experimental-force_fft_algo_dif4 = []
__c_api = ["cbindgen", "bincode"]
boolean-c-api = ["boolean", "__c_api"]
shortint-c-api = ["shortint", "__c_api"]
high-level-c-api = [
"boolean-c-api",
"shortint-c-api",
"integer",
"__c_api"
]
high-level-c-api = ["boolean-c-api", "shortint-c-api", "integer", "__c_api"]
__wasm_api = [
"wasm-bindgen",
@@ -189,30 +190,44 @@ path = "benches/utilities.rs"
harness = false
required-features = ["boolean", "shortint", "integer", "internal-keycache"]
# Examples used as tools
[[example]]
name = "wasm_benchmarks_parser"
path = "examples/utilities/wasm_benchmarks_parser.rs"
required-features = ["shortint", "internal-keycache"]
[[example]]
name = "generates_test_keys"
path = "examples/utilities/generates_test_keys.rs"
required-features = ["shortint", "internal-keycache"]
[[example]]
name = "boolean_key_sizes"
path = "examples/utilities/boolean_key_sizes.rs"
required-features = ["boolean", "internal-keycache"]
[[example]]
name = "dark_market"
required-features = ["integer", "internal-keycache"]
[[example]]
name = "shortint_key_sizes"
path = "examples/utilities/shortint_key_sizes.rs"
required-features = ["shortint", "internal-keycache"]
[[example]]
name = "integer_compact_pk_ct_sizes"
name = "hlapi_compact_pk_ct_sizes"
path = "examples/utilities/hlapi_compact_pk_ct_sizes.rs"
required-features = ["integer", "internal-keycache"]
[[example]]
name = "micro_bench_and"
path = "examples/utilities/micro_bench_and.rs"
required-features = ["boolean"]
# Real use-case examples
[[example]]
name = "dark_market"
required-features = ["integer", "internal-keycache"]
[[example]]
name = "regex_engine"
required-features = ["integer"]

View File

@@ -1,92 +0,0 @@
use rand::Rng;
use tfhe::core_crypto::commons::numeric::Numeric;
use tfhe::integer::block_decomposition::{DecomposableInto, RecomposableFrom};
use tfhe::integer::public_key::{CompactPublicKeyBig, CompactPublicKeySmall};
use tfhe::integer::{gen_keys, U256};
use tfhe::shortint::keycache::NamedParam;
use tfhe::shortint::parameters::*;
pub fn main() {
fn size_func<Scalar: Numeric + DecomposableInto<u64> + RecomposableFrom<u64> + From<u32>>() {
let mut rng = rand::thread_rng();
let num_bits = Scalar::BITS;
let params = PARAM_MESSAGE_2_CARRY_2_COMPACT_PK;
{
println!("Sizes for: {} and {num_bits} bits", params.name());
let (cks, _) = gen_keys(params);
let pk = CompactPublicKeyBig::new(&cks);
println!("PK size: {} bytes", bincode::serialize(&pk).unwrap().len());
let num_block =
(num_bits as f64 / (params.message_modulus.0 as f64).log(2.0)).ceil() as usize;
const MAX_CT: usize = 20;
let mut clear_vec = Vec::with_capacity(MAX_CT);
// 5 inputs to a smart contract
let num_ct_for_this_iter = 5;
clear_vec.truncate(0);
for _ in 0..num_ct_for_this_iter {
let clear = rng.gen::<u32>();
clear_vec.push(Scalar::from(clear));
}
let compact_encrypted_list = pk.encrypt_slice_radix_compact(&clear_vec, num_block);
println!(
"Compact CT list for {num_ct_for_this_iter} CTs: {} bytes",
bincode::serialize(&compact_encrypted_list).unwrap().len()
);
let ciphertext_vec = compact_encrypted_list.expand();
for (ciphertext, clear) in ciphertext_vec.iter().zip(clear_vec.iter().copied()) {
let decrypted: Scalar = cks.decrypt_radix(ciphertext);
assert_eq!(decrypted, clear);
}
}
let params = PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK;
{
println!("Sizes for: {} and {num_bits} bits", params.name());
let (cks, _) = gen_keys(params);
let pk = CompactPublicKeySmall::new(&cks);
println!("PK size: {} bytes", bincode::serialize(&pk).unwrap().len());
let num_block =
(num_bits as f64 / (params.message_modulus.0 as f64).log(2.0)).ceil() as usize;
const MAX_CT: usize = 20;
let mut clear_vec = Vec::with_capacity(MAX_CT);
// 5 inputs to a smart contract
let num_ct_for_this_iter = 5;
clear_vec.truncate(0);
for _ in 0..num_ct_for_this_iter {
let clear = rng.gen::<u32>();
clear_vec.push(Scalar::from(clear));
}
let compact_encrypted_list = pk.encrypt_slice_radix_compact(&clear_vec, num_block);
println!(
"Compact CT list for {num_ct_for_this_iter} CTs: {} bytes",
bincode::serialize(&compact_encrypted_list).unwrap().len()
);
let ciphertext_vec = compact_encrypted_list.expand();
for (ciphertext, clear) in ciphertext_vec.iter().zip(clear_vec.iter().copied()) {
let decrypted: Scalar = cks.decrypt_radix(ciphertext);
assert_eq!(decrypted, clear);
}
}
}
size_func::<u32>();
size_func::<U256>();
}

View File

@@ -1,4 +1,4 @@
#[path = "../benches/utilities.rs"]
#[path = "../../benches/utilities.rs"]
mod utilities;
use crate::utilities::{write_to_json, OperatorType};
@@ -81,13 +81,6 @@ fn client_server_key_sizes(results_file: &Path) {
}
fn main() {
let work_dir = std::env::current_dir().unwrap();
println!("work_dir: {}", std::env::current_dir().unwrap().display());
// Change workdir so that the location of the keycache matches the one for tests
let mut new_work_dir = work_dir;
new_work_dir.push("tfhe");
std::env::set_current_dir(new_work_dir).unwrap();
let results_file = Path::new("boolean_key_sizes.csv");
let results_file = Path::new("tfhe/boolean_key_sizes.csv");
client_server_key_sizes(results_file)
}

View File

@@ -0,0 +1,259 @@
#[path = "../../benches/utilities.rs"]
mod utilities;
use crate::utilities::{write_to_json, OperatorType};
use rand::Rng;
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::path::Path;
use tfhe::integer::U256;
use tfhe::prelude::*;
use tfhe::shortint::keycache::NamedParam;
use tfhe::shortint::parameters::{
PARAM_MESSAGE_2_CARRY_2_COMPACT_PK, PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK,
};
use tfhe::{
generate_keys, CompactFheUint256List, CompactFheUint32List, CompactPublicKey, ConfigBuilder,
};
fn write_result(file: &mut File, name: &str, value: usize) {
let line = format!("{name},{value}\n");
let error_message = format!("cannot write {name} result into file");
file.write_all(line.as_bytes()).expect(&error_message);
}
pub fn cpk_and_cctl_sizes(results_file: &Path) {
const NB_CTXT: usize = 5;
let mut rng = rand::thread_rng();
File::create(results_file).expect("create results file failed");
let mut file = OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(results_file)
.expect("cannot open results file");
let operator = OperatorType::Atomic;
{
let params = PARAM_MESSAGE_2_CARRY_2_COMPACT_PK;
let config = ConfigBuilder::all_disabled()
.enable_custom_integers(params, None)
.build();
let (client_key, _) = generate_keys(config);
let test_name = format!("hlapi_sizes_{}_cpk", params.name());
println!("Sizes for: {} and 32 bits", params.name());
let public_key = CompactPublicKey::new(&client_key);
let cpk_size = bincode::serialize(&public_key).unwrap().len();
println!("PK size: {cpk_size} bytes");
write_result(&mut file, &test_name, cpk_size);
write_to_json(
&test_name,
params,
params.name(),
"CPK",
&operator,
0,
vec![],
);
let test_name = format!("hlapi_sizes_{}_cctl_{NB_CTXT}_len_32_bits", params.name());
let vec_inputs: Vec<_> = (0..NB_CTXT).map(|_| rng.gen::<u32>()).collect();
let encrypted_inputs = CompactFheUint32List::encrypt(&vec_inputs, &public_key);
let cctl_size = bincode::serialize(&encrypted_inputs).unwrap().len();
println!("Compact CT list for {NB_CTXT} CTs: {} bytes", cctl_size);
write_result(&mut file, &test_name, cctl_size);
write_to_json(
&test_name,
params,
params.name(),
"CCTL",
&operator,
0,
vec![],
);
let expanded_inputs = encrypted_inputs.expand();
vec_inputs
.iter()
.zip(expanded_inputs.iter())
.for_each(|(&input, ct)| {
let clear: u32 = ct.decrypt(&client_key);
assert_eq!(clear, input);
});
}
{
let params = PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK;
let config = ConfigBuilder::all_disabled()
.enable_custom_integers(params, None)
.build();
let (client_key, _) = generate_keys(config);
let test_name = format!("hlapi_sizes_{}_cpk", params.name());
println!("Sizes for: {} and 32 bits", params.name());
let public_key = CompactPublicKey::new(&client_key);
let cpk_size = bincode::serialize(&public_key).unwrap().len();
println!("PK size: {cpk_size} bytes");
write_result(&mut file, &test_name, cpk_size);
write_to_json(
&test_name,
params,
params.name(),
"CPK",
&operator,
0,
vec![],
);
let test_name = format!("hlapi_sizes_{}_cctl_{NB_CTXT}_len_32_bits", params.name());
let vec_inputs: Vec<_> = (0..NB_CTXT).map(|_| rng.gen::<u32>()).collect();
let encrypted_inputs = CompactFheUint32List::encrypt(&vec_inputs, &public_key);
let cctl_size = bincode::serialize(&encrypted_inputs).unwrap().len();
println!("Compact CT list for {NB_CTXT} CTs: {} bytes", cctl_size);
write_result(&mut file, &test_name, cctl_size);
write_to_json(
&test_name,
params,
params.name(),
"CCTL",
&operator,
0,
vec![],
);
let expanded_inputs = encrypted_inputs.expand();
vec_inputs
.iter()
.zip(expanded_inputs.iter())
.for_each(|(&input, ct)| {
let clear: u32 = ct.decrypt(&client_key);
assert_eq!(clear, input);
});
}
// 256 bits
{
let params = PARAM_MESSAGE_2_CARRY_2_COMPACT_PK;
let config = ConfigBuilder::all_disabled()
.enable_custom_integers(params, None)
.build();
let (client_key, _) = generate_keys(config);
println!("Sizes for: {} and 256 bits", params.name());
let public_key = CompactPublicKey::new(&client_key);
println!(
"PK size: {} bytes",
bincode::serialize(&public_key).unwrap().len()
);
let test_name = format!("hlapi_sizes_{}_cctl_{NB_CTXT}_len_256_bits", params.name());
let vec_inputs: Vec<_> = (0..NB_CTXT).map(|_| rng.gen::<u32>()).collect();
let encrypted_inputs = CompactFheUint256List::encrypt(&vec_inputs, &public_key);
let cctl_size = bincode::serialize(&encrypted_inputs).unwrap().len();
println!("Compact CT list for {NB_CTXT} CTs: {} bytes", cctl_size);
write_result(&mut file, &test_name, cctl_size);
write_to_json(
&test_name,
params,
params.name(),
"CCTL",
&operator,
0,
vec![],
);
let expanded_inputs = encrypted_inputs.expand();
vec_inputs
.iter()
.zip(expanded_inputs.iter())
.for_each(|(&input, ct)| {
let clear: U256 = ct.decrypt(&client_key);
assert_eq!(clear, U256::from(input));
});
}
{
let params = PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK;
let config = ConfigBuilder::all_disabled()
.enable_custom_integers(params, None)
.build();
let (client_key, _) = generate_keys(config);
println!("Sizes for: {} and 256 bits", params.name());
let public_key = CompactPublicKey::new(&client_key);
println!(
"PK size: {} bytes",
bincode::serialize(&public_key).unwrap().len()
);
let test_name = format!("hlapi_sizes_{}_cctl_{NB_CTXT}_len_256_bits", params.name());
let vec_inputs: Vec<_> = (0..NB_CTXT).map(|_| rng.gen::<u32>()).collect();
let encrypted_inputs = CompactFheUint256List::encrypt(&vec_inputs, &public_key);
let cctl_size = bincode::serialize(&encrypted_inputs).unwrap().len();
println!("Compact CT list for {NB_CTXT} CTs: {} bytes", cctl_size);
write_result(&mut file, &test_name, cctl_size);
write_to_json(
&test_name,
params,
params.name(),
"CCTL",
&operator,
0,
vec![],
);
let expanded_inputs = encrypted_inputs.expand();
vec_inputs
.iter()
.zip(expanded_inputs.iter())
.for_each(|(&input, ct)| {
let clear: U256 = ct.decrypt(&client_key);
assert_eq!(clear, U256::from(input));
});
}
}
fn main() {
let work_dir = std::env::current_dir().unwrap();
println!("work_dir: {}", std::env::current_dir().unwrap().display());
// Change workdir so that the location of the keycache matches the one for tests
let mut new_work_dir = work_dir;
new_work_dir.push("tfhe");
std::env::set_current_dir(new_work_dir).unwrap();
let results_file = Path::new("hlapi_cpk_and_cctl_sizes.csv");
cpk_and_cctl_sizes(results_file)
}

View File

@@ -1,4 +1,4 @@
#[path = "../benches/utilities.rs"]
#[path = "../../benches/utilities.rs"]
mod utilities;
use crate::utilities::{write_to_json, OperatorType};

View File

@@ -0,0 +1,82 @@
#[path = "../../benches/utilities.rs"]
mod utilities;
use crate::utilities::{write_to_json, OperatorType};
use clap::Parser;
use std::collections::HashMap;
use std::fs;
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::path::Path;
use tfhe::shortint::keycache::NamedParam;
use tfhe::shortint::parameters::{
PARAM_MESSAGE_2_CARRY_2_COMPACT_PK, PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK,
};
use tfhe::shortint::ClassicPBSParameters;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
raw_results_dir: String,
}
fn params_from_name(name: &str) -> ClassicPBSParameters {
match name.to_lowercase().as_str() {
"param_message_2_carry_2_compact_pk" => PARAM_MESSAGE_2_CARRY_2_COMPACT_PK,
"param_small_message_2_carry_2_compact_pk" => PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK,
_ => panic!("failed to get parameters for name '{name}'"),
}
}
fn write_result(file: &mut File, name: &str, value: usize) {
let line = format!("{name},{value}\n");
let error_message = format!("cannot write {name} result into file");
file.write_all(line.as_bytes()).expect(&error_message);
}
pub fn parse_wasm_benchmarks(results_file: &Path, raw_results_dir: &Path) {
File::create(results_file).expect("create results file failed");
let mut file = OpenOptions::new()
.append(true)
.open(results_file)
.expect("cannot open parsed results file");
let operator = OperatorType::Atomic;
for entry in raw_results_dir
.read_dir()
.expect("cannot read results directory")
.flatten()
{
let raw_results = fs::read_to_string(entry.path()).expect("cannot open raw results file");
let results_as_json: HashMap<String, f32> = serde_json::from_str(&raw_results).unwrap();
for (full_name, val) in results_as_json.iter() {
let name_parts = full_name.split("_mean_").collect::<Vec<_>>();
let bench_name = name_parts[0];
let params = params_from_name(name_parts[1]);
let value_in_ns = (val * 1_000_000_f32) as usize;
write_result(&mut file, full_name, value_in_ns);
write_to_json(
full_name,
params,
params.name(),
bench_name,
&operator,
0,
vec![],
);
}
}
}
fn main() {
let args = Args::parse();
let results_file = Path::new("tfhe/wasm_pk_gen.csv");
let raw_results_dir = Path::new(&args.raw_results_dir);
parse_wasm_benchmarks(results_file, raw_results_dir);
}

View File

@@ -1,20 +1,25 @@
test:
test: script = test
test-ci: script = test-separate-processes
bench: script = bench
bench-ci: script = bench-separate-processes
run_server:
npm install
npm run build
# This runs the server in background, saving its PID
# to a file, so we can kill it when done
{ npm run server > /dev/null & echo $$! > server.PID; }
npm run test
npm run $(script)
kill `cat server.PID` && rm server.PID
test-ci:
npm install
npm run build
# This runs the server in background, saving its PID
# to a file, so we can kill it when done
{ npm run server > /dev/null & echo $$! > server.PID; }
npm run test-separate-processes
kill `cat server.PID` && rm server.PID
.PHONY: test # Run web client tests
test: run_server
.PHONY: test-ci # Run web client tests in CI
test-ci: run_server
.PHONY: test
.PHONY: bench # Run benchmarks on web client
bench: run_server
.PHONY: bench-ci # Run benchmarks on web client in CI
bench-ci: run_server

View File

@@ -33,6 +33,15 @@
<input type="checkbox" id="testSuccess" disabled>
<label for="testSuccess"> TestSuccess </label><br>
<input type="button" id="compactPublicKeyBench32BitSmall" value="Compact Public Key Bench 32 Bits Small" disabled />
<input type="button" id="compactPublicKeyBench32BitBig" value="Compact Public Key Bench 32 Bits Big" disabled />
<input type="button" id="compactPublicKeyBench256BitSmall" value="Compact Public Key Bench 256 Bits Small" disabled />
<input type="button" id="compactPublicKeyBench256BitBig" value="Compact Public Key Bench 256 Bits Big" disabled />
<input type="text" id="benchmarkResults" disabled>
<label for="benchmarkResults"> BenchmarkResults </label><br>
<div id="loader" class="loader" hidden></div>
</div>

View File

@@ -32,7 +32,11 @@ async function setup() {
'compactPublicKeyTest256BitBig',
'compactPublicKeyTest256BitSmall',
'compressedCompactPublicKeyTest256BitBig',
'compressedCompactPublicKeyTest256BitSmall'
'compressedCompactPublicKeyTest256BitSmall',
'compactPublicKeyBench32BitBig',
'compactPublicKeyBench32BitSmall',
'compactPublicKeyBench256BitBig',
'compactPublicKeyBench256BitSmall'
]
function setupBtn(id) {
@@ -54,8 +58,11 @@ async function setup() {
console.log("Running: ", id)
try {
await fn()
let results = await fn()
document.getElementById("testSuccess").checked = true
if (results !== undefined) {
document.getElementById("benchmarkResults").value = JSON.stringify(results);
}
} catch (error) {
console.error(`Test Failed: ${error}`)
document.getElementById("testSuccess").checked = false

View File

@@ -4,10 +4,12 @@
"description": "",
"main": "index.js",
"scripts": {
"test": "jest ./test --runInBand",
"test": "jest ./test --runInBand --testNamePattern=Test",
"bench": "jest ./test --runInBand --testNamePattern=Bench",
"build": "cp -r ../../tfhe/pkg ./ && webpack build ./index.js --mode production -o dist --output-filename index.js && cp index.html dist/ && cp favicon.ico dist/",
"server": "serve --config ../serve.json dist/",
"test-separate-processes": "jest --listTests | xargs -L 1 jest --runInBand",
"test-separate-processes": "jest --listTests | xargs -L 1 jest --runInBand --testNamePattern=Test",
"bench-separate-processes": "jest --listTests | xargs -L 1 jest --runInBand --testNamePattern=Bench",
"test2": "mocha"
},
"author": "",

View File

@@ -1,5 +1,11 @@
import puppeteer from 'puppeteer';
import process from 'process';
import * as fs from 'node:fs';
const benchmark_dir = __dirname + '/benchmark_results';
if (!fs.existsSync(benchmark_dir)){
fs.mkdirSync(benchmark_dir);
}
function isRoot() {
return process.getuid && process.getuid() === 0;
@@ -8,10 +14,14 @@ function isRoot() {
async function runActualTest(page, buttonId) {
const buttonSelector = `input#${buttonId}`
const successCheckBoxSelector = `input#testSuccess`
const benchmarkResultsSelector = `input#benchmarkResults`
const testSuccessCheckbox = await page.waitForSelector(
successCheckBoxSelector
);
const benchmarkResultsTextbox = await page.waitForSelector(
benchmarkResultsSelector
);
await page.waitForSelector(buttonSelector)
const isCheckedBefore = await testSuccessCheckbox?.evaluate(el => el.checked);
@@ -26,6 +36,12 @@ async function runActualTest(page, buttonId) {
const isCheckedAfter = await testSuccessCheckbox?.evaluate(el => el.checked);
expect(isCheckedAfter).toBe(true);
const results = await benchmarkResultsTextbox?.evaluate(el => el.value);
if (results) {
const parsed_results = JSON.parse(results);
fs.writeFileSync(`${benchmark_dir}/${buttonId}.json`, results, {'flag': 'w'});
}
}
async function runTestAttachedToButton(buttonId) {

View File

@@ -0,0 +1,18 @@
import { runTestAttachedToButton } from "./common.mjs";
it('Compact Public Key Bench Big 32 Bit', async () => {
await runTestAttachedToButton('compactPublicKeyBench32BitBig')
});
it('Compact Public Key Bench Small 32 Bit', async () => {
await runTestAttachedToButton('compactPublicKeyBench32BitSmall')
});
it('Compact Public Key Bench Big 256 Bit', async () => {
await runTestAttachedToButton('compactPublicKeyBench256BitBig')
});
it('Compact Public Key Bench Small 256 Bit', async () => {
await runTestAttachedToButton('compactPublicKeyBench256BitSmall')
});

View File

@@ -25,17 +25,25 @@ import init, {
CompactFheUint256List,
} from "./pkg/tfhe.js";
function assert(cond, text){
if( cond ) return;
if( console.assert.useDebugger ) debugger;
throw new Error(text || "Assertion failed!");
};
function assert(cond, text) {
if (cond) return;
if (console.assert.useDebugger) debugger;
throw new Error(text || "Assertion failed!");
}
function assert_eq(a, b, text){
if( a === b ) return;
if( console.assert.useDebugger ) debugger;
throw new Error(text || `Equality assertion failed!: ${a} != ${b}`);
};
function assert_eq(a, b, text) {
if (a === b) return;
if (console.assert.useDebugger) debugger;
throw new Error(text || `Equality assertion failed!: ${a} != ${b}`);
}
function append_param_name(bench_results, params_name) {
let results = {};
for (const bench_name in bench_results) {
results[`${bench_name}_${params_name}`] = bench_results[bench_name];
}
return results
}
async function compressedPublicKeyTest() {
let config = TfheConfigBuilder.all_disabled()
@@ -119,8 +127,7 @@ async function compactPublicKeyTest32BitOnConfig(config) {
assert_eq(encrypted_list.length, values.length);
for (let i = 0; i < values.length; i++)
{
for (let i = 0; i < values.length; i++) {
let decrypted = encrypted_list[i].decrypt(clientKey);
assert_eq(decrypted, values[i]);
}
@@ -133,8 +140,7 @@ async function compactPublicKeyTest32BitOnConfig(config) {
let encrypted_list = deserialized_list.expand();
assert_eq(encrypted_list.length, values.length);
for (let i = 0; i < values.length; i++)
{
for (let i = 0; i < values.length; i++) {
let decrypted = encrypted_list[i].decrypt(clientKey);
assert_eq(decrypted, values[i]);
}
@@ -156,6 +162,84 @@ async function compactPublicKeyTest32BitSmall() {
await compactPublicKeyTest32BitOnConfig(config)
}
async function compactPublicKeyBench32BitOnConfig(config) {
const bench_loops = 100;
let bench_results = {};
console.time('ClientKey Gen')
let clientKey = TfheClientKey.generate(config);
console.timeEnd('ClientKey Gen')
// Generate PK for encryption for later
console.time('CompactPublicKey Gen')
let publicKey = TfheCompactPublicKey.new(clientKey);
console.timeEnd('CompactPublicKey Gen')
// Bench the pk generation for bench_loops iterations
let start = performance.now();
for (let i = 0; i < bench_loops; i++) {
let _ = TfheCompactPublicKey.new(clientKey);
}
let end = performance.now();
const timing_1 = (end - start) / bench_loops
console.log('CompactPublicKey Gen bench: ', timing_1, ' ms');
bench_results["compact_public_key_gen_32bit_mean"] = timing_1;
let values = [0, 1, 2, 2394, U32_MAX];
// Encrypt compact CT list for serialization for later
console.time('CompactFheUint32List Encrypt')
let compact_list = CompactFheUint32List.encrypt_with_compact_public_key(values, publicKey);
console.timeEnd('CompactFheUint32List Encrypt')
// Bench the encryption for bench_loops iterations
start = performance.now();
for (let i = 0; i < bench_loops; i++) {
let _ = CompactFheUint32List.encrypt_with_compact_public_key(values, publicKey);
}
end = performance.now();
const timing_2 = (end - start) / bench_loops
console.log('CompactFheUint32List Encrypt bench: ', timing_2, ' ms');
bench_results["compact_fheunit32_list_encrypt_mean"] = timing_2;
let serialized_list = compact_list.serialize();
console.log("Serialized CompactFheUint32List size: ", serialized_list.length);
// Bench the serialization for bench_loops iterations
start = performance.now();
for (let i = 0; i < bench_loops; i++) {
let _ = compact_list.serialize();
}
end = performance.now();
const timing_3 = (end - start) / bench_loops
console.log('CompactFheUint32List serialization bench: ', timing_3, ' ms');
bench_results["compact_fheunit32_list_serialization_mean"] = timing_3;
return bench_results;
}
async function compactPublicKeyBench32BitBig() {
const block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK);
let config = TfheConfigBuilder.all_disabled()
.enable_custom_integers(block_params)
.build();
return append_param_name(
await compactPublicKeyBench32BitOnConfig(config),
"PARAM_MESSAGE_2_CARRY_2_COMPACT_PK"
);
}
async function compactPublicKeyBench32BitSmall() {
const block_params = new ShortintParameters(ShortintParametersName.PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK);
let config = TfheConfigBuilder.all_disabled()
.enable_custom_integers(block_params)
.build();
return append_param_name(
await compactPublicKeyBench32BitOnConfig(config),
"PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK"
);
}
async function compactPublicKeyTest256BitOnConfig(config) {
console.time('ClientKey Gen')
let clientKey = TfheClientKey.generate(config);
@@ -181,8 +265,7 @@ async function compactPublicKeyTest256BitOnConfig(config) {
assert_eq(encrypted_list.length, values.length);
for (let i = 0; i < values.length; i++)
{
for (let i = 0; i < values.length; i++) {
let decrypted = encrypted_list[i].decrypt(clientKey);
assert_eq(decrypted, values[i]);
}
@@ -195,8 +278,7 @@ async function compactPublicKeyTest256BitOnConfig(config) {
let encrypted_list = deserialized_list.expand();
assert_eq(encrypted_list.length, values.length);
for (let i = 0; i < values.length; i++)
{
for (let i = 0; i < values.length; i++) {
let decrypted = encrypted_list[i].decrypt(clientKey);
assert_eq(decrypted, values[i]);
}
@@ -248,8 +330,7 @@ async function compressedCompactPublicKeyTest256BitOnConfig(config) {
assert_eq(encrypted_list.length, values.length);
for (let i = 0; i < values.length; i++)
{
for (let i = 0; i < values.length; i++) {
let decrypted = encrypted_list[i].decrypt(clientKey);
assert_eq(decrypted, values[i]);
}
@@ -262,8 +343,7 @@ async function compressedCompactPublicKeyTest256BitOnConfig(config) {
let encrypted_list = deserialized_list.expand();
assert_eq(encrypted_list.length, values.length);
for (let i = 0; i < values.length; i++)
{
for (let i = 0; i < values.length; i++) {
let decrypted = encrypted_list[i].decrypt(clientKey);
assert_eq(decrypted, values[i]);
}
@@ -285,7 +365,83 @@ async function compressedCompactPublicKeyTest256BitSmall() {
await compressedCompactPublicKeyTest256BitOnConfig(config)
}
async function compactPublicKeyBench256BitOnConfig(config) {
const bench_loops = 100;
let bench_results = {}
console.time('ClientKey Gen')
let clientKey = TfheClientKey.generate(config);
console.timeEnd('ClientKey Gen')
// Generate PK for encryption for later
console.time('CompactPublicKey Gen')
let publicKey = TfheCompactPublicKey.new(clientKey);
console.timeEnd('CompactPublicKey Gen')
// Bench the pk generation for bench_loops iterations
let start = performance.now();
for (let i = 0; i < bench_loops; i++) {
let _ = TfheCompactPublicKey.new(clientKey);
}
let end = performance.now();
const timing_1 = (end - start) / bench_loops
console.log('CompactPublicKey Gen bench: ', timing_1, ' ms');
bench_results["compact_public_key_gen_256bit_mean"] = timing_1;
let values = [0, 1, 2, 2394, U32_MAX].map((e) => BigInt(e));
// Encrypt compact CT list for serialization for later
console.time('CompactFheUint256List Encrypt')
let compact_list = CompactFheUint256List.encrypt_with_compact_public_key(values, publicKey);
console.timeEnd('CompactFheUint256List Encrypt')
// Bench the encryption for bench_loops iterations
start = performance.now();
for (let i = 0; i < bench_loops; i++) {
let _ = CompactFheUint256List.encrypt_with_compact_public_key(values, publicKey);
}
end = performance.now();
const timing_2 = (end - start) / bench_loops
console.log('CompactFheUint256List Encrypt bench: ', timing_2, ' ms');
bench_results["compact_fheunit256_list_encrypt_mean"] = timing_2;
let serialized_list = compact_list.serialize();
console.log("Serialized CompactFheUint256List size: ", serialized_list.length);
// Bench the serialization for bench_loops iterations
start = performance.now();
for (let i = 0; i < bench_loops; i++) {
let _ = compact_list.serialize();
}
end = performance.now();
const timing_3 = (end - start) / bench_loops
console.log('CompactFheUint256List serialization bench: ', timing_3, ' ms');
bench_results["compact_fheunit256_list_serialization_mean"] = timing_3;
return bench_results
}
async function compactPublicKeyBench256BitBig() {
const block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK);
let config = TfheConfigBuilder.all_disabled()
.enable_custom_integers(block_params)
.build();
return append_param_name(
await compactPublicKeyBench256BitOnConfig(config),
"PARAM_MESSAGE_2_CARRY_2_COMPACT_PK"
);
}
async function compactPublicKeyBench256BitSmall() {
const block_params = new ShortintParameters(ShortintParametersName.PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK);
let config = TfheConfigBuilder.all_disabled()
.enable_custom_integers(block_params)
.build();
return append_param_name(
await compactPublicKeyBench256BitOnConfig(config),
"PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK"
);
}
async function main() {
await init()
@@ -301,6 +457,10 @@ async function main() {
compactPublicKeyTest256BitBig,
compressedCompactPublicKeyTest256BitSmall,
compressedCompactPublicKeyTest256BitBig,
compactPublicKeyBench32BitBig,
compactPublicKeyBench32BitSmall,
compactPublicKeyBench256BitBig,
compactPublicKeyBench256BitSmall,
})
}