Skip to content

Commit ae671e7

Browse files
Merge pull request #2771 from edwjchen:rotom_mlp
PiperOrigin-RevId: 886177587
2 parents 1858722 + 4877510 commit ae671e7

8 files changed

Lines changed: 345 additions & 0 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
load("@heir//tools:heir-openfhe.bzl", "openfhe_lib")
2+
load("@rules_python//python:py_test.bzl", "py_test")
3+
4+
package(default_applicable_licenses = ["@heir//:license"])
5+
6+
openfhe_lib(
7+
name = "mnist_rotom_openfhe",
8+
data = glob(["inputs/*.npz"]),
9+
# ensure CppCompile has enough memory to compile this large, unrolled program
10+
exec_properties = {"mem": "28g"},
11+
generated_lib_header = "mnist_rotom_openfhe_lib.inc.h",
12+
heir_opt_flags = [
13+
"--annotate-module=backend=openfhe scheme=ckks",
14+
"--torch-linalg-to-ckks=ciphertext-degree=32768 scaling-mod-bits=45",
15+
"--scheme-to-openfhe",
16+
],
17+
mlir_src = "@heir//tests/Examples/openfhe/ckks/rotom/mnist:mnist.mlir",
18+
pybind_target_name = "mnist_rotom_openfhe_pybind",
19+
tags = [
20+
"nofastbuild",
21+
"notap",
22+
],
23+
)
24+
25+
py_test(
26+
name = "mnist_rotom",
27+
size = "enormous",
28+
srcs = ["mnist_test.py"],
29+
data = [
30+
"@heir//tests/Examples/common/mnist/data:t10k-labels-idx1-ubyte",
31+
"@heir//tests/Examples/common/mnist/data:traced_model.pt",
32+
] + glob(["inputs/*.npz"]),
33+
main = "mnist_test.py",
34+
tags = [
35+
"nofastbuild",
36+
"requires-mem:28g",
37+
],
38+
deps = [
39+
":mnist_rotom_openfhe_pybind",
40+
"@abseil-py//absl/testing:absltest",
41+
"@heir_pip_deps//numpy",
42+
"@heir_pip_deps//torch",
43+
],
44+
)
3.27 KB
Binary file not shown.
24.7 KB
Binary file not shown.
582 Bytes
Binary file not shown.
1.43 MB
Binary file not shown.
Binary file not shown.

tests/Examples/openfhe/ckks/rotom/mnist/mnist.mlir

