mirror of
https://github.com/zama-ai/concrete.git
synced 2026-04-17 03:00:54 -04:00
docs: start updating docs to reflect the rewrite
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
Everything you need to compile and execute homomorphic functions is included in a single module. You can import it like so:
|
||||
|
||||
```python
|
||||
import concrete.numpy as hnp
|
||||
import concrete.numpy as cnp
|
||||
```
|
||||
|
||||
## Defining a function to compile
|
||||
@@ -41,22 +41,11 @@ Finally, we can compile our function to its homomorphic equivalent.
|
||||
|
||||
<!--pytest-codeblocks:cont-->
|
||||
```python
|
||||
compiler = hnp.NPFHECompiler(
|
||||
f, {"x": x, "y": y},
|
||||
)
|
||||
circuit = compiler.compile_on_inputset(inputset)
|
||||
compiler = cnp.Compiler(f, {"x": x, "y": y})
|
||||
circuit = compiler.compile(inputset)
|
||||
|
||||
# If you want, you can separate tracing and compilation steps like so:
|
||||
|
||||
# You can either evaluate in one go:
|
||||
compiler.eval_on_inputset(inputset)
|
||||
|
||||
# Or progressively:
|
||||
for input_values in inputset:
|
||||
compiler(*input_values)
|
||||
|
||||
# You can print the traced graph:
|
||||
print(str(compiler))
|
||||
# You can print the compiled circuit:
|
||||
print(circuit)
|
||||
|
||||
# Outputs
|
||||
|
||||
@@ -66,29 +55,26 @@ print(str(compiler))
|
||||
# return %2
|
||||
|
||||
# Or draw it
|
||||
compiler.draw_graph(show=True)
|
||||
|
||||
circuit = compiler.get_compiled_fhe_circuit()
|
||||
|
||||
circuit.draw(show=True)
|
||||
```
|
||||
|
||||
Here is the graph from the previous code block drawn with `draw_graph`:
|
||||
Here is the graph from the previous code block drawn with `draw`:
|
||||
|
||||

