Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...

docker pull zamafhe/concrete-python:v2.0.0
docker run --rm -it zamafhe/concrete-python:latest /bin/bashfrom concrete import fhe
def add(x, y):
return x + y
compiler = fhe.Compiler(add, {"x": "encrypted", "y": "clear"})
inputset = [(2, 3), (0, 0), (1, 6), (7, 7), (7, 1)]
circuit = compiler.compile(inputset)
x = 4
y = 4
clear_evaluation = add(x, y)
homomorphic_evaluation = circuit.encrypt_run_decrypt(x, y)
print(x, "+", y, "=", clear_evaluation, "=", homomorphic_evaluation)from concrete import fhedef add(x, y):
return x + ycompiler = fhe.Compiler(add, {"x": "encrypted", "y": "clear"})inputset = [(2, 3), (0, 0), (1, 6), (7, 7), (7, 1)]circuit = compiler.compile(inputset)homomorphic_evaluation = circuit.encrypt_run_decrypt(4, 4)import numpy as np
def encrypted_max(x: uint4):
return np.maximum(5, x)def encrypted_max(x: uint4):
lut = [5, 5, 5, 5, 5, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
return lut[x]module {
func.func @main(%arg0: !FHE.eint<4>) -> !FHE.eint<4> {
%cst = arith.constant dense<[5, 5, 5, 5, 5, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]> : tensor<16xi64>
%0 = "FHE.apply_lookup_table"(%arg0, %cst) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
return %0 : !FHE.eint<4>
}
}str(circuit)print(circuit)from concrete import fhe
@fhe.compiler({"x": "encrypted"})
def f(x):
return x + 42
inputset = range(10)
circuit = f.compile(inputset)
assert circuit.encrypt_run_decrypt(10) == f(10)
fuse:from concrete import fhe
@fhe.compiler({"x": "encrypted"})
def f(x):
return x ** 2
inputset = range(2 ** 4)
circuit = f.compile(inputset)from concrete import fhe
table = fhe.LookupTable([x ** 2 for x in range(2 ** 4)])
@fhe.compiler({"x": "encrypted"})
def f(x):
return table[x]
inputset = range(2 ** 4)
circuit = f.compile(inputset)circuit.graph(...)circuit.simulate(...)from concrete import fhe
import numpy as np
@fhe.compiler({"x": "encrypted"})
def f(x):
return (x + 1) ** 2
inputset = [np.random.randint(0, 10, size=(10,)) for _ in range(10)]
circuit = f.compile(inputset, p_error=0.1, fhe_simulation=True)
sample = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
actual = f(sample)
simulation = circuit.simulate(sample)
print(actual.tolist())
print(simulation.tolist())[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
[1, 4, 9, 16, 16, 36, 49, 64, 81, 100]%0 = x # EncryptedScalar<uint3> ∈ [0, 7]
%1 = y # EncryptedScalar<uint4> ∈ [0, 15]
%2 = add(%0, %1) # EncryptedScalar<uint5> ∈ [2, 22]
return %2 ^ these are ^^^^^^^
the assigned based on
bit-widths these boundsD: data
N: noise
3-bit number
------------
D2 D1 D0 0 0 0 ... 0 0 0 N N N N
4-bit number
------------
D3 D2 D1 D0 0 0 0 ... 0 0 0 N N N N5-bit number
------------
D4 D3 D2 D1 D0 0 0 0 ... 0 0 0 N N N N%0 = x # EncryptedScalar<uint5>
%1 = y # EncryptedScalar<uint5>
%2 = add(%0, %1) # EncryptedScalar<uint5>
return %2%0 = x # EncryptedScalar<uint2> ∈ [0, 3]
%1 = y # EncryptedScalar<uint5> ∈ [0, 31]
%2 = 2 # ClearScalar<uint2> ∈ [2, 2]
%3 = power(%0, %2) # EncryptedScalar<uint4> ∈ [0, 9]
%4 = add(%3, %1) # EncryptedScalar<uint6> ∈ [1, 39]
return %4%0 = x # EncryptedScalar<uint2> ∈ [0, 3]
%1 = y # EncryptedScalar<uint6> ∈ [0, 31]
%2 = 2 # ClearScalar<uint2> ∈ [2, 2]
%3 = power(%0, %2) # EncryptedScalar<uint6> ∈ [0, 9]
%4 = add(%3, %1) # EncryptedScalar<uint6> ∈ [1, 39]
return %4lut = [0, 1, 4, 9, 16, 25, 36, 49, 64]encryptfrom concrete import fhe
@fhe.compiler({"x": "encrypted", "y": "encrypted"})
def add(x, y):
return x + y
inputset = [(2, 3), (0, 0), (1, 6), (7, 7), (7, 1), (3, 2), (6, 1), (1, 7), (4, 5), (5, 4)]
circuit = add.compile(inputset)
sample_y = 4
_, encrypted_y = circuit.encrypt(None, sample_y)
for sample_x in range(3, 6):
encrypted_x, _ = circuit.encrypt(sample_x, None)
encrypted_result = circuit.run(encrypted_x, encrypted_y)
result = circuit.decrypt(encrypted_result)
assert result == sample_x + sample_ydef g(z):
with fhe.tag("def"):
a = 120 - z
b = a // 4
return b
def f(x):
with fhe.tag("abc"):
x = x * 2
with fhe.tag("foo"):
y = x + 42
z = np.sqrt(y).astype(np.int64)
return g(z + 3) * 2fhe.Keys
concretecompiler binary to compile this MLIR program. Same can be done with concrete-python, as we only need the compilation artifacts at the end.func.func @main(%arg0: tensor<4x4x!FHE.eint<6>>, %arg1: tensor<4x2xi7>) -> tensor<4x2x!FHE.eint<6>> {
%0 = "FHELinalg.matmul_eint_int"(%arg0, %arg1): (tensor<4x4x!FHE.eint<6>>, tensor<4x2xi7>) -> (tensor<4x2x!FHE.eint<6>>)
%tlu = arith.constant dense<[40, 13, 20, 62, 47, 41, 46, 30, 59, 58, 17, 4, 34, 44, 49, 5, 10, 63, 18, 21, 33, 45, 7, 14, 24, 53, 56, 3, 22, 29, 1, 39, 48, 32, 38, 28, 15, 12, 52, 35, 42, 11, 6, 43, 0, 16, 27, 9, 31, 51, 36, 37, 55, 57, 54, 2, 8, 25, 50, 23, 61, 60, 26, 19]> : tensor<64xi64>
%result = "FHELinalg.apply_lookup_table"(%0, %tlu): (tensor<4x2x!FHE.eint<6>>, tensor<64xi64>) -> (tensor<4x2x!FHE.eint<6>>)
return %result: tensor<4x2x!FHE.eint<6>>
} %0 = x # EncryptedScalar<uint4> ∈ [0, 9]
%1 = 2 # ClearScalar<uint2> ∈ [2, 2] @ abc
%2 = multiply(%0, %1) # EncryptedScalar<uint5> ∈ [0, 18] @ abc
%3 = 42 # ClearScalar<uint6> ∈ [42, 42] @ abc.foo
%4 = add(%2, %3) # EncryptedScalar<uint6> ∈ [42, 60] @ abc.foo
%5 = subgraph(%4) # EncryptedScalar<uint3> ∈ [6, 7] @ abc
%6 = 3 # ClearScalar<uint2> ∈ [3, 3]
%7 = add(%5, %6) # EncryptedScalar<uint4> ∈ [9, 10]
%8 = 120 # ClearScalar<uint7> ∈ [120, 120] @ def
%9 = subtract(%8, %7) # EncryptedScalar<uint7> ∈ [110, 111] @ def
%10 = 4 # ClearScalar<uint3> ∈ [4, 4] @ def
%11 = floor_divide(%9, %10) # EncryptedScalar<uint5> ∈ [27, 27] @ def
%12 = 2 # ClearScalar<uint2> ∈ [2, 2]
%13 = multiply(%11, %12) # EncryptedScalar<uint6> ∈ [54, 54]
return %13
Subgraphs:
%5 = subgraph(%4):
%0 = input # EncryptedScalar<uint2> @ abc.foo
%1 = sqrt(%0) # EncryptedScalar<float64> @ abc
%2 = astype(%1, dtype=int_) # EncryptedScalar<uint1> @ abc
return %2from concrete import fhe
@fhe.compiler({"x": "encrypted"})
def f(x):
return x ** 2
inputset = range(10)
circuit = f.compile(inputset)circuit.keys.generate()circuit.keys.generate(seed=420)serialized_keys: bytes = circuit.keys.serialize()keys: fhe.Keys = fhe.Keys.deserialize(serialized_keys)circuit.keys = keyscircuit.keys.save("/path/to/keys")circuit.keys.load("/path/to/keys")circuit.keys.load_if_exists_generate_and_save_otherwise("/path/to/keys")Game of Life in Concrete Python.
options:
-h, --help show this help message and exit
--dimension DIMENSION
Dimension of the grid
--refresh_every REFRESH_EVERY
Refresh the grid every X steps
--method {method_3b,method_4b,method_5b,method_basic}
Method for refreshing the grid
--log2_global_p_error LOG2_GLOBAL_P_ERROR
Probability of correctness issue (full circuit)
--log2_p_error LOG2_P_ERROR
Probability of correctness issue (individual TLU)
--simulate Simulate instead of running computations in FHE
--show_mlir Show the MLIR
--stop_after_compilation
Stop after compilation
--text_output Print a text output of the grid$ concretecompiler --action=compile -o python-demo example.mlir$ ls python-demo/
client_parameters.concrete.params.json compilation_feedback.json fhecircuit-client.h sharedlib.so staticlib.afrom concrete.compiler import (ClientSupport, LambdaArgument, LibrarySupport)lib_support = LibrarySupport.new("/path/to/your/python-demo/")
compilation_result = lib_support.reload()server_lambda = lib_support.load_server_lambda(compilation_result)
client_params = lib_support.load_client_parameters(compilation_result)client_support = ClientSupport.new()
key_set = client_support.key_set(client_params)
args = [
LambdaArgument.from_tensor_u8([1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4], [4, 4]),
LambdaArgument.from_tensor_u8([1, 2, 1, 2, 1, 2, 1, 2], [4, 2])
]
encrypted_args = client_support.encrypt_arguments(client_params, key_set, args)eval_keys = key_set.get_evaluation_keys()
encrypted_result = lib_support.server_call(server_lambda, encrypted_args, eval_keys)result_arg = client_support.decrypt_result(client_params, key_set, encrypted_result)
print("result tensor dims: {}".format(result_arg.n_values()))
print("result tensor data: {}".format(result_arg.get_values()))-DCONCRETELANG_CUDA_SUPPORT=${CUDA_SUPPORT}
-DCUDAToolkit_ROOT=$(CUDA_PATH)Tracing.trace_ciphertext (::mlir::concretelang::Tracing::TraceCiphertextOp)Tracing.trace_message (::mlir::concretelang::Tracing::TraceMessageOp)Tracing.trace_plaintext (::mlir::concretelang::Tracing::TracePlaintextOp)from concrete import fhe
import numpy as np
@fhe.compiler({"x": "encrypted"})
def f(x):
a = x + 1.5
b = np.sin(x)
c = np.around(a + b)
d = c.astype(np.int64)
return d
inputset = range(8)
circuit = f.compile(inputset)
for x in range(8):
assert circuit.encrypt_run_decrypt(x) == f(x)from concrete import fhe
@fhe.circuit({"x": "encrypted"})
def circuit(x: fhe.uint8):
return x + 42
assert circuit.encrypt_run_decrypt(10) == 52from concrete import fhe
import numpy as np
def square(value):
return value ** 2
@fhe.circuit({"x": "encrypted", "y": "encrypted"})
def circuit(x: fhe.uint8, y: fhe.int2):
a = x + 10
b = y + 10
c = np.sqrt(a).round().astype(fhe.uint4)
d = fhe.univariate(square, outputs=fhe.uint8)(b)
return d - c
print(circuit)%0 = x # EncryptedScalar<uint8>
%1 = y # EncryptedScalar<int2>
%2 = 10 # ClearScalar<uint4>
%3 = add(%0, %2) # EncryptedScalar<uint8>
%4 = 10 # ClearScalar<uint4>
%5 = add(%1, %4) # EncryptedScalar<int4>
%6 = subgraph(%3) # EncryptedScalar<uint4>
%7 = square(%5) # EncryptedScalar<uint8>
%8 = subtract(%7, %6) # EncryptedScalar<uint8>
return %8
Subgraphs:
%6 = subgraph(%3):
%0 = input # EncryptedScalar<uint8>
%1 = sqrt(%0) # EncryptedScalar<float64>
%2 = around(%1, decimals=0) # EncryptedScalar<float64>
%3 = astype(%2) # EncryptedScalar<uint4>
return %3%0 is uint8 because it's specified in the definition
%1 is int2 because it's specified in the definition
%2 is uint4 because it's the constant 10
%3 is uint8 because it's the addition between uint8 and uint4
%4 is uint4 because it's the constant 10
%5 is int4 because it's the addition between int2 and uint4
%6 is uint4 because it's specified in astype
%7 is uint8 because it's specified in univariate
%8 is uint8 because it's subtraction between uint8 and uint4make generate-curvesmake compare-curvesmake generate-codesage: X = load("128.sobj")sage: X["128"][0]
(2366, 64.0, 4.0, 128.51)sage: curves = load("verified_curves.sobj")sage: curves[2][:3]
(-0.026599462343105267, 2.981543184145991, 128)from concrete import fhe
import numpy as np
@fhe.compiler({"x": "encrypted", "y": "encrypted"})
def f(x, y):
a = x + 1.5
b = np.sin(y)
c = np.around(a + b)
d = c.astype(np.int64)
return d
inputset = [(1, 2), (3, 0), (2, 2), (1, 3)]
circuit = f.compile(inputset)
for x in range(8):
assert circuit.encrypt_run_decrypt(x) == f(x)RuntimeError: Function you are trying to compile cannot be converted to MLIR
%0 = x # EncryptedScalar<uint2>
%1 = 1.5 # ClearScalar<float64>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only integer constants are supported
%2 = y # EncryptedScalar<uint2>
%3 = add(%0, %1) # EncryptedScalar<float64>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only integer operations are supported
%4 = sin(%2) # EncryptedScalar<float64>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only integer operations are supported
%5 = add(%3, %4) # EncryptedScalar<float64>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only integer operations are supported
%6 = around(%5) # EncryptedScalar<float64>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only integer operations are supported
%7 = astype(%6, dtype=int_) # EncryptedScalar<uint3>
return %7from concrete import fhe
@fhe.compiler({"x": "encrypted"})
def function(x):
return x + 42
inputset = range(10)
circuit = function.compile(inputset)circuit.server.save("server.zip")from concrete import fhe
server = fhe.Server.load("server.zip")serialized_client_specs: str = server.client_specs.serialize()client_specs = fhe.ClientSpecs.deserialize(serialized_client_specs)
client = fhe.Client(client_specs)client.keys.generate()serialized_evaluation_keys: bytes = client.evaluation_keys.serialize()arg: fhe.Value = client.encrypt(7)
serialized_arg: bytes = arg.serialize()deserialized_evaluation_keys = fhe.EvaluationKeys.deserialize(serialized_evaluation_keys)
deserialized_arg = fhe.Value.deserialize(serialized_arg)result: fhe.Value = server.run(deserialized_arg, evaluation_keys=deserialized_evaluation_keys)
serialized_result: bytes = result.serialize()deserialized_result = fhe.Value.deserialize(serialized_result)decrypted_result = client.decrypt(deserialized_result)
assert decrypted_result == 49Evaluation: 10% |█████.............................................| 10% (scaling.r)
^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^
Title Progressbar TagEvaluation: 30% |███████████████...................................| 30% (scaling.g)Evaluation: 50% |█████████████████████████.........................| 50% (scaling.b)import time
import matplotlib.pyplot as plt
import numpy as np
import randimage
from concrete import fhe
configuration = fhe.Configuration(
enable_unsafe_features=True,
use_insecure_key_cache=True,
insecure_key_cache_location=".keys",
# To enable displaying progressbar
show_progress=True,
# To enable showing tags in the progressbar (does not work in notebooks)
progress_tag=True,
# To give a title to the progressbar
progress_title="Evaluation:",
)
@fhe.compiler({"image": "encrypted"})
def to_grayscale(image):
with fhe.tag("scaling.r"):
r = image[:, :, 0]
r = (r * 0.30).astype(np.int64)
with fhe.tag("scaling.g"):
g = image[:, :, 1]
g = (g * 0.59).astype(np.int64)
with fhe.tag("scaling.b"):
b = image[:, :, 2]
b = (b * 0.11).astype(np.int64)
with fhe.tag("combining.rgb"):
gray = r + g + b
with fhe.tag("creating.result"):
gray = np.expand_dims(gray, axis=2)
result = np.concatenate((gray, gray, gray), axis=2)
return result
image_size = (16, 16)
image_data = (randimage.get_random_image(image_size) * 255).round().astype(np.int64)
print()
print(f"Compilation started @ {time.strftime('%H:%M:%S', time.localtime())}")
start = time.time()
inputset = [np.random.randint(0, 256, size=image_data.shape) for _ in range(100)]
circuit = to_grayscale.compile(inputset, configuration)
end = time.time()
print(f"(took {end - start:.3f} seconds)")
print()
print(f"Key generation started @ {time.strftime('%H:%M:%S', time.localtime())}")
start = time.time()
circuit.keygen()
end = time.time()
print(f"(took {end - start:.3f} seconds)")
print()
print(f"Evaluation started @ {time.strftime('%H:%M:%S', time.localtime())}")
start = time.time()
grayscale_image_data = circuit.encrypt_run_decrypt(image_data)
end = time.time()
print(f"(took {end - start:.3f} seconds)")
fig, axs = plt.subplots(1, 2)
axs = axs.flatten()
axs[0].set_title("Original")
axs[0].imshow(image_data)
axs[0].axis("off")
axs[1].set_title("Grayscale")
axs[1].imshow(grayscale_image_data)
axs[1].axis("off")
plt.show()
from concrete import fhe
@fhe.compiler({"x": "encrypted"})
def f(x):
return (x**2) + (2*x) + 4
inputset = range(2**2)
circuit = f.compile(inputset, show_statistics=True)Statistics
--------------------------------------------------------------------------------
size_of_secret_keys: 22648
size_of_bootstrap_keys: 51274176
size_of_keyswitch_keys: 64092720
size_of_inputs: 16392
size_of_outputs: 16392
p_error: 9.627450598589458e-06
global_p_error: 9.627450598589458e-06
complexity: 99198195.0
programmable_bootstrap_count: 1
programmable_bootstrap_count_per_parameter: {
BootstrapKeyParam(polynomial_size=2048, glwe_dimension=1, input_lwe_dimension=781, level=1, base_log=23, variance=9.940977002694397e-32): 1
}
key_switch_count: 1
key_switch_count_per_parameter: {
KeyswitchKeyParam(level=5, base_log=3, variance=1.939836732335308e-11): 1
}
packing_key_switch_count: 0
clear_addition_count: 1
clear_addition_count_per_parameter: {
LweSecretKeyParam(dimension=2048): 1
}
encrypted_addition_count: 1
encrypted_addition_count_per_parameter: {
LweSecretKeyParam(dimension=2048): 1
}
clear_multiplication_count: 1
clear_multiplication_count_per_parameter: {
LweSecretKeyParam(dimension=2048): 1
}
encrypted_negation_count: 0
--------------------------------------------------------------------------------Statistics
--------------------------------------------------------------------------------
clear_multiplication_count_per_tag: {
/model/model: 53342
/model/model.0/Gemm: 14720
/model/model.0/Gemm.matmul: 14720
/model/model.2/Gemm: 11730
/model/model.2/Gemm.matmul: 11730
/model/model.4/Gemm: 9078
/model/model.4/Gemm.matmul: 9078
/model/model.6/Gemm: 6764
/model/model.6/Gemm.matmul: 6764
/model/model.8/Gemm: 4788
/model/model.8/Gemm.matmul: 4788
/model/model.10/Gemm: 3150
/model/model.10/Gemm.matmul: 3150
/model/model.12/Gemm: 1850
/model/model.12/Gemm.matmul: 1850
/model/model.14/Gemm: 888
/model/model.14/Gemm.matmul: 888
/model/model.16/Gemm: 264
/model/model.16/Gemm.matmul: 264
/model/model.18/Gemm: 110
/model/model.18/Gemm.matmul: 110
}
encrypted_addition_count_per_tag: {
/model/model: 53342
/model/model.0/Gemm: 14720
/model/model.0/Gemm.matmul: 14720
/model/model.2/Gemm: 11730
/model/model.2/Gemm.matmul: 11730
/model/model.4/Gemm: 9078
/model/model.4/Gemm.matmul: 9078
/model/model.6/Gemm: 6764
/model/model.6/Gemm.matmul: 6764
/model/model.8/Gemm: 4788
/model/model.8/Gemm.matmul: 4788
/model/model.10/Gemm: 3150
/model/model.10/Gemm.matmul: 3150
/model/model.12/Gemm: 1850
/model/model.12/Gemm.matmul: 1850
/model/model.14/Gemm: 888
/model/model.14/Gemm.matmul: 888
/model/model.16/Gemm: 264
/model/model.16/Gemm.matmul: 264
/model/model.18/Gemm: 110
/model/model.18/Gemm.matmul: 110
}
--------------------------------------------------------------------------------from concrete import fhe
@fhe.compiler({"x": "encrypted"})
def f(x):
return fhe.bits(x)[0], fhe.bits(x)[3]
inputset = range(32)
circuit = f.compile(inputset)
assert circuit.encrypt_run_decrypt(0b_00000) == (0, 0)
assert circuit.encrypt_run_decrypt(0b_00001) == (1, 0)
assert circuit.encrypt_run_decrypt(0b_01100) == (0, 1)
assert circuit.encrypt_run_decrypt(0b_01101) == (1, 1)from concrete import fhe
import numpy as np
@fhe.compiler({"x": "encrypted"})
def f(x):
return x + 42
inputset = range(10)
circuit = f.compile(inputset, p_error=0.01, dataflow_parallelize=True)from concrete import fhe
import numpy as np
configuration = fhe.Configuration(p_error=0.01)
@fhe.compiler({"x": "encrypted"})
def f(x):
return x + 42
inputset = range(10)
circuit = f.compile(inputset, configuration=configuration, loop_parallelize=True)x has.from concrete import fhe
@fhe.compiler({"x": "encrypted"})
def f(x):
return fhe.bits(x)[1:4]
inputset = range(32)
circuit = f.compile(inputset)
assert circuit.encrypt_run_decrypt(0b_01101) == 0b_110
assert circuit.encrypt_run_decrypt(0b_01011) == 0b_101from concrete import fhe
@fhe.compiler({"x": "encrypted"})
def f(x):
return fhe.bits(x)[3:0:-1]
inputset = range(32)
circuit = f.compile(inputset)
assert circuit.encrypt_run_decrypt(0b_01101) == 0b_011
assert circuit.encrypt_run_decrypt(0b_01011) == 0b_101from concrete import fhe
@fhe.compiler({"x": "encrypted"})
def f(x):
return fhe.bits(x)[1:3]
inputset = range(-16, 16)
circuit = f.compile(inputset)
assert circuit.encrypt_run_decrypt(-14) == 0b_01 # -14 == 0b_10010 (in two's complement)
assert circuit.encrypt_run_decrypt(-12) == 0b_10 # -12 == 0b_10100 (in two's complement)import numpy as np
from concrete import fhe
@fhe.compiler({"x": "encrypted"})
def is_even(x):
return 1 - fhe.bits(x)[0]
inputset = [
np.random.randint(-16, 16, size=(5,))
for _ in range(100)
]
circuit = is_even.compile(inputset)
sample = np.random.randint(-16, 16, size=(5,))
for value, value_is_even in zip(sample, circuit.encrypt_run_decrypt(sample)):
print(f"{value} is {'even' if value_is_even else 'odd'}")13 is odd
0 is even
-15 is odd
2 is even
-6 is evenfrom concrete import fhe
@fhe.compiler({"counter": "encrypted"})
def increment(counter):
return (counter + 1) % 100
print("Compiling `increment` function")
increment_fhe = increment.compile(list(range(0, 100)), composable=True)
print("Generating keyset ...")
increment_fhe.keygen()
print("Encrypting the initial counter value")
counter = 0
counter_enc = increment_fhe.encrypt(counter)
print(f"| iteration || decrypted | cleartext |")
for i in range(10):
counter_enc = increment_fhe.run(counter_enc)
counter = increment(counter)
# For demo purpose; no decryption is needed.
counter_dec = increment_fhe.decrypt(counter_enc)
print(f"| {i} || {counter_dec:<9} | {counter:<9} |")Compiling `increment` function
Generating keyset ...
Encrypting the initial counter value
| iteration || decrypted | cleartext |
| 0 || 1 | 1 |
| 1 || 2 | 2 |
| 2 || 3 | 3 |
| 3 || 4 | 4 |
| 4 || 5 | 5 |
| 5 || 6 | 6 |
| 6 || 7 | 7 |
| 7 || 8 | 8 |
| 8 || 9 | 9 |
| 9 || 10 | 10 |0b_1111 == 15xfhe.bits(x)[1:]*: [4, 4]

SDFG.get (::mlir::concretelang::SDFG::Get)from concrete import fhe
def noise_reset(x):
return fhe.univariate(lambda x: x)(x)
@fhe.compiler({"n1th": "encrypted", "nth": "encrypted"})
def fib(n1th, nth):
return noise_reset(nth), noise_reset(n1th + nth)
print("Compiling `fib` function ...")
inputset = list(zip(range(0, 100), range(0, 100)))
fib_fhe = fib.compile(inputset, composable=True)
print("Generating keyset ...")
fib_fhe.keygen()
print("Encrypting initial values")
n1th = 1
nth = 2
(n1th_enc, nth_enc) = fib_fhe.encrypt(n1th, nth)
print(f"| || (n-1)-th | n-th |")
print(f"| iteration || decrypted | cleartext | decrypted | cleartext |")
for i in range(10):
(n1th_enc, nth_enc) = fib_fhe.run(n1th_enc, nth_enc)
(n1th, nth) = fib(n1th, nth)
# For demo purpose; no decryption is needed.
(n1th_dec, nth_dec) = fib_fhe.decrypt(n1th_enc, nth_enc)
print(f"| {i} || {n1th_dec:<9} | {n1th:<9} | {nth_dec:<9} | {nth:<9} |")Compiling `fib` function ...
Generating keyset ...
Encrypting initial values
| || (n-1)-th | n-th |
| iteration || decrypted | cleartext | decrypted | cleartext |
| 0 || 2 | 2 | 3 | 3 |
| 1 || 3 | 3 | 5 | 5 |
| 2 || 5 | 5 | 8 | 8 |
| 3 || 8 | 8 | 13 | 13 |
| 4 || 13 | 13 | 21 | 21 |
| 5 || 21 | 21 | 34 | 34 |
| 6 || 34 | 34 | 55 | 55 |
| 7 || 55 | 55 | 89 | 89 |
| 8 || 89 | 89 | 144 | 144 |
| 9 || 144 | 144 | 233 | 233 |for i in some_cleartext_constant_range:
# Do something in FHE in the loop body, implement as an FHE circuit.from concrete import fhe
@fhe.compiler({"x": "encrypted"})
def collatz(x):
y = x // 2
z = 3 * x + 1
is_x_odd = fhe.bits(x)[0]
# In a fast way, compute ans = is_x_odd * (z - y) + y
ans = fhe.multivariate(lambda b, x: b * x)(is_x_odd, z - y) + y
is_one = ans == 1
return ans, is_one
print("Compiling `collatz` function ...")
inputset = [i for i in range(63)]
collatz_fhe = collatz.compile(inputset, composable=True)
print("Generating keyset ...")
collatz_fhe.keygen()
print("Encrypting initial value")
x = 19
x_enc = collatz_fhe.encrypt(x)
is_one_enc = None
print(f"| decrypted | cleartext |")
while is_one_enc is None or not collatz_fhe.decrypt(is_one_enc):
x_enc, is_one_enc = collatz_fhe.run(x_enc)
x, is_one = collatz(x)
# For demo purpose; no decryption is needed.
x_dec = collatz_fhe.decrypt(x_enc)
print(f"| {x_dec:<9} | {x:<9} |")Compiling `collatz` function ...
Generating keyset ...
Encrypting initial value
| decrypted | cleartext |
| 58 | 58 |
| 29 | 29 |
| 88 | 88 |
| 44 | 44 |
| 22 | 22 |
| 11 | 11 |
| 34 | 34 |
| 17 | 17 |
| 52 | 52 |
| 26 | 26 |
| 13 | 13 |
| 40 | 40 |
| 20 | 20 |
| 10 | 10 |
| 5 | 5 |
| 16 | 16 |
| 8 | 8 |
| 4 | 4 |
| 2 | 2 |
| 1 | 1 |@fhe.compiler({"counter": "encrypted"})
def double(counter):
return counter * 2def noise_reset(x):
return fhe.univariate(lambda x: x)(x)
@fhe.compiler({"counter": "encrypted"})
def double(counter):
return noise_reset(counter * 2)def f(x):
return (2 * x) + 3def f(x, y):
return x + 2 * y
x = Tracer(computation=Input("x"))
y = Tracer(computation=Input("y"))
resulting_tracer = f(x, y)[2, 3, 1]import numpy as np
from concrete import fhe
@fhe.compiler({"x": "encrypted", "y": "encrypted"})
def min_two(x, y):
diff = y - x
min_x_y = y - np.maximum(y - x, 0)
return min_x_y
inputset = [tuple(np.random.randint(0, 16, size=2)) for _ in range(50)]
circuit = min_two.compile(inputset)
x, y = np.random.randint(0, 16, size=2)
assert circuit.encrypt_run_decrypt(x, y) == min(x, y)import numpy as np
from concrete import fhe
@fhe.compiler({"x": "encrypted", "y": "encrypted"})
def max_two(x, y):
diff = y - x
max_x_y = y - np.minimum(y - x, 0)
return max_x_y
inputset = [tuple(np.random.randint(0, 16, size=2)) for _ in range(50)]
circuit = max_two.compile(inputset)
x, y = np.random.randint(0, 16, size=2)
assert circuit.encrypt_run_decrypt(x, y) == max(x, y)import numpy as np
from concrete import fhe
@fhe.compiler({"args": "encrypted"})
def fhe_min(args):
remaining = list(args)
while len(remaining) > 1:
a = remaining.pop()
b = remaining.pop()
min_a_b = b - np.maximum(b - a, 0)
remaining.insert(0, min_a_b)
return remaining[0]
inputset = [np.random.randint(0, 16, size=5) for _ in range(50)]
circuit = fhe_min.compile(inputset)
x1, x2, x3, x4, x5 = np.random.randint(0, 16, size=5)
assert circuit.encrypt_run_decrypt([x1, x2, x3, x4, x5]) == min(x1, x2, x3, x4, x5)import numpy as np
from concrete import fhe
@fhe.compiler({"array": "encrypted", "index": "encrypted"})
def indexed_value(array, index):
all_indices = np.arange(array.size)
index_selection = index == all_indices
selection_and_zeros = array * index_selection
selection = np.sum(selection_and_zeros)
return selection
inputset = [(np.random.randint(0, 16, size=5), np.random.randint(0, 5)) for _ in range(50)]
circuit = indexed_value.compile(inputset)
array = np.random.randint(0, 16, size=5)
index = np.random.randint(0, 5)
assert circuit.encrypt_run_decrypt(array, index) == array[index]import numpy as np
from concrete import fhe
@fhe.compiler({"numbers": "encrypted", "threshold": "encrypted"})
def filtering(numbers, threshold):
is_greater = numbers > threshold
shifted_numbers = numbers * 2 # open space for a single bit at the end
combined_numbers_and_is_greater = shifted_numbers + is_greater # put is_greater to that bit
def extract(combination):
is_greater = (combination % 2) == 1 # extract is_greater back from packing
if_true = combination // 2 # if is greater is true, we unpack the number and use it
if_false = 0 # otherwise we set the element to zero
return np.where(is_greater, if_true, if_false) # and apply the operation
return fhe.univariate(extract)(combined_numbers_and_is_greater)
inputset = [(np.random.randint(0, 16, size=5), np.random.randint(0, 16)) for _ in range(50)]
circuit = filtering.compile(inputset)
numbers = np.random.randint(0, 16, size=5)
threshold = np.random.randint(0, 16)
assert np.array_equal(circuit.encrypt_run_decrypt(numbers, threshold), list(map(lambda x: x if x > threshold else 0, numbers)))
import numpy as np
from concrete import fhe
def smallest_prime_divisor(n):
if n % 2 == 0:
return 2
for i in range(3, int(np.sqrt(n)) + 1):
if n % i == 0:
return i
return n
def mean_of_vector(x):
assert x.size != 0
if x.size == 1:
return x[0]
group_size = smallest_prime_divisor(x.size)
if x.size == group_size:
return np.round(np.sum(x) / x.size).astype(np.int64)
groups = []
for i in range(x.size // group_size):
start = i * group_size
end = start + group_size
groups.append(x[start:end])
mean_of_groups = []
for group in groups:
mean_of_groups.append(np.round(np.sum(group) / group_size).astype(np.int64))
return mean_of_vector(fhe.array(mean_of_groups))
@fhe.compiler(({"x": "encrypted"}))
def mean_of_matrix(x):
return mean_of_vector(x.flatten())
@fhe.compiler(({"x": "encrypted"}))
def mean_of_rows_of_matrix(x):
means = []
for i in range(x.shape[0]):
means.append(mean_of_vector(x[i]))
return fhe.array(means)
@fhe.compiler(({"x": "encrypted"}))
def mean_of_columns_of_matrix(x):
means = []
for i in range(x.shape[1]):
means.append(mean_of_vector(x[:, i]))
return fhe.array(means)
inputset = [np.random.randint(0, 16, size=(5,5)) for _ in range(50)]
matrix = np.random.randint(0, 16, size=(5, 5))
circuit = mean_of_matrix.compile(inputset)
assert circuit.encrypt_run_decrypt(matrix) == round(matrix.mean())
circuit = mean_of_rows_of_matrix.compile(inputset)
assert np.array_equal(circuit.encrypt_run_decrypt(matrix), [round(x) for x in matrix.mean(1)])
circuit = mean_of_columns_of_matrix.compile(inputset)
assert np.array_equal(circuit.encrypt_run_decrypt(matrix), [round(x) for x in matrix.mean(0)])"SDFG.get" (%stream) : (!SDFG.stream<1024xi64>) -> (tensor<1024xi64>)"SDFG.init" : () -> !SDFG.dfg%in0 = "SDFG.make_stream" { type = #SDFG.stream_kind<host_to_device> }(%dfg) : (!SDFG.dfg) -> !SDFG.stream<tensor<1024xi64>>
%in1 = "SDFG.make_stream" { type = #SDFG.stream_kind<host_to_device> }(%dfg) : (!SDFG.dfg) -> !SDFG.stream<tensor<1024xi64>>
%out = "SDFG.make_stream" { type = #SDFG.stream_kind<device_to_host> }(%dfg) : (!SDFG.dfg) -> !SDFG.stream<tensor<1024xi64>>
"SDFG.make_process" { type = #SDFG.process_kind<add_eint> }(%dfg, %in0, %in1, %out) :
(!SDFG.dfg, !SDFG.stream<tensor<1024xi64>>, !SDFG.stream<tensor<1024xi64>>, !SDFG.stream<tensor<1024xi64>>) -> ()"SDFG.make_stream" { name = "stream", type = #SDFG.stream_kind<host_to_device> }(%dfg)
: (!SDFG.dfg) -> !SDFG.stream<tensor<1024xi64>>"SDFG.put" (%stream, %data) : (!SDFG.stream<1024xi64>, tensor<1024xi64>) -> ()"SDFG.shutdown" (%dfg) : !SDFG.dfg"SDFG.start"(%dfg) : !SDFG.dfg#SDFG.process_kind<
::mlir::concretelang::SDFG::ProcessKind # value
>#SDFG.stream_kind<
::mlir::concretelang::SDFG::StreamKind # value
>RT.await_future (::mlir::concretelang::RT::AwaitFutureOp)RT.deref_work_function_argument_ptr_placeholder (::mlir::concretelang::RT::DerefWorkFunctionArgumentPtrPlaceholderOp)func @test(%0 : i64): (i64, i64) {
// Execute right now as %0 is ready.
%1, %2 = "RT.dataflow_task"(%0) ({
%a = addi %0, %0 : i64
%b = muli %0, %0 : i64
"RT.dataflow_yield"(%a, %b) : (i64, i64) -> i64
}) : (i64, i64) -> (i64, i64)
// Concurrently execute both tasks below when the task above is completed.
%3 = "RT.dataflow_task"(%1) ({
%c = constant 1 : %i64
%a = addi %1, %c : i64
"RT.dataflow_yield"(%a) : (i64, i64) -> i64
}) : (i64, i64) -> (i64, i64)
%4 = "RT.dataflow_task"(%2) ({
%c = constant 2 : %i64
%a = addi %2, %c : i64
"RT.dataflow_yield"(%a) : (i64, i64) -> i64
}) : (i64, i64) -> (i64, i64)
return %3, %4 : (i64, i64)
}%0 = constant 1 : i64
%1 = constant 2 : i64
"RT.dataflow_yield" %0, %1 : i64, i64!RT.future<i64>import numpy as np
from concrete import fhe
def complex_univariate_function(x):
def per_element(element):
result = 0
for i in range(element):
result += i
return result
return np.vectorize(per_element)(x)
@fhe.compiler({"x": "encrypted"})
def f(x):
return fhe.univariate(complex_univariate_function)(x)
inputset = [np.random.randint(0, 5, size=(3, 2)) for _ in range(10)]
circuit = f.compile(inputset)
sample = np.array([
[0, 4],
[2, 1],
[3, 0],
])
assert np.array_equal(circuit.encrypt_run_decrypt(sample), complex_univariate_function(sample))import matplotlib.pyplot as plt
import numpy as np
from concrete import fhe
original_bit_width = 5
lsbs_to_remove = 2
assert 0 < lsbs_to_remove < original_bit_width
original_values = list(range(2**original_bit_width))
truncated_values = [
fhe.truncate_bit_pattern(value, lsbs_to_remove)
for value in original_values
]
previous_truncated = truncated_values[0]
for original, truncated in zip(original_values, truncated_values):
if truncated != previous_truncated:
previous_truncated = truncated
print()
original_binary = np.binary_repr(original, width=(original_bit_width + 1))
truncated_binary = np.binary_repr(truncated, width=(original_bit_width + 1))
print(
f"{original:2} = 0b_{original_binary[:-lsbs_to_remove]}[{original_binary[-lsbs_to_remove:]}] "
f"=> "
f"0b_{truncated_binary[:-lsbs_to_remove]}[{truncated_binary[-lsbs_to_remove:]}] = {truncated}"
)
fig = plt.figure()
ax = fig.add_subplot()
plt.plot(original_values, original_values, label="original", color="black")
plt.plot(original_values, truncated_values, label="truncated", color="green")
plt.legend()
ax.set_aspect("equal", adjustable="box")
plt.show()


import numpy as np
from concrete import fhe
def value_if_condition_else_zero(value, condition):
return value if condition else np.zeros_like(value, dtype=np.int64)
def function(x, y):
return fhe.multivariate(value_if_condition_else_zero)(x, y)
inputset = [
(
np.random.randint(-2**4, 2**4, size=(2, 2)),
np.random.randint(0, 2**1, size=()),
)
for _ in range(100)
]
compiler = fhe.Compiler(function, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset)
sample = [np.array([[-2, 4], [0, 1]]), 0]
assert np.array_equal(circuit.encrypt_run_decrypt(*sample), function(*sample))
sample = [np.array([[3, -1], [2, 4]]), 1]
assert np.array_equal(circuit.encrypt_run_decrypt(*sample), function(*sample))import numpy as np
from concrete import fhe
weight = np.array([[2, 1], [3, 2]]).reshape(1, 1, 2, 2)
@fhe.compiler({"x": "encrypted"})
def f(x):
return fhe.conv(x, weight, strides=(2, 2), dilations=(1, 1), group=1)
inputset = [np.random.randint(0, 4, size=(1, 1, 4, 4)) for _ in range(10)]
circuit = f.compile(inputset)
sample = np.array(
[
[3, 2, 1, 0],
[3, 2, 1, 0],
[3, 2, 1, 0],
[3, 2, 1, 0],
]
).reshape(1, 1, 4, 4)
assert np.array_equal(circuit.encrypt_run_decrypt(sample), f(sample))import numpy as np
from concrete import fhe
@fhe.compiler({"x": "encrypted"})
def f(x):
return fhe.maxpool(x, kernel_shape=(2, 2), strides=(2, 2), dilations=(1, 1))
inputset = [np.random.randint(0, 4, size=(1, 1, 4, 4)) for _ in range(10)]
circuit = f.compile(inputset)
sample = np.array(
[
[3, 2, 1, 0],
[3, 2, 1, 0],
[3, 2, 1, 0],
[3, 2, 1, 0],
]
).reshape(1, 1, 4, 4)
assert np.array_equal(circuit.encrypt_run_decrypt(sample), f(sample))import numpy as np
from concrete import fhe
@fhe.compiler({"x": "encrypted", "y": "encrypted"})
def f(x, y):
return fhe.array([x, y])
inputset = [(3, 2), (7, 0), (0, 7), (4, 2)]
circuit = f.compile(inputset)
sample = (3, 4)
assert np.array_equal(circuit.encrypt_run_decrypt(*sample), f(*sample))from concrete import fhe
import numpy as np
@fhe.compiler({"x": "encrypted"})
def f(x):
z = fhe.zero()
return x + z
inputset = range(10)
circuit = f.compile(inputset)
for x in range(10):
assert circuit.encrypt_run_decrypt(x) == xfrom concrete import fhe
import numpy as np
@fhe.compiler({"x": "encrypted"})
def f(x):
z = fhe.zeros((2, 3))
return x + z
inputset = range(10)
circuit = f.compile(inputset)
for x in range(10):
assert np.array_equal(circuit.encrypt_run_decrypt(x), np.array([[x, x, x], [x, x, x]]))from concrete import fhe
import numpy as np
@fhe.compiler({"x": "encrypted"})
def f(x):
z = fhe.one()
return x + z
inputset = range(10)
circuit = f.compile(inputset)
for x in range(10):
assert circuit.encrypt_run_decrypt(x) == x + 1from concrete import fhe
import numpy as np
@fhe.compiler({"x": "encrypted"})
def f(x):
z = fhe.ones((2, 3))
return x + z
inputset = range(10)
circuit = f.compile(inputset)
for x in range(10):
assert np.array_equal(circuit.encrypt_run_decrypt(x), np.array([[x, x, x], [x, x, x]]) + 1)from concrete import fhe
import numpy as np
@fhe.compiler({"x": "encrypted"})
def f(x, y, z):
a = x | y
b = y & z
c = a ^ b
return c
inputset = [
(np.random.randint(0, 2**8), np.random.randint(0, 2**8), np.random.randint(0, 2**8))
for _ in range(3)
]
circuit = f.compile(inputset)
print(circuit)%0 = x # EncryptedScalar<uint8> ∈ [173, 240]
%1 = y # EncryptedScalar<uint8> ∈ [52, 219]
%2 = z # EncryptedScalar<uint8> ∈ [36, 252]
%3 = bitwise_or(%0, %1) # EncryptedScalar<uint8> ∈ [243, 255]
%4 = bitwise_and(%1, %2) # EncryptedScalar<uint7> ∈ [0, 112]
^^^^^ this can lead to bugs
%5 = bitwise_xor(%3, %4) # EncryptedScalar<uint8> ∈ [131, 255]
return %5@fhe.compiler({"x": "encrypted", "y": "encrypted", "z": "encrypted"})
def f(x, y, z):
# hint that inputs should be considered at least 8-bits
x = fhe.hint(x, bit_width=8)
y = fhe.hint(y, bit_width=8)
z = fhe.hint(z, bit_width=8)
# hint that intermediates should be considered at least 8-bits
a = fhe.hint(x | y, bit_width=8)
b = fhe.hint(y & z, bit_width=8)
c = fhe.hint(a ^ b, bit_width=8)
return c%0 = x # EncryptedScalar<uint8> ∈ [...]
%1 = y # EncryptedScalar<uint8> ∈ [...]
%2 = z # EncryptedScalar<uint8> ∈ [...]
%3 = bitwise_or(%0, %1) # EncryptedScalar<uint8> ∈ [...]
%4 = bitwise_and(%1, %2) # EncryptedScalar<uint8> ∈ [...]
%5 = bitwise_xor(%3, %4) # EncryptedScalar<uint8> ∈ [...]
return %5@fhe.compiler({"x": "encrypted", "y": "encrypted"})
def is_vectors_same(x, y):
assert x.ndim != 1
assert y.ndim != 1
assert len(x) == len(y)
n = len(x)
number_of_same_elements = np.sum(x == y)
fhe.hint(number_of_same_elements, can_store=n) # hint that number of same elements can go up to n
is_same = number_of_same_elements == n
return is_same 0 = 0b_0000[00] => 0b_0000[00] = 0
1 = 0b_0000[01] => 0b_0000[00] = 0
2 = 0b_0000[10] => 0b_0000[00] = 0
3 = 0b_0000[11] => 0b_0000[00] = 0
4 = 0b_0001[00] => 0b_0001[00] = 4
5 = 0b_0001[01] => 0b_0001[00] = 4
6 = 0b_0001[10] => 0b_0001[00] = 4
7 = 0b_0001[11] => 0b_0001[00] = 4
8 = 0b_0010[00] => 0b_0010[00] = 8
9 = 0b_0010[01] => 0b_0010[00] = 8
10 = 0b_0010[10] => 0b_0010[00] = 8
11 = 0b_0010[11] => 0b_0010[00] = 8
12 = 0b_0011[00] => 0b_0011[00] = 12
13 = 0b_0011[01] => 0b_0011[00] = 12
14 = 0b_0011[10] => 0b_0011[00] = 12
15 = 0b_0011[11] => 0b_0011[00] = 12
16 = 0b_0100[00] => 0b_0100[00] = 16
17 = 0b_0100[01] => 0b_0100[00] = 16
18 = 0b_0100[10] => 0b_0100[00] = 16
19 = 0b_0100[11] => 0b_0100[00] = 16
20 = 0b_0101[00] => 0b_0101[00] = 20
21 = 0b_0101[01] => 0b_0101[00] = 20
22 = 0b_0101[10] => 0b_0101[00] = 20
23 = 0b_0101[11] => 0b_0101[00] = 20
24 = 0b_0110[00] => 0b_0110[00] = 24
25 = 0b_0110[01] => 0b_0110[00] = 24
26 = 0b_0110[10] => 0b_0110[00] = 24
27 = 0b_0110[11] => 0b_0110[00] = 24
28 = 0b_0111[00] => 0b_0111[00] = 28
29 = 0b_0111[01] => 0b_0111[00] = 28
30 = 0b_0111[10] => 0b_0111[00] = 28
31 = 0b_0111[11] => 0b_0111[00] = 28import itertools
import time
import matplotlib.pyplot as plt
import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
enable_unsafe_features=True,
use_insecure_key_cache=True,
insecure_key_cache_location=".keys",
)
input_bit_width = 6
input_range = np.array(range(2**input_bit_width))
timings = {}
results = {}
for lsbs_to_remove in range(input_bit_width):
@fhe.compiler({"x": "encrypted"})
def f(x):
return fhe.truncate_bit_pattern(x, lsbs_to_remove) ** 2
circuit = f.compile(inputset=[input_range], configuration=configuration)
circuit.keygen()
encrypted_sample = circuit.encrypt(input_range)
start = time.time()
encrypted_result = circuit.run(encrypted_sample)
end = time.time()
result = circuit.decrypt(encrypted_result)
took = end - start
timings[lsbs_to_remove] = took
results[lsbs_to_remove] = result
number_of_figures = len(results)
columns = 1
for i in range(2, number_of_figures):
if number_of_figures % i == 0:
columns = i
rows = number_of_figures // columns
fig, axs = plt.subplots(rows, columns)
axs = axs.flatten()
baseline = timings[0]
for lsbs_to_remove in range(input_bit_width):
timing = timings[lsbs_to_remove]
speedup = baseline / timing
print(f"lsbs_to_remove={lsbs_to_remove} => {speedup:.2f}x speedup")
axs[lsbs_to_remove].set_title(f"lsbs_to_remove={lsbs_to_remove}")
axs[lsbs_to_remove].plot(input_range, results[lsbs_to_remove])
plt.show()lsbs_to_remove=0 => 1.00x speedup
lsbs_to_remove=1 => 1.69x speedup
lsbs_to_remove=2 => 3.48x speedup
lsbs_to_remove=3 => 3.06x speedup
lsbs_to_remove=4 => 3.46x speedup
lsbs_to_remove=5 => 3.14x speedupimport itertools
import time
import matplotlib.pyplot as plt
import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
enable_unsafe_features=True,
use_insecure_key_cache=True,
insecure_key_cache_location=".keys",
single_precision=False,
parameter_selection_strategy=fhe.ParameterSelectionStrategy.MULTI,
)
input_bit_width = 6
input_range = np.array(range(2**input_bit_width))
timings = {}
results = {}
for target_msbs in reversed(range(1, input_bit_width + 1)):
truncator = fhe.AutoTruncator(target_msbs)
@fhe.compiler({"x": "encrypted"})
def f(x):
return fhe.truncate_bit_pattern(x, lsbs_to_remove=truncator) ** 2
fhe.AutoTruncator.adjust(f, inputset=[input_range])
circuit = f.compile(inputset=[input_range], configuration=configuration)
circuit.keygen()
encrypted_sample = circuit.encrypt(input_range)
start = time.time()
encrypted_result = circuit.run(encrypted_sample)
end = time.time()
result = circuit.decrypt(encrypted_result)
took = end - start
timings[target_msbs] = took
results[target_msbs] = result
number_of_figures = len(results)
columns = 1
for i in range(2, number_of_figures):
if number_of_figures % i == 0:
columns = i
rows = number_of_figures // columns
fig, axs = plt.subplots(rows, columns)
axs = axs.flatten()
baseline = timings[input_bit_width]
for i, target_msbs in enumerate(reversed(range(1, input_bit_width + 1))):
timing = timings[target_msbs]
speedup = baseline / timing
print(f"target_msbs={target_msbs} => {speedup:.2f}x speedup")
axs[i].set_title(f"target_msbs={target_msbs}")
axs[i].plot(input_range, results[target_msbs])
plt.show()target_msbs=6 => 1.00x speedup
target_msbs=5 => 1.80x speedup
target_msbs=4 => 3.47x speedup
target_msbs=3 => 3.02x speedup
target_msbs=2 => 3.38x speedup
target_msbs=1 => 3.37x speedup(x - y).bit_width <= MAXIMUM_TLU_BIT_WIDTH# (example below is for bit-width of 8 and chunk size of 4)
# compare lhs and rhs
select_lhs = lhs < rhs # or lhs > rhs for maximum
# multiply lhs with select_lhs
lhs_contribution = lhs * select_lhs
# multiply rhs with 1 - select_lhs
rhs_contribution = rhs * (1 - select_lhs)
# compute the result
result = lhs_contribution + rhs_contributionimport numpy as np
from concrete import fhe
def f(x, y):
return np.minimum(x, y)
inputset = [
(np.random.randint(0, 2**4), np.random.randint(0, 2**4))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, show_mlir=True)module {
func.func @main(%arg0: !FHE.eint<4>, %arg1: !FHE.eint<4>) -> !FHE.eint<4> {
// calculating select_x, which is x < y since we're computing the minimum
%cst = arith.constant dense<[0, 0, 0, 0, 4, 4, 4, 4, 8, 8, 8, 8, 12, 12, 12, 12]> : tensor<16xi64>
%0 = "FHE.apply_lookup_table"(%arg0, %cst) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
%cst_0 = arith.constant dense<[0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3]> : tensor<16xi64>
%1 = "FHE.apply_lookup_table"(%arg1, %cst_0) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
%2 = "FHE.add_eint"(%0, %1) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
%cst_1 = arith.constant dense<[0, 1, 1, 1, 2, 0, 1, 1, 2, 2, 0, 1, 2, 2, 2, 0]> : tensor<16xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst_1) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
%cst_2 = arith.constant dense<[0, 4, 8, 12, 0, 4, 8, 12, 0, 4, 8, 12, 0, 4, 8, 12]> : tensor<16xi64>
%4 = "FHE.apply_lookup_table"(%arg0, %cst_2) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
%cst_3 = arith.constant dense<[0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]> : tensor<16xi64>
%5 = "FHE.apply_lookup_table"(%arg1, %cst_3) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
%6 = "FHE.add_eint"(%4, %5) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
%cst_4 = arith.constant dense<[0, 4, 4, 4, 8, 0, 4, 4, 8, 8, 0, 4, 8, 8, 8, 0]> : tensor<16xi64>
%7 = "FHE.apply_lookup_table"(%6, %cst_4) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
%8 = "FHE.add_eint"(%7, %3) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
%cst_5 = arith.constant dense<[0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0]> : tensor<16xi64>
%9 = "FHE.apply_lookup_table"(%8, %cst_5) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<1>
// extracting the first 2 bits of x shifhted to left by 1 bits for packing
%cst_6 = arith.constant dense<[0, 2, 4, 6, 0, 2, 4, 6, 0, 2, 4, 6, 0, 2, 4, 6]> : tensor<16xi64>
%10 = "FHE.apply_lookup_table"(%arg0, %cst_6) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<3>
// casting select_x to 3 bits for packing
%cst_7 = arith.constant dense<[0, 1]> : tensor<2xi64>
%11 = "FHE.apply_lookup_table"(%9, %cst_7) : (!FHE.eint<1>, tensor<2xi64>) -> !FHE.eint<3>
// packing the first 2 bits of x with select_x
%12 = "FHE.add_eint"(%10, %11) : (!FHE.eint<3>, !FHE.eint<3>) -> !FHE.eint<3>
// calculating contribution of 0 if select_x is 0 else the first 2 bits of x
%cst_8 = arith.constant dense<[0, 0, 0, 1, 0, 2, 0, 3]> : tensor<8xi64>
%13 = "FHE.apply_lookup_table"(%12, %cst_8) : (!FHE.eint<3>, tensor<8xi64>) -> !FHE.eint<4>
// extracting the last 2 bits of x shifhted to the left by 1 bit for packing
%cst_9 = arith.constant dense<[0, 0, 0, 0, 2, 2, 2, 2, 4, 4, 4, 4, 6, 6, 6, 6]> : tensor<16xi64>
%14 = "FHE.apply_lookup_table"(%arg0, %cst_9) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<3>
// packing the last 2 bits of x with select_x
%15 = "FHE.add_eint"(%14, %11) : (!FHE.eint<3>, !FHE.eint<3>) -> !FHE.eint<3>
// calculating contribution of 0 if select_x is 0 else the last 2 bits of x shifted by 2 bits for direct addition
%cst_10 = arith.constant dense<[0, 0, 0, 4, 0, 8, 0, 12]> : tensor<8xi64>
%16 = "FHE.apply_lookup_table"(%15, %cst_10) : (!FHE.eint<3>, tensor<8xi64>) -> !FHE.eint<4>
// computing x * select_x
%17 = "FHE.add_eint"(%13, %16) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
// extracting the first 2 bits of y shifhted to the left by 1 bit for packing
%18 = "FHE.apply_lookup_table"(%arg1, %cst_6) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<3>
// packing the first 2 bits of y with select_x
%19 = "FHE.add_eint"(%18, %11) : (!FHE.eint<3>, !FHE.eint<3>) -> !FHE.eint<3>
// calculating contribution of 0 if select_x is 1 else the first 2 bits of y
%cst_11 = arith.constant dense<[0, 0, 1, 0, 2, 0, 3, 0]> : tensor<8xi64>
%20 = "FHE.apply_lookup_table"(%19, %cst_11) : (!FHE.eint<3>, tensor<8xi64>) -> !FHE.eint<4>
// extracting the last 2 bits of y shifhted to left by 1 bit for packing
%21 = "FHE.apply_lookup_table"(%arg1, %cst_9) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<3>
// packing the last 2 bits of y with select_x
%22 = "FHE.add_eint"(%21, %11) : (!FHE.eint<3>, !FHE.eint<3>) -> !FHE.eint<3>
// calculating contribution of 0 if select_x is 1 else the last 2 bits of y shifted by 2 bits for direct addition
%cst_12 = arith.constant dense<[0, 0, 4, 0, 8, 0, 12, 0]> : tensor<8xi64>
%23 = "FHE.apply_lookup_table"(%22, %cst_12) : (!FHE.eint<3>, tensor<8xi64>) -> !FHE.eint<4>
// computing y * (1 - select_x)
%24 = "FHE.add_eint"(%20, %23) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
// computing the result
%25 = "FHE.add_eint"(%17, %24) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
return %25 : !FHE.eint<4>
}
}comparison_lut = fhe.LookupTable([...])
result = comparison_lut[x_promoted_to_uint7 - y_promoted_to_uint7] + y_promoted_to_uint7import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
min_max_strategy_preference=fhe.MinMaxStrategy.ONE_TLU_PROMOTED,
)
def f(x, y):
return np.minimum(x, y)
inputset = [
(np.random.randint(0, 2**4), np.random.randint(0, 2**2))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// promotions ............ ............
func.func @main(%arg0: !FHE.eint<5>, %arg1: !FHE.eint<5>) -> !FHE.eint<5> {
// subtraction
%0 = "FHE.to_signed"(%arg0) : (!FHE.eint<5>) -> !FHE.esint<5>
%1 = "FHE.to_signed"(%arg1) : (!FHE.eint<5>) -> !FHE.esint<5>
%2 = "FHE.sub_eint"(%0, %1) : (!FHE.esint<5>, !FHE.esint<5>) -> !FHE.esint<5>
// tlu
%cst = arith.constant dense<[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1]> : tensor<32xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst) : (!FHE.esint<5>, tensor<32xi64>) -> !FHE.eint<5>
// addition
%4 = "FHE.add_eint"(%3, %arg1) : (!FHE.eint<5>, !FHE.eint<5>) -> !FHE.eint<5>
return %4 : !FHE.eint<5>
}
}uint3_to_uint7_lut = fhe.LookupTable([...])
x_cast_to_uint7 = uint3_to_uint7_lut[x]
uint6_to_uint7_lut = fhe.LookupTable([...])
y_cast_to_uint7 = uint6_to_uint7_lut[y]
comparison_lut = fhe.LookupTable([...])
result = comparison_lut[x_cast_to_uint7 - y_cast_to_uint7] + yimport numpy as np
from concrete import fhe
configuration = fhe.Configuration(
min_max_strategy_preference=fhe.MinMaxStrategy.THREE_TLU_CASTED,
)
def f(x, y):
return np.minimum(x, y)
inputset = [
(np.random.randint(0, 2**4), np.random.randint(0, 2**2))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// no promotions
func.func @main(%arg0: !FHE.eint<4>, %arg1: !FHE.eint<2>) -> !FHE.eint<2> {
// casting x
%cst = arith.constant dense<[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]> : tensor<16xi64>
%0 = "FHE.apply_lookup_table"(%arg0, %cst) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.esint<5>
// casting y
%cst_0 = arith.constant dense<[0, 1, 2, 3]> : tensor<4xi64>
%1 = "FHE.apply_lookup_table"(%arg1, %cst_0) : (!FHE.eint<2>, tensor<4xi64>) -> !FHE.esint<5>
// subtraction
%2 = "FHE.sub_eint"(%0, %1) : (!FHE.esint<5>, !FHE.esint<5>) -> !FHE.esint<5>
// tlu
%cst_1 = arith.constant dense<[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1]> : tensor<32xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst_1) : (!FHE.esint<5>, tensor<32xi64>) -> !FHE.eint<2>
// addition
%4 = "FHE.add_eint"(%3, %arg1) : (!FHE.eint<2>, !FHE.eint<2>) -> !FHE.eint<2>
return %4 : !FHE.eint<2>
}
}
func.func @main(%arg0: tensor<4x3x!FHE.eint<2>>, %arg1: tensor<3x2xi3>) -> tensor<4x2x!FHE.eint<2>> {
%0 = "FHELinalg.matmul_eint_int"(%arg0, %arg1) : (tensor<4x3x!FHE.eint<2>>, tensor<3x2xi3>) -> tensor<4x2x!FHE.eint<2>>
return %0 : tensor<4x2x!FHE.eint<2>>
}#map = affine_map<(d0, d1, d2) -> (d0, d2)>
#map1 = affine_map<(d0, d1, d2) -> (d2, d1)>
#map2 = affine_map<(d0, d1, d2) -> (d0, d1)>
func.func @main(%arg0: tensor<4x3x!FHE.eint<2>>, %arg1: tensor<3x2xi3>) -> tensor<4x2x!FHE.eint<2>> {
%0 = "FHE.zero_tensor"() : () -> tensor<4x2x!FHE.eint<2>>
%1 = linalg.generic {indexing_maps = [#map, #map1, #map2], iterator_types = ["parallel", "parallel", "reduction"]} ins(%arg0, %arg1 : tensor<4x3x!FHE.eint<2>>, tensor<3x2xi3>) outs(%0 : tensor<4x2x!FHE.eint<2>>) {
^bb0(%in: !FHE.eint<2>, %in_0: i3, %out: !FHE.eint<2>):
%2 = "FHE.mul_eint_int"(%in, %in_0) : (!FHE.eint<2>, i3) -> !FHE.eint<2>
%3 = "FHE.add_eint"(%out, %2) : (!FHE.eint<2>, !FHE.eint<2>) -> !FHE.eint<2>
linalg.yield %3 : !FHE.eint<2>
} -> tensor<4x2x!FHE.eint<2>>
return %1 : tensor<4x2x!FHE.eint<2>>
}func.func @main(%arg0: tensor<4x3x!FHE.eint<2>>, %arg1: tensor<3x2xi3>) -> tensor<4x2x!FHE.eint<2>> {
%c0 = arith.constant 0 : index
%c4 = arith.constant 4 : index
%c1 = arith.constant 1 : index
%c2 = arith.constant 2 : index
%c3 = arith.constant 3 : index
%0 = "FHE.zero_tensor"() : () -> tensor<4x2x!FHE.eint<2>>
%1 = scf.for %arg2 = %c0 to %c4 step %c1 iter_args(%arg3 = %0) -> (tensor<4x2x!FHE.eint<2>>) {
%2 = scf.for %arg4 = %c0 to %c2 step %c1 iter_args(%arg5 = %arg3) -> (tensor<4x2x!FHE.eint<2>>) {
%3 = scf.for %arg6 = %c0 to %c3 step %c1 iter_args(%arg7 = %arg5) -> (tensor<4x2x!FHE.eint<2>>) {
%extracted = tensor.extract %arg0[%arg2, %arg6] : tensor<4x3x!FHE.eint<2>>
%extracted_0 = tensor.extract %arg1[%arg6, %arg4] : tensor<3x2xi3>
%extracted_1 = tensor.extract %arg7[%arg2, %arg4] : tensor<4x2x!FHE.eint<2>>
%4 = "FHE.mul_eint_int"(%extracted, %extracted_0) : (!FHE.eint<2>, i3) -> !FHE.eint<2>
%5 = "FHE.add_eint"(%extracted_1, %4) : (!FHE.eint<2>, !FHE.eint<2>) -> !FHE.eint<2>
%inserted = tensor.insert %5 into %arg7[%arg2, %arg4] : tensor<4x2x!FHE.eint<2>>
scf.yield %inserted : tensor<4x2x!FHE.eint<2>>
}
scf.yield %3 : tensor<4x2x!FHE.eint<2>>
}
scf.yield %2 : tensor<4x2x!FHE.eint<2>>
}
return %1 : tensor<4x2x!FHE.eint<2>>
}func.func @main(%arg0: tensor<4x3x!TFHE.glwe<sk?>>, %arg1: tensor<3x2xi3>) -> tensor<4x2x!TFHE.glwe<sk?>> {
%c0 = arith.constant 0 : index
%c4 = arith.constant 4 : index
%c1 = arith.constant 1 : index
%c2 = arith.constant 2 : index
%c3 = arith.constant 3 : index
%0 = "TFHE.zero_tensor"() : () -> tensor<4x2x!TFHE.glwe<sk?>>
%1 = scf.for %arg2 = %c0 to %c4 step %c1 iter_args(%arg3 = %0) -> (tensor<4x2x!TFHE.glwe<sk?>>) {
%2 = scf.for %arg4 = %c0 to %c2 step %c1 iter_args(%arg5 = %arg3) -> (tensor<4x2x!TFHE.glwe<sk?>>) {
%3 = scf.for %arg6 = %c0 to %c3 step %c1 iter_args(%arg7 = %arg5) -> (tensor<4x2x!TFHE.glwe<sk?>>) {
%extracted = tensor.extract %arg0[%arg2, %arg6] : tensor<4x3x!TFHE.glwe<sk?>>
%extracted_0 = tensor.extract %arg1[%arg6, %arg4] : tensor<3x2xi3>
%extracted_1 = tensor.extract %arg7[%arg2, %arg4] : tensor<4x2x!TFHE.glwe<sk?>>
%4 = arith.extsi %extracted_0 : i3 to i64
%5 = "TFHE.mul_glwe_int"(%extracted, %4) : (!TFHE.glwe<sk?>, i64) -> !TFHE.glwe<sk?>
%6 = "TFHE.add_glwe"(%extracted_1, %5) : (!TFHE.glwe<sk?>, !TFHE.glwe<sk?>) -> !TFHE.glwe<sk?>
%inserted = tensor.insert %6 into %arg7[%arg2, %arg4] : tensor<4x2x!TFHE.glwe<sk?>>
scf.yield %inserted : tensor<4x2x!TFHE.glwe<sk?>>
}
scf.yield %3 : tensor<4x2x!TFHE.glwe<sk?>>
}
scf.yield %2 : tensor<4x2x!TFHE.glwe<sk?>>
}
return %1 : tensor<4x2x!TFHE.glwe<sk?>>
}func.func @main(%arg0: tensor<4x3x!TFHE.glwe<sk<0,1,512>>>, %arg1: tensor<3x2xi3>) -> tensor<4x2x!TFHE.glwe<sk<0,1,512>>> {
%c0 = arith.constant 0 : index
%c4 = arith.constant 4 : index
%c1 = arith.constant 1 : index
%c2 = arith.constant 2 : index
%c3 = arith.constant 3 : index
%0 = "TFHE.zero_tensor"() : () -> tensor<4x2x!TFHE.glwe<sk<0,1,512>>>
%1 = scf.for %arg2 = %c0 to %c4 step %c1 iter_args(%arg3 = %0) -> (tensor<4x2x!TFHE.glwe<sk<0,1,512>>>) {
%2 = scf.for %arg4 = %c0 to %c2 step %c1 iter_args(%arg5 = %arg3) -> (tensor<4x2x!TFHE.glwe<sk<0,1,512>>>) {
%3 = scf.for %arg6 = %c0 to %c3 step %c1 iter_args(%arg7 = %arg5) -> (tensor<4x2x!TFHE.glwe<sk<0,1,512>>>) {
%extracted = tensor.extract %arg0[%arg2, %arg6] : tensor<4x3x!TFHE.glwe<sk<0,1,512>>>
%extracted_0 = tensor.extract %arg1[%arg6, %arg4] : tensor<3x2xi3>
%extracted_1 = tensor.extract %arg7[%arg2, %arg4] : tensor<4x2x!TFHE.glwe<sk<0,1,512>>>
%4 = arith.extsi %extracted_0 : i3 to i64
%5 = "TFHE.mul_glwe_int"(%extracted, %4) : (!TFHE.glwe<sk<0,1,512>>, i64) -> !TFHE.glwe<sk<0,1,512>>
%6 = "TFHE.add_glwe"(%extracted_1, %5) : (!TFHE.glwe<sk<0,1,512>>, !TFHE.glwe<sk<0,1,512>>) -> !TFHE.glwe<sk<0,1,512>>
%inserted = tensor.insert %6 into %arg7[%arg2, %arg4] : tensor<4x2x!TFHE.glwe<sk<0,1,512>>>
scf.yield %inserted : tensor<4x2x!TFHE.glwe<sk<0,1,512>>>
}
scf.yield %3 : tensor<4x2x!TFHE.glwe<sk<0,1,512>>>
}
scf.yield %2 : tensor<4x2x!TFHE.glwe<sk<0,1,512>>>
}
return %1 : tensor<4x2x!TFHE.glwe<sk<0,1,512>>>
}func.func @main(%arg0: tensor<4x3x513xi64>, %arg1: tensor<3x2xi3>) -> tensor<4x2x513xi64> {
%c0 = arith.constant 0 : index
%c4 = arith.constant 4 : index
%c1 = arith.constant 1 : index
%c2 = arith.constant 2 : index
%c3 = arith.constant 3 : index
%generated = tensor.generate {
^bb0(%arg2: index, %arg3: index, %arg4: index):
%c0_i64 = arith.constant 0 : i64
tensor.yield %c0_i64 : i64
} : tensor<4x2x513xi64>
%0 = scf.for %arg2 = %c0 to %c4 step %c1 iter_args(%arg3 = %generated) -> (tensor<4x2x513xi64>) {
%1 = scf.for %arg4 = %c0 to %c2 step %c1 iter_args(%arg5 = %arg3) -> (tensor<4x2x513xi64>) {
%2 = scf.for %arg6 = %c0 to %c3 step %c1 iter_args(%arg7 = %arg5) -> (tensor<4x2x513xi64>) {
%extracted_slice = tensor.extract_slice %arg0[%arg2, %arg6, 0] [1, 1, 513] [1, 1, 1] : tensor<4x3x513xi64> to tensor<513xi64>
%extracted = tensor.extract %arg1[%arg6, %arg4] : tensor<3x2xi3>
%extracted_slice_0 = tensor.extract_slice %arg7[%arg2, %arg4, 0] [1, 1, 513] [1, 1, 1] : tensor<4x2x513xi64> to tensor<513xi64>
%3 = arith.extsi %extracted : i3 to i64
%4 = "Concrete.mul_cleartext_lwe_tensor"(%extracted_slice, %3) : (tensor<513xi64>, i64) -> tensor<513xi64>
%5 = "Concrete.add_lwe_tensor"(%extracted_slice_0, %4) : (tensor<513xi64>, tensor<513xi64>) -> tensor<513xi64>
%inserted_slice = tensor.insert_slice %5 into %arg7[%arg2, %arg4, 0] [1, 1, 513] [1, 1, 1] : tensor<513xi64> into tensor<4x2x513xi64>
scf.yield %inserted_slice : tensor<4x2x513xi64>
}
scf.yield %2 : tensor<4x2x513xi64>
}
scf.yield %1 : tensor<4x2x513xi64>
}
return %0 : tensor<4x2x513xi64>
}func.func @main(%arg0: memref<4x3x513xi64, strided<[?, ?, ?], offset: ?>>, %arg1: memref<3x2xi3, strided<[?, ?], offset: ?>>, %arg2: !Concrete.context) -> memref<4x2x513xi64> {
%c0_i64 = arith.constant 0 : i64
call @_dfr_start(%c0_i64, %arg2) : (i64, !Concrete.context) -> ()
%c0 = arith.constant 0 : index
%c4 = arith.constant 4 : index
%c1 = arith.constant 1 : index
%c2 = arith.constant 2 : index
%c513 = arith.constant 513 : index
%c0_i64_0 = arith.constant 0 : i64
%c3 = arith.constant 3 : index
%alloc = memref.alloc() {alignment = 64 : i64} : memref<4x2x513xi64>
scf.for %arg3 = %c0 to %c4 step %c1 {
scf.for %arg4 = %c0 to %c2 step %c1 {
scf.for %arg5 = %c0 to %c513 step %c1 {
memref.store %c0_i64_0, %alloc[%arg3, %arg4, %arg5] : memref<4x2x513xi64>
}
}
}
scf.for %arg3 = %c0 to %c4 step %c1 {
scf.for %arg4 = %c0 to %c2 step %c1 {
%subview = memref.subview %alloc[%arg3, %arg4, 0] [1, 1, 513] [1, 1, 1] : memref<4x2x513xi64> to memref<513xi64, strided<[1], offset: ?>>
scf.for %arg5 = %c0 to %c3 step %c1 {
%subview_1 = memref.subview %arg0[%arg3, %arg5, 0] [1, 1, 513] [1, 1, 1] : memref<4x3x513xi64, strided<[?, ?, ?], offset: ?>> to memref<513xi64, strided<[?], offset: ?>>
%0 = memref.load %arg1[%arg5, %arg4] : memref<3x2xi3, strided<[?, ?], offset: ?>>
%1 = arith.extsi %0 : i3 to i64
%alloc_2 = memref.alloc() {alignment = 64 : i64} : memref<513xi64>
%cast = memref.cast %alloc_2 : memref<513xi64> to memref<?xi64, #map>
%cast_3 = memref.cast %subview_1 : memref<513xi64, strided<[?], offset: ?>> to memref<?xi64, #map>
func.call @memref_mul_cleartext_lwe_ciphertext_u64(%cast, %cast_3, %1) : (memref<?xi64, #map>, memref<?xi64, #map>, i64) -> ()
%alloc_4 = memref.alloc() {alignment = 64 : i64} : memref<513xi64>
%cast_5 = memref.cast %alloc_4 : memref<513xi64> to memref<?xi64, #map>
%cast_6 = memref.cast %subview : memref<513xi64, strided<[1], offset: ?>> to memref<?xi64, #map>
%cast_7 = memref.cast %alloc_2 : memref<513xi64> to memref<?xi64, #map>
func.call @memref_add_lwe_ciphertexts_u64(%cast_5, %cast_6, %cast_7) : (memref<?xi64, #map>, memref<?xi64, #map>, memref<?xi64, #map>) -> ()
memref.dealloc %alloc_2 : memref<513xi64>
memref.copy %alloc_4, %subview : memref<513xi64> to memref<513xi64, strided<[1], offset: ?>>
memref.dealloc %alloc_4 : memref<513xi64>
}
}
}
call @_dfr_stop(%c0_i64) : (i64) -> ()
return %alloc : memref<4x2x513xi64>
}IndexError: index 10 is out of bounds for axis 0 with size 6from concrete import fhe
table = fhe.LookupTable([2, -1, 3, 0])
@fhe.compiler({"x": "encrypted"})
def f(x):
return table[x]
inputset = range(4)
circuit = f.compile(inputset)
assert circuit.encrypt_run_decrypt(0) == table[0] == 2
assert circuit.encrypt_run_decrypt(1) == table[1] == -1
assert circuit.encrypt_run_decrypt(2) == table[2] == 3
assert circuit.encrypt_run_decrypt(3) == table[3] == 0from concrete import fhe
import numpy as np
table = fhe.LookupTable([2, -1, 3, 0])
@fhe.compiler({"x": "encrypted"})
def f(x):
return table[x]
inputset = [np.random.randint(0, 4, size=(2, 3)) for _ in range(10)]
circuit = f.compile(inputset)
sample = [
[0, 1, 3],
[2, 3, 1],
]
expected_output = [
[2, -1, 0],
[3, 0, -1],
]
actual_output = circuit.encrypt_run_decrypt(np.array(sample))
for i in range(2):
for j in range(3):
assert actual_output[i][j] == expected_output[i][j] == table[sample[i][j]]from concrete import fhe
table = fhe.LookupTable([2, -1, 3, 0])
@fhe.compiler({"x": "encrypted"})
def f(x):
return table[-x]
inputset = range(1, 5)
circuit = f.compile(inputset)
assert circuit.encrypt_run_decrypt(1) == table[-1] == 0
assert circuit.encrypt_run_decrypt(2) == table[-2] == 3
assert circuit.encrypt_run_decrypt(3) == table[-3] == -1
assert circuit.encrypt_run_decrypt(4) == table[-4] == 2from concrete import fhe
import numpy as np
squared = fhe.LookupTable([i ** 2 for i in range(4)])
cubed = fhe.LookupTable([i ** 3 for i in range(4)])
table = fhe.LookupTable([
[squared, cubed],
[squared, cubed],
[squared, cubed],
])
@fhe.compiler({"x": "encrypted"})
def f(x):
return table[x]
inputset = [np.random.randint(0, 4, size=(3, 2)) for _ in range(10)]
circuit = f.compile(inputset)
sample = [
[0, 1],
[2, 3],
[3, 0],
]
expected_output = [
[0, 1],
[4, 27],
[9, 0]
]
actual_output = circuit.encrypt_run_decrypt(np.array(sample))
for i in range(3):
for j in range(2):
if j == 0:
assert actual_output[i][j] == expected_output[i][j] == squared[sample[i][j]]
else:
assert actual_output[i][j] == expected_output[i][j] == cubed[sample[i][j]]from concrete import fhe
import numpy as np
@fhe.compiler({"x": "encrypted"})
def f(x):
return (42 * np.sin(x)).astype(np.int64) // 10
inputset = range(8)
circuit = f.compile(inputset)
for x in range(8):
assert circuit.encrypt_run_decrypt(x) == f(x)

def f(x):
return np.sin(x)Linux-5.12.13-arch1-2-x86_64-with-glibc2.29 #1 SMP PREEMPT Fri, 25 Jun 2021 22:56:51 +0000
Python 3.8.10astroid==2.15.0
attrs==22.2.0
auditwheel==5.3.0
...
wheel==0.40.0
wrapt==1.15.0
zipp==3.15.0def f(x):
return np.sin(x)x :: encrypted%0 = x # EncryptedScalar<uint3>
%1 = sin(%0) # EncryptedScalar<float64>
return %1%0 = x # EncryptedScalar<uint3>
%1 = sin(%0) # EncryptedScalar<float64>
return %1Traceback (most recent call last):
File "/path/to/your/script.py", line 9, in <module>
circuit = f.compile(inputset)
File "/usr/local/lib/python3.10/site-packages/concrete/fhe/compilation/decorators.py", line 159, in compile
return self.compiler.compile(inputset, configuration, artifacts, **kwargs)
File "/usr/local/lib/python3.10/site-packages/concrete/fhe/compilation/compiler.py", line 437, in compile
mlir = GraphConverter.convert(self.graph)
File "/usr/local/lib/python3.10/site-packages/concrete/fhe/mlir/graph_converter.py", line 677, in convert
GraphConverter._check_graph_convertibility(graph)
File "/usr/local/lib/python3.10/site-packages/concrete/fhe/mlir/graph_converter.py", line 240, in _check_graph_convertibility
raise RuntimeError(message)
RuntimeError: Function you are trying to compile cannot be converted to MLIR
%0 = x # EncryptedScalar<uint3> ∈ [3, 5]
%1 = sin(%0) # EncryptedScalar<float64> ∈ [-0.958924, 0.14112]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only integer operations are supported
/path/to/your/script.py:6
return %1from concrete import fhe
import numpy as np
artifacts = fhe.DebugArtifacts("/tmp/custom/export/path")
@fhe.compiler({"x": "encrypted"})
def f(x):
return 127 - (50 * (np.sin(x) + 1)).astype(np.int64)
inputset = range(2 ** 3)
circuit = f.compile(inputset, artifacts=artifacts)
artifacts.export()%0 = x # EncryptedScalar<uint1>
%1 = sin(%0) # EncryptedScalar<float64>
%2 = 1 # ClearScalar<uint1>
%3 = add(%1, %2) # EncryptedScalar<float64>
%4 = 50 # ClearScalar<uint6>
%5 = multiply(%4, %3) # EncryptedScalar<float64>
%6 = astype(%5, dtype=int_) # EncryptedScalar<uint1>
%7 = 127 # ClearScalar<uint7>
%8 = subtract(%7, %6) # EncryptedScalar<uint1>
return %8%0 = x # EncryptedScalar<uint1>
%1 = subgraph(%0) # EncryptedScalar<uint1>
%2 = 127 # ClearScalar<uint7>
%3 = subtract(%2, %1) # EncryptedScalar<uint1>
return %3
Subgraphs:
%1 = subgraph(%0):
%0 = input # EncryptedScalar<uint1>
%1 = sin(%0) # EncryptedScalar<float64>
%2 = 1 # ClearScalar<uint1>
%3 = add(%1, %2) # EncryptedScalar<float64>
%4 = 50 # ClearScalar<uint6>
%5 = multiply(%4, %3) # EncryptedScalar<float64>
%6 = astype(%5, dtype=int_) # EncryptedScalar<uint1>
return %6%0 = x # EncryptedScalar<uint3> ∈ [0, 7]
%1 = subgraph(%0) # EncryptedScalar<uint7> ∈ [2, 95]
%2 = 127 # ClearScalar<uint7> ∈ [127, 127]
%3 = subtract(%2, %1) # EncryptedScalar<uint7> ∈ [32, 125]
return %3
Subgraphs:
%1 = subgraph(%0):
%0 = input # EncryptedScalar<uint1>
%1 = sin(%0) # EncryptedScalar<float64>
%2 = 1 # ClearScalar<uint1>
%3 = add(%1, %2) # EncryptedScalar<float64>
%4 = 50 # ClearScalar<uint6>
%5 = multiply(%4, %3) # EncryptedScalar<float64>
%6 = astype(%5, dtype=int_) # EncryptedScalar<uint1>
return %6module {
func.func @main(%arg0: !FHE.eint<7>) -> !FHE.eint<7> {
%c127_i8 = arith.constant 127 : i8
%cst = arith.constant dense<"..."> : tensor<128xi64>
%0 = "FHE.apply_lookup_table"(%arg0, %cst) : (!FHE.eint<7>, tensor<128xi64>) -> !FHE.eint<7>
%1 = "FHE.sub_int_eint"(%c127_i8, %0) : (i8, !FHE.eint<7>) -> !FHE.eint<7>
return %1 : !FHE.eint<7>
}
}{
"bootstrapKeys": [
{
"baseLog": 22,
"glweDimension": 1,
"inputLweDimension": 908,
"inputSecretKeyID": 1,
"level": 1,
"outputSecretKeyID": 0,
"polynomialSize": 8192,
"variance": 4.70197740328915e-38
}
],
"functionName": "main",
"inputs": [
{
"encryption": {
"encoding": {
"isSigned": false,
"precision": 7
},
"secretKeyID": 0,
"variance": 4.70197740328915e-38
},
"shape": {
"dimensions": [],
"sign": false,
"size": 0,
"width": 7
}
}
],
"keyswitchKeys": [
{
"baseLog": 3,
"inputSecretKeyID": 0,
"level": 6,
"outputSecretKeyID": 1,
"variance": 1.7944329123150665e-13
}
],
"outputs": [
{
"encryption": {
"encoding": {
"isSigned": false,
"precision": 7
},
"secretKeyID": 0,
"variance": 4.70197740328915e-38
},
"shape": {
"dimensions": [],
"sign": false,
"size": 0,
"width": 7
}
}
],
"packingKeyswitchKeys": [],
"secretKeys": [
{
"dimension": 8192
},
{
"dimension": 908
}
]
}# (example below is for bit-width of 8 and chunk size of 4)
# extract chunks of lhs using table lookups
lhs_chunks = [lhs.bits[0:4], lhs.bits[4:8]]
# extract chunks of rhs using table lookups
rhs_chunks = [rhs.bits[0:4], rhs.bits[4:8]]
# pack chunks of lhs and rhs using clear multiplications and additions
packed_chunks = []
for lhs_chunk, rhs_chunk in zip(lhs_chunks, rhs_chunks):
shifted_lhs_chunk = lhs_chunk * 2**4 # (i.e., lhs_chunk << 4)
packed_chunks.append(shifted_lhs_chunk + rhs_chunk)
# apply comparison table lookup to packed chunks
bitwise_table = fhe.LookupTable([...])
result_chunks = bitwise_table[packed_chunks]
# sum resulting chunks obtain the result
result = np.sum(result_chunks)import matplotlib.pyplot as plt
import numpy as np
from concrete import fhe
original_bit_width = 5
lsbs_to_remove = 2
assert 0 < lsbs_to_remove < original_bit_width
original_values = list(range(2**original_bit_width))
rounded_values = [
fhe.round_bit_pattern(value, lsbs_to_remove)
for value in original_values
]
previous_rounded = rounded_values[0]
for original, rounded in zip(original_values, rounded_values):
if rounded != previous_rounded:
previous_rounded = rounded
print()
original_binary = np.binary_repr(original, width=(original_bit_width + 1))
rounded_binary = np.binary_repr(rounded, width=(original_bit_width + 1))
print(
f"{original:2} = 0b_{original_binary[:-lsbs_to_remove]}[{original_binary[-lsbs_to_remove:]}] "
f"=> "
f"0b_{rounded_binary[:-lsbs_to_remove]}[{rounded_binary[-lsbs_to_remove:]}] = {rounded}"
)
fig = plt.figure()
ax = fig.add_subplot()
plt.plot(original_values, original_values, label="original", color="black")
plt.plot(original_values, rounded_values, label="rounded", color="green")
plt.legend()
ax.set_aspect("equal", adjustable="box")
plt.show() 0 = 0b_0000[00] => 0b_0000[00] = 0
1 = 0b_0000[01] => 0b_0000[00] = 0
2 = 0b_0000[10] => 0b_0001[00] = 4
3 = 0b_0000[11] => 0b_0001[00] = 4
4 = 0b_0001[00] => 0b_0001[00] = 4
5 = 0b_0001[01] => 0b_0001[00] = 4
6 = 0b_0001[10] => 0b_0010[00] = 8
7 = 0b_0001[11] => 0b_0010[00] = 8
8 = 0b_0010[00] => 0b_0010[00] = 8
9 = 0b_0010[01] => 0b_0010[00] = 8
10 = 0b_0010[10] => 0b_0011[00] = 12
11 = 0b_0010[11] => 0b_0011[00] = 12
12 = 0b_0011[00] => 0b_0011[00] = 12
13 = 0b_0011[01] => 0b_0011[00] = 12
14 = 0b_0011[10] => 0b_0100[00] = 16
15 = 0b_0011[11] => 0b_0100[00] = 16
16 = 0b_0100[00] => 0b_0100[00] = 16
17 = 0b_0100[01] => 0b_0100[00] = 16
18 = 0b_0100[10] => 0b_0101[00] = 20
19 = 0b_0100[11] => 0b_0101[00] = 20
20 = 0b_0101[00] => 0b_0101[00] = 20
21 = 0b_0101[01] => 0b_0101[00] = 20
22 = 0b_0101[10] => 0b_0110[00] = 24
23 = 0b_0101[11] => 0b_0110[00] = 24
24 = 0b_0110[00] => 0b_0110[00] = 24
25 = 0b_0110[01] => 0b_0110[00] = 24
26 = 0b_0110[10] => 0b_0111[00] = 28
27 = 0b_0110[11] => 0b_0111[00] = 28
28 = 0b_0111[00] => 0b_0111[00] = 28
29 = 0b_0111[01] => 0b_0111[00] = 28
30 = 0b_0111[10] => 0b_1000[00] = 32
31 = 0b_0111[11] => 0b_1000[00] = 32


import numpy as np
from concrete import fhe
def f(x, y):
return x & y
inputset = [
(np.random.randint(0, 2**4), np.random.randint(0, 2**4))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, show_mlir=True)module {
// no promotions
func.func @main(%arg0: !FHE.eint<4>, %arg1: !FHE.eint<4>) -> !FHE.eint<4> {
// extracting the first chunk of x, adjusted for shifting
%cst = arith.constant dense<[0, 0, 0, 0, 4, 4, 4, 4, 8, 8, 8, 8, 12, 12, 12, 12]> : tensor<16xi64>
%0 = "FHE.apply_lookup_table"(%arg0, %cst) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
// extracting the first chunk of y
%cst_0 = arith.constant dense<[0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3]> : tensor<16xi64>
%1 = "FHE.apply_lookup_table"(%arg1, %cst_0) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
// packing the first chunks
%2 = "FHE.add_eint"(%0, %1) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
// applying the bitwise operation to the first chunks, adjusted for addition in the end
%cst_1 = arith.constant dense<[0, 0, 0, 0, 0, 4, 0, 4, 0, 0, 8, 8, 0, 4, 8, 12]> : tensor<16xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst_1) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
// extracting the second chunk of x, adjusted for shifting
%cst_2 = arith.constant dense<[0, 4, 8, 12, 0, 4, 8, 12, 0, 4, 8, 12, 0, 4, 8, 12]> : tensor<16xi64>
%4 = "FHE.apply_lookup_table"(%arg0, %cst_2) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
// extracting the second chunk of y
%cst_3 = arith.constant dense<[0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]> : tensor<16xi64>
%5 = "FHE.apply_lookup_table"(%arg1, %cst_3) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
// packing the second chunks
%6 = "FHE.add_eint"(%4, %5) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
// applying the bitwise operation to second chunks
%cst_4 = arith.constant dense<[0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 2, 2, 0, 1, 2, 3]> : tensor<16xi64>
%7 = "FHE.apply_lookup_table"(%6, %cst_4) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
// adding resulting chunks to obtain the result
%8 = "FHE.add_eint"(%7, %3) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
return %8 : !FHE.eint<4>
}
}x.bit_width + y.bit_width <= MAXIMUM_TLU_BIT_WIDTHbitwise_lut = fhe.LookupTable([...])
result = bitwise_lut[pack(x_promoted_to_uint9, y_promoted_to_uint9)]import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
bitwise_strategy_preference=fhe.BitwiseStrategy.ONE_TLU_PROMOTED,
)
def f(x, y):
return x & y
inputset = [
(np.random.randint(0, 2**4), np.random.randint(0, 2**4))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// promotions ............ ............
func.func @main(%arg0: !FHE.eint<8>, %arg1: !FHE.eint<8>) -> !FHE.eint<4> {
// packing
%c16_i9 = arith.constant 16 : i9
%0 = "FHE.mul_eint_int"(%arg0, %c16_i9) : (!FHE.eint<8>, i9) -> !FHE.eint<8>
%1 = "FHE.add_eint"(%0, %arg1) : (!FHE.eint<8>, !FHE.eint<8>) -> !FHE.eint<8>
// computing the result
%cst = arith.constant dense<"..."> : tensor<256xi64>
%2 = "FHE.apply_lookup_table"(%1, %cst) : (!FHE.eint<8>, tensor<256xi64>) -> !FHE.eint<4>
return %2 : !FHE.eint<4>
}
}uint3_to_uint9_lut = fhe.LookupTable([...])
x_cast_to_uint9 = uint3_to_uint9_lut[x]
uint6_to_uint9_lut = fhe.LookupTable([...])
y_cast_to_uint9 = uint6_to_uint9_lut[y]
bitwise_lut = fhe.LookupTable([...])
result = bitwise_lut[pack(x_cast_to_uint9, y_cast_to_uint9)]import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
comparison_strategy_preference=fhe.BitwiseStrategy.THREE_TLU_CASTED,
)
def f(x, y):
return x & y
inputset = [
(np.random.randint(0, 2**4), np.random.randint(0, 2**4))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// no promotions
func.func @main(%arg0: !FHE.eint<4>, %arg1: !FHE.eint<4>) -> !FHE.eint<4> {
// casting
%cst = arith.constant dense<[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]> : tensor<16xi64>
%0 = "FHE.apply_lookup_table"(%arg0, %cst) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<8>
%1 = "FHE.apply_lookup_table"(%arg1, %cst) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<8>
// packing
%c16_i9 = arith.constant 16 : i9
%2 = "FHE.mul_eint_int"(%0, %c16_i9) : (!FHE.eint<8>, i9) -> !FHE.eint<8>
%3 = "FHE.add_eint"(%2, %1) : (!FHE.eint<8>, !FHE.eint<8>) -> !FHE.eint<8>
// computing the result
%cst_0 = arith.constant dense<"..."> : tensor<256xi64>
%4 = "FHE.apply_lookup_table"(%3, %cst_0) : (!FHE.eint<8>, tensor<256xi64>) -> !FHE.eint<4>
return %4 : !FHE.eint<4>
}
}uint3_to_uint9_lut = fhe.LookupTable([...])
x_cast_to_uint9 = uint3_to_uint9_lut[x]
comparison_lut = fhe.LookupTable([...])
result = comparison_lut[x_cast_to_uint9 - y_promoted_to_uint9]import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
bitwise_strategy_preference=fhe.BitwiseStrategy.TWO_TLU_BIGGER_PROMOTED_SMALLER_CASTED,
)
def f(x, y):
return x & y
inputset = [
(np.random.randint(0, 2**3), np.random.randint(0, 2**6))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// promotions ............
func.func @main(%arg0: !FHE.eint<3>, %arg1: !FHE.eint<8>) -> !FHE.eint<3> {
// casting smaller operand
%cst = arith.constant dense<[0, 1, 2, 3, 4, 5, 6, 7]> : tensor<8xi64>
%0 = "FHE.apply_lookup_table"(%arg0, %cst) : (!FHE.eint<3>, tensor<8xi64>) -> !FHE.eint<8>
// packing
%c32_i9 = arith.constant 32 : i9
%1 = "FHE.mul_eint_int"(%0, %c32_i9) : (!FHE.eint<8>, i9) -> !FHE.eint<8>
%2 = "FHE.add_eint"(%1, %arg1) : (!FHE.eint<8>, !FHE.eint<8>) -> !FHE.eint<8>
// computing the result
%cst_0 = arith.constant dense<"..."> : tensor<256xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst_0) : (!FHE.eint<8>, tensor<256xi64>) -> !FHE.eint<3>
return %3 : !FHE.eint<3>
}
}uint6_to_uint9_lut = fhe.LookupTable([...])
y_cast_to_uint9 = uint6_to_uint9_lut[y]
comparison_lut = fhe.LookupTable([...])
result = comparison_lut[x_promoted_to_uint9 - y_cast_to_uint9]import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
bitwise_strategy_preference=fhe.BitwiseStrategy.TWO_TLU_BIGGER_CASTED_SMALLER_PROMOTED,
)
def f(x, y):
return x | y
inputset = [
(np.random.randint(0, 2**3), np.random.randint(0, 2**6))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// promotions ............
func.func @main(%arg0: !FHE.eint<9>, %arg1: !FHE.eint<6>) -> !FHE.eint<6> {
// casting bigger operand
%cst = arith.constant dense<[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63]> : tensor<64xi64>
%0 = "FHE.apply_lookup_table"(%arg1, %cst) : (!FHE.eint<6>, tensor<64xi64>) -> !FHE.eint<9>
// packing
%c64_i10 = arith.constant 64 : i10
%1 = "FHE.mul_eint_int"(%arg0, %c64_i10) : (!FHE.eint<9>, i10) -> !FHE.eint<9>
%2 = "FHE.add_eint"(%1, %0) : (!FHE.eint<9>, !FHE.eint<9>) -> !FHE.eint<9>
// computing the result
%cst_0 = arith.constant dense<"..."> : tensor<512xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst_0) : (!FHE.eint<9>, tensor<512xi64>) -> !FHE.eint<6>
return %3 : !FHE.eint<6>
}
}import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
shifts_with_promotion=True,
)
def f(x, y):
return x << y
inputset = [
(np.random.randint(0, 2**3), np.random.randint(0, 2**2))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// promotions ............
func.func @main(%arg0: !FHE.eint<6>, %arg1: !FHE.eint<2>) -> !FHE.eint<6> {
// shifting for the second bit of y
%cst = arith.constant dense<[0, 0, 1, 1]> : tensor<4xi64>
%0 = "FHE.apply_lookup_table"(%arg1, %cst) : (!FHE.eint<2>, tensor<4xi64>) -> !FHE.eint<4>
%cst_0 = arith.constant dense<[0, 0, 0, 2, 2, 2, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]> : tensor<64xi64>
%1 = "FHE.apply_lookup_table"(%arg0, %cst_0) : (!FHE.eint<6>, tensor<64xi64>) -> !FHE.eint<4>
%2 = "FHE.add_eint"(%1, %0) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
%cst_1 = arith.constant dense<[0, 0, 0, 8, 0, 16, 0, 24, 0, 32, 0, 40, 0, 48, 0, 56]> : tensor<16xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst_1) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<6>
%cst_2 = arith.constant dense<[0, 6, 12, 2, 8, 14, 4, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]> : tensor<64xi64>
%4 = "FHE.apply_lookup_table"(%arg0, %cst_2) : (!FHE.eint<6>, tensor<64xi64>) -> !FHE.eint<4>
%5 = "FHE.add_eint"(%4, %0) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
%cst_3 = arith.constant dense<[0, 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7]> : tensor<16xi64>
%6 = "FHE.apply_lookup_table"(%5, %cst_3) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<6>
%7 = "FHE.add_eint"(%3, %6) : (!FHE.eint<6>, !FHE.eint<6>) -> !FHE.eint<6>
%8 = "FHE.add_eint"(%7, %arg0) : (!FHE.eint<6>, !FHE.eint<6>) -> !FHE.eint<6>
// shifting for the first bit of y
%cst_4 = arith.constant dense<[0, 1, 0, 1]> : tensor<4xi64>
%9 = "FHE.apply_lookup_table"(%arg1, %cst_4) : (!FHE.eint<2>, tensor<4xi64>) -> !FHE.eint<4>
%cst_5 = arith.constant dense<[0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 8, 8, 8, 8, 8, 8, 8, 8, 10, 10, 10, 10, 10, 10, 10, 10, 12, 12, 12, 12, 12, 12, 12, 12, 14, 14, 14, 14, 14, 14, 14, 14]> : tensor<64xi64>
%10 = "FHE.apply_lookup_table"(%8, %cst_5) : (!FHE.eint<6>, tensor<64xi64>) -> !FHE.eint<4>
%11 = "FHE.add_eint"(%10, %9) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
%12 = "FHE.apply_lookup_table"(%11, %cst_1) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<6>
%cst_6 = arith.constant dense<[0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14]> : tensor<64xi64>
%13 = "FHE.apply_lookup_table"(%8, %cst_6) : (!FHE.eint<6>, tensor<64xi64>) -> !FHE.eint<4>
%14 = "FHE.add_eint"(%13, %9) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
%15 = "FHE.apply_lookup_table"(%14, %cst_3) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<6>
%16 = "FHE.add_eint"(%12, %15) : (!FHE.eint<6>, !FHE.eint<6>) -> !FHE.eint<6>
%17 = "FHE.add_eint"(%16, %8) : (!FHE.eint<6>, !FHE.eint<6>) -> !FHE.eint<6>
return %17 : !FHE.eint<6>
}
}import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
shifts_with_promotion=False,
)
def f(x, y):
return x << y
inputset = [
(np.random.randint(0, 2**3), np.random.randint(0, 2**2))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// no promotions
func.func @main(%arg0: !FHE.eint<3>, %arg1: !FHE.eint<2>) -> !FHE.eint<6> {
// shifting for the second bit of y
%cst = arith.constant dense<[0, 0, 1, 1]> : tensor<4xi64>
%0 = "FHE.apply_lookup_table"(%arg1, %cst) : (!FHE.eint<2>, tensor<4xi64>) -> !FHE.eint<4>
%cst_0 = arith.constant dense<[0, 0, 0, 2, 2, 2, 4, 4]> : tensor<8xi64>
%1 = "FHE.apply_lookup_table"(%arg0, %cst_0) : (!FHE.eint<3>, tensor<8xi64>) -> !FHE.eint<4>
%2 = "FHE.add_eint"(%1, %0) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
%cst_1 = arith.constant dense<[0, 0, 0, 8, 0, 16, 0, 24, 0, 32, 0, 40, 0, 48, 0, 56]> : tensor<16xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst_1) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<6>
%cst_2 = arith.constant dense<[0, 6, 12, 2, 8, 14, 4, 10]> : tensor<8xi64>
%4 = "FHE.apply_lookup_table"(%arg0, %cst_2) : (!FHE.eint<3>, tensor<8xi64>) -> !FHE.eint<4>
%5 = "FHE.add_eint"(%4, %0) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
%cst_3 = arith.constant dense<[0, 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7]> : tensor<16xi64>
%6 = "FHE.apply_lookup_table"(%5, %cst_3) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<6>
%7 = "FHE.add_eint"(%3, %6) : (!FHE.eint<6>, !FHE.eint<6>) -> !FHE.eint<6>
// cast x to 6-bits to compute the result using addition/subtraction
%cst_4 = arith.constant dense<[0, 1, 2, 3, 4, 5, 6, 7]> : tensor<8xi64>
%8 = "FHE.apply_lookup_table"(%arg0, %cst_4) : (!FHE.eint<3>, tensor<8xi64>) -> !FHE.eint<6>
// this was done using promotion instead of casting in runtime when the flag was turned on
%9 = "FHE.add_eint"(%7, %8) : (!FHE.eint<6>, !FHE.eint<6>) -> !FHE.eint<6>
// shifting for the first bit of y
%cst_5 = arith.constant dense<[0, 1, 0, 1]> : tensor<4xi64>
%10 = "FHE.apply_lookup_table"(%arg1, %cst_5) : (!FHE.eint<2>, tensor<4xi64>) -> !FHE.eint<4>
%cst_6 = arith.constant dense<[0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 8, 8, 8, 8, 8, 8, 8, 8, 10, 10, 10, 10, 10, 10, 10, 10, 12, 12, 12, 12, 12, 12, 12, 12, 14, 14, 14, 14, 14, 14, 14, 14]> : tensor<64xi64>
%11 = "FHE.apply_lookup_table"(%9, %cst_6) : (!FHE.eint<6>, tensor<64xi64>) -> !FHE.eint<4>
%12 = "FHE.add_eint"(%11, %10) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
%13 = "FHE.apply_lookup_table"(%12, %cst_1) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<6>
%cst_7 = arith.constant dense<[0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14]> : tensor<64xi64>
%14 = "FHE.apply_lookup_table"(%9, %cst_7) : (!FHE.eint<6>, tensor<64xi64>) -> !FHE.eint<4>
%15 = "FHE.add_eint"(%14, %10) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
%16 = "FHE.apply_lookup_table"(%15, %cst_3) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<6>
%17 = "FHE.add_eint"(%13, %16) : (!FHE.eint<6>, !FHE.eint<6>) -> !FHE.eint<6>
%18 = "FHE.add_eint"(%17, %9) : (!FHE.eint<6>, !FHE.eint<6>) -> !FHE.eint<6>
return %18 : !FHE.eint<6>
}
}import itertools
import time
import matplotlib.pyplot as plt
import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
enable_unsafe_features=True,
use_insecure_key_cache=True,
insecure_key_cache_location=".keys",
single_precision=False,
parameter_selection_strategy=fhe.ParameterSelectionStrategy.MULTI,
)
input_bit_width = 6
input_range = np.array(range(2**input_bit_width))
timings = {}
results = {}
for lsbs_to_remove in range(input_bit_width):
@fhe.compiler({"x": "encrypted"})
def f(x):
return fhe.round_bit_pattern(x, lsbs_to_remove) ** 2
circuit = f.compile(inputset=[input_range], configuration=configuration)
circuit.keygen()
encrypted_sample = circuit.encrypt(input_range)
start = time.time()
encrypted_result = circuit.run(encrypted_sample)
end = time.time()
result = circuit.decrypt(encrypted_result)
took = end - start
timings[lsbs_to_remove] = took
results[lsbs_to_remove] = result
number_of_figures = len(results)
columns = 1
for i in range(2, number_of_figures):
if number_of_figures % i == 0:
columns = i
rows = number_of_figures // columns
fig, axs = plt.subplots(rows, columns)
axs = axs.flatten()
baseline = timings[0]
for lsbs_to_remove in range(input_bit_width):
timing = timings[lsbs_to_remove]
speedup = baseline / timing
print(f"lsbs_to_remove={lsbs_to_remove} => {speedup:.2f}x speedup")
axs[lsbs_to_remove].set_title(f"lsbs_to_remove={lsbs_to_remove}")
axs[lsbs_to_remove].plot(input_range, results[lsbs_to_remove])
plt.show()lsbs_to_remove=0 => 1.00x speedup
lsbs_to_remove=1 => 1.20x speedup
lsbs_to_remove=2 => 2.17x speedup
lsbs_to_remove=3 => 3.75x speedup
lsbs_to_remove=4 => 2.64x speedup
lsbs_to_remove=5 => 2.61x speedupimport itertools
import time
import matplotlib.pyplot as plt
import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
enable_unsafe_features=True,
use_insecure_key_cache=True,
insecure_key_cache_location=".keys",
single_precision=False,
parameter_selection_strategy=fhe.ParameterSelectionStrategy.MULTI,
)
input_bit_width = 6
input_range = np.array(range(2**input_bit_width))
timings = {}
results = {}
for target_msbs in reversed(range(1, input_bit_width + 1)):
rounder = fhe.AutoRounder(target_msbs)
@fhe.compiler({"x": "encrypted"})
def f(x):
return fhe.round_bit_pattern(x, rounder) ** 2
fhe.AutoRounder.adjust(f, inputset=[input_range])
circuit = f.compile(inputset=[input_range], configuration=configuration)
circuit.keygen()
encrypted_sample = circuit.encrypt(input_range)
start = time.time()
encrypted_result = circuit.run(encrypted_sample)
end = time.time()
result = circuit.decrypt(encrypted_result)
took = end - start
timings[target_msbs] = took
results[target_msbs] = result
number_of_figures = len(results)
columns = 1
for i in range(2, number_of_figures):
if number_of_figures % i == 0:
columns = i
rows = number_of_figures // columns
fig, axs = plt.subplots(rows, columns)
axs = axs.flatten()
baseline = timings[input_bit_width]
for i, target_msbs in enumerate(reversed(range(1, input_bit_width + 1))):
timing = timings[target_msbs]
speedup = baseline / timing
print(f"target_msbs={target_msbs} => {speedup:.2f}x speedup")
axs[i].set_title(f"target_msbs={target_msbs}")
axs[i].plot(input_range, results[target_msbs])
plt.show()target_msbs=6 => 1.00x speedup
target_msbs=5 => 1.22x speedup
target_msbs=4 => 1.95x speedup
target_msbs=3 => 3.11x speedup
target_msbs=2 => 2.23x speedup
target_msbs=1 => 2.34x speedup(x - y).bit_width <= MAXIMUM_TLU_BIT_WIDTHx.bit_width != y.bit_widthsmaller = x if x.bit_width < y.bit_width else y
bigger = x if x.bit_width > y.bit_width else y
clipped = lambda value: np.clip(value, smaller.min() - 1, smaller.max() + 1)
any(
(
bit_width <= MAXIMUM_TLU_BIT_WIDTH and
bit_width <= bigger.dtype.bit_width and
bit_width > smaller.dtype.bit_width
)
for bit_width in [
(smaller - clipped(bigger)).bit_width,
(clipped(bigger) - smaller).bit_width,
]
)# (example below is for bit-width of 8 and chunk size of 4)
# extract chunks of lhs using table lookups
lhs_chunks = [lhs.bits[0:4], lhs.bits[4:8]]
# extract chunks of rhs using table lookups
rhs_chunks = [rhs.bits[0:4], rhs.bits[4:8]]
# pack chunks of lhs and rhs using clear multiplications and additions
packed_chunks = []
for lhs_chunk, rhs_chunk in zip(lhs_chunks, rhs_chunks):
shifted_lhs_chunk = lhs_chunk * 2**4 # (i.e., lhs_chunk << 4)
packed_chunks.append(shifted_lhs_chunk + rhs_chunk)
# apply comparison table lookup to packed chunks
comparison_table = fhe.LookupTable([...])
chunk_comparisons = comparison_table[packed_chunks]
# reduce chunk comparisons to comparison of numbers
result = chunk_comparisons[0]
for chunk_comparison in chunk_comparisons[1:]:
chunk_reduction_table = fhe.LookupTable([...])
shifted_chunk_comparison= chunk_comparison * 2**2 # (i.e., lhs_chunk << 2)
result = chunk_reduction_table[result + shifted_chunk_comparison]import numpy as np
from concrete import fhe
def f(x, y):
return x < y
inputset = [
(np.random.randint(0, 2**4), np.random.randint(0, 2**4))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, show_mlir=True)module {
func.func @main(%arg0: !FHE.eint<4>, %arg1: !FHE.eint<4>) -> !FHE.eint<1> {
// extracting the first chunk of x, adjusted for shifting
%cst = arith.constant dense<[0, 0, 0, 0, 4, 4, 4, 4, 8, 8, 8, 8, 12, 12, 12, 12]> : tensor<16xi64>
%0 = "FHE.apply_lookup_table"(%arg0, %cst) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
// extracting the first chunk of y
%cst_0 = arith.constant dense<[0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3]> : tensor<16xi64>
%1 = "FHE.apply_lookup_table"(%arg1, %cst_0) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
// packing first chunks
%2 = "FHE.add_eint"(%0, %1) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
// comparing first chunks
%cst_1 = arith.constant dense<[0, 1, 1, 1, 2, 0, 1, 1, 2, 2, 0, 1, 2, 2, 2, 0]> : tensor<16xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst_1) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
// extracting the second chunk of x, adjusted for shifting
%cst_2 = arith.constant dense<[0, 4, 8, 12, 0, 4, 8, 12, 0, 4, 8, 12, 0, 4, 8, 12]> : tensor<16xi64>
%4 = "FHE.apply_lookup_table"(%arg0, %cst_2) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
// extracting the second chunk of y
%cst_3 = arith.constant dense<[0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]> : tensor<16xi64>
%5 = "FHE.apply_lookup_table"(%arg1, %cst_3) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
// packing second chunks
%6 = "FHE.add_eint"(%4, %5) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
// comparing second chunks
%cst_4 = arith.constant dense<[0, 4, 4, 4, 8, 0, 4, 4, 8, 8, 0, 4, 8, 8, 8, 0]> : tensor<16xi64>
%7 = "FHE.apply_lookup_table"(%6, %cst_4) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
// packing comparisons
%8 = "FHE.add_eint"(%7, %3) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
// reducing comparisons to result
%cst_5 = arith.constant dense<[0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0]> : tensor<16xi64>
%9 = "FHE.apply_lookup_table"(%8, %cst_5) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<1>
return %9 : !FHE.eint<1>
}
}comparison_lut = fhe.LookupTable([...])
result = comparison_lut[x_promoted_to_uint7 - y_promoted_to_uint7]import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
comparison_strategy_preference=fhe.ComparisonStrategy.ONE_TLU_PROMOTED,
)
def f(x, y):
return x < y
inputset = [
(np.random.randint(0, 2**4), np.random.randint(0, 2**4))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// promotions ............ ............
func.func @main(%arg0: !FHE.eint<5>, %arg1: !FHE.eint<5>) -> !FHE.eint<1> {
// subtraction
%0 = "FHE.to_signed"(%arg0) : (!FHE.eint<5>) -> !FHE.esint<5>
%1 = "FHE.to_signed"(%arg1) : (!FHE.eint<5>) -> !FHE.esint<5>
%2 = "FHE.sub_eint"(%0, %1) : (!FHE.esint<5>, !FHE.esint<5>) -> !FHE.esint<5>
// computing the result
%cst = arith.constant dense<[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]> : tensor<32xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst) : (!FHE.esint<5>, tensor<32xi64>) -> !FHE.eint<1>
return %3 : !FHE.eint<1>
}
}uint3_to_uint7_lut = fhe.LookupTable([...])
x_cast_to_uint7 = uint3_to_uint7_lut[x]
uint6_to_uint7_lut = fhe.LookupTable([...])
y_cast_to_uint7 = uint6_to_uint7_lut[y]
comparison_lut = fhe.LookupTable([...])
result = comparison_lut[x_cast_to_uint7 - y_cast_to_uint7]import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
comparison_strategy_preference=fhe.ComparisonStrategy.THREE_TLU_CASTED,
)
def f(x, y):
return x < y
inputset = [
(np.random.randint(0, 2**4), np.random.randint(0, 2**4))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// no promotions
func.func @main(%arg0: !FHE.eint<3>, %arg1: !FHE.eint<6>) -> !FHE.eint<1> {
// casting
%cst = arith.constant dense<[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]> : tensor<16xi64>
%0 = "FHE.apply_lookup_table"(%arg0, %cst) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.esint<5>
%1 = "FHE.apply_lookup_table"(%arg1, %cst) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.esint<5>
// subtraction
%2 = "FHE.sub_eint"(%0, %1) : (!FHE.esint<5>, !FHE.esint<5>) -> !FHE.esint<5>
// computing the result
%cst_0 = arith.constant dense<[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]> : tensor<32xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst_0) : (!FHE.esint<5>, tensor<32xi64>) -> !FHE.eint<1>
return %3 : !FHE.eint<1>
}
}uint3_to_uint7_lut = fhe.LookupTable([...])
x_cast_to_uint7 = uint3_to_uint7_lut[x]
comparison_lut = fhe.LookupTable([...])
result = comparison_lut[x_cast_to_uint7 - y_promoted_to_uint7]import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
comparison_strategy_preference=fhe.ComparisonStrategy.TWO_TLU_BIGGER_PROMOTED_SMALLER_CASTED,
)
def f(x, y):
return x < y
inputset = [
(np.random.randint(0, 2**3), np.random.randint(0, 2**5))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// promotions ............
func.func @main(%arg0: !FHE.eint<3>, %arg1: !FHE.eint<6>) -> !FHE.eint<1> {
// casting the smaller operand
%cst = arith.constant dense<[0, 1, 2, 3, 4, 5, 6, 7]> : tensor<8xi64>
%0 = "FHE.apply_lookup_table"(%arg0, %cst) : (!FHE.eint<3>, tensor<8xi64>) -> !FHE.esint<6>
// subtraction
%1 = "FHE.to_signed"(%arg1) : (!FHE.eint<6>) -> !FHE.esint<6>
%2 = "FHE.sub_eint"(%0, %1) : (!FHE.esint<6>, !FHE.esint<6>) -> !FHE.esint<6>
// computing the result
%cst_0 = arith.constant dense<[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]> : tensor<64xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst_0) : (!FHE.esint<6>, tensor<64xi64>) -> !FHE.eint<1>
return %3 : !FHE.eint<1>
}
}uint6_to_uint7_lut = fhe.LookupTable([...])
y_cast_to_uint7 = uint6_to_uint7_lut[y]
comparison_lut = fhe.LookupTable([...])
result = comparison_lut[x_promoted_to_uint7 - y_cast_to_uint7]import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
comparison_strategy_preference=fhe.ComparisonStrategy.TWO_TLU_BIGGER_PROMOTED_SMALLER_CASTED,
)
def f(x, y):
return x < y
inputset = [
(np.random.randint(0, 2**3), np.random.randint(0, 2**5))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// promotions ............
func.func @main(%arg0: !FHE.eint<6>, %arg1: !FHE.eint<5>) -> !FHE.eint<1> {
// casting the bigger operand
%cst = arith.constant dense<[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]> : tensor<32xi64>
%0 = "FHE.apply_lookup_table"(%arg1, %cst) : (!FHE.eint<5>, tensor<32xi64>) -> !FHE.esint<6>
// subtraction
%1 = "FHE.to_signed"(%arg0) : (!FHE.eint<6>) -> !FHE.esint<6>
%2 = "FHE.sub_eint"(%1, %0) : (!FHE.esint<6>, !FHE.esint<6>) -> !FHE.esint<6>
// computing the result
%cst_0 = arith.constant dense<[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]> : tensor<64xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst_0) : (!FHE.esint<6>, tensor<64xi64>) -> !FHE.eint<1>
return %3 : !FHE.eint<1>
}
}uint3_to_uint4_lut = fhe.LookupTable([...])
x_cast_to_uint4 = uint3_to_uint4_lut[x]
clipper = fhe.LookupTable([...])
y_clipped = clipper[y]
comparison_lut = fhe.LookupTable([...])
result = comparison_lut[x_cast_to_uint4 - y_clipped]
# or
another_comparison_lut = fhe.LookupTable([...])
result = another_comparison_lut[y_clipped - x_cast_to_uint4]import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
comparison_strategy_preference=fhe.ComparisonStrategy.THREE_TLU_BIGGER_CLIPPED_SMALLER_CASTED
)
def f(x, y):
return x < y
inputset = [
(np.random.randint(0, 2**3), np.random.randint(0, 2**6))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// no promotions
func.func @main(%arg0: !FHE.eint<3>, %arg1: !FHE.eint<6>) -> !FHE.eint<1> {
// casting the smaller operand
%cst = arith.constant dense<[0, 1, 2, 3, 4, 5, 6, 7]> : tensor<8xi64>
%0 = "FHE.apply_lookup_table"(%arg0, %cst) : (!FHE.eint<3>, tensor<8xi64>) -> !FHE.esint<4>
// clipping the bigger operand
%cst_0 = arith.constant dense<[0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8]> : tensor<64xi64>
%1 = "FHE.apply_lookup_table"(%arg1, %cst_0) : (!FHE.eint<6>, tensor<64xi64>) -> !FHE.esint<4>
// subtraction
%2 = "FHE.sub_eint"(%0, %1) : (!FHE.esint<4>, !FHE.esint<4>) -> !FHE.esint<4>
// computing the result
%cst_1 = arith.constant dense<[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]> : tensor<16xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst_1) : (!FHE.esint<4>, tensor<16xi64>) -> !FHE.eint<1>
return %3 : !FHE.eint<1>
}
}clipper = fhe.LookupTable([...])
y_clipped = clipper[y]
comparison_lut = fhe.LookupTable([...])
result = comparison_lut[x_promoted_to_uint4 - y_clipped]
# or
another_comparison_lut = fhe.LookupTable([...])
result = another_comparison_lut[y_clipped - x_promoted_to_uint4]import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
comparison_strategy_preference=fhe.ComparisonStrategy.TWO_TLU_BIGGER_CLIPPED_SMALLER_PROMOTED
)
def f(x, y):
return x < y
inputset = [
(np.random.randint(0, 2**3), np.random.randint(0, 2**6))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// promotions ............
func.func @main(%arg0: !FHE.eint<4>, %arg1: !FHE.eint<6>) -> !FHE.eint<1> {
// clipping the bigger operand
%cst = arith.constant dense<[0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8]> : tensor<64xi64>
%0 = "FHE.apply_lookup_table"(%arg1, %cst) : (!FHE.eint<6>, tensor<64xi64>) -> !FHE.esint<4>
// subtraction
%1 = "FHE.to_signed"(%arg0) : (!FHE.eint<4>) -> !FHE.esint<4>
%2 = "FHE.sub_eint"(%1, %0) : (!FHE.esint<4>, !FHE.esint<4>) -> !FHE.esint<4>
// computing the result
%cst_0 = arith.constant dense<[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]> : tensor<16xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst_0) : (!FHE.esint<4>, tensor<16xi64>) -> !FHE.eint<1>
return %3 : !FHE.eint<1>
}
}TFHE.batched_add_glwe_cst_int (::mlir::concretelang::TFHE::ABatchedAddGLWECstIntOp)TFHE.batched_add_glwe_int_cst (::mlir::concretelang::TFHE::ABatchedAddGLWEIntCstOp)TFHE.batched_add_glwe_int (::mlir::concretelang::TFHE::ABatchedAddGLWEIntOp)TFHE.batched_add_glwe (::mlir::concretelang::TFHE::ABatchedAddGLWEOp)TFHE.add_glwe_int (::mlir::concretelang::TFHE::AddGLWEIntOp)TFHE.add_glwe (::mlir::concretelang::TFHE::AddGLWEOp)TFHE.batched_bootstrap_glwe (::mlir::concretelang::TFHE::BatchedBootstrapGLWEOp)TFHE.batched_keyswitch_glwe (::mlir::concretelang::TFHE::BatchedKeySwitchGLWEOp)TFHE.batched_mapped_bootstrap_glwe (::mlir::concretelang::TFHE::BatchedMappedBootstrapGLWEOp)TFHE.batched_mul_glwe_cst_int (::mlir::concretelang::TFHE::BatchedMulGLWECstIntOp)TFHE.batched_mul_glwe_int_cst (::mlir::concretelang::TFHE::BatchedMulGLWEIntCstOp)TFHE.batched_mul_glwe_int (::mlir::concretelang::TFHE::BatchedMulGLWEIntOp)TFHE.batched_neg_glwe (::mlir::concretelang::TFHE::BatchedNegGLWEOp)TFHE.bootstrap_glwe (::mlir::concretelang::TFHE::BootstrapGLWEOp)TFHE.encode_expand_lut_for_bootstrap (::mlir::concretelang::TFHE::EncodeExpandLutForBootstrapOp)TFHE.encode_lut_for_crt_woppbs (::mlir::concretelang::TFHE::EncodeLutForCrtWopPBSOp)TFHE.encode_plaintext_with_crt (::mlir::concretelang::TFHE::EncodePlaintextWithCrtOp)TFHE.keyswitch_glwe (::mlir::concretelang::TFHE::KeySwitchGLWEOp)TFHE.mul_glwe_int (::mlir::concretelang::TFHE::MulGLWEIntOp)TFHE.neg_glwe (::mlir::concretelang::TFHE::NegGLWEOp)TFHE.sub_int_glwe (::mlir::concretelang::TFHE::SubGLWEIntOp)TFHE.wop_pbs_glwe (::mlir::concretelang::TFHE::WopPBSGLWEOp)TFHE.zero (::mlir::concretelang::TFHE::ZeroGLWEOp)TFHE.zero_tensor (::mlir::concretelang::TFHE::ZeroTensorGLWEOp)#TFHE.bsk<
mlir::concretelang::TFHE::GLWESecretKey, # inputKey
mlir::concretelang::TFHE::GLWESecretKey, # outputKey
int, # polySize
int, # glweDim
int, # levels
int, # baseLog
int # index
>#TFHE.ksk<
mlir::concretelang::TFHE::GLWESecretKey, # inputKey
mlir::concretelang::TFHE::GLWESecretKey, # outputKey
int, # levels
int, # baseLog
int # index
>#TFHE.pksk<
mlir::concretelang::TFHE::GLWESecretKey, # inputKey
mlir::concretelang::TFHE::GLWESecretKey, # outputKey
int, # outputPolySize
int, # innerLweDim
int, # glweDim
int, # levels
int, # baseLog
int # index
>FHE.add_eint_int (::mlir::concretelang::FHE::AddEintIntOp)FHE.add_eint (::mlir::concretelang::FHE::AddEintOp)FHE.apply_lookup_table (::mlir::concretelang::FHE::ApplyLookupTableEintOp)FHE.and (::mlir::concretelang::FHE::BoolAndOp)FHE.nand (::mlir::concretelang::FHE::BoolNandOp)FHE.not (::mlir::concretelang::FHE::BoolNotOp)FHE.or (::mlir::concretelang::FHE::BoolOrOp)FHE.xor (::mlir::concretelang::FHE::BoolXorOp)FHE.from_bool (::mlir::concretelang::FHE::FromBoolOp)FHE.gen_gate (::mlir::concretelang::FHE::GenGateOp)FHE.lsb (::mlir::concretelang::FHE::LsbEintOp)FHE.max_eint (::mlir::concretelang::FHE::MaxEintOp)FHE.mul_eint_int (::mlir::concretelang::FHE::MulEintIntOp)FHE.mul_eint (::mlir::concretelang::FHE::MulEintOp)FHE.mux (::mlir::concretelang::FHE::MuxOp)FHE.neg_eint (::mlir::concretelang::FHE::NegEintOp)FHE.reinterpret_precision (::mlir::concretelang::FHE::ReinterpretPrecisionEintOp)FHE.round (::mlir::concretelang::FHE::RoundEintOp)FHE.sub_eint_int (::mlir::concretelang::FHE::SubEintIntOp)FHE.sub_eint (::mlir::concretelang::FHE::SubEintOp)FHE.sub_int_eint (::mlir::concretelang::FHE::SubIntEintOp)FHE.to_bool (::mlir::concretelang::FHE::ToBoolOp)FHE.to_signed (::mlir::concretelang::FHE::ToSignedOp)FHE.to_unsigned (::mlir::concretelang::FHE::ToUnsignedOp)FHE.zero (::mlir::concretelang::FHE::ZeroEintOp)FHE.zero_tensor (::mlir::concretelang::FHE::ZeroTensorOp)// ok
"FHE.add_eint_int"(%a, %i) : (!FHE.eint<2>, i3) -> !FHE.eint<2>
"FHE.add_eint_int"(%a, %i) : (!FHE.esint<2>, i3) -> !FHE.esint<2>
// error
"FHE.add_eint_int"(%a, %i) : (!FHE.eint<2>, i4) -> !FHE.eint<2>
"FHE.add_eint_int"(%a, %i) : (!FHE.eint<2>, i3) -> !FHE.eint<3>
"FHE.add_eint_int"(%a, %i) : (!FHE.eint<2>, i3) -> !FHE.esint<2>// ok
"FHE.add_eint"(%a, %b): (!FHE.eint<2>, !FHE.eint<2>) -> (!FHE.eint<2>)
"FHE.add_eint"(%a, %b): (!FHE.esint<2>, !FHE.esint<2>) -> (!FHE.esint<2>)
// error
"FHE.add_eint"(%a, %b): (!FHE.eint<2>, !FHE.eint<3>) -> (!FHE.eint<2>)
"FHE.add_eint"(%a, %b): (!FHE.eint<2>, !FHE.eint<2>) -> (!FHE.eint<3>)
"FHE.add_eint"(%a, %b): (!FHE.eint<2>, !FHE.eint<2>) -> (!FHE.esint<2>)
"FHE.add_eint"(%a, %b): (!FHE.esint<2>, !FHE.eint<2>) -> (!FHE.eint<2>)// ok
"FHE.apply_lookup_table"(%a, %lut): (!FHE.eint<2>, tensor<4xi64>) -> (!FHE.eint<2>)
"FHE.apply_lookup_table"(%a, %lut): (!FHE.eint<2>, tensor<4xi64>) -> (!FHE.eint<3>)
"FHE.apply_lookup_table"(%a, %lut): (!FHE.eint<3>, tensor<4xi64>) -> (!FHE.eint<2>)
// error
"FHE.apply_lookup_table"(%a, %lut): (!FHE.eint<2>, tensor<8xi64>) -> (!FHE.eint<2>)"FHE.and"(%a, %b): (!FHE.ebool, !FHE.ebool) -> (!FHE.ebool)"FHE.nand"(%a, %b): (!FHE.ebool, !FHE.ebool) -> (!FHE.ebool)"FHE.not"(%a): (!FHE.ebool) -> (!FHE.ebool)"FHE.or"(%a, %b): (!FHE.ebool, !FHE.ebool) -> (!FHE.ebool)"FHE.xor"(%a, %b): (!FHE.ebool, !FHE.ebool) -> (!FHE.ebool)"FHE.from_bool"(%x) : (!FHE.ebool) -> !FHE.eint<1>
"FHE.from_bool"(%x) : (!FHE.ebool) -> !FHE.eint<2>
"FHE.from_bool"(%x) : (!FHE.ebool) -> !FHE.eint<4>// ok
"FHE.gen_gate"(%a, %b, %ttable): (!FHE.ebool, !FHE.ebool, tensor<4xi64>) -> (!FHE.ebool)
// error
"FHE.gen_gate"(%a, %b, %ttable): (!FHE.ebool, !FHE.ebool, tensor<7xi64>) -> (!FHE.ebool) // Checking if even or odd
%even = "FHE.lsb"(%a): (!FHE.eint<4>) -> (!FHE.eint<1>)
Usually when you extract the lsb bit, you also need to extract the next one.
In that case you first need to clear the first lsb of the input to be able to reduce its precision and extract the next one.
To be able to clear the lsb just extracted, you can get it in the original precision.
Example:
```mlir
// Extracting the first lsb with original precision
%lsb_0 = "FHE.lsb"(%input): (!FHE.eint<4>) -> (!FHE.eint<4>)
// Clearing the first lsb from original input
%input_lsb0_cleared = "FHE.sub_eint"(%input, %lsb_0) : (!FHE.eint<4>, !FHE.eint<4>) -> (!FHE.eint<4>)
// Reducing the precision of the input
%input_3b = "FHE.reinterpret_precision(%input) : (!FHE.eint<4>) -> !FHE.eint<3>
// Now, we can do it again with the second lsb
%lsb_1 = "FHE.lsb"(%input_3b): (!FHE.eint<3>) -> (!FHE.eint<3>)
...
// later if you need %b_lsb at same position as in the input
%lsb_1_at_input_position = "FHE.reinterpret_precision(%b_lsb)" : (!FHE.eint<3>) -> !FHE.eint<4>
// that way you can recombine the extracted bits
%input_mod_4 = "FHE.add_eint"(%lsb_0, %lsb_1_at_input_position) : (!FHE.eint<4>, !FHE.eint<4>) -> (!FHE.eint<4>)// ok
"FHE.max_eint"(%x, %y) : (!FHE.eint<2>, !FHE.eint<2>) -> !FHE.eint<2>
"FHE.max_eint"(%x, %y) : (!FHE.esint<3>, !FHE.esint<3>) -> !FHE.esint<3>
// error
"FHE.max_eint"(%x, %y) : (!FHE.eint<2>, !FHE.eint<3>) -> !FHE.eint<2>
"FHE.max_eint"(%x, %y) : (!FHE.eint<2>, !FHE.eint<2>) -> !FHE.esint<2>
"FHE.max_eint"(%x, %y) : (!FHE.esint<2>, !FHE.eint<2>) -> !FHE.eint<2>// ok
"FHE.mul_eint_int"(%a, %i) : (!FHE.eint<2>, i3) -> !FHE.eint<2>
"FHE.mul_eint_int"(%a, %i) : (!FHE.esint<2>, i3) -> !FHE.esint<2>
// error
"FHE.mul_eint_int"(%a, %i) : (!FHE.eint<2>, i4) -> !FHE.eint<2>
"FHE.mul_eint_int"(%a, %i) : (!FHE.eint<2>, i3) -> !FHE.eint<3>
"FHE.mul_eint_int"(%a, %i) : (!FHE.eint<2>, i3) -> !FHE.esint<2>// ok
"FHE.mul_eint"(%a, %b): (!FHE.eint<2>, !FHE.eint<2>) -> (!FHE.eint<2>)
"FHE.mul_eint"(%a, %b): (!FHE.eint<3>, !FHE.eint<3>) -> (!FHE.eint<3>)
"FHE.mul_eint"(%a, %b): (!FHE.esint<3>, !FHE.esint<3>) -> (!FHE.esint<3>)
// error
"FHE.mul_eint"(%a, %b): (!FHE.eint<2>, !FHE.eint<3>) -> (!FHE.eint<2>)
"FHE.mul_eint"(%a, %b): (!FHE.eint<2>, !FHE.eint<2>) -> (!FHE.eint<3>)
"FHE.mul_eint"(%a, %b): (!FHE.eint<2>, !FHE.eint<2>) -> (!FHE.esint<2>)
"FHE.mul_eint"(%a, %b): (!FHE.esint<2>, !FHE.eint<2>) -> (!FHE.eint<2>)"FHE.mux"(%cond, %c1, %c2): (!FHE.ebool, !FHE.ebool, !FHE.ebool) -> (!FHE.ebool)// ok
"FHE.neg_eint"(%a): (!FHE.eint<2>) -> (!FHE.eint<2>)
"FHE.neg_eint"(%a): (!FHE.esint<2>) -> (!FHE.esint<2>)
// error
"FHE.neg_eint"(%a): (!FHE.eint<2>) -> (!FHE.eint<3>)
"FHE.neg_eint"(%a): (!FHE.eint<2>) -> (!FHE.esint<2>) // assuming %a is stored as 4bits but can be stored with only 2bits
// we can reduce its storage precision
%shifted_a = "FHE.mul_eint_int"(%a, %c_4): (!FHE.eint<4>) -> (!FHE.eint<4>)
%a_small_precision = "FHE.reinterpret_precision"(%shifted_a, %lsb) : (!FHE.eint<4>) -> (!FHE.eint<2>) // ok
"FHE.round"(%a): (!FHE.eint<6>) -> (!FHE.eint<5>)
"FHE.round"(%a): (!FHE.eint<5>) -> (!FHE.eint<3>)
"FHE.round"(%a): (!FHE.eint<3>) -> (!FHE.eint<2>)
"FHE.round"(%a): (!FHE.esint<3>) -> (!FHE.esint<2>)
// error
"FHE.round"(%a): (!FHE.eint<6>) -> (!FHE.eint<6>)
"FHE.round"(%a): (!FHE.eint<4>) -> (!FHE.eint<5>)
"FHE.round"(%a): (!FHE.eint<4>) -> (!FHE.esint<5>)
// ok
"FHE.sub_eint_int"(%i, %a) : (!FHE.eint<2>, i3) -> !FHE.eint<2>
"FHE.sub_eint_int"(%i, %a) : (!FHE.esint<2>, i3) -> !FHE.esint<2>
// error
"FHE.sub_eint_int"(%i, %a) : (!FHE.eint<2>, i4) -> !FHE.eint<2>
"FHE.sub_eint_int"(%i, %a) : (!FHE.eint<2>, i3) -> !FHE.eint<3>
"FHE.sub_eint_int"(%i, %a) : (!FHE.eint<2>, i3) -> !FHE.esint<2>// ok
"FHE.sub_eint"(%a, %b): (!FHE.eint<2>, !FHE.eint<2>) -> (!FHE.eint<2>)
"FHE.sub_eint"(%a, %b): (!FHE.esint<2>, !FHE.esint<2>) -> (!FHE.esint<2>)
// error
"FHE.sub_eint"(%a, %b): (!FHE.eint<2>, !FHE.eint<3>) -> (!FHE.eint<2>)
"FHE.sub_eint"(%a, %b): (!FHE.eint<2>, !FHE.eint<2>) -> (!FHE.eint<3>)
"FHE.sub_eint"(%a, %b): (!FHE.eint<2>, !FHE.esint<2>) -> (!FHE.esint<2>)
"FHE.sub_eint"(%a, %b): (!FHE.eint<2>, !FHE.eint<2>) -> (!FHE.esint<2>)// ok
"FHE.sub_int_eint"(%i, %a) : (i3, !FHE.eint<2>) -> !FHE.eint<2>
"FHE.sub_int_eint"(%i, %a) : (i3, !FHE.esint<2>) -> !FHE.esint<2>
// error
"FHE.sub_int_eint"(%i, %a) : (i4, !FHE.eint<2>) -> !FHE.eint<2>
"FHE.sub_int_eint"(%i, %a) : (i3, !FHE.eint<2>) -> !FHE.eint<3>
"FHE.sub_int_eint"(%i, %a) : (i3, !FHE.eint<2>) -> !FHE.esint<2>// ok
"FHE.to_bool"(%x) : (!FHE.eint<1>) -> !FHE.ebool
"FHE.to_bool"(%x) : (!FHE.eint<2>) -> !FHE.ebool
// error
"FHE.to_bool"(%x) : (!FHE.eint<3>) -> !FHE.ebool// ok
"FHE.to_signed"(%x) : (!FHE.eint<2>) -> !FHE.esint<2>
// error
"FHE.to_signed"(%x) : (!FHE.eint<2>) -> !FHE.esint<3>// ok
"FHE.to_unsigned"(%x) : (!FHE.esint<2>) -> !FHE.eint<2>
// error
"FHE.to_unsigned"(%x) : (!FHE.esint<2>) -> !FHE.eint<3>"FHE.zero"() : () -> !FHE.eint<2>
"FHE.zero"() : () -> !FHE.esint<2>%tensor = "FHE.zero_tensor"() : () -> tensor<5x!FHE.eint<4>>
%tensor = "FHE.zero_tensor"() : () -> tensor<5x!FHE.esint<4>>!FHE.esint<7>
!FHE.esint<6>!FHE.eint<7>
!FHE.eint<6>Concrete.add_lwe_buffer (::mlir::concretelang::Concrete::AddLweBufferOp)Concrete.add_lwe_tensor (::mlir::concretelang::Concrete::AddLweTensorOp)Concrete.add_plaintext_lwe_buffer (::mlir::concretelang::Concrete::AddPlaintextLweBufferOp)Concrete.add_plaintext_lwe_tensor (::mlir::concretelang::Concrete::AddPlaintextLweTensorOp)Concrete.batched_add_lwe_buffer (::mlir::concretelang::Concrete::BatchedAddLweBufferOp)Concrete.batched_add_lwe_tensor (::mlir::concretelang::Concrete::BatchedAddLweTensorOp)Concrete.batched_add_plaintext_cst_lwe_buffer (::mlir::concretelang::Concrete::BatchedAddPlaintextCstLweBufferOp)Concrete.batched_add_plaintext_cst_lwe_tensor (::mlir::concretelang::Concrete::BatchedAddPlaintextCstLweTensorOp)Concrete.batched_add_plaintext_lwe_buffer (::mlir::concretelang::Concrete::BatchedAddPlaintextLweBufferOp)Concrete.batched_add_plaintext_lwe_tensor (::mlir::concretelang::Concrete::BatchedAddPlaintextLweTensorOp)Concrete.batched_bootstrap_lwe_buffer (::mlir::concretelang::Concrete::BatchedBootstrapLweBufferOp)Concrete.batched_bootstrap_lwe_tensor (::mlir::concretelang::Concrete::BatchedBootstrapLweTensorOp)Concrete.batched_keyswitch_lwe_buffer (::mlir::concretelang::Concrete::BatchedKeySwitchLweBufferOp)Concrete.batched_keyswitch_lwe_tensor (::mlir::concretelang::Concrete::BatchedKeySwitchLweTensorOp)Concrete.batched_mapped_bootstrap_lwe_buffer (::mlir::concretelang::Concrete::BatchedMappedBootstrapLweBufferOp)Concrete.batched_mapped_bootstrap_lwe_tensor (::mlir::concretelang::Concrete::BatchedMappedBootstrapLweTensorOp)Concrete.batched_mul_cleartext_cst_lwe_buffer (::mlir::concretelang::Concrete::BatchedMulCleartextCstLweBufferOp)Concrete.batched_mul_cleartext_cst_lwe_tensor (::mlir::concretelang::Concrete::BatchedMulCleartextCstLweTensorOp)Concrete.batched_mul_cleartext_lwe_buffer (::mlir::concretelang::Concrete::BatchedMulCleartextLweBufferOp)Concrete.batched_mul_cleartext_lwe_tensor (::mlir::concretelang::Concrete::BatchedMulCleartextLweTensorOp)Concrete.batched_negate_lwe_buffer (::mlir::concretelang::Concrete::BatchedNegateLweBufferOp)Concrete.batched_negate_lwe_tensor (::mlir::concretelang::Concrete::BatchedNegateLweTensorOp)Concrete.bootstrap_lwe_buffer (::mlir::concretelang::Concrete::BootstrapLweBufferOp)Concrete.bootstrap_lwe_tensor (::mlir::concretelang::Concrete::BootstrapLweTensorOp)Concrete.encode_expand_lut_for_bootstrap_buffer (::mlir::concretelang::Concrete::EncodeExpandLutForBootstrapBufferOp)Concrete.encode_expand_lut_for_bootstrap_tensor (::mlir::concretelang::Concrete::EncodeExpandLutForBootstrapTensorOp)Concrete.encode_lut_for_crt_woppbs_buffer (::mlir::concretelang::Concrete::EncodeLutForCrtWopPBSBufferOp)Concrete.encode_lut_for_crt_woppbs_tensor (::mlir::concretelang::Concrete::EncodeLutForCrtWopPBSTensorOp)Concrete.encode_plaintext_with_crt_buffer (::mlir::concretelang::Concrete::EncodePlaintextWithCrtBufferOp)Concrete.encode_plaintext_with_crt_tensor (::mlir::concretelang::Concrete::EncodePlaintextWithCrtTensorOp)Concrete.keyswitch_lwe_buffer (::mlir::concretelang::Concrete::KeySwitchLweBufferOp)Concrete.keyswitch_lwe_tensor (::mlir::concretelang::Concrete::KeySwitchLweTensorOp)Concrete.mul_cleartext_lwe_buffer (::mlir::concretelang::Concrete::MulCleartextLweBufferOp)Concrete.mul_cleartext_lwe_tensor (::mlir::concretelang::Concrete::MulCleartextLweTensorOp)Concrete.negate_lwe_buffer (::mlir::concretelang::Concrete::NegateLweBufferOp)Concrete.negate_lwe_tensor (::mlir::concretelang::Concrete::NegateLweTensorOp)Concrete.wop_pbs_crt_lwe_buffer (::mlir::concretelang::Concrete::WopPBSCRTLweBufferOp)Concrete.wop_pbs_crt_lwe_tensor (::mlir::concretelang::Concrete::WopPBSCRTLweTensorOp)FHELinalg.add_eint_int (::mlir::concretelang::FHELinalg::AddEintIntOp)FHELinalg.add_eint (::mlir::concretelang::FHELinalg::AddEintOp)FHELinalg.apply_lookup_table (::mlir::concretelang::FHELinalg::ApplyLookupTableEintOp)FHELinalg.apply_mapped_lookup_table (::mlir::concretelang::FHELinalg::ApplyMappedLookupTableEintOp)FHELinalg.apply_multi_lookup_table (::mlir::concretelang::FHELinalg::ApplyMultiLookupTableEintOp)FHELinalg.concat (::mlir::concretelang::FHELinalg::ConcatOp)FHELinalg.conv2d (::mlir::concretelang::FHELinalg::Conv2dOp)FHELinalg.dot_eint_int (::mlir::concretelang::FHELinalg::Dot)FHELinalg.dot_eint_eint (::mlir::concretelang::FHELinalg::DotEint)FHELinalg.from_element (::mlir::concretelang::FHELinalg::FromElementOp)FHELinalg.lsb (::mlir::concretelang::FHELinalg::LsbEintOp)FHELinalg.matmul_eint_eint (::mlir::concretelang::FHELinalg::MatMulEintEintOp)FHELinalg.matmul_eint_int (::mlir::concretelang::FHELinalg::MatMulEintIntOp)FHELinalg.matmul_int_eint (::mlir::concretelang::FHELinalg::MatMulIntEintOp)FHELinalg.maxpool2d (::mlir::concretelang::FHELinalg::Maxpool2dOp)FHELinalg.mul_eint_int (::mlir::concretelang::FHELinalg::MulEintIntOp)FHELinalg.mul_eint (::mlir::concretelang::FHELinalg::MulEintOp)FHELinalg.neg_eint (::mlir::concretelang::FHELinalg::NegEintOp)FHELinalg.reinterpret_precision (::mlir::concretelang::FHELinalg::ReinterpretPrecisionEintOp)FHELinalg.round (::mlir::concretelang::FHELinalg::RoundOp)FHELinalg.sub_eint (::mlir::concretelang::FHELinalg::SubEintOp)FHELinalg.sub_int_eint (::mlir::concretelang::FHELinalg::SubIntEintOp)FHELinalg.sum (::mlir::concretelang::FHELinalg::SumOp)FHELinalg.to_signed (::mlir::concretelang::FHELinalg::ToSignedOp)FHELinalg.to_unsigned (::mlir::concretelang::FHELinalg::ToUnsignedOp)FHELinalg.transpose (::mlir::concretelang::FHELinalg::TransposeOp)// Returns the term-by-term addition of `%a0` with `%a1`
"FHELinalg.add_eint_int"(%a0, %a1) : (tensor<4x!FHE.eint<4>>, tensor<4xi5>) -> tensor<4x!FHE.eint<4>>
// Returns the term-by-term addition of `%a0` with `%a1`, where dimensions equal to one are stretched.
"FHELinalg.add_eint_int"(%a0, %a1) : (tensor<4x1x4x!FHE.eint<4>>, tensor<1x4x4xi5>) -> tensor<4x4x4x!FHE.eint<4>>
// Returns the addition of a 3x3 matrix of encrypted integers and a 3x1 matrix (a column) of integers.
//
// [1,2,3] [1] [2,3,4]
// [4,5,6] + [2] = [6,7,8]
// [7,8,9] [3] [10,11,12]
//
// The dimension #1 of operand #2 is stretched as it is equal to 1.
"FHELinalg.add_eint_int"(%a0, %a1) : (tensor<3x3x!FHE.eint<4>>, tensor<3x1xi5>) -> tensor<3x3x!FHE.eint<4>>
// Returns the addition of a 3x3 matrix of encrypted integers and a 1x3 matrix (a line) of integers.
//
// [1,2,3] [2,4,6]
// [4,5,6] + [1,2,3] = [5,7,9]
// [7,8,9] [8,10,12]
//
// The dimension #2 of operand #2 is stretched as it is equal to 1.
"FHELinalg.add_eint_int"(%a0, %a1) : (tensor<3x3x!FHE.eint<4>>, tensor<1x3xi5>) -> tensor<3x3x!FHE.eint<4>>
// Same behavior as the previous one, but as the dimension #2 is missing of operand #2.
"FHELinalg.add_eint_int(%a0, %a1)" : (tensor<3x4x!FHE.eint<4>>, tensor<3xi5>) -> tensor<4x4x4x!FHE.eint<4>>
// Returns the term-by-term addition of `%a0` with `%a1`
"FHELinalg.add_eint"(%a0, %a1) : (tensor<4x!FHE.eint<4>>, tensor<4x!FHE.eint<4>>) -> tensor<4x!FHE.eint<4>>
// Returns the term-by-term addition of `%a0` with `%a1`, where dimensions equal to one are stretched.
"FHELinalg.add_eint"(%a0, %a1) : (tensor<4x1x4x!FHE.eint<4>>, tensor<1x4x4x!FHE.eint<4>>) -> tensor<4x4x4x!FHE.eint<4>>
// Returns the addition of a 3x3 matrix of encrypted integers and a 3x1 matrix (a column) of encrypted integers.
//
// [1,2,3] [1] [2,3,4]
// [4,5,6] + [2] = [6,7,8]
// [7,8,9] [3] [10,11,12]
//
// The dimension #1 of operand #2 is stretched as it is equal to 1.
"FHELinalg.add_eint"(%a0, %a1) : (tensor<3x3x!FHE.eint<4>>, tensor<3x1x!FHE.eint<4>>) -> tensor<3x3x!FHE.eint<4>>
// Returns the addition of a 3x3 matrix of encrypted integers and a 1x3 matrix (a line) of encrypted integers.
//
// [1,2,3] [2,4,6]
// [4,5,6] + [1,2,3] = [5,7,9]
// [7,8,9] [8,10,12]
//
// The dimension #2 of operand #2 is stretched as it is equal to 1.
"FHELinalg.add_eint"(%a0, %a1) : (tensor<3x3x!FHE.eint<4>>, tensor<1x3x!FHE.eint<4>>) -> tensor<3x3x!FHE.eint<4>>
// Same behavior as the previous one, but as the dimension #2 of operand #2 is missing.
"FHELinalg.add_eint"(%a0, %a1) : (tensor<3x3x!FHE.eint<4>>, tensor<3x!FHE.eint<4>>) -> tensor<3x3x!FHE.eint<4>>// The result of this operation, is a tensor that contains the result of a lookup table.
// i.e. %res[i, ..., k] = %lut[%t[i, ..., k]]
%res = FHELinalg.apply_lookup_table(%t, %lut): tensor<DNx...xD1x!FHE.eint<$p>>, tensor<D2^$pxi64> -> tensor<DNx...xD1x!FHE.eint<$p>>
// Returns the lookup of 3x3 matrix of encrypted indices of with 2 on a table of size 4=2² of clear integers.
//
// [0,1,2] [1,3,5]
// [3,0,1] lut [1,3,5,7] = [7,1,3]
// [2,3,0] [5,7,1]
"FHELinalg.apply_lookup_table"(%t, %lut) : (tensor<3x3x!FHE.eint<2>>, tensor<4xi64>) -> tensor<3x3x!FHE.eint<3>>// The result of this operation, is a tensor that contains the result of the lookup on different tables.
// i.e. %res[i, ..., k] = %luts[ %map[i, ..., k] ][ %t[i, ..., k] ]
%res = FHELinalg.apply_mapped_lookup_table(%t, %luts, %map): tensor<DNx...xD1x!FHE.eint<$p>>, tensor<DM x ^$p>, tensor<DNx...xD1xindex> -> tensor<DNx...xD1x!FHE.eint<$p>>
// Returns the lookup of 3x2 matrix of encrypted indices of width 2 on a vector of 2 tables of size 4=2^2 of clear integers.
//
// [0,1] [0, 1] = [1,2]
// [3,0] lut [[1,3,5,7], [0,2,4,6]] with [0, 1] = [7,0]
// [2,3] [0, 1] = [5,6]
"FHELinalg.apply_mapped_lookup_table"(%t, %luts, %map) : (tensor<3x2x!FHE.eint<2>>, tensor<2x4xi64>, tensor<3x2xindex>) -> tensor<3x2x!FHE.eint<3>>// The result of this operation, is a tensor that contains the result of the lookup on different tables.
// i.e. %res[i, ..., k] = [ %luts[i][%t[i]], ..., %luts[k][%t[k]] ]
%res = FHELinalg.apply_multi_lookup_table(%t, %lut): tensor<DNx...xD1x!FHE.eint<$p>>, tensor<DMx...xD1xD2^$pxi64> -> tensor<DNx...xD1x!FHE.eint<$p>>
// Returns the lookup of 3x2 matrix of encrypted indices of width 2 on a vector of 2 tables of size 4=2² of clear integers.
// The tables are broadcasted along the first dimension of the tensor.
//
// [0,1] = [1,2]
// [3,0] lut [[1,3,5,7], [0,2,4,6]] = [7,0]
// [2,3] = [5,6]
"FHELinalg.apply_multi_lookup_table"(%t, %luts) : (tensor<3x2x!FHE.eint<2>>, tensor<2x4xi64>) -> tensor<3x2x!FHE.eint<3>>
// Returns the lookup of a vector of 3 encrypted indices of width 2 on a vector of 3 tables of size 4=2² of clear integers.
//
// [3,0,1] lut [[1,3,5,7], [0,2,4,6], [1,2,3,4]] = [7,0,2]
"FHELinalg.apply_multi_lookup_table"(%t, %luts) : (tensor<3x!FHE.eint<2>>, tensor<3x4xi64>) -> tensor<3x!FHE.eint<3>>"FHELinalg.concat"(%a, %b) { axis = 0 } : (tensor<3x3x!FHE.eint<4>>, tensor<3x3x!FHE.eint<4>>) -> tensor<6x3x!FHE.eint<4>>
//
// ( [1,2,3] [1,2,3] ) [1,2,3]
// concat ( [4,5,6], [4,5,6] ) = [4,5,6]
// ( [7,8,9] [7,8,9] ) [7,8,9]
// [1,2,3]
// [4,5,6]
// [7,8,9]
//"FHELinalg.concat"(%a, %b) { axis = 1 } : (tensor<3x3x!FHE.eint<4>>, tensor<3x3x!FHE.eint<4>>) -> tensor<3x6x!FHE.eint<4>>
//
// ( [1,2,3] [1,2,3] ) [1,2,3,1,2,3]
// concat ( [4,5,6], [4,5,6] ) = [4,5,6,4,5,6]
// ( [7,8,9] [7,8,9] ) [7,8,9,7,8,9]
//// Returns the dot product of `%a0` with `%a1`
"FHELinalg.dot_eint_int"(%a0, %a1) : (tensor<4x!FHE.eint<4>>, tensor<4xi5>) -> !FHE.eint<4>
// Returns the dot product of `%a0` with `%a1`
"FHELinalg.dot_eint_eint"(%a0, %a1) : (tensor<4x!FHE.eint<4>>, tensor<4x!FHE.eint<4>>) -> !FHE.eint<4>
"FHELinalg.from_element"(%a) : (Type) -> tensor<1xType> // ok
%lsb = "FHE.lsb"(%a): (tensor<1x!FHE.eint<4>)) -> (tensor<1x!FHE.eint<1>>)
If you need to clear the lsb of the original ciphertext, you should extract to the same precision as the ciphertext.
If you need to extract several bits, you can extract sequentially using explicit bitwidth change and bit clearing.
Example:
```mlir
// ok
%a_lsb = "FHELinalg.lsb"(%a): (tensor<1x!FHE.eint<4>)) -> (tensor<1x!FHE.eint<4>))
%a_lsb_cleared = "FHELinalg.sub_eint"(%a, %lsb) : (tensor<1x!FHE.eint<4>), tensor<1x!FHE.eint<4>)) -> (tensor<1x!FHE.eint<4>))
%b = %a : tensor<1x!FHE.eint<3>>
// now you can extract the next lsb from %b
%b_lsb = "FHELinalg.lsb"(%b): (tensor<1x!FHE.eint<3>>) -> (tensor<1x!FHE.eint<3>>)
// later if you need %b_lsb at the original position
%b_lsb_as_in_a = %b_lsb : tensor<1x!FHE.eint<3>>- If both arguments are 2-D,
they are multiplied like conventional matrices.
e.g.,
arg0: tensor<MxN> = [...]
arg1: tensor<NxP> = [...]
result: tensor<MxP> = [...]
- If the first argument is a vector (1-D),
it is treated as a matrix with a single row and standard matrix multiplication is performed.
After standard matrix multiplication,
the first dimension is removed from the result.
e.g.,
arg0: tensor<3> = [x, y, z]
arg1: tensor<3xM> = [
[_, _, ..., _, _],
[_, _, ..., _, _],
[_, _, ..., _, _],
]
is treated as
arg0: tensor<1x3> = [
[x, y, z]
]
arg1: tensor<3xM> = [
[_, _, ..., _, _],
[_, _, ..., _, _],
[_, _, ..., _, _],
]
and matrix multiplication is performed with the following form (1x3 @ 3xM -> 1xM)
result: tensor<1xM> = [[_, _, ..., _, _]]
finally, the first dimension is removed by definition so the result has the following form
result: tensor<M> = [_, _, ..., _, _]
- If the second argument is 1-D,
it is treated as a matrix with a single column and standard matrix multiplication is performed.
After standard matrix multiplication,
the last dimension is removed from the result.
e.g.,
arg0: tensor<Mx3> = [
[_, _, _],
[_, _, _],
...,
[_, _, _],
[_, _, _],
]
arg1: tensor<3> = [x, y, z]
is treated as
arg0: tensor<Mx3> = [
[_, _, _],
[_, _, _],
...,
[_, _, _],
[_, _, _],
]
arg1: tensor<3x1> = [
[x],
[y],
[z],
]
and matrix multiplication is performed with the following form (Mx3 @ 3x1 -> Mx1)
result: tensor<Mx1> = [
[_],
[_],
...,
[_],
[_],
]
finally, the last dimension is removed by definition so the result has the following form
result: tensor<M> = [_, _, _]
- If either argument is N-D where N > 2,
the operation is treated as a collection of matrices residing in the last two indices and broadcasted accordingly.
arg0: tensor<Kx1MxN> = [...]
arg1: tensor<LxNxP> = [...]
result: tensor<KxLxMxP> = [...]"FHELinalg.matmul_eint_eint(%a, %b) : (tensor<MxNx!FHE.eint<p>>, tensor<NxPx!FHE.eint<p>'>) -> tensor<MxPx!FHE.eint<p>>"
"FHELinalg.matmul_eint_eint(%a, %b) : (tensor<KxLxMxNx!FHE.eint<p>>, tensor<KxLxNxPx!FHE.eint<p>'>) -> tensor<KxLxMxPx!FHE.eint<p>>"
"FHELinalg.matmul_eint_eint(%a, %b) : (tensor<MxNx!FHE.eint<p>>, tensor<Nx!FHE.eint<p>'>) -> tensor<Mx!FHE.eint<p>>"
"FHELinalg.matmul_eint_eint(%a, %b) : (tensor<Nx!FHE.eint<p>>, tensor<NxPx!FHE.eint<p>'>) -> tensor<Px!FHE.eint<p>>"// Returns the matrix multiplication of a 3x2 matrix of encrypted integers and a 2x3 matrix of integers.
// [ 1, 2, 3]
// [ 2, 3, 4]
// *
// [1,2] [ 5, 8,11]
// [3,4] = [11,18,25]
// [5,6] [17,28,39]
//
"FHELinalg.matmul_eint_eint"(%a, %b) : (tensor<3x2x!FHE.eint<6>>, tensor<2x3x!FHE.eint<6>>) -> tensor<3x3x!FHE.eint<12>>- If both arguments are 2-D,
they are multiplied like conventional matrices.
e.g.,
arg0: tensor<MxN> = [...]
arg1: tensor<NxP> = [...]
result: tensor<MxP> = [...]
- If the first argument is a vector (1-D),
it is treated as a matrix with a single row and standard matrix multiplication is performed.
After standard matrix multiplication,
the first dimension is removed from the result.
e.g.,
arg0: tensor<3> = [x, y, z]
arg1: tensor<3xM> = [
[_, _, ..., _, _],
[_, _, ..., _, _],
[_, _, ..., _, _],
]
is treated as
arg0: tensor<1x3> = [
[x, y, z]
]
arg1: tensor<3xM> = [
[_, _, ..., _, _],
[_, _, ..., _, _],
[_, _, ..., _, _],
]
and matrix multiplication is performed with the following form (1x3 @ 3xM -> 1xM)
result: tensor<1xM> = [[_, _, ..., _, _]]
finally, the first dimension is removed by definition so the result has the following form
result: tensor<M> = [_, _, ..., _, _]
- If the second argument is 1-D,
it is treated as a matrix with a single column and standard matrix multiplication is performed.
After standard matrix multiplication,
the last dimension is removed from the result.
e.g.,
arg0: tensor<Mx3> = [
[_, _, _],
[_, _, _],
...,
[_, _, _],
[_, _, _],
]
arg1: tensor<3> = [x, y, z]
is treated as
arg0: tensor<Mx3> = [
[_, _, _],
[_, _, _],
...,
[_, _, _],
[_, _, _],
]
arg1: tensor<3x1> = [
[x],
[y],
[z],
]
and matrix multiplication is performed with the following form (Mx3 @ 3x1 -> Mx1)
result: tensor<Mx1> = [
[_],
[_],
...,
[_],
[_],
]
finally, the last dimension is removed by definition so the result has the following form
result: tensor<M> = [_, _, _]
- If either argument is N-D where N > 2,
the operation is treated as a collection of matrices residing in the last two indices and broadcasted accordingly.
arg0: tensor<Kx1MxN> = [...]
arg1: tensor<LxNxP> = [...]
result: tensor<KxLxMxP> = [...]"FHELinalg.matmul_eint_int(%a, %b) : (tensor<MxNx!FHE.eint<p>>, tensor<NxPxip'>) -> tensor<MxPx!FHE.eint<p>>"
"FHELinalg.matmul_eint_int(%a, %b) : (tensor<KxLxMxNx!FHE.eint<p>>, tensor<KxLxNxPxip'>) -> tensor<KxLxMxPx!FHE.eint<p>>"
"FHELinalg.matmul_eint_int(%a, %b) : (tensor<MxNx!FHE.eint<p>>, tensor<Nxip'>) -> tensor<Mx!FHE.eint<p>>"
"FHELinalg.matmul_eint_int(%a, %b) : (tensor<Nx!FHE.eint<p>>, tensor<NxPxip'>) -> tensor<Px!FHE.eint<p>>"// Returns the matrix multiplication of a 3x2 matrix of encrypted integers and a 2x3 matrix of integers.
// [ 1, 2, 3]
// [ 2, 3, 4]
// *
// [1,2] [ 5, 8,11]
// [3,4] = [11,18,25]
// [5,6] [17,28,39]
//
"FHELinalg.matmul_eint_int"(%a, %b) : (tensor<3x2x!FHE.eint<6>>, tensor<2x3xi7>) -> tensor<3x3x!FHE.eint<6>>- If both arguments are 2-D,
they are multiplied like conventional matrices.
e.g.,
arg0: tensor<MxN> = [...]
arg1: tensor<NxP> = [...]
result: tensor<MxP> = [...]
- If the first argument is a vector (1-D),
it is treated as a matrix with a single row and standard matrix multiplication is performed.
After standard matrix multiplication,
the first dimension is removed from the result.
e.g.,
arg0: tensor<3> = [x, y, z]
arg1: tensor<3xM> = [
[_, _, ..., _, _],
[_, _, ..., _, _],
[_, _, ..., _, _],
]
is treated as
arg0: tensor<1x3> = [
[x, y, z]
]
arg1: tensor<3xM> = [
[_, _, ..., _, _],
[_, _, ..., _, _],
[_, _, ..., _, _],
]
and matrix multiplication is performed with the following form (1x3 @ 3xM -> 1xM)
result: tensor<1xM> = [[_, _, ..., _, _]]
finally, the first dimension is removed by definition so the result has the following form
result: tensor<M> = [_, _, ..., _, _]
- If the second argument is 1-D,
it is treated as a matrix with a single column and standard matrix multiplication is performed.
After standard matrix multiplication,
the last dimension is removed from the result.
e.g.,
arg0: tensor<Mx3> = [
[_, _, _],
[_, _, _],
...,
[_, _, _],
[_, _, _],
]
arg1: tensor<3> = [x, y, z]
is treated as
arg0: tensor<Mx3> = [
[_, _, _],
[_, _, _],
...,
[_, _, _],
[_, _, _],
]
arg1: tensor<3x1> = [
[x],
[y],
[z],
]
and matrix multiplication is performed with the following form (Mx3 @ 3x1 -> Mx1)
result: tensor<Mx1> = [
[_],
[_],
...,
[_],
[_],
]
finally, the last dimension is removed by definition so the result has the following form
result: tensor<M> = [_, _, _]
- If either argument is N-D where N > 2,
the operation is treated as a collection of matrices residing in the last two indices and broadcasted accordingly.
arg0: tensor<Kx1MxN> = [...]
arg1: tensor<LxNxP> = [...]
result: tensor<KxLxMxP> = [...]"FHELinalg.matmul_int_eint(%a, %b) : (tensor<MxNxip'>, tensor<NxPxFHE.eint<p>>) -> tensor<MxPx!FHE.eint<p>>"
"FHELinalg.matmul_int_eint(%a, %b) : (tensor<KxLxMxNxip'>, tensor<KxLxNxPxFHE.eint<p>>) -> tensor<KxLxMxPx!FHE.eint<p>>"
"FHELinalg.matmul_int_eint(%a, %b) : (tensor<MxNxip'>, tensor<NxFHE.eint<p>>) -> tensor<Mx!FHE.eint<p>>"
"FHELinalg.matmul_int_eint(%a, %b) : (tensor<Nxip'>, tensor<NxPxFHE.eint<p>>) -> tensor<Px!FHE.eint<p>>"// Returns the matrix multiplication of a 3x2 matrix of clear integers and a 2x3 matrix of encrypted integers.
// [ 1, 2, 3]
// [ 2, 3, 4]
// *
// [1,2] [ 5, 8,11]
// [3,4] = [11,18,25]
// [5,6] [17,28,39]
//
"FHELinalg.matmul_int_eint"(%a, %b) : (tensor<3x2xi7>, tensor<2x3x!FHE.eint<6>>) -> tensor<3x3x!FHE.eint<6>>// Returns the term-by-term multiplication of `%a0` with `%a1`
"FHELinalg.mul_eint_int"(%a0, %a1) : (tensor<4x!FHE.eint<4>>, tensor<4xi5>) -> tensor<4x!FHE.eint<4>>
// Returns the term-by-term multiplication of `%a0` with `%a1`, where dimensions equal to one are stretched.
"FHELinalg.mul_eint_int"(%a0, %a1) : (tensor<4x1x4x!FHE.eint<4>>, tensor<1x4x4xi5>) -> tensor<4x4x4x!FHE.eint<4>>
// Returns the multiplication of a 3x3 matrix of encrypted integers and a 3x1 matrix (a column) of integers.
//
// [1,2,3] [1] [1,2,3]
// [4,5,6] * [2] = [8,10,18]
// [7,8,9] [3] [21,24,27]
//
// The dimension #1 of operand #2 is stretched as it is equal to 1.
"FHELinalg.mul_eint_int"(%a0, %a1) : (tensor<3x3x!FHE.eint<4>>, tensor<3x1xi5>) -> tensor<3x3x!FHE.eint<4>>
// Returns the multiplication of a 3x3 matrix of encrypted integers and a 1x3 matrix (a line) of integers.
//
// [1,2,3] [2,4,6]
// [4,5,6] * [1,2,3] = [5,7,9]
// [7,8,9] [8,10,12]
//
// The dimension #2 of operand #2 is stretched as it is equal to 1.
"FHELinalg.mul_eint_int"(%a0, %a1) : (tensor<3x3x!FHE.eint<4>>, tensor<1x3xi5>) -> tensor<3x3x!FHE.eint<4>>
// Same behavior as the previous one, but as the dimension #2 is missing of operand #2.
"FHELinalg.mul_eint_int"(%a0, %a1) : (tensor<3x3x!FHE.eint<4>>, tensor<3xi5>) -> tensor<3x3x!FHE.eint<4>>
// Returns the term-by-term multiplication of `%a0` with `%a1`
"FHELinalg.mul_eint"(%a0, %a1) : (tensor<4x!FHE.eint<8>>, tensor<4x!FHE.eint<8>>) -> tensor<4x!FHE.eint<8>>
// Returns the term-by-term multiplication of `%a0` with `%a1`, where dimensions equal to one are stretched.
"FHELinalg.mul_eint"(%a0, %a1) : (tensor<4x1x4x!FHE.eint<8>>, tensor<1x4x4x!FHE.eint<8>>) -> tensor<4x4x4x!FHE.eint<8>>
// Returns the multiplication of a 3x3 matrix of encrypted integers and a 3x1 matrix (a column) of encrypted integers.
//
// [1,2,3] [1] [1,2,3]
// [4,5,6] * [2] = [8,10,12]
// [7,8,9] [3] [21,24,27]
//
// The dimension #1 of operand #2 is stretched as it is equal to 1.
"FHELinalg.mul_eint"(%a0, %a1) : (tensor<3x3x!FHE.eint<8>>, tensor<3x1x!FHE.eint<8>>) -> tensor<3x3x!FHE.eint<8>>
// Returns the multiplication of a 3x3 matrix of encrypted integers and a 1x3 matrix (a line) of encrypted integers.
//
// [1,2,3] [1,4,9]
// [4,5,6] * [1,2,3] = [4,10,18]
// [7,8,9] [7,16,27]
//
// The dimension #2 of operand #2 is stretched as it is equal to 1.
"FHELinalg.mul_eint"(%a0, %a1) : (tensor<3x3x!FHE.eint<8>>, tensor<1x3x!FHE.eint<8>>) -> tensor<3x3x!FHE.eint<8>>
// Same behavior as the previous one, but as the dimension #2 of operand #2 is missing.
"FHELinalg.mul_eint"(%a0, %a1) : (tensor<3x3x!FHE.eint<8>>, tensor<3x!FHE.eint<8>>) -> tensor<3x3x!FHE.eint<8>>// Returns the term-by-term negation of `%a0`
"FHELinalg.neg_eint"(%a0) : (tensor<3x3x!FHE.eint<4>>) -> tensor<3x3x!FHE.eint<4>>
//
// ( [1,2,3] ) [31,30,29]
// negate ( [4,5,6] ) = [28,27,26]
// ( [7,8,9] ) [25,24,23]
//
// The negation is computed as `2**(p+1) - a` where p=4 here. // assuming a is encoded as 4bits but can be stored in 2bits
// we can obtain a to a smaller 2 bits precision
%shifted_a = "FHELinalg.mul_eint_intlsb"(%a, %c_4): (tensor<1x!FHE.eint<4>>) -> (tensor<1x!FHE.eint<2>>)
%a_small_precision = "FHELinalg.reinterpret_precision"(%shifted_a, %lsb) : (tensor<1x!FHE.eint<4>>) -> (tensor<1x!FHE.eint<2>>) Assuming a ciphertext whose message is implemented over `p` bits, this
operation rounds it to fit to `q` bits where `p>q`.
Example:
```mlir
// ok
"FHELinalg.round"(%a): (tensor<3x!FHE.eint<6>>) -> (tensor<3x!FHE.eint<5>>)
"FHELinalg.round"(%a): (tensor<3x!FHE.eint<5>>) -> (tensor<3x!FHE.eint<3>>)
"FHELinalg.round"(%a): (tensor<3x!FHE.eint<3>>) -> (tensor<3x!FHE.eint<2>>)
"FHELinalg.round"(%a): (tensor<3x!FHE.esint<3>>) -> (tensor<3x!FHE.esint<2>>)
// error
"FHELinalg.round"(%a): (tensor<3x!FHE.eint<6>>) -> (tensor<3x!FHE.eint<6>>)
"FHELinalg.round"(%a): (tensor<3x!FHE.eint<4>>) -> (tensor<3x!FHE.eint<5>>)
"FHELinalg.round"(%a): (tensor<3x!FHE.eint<4>>) -> (tensor<3x!FHE.esint<2>>)
Traits: AlwaysSpeculatableImplTrait, TensorUnaryEint
Interfaces: ConditionallySpeculatable, NoMemoryEffect (MemoryEffectOpInterface), UnaryEint
Effects: MemoryEffects::Effect{}
#### Operands:
| Operand | Description |
| :-----: | ----------- |
| `input` |
#### Results:
| Result | Description |
| :----: | ----------- |
| `output` |
### `FHELinalg.sub_eint_int` (::mlir::concretelang::FHELinalg::SubEintIntOp)
Returns a tensor that contains the subtraction of a tensor of clear integers from a tensor of encrypted integers.
Performs a subtraction following the broadcasting rules between a tensor of clear integers from a tensor of encrypted integers.
The width of the clear integers must be less than or equal to the width of encrypted integers.
Examples:
```mlir
// Returns the term-by-term subtraction of `%a0` with `%a1`
"FHELinalg.sub_eint_int"(%a0, %a1) : (tensor<4x!FHE.eint<4>>, tensor<4xi5>) -> tensor<4x!FHE.eint<4>>
// Returns the term-by-term subtraction of `%a0` with `%a1`, where dimensions equal to one are stretched.
"FHELinalg.sub_eint_int"(%a0, %a1) : (tensor<1x4x4x!FHE.eint<4>>, tensor<4x1x4xi5>) -> tensor<4x4x4x!FHE.eint<4>>
// Returns the subtraction of a 3x3 matrix of integers and a 3x1 matrix (a column) of encrypted integers.
//
// [1,2,3] [1] [0,2,3]
// [4,5,6] - [2] = [2,3,4]
// [7,8,9] [3] [4,5,6]
//
// The dimension #1 of operand #2 is stretched as it is equal to 1.
"FHELinalg.sub_eint_int"(%a0, %a1) : (tensor<3x1x!FHE.eint<4>>, tensor<3x3xi5>) -> tensor<3x3x!FHE.eint<4>>
// Returns the subtraction of a 3x3 matrix of integers and a 1x3 matrix (a line) of encrypted integers.
//
// [1,2,3] [0,0,0]
// [4,5,6] - [1,2,3] = [3,3,3]
// [7,8,9] [6,6,6]
//
// The dimension #2 of operand #2 is stretched as it is equal to 1.
"FHELinalg.sub_eint_int"(%a0, %a1) : (tensor<1x3x!FHE.eint<4>>, tensor<3x3xi5>) -> tensor<3x3x!FHE.eint<4>>
// Same behavior as the previous one, but as the dimension #2 is missing of operand #2.
"FHELinalg.sub_eint_int"(%a0, %a1) : (tensor<3x!FHE.eint<4>>, tensor<3x3xi5>) -> tensor<3x3x!FHE.eint<4>>
// Returns the term-by-term subtraction of `%a0` with `%a1`
"FHELinalg.sub_eint"(%a0, %a1) : (tensor<4x!FHE.eint<4>>, tensor<4x!FHE.eint<4>>) -> tensor<4x!FHE.eint<4>>
// Returns the term-by-term subtraction of `%a0` with `%a1`, where dimensions equal to one are stretched.
"FHELinalg.sub_eint"(%a0, %a1) : (tensor<4x1x4x!FHE.eint<4>>, tensor<1x4x4x!FHE.eint<4>>) -> tensor<4x4x4x!FHE.eint<4>>
// Returns the subtraction of a 3x3 matrix of integers and a 3x1 matrix (a column) of encrypted integers.
//
// [1,2,3] [1] [0,2,3]
// [4,5,6] - [2] = [2,3,4]
// [7,8,9] [3] [4,5,6]
//
// The dimension #1 of operand #2 is stretched as it is equal to 1.
"FHELinalg.sub_eint"(%a0, %a1) : (tensor<3x3x!FHE.eint<4>>, tensor<3x1x!FHE.eint<4>>) -> tensor<3x3x!FHE.eint<4>>
// Returns the subtraction of a 3x3 matrix of integers and a 1x3 matrix (a line) of encrypted integers.
//
// [1,2,3] [0,0,0]
// [4,5,6] - [1,2,3] = [3,3,3]
// [7,8,9] [6,6,6]
//
// The dimension #2 of operand #2 is stretched as it is equal to 1.
"FHELinalg.sub_eint"(%a0, %a1) : (tensor<3x3x!FHE.eint<4>>, tensor<1x3x!FHE.eint<4>>) -> tensor<3x3x!FHE.eint<4>>
// Same behavior as the previous one, but as the dimension #2 of operand #2 is missing.
"FHELinalg.sub_eint"(%a0, %a1) : (tensor<3x3x!FHE.eint<4>>, tensor<3x!FHE.eint<4>>) -> tensor<3x3x!FHE.eint<4>>// Returns the term-by-term subtraction of `%a0` with `%a1`
"FHELinalg.sub_int_eint"(%a0, %a1) : (tensor<4xi5>, tensor<4x!FHE.eint<4>>) -> tensor<4x!FHE.eint<4>>
// Returns the term-by-term subtraction of `%a0` with `%a1`, where dimensions equal to one are stretched.
"FHELinalg.sub_int_eint"(%a0, %a1) : (tensor<4x1x4xi5>, tensor<1x4x4x!FHE.eint<4>>) -> tensor<4x4x4x!FHE.eint<4>>
// Returns the subtraction of a 3x3 matrix of integers and a 3x1 matrix (a column) of encrypted integers.
//
// [1,2,3] [1] [0,2,3]
// [4,5,6] - [2] = [2,3,4]
// [7,8,9] [3] [4,5,6]
//
// The dimension #1 of operand #2 is stretched as it is equal to 1.
"FHELinalg.sub_int_eint"(%a0, %a1) : (tensor<3x3xi5>, tensor<3x1x!FHE.eint<4>>) -> tensor<3x3x!FHE.eint<4>>
// Returns the subtraction of a 3x3 matrix of integers and a 1x3 matrix (a line) of encrypted integers.
//
// [1,2,3] [0,0,0]
// [4,5,6] - [1,2,3] = [3,3,3]
// [7,8,9] [6,6,6]
//
// The dimension #2 of operand #2 is stretched as it is equal to 1.
"FHELinalg.sub_int_eint"(%a0, %a1) : (tensor<3x3xi5>, tensor<1x3x!FHE.eint<4>>) -> tensor<3x3x!FHE.eint<4>>
// Same behavior as the previous one, but as the dimension #2 is missing of operand #2.
"FHELinalg.sub_int_eint"(%a0, %a1) : (tensor<3x3xi5>, tensor<3x!FHE.eint<4>>) -> tensor<3x3x!FHE.eint<4>>
// Returns the sum of all elements of `%a0`
"FHELinalg.sum"(%a0) : (tensor<3x3x!FHE.eint<4>>) -> !FHE.eint<4>
//
// ( [1,2,3] )
// sum ( [4,5,6] ) = 45
// ( [7,8,9] )
//// Returns the sum of all elements of `%a0` along columns
"FHELinalg.sum"(%a0) { axes = [0] } : (tensor<3x2x!FHE.eint<4>>) -> tensor<2x!FHE.eint<4>>
//
// ( [1,2] )
// sum ( [3,4] ) = [9, 12]
// ( [5,6] )
//// Returns the sum of all elements of `%a0` along columns while preserving dimensions
"FHELinalg.sum"(%a0) { axes = [0], keep_dims = true } : (tensor<3x2x!FHE.eint<4>>) -> tensor<1x2x!FHE.eint<4>>
//
// ( [1,2] )
// sum ( [3,4] ) = [[9, 12]]
// ( [5,6] )
//// Returns the sum of all elements of `%a0` along rows
"FHELinalg.sum"(%a0) { axes = [1] } : (tensor<3x2x!FHE.eint<4>>) -> tensor<3x!FHE.eint<4>>
//
// ( [1,2] )
// sum ( [3,4] ) = [3, 7, 11]
// ( [5,6] )
//// Returns the sum of all elements of `%a0` along rows while preserving dimensions
"FHELinalg.sum"(%a0) { axes = [1], keep_dims = true } : (tensor<3x2x!FHE.eint<4>>) -> tensor<3x1x!FHE.eint<4>>
//
// ( [1,2] ) [3]
// sum ( [3,4] ) = [7]
// ( [5,6] ) [11]
//// ok
"FHELinalg.to_signed"(%x) : (tensor<3x2x!FHE.eint<2>>) -> tensor<3x2x!FHE.esint<2>>
// error
"FHELinalg.to_signed"(%x) : (tensor<3x2x!FHE.eint<2>>) -> tensor<3x2x!FHE.esint<3>>// ok
"FHELinalg.to_unsigned"(%x) : (tensor<3x2x!FHE.esint<2>>) -> tensor<3x2x!FHE.eint<2>>
// error
"FHELinalg.to_unsigned"(%x) : (tensor<3x2x!FHE.esint<2>>) -> tensor<3x2x!FHE.eint<3>>"FHELinalg.transpose"(%a) : (tensor<n0xn1x...xnNxType>) -> tensor<nNx...xn1xn0xType>// Transpose the input tensor
// [1,2] [1, 3, 5]
// [3,4] => [2, 4, 6]
// [5,6]
//
"FHELinalg.transpose"(%a) : (tensor<3x2xi7>) -> tensor<2x3xi7>"FHELinalg.transpose"(%a) { axes = [1, 3, 0, 2] } : (tensor<2x3x4x5xi7>) -> tensor<3x5x2x4xi7>