Concrete v2.4.0: Multi-Parameter Optimization and More Accurate Bitwidth

October 17, 2023
Quentin Bourgerie

Since the release of Concrete v2.0.0 in July 2023, we have released 4 minor versions which come with important performance improvements, some new features and some bug fixes.

Better performance

Multi-parameter optimization

One of the major improvements on performance since Concrete v2.0.0 is the introduction of the multi-parameter optimization: as a reminder, this optimization enables the FHE evaluation to use several key-switching and bootstrapping keys. This allows the use of more efficient parameters on specific partitions of the circuit. Depending on the use cases this optimization can dramatically improve the latency of the FHE evaluation. This optimization was not activated by default in Concrete v2.0.0; since v2.1.0 the multi-parameter is ready and is used by default.

More accurate bitwidth for some operators

The partitioning of the Concrete Optimizer essentially uses the bitwidth information to create partitions. In order to achieve better performances, we have redesigned some operators to take advantage of the multi-parameter optimization by giving more accurate bitwith information than before. In one word, multiplication, matrix multiplication, dot product, bitwise, shifts and comparison operators will run faster with v2.4.0 than with the v2.0.0.

Other improvements

There are a few other minor improvements which are added to the optimization to speedup some particular cases; you can find the whole list in our Github release note or just try Concrete v2.4.0 and see your program run faster. For example, we were able to run CIFAR benchmark in less than 4 minutes per inference, on m6i metal, which is about 5 times better than previous quarter. 

Statistics

One of the key points that allows developers to improve their programs is to give them a deep understanding of the FHE programs they implement. While it is pretty straightforward with a program written in clear, it is more challenging when the program is turned into its homomorphic equivalent. Since the v2.1.0 release, some statistics about the compiled program are now returned by the compiler. This should help the developer to better understand the complexity of the FHE evaluation, and make appropriate improvements. Those statistics are the nature and the number of the cryptographic operations with their theoretical costs. Statistics are reported for the whole program but also by locations and tags that should help the developer to focus on the most costly part of its program.

from concrete import fhe

@fhe.compiler({"x": "encrypted"})
def f(x):
    return (x**2) - (3*x) + 4

inputset = range(2**2)
circuit = f.compile(inputset, show_statistics=True)

assert circuit.programmable_bootstrap_count == 1  # x**2
assert circuit.clear_multiplication_count == 1  # 3*x
assert circuit.encrypted_negation_count == 1  # -(3*x)
assert circuit.encrypted_addition_count == 1  # (x**2) + (-(3*x))
assert circuit.clear_addition_count == 1  # ((x**2) - (3*x)) + 4

Table lookup with dynamic tables

For a long time, it was possible to compute [.c-inline-code]y = T[x][.c-inline-code] with an encrypted x and a table T which was known at compilation time. That’s basically what a programmable bootstrapping is. We have added a functionality, to be able to compute [.c-inline-code]y = T[x][.c-inline-code] still with an encrypted x but with a table T which is dynamically computed at execution time.

import numpy as np
from concrete import fhe

def f(value, table):
    return table[value]

inputset = [
    (
        np.random.randint(0, 2**2, size=()),
        np.random.randint(0, 2**5, size=(2**2,)),
    )
    for _ in range(100)
]

compiler = fhe.Compiler(f, {"value": "encrypted", "table": "clear"})
circuit = compiler.compile(inputset, show_mlir=True)

assert circuit.encrypt_run_decrypt(2, [20, 4, 9, 18]) == 9

Additional links

Read more related posts