SEAL
SEAL copied to clipboard
Guide for achive x^4 for a naive profiling
I'm new in this world and one task that my advisor give was to implement a simple x^4.
The idea is to try an intensive workload for a domestic PC to do a naive profile using something like Perf.
I implement x^3 with no problem, but when I try x^4 I get wrong answers, even if use big parameters.
Here is what i have (working for x^3). I try different polynomial modulus, different coefficient modulus, etc. I know that doesn't exist THE way to choose the parameters, but some guide for the modulus array? Some guidance?
#include <iostream>
#include <vector>
#include <seal/seal.h>
using std::cout;
using std::endl;
using std::vector;
using namespace seal;
int main() {
EncryptionParameters params(scheme_type::ckks);
size_t poly_modulus_degree = 16384; // Try 32768
int depth = 2; // with 3 is for x^4
params.set_poly_modulus_degree(poly_modulus_degree);
params.set_coeff_modulus(CoeffModulus::Create(poly_modulus_degree, { 60, 40, 40, 40, 40, 60 })); // For 32768 I try with { 60, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 60 }
double scale = pow(2.0, 60);
SEALContext context(params);
KeyGenerator keygen(context);
auto secret_key = keygen.secret_key();
PublicKey public_key;
keygen.create_public_key(public_key);
RelinKeys relin_keys;
keygen.create_relin_keys(relin_keys);
Encryptor encryptor(context, public_key);
Evaluator evaluator(context);
Decryptor decryptor(context, secret_key);
CKKSEncoder encoder(context);
size_t slot_count = encoder.slot_count();
cout << "Number of slots: " << slot_count << endl;
vector<double> input;
input.reserve(slot_count);
double curr_point = 0;
double step_size = 1.0 / (static_cast<double>(slot_count) - 1);
for (size_t i = 0; i < slot_count; i++)
{
input.push_back(curr_point*0.5+1);
curr_point += step_size;
}
vector<double> vect_mul;
for (int i=0; i<input.size(); i++)
vect_mul.push_back(input[i]);
Plaintext x_plain;
encoder.encode(input, scale, x_plain);
Ciphertext x_encrypted;
encryptor.encrypt(x_plain, x_encrypted);
Ciphertext x1 = x_encrypted;
Ciphertext x2 = x_encrypted;
evaluator.square(x1, x2);
evaluator.relinearize_inplace(x2, relin_keys);
evaluator.rescale_to_next_inplace(x2);
evaluator.rescale_to_next_inplace(x_encrypted);
Ciphertext x3 = x2;
evaluator.multiply_inplace(x3,x_encrypted);
evaluator.relinearize_inplace(x3, relin_keys);
evaluator.rescale_to_next_inplace(x_encrypted);
evaluator.rescale_to_next_inplace(x3);
Ciphertext x4 = x3;
evaluator.multiply_inplace(x4,x_encrypted);
evaluator.relinearize_inplace(x4, relin_keys);
evaluator.rescale_to_next_inplace(x_encrypted);
evaluator.rescale_to_next_inplace(x4);
Plaintext plain_result;
decryptor.decrypt(x4, plain_result);
vector<double> result;
encoder.decode(plain_result, result);
for (int i=0; i<depth; i++){
for (int i = 0; i < vect_mul.size(); i++) {
vect_mul[i] *= input[i];
}
}
for (int i=0; i<slot_count; i=i+100){
cout << vect_mul[i] << " - "<< result[i] <<"\n";
}
return 0;
}
- You should NOT call rescale on
x_encryptedsince it has only one level of scaling factor. - Rescale usually comes after a multiplication, eg. the mul
Enc(Delta * x) * Enc(Delta * y)givesEnc(Delta^2 * x* y)so we rescale it down toEnc(Delta^2/q * x * y)and hopingDelta^2/qis approximately closes toDelta(whereqis the current modulus to drop) - We can use
evaluator.mod_switch_to_next()API to align the number of modulus WITHOUT doing the rescale.