chore: adding a test with DL
Browse files- README.md +44 -0
- compiled_model/client.zip +3 -0
- compiled_model/server.zip +3 -0
- compiled_model/versions.json +1 -0
- creating_models.py +191 -0
- handler.py +82 -0
- play_with_endpoint.py +149 -0
- requirements.txt +1 -0
README.md
CHANGED
@@ -1,3 +1,47 @@
|
|
1 |
---
|
2 |
license: apache-2.0
|
3 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
---
|
2 |
license: apache-2.0
|
3 |
---
|
4 |
+
|
5 |
+
# Template for Concrete ML
|
6 |
+
|
7 |
+
Concrete ML is Zama's open-source privacy-preserving ML package, based on fully homomorphic encryption (FHE). We refer the reader to fhe.org or Zama's websites for more information on FHE.
|
8 |
+
|
9 |
+
This directory is used:
|
10 |
+
- by ML practicioners, to create Concrete ML FHE-friendly models, and make them available to HF users
|
11 |
+
- by companies, institutions or people to deploy those models over HF inference endpoints
|
12 |
+
- by developers, to use these entry points to make applications on privacy-preserving ML
|
13 |
+
|
14 |
+
## Creating models and making them available on HF
|
15 |
+
|
16 |
+
This is quite easy. Fork this template (maybe use this experimental tool https://huggingface.co/spaces/huggingface-projects/repo_duplicator for that), and then:
|
17 |
+
- install everything with: `pip install -r requirements.txt`
|
18 |
+
- edit `creating_models.py`, and fill the part between "# BEGIN: insert your ML task here" and
|
19 |
+
"# END: insert your ML task here"
|
20 |
+
- run the python file: `python creating_models.py`
|
21 |
+
|
22 |
+
At the end, if the script is successful, you'll have your compiled model ready in `compiled_model`. Now you can commit and push your repository (with in particular `compiled_model`, `handler.py`, `play_with_endpoint.py` and `requirements.txt`, but you can include the other files as well).
|
23 |
+
|
24 |
+
We recommend you to tag your Concrete ML compiled repository with `Concrete ML FHE friendly` tag, such that people can find them easily.
|
25 |
+
|
26 |
+
## Deploying a compiled model on HF inference endpoint
|
27 |
+
|
28 |
+
If you find an `Concrete ML FHE friendly` repository that you would like to deploy, it is very easy.
|
29 |
+
- click on 'Deploy' button in HF interface
|
30 |
+
- chose "Inference endpoints"
|
31 |
+
- chose the right model repository
|
32 |
+
- (the rest of the options are classical to HF end points; we refer you to their documentation for more information)
|
33 |
+
and then click on 'Create endpoint'
|
34 |
+
|
35 |
+
And now, your model should be deployed, after few secunds of installation.
|
36 |
+
|
37 |
+
## Using HF entry points on privacy-preserving models
|
38 |
+
|
39 |
+
Now, this is the final step: using the entry point. You should:
|
40 |
+
- if your inference endpoint is private, set an environment variable HF_TOKEN with your HF token
|
41 |
+
- edit `play_with_endpoint.py`
|
42 |
+
- replace `API_URL` by your entry point URL
|
43 |
+
- replace the part between "# BEGIN: replace this part with your privacy-preserving application" and
|
44 |
+
"# END: replace this part with your privacy-preserving application" with your application
|
45 |
+
|
46 |
+
Finally, you'll be able to launch your application with `python play_with_endpoint.py`.
|
47 |
+
|
compiled_model/client.zip
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:c24aec589f5004924933388404ed735703dd743ae5fad0fc1c24f6ab413107ef
|
3 |
+
size 30194
|
compiled_model/server.zip
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:de29e9e7c7b1ba38d25d811d29791d71deb9c14ddcdf4eea15a2526a62550578
|
3 |
+
size 9144
|
compiled_model/versions.json
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
{"concrete-python": "2.5.0rc1", "concrete-ml": "1.3.0", "python": "3.9.15"}
|
creating_models.py
ADDED
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import shutil
|
2 |
+
import sys
|
3 |
+
from pathlib import Path
|
4 |
+
|
5 |
+
from concrete.ml.deployment import FHEModelDev
|
6 |
+
from concrete.ml.deployment import FHEModelClient
|
7 |
+
|
8 |
+
|
9 |
+
def compile_and_make_it_deployable(model_dev, X_train):
|
10 |
+
|
11 |
+
path_to_model = Path("compiled_model")
|
12 |
+
|
13 |
+
n_bits = 4
|
14 |
+
|
15 |
+
model_dev = compile_torch_model(model_dev, X_train, rounding_threshold_bits=6, p_error=0.1)
|
16 |
+
|
17 |
+
# Accuracy in simulation
|
18 |
+
accs = test_with_concrete(
|
19 |
+
model_dev,
|
20 |
+
test_dataloader,
|
21 |
+
use_sim=True,
|
22 |
+
)
|
23 |
+
|
24 |
+
print(f"Simulated FHE execution for {n_bits} bit model_devwork accuracy: {accs:.2f}")
|
25 |
+
|
26 |
+
# Saving the model
|
27 |
+
shutil.rmtree(path_to_model, ignore_errors=True)
|
28 |
+
fhemodel_dev = FHEModelDev(path_to_model, model_dev)
|
29 |
+
fhemodel_dev.save(via_mlir=True)
|
30 |
+
|
31 |
+
|
32 |
+
# BEGIN: insert your ML task here
|
33 |
+
# Typically
|
34 |
+
import time
|
35 |
+
|
36 |
+
import numpy as np
|
37 |
+
import torch
|
38 |
+
import torch.utils
|
39 |
+
from sklearn.datasets import load_digits
|
40 |
+
from sklearn.model_selection import train_test_split
|
41 |
+
from torch import nn
|
42 |
+
from torch.utils.data import DataLoader, TensorDataset
|
43 |
+
from tqdm import tqdm
|
44 |
+
|
45 |
+
from concrete.ml.torch.compile import compile_torch_model
|
46 |
+
|
47 |
+
X, y = load_digits(return_X_y=True)
|
48 |
+
|
49 |
+
# The sklearn Digits data-set, though it contains digit images, keeps these images in vectors
|
50 |
+
# so we need to reshape them to 2D first. The images are 8x8 px in size and monochrome
|
51 |
+
X = np.expand_dims(X.reshape((-1, 8, 8)), 1)
|
52 |
+
|
53 |
+
X_train, X_test, Y_train, Y_test = train_test_split(
|
54 |
+
X, y, test_size=0.25, shuffle=True, random_state=42
|
55 |
+
)
|
56 |
+
|
57 |
+
|
58 |
+
class TinyCNN(nn.Module):
|
59 |
+
"""A very small CNN to classify the sklearn digits data-set."""
|
60 |
+
|
61 |
+
def __init__(self, n_classes) -> None:
|
62 |
+
"""Construct the CNN with a configurable number of classes."""
|
63 |
+
super().__init__()
|
64 |
+
|
65 |
+
# This model_devwork has a total complexity of 1216 MAC
|
66 |
+
self.conv1 = nn.Conv2d(1, 8, 3, stride=1, padding=0)
|
67 |
+
self.conv2 = nn.Conv2d(8, 16, 3, stride=2, padding=0)
|
68 |
+
self.conv3 = nn.Conv2d(16, 32, 2, stride=1, padding=0)
|
69 |
+
self.fc1 = nn.Linear(32, n_classes)
|
70 |
+
|
71 |
+
def forward(self, x):
|
72 |
+
"""Run inference on the tiny CNN, apply the decision layer on the reshaped conv output."""
|
73 |
+
x = self.conv1(x)
|
74 |
+
x = torch.relu(x)
|
75 |
+
x = self.conv2(x)
|
76 |
+
x = torch.relu(x)
|
77 |
+
x = self.conv3(x)
|
78 |
+
x = torch.relu(x)
|
79 |
+
x = x.flatten(1)
|
80 |
+
x = self.fc1(x)
|
81 |
+
return x
|
82 |
+
|
83 |
+
|
84 |
+
torch.manual_seed(42)
|
85 |
+
|
86 |
+
|
87 |
+
def train_one_epoch(model_dev, optimizer, train_loader):
|
88 |
+
# Cross Entropy loss for classification when not using a softmax layer in the model_devwork
|
89 |
+
loss = nn.CrossEntropyLoss()
|
90 |
+
|
91 |
+
model_dev.train()
|
92 |
+
avg_loss = 0
|
93 |
+
for data, target in train_loader:
|
94 |
+
optimizer.zero_grad()
|
95 |
+
output = model_dev(data)
|
96 |
+
loss_model_dev = loss(output, target.long())
|
97 |
+
loss_model_dev.backward()
|
98 |
+
optimizer.step()
|
99 |
+
avg_loss += loss_model_dev.item()
|
100 |
+
|
101 |
+
return avg_loss / len(train_loader)
|
102 |
+
|
103 |
+
|
104 |
+
def test_torch(model_dev, test_loader):
|
105 |
+
"""Test the model_devwork: measure accuracy on the test set."""
|
106 |
+
|
107 |
+
# Freeze normalization layers
|
108 |
+
model_dev.eval()
|
109 |
+
|
110 |
+
all_y_pred = np.zeros((len(test_loader)), dtype=np.int64)
|
111 |
+
all_targets = np.zeros((len(test_loader)), dtype=np.int64)
|
112 |
+
|
113 |
+
# Iterate over the batches
|
114 |
+
idx = 0
|
115 |
+
for data, target in test_loader:
|
116 |
+
# Accumulate the ground truth labels
|
117 |
+
endidx = idx + target.shape[0]
|
118 |
+
all_targets[idx:endidx] = target.numpy()
|
119 |
+
|
120 |
+
# Run forward and get the predicted class id
|
121 |
+
output = model_dev(data).argmax(1).detach().numpy()
|
122 |
+
all_y_pred[idx:endidx] = output
|
123 |
+
|
124 |
+
idx += target.shape[0]
|
125 |
+
|
126 |
+
# Print out the accuracy as a percentage
|
127 |
+
n_correct = np.sum(all_targets == all_y_pred)
|
128 |
+
print(
|
129 |
+
f"Test accuracy for fp32 weights and activations: "
|
130 |
+
f"{n_correct / len(test_loader) * 100:.2f}%"
|
131 |
+
)
|
132 |
+
|
133 |
+
|
134 |
+
def test_with_concrete(quantized_module, test_loader, use_sim):
|
135 |
+
"""Test a neural model_devwork that is quantized and compiled with Concrete ML."""
|
136 |
+
|
137 |
+
# Casting the inputs into int64 is recommended
|
138 |
+
all_y_pred = np.zeros((len(test_loader)), dtype=np.int64)
|
139 |
+
all_targets = np.zeros((len(test_loader)), dtype=np.int64)
|
140 |
+
|
141 |
+
# Iterate over the test batches and accumulate predictions and ground truth labels in a vector
|
142 |
+
idx = 0
|
143 |
+
for data, target in tqdm(test_loader):
|
144 |
+
data = data.numpy()
|
145 |
+
target = target.numpy()
|
146 |
+
|
147 |
+
fhe_mode = "simulate" if use_sim else "execute"
|
148 |
+
|
149 |
+
# Quantize the inputs and cast to appropriate data type
|
150 |
+
y_pred = quantized_module.forward(data, fhe=fhe_mode)
|
151 |
+
|
152 |
+
endidx = idx + target.shape[0]
|
153 |
+
|
154 |
+
# Accumulate the ground truth labels
|
155 |
+
all_targets[idx:endidx] = target
|
156 |
+
|
157 |
+
# Get the predicted class id and accumulate the predictions
|
158 |
+
y_pred = np.argmax(y_pred, axis=1)
|
159 |
+
all_y_pred[idx:endidx] = y_pred
|
160 |
+
|
161 |
+
# Update the index
|
162 |
+
idx += target.shape[0]
|
163 |
+
|
164 |
+
# Compute and report results
|
165 |
+
n_correct = np.sum(all_targets == all_y_pred)
|
166 |
+
|
167 |
+
return n_correct / len(test_loader)
|
168 |
+
|
169 |
+
|
170 |
+
# Create the tiny CNN with 10 output classes
|
171 |
+
N_EPOCHS = 50
|
172 |
+
|
173 |
+
# Create a train data loader
|
174 |
+
train_dataset = TensorDataset(torch.Tensor(X_train), torch.Tensor(Y_train))
|
175 |
+
train_dataloader = DataLoader(train_dataset, batch_size=64)
|
176 |
+
|
177 |
+
# Create a test data loader to supply batches for model_devwork evaluation (test)
|
178 |
+
test_dataset = TensorDataset(torch.Tensor(X_test), torch.Tensor(Y_test))
|
179 |
+
test_dataloader = DataLoader(test_dataset)
|
180 |
+
|
181 |
+
# Train the model_devwork with Adam, output the test set accuracy every epoch
|
182 |
+
model_dev = TinyCNN(10)
|
183 |
+
losses_bits = []
|
184 |
+
optimizer = torch.optim.Adam(model_dev.parameters())
|
185 |
+
for _ in tqdm(range(N_EPOCHS), desc="Training"):
|
186 |
+
losses_bits.append(train_one_epoch(model_dev, optimizer, train_dataloader))
|
187 |
+
|
188 |
+
# END: insert your ML task here
|
189 |
+
|
190 |
+
compile_and_make_it_deployable(model_dev, X_train)
|
191 |
+
print("Your model is ready to be deployable.")
|
handler.py
ADDED
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Dict, List, Any
|
2 |
+
import numpy as np
|
3 |
+
from concrete.ml.deployment import FHEModelServer
|
4 |
+
|
5 |
+
|
6 |
+
def from_json(python_object):
|
7 |
+
if "__class__" in python_object:
|
8 |
+
return bytes(python_object["__value__"])
|
9 |
+
|
10 |
+
|
11 |
+
def to_json(python_object):
|
12 |
+
if isinstance(python_object, bytes):
|
13 |
+
return {"__class__": "bytes", "__value__": list(python_object)}
|
14 |
+
raise TypeError(repr(python_object) + " is not JSON serializable")
|
15 |
+
|
16 |
+
|
17 |
+
class EndpointHandler:
|
18 |
+
def __init__(self, path=""):
|
19 |
+
|
20 |
+
# For server
|
21 |
+
self.fhemodel_server = FHEModelServer(path + "/compiled_model")
|
22 |
+
|
23 |
+
# Simulate a database of keys
|
24 |
+
self.key_database = {}
|
25 |
+
|
26 |
+
def __call__(self, data: Dict[str, Any]) -> List[Dict[str, Any]]:
|
27 |
+
"""
|
28 |
+
data args:
|
29 |
+
inputs (:obj: `str`)
|
30 |
+
date (:obj: `str`)
|
31 |
+
Return:
|
32 |
+
A :obj:`list` | `dict`: will be serialized and returned
|
33 |
+
"""
|
34 |
+
|
35 |
+
# Get method
|
36 |
+
method = data.pop("method", data)
|
37 |
+
|
38 |
+
if method == "save_key":
|
39 |
+
|
40 |
+
# Get keys
|
41 |
+
evaluation_keys = from_json(data.pop("evaluation_keys", data))
|
42 |
+
|
43 |
+
uid = np.random.randint(2**32)
|
44 |
+
|
45 |
+
while uid in self.key_database.keys():
|
46 |
+
uid = np.random.randint(2**32)
|
47 |
+
|
48 |
+
self.key_database[uid] = evaluation_keys
|
49 |
+
|
50 |
+
return {"uid": uid}
|
51 |
+
|
52 |
+
elif method == "append_key":
|
53 |
+
|
54 |
+
# Get key piece
|
55 |
+
evaluation_keys = from_json(data.pop("evaluation_keys", data))
|
56 |
+
|
57 |
+
uid = data.pop("uid", data)
|
58 |
+
|
59 |
+
self.key_database[uid] += evaluation_keys
|
60 |
+
|
61 |
+
return
|
62 |
+
|
63 |
+
elif method == "inference":
|
64 |
+
|
65 |
+
uid = data.pop("uid", data)
|
66 |
+
|
67 |
+
assert uid in self.key_database.keys(), f"{uid} not in DB, {self.key_database.keys()=}"
|
68 |
+
|
69 |
+
# Get inputs
|
70 |
+
encrypted_inputs = from_json(data.pop("encrypted_inputs", data))
|
71 |
+
|
72 |
+
# Find key in the database
|
73 |
+
evaluation_keys = self.key_database[uid]
|
74 |
+
|
75 |
+
# Run CML prediction
|
76 |
+
encrypted_prediction = self.fhemodel_server.run(encrypted_inputs, evaluation_keys)
|
77 |
+
|
78 |
+
return to_json(encrypted_prediction)
|
79 |
+
|
80 |
+
else:
|
81 |
+
|
82 |
+
return
|
play_with_endpoint.py
ADDED
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
import time
|
3 |
+
import os, sys
|
4 |
+
|
5 |
+
from pathlib import Path
|
6 |
+
|
7 |
+
from concrete.ml.deployment import FHEModelClient
|
8 |
+
|
9 |
+
import requests
|
10 |
+
|
11 |
+
|
12 |
+
def to_json(python_object):
|
13 |
+
if isinstance(python_object, bytes):
|
14 |
+
return {"__class__": "bytes", "__value__": list(python_object)}
|
15 |
+
raise TypeError(repr(python_object) + " is not JSON serializable")
|
16 |
+
|
17 |
+
|
18 |
+
def from_json(python_object):
|
19 |
+
if "__class__" in python_object:
|
20 |
+
return bytes(python_object["__value__"])
|
21 |
+
|
22 |
+
|
23 |
+
# TODO: put the right link `API_URL` for your entry point
|
24 |
+
API_URL = "https://XXXXXXX.us-east-1.aws.endpoints.huggingface.cloud"
|
25 |
+
headers = {
|
26 |
+
"Authorization": "Bearer " + os.environ.get("HF_TOKEN"),
|
27 |
+
"Content-Type": "application/json",
|
28 |
+
}
|
29 |
+
|
30 |
+
|
31 |
+
def query(payload):
|
32 |
+
response = requests.post(API_URL, headers=headers, json=payload)
|
33 |
+
|
34 |
+
if "error" in response:
|
35 |
+
assert False, f"Got an error: {response=}"
|
36 |
+
|
37 |
+
return response.json()
|
38 |
+
|
39 |
+
|
40 |
+
path_to_model = Path("compiled_model")
|
41 |
+
|
42 |
+
# BEGIN: replace this part with your privacy-preserving application
|
43 |
+
from sklearn.datasets import make_classification
|
44 |
+
from sklearn.model_selection import train_test_split
|
45 |
+
|
46 |
+
x, y = make_classification(n_samples=1000, class_sep=2, n_features=30, random_state=42)
|
47 |
+
_, X_test, _, Y_test = train_test_split(x, y, test_size=0.2, random_state=42)
|
48 |
+
|
49 |
+
# Recover parameters for client side
|
50 |
+
fhemodel_client = FHEModelClient(path_to_model)
|
51 |
+
|
52 |
+
# Generate the keys
|
53 |
+
fhemodel_client.generate_private_and_evaluation_keys()
|
54 |
+
evaluation_keys = fhemodel_client.get_serialized_evaluation_keys()
|
55 |
+
|
56 |
+
# Save the key in the database
|
57 |
+
evaluation_keys_remaining = evaluation_keys[:]
|
58 |
+
uid = None
|
59 |
+
is_first = True
|
60 |
+
is_finished = False
|
61 |
+
i = 0
|
62 |
+
packet_size = 1024 * 1024 * 100
|
63 |
+
|
64 |
+
while not is_finished:
|
65 |
+
|
66 |
+
# Send by packets of 100M
|
67 |
+
if sys.getsizeof(evaluation_keys_remaining) > packet_size:
|
68 |
+
evaluation_keys_piece = evaluation_keys_remaining[:packet_size]
|
69 |
+
evaluation_keys_remaining = evaluation_keys_remaining[packet_size:]
|
70 |
+
else:
|
71 |
+
evaluation_keys_piece = evaluation_keys_remaining
|
72 |
+
is_finished = True
|
73 |
+
|
74 |
+
print(
|
75 |
+
f"Sending {i}-th piece of the key (remaining size is {sys.getsizeof(evaluation_keys_remaining)})"
|
76 |
+
)
|
77 |
+
i += 1
|
78 |
+
|
79 |
+
if is_first:
|
80 |
+
is_first = False
|
81 |
+
payload = {
|
82 |
+
"inputs": "fake",
|
83 |
+
"evaluation_keys": to_json(evaluation_keys_piece),
|
84 |
+
"method": "save_key",
|
85 |
+
}
|
86 |
+
|
87 |
+
uid = query(payload)["uid"]
|
88 |
+
print(f"Storing the key in the database under {uid=}")
|
89 |
+
|
90 |
+
else:
|
91 |
+
payload = {
|
92 |
+
"inputs": "fake",
|
93 |
+
"evaluation_keys": to_json(evaluation_keys_piece),
|
94 |
+
"method": "append_key",
|
95 |
+
"uid": uid,
|
96 |
+
}
|
97 |
+
|
98 |
+
query(payload)
|
99 |
+
|
100 |
+
# Test the handler
|
101 |
+
nb_good = 0
|
102 |
+
nb_samples = len(X_test)
|
103 |
+
verbose = True
|
104 |
+
time_start = time.time()
|
105 |
+
duration = 0
|
106 |
+
is_first = True
|
107 |
+
|
108 |
+
for i in range(nb_samples):
|
109 |
+
|
110 |
+
# Quantize the input and encrypt it
|
111 |
+
encrypted_inputs = fhemodel_client.quantize_encrypt_serialize([X_test[i]])
|
112 |
+
|
113 |
+
# Prepare the payload
|
114 |
+
payload = {
|
115 |
+
"inputs": "fake",
|
116 |
+
"encrypted_inputs": to_json(encrypted_inputs),
|
117 |
+
"method": "inference",
|
118 |
+
"uid": uid,
|
119 |
+
}
|
120 |
+
|
121 |
+
if is_first:
|
122 |
+
print(f"Size of the payload: {sys.getsizeof(payload) / 1024} kilobytes")
|
123 |
+
is_first = False
|
124 |
+
|
125 |
+
# Run the inference on HF servers
|
126 |
+
duration -= time.time()
|
127 |
+
duration_inference = -time.time()
|
128 |
+
encrypted_prediction = query(payload)
|
129 |
+
duration += time.time()
|
130 |
+
duration_inference += time.time()
|
131 |
+
|
132 |
+
encrypted_prediction = from_json(encrypted_prediction)
|
133 |
+
|
134 |
+
# Decrypt the result and dequantize
|
135 |
+
prediction_proba = fhemodel_client.deserialize_decrypt_dequantize(encrypted_prediction)[0]
|
136 |
+
prediction = np.argmax(prediction_proba)
|
137 |
+
|
138 |
+
if verbose:
|
139 |
+
print(
|
140 |
+
f"for {i}-th input, {prediction=} with expected {Y_test[i]} in {duration_inference:.3f} seconds"
|
141 |
+
)
|
142 |
+
|
143 |
+
# Measure accuracy
|
144 |
+
nb_good += Y_test[i] == prediction
|
145 |
+
|
146 |
+
print(f"Accuracy on {nb_samples} samples is {nb_good * 1. / nb_samples}")
|
147 |
+
print(f"Total time: {time.time() - time_start:.3f} seconds")
|
148 |
+
print(f"Duration per inference: {duration / nb_samples:.3f} seconds")
|
149 |
+
# END: replace this part with your privacy-preserving application
|
requirements.txt
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
concrete-ml==1.3.0
|