|
||||
|
||||
## Performing homomorphic evaluation
|
||||
|
||||
You can use `.encrypt_run_decrypt(...)` method of `FHECircuit` returned by `hnp.compile_numpy_function(...)` to perform fully homomorphic evaluation. Here are some examples:
|
||||
You can use `.run(...)` method of `Circuit` to perform fully homomorphic evaluation. Here are some examples:
|
||||
|
||||
<!--pytest-codeblocks:cont-->
|
||||
```python
|
||||
circuit.encrypt_run_decrypt(3, 4)
|
||||
circuit.run(3, 4)
|
||||
# 7
|
||||
circuit.encrypt_run_decrypt(1, 2)
|
||||
circuit.run(1, 2)
|
||||
# 3
|
||||
circuit.encrypt_run_decrypt(7, 7)
|
||||
circuit.run(7, 7)
|
||||
# 14
|
||||
circuit.encrypt_run_decrypt(0, 0)
|
||||
circuit.run(0, 0)
|
||||
# 0
|
||||
```
|
||||
|
||||
@@ -97,20 +83,11 @@ Be careful about the inputs, though.
|
||||
If you were to run with values outside the range of the inputset, the result might not be correct.
|
||||
```
|
||||
|
||||
While `.encrypt_run_decrypt(...)` is a good start for prototyping examples, more advanced usages require control over the different steps that are happening behind the scene, mainly key generation, encryption, execution, and decryption. The different steps can of course be called separately as in the example below:
|
||||
|
||||
<!--pytest-codeblocks:cont-->
|
||||
```python
|
||||
# generate keys required for encrypted computation
|
||||
circuit.keygen()
|
||||
# this will encrypt arguments that require encryption and pack all arguments
|
||||
# as well as public materials (public keys)
|
||||
public_args = circuit.encrypt(3, 4)
|
||||
# this will run the encrypted computation using public materials and inputs provided
|
||||
encrypted_result = circuit.run(public_args)
|
||||
# the execution returns the encrypted result which can later be decrypted
|
||||
decrypted_result = circuit.decrypt(encrypted_result)
|
||||
```
|
||||
Today, we cannot simulate a client / server API in python, but it is for very soon. Then, we will have:
|
||||
- a `keygen` API, which is used to generate both public and private keys
|
||||
- an `encrypt` API, which happens on the user's device, and is using private keys
|
||||
- a `run_inference` API, which happens on the untrusted server and only uses public material
|
||||
- a `encrypt` API, which happens on the user's device to get final clear result, and is using private keys
|
||||
|
||||
## Further reading
|
||||
|
||||
|
||||
@@ -2,64 +2,38 @@
|
||||
|
||||
In this section, we list the operations which are supported currently in **Concrete Numpy**. Please have a look to numpy [documentation](https://numpy.org/doc/stable/user/index.html) to know what these operations are about.
|
||||
|
||||
## Unary operations
|
||||
|
||||
<!--- gen_supported_ufuncs.py: inject supported operations [BEGIN] -->
|
||||
<!--- do not edit, auto generated part by `python3 gen_supported_ufuncs.py` in docker -->
|
||||
List of supported unary functions:
|
||||
List of supported functions:
|
||||
- absolute
|
||||
- add
|
||||
- arccos
|
||||
- arccosh
|
||||
- arcsin
|
||||
- arcsinh
|
||||
- arctan
|
||||
- arctan2
|
||||
- arctanh
|
||||
- bitwise_and
|
||||
- bitwise_or
|
||||
- bitwise_xor
|
||||
- cbrt
|
||||
- ceil
|
||||
- clip
|
||||
- concatenate
|
||||
- copysign
|
||||
- cos
|
||||
- cosh
|
||||
- deg2rad
|
||||
- degrees
|
||||
- dot
|
||||
- equal
|
||||
- exp
|
||||
- exp2
|
||||
- expm1
|
||||
- fabs
|
||||
- floor
|
||||
- isfinite
|
||||
- isinf
|
||||
- isnan
|
||||
- log
|
||||
- log10
|
||||
- log1p
|
||||
- log2
|
||||
- logical_not
|
||||
- negative
|
||||
- positive
|
||||
- rad2deg
|
||||
- radians
|
||||
- reciprocal
|
||||
- rint
|
||||
- sign
|
||||
- signbit
|
||||
- sin
|
||||
- sinh
|
||||
- spacing
|
||||
- sqrt
|
||||
- square
|
||||
- tan
|
||||
- tanh
|
||||
- trunc
|
||||
|
||||
## Binary operations
|
||||
|
||||
List of supported binary functions if one of the two operators is a constant scalar:
|
||||
- arctan2
|
||||
- bitwise_and
|
||||
- bitwise_or
|
||||
- bitwise_xor
|
||||
- copysign
|
||||
- equal
|
||||
- float_power
|
||||
- floor
|
||||
- floor_divide
|
||||
- fmax
|
||||
- fmin
|
||||
@@ -69,24 +43,54 @@ List of supported binary functions if one of the two operators is a constant sca
|
||||
- greater_equal
|
||||
- heaviside
|
||||
- hypot
|
||||
- invert
|
||||
- isfinite
|
||||
- isinf
|
||||
- isnan
|
||||
- lcm
|
||||
- ldexp
|
||||
- left_shift
|
||||
- less
|
||||
- less_equal
|
||||
- log
|
||||
- log10
|
||||
- log1p
|
||||
- log2
|
||||
- logaddexp
|
||||
- logaddexp2
|
||||
- logical_and
|
||||
- logical_not
|
||||
- logical_or
|
||||
- logical_xor
|
||||
- matmul
|
||||
- maximum
|
||||
- minimum
|
||||
- multiply
|
||||
- negative
|
||||
- nextafter
|
||||
- not_equal
|
||||
- positive
|
||||
- power
|
||||
- rad2deg
|
||||
- radians
|
||||
- reciprocal
|
||||
- remainder
|
||||
- reshape
|
||||
- right_shift
|
||||
- rint
|
||||
- sign
|
||||
- signbit
|
||||
- sin
|
||||
- sinh
|
||||
- spacing
|
||||
- sqrt
|
||||
- square
|
||||
- subtract
|
||||
- sum
|
||||
- tan
|
||||
- tanh
|
||||
- true_divide
|
||||
- trunc
|
||||
<!--- gen_supported_ufuncs.py: inject supported operations [END] -->
|
||||
|
||||
# Shapes
|
||||
|
||||
@@ -9,13 +9,13 @@ You get a compilation error. Here is an example:
|
||||
|
||||
<!--pytest-codeblocks:skip-->
|
||||
```python
|
||||
import concrete.numpy as hnp
|
||||
import concrete.numpy as cnp
|
||||
|
||||
@cnp.compiler({"x": "encrypted"})
|
||||
def f(x):
|
||||
return 42 * x
|
||||
|
||||
compiler = hnp.NPFHECompiler(f, {"x": "encrypted"})
|
||||
circuit = compiler.compile_on_inputset(range(2 ** 3))
|
||||
circuit = f.compile(range(2 ** 3))
|
||||
```
|
||||
|
||||
results in
|
||||
|
||||
@@ -115,17 +115,17 @@ return %1
|
||||
Manual exports are mostly used for visualization. Nonetheless, they can be very useful for demonstrations. Here is how to do it:
|
||||
|
||||
```python
|
||||
import concrete.numpy as hnp
|
||||
import concrete.numpy as cnp
|
||||
import numpy as np
|
||||
import pathlib
|
||||
|
||||
artifacts = cnp.CompilationArtifacts("/tmp/custom/export/path")
|
||||
|
||||
@cnp.compiler({"x": "encrypted"}, artifacts=artifacts)
|
||||
def f(x):
|
||||
return 127 - (50 * (np.sin(x) + 1)).astype(np.uint32)
|
||||
|
||||
artifacts = hnp.CompilationArtifacts(pathlib.Path("/tmp/custom/export/path"))
|
||||
|
||||
compiler = hnp.NPFHECompiler(f, {"x": "encrypted"}, compilation_artifacts=artifacts)
|
||||
compiler.compile_on_inputset(range(2 ** 3))
|
||||
f.compile(range(2 ** 3))
|
||||
|
||||
artifacts.export()
|
||||
```
|
||||
|
||||
@@ -9,16 +9,15 @@ Here are some examples of constant indexing:
|
||||
### Extracting a single element
|
||||
|
||||
```python
|
||||
import concrete.numpy as hnp
|
||||
import concrete.numpy as cnp
|
||||
import numpy as np
|
||||
|
||||
@cnp.compiler({"x": "encrypted"})
|
||||
def f(x):
|
||||
return x[1]
|
||||
|
||||
inputset = [np.random.randint(0, 2 ** 3, size=(3,), dtype=np.uint8) for _ in range(10)]
|
||||
|
||||
compiler = hnp.NPFHECompiler(f, {"x": "encrypted"})
|
||||
circuit = compiler.compile_on_inputset(inputset)
|
||||
circuit = f.compile(inputset)
|
||||
|
||||
test_input = np.array([4, 2, 6], dtype=np.uint8)
|
||||
expected_output = 2
|
||||
@@ -29,16 +28,15 @@ assert np.array_equal(circuit.encrypt_run_decrypt(test_input), expected_output)
|
||||
You can use negative indexing.
|
||||
|
||||
```python
|
||||
import concrete.numpy as hnp
|
||||
import concrete.numpy as cnp
|
||||
import numpy as np
|
||||
|
||||
@cnp.compiler({"x": "encrypted"})
|
||||
def f(x):
|
||||
return x[-1]
|
||||
|
||||
inputset = [np.random.randint(0, 2 ** 3, size=(3,), dtype=np.uint8) for _ in range(10)]
|
||||
|
||||
compiler = hnp.NPFHECompiler(f, {"x": "encrypted"})
|
||||
circuit = compiler.compile_on_inputset(inputset)
|
||||
circuit = f.compile(inputset)
|
||||
|
||||
test_input = np.array([4, 2, 6], dtype=np.uint8)
|
||||
expected_output = 6
|
||||
@@ -49,16 +47,15 @@ assert np.array_equal(circuit.encrypt_run_decrypt(test_input), expected_output)
|
||||
You can use multidimensional indexing as well.
|
||||
|
||||
```python
|
||||
import concrete.numpy as hnp
|
||||
import concrete.numpy as cnp
|
||||
import numpy as np
|
||||
|
||||
@cnp.compiler({"x": "encrypted"})
|
||||
def f(x):
|
||||
return x[-1, 1]
|
||||
|
||||
inputset = [np.random.randint(0, 2 ** 3, size=(3, 2), dtype=np.uint8) for _ in range(10)]
|
||||
|
||||
compiler = hnp.NPFHECompiler(f, {"x": "encrypted"})
|
||||
circuit = compiler.compile_on_inputset(inputset)
|
||||
circuit = f.compile(inputset)
|
||||
|
||||
test_input = np.array([[4, 2], [1, 5], [7, 6]], dtype=np.uint8)
|
||||
expected_output = 6
|
||||
@@ -69,16 +66,15 @@ assert np.array_equal(circuit.encrypt_run_decrypt(test_input), expected_output)
|
||||
### Extracting a slice
|
||||
|
||||
```python
|
||||
import concrete.numpy as hnp
|
||||
import concrete.numpy as cnp
|
||||
import numpy as np
|
||||
|
||||
@cnp.compiler({"x": "encrypted"})
|
||||
def f(x):
|
||||
return x[1:4]
|
||||
|
||||
inputset = [np.random.randint(0, 2 ** 3, size=(5,), dtype=np.uint8) for _ in range(10)]
|
||||
|
||||
compiler = hnp.NPFHECompiler(f, {"x": "encrypted"})
|
||||
circuit = compiler.compile_on_inputset(inputset)
|
||||
circuit = f.compile(inputset)
|
||||
|
||||
test_input = np.array([4, 2, 6, 1, 7], dtype=np.uint8)
|
||||
expected_output = np.array([2, 6, 1], dtype=np.uint8)
|
||||
|
||||
@@ -7,9 +7,9 @@ In this tutorial, we are going to go over the ways to perform direct table looku
|
||||
**Concrete Numpy** provides a special class to allow direct table lookups. Here is how to use it:
|
||||
|
||||
```python
|
||||
import concrete.numpy as hnp
|
||||
import concrete.numpy as cnp
|
||||
|
||||
table = hnp.LookupTable([2, 1, 3, 0])
|
||||
table = cnp.LookupTable([2, 1, 3, 0])
|
||||
|
||||
def f(x):
|
||||
return table[x]
|
||||
@@ -47,12 +47,12 @@ Sometimes you may want to apply a different lookup table to each value in a tens
|
||||
|
||||
<!--pytest-codeblocks:skip-->
|
||||
```python
|
||||
import concrete.numpy as hnp
|
||||
import concrete.numpy as cnp
|
||||
|
||||
squared = hnp.LookupTable([i ** 2 for i in range(4)])
|
||||
cubed = hnp.LookupTable([i ** 3 for i in range(4)])
|
||||
squared = cnp.LookupTable([i ** 2 for i in range(4)])
|
||||
cubed = cnp.LookupTable([i ** 3 for i in range(4)])
|
||||
|
||||
table = hnp.MultiLookupTable([
|
||||
table = cnp.MultiLookupTable([
|
||||
[squared, cubed],
|
||||
[squared, cubed],
|
||||
[squared, cubed],
|
||||
@@ -118,7 +118,7 @@ Internally, it uses the following lookup table
|
||||
|
||||
<!--pytest-codeblocks:skip-->
|
||||
```python
|
||||
table = hnp.LookupTable([50, 92, 95, 57, 12, 2, 36, 82])
|
||||
table = cnp.LookupTable([50, 92, 95, 57, 12, 2, 36, 82])
|
||||
```
|
||||
|
||||
which is calculated by:
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import concrete.numpy as hnp\n",
|
||||
"import concrete.numpy as cnp\n",
|
||||
"import inspect\n",
|
||||
"import numpy as np"
|
||||
]
|
||||
@@ -535,8 +535,8 @@
|
||||
],
|
||||
"source": [
|
||||
"for operation in supported_operations:\n",
|
||||
" compiler = hnp.NPFHECompiler(operation, {\"x\": \"encrypted\"})\n",
|
||||
" circuit = compiler.compile_on_inputset(inputset)\n",
|
||||
" compiler = cnp.Compiler(operation, {\"x\": \"encrypted\"})\n",
|
||||
" circuit = compiler.compile(inputset)\n",
|
||||
" \n",
|
||||
" # We setup an example tensor that will be encrypted and passed on to the current operation\n",
|
||||
" sample = np.random.randint(3, 11, size=(3, 2), dtype=np.uint8)\n",
|
||||
|
||||
@@ -3,17 +3,16 @@
|
||||
## An example
|
||||
|
||||
```python
|
||||
import concrete.numpy as cnp
|
||||
import numpy as np
|
||||
import concrete.numpy as hnp
|
||||
|
||||
# Function using floating points values converted back to integers at the end
|
||||
@cnp.compiler({"x": "encrypted"})
|
||||
def f(x):
|
||||
return np.fabs(50 * (2 * np.sin(x) * np.cos(x))).astype(np.uint32)
|
||||
# astype is to go back to the integer world
|
||||
|
||||
# Compiling with x encrypted
|
||||
compiler = hnp.NPFHECompiler(f, {"x": "encrypted"})
|
||||
circuit = compiler.compile_on_inputset(range(64))
|
||||
circuit = f.compile(range(64))
|
||||
|
||||
print(circuit.encrypt_run_decrypt(3) == f(3))
|
||||
print(circuit.encrypt_run_decrypt(0) == f(0))
|
||||
|
||||
Reference in New Issue
Block a user