mirror of
https://github.com/luxfhe/ml-sdk.git
synced 2026-10-30 18:13:04 +00:00
Files edited: 150 Brand-hit files before: 163 Brand-hit files after: 28 Substitutions: - Zama Concrete ML -> TorusML - Zama Concrete / Concrete-> Torus - Zama fhEVM / fhEVM -> TorusEVM - Zama TFHE-rs / tfhe-rs -> Lux-FHE (github.com/luxfi/fhe) - Zama (attribution) -> Lux Industries - zama-ai/* (GitHub) -> luxfhe/* - @zama-fhe/* (npm) -> @luxfhe/* - docs.zama.ai -> docs.lux.network - Fhenix / CoFHE -> dropped or rewritten to Lux FHE / TorusEVM coprocessor Text-only changes. No runtime code semantics altered.
77 KiB
77 KiB
In [1]:
import os
import random
import time
import warnings
import numpy as np
import torch
from torchvision import datasets, transforms
from utils_experiments import MEAN, STD, format_results_df, plot_dataset, torch_inference
from concrete.ml.torch.compile import compile_torch_model
warnings.filterwarnings("ignore", category=UserWarning)In [2]:
DEVICE = "cpu"
# Input size, 28x28 pixels, a standard size for MNIST images
INPUT_IMG_SIZE = 28
# Batch size
BATCH_SIZE = 64
# Seed to ensure reproducibility
SEED = 42
# Whether the experiments are run on PC or other machines, like HP7C on AWS
MACHINE = f"{os.cpu_count()}-core machine"
# The timing and the accuracy recorded in the article
if os.cpu_count() > 48:
PAPER_NOTES = {20: [21.17, 0.971], 50: [43.91, 0.947]}
else:
PAPER_NOTES = {20: [115.52, 0.971], 50: [233.55, 0.947]}In [3]:
FEATURES_MAPS = [
# Convolution layer, with:
# in_channel=1, out_channels=1, kernel_size=3, stride=1, padding_mode='replicate'
("C", 1, 1, 3, 1, "replicate"),
("R",),
("B", 1, 30), # 2d batch-norm for 1 channel
]
# The article presents 3 neural network depths. In this notebook, we focus NN-20 and NN-50
# architectures. The parameter `nb_layers`: controls the depth of the NN.
def LINEAR_LAYERS(nb_layers: int, output_size: int):
return ( # noqa: W503
[
("L", INPUT_IMG_SIZE * INPUT_IMG_SIZE, 92),
("R",),
("B", 92), # 1d batch norm
] # noqa: W503
+ [ # noqa: W503
("L", 92, 92),
("R",),
("B", 92), # 1d batch norm
]
* (nb_layers - 3) # noqa: W503
+ [("L", 92, output_size)] # noqa: W503
)
class Fp32MNIST(torch.nn.Module):
"""MNIST Torch model."""
def __init__(self, nb_layers: int, output_size: int = 10):
"""MNIST Torch model.
Args:
nb_layers (int): Number of layers.
output_size (int): Number of classes.
"""
super().__init__()
self.nb_layers = nb_layers
self.output_size = output_size
def make_layers(t):
if t[0] == "C":
# Workaround: stride=1, padding_mode='replicate' is replaced by
# transforms.Pad(1, padding_mode="edge")
return torch.nn.Conv2d(
in_channels=t[1],
out_channels=t[2],
kernel_size=t[3],
)
if t[0] == "L":
return torch.nn.Linear(in_features=t[1], out_features=t[2])
if t[0] == "R":
return torch.nn.ReLU()
if t[0] == "B":
if len(t) == 2:
return torch.nn.BatchNorm1d(t[1])
if len(t) == 3:
return torch.nn.BatchNorm2d(t[1])
raise NameError(f"'{t}' not defined")
self.features_maps = torch.nn.Sequential(*[make_layers(t) for t in FEATURES_MAPS])
self.linears = torch.nn.Sequential(
*[make_layers(t) for t in LINEAR_LAYERS(self.nb_layers, self.output_size)]
)
def forward(self, x):
x = self.features_maps(x)
x = torch.nn.Flatten()(x)
x = self.linears(x)
return xIn [4]:
g = torch.Generator()
g.manual_seed(SEED)
np.random.seed(SEED)
torch.manual_seed(SEED)
random.seed(SEED)
train_transform = transforms.Compose(
[ # Workaround: stride=1, padding_mode='replicate' is replaced by
transforms.Pad(1, padding_mode="edge"),
transforms.ToTensor(),
transforms.Normalize((MEAN,), (STD,)),
]
)
test_transform = transforms.Compose(
[ # Workaround: stride=1, padding_mode='replicate' is replaced by
transforms.Pad(1, padding_mode="edge"),
transforms.ToTensor(),
transforms.Normalize((MEAN,), (STD,)),
]
)
train_dataset = datasets.MNIST(download=True, root="./data", train=True, transform=train_transform)
test_dataset = datasets.MNIST(download=True, root="./data", train=False, transform=test_transform)
train_loader = torch.utils.data.DataLoader(
train_dataset,
batch_size=BATCH_SIZE,
shuffle=True,
drop_last=True,
generator=g,
)
test_loader = torch.utils.data.DataLoader(
test_dataset,
batch_size=BATCH_SIZE,
shuffle=True,
drop_last=True,
generator=g,
)
plot_dataset(train_loader)Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz Failed to download (trying next): HTTP Error 403: Forbidden Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-images-idx3-ubyte.gz
Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-images-idx3-ubyte.gz to ./data/MNIST/raw/train-images-idx3-ubyte.gz
0%| | 0/9912422 [00:00<?, ?it/s]
Extracting ./data/MNIST/raw/train-images-idx3-ubyte.gz to ./data/MNIST/raw
Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz Failed to download (trying next): HTTP Error 403: Forbidden Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-labels-idx1-ubyte.gz
Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-labels-idx1-ubyte.gz to ./data/MNIST/raw/train-labels-idx1-ubyte.gz
0%| | 0/28881 [00:00<?, ?it/s]
Extracting ./data/MNIST/raw/train-labels-idx1-ubyte.gz to ./data/MNIST/raw Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz Failed to download (trying next): HTTP Error 403: Forbidden Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-images-idx3-ubyte.gz
Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-images-idx3-ubyte.gz to ./data/MNIST/raw/t10k-images-idx3-ubyte.gz
0%| | 0/1648877 [00:00<?, ?it/s]
Extracting ./data/MNIST/raw/t10k-images-idx3-ubyte.gz to ./data/MNIST/raw Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz Failed to download (trying next): HTTP Error 403: Forbidden Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-labels-idx1-ubyte.gz
Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-labels-idx1-ubyte.gz to ./data/MNIST/raw/t10k-labels-idx1-ubyte.gz
0%| | 0/4542 [00:00<?, ?it/s]
Extracting ./data/MNIST/raw/t10k-labels-idx1-ubyte.gz to ./data/MNIST/raw
In [5]:
fp32_mnist = Fp32MNIST(nb_layers=20).to(DEVICE)
print(fp32_mnist)Fp32MNIST(
(features_maps): Sequential(
(0): Conv2d(1, 1, kernel_size=(3, 3), stride=(1, 1))
(1): ReLU()
(2): BatchNorm2d(1, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
(linears): Sequential(
(0): Linear(in_features=784, out_features=92, bias=True)
(1): ReLU()
(2): BatchNorm1d(92, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(3): Linear(in_features=92, out_features=92, bias=True)
(4): ReLU()
(5): BatchNorm1d(92, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(6): Linear(in_features=92, out_features=92, bias=True)
(7): ReLU()
(8): BatchNorm1d(92, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(9): Linear(in_features=92, out_features=92, bias=True)
(10): ReLU()
(11): BatchNorm1d(92, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(12): Linear(in_features=92, out_features=92, bias=True)
(13): ReLU()
(14): BatchNorm1d(92, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(15): Linear(in_features=92, out_features=92, bias=True)
(16): ReLU()
(17): BatchNorm1d(92, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(18): Linear(in_features=92, out_features=92, bias=True)
(19): ReLU()
(20): BatchNorm1d(92, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(21): Linear(in_features=92, out_features=92, bias=True)
(22): ReLU()
(23): BatchNorm1d(92, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(24): Linear(in_features=92, out_features=92, bias=True)
(25): ReLU()
(26): BatchNorm1d(92, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(27): Linear(in_features=92, out_features=92, bias=True)
(28): ReLU()
(29): BatchNorm1d(92, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(30): Linear(in_features=92, out_features=92, bias=True)
(31): ReLU()
(32): BatchNorm1d(92, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(33): Linear(in_features=92, out_features=92, bias=True)
(34): ReLU()
(35): BatchNorm1d(92, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(36): Linear(in_features=92, out_features=92, bias=True)
(37): ReLU()
(38): BatchNorm1d(92, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(39): Linear(in_features=92, out_features=92, bias=True)
(40): ReLU()
(41): BatchNorm1d(92, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(42): Linear(in_features=92, out_features=92, bias=True)
(43): ReLU()
(44): BatchNorm1d(92, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(45): Linear(in_features=92, out_features=92, bias=True)
(46): ReLU()
(47): BatchNorm1d(92, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(48): Linear(in_features=92, out_features=92, bias=True)
(49): ReLU()
(50): BatchNorm1d(92, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(51): Linear(in_features=92, out_features=92, bias=True)
(52): ReLU()
(53): BatchNorm1d(92, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(54): Linear(in_features=92, out_features=10, bias=True)
)
)
In [6]:
data_calibration = next(iter(train_loader))[0]
results_cml = {}
for nb_layers in [20, 50]:
fp32_mnist = Fp32MNIST(nb_layers=nb_layers).to(DEVICE)
checkpoint = torch.load(
f"./checkpoints/MNIST/MLP_{nb_layers}/fp32/MNIST_fp32_state_dict.pt", map_location=DEVICE
)
fp32_mnist.load_state_dict(checkpoint)
fp32_mnist.eval()
acc_test = torch_inference(fp32_mnist, test_loader, device=DEVICE)
# The model is compiled through 'compile_torch_model' method
# We use approximate rounding and a p_error value of 0.1
# These values work well for neural networks which are robust to noise
# in the computation of intermediate values.
q_module = compile_torch_model(
fp32_mnist.to(DEVICE),
torch_inputset=data_calibration,
n_bits=6,
rounding_threshold_bits={"n_bits": 6, "method": "APPROXIMATE"},
p_error=0.1,
)
fhe_timing = []
y_predictions = []
fhe_samples = 3
# The model is evaluated through all the test data-set in 'simulation' mode
for i, (data, labels) in enumerate(test_loader):
data, labels = data.detach().cpu().numpy(), labels.detach().cpu().numpy()
simulate_predictions = q_module.forward(data, fhe="simulate")
y_predictions.extend(simulate_predictions.argmax(1) == labels)
# Measure FHE latency on three samples and take the minimum
if i <= fhe_samples:
start_time = time.time()
q_module.forward(data[0, None], fhe="execute")
fhe_timing.append((time.time() - start_time))
results_cml[nb_layers] = [acc_test, np.mean(y_predictions), np.min(fhe_timing)]
print(
f"Running NN-{nb_layers} on a {MACHINE} machine:"
f"Accuracy in fp32 : {results_cml[nb_layers][0]:.3%} for the test set\n"
f"Accuracy with FHE-simulation mode : {results_cml[nb_layers][1]:.3%} for the test set\n"
f"FHE Latency on encrypted data : {results_cml[nb_layers][2]:.3f}s per encrypted sample.\n"
f"Number of PBS: {q_module.fhe_circuit.statistics['programmable_bootstrap_count']}"
)Running NN-20 on a 128-core machine machine:Accuracy in fp32 : 98.067% for the test set Accuracy with FHE-simulation mode : 95.803% for the test set FHE Latency on encrypted data : 1.918s per encrypted sample. Number of PBS: 2440
Running NN-50 on a 128-core machine machine:Accuracy in fp32 : 97.446% for the test set Accuracy with FHE-simulation mode : 94.631% for the test set FHE Latency on encrypted data : 4.902s per encrypted sample. Number of PBS: 5200
In [7]:
df, fmt = format_results_df(PAPER_NOTES, results_cml, "Our m6i.metal")
df.style.format(fmt)Out [7]:
| Num Layers | Accuracy [1] | FHE Latency [1] | Our m6i.metal Accuracy fp32 | Our m6i.metal Accuracy FHE | Our m6i.metal FHE Latency | Speedup | |
|---|---|---|---|---|---|---|---|
| 0 | 20 | 97.1% | 21.17s | 98.1% | 95.8% | 1.92s | 11.0x |
| 1 | 50 | 94.7% | 43.91s | 97.4% | 94.6% | 4.90s | 9.0x |
In [8]:
# This benchmark was done on a hpc7 machine with 192-cores
BENCH_HPC7A = {20: [0.987, 0.959, 0.995], 50: [0.9745, 0.9477, 3.03]}
df, fmt = format_results_df(PAPER_NOTES, BENCH_HPC7A, "Our hpc7a.96xlarge")
df.style.format(fmt)Out [8]:
| Num Layers | Accuracy [1] | FHE Latency [1] | Our hpc7a.96xlarge Accuracy fp32 | Our hpc7a.96xlarge Accuracy FHE | Our hpc7a.96xlarge FHE Latency | Speedup | |
|---|---|---|---|---|---|---|---|
| 0 | 20 | 97.1% | 21.17s | 98.7% | 95.9% | 0.99s | 21.3x |
| 1 | 50 | 94.7% | 43.91s | 97.5% | 94.8% | 3.03s | 14.5x |