mirror of
https://github.com/luxfhe/torus.git
synced 2026-10-30 17:07:26 +00:00
The MLIR dialect, its conversion passes (TFHEToConcrete, ConcreteToCAPI, ConcreteToLLVM), the optimizer component, the compiler binary, the Rust crate and the program_info parameter file all still carried the upstream project's name. They now carry this one's. Two things deliberately keep the word, because in them it is not the name. mlir::OpTrait CRTP parameters mean "the derived type", so they are spelled Derived and OpTy — LLVM's other conventional spelling for the same idiom. concreteShape is a capnp field: renaming it would change the serialized protocol, and the prose describing it is ordinary English. This also repairs references the earlier partial rename left dangling: Security.cpp included concrete/curves.gen.h and the directory is torus/, torus-cpu.h included concrete-num_complex.h, the Torus dialect CMakeLists named ConcreteOps.td, and compiler/setup.py asked find_namespace_packages for a "concrete" package that no longer exists — so it built without the package it ships.
22 KiB
22 KiB
In [1]:
import numpy as np
from torus import fhe
from time import time
from numpy.random import randint
from numpy.random import rand
from numpy import roundIn [2]:
nb_bits = 4
length_inputset = 100
nb_test_samples = 16
@fhe.compiler({"x": "encrypted", "y": "encrypted"})
def add_integers(x, y):
return x + y
# Compile
inputset = [(randint(2**nb_bits), randint(2**nb_bits)) for _ in range(length_inputset)]
circuit = add_integers.compile(inputset)
# Check
time_begin = time()
for _ in range(nb_test_samples):
random_sample = (randint(2**nb_bits), randint(2**nb_bits))
assert circuit.encrypt_run_decrypt(*random_sample) == add_integers(*random_sample)
print(
"Compilation and test look good, with FHE execution time of about "
f"{(time() - time_begin) / nb_test_samples:.2f} seconds per inference"
)Compilation and test look good, with FHE execution time of about 0.00 seconds per inference
In [3]:
print(circuit.mlir)module {
func.func @main(%arg0: !FHE.eint<5>, %arg1: !FHE.eint<5>) -> !FHE.eint<5> {
%0 = "FHE.add_eint"(%arg0, %arg1) : (!FHE.eint<5>, !FHE.eint<5>) -> !FHE.eint<5>
return %0 : !FHE.eint<5>
}
}
In [4]:
random_sample = (1.5, 2.42)
try:
circuit.encrypt_run_decrypt(*random_sample)
except Exception as err:
print(err)
passExpected argument 0 to be EncryptedScalar<uint5> but it's EncryptedScalar<float64>
In [5]:
max_value_for_floats = 2.5
scaling_factor = 100
@fhe.compiler({"x": "encrypted", "y": "encrypted"})
def add_integers(x, y):
return x + y
def are_almost_the_same(a, b, threshold):
abs_diff = abs(b - a)
assert abs_diff <= threshold, f"Too far {a=} {b=} {abs_diff=} > {threshold=}"
# Compile
inputset = [
(
round(scaling_factor * rand() * max_value_for_floats).astype(np.uint32),
round(scaling_factor * rand() * max_value_for_floats).astype(np.uint32),
)
for _ in range(length_inputset)
]
circuit = add_integers.compile(inputset)
# Check
verbose = True
time_begin = time()
for _ in range(nb_test_samples):
# Take a random float input
random_sample = (rand() * max_value_for_floats, rand() * max_value_for_floats)
if verbose:
print("Showing an example")
print(f"{random_sample=}")
# Scale it and round
scaled_sample = (
round(random_sample[0] * scaling_factor).astype(np.uint32),
round(random_sample[1] * scaling_factor).astype(np.uint32),
)
if verbose:
print(f"{scaled_sample=}")
# Encrypt
encrypted_scaled_sample = circuit.encrypt(*scaled_sample)
# Computations in FHE
encrypted_scaled_result = circuit.run(*encrypted_scaled_sample)
# Decrypt
scaled_result = circuit.decrypt(encrypted_scaled_result)
if verbose:
print(f"{scaled_result=}")
# Unscale
result = scaled_result * 1.0 / scaling_factor
bounded_error = 2.0 / scaling_factor
clear_result = random_sample[0] + random_sample[1]
if verbose:
print()
print(f"FHE: {result}")
print(f"clear: {clear_result}")
print(f"bounded error: {bounded_error}")
are_almost_the_same(result, clear_result, bounded_error)
verbose = False
print(
"\nCompilation and test look good, with FHE execution time of about "
f"{(time() - time_begin) / nb_test_samples:.2f} seconds per inference"
)Showing an example random_sample=(1.583812144515164, 0.38770109380525164) scaled_sample=(158, 39) scaled_result=197 FHE: 1.97 clear: 1.9715132383204157 bounded error: 0.02 Compilation and test look good, with FHE execution time of about 0.00 seconds per inference
In [6]:
print(circuit.mlir)module {
func.func @main(%arg0: !FHE.eint<9>, %arg1: !FHE.eint<9>) -> !FHE.eint<9> {
%0 = "FHE.add_eint"(%arg0, %arg1) : (!FHE.eint<9>, !FHE.eint<9>) -> !FHE.eint<9>
return %0 : !FHE.eint<9>
}
}
In [7]:
max_value_for_floats = 2.5
scaling_factor = 8
nb_test_samples = 10
@fhe.compiler({"x": "encrypted", "y": "encrypted"})
def norm(x, y):
return np.round(fhe.univariate(lambda x: np.sqrt(x))(x**2 + y**2)).astype(np.int64)
# Compile
inputset = [
(
round(scaling_factor * rand() * max_value_for_floats).astype(np.uint32),
round(scaling_factor * rand() * max_value_for_floats).astype(np.uint32),
)
for _ in range(length_inputset)
]
circuit = norm.compile(inputset, show_mlir=False)
print(f"Maximal bitwidth in the circuit: {circuit.graph.maximum_integer_bit_width()}\n")
# Check
verbose = True
time_begin = time()
for _ in range(nb_test_samples):
# Take a random float input
random_sample = (rand() * max_value_for_floats, rand() * max_value_for_floats)
if verbose:
print("Showing an example")
print(f"{random_sample=}")
# Scale it and round
scaled_sample = (
round(random_sample[0] * scaling_factor).astype(np.uint32),
round(random_sample[1] * scaling_factor).astype(np.uint32),
)
if verbose:
print(f"{scaled_sample=}")
# Encrypt
encrypted_scaled_sample = circuit.encrypt(*scaled_sample)
# Computations in FHE
encrypted_scaled_result = circuit.run(*encrypted_scaled_sample)
# Decrypt
scaled_result = circuit.decrypt(encrypted_scaled_result)
if verbose:
print(f"{scaled_result=}")
# Unscale
result = scaled_result * 1.0 / scaling_factor
bounded_error = 2.0 / scaling_factor
clear_result = np.sqrt(random_sample[0] ** 2 + random_sample[1] ** 2)
if verbose:
print()
print(f"FHE: {result}")
print(f"clear: {clear_result}")
print(f"bounded error: {bounded_error}")
are_almost_the_same(result, clear_result, bounded_error)
verbose = False
print(
"\nCompilation and test look good, with FHE execution time of about "
f"{(time() - time_begin) / nb_test_samples:.2f} seconds per inference"
)Maximal bitwidth in the circuit: 10 Showing an example random_sample=(0.6597925446542308, 2.2648103856817494) scaled_sample=(5, 18) scaled_result=19 FHE: 2.375 clear: 2.358960000736176 bounded error: 0.25 Compilation and test look good, with FHE execution time of about 11.16 seconds per inference
In [8]:
max_value_for_floats = 2
scaling_factor = 8
nb_test_samples = 8
def special_function_in_clear(x, y):
u = x**2
v = u + 4 * y
return v / 1.33
@fhe.compiler({"x": "encrypted", "y": "encrypted"})
def special_function(x, y):
u = fhe.univariate(lambda x: x**2 // scaling_factor)(x)
v = u + 4 * y
return np.round(fhe.univariate(lambda x: x / 1.33)(v)).astype(np.int64)
# Compile
inputset = [
(
round(scaling_factor * rand() * max_value_for_floats).astype(np.uint32),
round(scaling_factor * rand() * max_value_for_floats).astype(np.uint32),
)
for _ in range(length_inputset)
]
circuit = special_function.compile(inputset, show_mlir=False)
print(f"Maximal bitwidth in the circuit: {circuit.graph.maximum_integer_bit_width()}\n")
# Check
verbose = True
time_begin = time()
for _ in range(nb_test_samples):
# Take a random float input
random_sample = (rand() * max_value_for_floats, rand() * max_value_for_floats)
if verbose:
print("Showing an example")
print(f"{random_sample=}")
# Scale it and round
scaled_sample = (
round(random_sample[0] * scaling_factor).astype(np.uint32),
round(random_sample[1] * scaling_factor).astype(np.uint32),
)
if verbose:
print(f"{scaled_sample=}")
# Encrypt
encrypted_scaled_sample = circuit.encrypt(*scaled_sample)
# Computations in FHE
encrypted_scaled_result = circuit.run(*encrypted_scaled_sample)
# Decrypt
scaled_result = circuit.decrypt(encrypted_scaled_result)
if verbose:
print(f"{scaled_result=}")
# Unscale
result = scaled_result * 1.0 / scaling_factor
bounded_error = 3.0 / scaling_factor
clear_result = special_function_in_clear(*random_sample)
if verbose:
print()
print(f"FHE: {result}")
print(f"clear: {clear_result}")
print(f"bounded error: {bounded_error}")
are_almost_the_same(result, clear_result, bounded_error)
verbose = False
print(
"\nCompilation and test look good, with FHE execution time of about "
f"{(time() - time_begin) / nb_test_samples:.2f} seconds per inference"
)Maximal bitwidth in the circuit: 7 Showing an example random_sample=(0.43374356260757363, 1.6491834206477578) scaled_sample=(3, 13) scaled_result=40 FHE: 5.0 clear: 5.10140388022146 bounded error: 0.375 Compilation and test look good, with FHE execution time of about 0.82 seconds per inference