From 4d716bafbd19dded15b9f4abb4b662148df300c9 Mon Sep 17 00:00:00 2001 From: Fang Xu Date: Wed, 28 Aug 2024 11:22:44 +0800 Subject: [PATCH] Property for model cache encryption/decryption functions (#24417) ### Details: - *add new property for model cache encryption/decryption function* - *encrypt/decrypt topology in CPU model cache if the callbacks are provided.* ### Tickets: - *CVS-139600* --------- Co-authored-by: Chen Peter --- src/bindings/c/include/openvino/c/ov_common.h | 19 ++++++ .../c/include/openvino/c/ov_property.h | 9 +++ src/bindings/c/src/common.h | 47 +++++++++++-- src/bindings/c/src/ov_property.cpp | 5 +- src/bindings/c/tests/ov_core_test.cpp | 56 ++++++++++++++++ .../openvino/runtime/properties/__init__.py | 1 + .../pyopenvino/core/properties/properties.cpp | 2 + .../pyopenvino/core/properties/properties.hpp | 7 ++ .../python/src/pyopenvino/utils/utils.cpp | 26 +++++++- .../tests/test_runtime/test_compiled_model.py | 28 ++++++++ src/bindings/python/tests/utils/helpers.py | 22 +++++++ .../include/openvino/runtime/properties.hpp | 16 +++++ src/plugins/hetero/src/config.cpp | 2 + src/plugins/hetero/src/config.hpp | 2 + src/plugins/intel_cpu/src/compiled_model.cpp | 2 +- src/plugins/intel_cpu/src/config.cpp | 8 +++ src/plugins/intel_cpu/src/config.h | 2 + src/plugins/intel_cpu/src/plugin.cpp | 25 ++++++- src/plugins/intel_cpu/src/serialize.cpp | 15 +++-- src/plugins/intel_cpu/src/serialize.h | 8 ++- .../behavior/ov_plugin/caching_tests.cpp | 4 ++ .../src/runtime/execution_config.cpp | 1 + .../behavior/ov_plugin/caching_tests.cpp | 4 ++ .../intel_npu/src/plugin/src/plugin.cpp | 4 ++ src/plugins/template/src/config.cpp | 2 + src/plugins/template/src/config.hpp | 2 + .../behavior/ov_plugin/caching_tests.cpp | 4 ++ .../behavior/ov_plugin/caching_tests.hpp | 15 +++++ .../src/behavior/ov_plugin/caching_tests.cpp | 66 +++++++++++++++++++ 29 files changed, 385 insertions(+), 19 deletions(-) diff --git a/src/bindings/c/include/openvino/c/ov_common.h b/src/bindings/c/include/openvino/c/ov_common.h index 321dca8eb2023d..fabff963e30f0d 100644 --- a/src/bindings/c/include/openvino/c/ov_common.h +++ b/src/bindings/c/include/openvino/c/ov_common.h @@ -198,6 +198,25 @@ typedef enum { F8E8M0, //!< f8e8m0 element type } ov_element_type_e; +/** + * @brief encryption_func is a function pointer that encrypt or decrypt the input memory, example of this function is + * codec(const char* input, const size_t in_size, const char* output, size_t* out_size) + * This function needs to be called twice, + * the first call to obtain out_size (the size of output buffer), the second call to obtain output buffer. + * The first call output is nullptr, before the second call, the caller needs to apply for output + * memory based on the out_size returned by the first call. + * the memory of parameter output is allocated and released by the caller. + * @param input The pointer to the input buffer. + * @param in_size The size of input. + * @param output The pointer to the encrypted/decrypted buffer. + * @param out_size The size of output. + */ +typedef void (*encryption_func)(const char*, const size_t, char*, size_t*); +typedef struct { + encryption_func encrypt_func; // encryption function pointer + encryption_func decrypt_func; // decryption function pointer +} ov_encryption_callbacks; + /** * @brief Print the error info. * @ingroup ov_base_c_api diff --git a/src/bindings/c/include/openvino/c/ov_property.h b/src/bindings/c/include/openvino/c/ov_property.h index 081932cdb50879..905d473fc36ba8 100644 --- a/src/bindings/c/include/openvino/c/ov_property.h +++ b/src/bindings/c/include/openvino/c/ov_property.h @@ -107,6 +107,15 @@ ov_property_key_cache_dir; OPENVINO_C_VAR(const char*) ov_property_key_cache_mode; +/** + * @brief Write-only property to set encryption/decryption function for model cache. + * If ov_property_key_cache_encryption_callbacks is set, model cache will be encrypted/decrypted when saving/loading + * model cache. ov_property_key_cache_encryption_callbacks is enabled in ov_core_compile_model_* only + * @ingroup ov_property_c_api + */ +OPENVINO_C_VAR(const char*) +ov_property_key_cache_encryption_callbacks; + /** * @brief Read-write property to set/get the number of executor logical partitions. * @ingroup ov_property_c_api diff --git a/src/bindings/c/src/common.h b/src/bindings/c/src/common.h index bf41e520cfa8fd..f4fc3f40d42bfb 100644 --- a/src/bindings/c/src/common.h +++ b/src/bindings/c/src/common.h @@ -10,6 +10,7 @@ #include #include +#include "openvino/c/ov_common.h" #include "openvino/core/except.hpp" #include "openvino/openvino.hpp" #include "openvino/runtime/exception.hpp" @@ -30,11 +31,47 @@ return ov_status_e::UNKNOW_EXCEPTION; \ } -#define GET_PROPERTY_FROM_ARGS_LIST \ - std::string property_key = va_arg(args_ptr, char*); \ - std::string _value = va_arg(args_ptr, char*); \ - ov::Any value = _value; \ - property[property_key] = value; +#define GET_PROPERTY_FROM_ARGS_LIST \ + std::string property_key = va_arg(args_ptr, char*); \ + if (property_key == ov::cache_encryption_callbacks.name()) { \ + ov_encryption_callbacks* _value = va_arg(args_ptr, ov_encryption_callbacks*); \ + auto encrypt_func = _value->encrypt_func; \ + auto decrypt_func = _value->decrypt_func; \ + std::function encrypt_value = [encrypt_func](const std::string& in) { \ + size_t out_size = 0; \ + std::string out_str; \ + encrypt_func(in.c_str(), in.length(), nullptr, &out_size); \ + if (out_size > 0) { \ + std::unique_ptr output_ptr(new char[out_size]); \ + if (output_ptr) { \ + char* output = output_ptr.get(); \ + encrypt_func(in.c_str(), in.length(), output, &out_size); \ + out_str.assign(output, out_size); \ + } \ + } \ + return out_str; \ + }; \ + std::function decrypt_value = [decrypt_func](const std::string& in) { \ + size_t out_size = 0; \ + std::string out_str; \ + decrypt_func(in.c_str(), in.length(), nullptr, &out_size); \ + if (out_size > 0) { \ + std::unique_ptr output_ptr(new char[out_size]); \ + if (output_ptr) { \ + char* output = output_ptr.get(); \ + decrypt_func(in.c_str(), in.length(), output, &out_size); \ + out_str.assign(output, out_size); \ + } \ + } \ + return out_str; \ + }; \ + ov::EncryptionCallbacks encryption_callbacks{encrypt_value, decrypt_value}; \ + property[property_key] = encryption_callbacks; \ + } else { \ + std::string _value = va_arg(args_ptr, char*); \ + ov::Any value = _value; \ + property[property_key] = value; \ + } /** * @struct ov_core diff --git a/src/bindings/c/src/ov_property.cpp b/src/bindings/c/src/ov_property.cpp index 7c33b4b8dbb9cd..3786499ab79677 100644 --- a/src/bindings/c/src/ov_property.cpp +++ b/src/bindings/c/src/ov_property.cpp @@ -36,4 +36,7 @@ const char* ov_property_key_device_priorities = "MULTI_DEVICE_PRIORITIES"; const char* ov_property_key_hint_execution_mode = "EXECUTION_MODE_HINT"; const char* ov_property_key_force_tbb_terminate = "FORCE_TBB_TERMINATE"; const char* ov_property_key_enable_mmap = "ENABLE_MMAP"; -const char* ov_property_key_auto_batch_timeout = "AUTO_BATCH_TIMEOUT"; \ No newline at end of file +const char* ov_property_key_auto_batch_timeout = "AUTO_BATCH_TIMEOUT"; + +// Write-only property key +const char* ov_property_key_cache_encryption_callbacks = "CACHE_ENCRYPTION_CALLBACKS"; diff --git a/src/bindings/c/tests/ov_core_test.cpp b/src/bindings/c/tests/ov_core_test.cpp index 40aab57b2c7e6b..04e51ff9b175d3 100644 --- a/src/bindings/c/tests/ov_core_test.cpp +++ b/src/bindings/c/tests/ov_core_test.cpp @@ -574,6 +574,62 @@ TEST_P(ov_core_test, ov_core_import_model) { ov_core_free(core); } +static const char codec_key[] = {0x30, 0x60, 0x70, 0x02, 0x04, 0x08, 0x3F, 0x6F, 0x72, 0x74, 0x78, 0x7F}; + +static void codec_xor(const char* in, const size_t in_size, char* out, size_t* out_size) { + if (!out || *out_size < in_size) { + *out_size = in_size; + return; + } + size_t key_size = sizeof(codec_key); + for (size_t i = 0; i < in_size; i++) { + out[i] = in[i] ^ codec_key[i % key_size]; + } + *out_size = in_size; +} + +TEST_P(ov_core_test, ov_core_import_model_with_encryption) { + auto device_name = GetParam(); + ov_core_t* core = nullptr; + + OV_EXPECT_OK(ov_core_create(&core)); + EXPECT_NE(nullptr, core); + + char* optimization_capabilites = NULL; + ov_core_get_property(core, device_name.c_str(), "OPTIMIZATION_CAPABILITIES", &optimization_capabilites); + if (std::string(optimization_capabilites).find("EXPORT_IMPORT") == std::string::npos) { + GTEST_SKIP() << "Skip this test, cause no EXPORT_IMPORT supported"; + } + + const char* key = ov_property_key_cache_encryption_callbacks; + ov_encryption_callbacks encryption_callbacks{codec_xor, codec_xor}; + + ov_compiled_model_t* compiled_model = nullptr; + OV_EXPECT_OK(ov_core_compile_model_from_file(core, + xml_file_name.c_str(), + device_name.c_str(), + 2, + &compiled_model, + key, + &encryption_callbacks)); + EXPECT_NE(nullptr, compiled_model); + + std::string export_path = TestDataHelpers::get_exported_blob_file_name(); + OV_EXPECT_OK(ov_compiled_model_export_model(compiled_model, export_path.c_str())); + ov_compiled_model_free(compiled_model); + + std::vector buffer(content_from_file(export_path.c_str(), true)); + ov_compiled_model_t* compiled_model_imported = nullptr; + OV_EXPECT_OK(ov_core_import_model(core, + reinterpret_cast(buffer.data()), + buffer.size(), + device_name.c_str(), + &compiled_model_imported)); + EXPECT_NE(nullptr, compiled_model_imported); + ov_compiled_model_free(compiled_model_imported); + ov_core_free(core); +} + TEST_P(ov_core_test, ov_core_get_versions_by_device_name) { auto device_name = GetParam(); ov_core_t* core = nullptr; diff --git a/src/bindings/python/src/openvino/runtime/properties/__init__.py b/src/bindings/python/src/openvino/runtime/properties/__init__.py index c25db0bfa4d884..caaa93f37223b0 100644 --- a/src/bindings/python/src/openvino/runtime/properties/__init__.py +++ b/src/bindings/python/src/openvino/runtime/properties/__init__.py @@ -28,6 +28,7 @@ from openvino._pyopenvino.properties import range_for_async_infer_requests from openvino._pyopenvino.properties import execution_devices from openvino._pyopenvino.properties import loaded_from_cache +from openvino._pyopenvino.properties import cache_encryption_callbacks # Submodules from openvino.runtime.properties import hint diff --git a/src/bindings/python/src/pyopenvino/core/properties/properties.cpp b/src/bindings/python/src/pyopenvino/core/properties/properties.cpp index ad53a1b64a1e0f..470161d9779558 100644 --- a/src/bindings/python/src/pyopenvino/core/properties/properties.cpp +++ b/src/bindings/python/src/pyopenvino/core/properties/properties.cpp @@ -55,6 +55,8 @@ void regmodule_properties(py::module m) { wrap_property_RO(m_properties, ov::execution_devices, "execution_devices"); wrap_property_RO(m_properties, ov::loaded_from_cache, "loaded_from_cache"); + wrap_property_WO(m_properties, ov::cache_encryption_callbacks, "cache_encryption_callbacks"); + // Submodule hint py::module m_hint = m_properties.def_submodule("hint", "openvino.properties.hint submodule that simulates ov::hint"); diff --git a/src/bindings/python/src/pyopenvino/core/properties/properties.hpp b/src/bindings/python/src/pyopenvino/core/properties/properties.hpp index a7c20c6f8707ba..831c8336de8d65 100644 --- a/src/bindings/python/src/pyopenvino/core/properties/properties.hpp +++ b/src/bindings/python/src/pyopenvino/core/properties/properties.hpp @@ -33,4 +33,11 @@ void wrap_property_RW(py::module m, ov::Property }); } +template +void wrap_property_WO(py::module m, ov::Property property, std::string func_name) { + m.def(func_name.c_str(), [property](T value) { + return property(value); + }); +} + void regmodule_properties(py::module m); diff --git a/src/bindings/python/src/pyopenvino/utils/utils.cpp b/src/bindings/python/src/pyopenvino/utils/utils.cpp index 3b7501824c8264..e165c2e00b4808 100644 --- a/src/bindings/python/src/pyopenvino/utils/utils.cpp +++ b/src/bindings/python/src/pyopenvino/utils/utils.cpp @@ -253,7 +253,31 @@ py::object from_ov_any(const ov::Any& any) { std::map properties_to_any_map(const std::map& properties) { std::map properties_to_cpp; for (const auto& property : properties) { - properties_to_cpp[property.first] = Common::utils::py_object_to_any(property.second); + if (property.first == ov::cache_encryption_callbacks.name()) { + auto property_value = property.second; + if (!py::isinstance(property_value)) { + OPENVINO_THROW("The value type of ov::cache_encryption_callbacks property is expected list"); + } + std::function encrypt_func = + [property_value](const std::string& in_str) -> std::string { + // Acquire GIL, execute Python function + py::gil_scoped_acquire acquire; + auto _list = property_value.cast(); + return _list[0](in_str).cast(); + }; + + std::function decrypt_func = + [property_value](const std::string& in_str) -> std::string { + // Acquire GIL, execute Python function + py::gil_scoped_acquire acquire; + auto _list = property_value.cast(); + return _list[1](in_str).cast(); + }; + ov::EncryptionCallbacks encryption_callbacks{encrypt_func, decrypt_func}; + properties_to_cpp[property.first] = encryption_callbacks; + } else { + properties_to_cpp[property.first] = Common::utils::py_object_to_any(property.second); + } } return properties_to_cpp; } diff --git a/src/bindings/python/tests/test_runtime/test_compiled_model.py b/src/bindings/python/tests/test_runtime/test_compiled_model.py index 3c71b09323f0f5..5516d8430e5839 100644 --- a/src/bindings/python/tests/test_runtime/test_compiled_model.py +++ b/src/bindings/python/tests/test_runtime/test_compiled_model.py @@ -11,6 +11,9 @@ generate_image, generate_model_and_image, generate_relu_compiled_model, + generate_relu_compiled_model_with_config, + encrypt_base64, + decrypt_base64, create_filename_for_test) from openvino import Model, Shape, Core, Tensor, serialize from openvino.runtime import ConstOutput @@ -54,6 +57,31 @@ def test_export_import(device): assert np.argmax(res[new_compiled.outputs[0]]) == 531 +@pytest.mark.skipif( + condition=sys.version_info >= (3, 12), + reason="Fails on any Linux platform with Python 3.12. Ticket CVS-133903", +) +def test_export_import_with_encryption(device): + core = Core() + + if props.device.Capability.EXPORT_IMPORT not in core.get_property(device, props.device.capabilities): + pytest.skip(f"{core.get_property(device, props.device.full_name)} plugin due-to export, import model API isn't implemented.") + + config = {} + config["CACHE_ENCRYPTION_CALLBACKS"] = [encrypt_base64, decrypt_base64] + + compiled_model = generate_relu_compiled_model_with_config(device, config) + + user_stream = compiled_model.export_model() + + new_compiled = core.import_model(user_stream, device, config) + + img = generate_image() + res = new_compiled.infer_new_request({"data": img}) + + assert np.argmax(res[new_compiled.outputs[0]]) == 531 + + def test_export_import_advanced(device): import io diff --git a/src/bindings/python/tests/utils/helpers.py b/src/bindings/python/tests/utils/helpers.py index ce21dbe8db08cf..098e968b64e241 100644 --- a/src/bindings/python/tests/utils/helpers.py +++ b/src/bindings/python/tests/utils/helpers.py @@ -7,6 +7,7 @@ import os import sys import numpy as np +import base64 from sys import platform from pathlib import Path @@ -196,6 +197,27 @@ def generate_relu_compiled_model( return core.compile_model(model, device, {}) +def encrypt_base64(src): + return base64.b64encode(bytes(src, "utf-8")) + + +def decrypt_base64(src): + return base64.b64decode(bytes(src, "utf-8")) + + +def generate_relu_compiled_model_with_config( + device, + config, + input_shape: List[int] = None, + input_dtype=np.float32, +) -> openvino.CompiledModel: + if input_shape is None: + input_shape = [1, 3, 32, 32] + model = get_relu_model(input_shape, input_dtype) + core = Core() + return core.compile_model(model, device, config) + + def generate_model_and_image(device, input_shape: List[int] = None): if input_shape is None: input_shape = [1, 3, 32, 32] diff --git a/src/inference/include/openvino/runtime/properties.hpp b/src/inference/include/openvino/runtime/properties.hpp index e0f7df1b16b0c2..878cba81590d98 100644 --- a/src/inference/include/openvino/runtime/properties.hpp +++ b/src/inference/include/openvino/runtime/properties.hpp @@ -784,6 +784,22 @@ inline std::istream& operator>>(std::istream& is, CacheMode& mode) { */ static constexpr Property cache_mode{"CACHE_MODE"}; +struct EncryptionCallbacks { + std::function encrypt; + std::function decrypt; +}; + +/** + * @brief Write-only property to set encryption/decryption function for saving/loading model cache. + * If cache_encryption_callbacks is set, the model cache will be encrypted/decrypted when saving/loading cache. + * cache_encryption_callbacks is enabled in core.compile_model only. + * - First value of the struct is encryption function. + * - Second value of the struct is decryption function. + * @ingroup ov_runtime_cpp_prop_api + */ +static constexpr Property cache_encryption_callbacks{ + "CACHE_ENCRYPTION_CALLBACKS"}; + /** * @brief Read-only property to provide information about a range for streams on platforms where streams are supported. * @ingroup ov_runtime_cpp_prop_api diff --git a/src/plugins/hetero/src/config.cpp b/src/plugins/hetero/src/config.cpp index a54a91fa19d900..12d3b8dee92e2a 100644 --- a/src/plugins/hetero/src/config.cpp +++ b/src/plugins/hetero/src/config.cpp @@ -33,6 +33,8 @@ Configuration::Configuration(const ov::AnyMap& config, const Configuration& defa } } modelDistributionPolicy = value.as>(); + } else if (ov::cache_encryption_callbacks == key) { + encryption_callbacks = value.as(); } else { if (throwOnUnsupported) OPENVINO_THROW("Property was not found: ", key); diff --git a/src/plugins/hetero/src/config.hpp b/src/plugins/hetero/src/config.hpp index 42d972c021343d..36d928a00535cf 100644 --- a/src/plugins/hetero/src/config.hpp +++ b/src/plugins/hetero/src/config.hpp @@ -38,6 +38,8 @@ struct Configuration { std::set modelDistributionPolicy = {}; + EncryptionCallbacks encryption_callbacks; + ov::AnyMap device_properties; }; } // namespace hetero diff --git a/src/plugins/intel_cpu/src/compiled_model.cpp b/src/plugins/intel_cpu/src/compiled_model.cpp index af8df9657ad34b..fb474e129cd3bb 100644 --- a/src/plugins/intel_cpu/src/compiled_model.cpp +++ b/src/plugins/intel_cpu/src/compiled_model.cpp @@ -338,7 +338,7 @@ ov::Any CompiledModel::get_property(const std::string& name) const { } void CompiledModel::export_model(std::ostream& modelStream) const { - ModelSerializer serializer(modelStream); + ModelSerializer serializer(modelStream, m_cfg.cacheEncrypt); serializer << m_model; } diff --git a/src/plugins/intel_cpu/src/config.cpp b/src/plugins/intel_cpu/src/config.cpp index 3a383a04c48a28..1aae0adf83bb47 100644 --- a/src/plugins/intel_cpu/src/config.cpp +++ b/src/plugins/intel_cpu/src/config.cpp @@ -371,6 +371,14 @@ void Config::readProperties(const ov::AnyMap& prop, const ModelType modelType) { ov::hint::kv_cache_precision.name(), ". Supported values: u8, bf16, f16, f32"); } + } else if (key == ov::cache_encryption_callbacks.name()) { + try { + auto encryption_callbacks = val.as(); + cacheEncrypt = encryption_callbacks.encrypt; + cacheDecrypt = encryption_callbacks.decrypt; + } catch (ov::Exception&) { + OPENVINO_THROW("Wrong value for property key ", ov::cache_encryption_callbacks.name()); + } } else { OPENVINO_THROW("NotFound: Unsupported property ", key, " by CPU plugin."); } diff --git a/src/plugins/intel_cpu/src/config.h b/src/plugins/intel_cpu/src/config.h index e0bcc6786a85b9..eeb8e78f5fa91a 100644 --- a/src/plugins/intel_cpu/src/config.h +++ b/src/plugins/intel_cpu/src/config.h @@ -105,6 +105,8 @@ struct Config { int modelPreferThreads = -1; ModelType modelType = ModelType::Unknown; + std::function cacheEncrypt; + std::function cacheDecrypt; #ifdef CPU_DEBUG_CAPS DebugCapsConfig debugCaps; diff --git a/src/plugins/intel_cpu/src/plugin.cpp b/src/plugins/intel_cpu/src/plugin.cpp index 276502a0f62ebd..0f38e47572771e 100644 --- a/src/plugins/intel_cpu/src/plugin.cpp +++ b/src/plugins/intel_cpu/src/plugin.cpp @@ -11,6 +11,7 @@ #include "openvino/runtime/properties.hpp" #include "openvino/runtime/threading/cpu_streams_info.hpp" #include "openvino/runtime/threading/executor_manager.hpp" +#include "openvino/util/codec_xor.hpp" #include "serialize.h" #include "transformations/transformation_pipeline.h" #include "transformations/utils/utils.hpp" @@ -292,6 +293,11 @@ std::shared_ptr Plugin::compile_model(const std::shared_ptr< conf.readProperties(config, modelType); calculate_streams(conf, cloned_model); + if (!conf.cacheEncrypt || !conf.cacheDecrypt) { + conf.cacheEncrypt = ov::util::codec_xor; + conf.cacheDecrypt = ov::util::codec_xor; + } + transformations.PostLpt(); transformations.Snippets(); @@ -589,9 +595,22 @@ ov::SupportedOpsMap Plugin::query_model(const std::shared_ptr& std::shared_ptr Plugin::import_model(std::istream& networkModel, const ov::AnyMap& config) const { OV_ITT_SCOPE(FIRST_INFERENCE, itt::domains::intel_cpu_LT, "import_model"); - ModelDeserializer deserializer(networkModel, [this](const std::string& model, const ov::Tensor& weights) { - return get_core()->read_model(model, weights, true); - }); + std::function decrypt; + if (config.count(ov::cache_encryption_callbacks.name())) { + auto encryption_callbacks = config.at(ov::cache_encryption_callbacks.name()).as(); + decrypt = encryption_callbacks.decrypt; + } + + if (!decrypt) { + decrypt = ov::util::codec_xor; + } + + ModelDeserializer deserializer( + networkModel, + [this](const std::string& model, const ov::Tensor& weights) { + return get_core()->read_model(model, weights, true); + }, + decrypt); std::shared_ptr model; deserializer >> model; diff --git a/src/plugins/intel_cpu/src/serialize.cpp b/src/plugins/intel_cpu/src/serialize.cpp index 266a89aa0b0cf3..511ef93e8b8944 100644 --- a/src/plugins/intel_cpu/src/serialize.cpp +++ b/src/plugins/intel_cpu/src/serialize.cpp @@ -25,8 +25,8 @@ static void setInfo(pugi::xml_node& root, std::shared_ptr& model) { } } -ModelSerializer::ModelSerializer(std::ostream& ostream) - : _ostream(ostream) {} +ModelSerializer::ModelSerializer(std::ostream& ostream, cache_encrypt encrypt_fn) + : _ostream(ostream), _cache_encrypt(encrypt_fn) {} void ModelSerializer::operator<<(const std::shared_ptr& model) { auto serializeInfo = [&](std::ostream& stream) { @@ -42,13 +42,14 @@ void ModelSerializer::operator<<(const std::shared_ptr& model) { xml_doc.save(stream); }; - ov::pass::StreamSerialize serializer(_ostream, serializeInfo, ov::util::codec_xor); + ov::pass::StreamSerialize serializer(_ostream, serializeInfo, _cache_encrypt); serializer.run_on_model(std::const_pointer_cast(model->clone())); } -ModelDeserializer::ModelDeserializer(std::istream & istream, model_builder fn) +ModelDeserializer::ModelDeserializer(std::istream & istream, model_builder fn, cache_decrypt decrypt_fn) : _istream(istream) - , _model_builder(fn) { + , _model_builder(fn) + , _cache_decrypt(decrypt_fn) { } void ModelDeserializer::operator>>(std::shared_ptr& model) { @@ -100,7 +101,9 @@ void ModelDeserializer::operator>>(std::shared_ptr& model) { _istream.seekg(hdr.model_offset); xmlString.resize(hdr.model_size); _istream.read(const_cast(xmlString.c_str()), hdr.model_size); - xmlString = ov::util::codec_xor(xmlString); + if (_cache_decrypt) { + xmlString = _cache_decrypt(xmlString); + } model = _model_builder(xmlString, std::move(dataBlob)); diff --git a/src/plugins/intel_cpu/src/serialize.h b/src/plugins/intel_cpu/src/serialize.h index b473ed521bbc1f..b364c428419c96 100644 --- a/src/plugins/intel_cpu/src/serialize.h +++ b/src/plugins/intel_cpu/src/serialize.h @@ -16,22 +16,26 @@ namespace intel_cpu { class ModelSerializer { public: - ModelSerializer(std::ostream& ostream); + typedef std::function cache_encrypt; + ModelSerializer(std::ostream& ostream, cache_encrypt encrypt_fn = {}); void operator<<(const std::shared_ptr& model); private: std::ostream& _ostream; + cache_encrypt _cache_encrypt; }; class ModelDeserializer { public: typedef std::function(const std::string&, const ov::Tensor&)> model_builder; - ModelDeserializer(std::istream& istream, model_builder fn); + typedef std::function cache_decrypt; + ModelDeserializer(std::istream& istream, model_builder fn, cache_decrypt decrypt_fn = {}); void operator>>(std::shared_ptr& model); private: std::istream& _istream; model_builder _model_builder; + cache_decrypt _cache_decrypt; }; } // namespace intel_cpu diff --git a/src/plugins/intel_cpu/tests/functional/shared_tests_instances/behavior/ov_plugin/caching_tests.cpp b/src/plugins/intel_cpu/tests/functional/shared_tests_instances/behavior/ov_plugin/caching_tests.cpp index e0ab9510a5b0a6..72c263917337a4 100644 --- a/src/plugins/intel_cpu/tests/functional/shared_tests_instances/behavior/ov_plugin/caching_tests.cpp +++ b/src/plugins/intel_cpu/tests/functional/shared_tests_instances/behavior/ov_plugin/caching_tests.cpp @@ -134,4 +134,8 @@ namespace { CompileModelLoadFromCacheTest, ::testing::Combine(::testing::ValuesIn(TestCpuTargets), ::testing::ValuesIn(CpuConfigs)), CompileModelLoadFromCacheTest::getTestCaseName); + INSTANTIATE_TEST_SUITE_P(smoke_CachingSupportCase_CPU, + CompileModelWithCacheEncryptionTest, + ::testing::ValuesIn(TestCpuTargets), + CompileModelWithCacheEncryptionTest::getTestCaseName); } // namespace diff --git a/src/plugins/intel_gpu/src/runtime/execution_config.cpp b/src/plugins/intel_gpu/src/runtime/execution_config.cpp index 93c6b8574c9e23..bb6e15707c39cd 100644 --- a/src/plugins/intel_gpu/src/runtime/execution_config.cpp +++ b/src/plugins/intel_gpu/src/runtime/execution_config.cpp @@ -57,6 +57,7 @@ void ExecutionConfig::set_default() { std::make_tuple(ov::internal::exclusive_async_requests, false), std::make_tuple(ov::internal::query_model_ratio, 1.0f), std::make_tuple(ov::cache_mode, ov::CacheMode::OPTIMIZE_SPEED), + std::make_tuple(ov::cache_encryption_callbacks, EncryptionCallbacks{}), std::make_tuple(ov::hint::dynamic_quantization_group_size, 0), std::make_tuple(ov::intel_gpu::hint::enable_kernels_reuse, false), diff --git a/src/plugins/intel_gpu/tests/functional/shared_tests_instances/behavior/ov_plugin/caching_tests.cpp b/src/plugins/intel_gpu/tests/functional/shared_tests_instances/behavior/ov_plugin/caching_tests.cpp index d22e4dd228ac9d..6fd9c69ee76376 100644 --- a/src/plugins/intel_gpu/tests/functional/shared_tests_instances/behavior/ov_plugin/caching_tests.cpp +++ b/src/plugins/intel_gpu/tests/functional/shared_tests_instances/behavior/ov_plugin/caching_tests.cpp @@ -78,4 +78,8 @@ INSTANTIATE_TEST_SUITE_P(smoke_CachingSupportCase_GPU, ::testing::Combine(::testing::Values(ov::test::utils::DEVICE_GPU), ::testing::ValuesIn(GPULoadFromFileConfigs)), CompileModelLoadFromCacheTest::getTestCaseName); +INSTANTIATE_TEST_SUITE_P(smoke_CachingSupportCase_GPU, + CompileModelWithCacheEncryptionTest, + ::testing::Values(ov::test::utils::DEVICE_GPU), + CompileModelWithCacheEncryptionTest::getTestCaseName); } // namespace diff --git a/src/plugins/intel_npu/src/plugin/src/plugin.cpp b/src/plugins/intel_npu/src/plugin/src/plugin.cpp index 7edbef2fddfcb2..e9c8aa2bab7255 100644 --- a/src/plugins/intel_npu/src/plugin/src/plugin.cpp +++ b/src/plugins/intel_npu/src/plugin/src/plugin.cpp @@ -119,6 +119,10 @@ void set_batch_config(bool isBatchingSupported, Config& config) { std::map any_copy(const ov::AnyMap& params) { std::map result; for (auto&& value : params) { + // The value of cache_encryption_callbacks cannot be converted to std::string + if (value.first == ov::cache_encryption_callbacks.name()) { + continue; + } result.emplace(value.first, value.second.as()); } return result; diff --git a/src/plugins/template/src/config.cpp b/src/plugins/template/src/config.cpp index d47e826e826283..f6541bad1325a3 100644 --- a/src/plugins/template/src/config.cpp +++ b/src/plugins/template/src/config.cpp @@ -83,6 +83,8 @@ Configuration::Configuration(const ov::AnyMap& config, const Configuration& defa log_level = value.as(); } else if (ov::hint::model_priority == key) { model_priority = value.as(); + } else if (ov::cache_encryption_callbacks == key) { + encryption_callbacks = value.as(); } else if (throwOnUnsupported) { OPENVINO_THROW("Property was not found: ", key); } diff --git a/src/plugins/template/src/config.hpp b/src/plugins/template/src/config.hpp index 429c2ef5b3613b..ce10a57f3f72c6 100644 --- a/src/plugins/template/src/config.hpp +++ b/src/plugins/template/src/config.hpp @@ -47,6 +47,8 @@ struct Configuration { ov::log::Level log_level = ov::log::Level::NO; ov::hint::Priority model_priority = ov::hint::Priority::DEFAULT; + + EncryptionCallbacks encryption_callbacks; }; // ! [configuration:header] diff --git a/src/plugins/template/tests/functional/shared_tests_instances/behavior/ov_plugin/caching_tests.cpp b/src/plugins/template/tests/functional/shared_tests_instances/behavior/ov_plugin/caching_tests.cpp index 373c389b478e00..5e1bd33b5a811b 100644 --- a/src/plugins/template/tests/functional/shared_tests_instances/behavior/ov_plugin/caching_tests.cpp +++ b/src/plugins/template/tests/functional/shared_tests_instances/behavior/ov_plugin/caching_tests.cpp @@ -39,4 +39,8 @@ INSTANTIATE_TEST_SUITE_P(smoke_CachingSupportCase_Template, ::testing::Combine(::testing::ValuesIn(TestTemplateTargets), ::testing::ValuesIn(TemplateConfigs)), CompileModelLoadFromCacheTest::getTestCaseName); +INSTANTIATE_TEST_SUITE_P(smoke_CachingSupportCase_Template, + CompileModelWithCacheEncryptionTest, + testing::ValuesIn(TestTemplateTargets), + CompileModelWithCacheEncryptionTest::getTestCaseName); } // namespace diff --git a/src/tests/functional/plugin/shared/include/behavior/ov_plugin/caching_tests.hpp b/src/tests/functional/plugin/shared/include/behavior/ov_plugin/caching_tests.hpp index 24ada36c80dcfc..f3b14b47b27a41 100644 --- a/src/tests/functional/plugin/shared/include/behavior/ov_plugin/caching_tests.hpp +++ b/src/tests/functional/plugin/shared/include/behavior/ov_plugin/caching_tests.hpp @@ -10,6 +10,7 @@ #include "shared_test_classes/base/ov_subgraph.hpp" #include "common_test_utils/unicode_utils.hpp" #include "openvino/util/common_util.hpp" +#include "openvino/util/codec_xor.hpp" #include "base/ov_behavior_test_utils.hpp" namespace ov { @@ -156,6 +157,20 @@ class CompiledKernelsCacheTest : virtual public SubgraphBaseTest, void SetUp() override; void TearDown() override; }; +class CompileModelWithCacheEncryptionTest : public testing::WithParamInterface, + virtual public SubgraphBaseTest, + virtual public OVPluginTestBase { + std::string m_cacheFolderName; + std::string m_modelName; + std::string m_weightsName; + +public: + static std::string getTestCaseName(testing::TestParamInfo obj); + + void SetUp() override; + void TearDown() override; + void run() override; +}; } // namespace behavior } // namespace test } // namespace ov diff --git a/src/tests/functional/plugin/shared/src/behavior/ov_plugin/caching_tests.cpp b/src/tests/functional/plugin/shared/src/behavior/ov_plugin/caching_tests.cpp index bac4a6661faf44..73cefa6cc4d8d1 100644 --- a/src/tests/functional/plugin/shared/src/behavior/ov_plugin/caching_tests.cpp +++ b/src/tests/functional/plugin/shared/src/behavior/ov_plugin/caching_tests.cpp @@ -886,6 +886,72 @@ TEST_P(CompiledKernelsCacheTest, CanCreateCacheDirAndDumpBinariesUnicodePath) { } } #endif + +std::string CompileModelWithCacheEncryptionTest::getTestCaseName( + testing::TestParamInfo obj) { + auto deviceName = obj.param; + std::ostringstream result; + std::replace(deviceName.begin(), deviceName.end(), ':', '.'); + result << "device_name=" << deviceName << "_"; + return result.str(); +} + +void CompileModelWithCacheEncryptionTest::SetUp() { + ovModelWithName funcPair; + targetDevice = GetParam(); + target_device = targetDevice; + EncryptionCallbacks encryption_callbacks; + encryption_callbacks.encrypt = ov::util::codec_xor; + encryption_callbacks.decrypt = ov::util::codec_xor; + configuration.insert(ov::cache_encryption_callbacks(encryption_callbacks)); + APIBaseTest::SetUp(); + std::stringstream ss; + std::string filePrefix = ov::test::utils::generateTestFilePrefix(); + ss << "testCache_" << filePrefix; + m_modelName = ss.str() + ".xml"; + m_weightsName = ss.str() + ".bin"; + m_cacheFolderName = ss.str(); + core->set_property(ov::cache_dir()); + ov::pass::Manager manager; + manager.register_pass(m_modelName, m_weightsName); + manager.run_passes(ov::test::utils::make_conv_pool_relu({1, 3, 227, 227}, ov::element::f32)); +} + +void CompileModelWithCacheEncryptionTest::TearDown() { + ov::test::utils::removeFilesWithExt(m_cacheFolderName, "blob"); + ov::test::utils::removeFilesWithExt(m_cacheFolderName, "cl_cache"); + ov::test::utils::removeIRFiles(m_modelName, m_weightsName); + std::remove(m_cacheFolderName.c_str()); + core->set_property(ov::cache_dir()); + ov::test::utils::PluginCache::get().reset(); + APIBaseTest::TearDown(); +} + +void CompileModelWithCacheEncryptionTest::run() { + SKIP_IF_CURRENT_TEST_IS_DISABLED(); + core->set_property(ov::cache_dir(m_cacheFolderName)); + try { + compiledModel = core->compile_model(m_modelName, targetDevice, configuration); + EXPECT_EQ(false, compiledModel.get_property(ov::loaded_from_cache.name()).as()); + + std::stringstream strm; + compiledModel.export_model(strm); + ov::CompiledModel importedCompiledModel = core->import_model(strm, target_device, configuration); + EXPECT_EQ(false, importedCompiledModel.get_property(ov::loaded_from_cache.name()).as()); + + compiledModel = core->compile_model(m_modelName, targetDevice, configuration); + EXPECT_EQ(true, compiledModel.get_property(ov::loaded_from_cache.name()).as()); + } catch (const Exception& ex) { + GTEST_FAIL() << "Can't compile network from cache dir " << m_cacheFolderName << + "\nException [" << ex.what() << "]" << std::endl; + } catch (...) { + GTEST_FAIL() << "Can't compile network with model path " << m_modelName << std::endl; + } +} + +TEST_P(CompileModelWithCacheEncryptionTest, CanImportModelWithoutException) { + run(); +} } // namespace behavior } // namespace test } // namespace ov