Unfolding concrete implementations

programmable_bootstrap_count_per_parameter",
programmable_bootstrap_count_per_tag",
programmable_bootstrap_count_per_tag_per_parameter"

count_per_parameter",bootstrap_count_per_tag",count_per_tag_per_parameter",I am not sure If i understand these ,it has been used in several context even for other operations ,If someone could elaborate on this?

Hello @Laser_beam,

Let’s go over them one by one!

programmable_bootstrap_count_per_parameter

Imagine having a circuit with a 4-bit table lookup and a 2-bit table lookup. These table lookups will use different bootstrapping parameters by default to be as fast as possible. programmable_bootstrap_count_per_parameter statistic count the number of table lookups per bootstrapping parameter in the circuit:

import numpy as np
from concrete import fhe

inputset = [
    (
        np.random.randint(0, 2**4, size=()),  # x is 4 bits
        np.random.randint(0, 2**2, size=()),  # y is 2 bits
    )
    for _ in range(100)
]

def f(x, y):
    a = x ** 2  # 4-bit tlu
    b = y ** 2  # 2-bit tlu
    c = y // 2  # 2-bit tlu
    return a + b + c

compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset)

print(circuit.programmable_bootstrap_count_per_parameter)

prints

{
    BootstrapKeyParam(polynomial_size=1024, glwe_dimension=2, input_lwe_dimension=813, level=1, base_log=23, variance=9.940977002694397e-32): 1, 
    BootstrapKeyParam(polynomial_size=512, glwe_dimension=4, input_lwe_dimension=671, level=1, base_log=23, variance=9.940977002694397e-32): 2
}

which means there will be:

  • 1 TLU which use BootstrapKeyParam(polynomial_size=1024, glwe_dimension=2, input_lwe_dimension=813, level=1, base_log=23, variance=9.940977002694397e-32)
  • 2 TLUs which use BootstrapKeyParam(polynomial_size=512, glwe_dimension=4, input_lwe_dimension=671, level=1, base_log=23, variance=9.940977002694397e-32)

programmable_bootstrap_count_per_tag

This statistic requires usage of tags (see Tagging). It’ll count the number of TLUs per tag:

import numpy as np
from concrete import fhe

inputset = [
    (
        np.random.randint(0, 2**4, size=()),  # x is 4 bits
        np.random.randint(0, 2**2, size=()),  # y is 2 bits
    )
    for _ in range(100)
]

def f(x, y):
    with fhe.tag("processing_x"):
        a = x ** 2  # 4-bit tlu
    with fhe.tag("processing_y"):
        b = y ** 2  # 2-bit tlu
        c = y // 2  # 2-bit tlu
    return a + b + c

compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset)

print(circuit.programmable_bootstrap_count_per_tag)

prints

{'processing_x': 1, 'processing_y': 2}

which means there will be:

  • 1 TLU in processing_x region of the code
  • 2 TLUs in processing_y region of the code

programmable_bootstrap_count_per_tag_per_parameter

And this one will first group by tag and then by parameter:

import numpy as np
from concrete import fhe

inputset = [
    (
        np.random.randint(0, 2**4, size=()),  # x is 4 bits
        np.random.randint(0, 2**2, size=()),  # y is 2 bits
    )
    for _ in range(100)
]

def f(x, y):
    with fhe.tag("squaring"):
        a = x ** 2  # 4-bit tlu
        b = y ** 2  # 2-bit tlu
    with fhe.tag("dividing"):
        c = y // 2  # 2-bit tlu
    return a + b + c

compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset)

print(circuit.programmable_bootstrap_count_per_tag_per_parameter)

prints

{
    'squaring': {
        BootstrapKeyParam(polynomial_size=1024, glwe_dimension=2, input_lwe_dimension=813, level=1, base_log=23, variance=9.940977002694397e-32): 1,
        BootstrapKeyParam(polynomial_size=512, glwe_dimension=4, input_lwe_dimension=671, level=1, base_log=23, variance=9.940977002694397e-32): 1
    },
    'dividing': {
        BootstrapKeyParam(polynomial_size=512, glwe_dimension=4, input_lwe_dimension=671, level=1, base_log=23, variance=9.940977002694397e-32): 1
    }
}

which means there will be:

  • 1 TLU in squaring region of the code which use BootstrapKeyParam(polynomial_size=1024, glwe_dimension=2, input_lwe_dimension=813, level=1, base_log=23, variance=9.940977002694397e-32)
  • 1 TLU in squaring region of the code which use BootstrapKeyParam(polynomial_size=512, glwe_dimension=4, input_lwe_dimension=671, level=1, base_log=23, variance=9.940977002694397e-32)
  • 1 TLU in dividing region of the code which use BootstrapKeyParam(polynomial_size=512, glwe_dimension=4, input_lwe_dimension=671, level=1, base_log=23, variance=9.940977002694397e-32)

Let us know if you have more questions!

1 Like

Wow you described it nicely ,thanks@umutsahin
In this link I am not able to understand these properly ONE_TLU_PROMOTED, THREE_TLU_CASTED , TWO_TLU_BIGGER_CASTED_SMALLER_PROMOTED …and till last WO_TLU_BIGGER_CLIPPED_SMALLER_PROMOTED
Please help me in understanding both in context of comparsion startegy and bitwise strategy

Also the three ways of parameterselection strategy ,for a circuit you already demonstarted takes diffetent keygen timings,any relation with the concrete optimizer?

They are extensively documented in Comparisons and Bitwise Operations respectively. Did you read those?

Oh I missed them ,thanks for pointing out

My pleasure :slight_smile:

Let me know if anything is not clear after reading those and I’ll try my best to explain!

1 Like

I had one more doubt ,Suppose If we use these different parameter selection strategy ,what are the varying cipher text sizes(using V0 MONO ,MULTI)?We have seen the size of the key changing…

I guess it’ll depend on the circuit, multi will be better probably as some integers could have smaller representation.

Can we see this using some code example

Sure, you can create a circuit with multiple bit-widths (like the examples in the beginning of this thread), do an encryption, serialize the encrypted value, and check the size. For both options :slightly_smiling_face:

But how do i return the size of the serialized object ?I am unsure whether size attribute works

Serialization returns a bytes object, which has a len :slightly_smiling_face:

import numpy as np
from concrete import fhe

def f(x, y):
    return (x ** 2) + (y ** 2)

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

compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset)

x, y = circuit.encrypt(50, 2)

print(len(x.serialize()))
print(len(y.serialize()))

run this, then enable mono parameters, and run it again to see the difference.

probably a trivial doubt
y=x.serialize()
y.deserialize()
AttributeError: ‘bytes’ object has no attribute ‘deserialize’
Any ways to deserialize?