This tutorial details the steps involved in implementing a new backend utilizing the C++ wrapper for use with the testing framework.
This guide is intended to be a beginners guide for people who are looking to get a backend with a single test implemented quickly. It is based on using the api_bridge_example_backend
as a starting point for a new backend implementation which uses the C++ wrapper.
For the purposes of this guide we will be creating a very basic backend implementing a benchmark for the vector element-wise addition workload, under the offline category, with first operand in plain text and second encrypted, using Microsoft SEAL 3.6 to perform Homomorphic Encryption (HE) operations.
Assume that we already have a functional workflow for our workload as listed below, and we want to benchmark it using HEBench.
15 #include "seal/seal.h"
22 std::vector<seal::Plaintext> encodeVector(
const std::vector<std::vector<std::int64_t>> &vec);
23 std::vector<seal::Ciphertext> encryptVector(
const std::vector<seal::Plaintext> &encoded_vec);
24 std::vector<seal::Ciphertext> eltwiseadd(
const std::vector<seal::Plaintext> &A,
25 const std::vector<seal::Ciphertext> &B);
26 std::vector<seal::Plaintext> decryptResult(
const std::vector<seal::Ciphertext> &encrypted_result);
27 std::vector<std::vector<int64_t>> decodeResult(
const std::vector<seal::Plaintext> &encoded_result);
33 SealBFVContext(
int poly_modulus_degree)
35 seal::EncryptionParameters parms{ seal::scheme_type::bfv };
36 parms.set_poly_modulus_degree(poly_modulus_degree);
37 parms.set_coeff_modulus(seal::CoeffModulus::Create(poly_modulus_degree, { 60, 60 }));
38 parms.set_plain_modulus(seal::PlainModulus::Batching(poly_modulus_degree, 20));
39 m_p_seal_context.reset(
40 new seal::SEALContext(parms,
true, seal::sec_level_type::tc128));
41 m_p_keygen = std::make_unique<seal::KeyGenerator>(context());
43 m_p_keygen->create_public_key(m_public_key);
44 m_secret_key = m_p_keygen->secret_key();
46 m_p_encryptor = std::make_unique<seal::Encryptor>(context(), m_public_key);
47 m_p_evaluator = std::make_unique<seal::Evaluator>(context());
48 m_p_decryptor = std::make_unique<seal::Decryptor>(context(), m_secret_key);
49 m_p_batch_encoder = std::make_unique<seal::BatchEncoder>(context());
52 seal::BatchEncoder &encoder() {
return *m_p_batch_encoder; }
53 seal::Evaluator &evaluator() {
return *m_p_evaluator; }
54 seal::Decryptor &decryptor() {
return *m_p_decryptor; }
55 const seal::Encryptor &encryptor()
const {
return *m_p_encryptor; }
56 const seal::PublicKey &public_key()
const {
return m_public_key; }
57 const seal::SecretKey &secret_key()
const {
return m_secret_key; }
58 seal::SEALContext &context() {
return *m_p_seal_context; }
61 std::shared_ptr<seal::SEALContext> m_p_seal_context;
62 std::unique_ptr<seal::KeyGenerator> m_p_keygen;
63 seal::PublicKey m_public_key;
64 seal::SecretKey m_secret_key;
65 std::unique_ptr<seal::Encryptor> m_p_encryptor;
66 std::unique_ptr<seal::Evaluator> m_p_evaluator;
67 std::unique_ptr<seal::Decryptor> m_p_decryptor;
68 std::unique_ptr<seal::BatchEncoder> m_p_batch_encoder;
71 std::size_t m_vector_size;
72 std::shared_ptr<SealBFVContext> m_p_context;
74 SealBFVContext &context() {
return *m_p_context; }
80 throw std::invalid_argument(
"vector_size");
81 m_vector_size = vector_size;
82 m_p_context = std::make_shared<SealBFVContext>(8192);
85 std::vector<seal::Plaintext> Workload::encodeVector(
const std::vector<std::vector<std::int64_t>> &vec)
87 std::vector<seal::Plaintext> retval(vec.size());
89 for (std::size_t i = 0; i < vec.size(); ++i)
91 assert(vec[i].size() <= context().encoder().slot_count());
92 context().encoder().encode(vec[i], retval[i]);
97 std::vector<seal::Ciphertext> Workload::encryptVector(
const std::vector<seal::Plaintext> &encoded_vec)
99 std::vector<seal::Ciphertext> retval(encoded_vec.size());
100 for (std::size_t i = 0; i < encoded_vec.size(); i++)
101 context().encryptor().encrypt(encoded_vec[i], retval[i]);
105 std::vector<seal::Ciphertext> Workload::eltwiseadd(
const std::vector<seal::Plaintext> &A,
106 const std::vector<seal::Ciphertext> &B)
108 std::vector<seal::Ciphertext> retval(A.size() * B.size());
115 for (std::size_t A_i = 0; A_i < A.size(); ++A_i)
116 for (std::size_t B_i = 0; B_i < B.size(); ++B_i)
118 seal::Ciphertext &retval_item = retval[A_i * B.size() + B_i];
119 context().evaluator().add_plain(B[B_i], A[A_i], retval_item);
125 std::vector<seal::Plaintext> Workload::decryptResult(
const std::vector<seal::Ciphertext> &encrypted_result)
127 std::vector<seal::Plaintext> retval(encrypted_result.size());
128 for (std::size_t i = 0; i < encrypted_result.size(); i++)
129 context().decryptor().decrypt(encrypted_result[i], retval[i]);
133 std::vector<std::vector<int64_t>> Workload::decodeResult(
const std::vector<seal::Plaintext> &encoded_result)
135 std::vector<std::vector<int64_t>> retval(encoded_result.size());
136 for (std::size_t i = 0; i < encoded_result.size(); ++i)
138 context().encoder().decode(encoded_result[i], retval[i]);
139 retval[i].resize(m_vector_size);
149 static const std::size_t VectorSize = 400;
150 static const std::size_t DimensionA = 2;
151 static const std::size_t DimensionB = 5;
154 std::vector<std::vector<std::int64_t>> A(DimensionA, std::vector<std::int64_t>(VectorSize));
155 std::vector<std::vector<std::int64_t>> B(DimensionB, std::vector<std::int64_t>(VectorSize));
158 std::random_device rd;
159 std::mt19937 gen(rd());
160 std::uniform_int_distribution<> distrib(-100, 100);
162 for (std::size_t i = 0; i < A.size(); ++i)
163 for (std::size_t j = 0; j < A[i].size(); ++j)
164 A[i][j] = distrib(gen);
165 for (std::size_t i = 0; i < B.size(); ++i)
166 for (std::size_t j = 0; j < B[i].size(); ++j)
167 B[i][j] = distrib(gen);
173 std::shared_ptr<Workload> p_workload =
174 std::make_shared<Workload>(VectorSize);
178 std::vector<std::vector<std::int64_t>> result =
179 workload.decodeResult(
180 workload.decryptResult(
181 workload.eltwiseadd(workload.encodeVector(A),
182 workload.encryptVector(workload.encodeVector(B)))));
190 std::vector<std::vector<std::int64_t>> exp_out(DimensionA * DimensionB, std::vector<std::int64_t>(VectorSize));
191 assert(exp_out.size() == result.size());
194 std::size_t result_i = 0;
195 for (std::size_t A_i = 0; A_i < A.size(); ++A_i)
196 for (std::size_t B_i = 0; B_i < B.size(); ++B_i)
198 for (std::size_t i = 0; i < VectorSize; ++i)
199 exp_out[result_i][i] = A[A_i][i] + B[B_i][i];
203 if (result == exp_out)
204 std::cout <<
"OK" << std::endl;
206 std::cout <<
"Fail" << std::endl;
Workload
Defines all possible workloads.
int main(int argc, char **argv)
What we have to do is extract the stages of the testing pipeline from our workflow and integrate them into the C++ wrapper.
Follow the tutorial steps in order to complete it.