SecureLens / utils /benchmark.py
Your Name
Initial commit: SecureLens privacy-preserving medical AI
9e00302
Raw
History Blame Contribute Delete
10.1 kB
"""
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()