Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(frontend): simplify the doc for the non-power user #864

Merged
merged 1 commit into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,9 @@
## Core features

* [Overview](core-features/fhe_basics.md)
* [Table lookups](core-features/table_lookups.md)
* [Table lookups (basics)](core-features/table_lookups.md)
* [Bit extraction](core-features/bit_extraction.md)
* [Rounding](core-features/rounding.md)
* [Truncating](core-features/truncating.md)
* [Floating points](core-features/floating_points.md)
* [Comparisons](core-features/comparisons.md)
* [Min/Max operations](core-features/minmax.md)
* [Bitwise operations](core-features/bitwise.md)
* [Non-linear operations](core-features/non_linear_operations.md)
* [Common tips](core-features/workarounds.md)
* [Extensions](core-features/extensions.md)
* [Tagging](core-features/tagging.md)
Expand Down Expand Up @@ -64,6 +59,14 @@
## Explanations

* [Compiler workflow](dev/compilation/compiler_workflow.md)
* [Compiler internals](dev/compilation/compiler_internals.md)
* [Table lookups](core-features/table_lookups_advanced.md)
* [Rounding](core-features/rounding.md)
* [Truncating](core-features/truncating.md)
* [Floating points](core-features/floating_points.md)
* [Comparisons](core-features/comparisons.md)
* [Min/Max operations](core-features/minmax.md)
* [Bitwise operations](core-features/bitwise.md)
* [Frontend fusing](explanations/fusing.md)
* [Compiler backend](explanations/backends/README.md)
* [Adding a new backend](explanations/backends/new_backend.md)
Expand Down
4 changes: 2 additions & 2 deletions docs/core-features/minmax.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ What this means is that if we are comparing `uint3` and `uint6`, we need to conv
(x - y).bit_width <= MAXIMUM_TLU_BIT_WIDTH
```

### 1. fhe.ComparisonStrategy.ONE_TLU_PROMOTED
### 1. fhe.MinMaxStrategy.ONE_TLU_PROMOTED

This strategy makes sure that during bit-width assignment, both operands are assigned the same bit-width, and that bit-width contains at least the amount of bits required to store `x - y`. The idea is:

Expand Down Expand Up @@ -226,7 +226,7 @@ module {
}
```

### 2. fhe.ComparisonStrategy.THREE_TLU_CASTED
### 2. fhe.MinMaxStrategy.THREE_TLU_CASTED

This strategy will not put any constraint on bit-widths during bit-width assignment. Instead, operands are cast to a bit-width that can store `x - y` during runtime using table lookups. The idea is:

Expand Down
165 changes: 165 additions & 0 deletions docs/core-features/non_linear_operations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# Non-linear operations
BourgerieQuentin marked this conversation as resolved.
Show resolved Hide resolved

In Concrete, there are basically two types of operations:
- linear operations, like additions, subtraction and multiplication by an integer, which are very fast
- and all the rest, which is done by a table lookup (TLU).

TLU are essential to be able to compile all functions, by keeping the semantic of user's program, but
they can be slower, depending on the bitwidth of the inputs of the TLU.

In this document, we explain briefly, from a user point of view, how it works for non-linear operations as comparisons, min/max, bitwise operations, shifts. In [the poweruser documentation](../dev/compilation/compiler_internals.md), we enter a bit more into the details.

## Changing bit width in the MLIR or dynamically with a TLU

Often, for binary operations, we need to have equivalent bit width for the two operands: it can be done in two ways. Either directly in the MLIR, or dynamically (i.e., at execution time) with a TLU. Because of these different methods, and the fact that none is stricly better than the other one in the general case, we offer different configurations for the non-linear functions.

The first method has the advantage to not require an expensive TLU. However, it may have impact in other parts of the program, since the operand of which we change the bit width may be used elsewhere in the program, so it may create more bit widths changes. Also, if ever the modified operands are used in TLUs, the impact may be significative.

The second method has the advantage to be very local: it has no impact elsewhere. However, it is costly, since it uses a TLU.

## Generic Principle for the user

In the following non-linear operations, we propose a certain number of configurations, using the two methods on the different operands. In general, it is not easy to know in advance which configuration will be the fastest one, but with some Concrete experience. We recommend the users to test and try what are the best configuration depending on their circuits.

By running the following programs with `show_mlir=True`, the advanced user may look the MLIR, and see the different uses of TLUs, bit width changes in the MLIR and dynamic change of the bit width. However, for the classical user, it is not critical to understand the different flavours. We would just recommend to try the different configurations and see which one fits the best for your case.

## Comparisons

For comparison, there are 7 available methods. The generic principle is

```python
import numpy as np
from concrete import fhe

configuration = fhe.Configuration(
comparison_strategy_preference=config,
)

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

where `config` is one of
- `fhe.ComparisonStrategy.CHUNKED`
- `fhe.ComparisonStrategy.ONE_TLU_PROMOTED`
- `fhe.ComparisonStrategy.THREE_TLU_CASTED`
- `fhe.ComparisonStrategy.TWO_TLU_BIGGER_PROMOTED_SMALLER_CASTED`
- `fhe.ComparisonStrategy.TWO_TLU_BIGGER_CASTED_SMALLER_PROMOTED`
- `fhe.ComparisonStrategy.THREE_TLU_BIGGER_CLIPPED_SMALLER_CASTED`
- `fhe.ComparisonStrategy.TWO_TLU_BIGGER_CLIPPED_SMALLER_PROMOTED`

## Min / Max operations

For min / max operations, there are 3 available methods. The generic principle is

```python
import numpy as np
from concrete import fhe

configuration = fhe.Configuration(
min_max_strategy_preference=config,
)

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

where `config` is one of
- `fhe.MinMaxStrategy.CHUNKED` (default)
- `fhe.MinMaxStrategy.ONE_TLU_PROMOTED`
- `fhe.MinMaxStrategy.THREE_TLU_CASTED`

## Bitwise operations

For bit wise operations (typically, AND, OR, XOR), there are 5 available methods. The generic principle is

```python
import numpy as np
from concrete import fhe

configuration = fhe.Configuration(
bitwise_strategy_preference=config,
)

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

where `config` is one of
- `fhe.BitwiseStrategy.CHUNKED`
- `fhe.BitwiseStrategy.ONE_TLU_PROMOTED`
- `fhe.BitwiseStrategy.THREE_TLU_CASTED`
- `fhe.BitwiseStrategy.TWO_TLU_BIGGER_PROMOTED_SMALLER_CASTED`
- `fhe.BitwiseStrategy.TWO_TLU_BIGGER_CASTED_SMALLER_PROMOTED`

## Shift operations

For shift operations, there are 2 available methods. The generic principle is

```python
import numpy as np
from concrete import fhe

configuration = fhe.Configuration(
shifts_with_promotion=shifts_with_promotion,
)

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

where `shifts_with_promotion` is either `True` or `False`.

## Relation with `fhe.multivariate`

Let us just remark that all binary operations described in this document can also be implemented with the `fhe.multivariate` function which is described in [this section](../core-features/extensions.md#fhe.multivariate-function).

```python
import numpy as np
from concrete import fhe


def f(x, y):
return fhe.multivariate(lambda x, y: x << y)(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, show_mlir=True)
```



Loading
Loading