Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simulation #457

Merged
merged 14 commits into from
Jun 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions compilers/concrete-compiler/compiler/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,30 @@ add_library(concrete_cpu STATIC IMPORTED)
set_target_properties(concrete_cpu PROPERTIES IMPORTED_LOCATION "${CONCRETE_CPU_STATIC_LIB}")
add_dependencies(concrete_cpu concrete_cpu_rust)

# -------------------------------------------------------------------------------
# Concrete CPU Noise Model Configuration
# -------------------------------------------------------------------------------

set(CONCRETE_CPU_NOISE_MODEL_DIR "${CONCRETE_BACKENDS_DIR}/concrete-cpu/noise-model")
set(CONCRETE_CPU_NOISE_MODEL_RELEASE_DIR "${CONCRETE_CPU_NOISE_MODEL_DIR}/target/release")
set(CONCRETE_CPU_NOISE_MODEL_INCLUDE_DIR "${CONCRETE_CPU_NOISE_MODEL_DIR}/include")
set(CONCRETE_CPU_NOISE_MODEL_STATIC_LIB "${CONCRETE_CPU_NOISE_MODEL_RELEASE_DIR}/libconcrete_cpu_noise_model.a")

ExternalProject_Add(
concrete_cpu_noise_model_rust
DOWNLOAD_COMMAND ""
CONFIGURE_COMMAND "" OUTPUT "${CONCRETE_CPU_NOISE_MODEL_STATIC_LIB}"
BUILD_ALWAYS true
BUILD_COMMAND cargo +nightly build --release
BINARY_DIR "${CONCRETE_CPU_NOISE_MODEL_DIR}"
INSTALL_COMMAND ""
LOG_BUILD ON
LOG_OUTPUT_ON_FAILURE ON)

add_library(concrete_cpu_noise_model STATIC IMPORTED)
set_target_properties(concrete_cpu_noise_model PROPERTIES IMPORTED_LOCATION "${CONCRETE_CPU_NOISE_MODEL_STATIC_LIB}")
add_dependencies(concrete_cpu_noise_model concrete_cpu_noise_model_rust)

# --------------------------------------------------------------------------------
# Concrete Cuda Configuration
# --------------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ library_server_call(LibrarySupport_Py support,
concretelang::clientlib::PublicArguments &args,
concretelang::clientlib::EvaluationKeys &evaluationKeys);

MLIR_CAPI_EXPORTED std::unique_ptr<concretelang::clientlib::PublicResult>
library_simulate(LibrarySupport_Py support,
concretelang::serverlib::ServerLambda lambda,
concretelang::clientlib::PublicArguments &args);

