Key-generation time tree-based models

Hi,

I performed some comparisons between the different classifiers available in concrete-ml and noticed that, for tree-based models (at least for decision trees and XGBoost classes) the key-generation time increases a lot at a specific value of n_bits (n_bits=7) and return to a normal value after.

Do you have any idea where this increase could come from?

Thanks

Hi @PaulC,

I can only speculate without more details but I would guess that your model with n_bits = 7 has a max precision above 8 bits. You can check this by printing model.fhe_circuit.graph.maximum_integer_bit_width(). If it’s not the case, could you share the graph of your model so that we can check what is happening? You can get it with print(model.fhe_circuit).

Basically, for a max precision from 1 to 8 bits we use a type 1 FHE execution and then from 9 to 16 bits we use a type 2 FHE execution. Type 1 can have a long keygen for higher bitwidth (e.g. 7, 8) and type 2 has a faster keygen which will be relatively the same for any bitwidth (above 8). I suspect you have reached the second type with n_bits = 7.

Hi,

My model is a simple decision tree used as a classifier with no parameter sets except the n_bits. My data are generated by the ‘make_classification’ function from scikit-learn with various numbers of features.

My model has a max precision of 7 bits for n_bits=7, 8 bits for n_bits = 8 , etc…

Here is the graph of my model for n_bits = 7 :

 %0 = _inputs                                  # EncryptedTensor<uint7, shape=(1, 2)>            ∈ [0, 127]
 %1 = transpose(%0)                            # EncryptedTensor<uint7, shape=(2, 1)>            ∈ [0, 127]
 %2 = [[1 0] [1  ...  1] [0 1]]                # ClearTensor<uint1, shape=(20, 2)>               ∈ [0, 1]
 %3 = matmul(%2, %1)                           # EncryptedTensor<uint7, shape=(20, 1)>           ∈ [0, 127]
 %4 = [[59] [35] ... [91] [93]]                # ClearTensor<uint7, shape=(20, 1)>               ∈ [21, 93]
 %5 = less_equal(%3, %4)                       # EncryptedTensor<uint1, shape=(20, 1)>           ∈ [0, 1]
 %6 = reshape(%5, newshape=[ 1 20 -1])         # EncryptedTensor<uint1, shape=(1, 20, 1)>        ∈ [0, 1]
 %7 = [[[ 1  1   ... 0 -1 -1]]]                # ClearTensor<int2, shape=(1, 21, 20)>            ∈ [-1, 1]
 %8 = matmul(%7, %6)                           # EncryptedTensor<int4, shape=(1, 21, 1)>         ∈ [-3, 6]
 %9 = reshape(%8, newshape=[21 -1])            # EncryptedTensor<int4, shape=(21, 1)>            ∈ [-3, 6]
%10 = [[4] [4] [ ... ] [1] [0]]                # ClearTensor<uint3, shape=(21, 1)>               ∈ [0, 6]
%11 = equal(%10, %9)                           # EncryptedTensor<uint1, shape=(21, 1)>           ∈ [0, 1]
%12 = reshape(%11, newshape=[ 1 21 -1])        # EncryptedTensor<uint1, shape=(1, 21, 1)>        ∈ [0, 1]
%13 = [[[127   0 ...   0 127]]]                # ClearTensor<uint7, shape=(1, 2, 21)>            ∈ [0, 127]
%14 = matmul(%13, %12)                         # EncryptedTensor<uint7, shape=(1, 2, 1)>         ∈ [0, 127]
%15 = reshape(%14, newshape=[ 1  2 -1])        # EncryptedTensor<uint7, shape=(1, 2, 1)>         ∈ [0, 127]
%16 = transpose(%15, axes=(2, 1, 0))           # EncryptedTensor<uint7, shape=(1, 2, 1)>         ∈ [0, 127]
return %16

Where can I find more informations about these FHE execution types ?

Thanks

NB : Just switch the verbose option on true during compilation and got these information about crypto-parameters, is this related to the execution types you mentioned above ?

For n_bits=7 :

-- Dag Solution
  1x glwe_dimension
  2**14 polynomial (16384)
  942 lwe dimension 
  keyswitch l,b=6,3
  blindrota l,b=3,11
  wopPbs : false

For n_bits=8 :

-- Dag Solution
  2x glwe_dimension
  2**10 polynomial (1024)
  713 lwe dimension 
  keyswitch l,b=7,2
  blindrota l,b=5,8
  wopPbs : true
    |cb_decomp l,b=2,10
    |pp_decomp l,b=3,13

So it looks like I was partly wrong. A circuit with a maximum bit-width of 8 can also use the type 2 FHE inference. You can see this below the “Dag Solution”. The 7 bits has wopPbs : false (which is the type 1 FHE execution I referred to) and the 8 bits circuit has wopPbs : true (which would be type 2 FHE execution).

The difference is quite low level has it’s different ways to execute operation on ciphertext. At some precision (here 8), the optimizer backend in concrete choose to use the woppbs implementation as it gives better FHE runtime. The keygen time excecution is not really taken into account as it’s just a one time run.