mirror of
https://github.com/luxfhe/ml-sdk.git
synced 2026-10-30 18:13:04 +00:00
Brand-only sweep. Touches READMEs, docs, package metadata, comments,
notebook markdown, requirements.txt files; does NOT yet touch Python
`concrete.*` imports (those are module-level dependencies requiring the
phase-2 Python module port from `concrete` → `torus`).
Substitutions applied:
Concrete ML → TorusML
concrete-ml → torus-ml
Concrete-ML → Torus-ML
concrete_ml → torus_ml
zama-ai/concrete-ml → luxfhe/ml-sdk
zama-ai/concrete → luxfhe/torus
github.com/zama-ai → github.com/luxfhe
docs.zama.ai/concrete-ml → docs.luxfhe.com/ml-sdk
docs.zama.ai/concrete → docs.luxfhe.com/torus
Zama Concrete ML → Lux TorusML
Zama Concrete → Lux Torus
Zama tfhe-rs / Zama TFHE-rs → Lux-FHE
tfhe-rs / TFHE-rs → Lux-FHE
Zama fhEVM / fhEVM → TorusEVM
Concrete framework → Torus framework
Concrete compiler → Torus compiler
Concrete by Zama → Torus by Lux
the Zama team → the Lux Industries team
Zama's → Lux Industries'
(Zama) → (Lux)
CoFHE / Fhenix → (removed)
LICENSE: original Zama copyright RETAINED per BSD-3-Clause-Clear §1-§2
requirements; Lux Industries modifications copyright added. Per §3 no
name of ZAMA used to endorse the derived work — product marketed
exclusively as TorusML.
README image URLs migrated to github.com/luxfhe/ml-sdk/assets to break
the dependency on Zama's CDN.
Hit count: 350 → 295. Residual hits are Python `from concrete.fhe import`
imports referencing the upstream Zama `concrete` PyPI package; these
require module-level port (phase 2):
Phase 2 plan:
1. Fork zama-ai/concrete (the Python compiler) to luxfhe/torus
2. Rename Python module `concrete` → `torus`; PyPI publish as `torus-fhe`
3. Update ml-sdk imports from `concrete.fhe` → `torus.fhe`, etc.
4. Wire `torus.compiler` GPU dispatch through `luxcpp/gpu` vtbl
(lux-private/gpu-kernels for CUDA/Metal/WebGPU/FPGA targets)
5. Port concrete-cuda + concrete-mlir kernels to
lux-private/gpu-kernels/{cuda,metal,wgsl}/ckks/ (matrix-vector
mul, rescale, rotate, bootstrap for TorusML inference)
Phase 3 plan (kernels):
- CKKS/BGV/BFV GPU kernels in lux-private/gpu-kernels/
(currently only TFHE PBS / keyswitch / blind-rotate exist)
- FPGA bitstreams in lux-private/fpga/ (currently scaffold only)
Phase 1 = docs/branding sweep. PR not opened; user reviews and merges.
607 lines
21 KiB
Python
607 lines
21 KiB
Python
"""
|
|
ResNet18 Hybrid FHE implementation using HybridFHEModel.
|
|
|
|
This script demonstrates how to run ResNet18 with specific layers executing in FHE mode
|
|
while others remain in clear text. All linear and conv layers are executed in FHE.
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import platform
|
|
import socket
|
|
import time
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
import cpuinfo
|
|
import psutil
|
|
import torch
|
|
from resnet import ResNet18_Weights, resnet18_custom
|
|
from utils_resnet import ImageNetProcessor
|
|
|
|
from concrete.ml.torch.hybrid_model import HybridFHEModel
|
|
|
|
BASE_DIR = Path(__file__).resolve().parent
|
|
CALIBRATION_SAMPLES = 10
|
|
NUM_TEST_SAMPLES = 20
|
|
|
|
|
|
def get_system_info():
|
|
"""Gather system information for metrics."""
|
|
cpu_info_data = cpuinfo.get_cpu_info()
|
|
|
|
return {
|
|
"platform": platform.platform(),
|
|
"python_version": platform.python_version(),
|
|
"torch_version": torch.__version__,
|
|
"cpu": {
|
|
"name": cpu_info_data.get("brand_raw", "Unknown"),
|
|
"count": psutil.cpu_count(logical=False),
|
|
"threads": psutil.cpu_count(logical=True),
|
|
"memory_gb": psutil.virtual_memory().total // (1024**3),
|
|
},
|
|
"device": "cpu",
|
|
}
|
|
|
|
|
|
def load_model():
|
|
"""Load the ResNet18 model with pre-trained weights."""
|
|
return resnet18_custom(weights=ResNet18_Weights.IMAGENET1K_V1)
|
|
|
|
|
|
def load_data(dataset_cache_dir=None):
|
|
"""Load the ImageNet data processor and calibration data."""
|
|
print(f"Creating ImageNetProcessor with cache_dir: {dataset_cache_dir}")
|
|
processor = ImageNetProcessor(
|
|
NUM_TEST_SAMPLES, CALIBRATION_SAMPLES, cache_dir=dataset_cache_dir
|
|
)
|
|
|
|
calib_images = processor.get_calibration_tensor()
|
|
|
|
if calib_images is None:
|
|
raise ValueError("get_calibration_tensor() returned None")
|
|
if len(calib_images) == 0:
|
|
raise ValueError(
|
|
f"get_calibration_tensor() returned empty tensor with shape {calib_images.shape}"
|
|
)
|
|
|
|
print(f"Loaded calibration data: shape={calib_images.shape}, dtype={calib_images.dtype}")
|
|
return processor, calib_images
|
|
|
|
|
|
def create_hybrid_model(model, target_modules):
|
|
"""
|
|
Create a hybrid FHE model with specified modules running in FHE.
|
|
|
|
Args:
|
|
model: PyTorch model
|
|
target_modules: List of module names to execute in FHE
|
|
|
|
Returns:
|
|
HybridFHEModel instance
|
|
"""
|
|
print("\nCreating HybridFHEModel...")
|
|
hybrid_model = HybridFHEModel(
|
|
model=model,
|
|
module_names=target_modules,
|
|
model_name="resnet18_hybrid",
|
|
)
|
|
return hybrid_model
|
|
|
|
|
|
def compile_hybrid_model(hybrid_model, calibration_data):
|
|
"""
|
|
Compile the hybrid model for FHE execution.
|
|
|
|
Args:
|
|
hybrid_model: HybridFHEModel instance
|
|
calibration_data: Calibration tensor data
|
|
|
|
Returns:
|
|
Compilation time in seconds
|
|
"""
|
|
print("\nCompiling hybrid model for FHE...")
|
|
start_time = time.time()
|
|
|
|
hybrid_model.compile_model(x=calibration_data, n_bits=8, device="cpu") # 8-bit quantization
|
|
|
|
compile_time = time.time() - start_time
|
|
print(f"\nCompilation completed in {compile_time:.2f} seconds")
|
|
return compile_time
|
|
|
|
|
|
def run_single_fhe_inference(hybrid_model, test_data, num_samples=1):
|
|
"""
|
|
Run inference in FHE mode and measure timing.
|
|
|
|
Args:
|
|
hybrid_model: Compiled HybridFHEModel
|
|
test_data: Test tensor data
|
|
num_samples: Number of samples to process
|
|
|
|
Returns:
|
|
Dictionary with results and timing information
|
|
"""
|
|
print(f"\nRunning inference on {num_samples} sample(s)...")
|
|
|
|
# Validate inputs
|
|
if test_data is None or len(test_data) == 0:
|
|
print("ERROR: Test data is None or empty!")
|
|
return {}
|
|
|
|
if num_samples > len(test_data):
|
|
print(f"WARNING: Requested {num_samples} samples but only {len(test_data)} available")
|
|
num_samples = len(test_data)
|
|
|
|
# Use specified number of samples
|
|
test_subset = test_data[:num_samples]
|
|
print(f"Test subset shape: {test_subset.shape}")
|
|
|
|
def get_top5_labels(output):
|
|
"""Extract top 5 predicted labels."""
|
|
return torch.topk(output, 5, dim=1)[1].cpu().numpy()
|
|
|
|
results = {}
|
|
|
|
# Test clear mode first
|
|
print(f"\n--- Running in CLEAR mode ---")
|
|
try:
|
|
total_clear_time = 0
|
|
clear_predictions = []
|
|
|
|
for i in range(num_samples):
|
|
sample = test_subset[i : i + 1]
|
|
start_time = time.time()
|
|
with torch.no_grad():
|
|
output_clear = hybrid_model(sample, fhe="disable")
|
|
clear_time = time.time() - start_time
|
|
total_clear_time += clear_time
|
|
|
|
top5_clear = get_top5_labels(output_clear)
|
|
clear_predictions.append(top5_clear[0])
|
|
|
|
print(f" Sample {i+1}: {clear_time:.4f}s, Top-5: {top5_clear[0]}")
|
|
|
|
avg_clear_time = total_clear_time / num_samples
|
|
results["clear"] = {
|
|
"total_time": total_clear_time,
|
|
"avg_time": avg_clear_time,
|
|
"predictions": clear_predictions,
|
|
}
|
|
|
|
print(f" Total clear time: {total_clear_time:.4f}s")
|
|
print(f" Average clear time: {avg_clear_time:.4f}s")
|
|
|
|
except Exception as e:
|
|
print(f" Error in clear mode: {type(e).__name__}: {str(e)}")
|
|
import traceback
|
|
|
|
traceback.print_exc()
|
|
|
|
# Test FHE execute mode
|
|
print(f"\n--- Running in FHE EXECUTE mode ---")
|
|
print("\n⚠️ IMPORTANT: The first FHE inference run will include key generation time")
|
|
print(" Subsequent runs will be faster as they reuse the generated keys.")
|
|
print(" This is normal behavior and only affects the first run.")
|
|
|
|
try:
|
|
total_fhe_time = 0
|
|
fhe_predictions = []
|
|
|
|
for i in range(num_samples):
|
|
sample = test_subset[i : i + 1]
|
|
print(f" Starting FHE execution for sample {i+1}/{num_samples}...")
|
|
start_time = time.time()
|
|
with torch.no_grad():
|
|
output_fhe = hybrid_model(sample, fhe="execute")
|
|
fhe_time = time.time() - start_time
|
|
total_fhe_time += fhe_time
|
|
|
|
top5_fhe = get_top5_labels(output_fhe)
|
|
fhe_predictions.append(top5_fhe[0])
|
|
|
|
print(f" ✅ Sample {i+1} completed: {fhe_time:.4f}s, Top-5: {top5_fhe[0]}")
|
|
print(f" Elapsed total time: {total_fhe_time:.4f}s")
|
|
|
|
avg_fhe_time = total_fhe_time / num_samples
|
|
results["fhe"] = {
|
|
"total_time": total_fhe_time,
|
|
"avg_time": avg_fhe_time,
|
|
"predictions": fhe_predictions,
|
|
}
|
|
|
|
print(f" Total FHE time: {total_fhe_time:.4f}s")
|
|
print(f" Average FHE time: {avg_fhe_time:.4f}s")
|
|
|
|
except Exception as e:
|
|
print(f" Error in FHE execute mode: {type(e).__name__}: {str(e)}")
|
|
import traceback
|
|
|
|
traceback.print_exc()
|
|
|
|
# Compare results
|
|
print(f"\n--- Results Summary ---")
|
|
if "clear" in results and "fhe" in results:
|
|
clear_time = results["clear"]["avg_time"]
|
|
fhe_time = results["fhe"]["avg_time"]
|
|
overhead = fhe_time / clear_time if clear_time > 0 else float("inf")
|
|
|
|
print(f"Clear execution time: {clear_time:.4f}s per sample")
|
|
print(f"FHE execution time: {fhe_time:.4f}s per sample")
|
|
print(f"FHE overhead: {overhead:.1f}x")
|
|
|
|
# Check prediction consistency
|
|
correct_predictions = sum(
|
|
1
|
|
for i in range(num_samples)
|
|
if results["clear"]["predictions"][i][0] == results["fhe"]["predictions"][i][0]
|
|
)
|
|
consistency = correct_predictions / num_samples * 100
|
|
|
|
print(
|
|
f"Top-1 prediction consistency: {consistency:.1f}% ({correct_predictions}/{num_samples})"
|
|
)
|
|
|
|
elif "fhe" in results:
|
|
print(f"FHE execution time: {results['fhe']['avg_time']:.4f}s per sample")
|
|
else:
|
|
print("No successful inference results")
|
|
|
|
return results
|
|
|
|
|
|
def evaluate_model_accuracy(hybrid_model, processor, mode="disable"):
|
|
"""
|
|
Evaluate model accuracy on the full test set.
|
|
|
|
Args:
|
|
hybrid_model: HybridFHEModel instance
|
|
processor: ImageNetProcessor
|
|
mode: FHE mode for evaluation
|
|
|
|
Returns:
|
|
Tuple of (top1_accuracy, top5_accuracy)
|
|
"""
|
|
print(f"\nEvaluating model accuracy in {mode} mode...")
|
|
|
|
total_correct = 0
|
|
total_top5_correct = 0
|
|
total_samples = 0
|
|
|
|
with torch.no_grad():
|
|
for batch in processor.dataloader:
|
|
batch_images = batch["pixel_values"]
|
|
batch_labels = batch["labels"]
|
|
|
|
# Run inference
|
|
outputs = hybrid_model(batch_images, fhe=mode)
|
|
|
|
# Calculate top-1 and top-5 accuracy
|
|
_, top1_pred = torch.max(outputs, 1)
|
|
_, top5_pred = torch.topk(outputs, 5, dim=1)
|
|
|
|
total_correct += (top1_pred == batch_labels).sum().item()
|
|
|
|
# Check if true label is in top-5
|
|
for i, label in enumerate(batch_labels):
|
|
if label in top5_pred[i]:
|
|
total_top5_correct += 1
|
|
|
|
total_samples += batch_labels.size(0)
|
|
|
|
top1_accuracy = (total_correct / total_samples) * 100
|
|
top5_accuracy = (total_top5_correct / total_samples) * 100
|
|
|
|
print(f"Top-1 Accuracy ({mode}): {top1_accuracy:.2f}%")
|
|
print(f"Top-5 Accuracy ({mode}): {top5_accuracy:.2f}%")
|
|
|
|
return top1_accuracy, top5_accuracy
|
|
|
|
|
|
def save_metrics(
|
|
results, system_info, compilation_time, target_modules, args, accuracy_results=None
|
|
):
|
|
"""Save benchmark metrics in the expected format for the database."""
|
|
|
|
# Get git information (placeholder for now)
|
|
git_info = {"hash": "unknown_resnet_hash", "timestamp": datetime.utcnow().timestamp()}
|
|
|
|
# 1. Prepare the 'machine' part
|
|
machine_data = {
|
|
"machine_name": socket.gethostname(),
|
|
"machine_specs": system_info,
|
|
}
|
|
|
|
# 2. Prepare the 'experiment_metadata' part
|
|
experiment_metadata = {
|
|
"num_samples": args.num_samples,
|
|
"target_all_layers": args.target_all_layers,
|
|
"evaluate_accuracy": args.evaluate_accuracy,
|
|
"save_model": args.save_model,
|
|
"num_fhe_layers": len(target_modules),
|
|
"fhe_layers_targeted": target_modules,
|
|
"debug_mode": args.debug_mode,
|
|
"device_used": system_info.get("device", "cpu"),
|
|
"framework": "torus-ml",
|
|
"model": "resnet18",
|
|
"task": "image_classification",
|
|
"dataset": "imagenet",
|
|
"quantization_bits": 8,
|
|
"run_type": "benchmark",
|
|
}
|
|
|
|
# 3. Prepare the 'metrics' list
|
|
metrics_list = []
|
|
|
|
# Add compilation time
|
|
metrics_list.append({"metric_name": "compilation_time_seconds", "value": compilation_time})
|
|
|
|
# Add timing results
|
|
if "clear" in results:
|
|
metrics_list.append(
|
|
{"metric_name": "clear_total_time_seconds", "value": results["clear"]["total_time"]}
|
|
)
|
|
metrics_list.append(
|
|
{
|
|
"metric_name": "clear_avg_time_per_sample_seconds",
|
|
"value": results["clear"]["avg_time"],
|
|
}
|
|
)
|
|
|
|
if "fhe" in results:
|
|
metrics_list.append(
|
|
{"metric_name": "fhe_total_time_seconds", "value": results["fhe"]["total_time"]}
|
|
)
|
|
metrics_list.append(
|
|
{"metric_name": "fhe_avg_time_per_sample_seconds", "value": results["fhe"]["avg_time"]}
|
|
)
|
|
|
|
# Calculate overhead if both clear and FHE results exist
|
|
if "clear" in results and results["clear"]["avg_time"] > 0:
|
|
overhead = results["fhe"]["avg_time"] / results["clear"]["avg_time"]
|
|
metrics_list.append({"metric_name": "fhe_overhead_factor", "value": overhead})
|
|
|
|
# Calculate prediction consistency
|
|
if "predictions" in results["clear"] and "predictions" in results["fhe"]:
|
|
correct = sum(
|
|
1
|
|
for i in range(args.num_samples)
|
|
if results["clear"]["predictions"][i][0] == results["fhe"]["predictions"][i][0]
|
|
)
|
|
consistency = (correct / args.num_samples) * 100
|
|
metrics_list.append(
|
|
{"metric_name": "top1_prediction_consistency_percent", "value": consistency}
|
|
)
|
|
|
|
# Add performance per FHE layer
|
|
if len(target_modules) > 0:
|
|
metrics_list.append(
|
|
{
|
|
"metric_name": "avg_time_per_fhe_layer_seconds",
|
|
"value": results["fhe"]["avg_time"] / len(target_modules),
|
|
}
|
|
)
|
|
|
|
# Add accuracy results if available
|
|
if accuracy_results:
|
|
for mode, acc_data in accuracy_results.items():
|
|
if "top1" in acc_data:
|
|
metrics_list.append(
|
|
{"metric_name": f"accuracy_{mode}_top1_percent", "value": acc_data["top1"]}
|
|
)
|
|
if "top5" in acc_data:
|
|
metrics_list.append(
|
|
{"metric_name": f"accuracy_{mode}_top5_percent", "value": acc_data["top5"]}
|
|
)
|
|
|
|
# 4. Build the complete structure
|
|
session_data = {
|
|
"machine": machine_data,
|
|
"experiments": [
|
|
{
|
|
"experiment_name": "resnet18_hybrid_fhe_benchmark",
|
|
"experiment_metadata": experiment_metadata,
|
|
"git_hash": git_info["hash"],
|
|
"git_timestamp": git_info["timestamp"],
|
|
"experiment_timestamp": datetime.utcnow().timestamp(),
|
|
"metrics": metrics_list,
|
|
}
|
|
],
|
|
}
|
|
|
|
# Save to file
|
|
output_file = BASE_DIR / "to_upload.json"
|
|
with open(output_file, "w") as f:
|
|
json.dump(session_data, f, indent=2)
|
|
|
|
print(f"\nMetrics saved to {output_file}")
|
|
return session_data
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Run ResNet18 with hybrid FHE execution")
|
|
parser.add_argument(
|
|
"--dataset_cache_dir",
|
|
type=str,
|
|
default=None,
|
|
help="Path to the directory where the dataset is cached",
|
|
)
|
|
parser.add_argument(
|
|
"--num_samples",
|
|
type=int,
|
|
default=1,
|
|
help="Number of samples for inference timing comparison (default: 1)",
|
|
)
|
|
parser.add_argument(
|
|
"--evaluate_accuracy", action="store_true", help="Evaluate model accuracy on full test set"
|
|
)
|
|
parser.add_argument("--save_model", action="store_true", help="Save the compiled hybrid model")
|
|
parser.add_argument(
|
|
"--target_all_layers",
|
|
action="store_true",
|
|
help="Target all conv and linear layers instead of just linear layers",
|
|
)
|
|
parser.add_argument(
|
|
"--debug_mode",
|
|
action="store_true",
|
|
help="Only select the first non-conv1 layer for FHE execution",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
print("=== ResNet18 Hybrid FHE Execution ===")
|
|
print(
|
|
"\n⚠️ NOTE: The first convolutional layer ('conv1') is currently excluded from FHE execution"
|
|
)
|
|
|
|
# Get system information
|
|
system_info = get_system_info()
|
|
print(f"\nRunning on CPU")
|
|
print(f"CPU: {system_info['cpu']['name']}")
|
|
print(
|
|
f"CPU Cores: {system_info['cpu']['count']} physical, {system_info['cpu']['threads']} logical"
|
|
)
|
|
print(f"Memory: {system_info['cpu']['memory_gb']} GB")
|
|
|
|
# Load model and data
|
|
print("\nLoading model and data...")
|
|
resnet18 = load_model()
|
|
processor, calib_images = load_data(args.dataset_cache_dir)
|
|
|
|
print(f"\nDebug: Calibration tensor shape: {calib_images.shape}")
|
|
print(f"Debug: Calibration tensor dtype: {calib_images.dtype}")
|
|
print(f"Debug: Calibration tensor device: {calib_images.device}")
|
|
|
|
# Select all Conv2d and Linear layers except 'conv1'
|
|
print("\nSelecting layers for FHE execution...")
|
|
target_modules = []
|
|
for name, module in resnet18.named_modules():
|
|
if name != "conv1" and (
|
|
isinstance(module, torch.nn.Conv2d) or isinstance(module, torch.nn.Linear)
|
|
):
|
|
target_modules.append(name)
|
|
print(f" ✓ Adding layer: {name} ({type(module).__name__})")
|
|
if args.debug_mode:
|
|
break
|
|
|
|
print(f"\nSelected {len(target_modules)} target modules for FHE execution:")
|
|
for i, module_name in enumerate(target_modules, 1):
|
|
print(f" {i}. {module_name}")
|
|
|
|
# Create hybrid model
|
|
hybrid_model = create_hybrid_model(resnet18, target_modules)
|
|
|
|
# Compile for FHE
|
|
compilation_time = compile_hybrid_model(hybrid_model, calib_images)
|
|
|
|
# Run FHE inference BEFORE saving the model
|
|
test_data = processor.get_calibration_tensor()
|
|
print(f"\nDebug: Test data shape: {test_data.shape}")
|
|
print(f"Debug: Test data dtype: {test_data.dtype}")
|
|
print(f"Debug: Test data min/max: {test_data.min():.3f}/{test_data.max():.3f}")
|
|
|
|
results = run_single_fhe_inference(hybrid_model, test_data, args.num_samples)
|
|
|
|
# Evaluate accuracy if requested
|
|
accuracy_results = {}
|
|
if args.evaluate_accuracy:
|
|
for mode in ["disable", "simulate"]:
|
|
top1, top5 = evaluate_model_accuracy(hybrid_model, processor, mode)
|
|
accuracy_results[mode] = {"top1": top1, "top5": top5}
|
|
|
|
# Save model AFTER inference is complete
|
|
if args.save_model:
|
|
save_path = BASE_DIR / "compiled_hybrid_model"
|
|
save_path.mkdir(exist_ok=True, parents=True)
|
|
print(f"\nSaving compiled model to {save_path}...")
|
|
try:
|
|
hybrid_model.save_and_clear_private_info(save_path, via_mlir=True)
|
|
print("Model saved successfully!")
|
|
# Verify the save
|
|
if save_path.exists() and any(save_path.iterdir()):
|
|
print(f"Verified: Model directory contains {len(list(save_path.iterdir()))} files")
|
|
else:
|
|
print("WARNING: Model directory appears to be empty after save")
|
|
except Exception as e:
|
|
print(f"Error saving model: {type(e).__name__}: {str(e)}")
|
|
import traceback
|
|
|
|
traceback.print_exc()
|
|
|
|
# Save metrics for the benchmark database
|
|
metrics = save_metrics(
|
|
results, system_info, compilation_time, target_modules, args, accuracy_results
|
|
)
|
|
|
|
# Print comprehensive final statistics
|
|
print("\n" + "=" * 60)
|
|
print(" FINAL EXECUTION STATISTICS")
|
|
print("=" * 60)
|
|
|
|
# FHE Configuration Summary
|
|
print(f"FHE Configuration:")
|
|
print(f" • Number of layers executed in FHE: {len(target_modules)}")
|
|
print(f" • FHE layer types: ALL Linear + Conv2d layers")
|
|
print(f" • Number of samples processed: {args.num_samples}")
|
|
|
|
# Timing Results
|
|
if "fhe" in results and "clear" in results:
|
|
fhe_total = results["fhe"]["total_time"]
|
|
fhe_avg = results["fhe"]["avg_time"]
|
|
clear_total = results["clear"]["total_time"]
|
|
clear_avg = results["clear"]["avg_time"]
|
|
overhead = fhe_avg / clear_avg if clear_avg > 0 else float("inf")
|
|
|
|
print(f"\nTiming Results:")
|
|
print(f" • Total FHE execution time: {fhe_total:.4f}s")
|
|
print(f" • Average FHE time per sample: {fhe_avg:.4f}s")
|
|
print(f" • Total clear execution time: {clear_total:.4f}s")
|
|
print(f" • Average clear time per sample: {clear_avg:.4f}s")
|
|
print(f" • FHE overhead factor: {overhead:.1f}x")
|
|
|
|
# Performance per FHE layer
|
|
time_per_fhe_layer = fhe_avg / len(target_modules) if len(target_modules) > 0 else 0
|
|
print(f" • Average time per FHE layer: {time_per_fhe_layer:.4f}s")
|
|
|
|
elif "fhe" in results:
|
|
fhe_total = results["fhe"]["total_time"]
|
|
fhe_avg = results["fhe"]["avg_time"]
|
|
print(f"\nTiming Results:")
|
|
print(f" • **TOTAL FHE EXECUTION TIME: {fhe_total:.4f}s**")
|
|
print(f" • Average FHE time per sample: {fhe_avg:.4f}s")
|
|
|
|
# Performance per FHE layer
|
|
time_per_fhe_layer = fhe_avg / len(target_modules) if len(target_modules) > 0 else 0
|
|
print(f" • Average time per FHE layer: {time_per_fhe_layer:.4f}s")
|
|
else:
|
|
print(f"\nTiming Results:")
|
|
print(f" • ❌ No FHE execution results available")
|
|
|
|
# Accuracy Results
|
|
if "fhe" in results and "clear" in results:
|
|
correct_predictions = sum(
|
|
1
|
|
for i in range(args.num_samples)
|
|
if results["clear"]["predictions"][i][0] == results["fhe"]["predictions"][i][0]
|
|
)
|
|
consistency = correct_predictions / args.num_samples * 100
|
|
|
|
print(f"\nAccuracy Results:")
|
|
print(f" • Top-1 prediction consistency: {consistency:.1f}%")
|
|
print(f" • Correct predictions: {correct_predictions}/{args.num_samples}")
|
|
if consistency == 100:
|
|
print(f" • ✅ Perfect accuracy maintained in FHE mode")
|
|
else:
|
|
print(f" • ⚠️ Some accuracy loss in FHE mode")
|
|
|
|
print("\n" + "=" * 60)
|
|
|
|
# Exit with error if no results
|
|
if not results:
|
|
print("\nERROR: No inference results obtained. Exiting with error code.")
|
|
exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|