-
Notifications
You must be signed in to change notification settings - Fork 159
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
308 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
# TFHE-rs Interoperability Example | ||
|
||
This is the full execution for the example explained in the [TFHE-rs Interoperability Guide](../../../../docs/guides/tfhers) (use case 1). You can find the TFHE-rs code [here](../../tests/tfhers-utils/src/main.rs), while the Python code is under this direcotry [here](example.py). Both are CLI tools, so that we can execute the example step by step. You can refer to the code at every step to see how it's implemented. | ||
|
||
## Make tmpdir | ||
|
||
We want to setup a temporary working directory first | ||
|
||
```sh | ||
export TDIR=`mktemp -d` | ||
``` | ||
|
||
## KeyGen | ||
|
||
First we need to build the TFHE-rs utility in [this directory](../../tests/tfhers-utils/) by running | ||
|
||
```sh | ||
cd ../../tests/tfhers-utils/ | ||
make build | ||
cd - | ||
``` | ||
|
||
Then we can generate keys in two different ways. You only need to run one of the following methods | ||
|
||
#### Generate the Secret Key in Concrete | ||
|
||
We start by doing keygen in Concrete | ||
|
||
```sh | ||
python example.py keygen -o $TDIR/concrete_sk -k $TDIR/concrete_keyset | ||
``` | ||
|
||
Then we do a partial keygen in TFHE-rs | ||
|
||
```sh | ||
../../tests/tfhers-utils/target/release/tfhers_utils keygen --lwe-sk $TDIR/concrete_sk --output-lwe-sk $TDIR/tfhers_sk -c $TDIR/tfhers_client_key -s $TDIR/tfhers_server_key | ||
``` | ||
|
||
#### Generate the Secret Key in TFHE-rs | ||
|
||
We start by doing keygen in TFHE-rs | ||
|
||
```sh | ||
../../tests/tfhers-utils/target/release/tfhers_utils keygen --output-lwe-sk $TDIR/tfhers_sk -c $TDIR/tfhers_client_key -s $TDIR/tfhers_server_key | ||
``` | ||
|
||
Then we do a partial keygen in Concrete. | ||
|
||
```sh | ||
python example.py keygen -s $TDIR/tfhers_sk -o $TDIR/concrete_sk -k $TDIR/concrete_keyset | ||
``` | ||
|
||
## Encrypt in TFHE-rs | ||
|
||
```sh | ||
../../tests/tfhers-utils/target/release/tfhers_utils encrypt-with-key --value 162 --ciphertext $TDIR/tfhers_ct_1 --client-key $TDIR/tfhers_client_key | ||
../../tests/tfhers-utils/target/release/tfhers_utils encrypt-with-key --value 73 --ciphertext $TDIR/tfhers_ct_2 --client-key $TDIR/tfhers_client_key | ||
``` | ||
|
||
{% hint style="info" %} | ||
|
||
If you have tensor inputs, then you can encrypt by passing your flat tensor in `--value`. Concrete will take care of reshaping the values to the corresponding shape. For example `--value=1,2,3,4` can represent a 2 by 2 tensor, or a flat vector of 4 values. | ||
|
||
{% endhint %} | ||
|
||
## Compute in TFHE-rs | ||
|
||
```sh | ||
# encrypt value to add first | ||
../../tests/tfhers-utils/target/release/tfhers_utils encrypt-with-key --value 9 --ciphertext $TDIR/tfhers_ct_inc --client-key $TDIR/tfhers_client_key | ||
# add two ciphertexts | ||
../../tests/tfhers-utils/target/release/tfhers_utils add --server-key $TDIR/tfhers_server_key --cts $TDIR/tfhers_ct_2 $TDIR/tfhers_ct_inc --output-ct $TDIR/tfhers_ct_2 | ||
``` | ||
|
||
## Run in Concrete | ||
|
||
```sh | ||
python example.py run -k $TDIR/concrete_keyset -c1 $TDIR/tfhers_ct_1 -c2 $TDIR/tfhers_ct_2 -o $TDIR/tfhers_ct_out | ||
``` | ||
|
||
## Decrypt in TFHE-rs | ||
|
||
```sh | ||
../../tests/tfhers-utils/target/release/tfhers_utils decrypt-with-key --ciphertext $TDIR/tfhers_ct_out --client-key $TDIR/tfhers_client_key | ||
``` | ||
|
||
## Clean tmpdir | ||
|
||
```sh | ||
rm -rf $TDIR | ||
``` |
197 changes: 197 additions & 0 deletions
197
frontends/concrete-python/examples/tfhers-ml/example.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
import os | ||
from functools import partial | ||
|
||
import click | ||
import numpy as np | ||
|
||
from concrete import fhe | ||
from concrete.fhe import tfhers | ||
|
||
### Options ########################### | ||
# These parameters were saved by running the tfhers_utils utility: | ||
# tfhers_utils save-params tfhers_params.json | ||
TFHERS_PARAMS_FILE = "tfhers_params.json" | ||
FHEUINT_PRECISION = 8 | ||
IS_SIGNED = True | ||
####################################### | ||
|
||
tfhers_type = tfhers.get_type_from_params( | ||
TFHERS_PARAMS_FILE, | ||
is_signed=IS_SIGNED, | ||
precision=FHEUINT_PRECISION, | ||
) | ||
tfhers_int = partial(tfhers.TFHERSInteger, tfhers_type) | ||
|
||
|
||
q_weights = np.array( | ||
[ | ||
[-81], | ||
[-95], | ||
[-51], | ||
[-77], | ||
[-64], | ||
[-64], | ||
[-128], | ||
[127], | ||
[-122], | ||
[-81], | ||
[-96], | ||
[-93], | ||
[-63], | ||
[-50], | ||
[-104], | ||
[-99], | ||
[-112], | ||
[-46], | ||
[-106], | ||
[-42], | ||
[-56], | ||
[-46], | ||
[-67], | ||
[-116], | ||
[-107], | ||
[-75], | ||
[-105], | ||
[-109], | ||
[-88], | ||
[-80], | ||
] | ||
) | ||
q_weights = np.ones_like(q_weights) | ||
q_bias = np.array([[680]]) | ||
weight_quantizer_zero_point = -74 | ||
|
||
|
||
def ml_inference(q_X: np.ndarray) -> np.ndarray: | ||
# Quantizing weights and inputs makes an additional term appear in the inference function | ||
y_pred = q_X @ q_weights - weight_quantizer_zero_point * np.sum(q_X, axis=1, keepdims=True) | ||
y_pred += q_bias | ||
return y_pred | ||
|
||
|
||
def compute(tfhers_x): | ||
####### TFHE-rs to Concrete ######### | ||
|
||
# x and y are supposed to be TFHE-rs values. | ||
# to_native will use type information from x and y to do | ||
# a correct conversion from TFHE-rs to Concrete | ||
concrete_x = tfhers.to_native(tfhers_x) | ||
####### TFHE-rs to Concrete ######### | ||
|
||
####### Concrete Computation ######## | ||
concrete_res = ml_inference(concrete_x) | ||
####### Concrete Computation ######## | ||
|
||
####### Concrete to TFHE-rs ######### | ||
tfhers_res = tfhers.from_native( | ||
concrete_res, tfhers_type | ||
) # we have to specify the type we want to convert to | ||
####### Concrete to TFHE-rs ######### | ||
return tfhers_res | ||
|
||
|
||
def ccompilee(): | ||
compiler = fhe.Compiler( | ||
compute, | ||
{ | ||
"tfhers_x": "encrypted", | ||
}, | ||
) | ||
|
||
inputset = [ | ||
( | ||
tfhers_int( | ||
np.array( | ||
[ | ||
1, | ||
] | ||
* 30 | ||
).reshape((1, 30)) | ||
), | ||
) | ||
] | ||
circuit = compiler.compile(inputset, show_graph=True, show_mlir=True) | ||
|
||
tfhers_bridge = tfhers.new_bridge(circuit=circuit) | ||
return circuit, tfhers_bridge | ||
|
||
|
||
@click.group() | ||
def cli(): | ||
pass | ||
|
||
|
||
@cli.command() | ||
@click.option("-s", "--secret-key", type=str, required=False) | ||
@click.option("-o", "--output-secret-key", type=str, required=True) | ||
@click.option("-k", "--concrete-keyset-path", type=str, required=True) | ||
def keygen(output_secret_key: str, secret_key: str, concrete_keyset_path: str): | ||
"""Concrete Key Generation""" | ||
|
||
circuit, tfhers_bridge = ccompilee() | ||
|
||
if os.path.exists(concrete_keyset_path): | ||
print(f"removing old keyset at '{concrete_keyset_path}'") | ||
os.remove(concrete_keyset_path) | ||
|
||
if secret_key: | ||
print(f"partial keygen from sk at '{secret_key}'") | ||
# load the initial secret key to use for keygen | ||
with open( | ||
secret_key, | ||
"rb", | ||
) as f: | ||
buff = f.read() | ||
input_idx_to_key = {0: buff} | ||
tfhers_bridge.keygen_with_initial_keys(input_idx_to_key_buffer=input_idx_to_key) | ||
else: | ||
print("full keygen") | ||
circuit.keygen() | ||
|
||
print("saving Concrete keyset") | ||
circuit.client.keys.save(concrete_keyset_path) | ||
print(f"saved Concrete keyset to '{concrete_keyset_path}'") | ||
|
||
sk: bytes = tfhers_bridge.serialize_input_secret_key(input_idx=0) | ||
print(f"writing secret key of size {len(sk)} to '{output_secret_key}'") | ||
with open(output_secret_key, "wb") as f: | ||
f.write(sk) | ||
|
||
|
||
@cli.command() | ||
@click.option("-c1", "--rust-ct-1", type=str, required=True) | ||
@click.option("-o", "--output-rust-ct", type=str, required=False) | ||
@click.option("-k", "--concrete-keyset-path", type=str, required=True) | ||
def run(rust_ct_1: str, output_rust_ct: str, concrete_keyset_path: str): | ||
"""Run circuit""" | ||
circuit, tfhers_bridge = ccompilee() | ||
|
||
if not os.path.exists(concrete_keyset_path): | ||
raise RuntimeError("cannot find keys, you should run keygen before") | ||
print(f"loading keys from '{concrete_keyset_path}'") | ||
circuit.client.keys.load(concrete_keyset_path) | ||
|
||
# read tfhers int from file | ||
with open(rust_ct_1, "rb") as f: | ||
buff = f.read() | ||
# import fheuint8 and get its description | ||
tfhers_uint8_x = tfhers_bridge.import_value(buff, input_idx=0) | ||
|
||
print("Homomorphic evaluation...") | ||
encrypted_result = circuit.run(tfhers_uint8_x) | ||
|
||
if output_rust_ct: | ||
print("exporting Rust ciphertexts") | ||
# export fheuint8 | ||
buff = tfhers_bridge.export_value(encrypted_result, output_idx=0) | ||
# write it to file | ||
with open(output_rust_ct, "wb") as f: | ||
f.write(buff) | ||
else: | ||
result = circuit.decrypt(encrypted_result) | ||
decoded = tfhers_type.decode(result) | ||
print(f"Concrete decryption result: raw({result}), decoded({decoded})") | ||
|
||
|
||
if __name__ == "__main__": | ||
cli() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
#!/bin/sh | ||
|
||
# This file tests that the example is working | ||
|
||
shell_blocks=$(sed -n '/^```sh/,/^```/ p' < README.md | sed '/^```sh/d' | sed '/^```/d') | ||
|
||
set -e | ||
output=$(eval "$shell_blocks" 2>&1) || echo "$output" | ||
|
||
result=$(echo "$output" | grep "result: " | sed 's/result: //g') | ||
|
||
expected="31" | ||
if [ $result -eq $expected ] | ||
then | ||
exit 0 | ||
else | ||
echo "expected result to be $expected, but result was $result" | ||
exit 1 | ||
fi |
1 change: 1 addition & 0 deletions
1
frontends/concrete-python/examples/tfhers-ml/tfhers_params.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"lwe_dimension":902,"glwe_dimension":1,"polynomial_size":4096,"lwe_noise_distribution":{"Gaussian":{"std":1.0994794733558207e-6,"mean":0.0}},"glwe_noise_distribution":{"Gaussian":{"std":2.168404344971009e-19,"mean":0.0}},"pbs_base_log":15,"pbs_level":2,"ks_base_log":3,"ks_level":6,"message_modulus":4,"carry_modulus":8,"max_noise_level":10,"log2_p_fail":-64.084,"ciphertext_modulus":{"modulus":0,"scalar_bits":64},"encryption_key_choice":"Big"} |