Lines changed: 157 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import os
2+
import random
3+
import time
4+
from typing import Dict, List
5+
import numpy as np
6+
import torch
7+
from torch.utils.data import Dataset
8+
import absl.testing.absltest
9+
import tests.Examples.openfhe.ckks.rotom.mnist.mnist_rotom_openfhe_pybind as mnist
10+
11+
MODEL_PATH = "tests/Examples/common/mnist/data/traced_model.pt"
12+
DATA_PATH = "tests/Examples/common/mnist/data"
13+
14+
15+
def read_from_directory(dirpath: str) -> Dict[str, List[float]]:
16+
"""Reads all .npz files from a directory and maps filename to list of floats.
17+
18+
File format: compressed numpy .npz files with a 'data' key containing the
19+
array.
20+
Returns a dict from filename (with extension) to list of floats.
21+
"""
22+
result = {}
23+
24+
if not os.path.isdir(dirpath):
25+
print(f"Error: Could not open directory {dirpath}")
26+
return result
27+
28+
for filename in os.listdir(dirpath):
29+
if filename == "." or filename == "..":
30+
continue
31+
32+
if not filename.endswith(".npz"):
33+
continue
34+
35+
fullpath = os.path.join(dirpath, filename)
36+
try:
37+
npz_data = np.load(fullpath)
38+
if "data" not in npz_data:
39+
print(f"Warning: 'data' key not found in {fullpath}")
40+
continue
41+
42+
# Extract data array and flatten to 1D list
43+
data_array = npz_data["data"]
44+
values = data_array.flatten().tolist()
45+
result[filename] = values
46+
npz_data.close()
47+
except Exception as e:
48+
print(f"Warning: Could not load file {fullpath}: {e}")
49+
continue
50+
51+
return result
52+
53+
54+
class RotomMNISTTestDataset(Dataset):
55+
"""This custom dataset loads the raw MNIST test data and labels
56+
57+
from the files specified by `data_root`.
58+
It applies the Normalize transform manually during loading.
59+
"""
60+
61+
def __init__(
62+
self,
63+
data_root: str,
64+
):
65+
self.data_root = data_root
66+
67+
labels_path = os.path.join(self.data_root, "t10k-labels-idx1-ubyte")
68+
with open(labels_path, "rb") as f:
69+
# Skip header
70+
f.read(8)
71+
labels = np.frombuffer(f.read(), dtype=np.uint8)
72+
self.targets = torch.tensor(labels, dtype=torch.long)
73+
74+
# Read Rotom packed inputs from inputs/ directory
75+
inputs_map = read_from_directory(
76+
"tests/Examples/openfhe/ckks/rotom/mnist/inputs"
77+
)
78+
self.images = inputs_map["mlp_mnist_inputs.npz"]
79+
self.weights = {}
80+
self.weights["3.npz"] = inputs_map["3.npz"]
81+
self.weights["21.npz"] = inputs_map["21.npz"]
82+
self.weights["23.npz"] = inputs_map["23.npz"]
83+
self.weights["26.npz"] = inputs_map["26.npz"]
84+
85+
def __len__(self) -> int:
86+
return len(self.images)
87+
88+
def __getitem__(self, index: int) -> tuple[torch.Tensor, torch.Tensor]:
89+
image = self.images[index]
90+
label = self.targets[index]
91+
return image, label
92+
93+
94+
class MNISTTest(absl.testing.absltest.TestCase):
95+
96+
def test_run_test(self):
97+
test_dataset = RotomMNISTTestDataset(data_root=DATA_PATH)
98+
99+
crypto_context = mnist.mnist__generate_crypto_context()
100+
key_pair = crypto_context.KeyGen()
101+
public_key = key_pair.publicKey
102+
secret_key = key_pair.secretKey
103+
crypto_context = mnist.mnist__configure_crypto_context(
104+
crypto_context, secret_key
105+
)
106+
107+
# 4. Evaluation Loop
108+
total = 4
109+
correct = 0
110+
111+
# choose 4 random images from the test dataset
112+
random_samples = random.sample(test_dataset, 4)
113+
114+
for image, label in random_samples:
115+
input_encrypted = mnist.mnist__encrypt__arg0(
116+
crypto_context, image, public_key
117+
)
118+
119+
start_time = time.time()
120+
output_encrypted = mnist.mnist(
121+
crypto_context,
122+
input_encrypted,
123+
test_dataset.weights["3.npz"],
124+
test_dataset.weights["21.npz"],
125+
test_dataset.weights["23.npz"],
126+
test_dataset.weights["26.npz"],
127+
)
128+
end_time = time.time()
129+
130+
time_elapsed_ms = (end_time - start_time) * 1000.0
131+
print(f"CPU time used: {time_elapsed_ms:.2f} ms")
132+
133+
output = mnist.mnist__decrypt__result0(
134+
crypto_context, output_encrypted, secret_key
135+
)
136+
non_zero_results = [result for result in output if result != 0]
137+
guessed_label = non_zero_results.index(max(non_zero_results))
138+
139+
if guessed_label == label.item():
140+
correct += 1
141+
142+
print(f"guessed_label: {guessed_label}, label: {label.item()}")
143+
144+
self.assertGreaterEqual(correct, 0.75 * total)

0 commit comments

Comments
 (0)