docs: start updating docs to reflect the rewrite

This commit is contained in:
Umut
2022-04-04 13:36:19 +02:00
parent f17745130e
commit 79685ed7dc
14 changed files with 137 additions and 219 deletions

View File

@@ -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`:
![Drawn graph of previous code block](../../_static/howto/compiling_and_executing_example_graph.png)
## 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

View File

@@ -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

View File

@@ -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

View File

@@ -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()
```

View File

@@ -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)

View File

@@ -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:

View File

@@ -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",

View File

@@ -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))