MLIR_CAPI_EXPORTED std::string
library_get_shared_lib_path(LibrarySupport_Py support);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ class TypedClientLambda : public ClientLambda {

outcome::checked<std::unique_ptr<PublicArguments>, StringError>
publicArguments(Args... args, KeySet &keySet) {
OUTCOME_TRY(auto clientArguments,
EncryptedArguments::create(keySet, args...));
OUTCOME_TRY(
auto clientArguments,
EncryptedArguments::create(/*simulation*/ false, keySet, args...));

return clientArguments->exportPublicArguments(clientParameters);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,9 @@ struct ClientParameters {
}

/// bufferShape returns the shape of the tensor for the given gate. It returns
/// the shape used at low-level, i.e. contains the dimensions for ciphertexts.
std::vector<int64_t> bufferShape(CircuitGate gate) {
/// the shape used at low-level, i.e. contains the dimensions for ciphertexts
/// (if not in simulation).
std::vector<int64_t> bufferShape(CircuitGate gate, bool simulation = false) {
if (!gate.encryption.has_value()) {
// Value is not encrypted just returns the tensor shape
return gate.shape.dimensions;
Expand All @@ -289,8 +290,10 @@ struct ClientParameters {
if (!crt.empty()) {
shape.push_back(crt.size());
}
// Add one dimension for the size of ciphertext(s)
shape.push_back(lweSecreteKeyParam.value().lweSize());
if (!simulation) {
// Add one dimension for the size of ciphertext(s)
shape.push_back(lweSecreteKeyParam.value().lweSize());
}
return shape;
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@

#include "boost/outcome.h"

#include "../Common/Error.h"
#include "concretelang/ClientLib/ClientParameters.h"
#include "concretelang/ClientLib/KeySet.h"
#include "concretelang/ClientLib/Types.h"
#include "concretelang/ClientLib/ValueExporter.h"
#include "concretelang/Common/BitsSize.h"
#include "concretelang/Common/Error.h"

namespace concretelang {
namespace clientlib {
Expand All @@ -23,173 +24,42 @@ using concretelang::error::StringError;

class PublicArguments;

/// @brief The ArgumentsExporter allows to transform clear
/// arguments to the one expected by a server lambda.
class ValueExporter {

public:
/// @brief
/// @param keySet
/// @param clientParameters
// TODO: Get rid of the reference here could make troubles (see for KeySet
// copy constructor or shared pointers)
ValueExporter(KeySet &keySet, ClientParameters clientParameters)
: _keySet(keySet), _clientParameters(clientParameters) {}

/// @brief Export a scalar 64 bits integer to a concreteprocol::Value
/// @param arg An 64 bits integer
/// @param argPos The position of the argument to export
/// @return Either the exported value ready to be sent to the server or an
/// error if the gate doesn't match the expected argument.
outcome::checked<ScalarOrTensorData, StringError> exportValue(uint64_t arg,
size_t argPos) {
OUTCOME_TRY(auto gate, _clientParameters.input(argPos));
if (gate.shape.size != 0) {
return StringError("argument #") << argPos << " is not a scalar";
}
if (gate.encryption.has_value()) {
return exportEncryptValue(arg, gate, argPos);
}
return exportClearValue(arg);
}

/// @brief Export a tensor like buffer of values to a serializable value
/// @tparam T The type of values hold by the buffer
/// @param arg A pointer to a memory area where the values are stored
/// @param shape The shape of the tensor
/// @param argPos The position of the argument to export
/// @return Either the exported value ready to be sent to the server or an
/// error if the gate doesn't match the expected argument.
template <typename T>
outcome::checked<ScalarOrTensorData, StringError>
exportValue(const T *arg, llvm::ArrayRef<int64_t> shape, size_t argPos) {
OUTCOME_TRY(auto gate, _clientParameters.input(argPos));
OUTCOME_TRYV(checkShape(shape, gate.shape, argPos));
if (gate.encryption.has_value()) {
return exportEncryptTensor(arg, shape, gate, argPos);
}
return exportClearTensor(arg, shape, gate);
}

private:
/// Export a 64bits integer to a serializable value
outcome::checked<ScalarOrTensorData, StringError>
exportClearValue(uint64_t arg) {
return ScalarData(arg);
}

/// Encrypt and export a 64bits integer to a serializale value
outcome::checked<ScalarOrTensorData, StringError>
exportEncryptValue(uint64_t arg, CircuitGate &gate, size_t argPos) {
std::vector<int64_t> shape = _clientParameters.bufferShape(gate);

// Create and allocate the TensorData that will holds encrypted value
TensorData td(shape, clientlib::EncryptedScalarElementType,
clientlib::EncryptedScalarElementWidth);

// Encrypt the value
OUTCOME_TRYV(
_keySet.encrypt_lwe(argPos, td.getElementPointer<uint64_t>(0), arg));
return std::move(td);
}

/// Export a tensor like buffer to a serializable value
template <typename T>
outcome::checked<ScalarOrTensorData, StringError>
exportClearTensor(const T *arg, llvm::ArrayRef<int64_t> shape,
CircuitGate &gate) {
auto bitsPerValue = bitWidthAsWord(gate.shape.width);
auto sizes = _clientParameters.bufferShape(gate);
TensorData td(sizes, bitsPerValue, gate.shape.sign);
llvm::ArrayRef<T> values(arg, TensorData::getNumElements(sizes));
td.bulkAssign(values);
return std::move(td);
}

/// Export and encrypt a tensor like buffer to a serializable value
template <typename T>
outcome::checked<ScalarOrTensorData, StringError>
exportEncryptTensor(const T *arg, llvm::ArrayRef<int64_t> shape,
CircuitGate &gate, size_t argPos) {
// Create and allocate the TensorData that will holds encrypted values
auto sizes = _clientParameters.bufferShape(gate);
TensorData td(sizes, EncryptedScalarElementType,
EncryptedScalarElementWidth);

// Iterate over values and encrypt at the right place the value
auto lweSize = _clientParameters.lweBufferSize(gate);
for (size_t i = 0, offset = 0; i < gate.shape.size;
i++, offset += lweSize) {
OUTCOME_TRYV(_keySet.encrypt_lwe(
argPos, td.getElementPointer<uint64_t>(offset), arg[i]));
}
return std::move(td);
}

static outcome::checked<void, StringError>
checkShape(llvm::ArrayRef<int64_t> shape, CircuitGateShape expected,
size_t argPos) {
// Check the shape of tensor
if (expected.dimensions.empty()) {
return StringError("argument #") << argPos << "is not a tensor";
}
if (shape.size() != expected.dimensions.size()) {
return StringError("argument #")
<< argPos << "has not the expected number of dimension, got "
<< shape.size() << " expected " << expected.dimensions.size();
}

// Check shape
for (size_t i = 0; i < shape.size(); i++) {
if (shape[i] != expected.dimensions[i]) {
return StringError("argument #")
<< argPos << " has not the expected dimension #" << i
<< " , got " << shape[i] << " expected "
<< expected.dimensions[i];
}
}
return outcome::success();
}

private:
KeySet &_keySet;
ClientParameters _clientParameters;
};

/// Temporary object used to hold and encrypt parameters before calling a
/// ClientLambda. Use preferably TypeClientLambda and serializeCall(Args...).
/// Otherwise convert it to a PublicArguments and use
/// serializeCall(PublicArguments, KeySet).
class EncryptedArguments {

public:
EncryptedArguments() {}
EncryptedArguments(bool simulation = false) : simulation(simulation) {}

/// Encrypts args thanks the given KeySet and pack the encrypted arguments
/// to an EncryptedArguments
template <typename... Args>
static outcome::checked<std::unique_ptr<EncryptedArguments>, StringError>
create(KeySet &keySet, Args... args) {
auto encryptedArgs = std::make_unique<EncryptedArguments>();
create(bool simulation, KeySet &keySet, Args... args) {
auto encryptedArgs = std::make_unique<EncryptedArguments>(simulation);
OUTCOME_TRYV(encryptedArgs->pushArgs(keySet, args...));
return std::move(encryptedArgs);
}

template <typename ArgT>
static outcome::checked<std::unique_ptr<EncryptedArguments>, StringError>
create(KeySet &keySet, const llvm::ArrayRef<ArgT> args) {
auto encryptedArgs = EncryptedArguments::empty();
create(bool simulation, KeySet &keySet, const llvm::ArrayRef<ArgT> args) {
auto encryptedArgs = EncryptedArguments::empty(simulation);
for (size_t i = 0; i < args.size(); i++) {
OUTCOME_TRYV(encryptedArgs->pushArg(args[i], keySet));
}
OUTCOME_TRYV(encryptedArgs->checkAllArgs(keySet));
return std::move(encryptedArgs);
}

static std::unique_ptr<EncryptedArguments> empty() {
return std::make_unique<EncryptedArguments>();
static std::unique_ptr<EncryptedArguments> empty(bool simulation = false) {
return std::make_unique<EncryptedArguments>(simulation);
}

bool isSimulated() { return simulation; }

/// Export encrypted arguments as public arguments, reset the encrypted
/// arguments, i.e. move all buffers to the PublicArguments and reset the
/// positional counter.
Expand All @@ -199,12 +69,13 @@ class EncryptedArguments {
/// Check that all arguments as been pushed.
// TODO: Remove public method here
outcome::checked<void, StringError> checkAllArgs(KeySet &keySet);
outcome::checked<void, StringError> checkAllArgs(ClientParameters &params);

public:
/// Add a uint64_t scalar argument.
outcome::checked<void, StringError> pushArg(uint64_t arg, KeySet &keySet) {
ValueExporter exporter(keySet, keySet.clientParameters());
OUTCOME_TRY(auto value, exporter.exportValue(arg, values.size()));
auto exporter = getExporter(keySet);
OUTCOME_TRY(auto value, exporter->exportValue(arg, values.size()));
values.push_back(std::move(value));
return outcome::success();
}
Expand Down Expand Up @@ -267,8 +138,8 @@ class EncryptedArguments {
template <typename T>
outcome::checked<void, StringError>
pushArg(const T *data, llvm::ArrayRef<int64_t> shape, KeySet &keySet) {
ValueExporter exporter(keySet, keySet.clientParameters());
OUTCOME_TRY(auto value, exporter.exportValue(data, shape, values.size()));
auto exporter = getExporter(keySet);
OUTCOME_TRY(auto value, exporter->exportValue(data, shape, values.size()));
values.push_back(std::move(value));
return outcome::success();
}
Expand Down Expand Up @@ -297,8 +168,19 @@ class EncryptedArguments {
}

private:
std::unique_ptr<ValueExporterInterface> getExporter(KeySet &keySet) {
if (isSimulated()) {
return std::make_unique<SimulatedValueExporter>(
keySet.clientParameters());
} else {
return std::make_unique<ValueExporter>(keySet, keySet.clientParameters());
}
}

/// Store buffers of ciphertexts
std::vector<ScalarOrTensorData> values;
/// Whether it a simulates an encrypted argument or not
bool simulation;
};

} // namespace clientlib
Expand Down
Loading