""" utils/benchmark.py SecureLens — Performance Benchmarking Measures encryption time, inference time, decryption time. Run: python utils/benchmark.py """ import os, sys, time, json import numpy as np sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from crypto_layer.ckks_engine import CKKSEngine from cloud_server.encrypted_inference.he_inference import HEInferenceEngine MODELS_DIR = os.path.join( os.path.dirname(__file__), "..", "cloud_server", "models") RESULTS_DIR = os.path.join( os.path.dirname(__file__), "..", "docs") os.makedirs(RESULTS_DIR, exist_ok=True) N_RUNS = 10 # number of benchmark runs for averaging def benchmark_ckks(engine, n_runs=N_RUNS): """Benchmarks encrypt, multiply, decrypt operations.""" print(f"\n[Benchmark] CKKS Operations ({n_runs} runs each)") np.random.seed(42) vector = np.random.rand(512).tolist() # Encryption enc_times = [] for _ in range(n_runs): t = time.perf_counter() enc = engine.encrypt_vector(vector) enc_times.append(time.perf_counter() - t) # Scalar multiply mul_times = [] enc = engine.encrypt_vector(vector) for _ in range(n_runs): t = time.perf_counter() _ = enc * 0.5 mul_times.append(time.perf_counter() - t) # Decryption dec_times = [] for _ in range(n_runs): t = time.perf_counter() _ = engine.decrypt_vector(enc) dec_times.append(time.perf_counter() - t) # Serialization ser_times = [] for _ in range(n_runs): t = time.perf_counter() blob = engine.serialize_ciphertext(enc) ser_times.append(time.perf_counter() - t) blob = engine.serialize_ciphertext(enc) results = { "encrypt_ms" : round(np.mean(enc_times)*1000, 3), "multiply_ms" : round(np.mean(mul_times)*1000, 3), "decrypt_ms" : round(np.mean(dec_times)*1000, 3), "serialize_ms" : round(np.mean(ser_times)*1000, 3), "ciphertext_kb" : round(len(blob)/1024, 2), "plaintext_kb" : round(len(vector)*8/1024, 4), "overhead_ratio": round(len(blob)/(len(vector)*8), 1), } print(f" Encryption time : {results['encrypt_ms']} ms") print(f" Multiply time : {results['multiply_ms']} ms") print(f" Decryption time : {results['decrypt_ms']} ms") print(f" Serialization time : {results['serialize_ms']} ms") print(f" Ciphertext size : {results['ciphertext_kb']} KB") print(f" Plaintext size : {results['plaintext_kb']} KB") print(f" Overhead ratio : {results['overhead_ratio']}x") return results def benchmark_inference(engine, ckks_engine, n_runs=N_RUNS): """Benchmarks full HE inference pipeline.""" print(f"\n[Benchmark] HE Inference ({n_runs} runs)") np.random.seed(0) features = np.random.rand(512).tolist() import tenseal as ts enc = ts.ckks_vector(ckks_engine.context, features) # Layer 1: 512 → 256 l1_times = [] for _ in range(n_runs): t = time.perf_counter() h1 = engine._linear(enc, engine.W1, engine.b1, ckks_engine.context) l1_times.append(time.perf_counter() - t) # Layer 2: 256 → 2 l2_times = [] for _ in range(n_runs): t = time.perf_counter() _ = engine._linear(h1, engine.W2, engine.b2, ckks_engine.context) l2_times.append(time.perf_counter() - t) # Full pipeline full_times = [] for _ in range(n_runs): t = time.perf_counter() enc_f = ts.ckks_vector(ckks_engine.context, features) out = engine.infer_head(enc_f, ckks_engine.context) _ = ckks_engine.decrypt_prediction(out) full_times.append(time.perf_counter() - t) # Plaintext baseline plain_times = [] W1 = engine.W1 b1 = engine.b1 W2 = engine.W2 b2 = engine.b2 f = np.array(features) for _ in range(n_runs): t = time.perf_counter() h1 = W1 @ f + b1 h1 = np.maximum(h1, 0) _ = W2 @ h1 + b2 plain_times.append(time.perf_counter() - t) results = { "layer1_ms" : round(np.mean(l1_times)*1000, 3), "layer2_ms" : round(np.mean(l2_times)*1000, 3), "full_pipeline_ms": round(np.mean(full_times)*1000, 3), "plaintext_ms" : round(np.mean(plain_times)*1000, 4), "overhead_factor": round( np.mean(full_times)/max(np.mean(plain_times), 1e-9), 1), } print(f" Layer 1 (512→256) : {results['layer1_ms']} ms") print(f" Layer 2 (256→2) : {results['layer2_ms']} ms") print(f" Full pipeline : {results['full_pipeline_ms']} ms") print(f" Plaintext baseline : {results['plaintext_ms']} ms") print(f" Overhead factor : {results['overhead_factor']}x") return results def benchmark_memory(): """Measures memory usage of key objects.""" print("\n[Benchmark] Memory Usage") import sys as _sys np.random.seed(42) features = np.random.rand(512) plain_size = features.nbytes import tenseal as ts ctx = ts.context( ts.SCHEME_TYPE.CKKS, poly_modulus_degree=8192, coeff_mod_bit_sizes=[60,40,40,60]) ctx.generate_galois_keys() ctx.generate_relin_keys() ctx.global_scale = 2**40 enc = ts.ckks_vector(ctx, features.tolist()) enc_blob = enc.serialize() ctx_blob = ctx.serialize(save_secret_key=True) results = { "plaintext_bytes" : int(plain_size), "ciphertext_bytes" : int(len(enc_blob)), "context_bytes" : int(len(ctx_blob)), "overhead_ratio" : round(len(enc_blob)/plain_size, 1), "plaintext_kb" : round(plain_size/1024, 3), "ciphertext_kb" : round(len(enc_blob)/1024, 2), "context_kb" : round(len(ctx_blob)/1024, 2), } print(f" Plaintext : {results['plaintext_kb']} KB") print(f" Ciphertext : {results['ciphertext_kb']} KB " f"({results['overhead_ratio']}x overhead)") print(f" Context : {results['context_kb']} KB") return results def benchmark_accuracy(engine, ckks_engine): """Verifies FHE produces same result as plaintext.""" print("\n[Benchmark] Accuracy / Correctness Verification") np.random.seed(123) n_tests = 100 errors = [] matches = 0 for i in range(n_tests): feat = np.random.randn(512) * 0.5 # Plaintext h1p = engine.W1 @ feat + engine.b1 h1p = np.maximum(h1p, 0) out_p = engine.W2 @ h1p + engine.b2 pred_p = "Normal" if out_p[0] > out_p[1] else "Pneumonia" # FHE import tenseal as ts enc = ts.ckks_vector(ckks_engine.context, feat.tolist()) enc_out = engine.infer_head(enc, ckks_engine.context) result = ckks_engine.decrypt_prediction(enc_out) pred_f = result["prediction"] decrypted = np.array(enc_out.decrypt()[:2]) error = np.max(np.abs(decrypted - out_p[:2])) errors.append(error) if pred_p == pred_f: matches += 1 results = { "n_tests" : n_tests, "prediction_match": matches, "match_rate_pct" : round(matches/n_tests*100, 1), "mean_error" : float(f"{np.mean(errors):.2e}"), "max_error" : float(f"{np.max(errors):.2e}"), "min_error" : float(f"{np.min(errors):.2e}"), } print(f" Tests run : {n_tests}") print(f" Prediction match: {matches}/{n_tests} " f"({results['match_rate_pct']}%)") print(f" Mean CKKS error : {results['mean_error']}") print(f" Max CKKS error : {results['max_error']}") return results def main(): print("="*55) print(" SecureLens — Performance Benchmark Suite") print("="*55) print("\n[Init] Loading CKKS Engine...") ckks_engine = CKKSEngine( poly_modulus_degree=8192, coeff_mod_bit_sizes=[60,40,40,60], global_scale=2**40) print("[Init] Loading HE Inference Engine...") he_engine = HEInferenceEngine(MODELS_DIR) # Run all benchmarks ckks_results = benchmark_ckks(ckks_engine) infer_results = benchmark_inference(he_engine, ckks_engine) memory_results = benchmark_memory() accuracy_results = benchmark_accuracy(he_engine, ckks_engine) # Combine all results all_results = { "model" : "SecureLensNet (ResNet-18 + Linear HE Head)", "dataset" : "Chest X-Ray (Kaggle) — 5856 images", "test_acc" : "89.42%", "ckks_params": { "scheme" : "CKKS", "library" : "TenSEAL 0.3.14", "poly_modulus_degree": 8192, "coeff_mod_bit_sizes": [60,40,40,60], "global_scale" : "2^40", "security_bits" : 128, }, "ckks_operations" : ckks_results, "inference" : infer_results, "memory" : memory_results, "accuracy" : accuracy_results, } # Save results out_path = os.path.join(RESULTS_DIR, "benchmark_results.json") with open(out_path, "w") as f: json.dump(all_results, f, indent=2) print(f"\n[Saved] Results → {out_path}") # Print summary print("\n" + "="*55) print(" BENCHMARK SUMMARY") print("="*55) print(f" Encryption time : {ckks_results['encrypt_ms']} ms") print(f" Inference time (FHE) : " f"{infer_results['full_pipeline_ms']} ms") print(f" Decryption time : {ckks_results['decrypt_ms']} ms") print(f" Total latency : " f"{ckks_results['encrypt_ms'] + infer_results['full_pipeline_ms'] + ckks_results['decrypt_ms']:.1f} ms") print(f" Ciphertext size : {ckks_results['ciphertext_kb']} KB") print(f" Prediction match rate: " f"{accuracy_results['match_rate_pct']}%") print(f" Max CKKS error : {accuracy_results['max_error']}") print("\n✅ Benchmark complete.") if __name__ == "__main__": main()