diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 1d1ab75bd..4b6fe006b 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -19,7 +19,7 @@ * [Table Lookups](tutorial/table\_lookups.md) * [Rounded Table Lookups](tutorial/rounded\_table\_lookups.md) * [Floating Points](tutorial/floating\_points.md) -* [Virtual Circuits](tutorial/virtual\_circuits.md) +* [Simulation](tutorial/simulation.md) * [Direct Circuits](tutorial/direct\_circuits.md) * [Key Value Database](tutorial/key\_value\_database.md) diff --git a/docs/howto/configure.md b/docs/howto/configure.md index 2f3216f5f..286b5a4e2 100644 --- a/docs/howto/configure.md +++ b/docs/howto/configure.md @@ -94,9 +94,6 @@ Additional kwarg to `compile` function have higher precedence. So if you set an * **enable\_unsafe\_features**: bool = False * Whether to enable unsafe features. -* **virtual**: bool = False _(Unsafe)_ - * Whether to create a virtual circuit. - * **use\_insecure\_key\_cache**: bool = False _(Unsafe)_ * Whether to use the insecure key cache. diff --git a/docs/howto/debug.md b/docs/howto/debug.md index 800369ee0..7b0e866e1 100644 --- a/docs/howto/debug.md +++ b/docs/howto/debug.md @@ -96,7 +96,7 @@ This file contains information about the error you received. ``` Traceback (most recent call last): File "/home/default/Documents/Projects/Zama/hdk/concrete/numpy/compilation/compiler.py", line 320, in compile - mlir = GraphConverter.convert(self.graph, virtual=self.configuration.virtual) + mlir = GraphConverter.convert(self.graph) File "/home/default/Documents/Projects/Zama/hdk/concrete/numpy/mlir/graph_converter.py", line 298, in convert GraphConverter._check_graph_convertibility(graph) File "/home/default/Documents/Projects/Zama/hdk/concrete/numpy/mlir/graph_converter.py", line 175, in _check_graph_convertibility diff --git a/docs/tutorial/key_value_database.ipynb b/docs/tutorial/key_value_database.ipynb index 1f9f54871..d8e734396 100644 --- a/docs/tutorial/key_value_database.ipynb +++ b/docs/tutorial/key_value_database.ipynb @@ -582,7 +582,6 @@ " enable_unsafe_features=True,\n", " use_insecure_key_cache=True,\n", " insecure_key_cache_location=\".keys\",\n", - " # virtual=True,\n", " )\n", "\n", " # Create the compilers for the circuits\n", diff --git a/docs/tutorial/rounded_table_lookups.md b/docs/tutorial/rounded_table_lookups.md index 0fb662cae..c2f266840 100644 --- a/docs/tutorial/rounded_table_lookups.md +++ b/docs/tutorial/rounded_table_lookups.md @@ -1,7 +1,7 @@ # Rounded Table Lookups {% hint style="warning" %} -Rounded table lookups are only available in [virtual circuits](./virtual_circuits.md) for the time being. +Rounded table lookups are not compilable yet. API is stable and will not change so it's documented, but you might not be able to run the code samples in this document. {% endhint %} Table lookups have a strict constraint on number of bits they support. This can be quite limiting, especially if you don't need the exact precision. @@ -123,10 +123,10 @@ def f(x): return cnp.univariate(relu)(x) inputset = [-100_000, (100_000 - 1)] -circuit = f.compile(inputset, enable_unsafe_features=True, virtual=True) +circuit = f.compile(inputset) xs = range(-100_000, 100_000) -ys = [circuit.encrypt_run_decrypt(x) for x in xs] +ys = [circuit.simulate(x) for x in xs] plt.plot(xs, ys) plt.show() @@ -159,10 +159,10 @@ def f(x): inputset = [-100_000, (100_000 - 1)] cnp.AutoRounder.adjust(f, inputset) # alternatively, you can use `auto_adjust_rounders=True` below -circuit = f.compile(inputset, enable_unsafe_features=True, virtual=True) +circuit = f.compile(inputset) xs = range(-100_000, 100_000) -ys = [circuit.encrypt_run_decrypt(x) for x in xs] +ys = [circuit.simulate(x) for x in xs] plt.plot(xs, ys) plt.show() diff --git a/docs/tutorial/simulation.md b/docs/tutorial/simulation.md new file mode 100644 index 000000000..2558341b0 --- /dev/null +++ b/docs/tutorial/simulation.md @@ -0,0 +1,37 @@ +# Simulation + +During development, speed of homomorphic execution is a big blocker for fast prototyping. +You could call the function you're trying to compile directly of course, but it won't be exactly the same as FHE execution, which has a certain probability of error (see [Exactness](../getting-started/exactness.md)). + +Considering these, simulation is introduced: + +```python +import concrete.numpy as cnp +import numpy as np + +@cnp.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) + +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()) +``` + +prints + +``` +[1, 4, 9, 16, 25, 36, 49, 64, 81, 100] +[1, 4, 9, 16, 16, 36, 49, 64, 81, 100] +``` + +{% hint style="warning" %} +Currently, simulation is better than directly calling from Python, but it's not exactly the same with FHE execution. The reason is that it is implemented in Python. Imagine you have an identity table lookup, it might be ommitted from the generated FHE code by the compiler, but it'll be present in Python as optimizations are not done in Python. This will result in a bigger error in simulation. Furthermore, some operations have multiple table lookups within them, and those cannot be simulated unless the actual implementations of said operations are ported to Python. In the future, simulation functionality will be provided by the compiler so all of these issues would be addressed. Until then, keep these in mind. +{% endhint %} diff --git a/docs/tutorial/virtual_circuits.md b/docs/tutorial/virtual_circuits.md deleted file mode 100644 index b3b417ebf..000000000 --- a/docs/tutorial/virtual_circuits.md +++ /dev/null @@ -1,54 +0,0 @@ -# Virtual Circuits - -During development, speed of homomorphic execution is a big blocker for fast prototyping. Furthermore, it might be desirable to experiment with more bit-widths, even though they are not supported yet, to get insights about the requirements of your system (e.g., we would have an XYZ model with 95% accuracy if we have 25-bits). - -To simplify this process, we've introduces virtual circuits: - -```python -import concrete.numpy as cnp -import numpy as np - -@cnp.compiler({"x": "encrypted"}) -def f(x): - return np.sqrt(x * 100_000).round().astype(np.int64) - -inputset = range(100_000, 101_000) -circuit = f.compile(inputset, enable_unsafe_features=True, virtual=True) - -print(circuit) -print(circuit.encrypt_run_decrypt(100_500), "~=", np.sqrt(100_500 * 100_000)) -``` - -prints - -``` -%0 = x # EncryptedScalar ∈ [100000, 100999] -%1 = 100000 # ClearScalar ∈ [100000, 100000] -%2 = multiply(%0, %1) # EncryptedScalar ∈ [10000000000, 10099900000] -%3 = subgraph(%2) # EncryptedScalar ∈ [100000, 100498] -return %3 - -Subgraphs: - - %3 = subgraph(%2): - - %0 = input # EncryptedScalar - %1 = sqrt(%0) # EncryptedScalar - %2 = around(%1, decimals=0) # EncryptedScalar - %3 = astype(%2, dtype=int_) # EncryptedScalar - return %3 - -100250 ~= 100249.6882788171 -``` - -and it doesn't perform any homomorphic computation. It just simulates execution. - -Keyword arguments `enable_unsafe_features=True` and `virtual=True` passed to `compile` are configuration options. `virtaul=True` enables makes the circuit virtual, and because virtual circuits are highly experimental, unsafe features must be enabled using `enable_unsafe_features=True` to utilize virtual circuits. See [How to Configure](../howto/configure.md) to learn more about configuration options. - -{% hint style="info" %} -Virtual circuits still check for operational constraints and type constraints. Which means you cannot have floating points, or unsupported operations. They just ignore bit-width constraints. -{% endhint %} - -{% hint style="warning" %} -Virtual circuits are still experimental, and they don't properly consider [error probability](../getting-started/exactness.md) for example. That's why you need to enable unsafe features to use them. Use them with care! -{% endhint %}