From 665c87317e452f08fcb4923e1274abeaf978d220 Mon Sep 17 00:00:00 2001 From: Piero Viscone Date: Thu, 12 Sep 2024 21:59:11 +0200 Subject: [PATCH 01/12] autoload bridge shared library for the cpp backend --- conifer/model.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/conifer/model.py b/conifer/model.py index 2812fe6..7323927 100644 --- a/conifer/model.py +++ b/conifer/model.py @@ -532,7 +532,7 @@ def make_model(ensembleDict, config=None): backend = get_backend(backend) return backend.make_model(ensembleDict, config) -def load_model(filename, new_config=None): +def load_model(filename, shared_library=None, new_config=None): ''' Load a Model from JSON file @@ -561,4 +561,17 @@ def load_model(filename, new_config=None): model = make_model(js, config) model._metadata = metadata + model._metadata + + try: + if config["backend"] == "cpp": + import importlib + last_timestamp=int(model._metadata[-2]._to_dict()["time"]) + #look for the shared library in the same directory as the model json if not specified + shared_library_path=os.path.join(os.path.dirname(filename), f'conifer_bridge_{last_timestamp}.so') if shared_library is None else shared_library + spec = importlib.util.spec_from_file_location(f'conifer_bridge_{last_timestamp}', shared_library_path) + model.bridge = importlib.util.module_from_spec(spec).BDT(filename) + spec.loader.exec_module(model.bridge) + except Exception as e: + logger.warn("Was not able to load the shared library. Run model.compile(): ", e) + return model From fe5f9b358ba278f24ce1b947e16c9f912ebcfec4 Mon Sep 17 00:00:00 2001 From: Piero Viscone Date: Thu, 12 Sep 2024 22:05:29 +0200 Subject: [PATCH 02/12] Added same behavior for xilinxhls --- conifer/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conifer/model.py b/conifer/model.py index 7323927..83f2e61 100644 --- a/conifer/model.py +++ b/conifer/model.py @@ -563,7 +563,7 @@ def load_model(filename, shared_library=None, new_config=None): model._metadata = metadata + model._metadata try: - if config["backend"] == "cpp": + if config["backend"] in ["cpp","xilinxhls"]: import importlib last_timestamp=int(model._metadata[-2]._to_dict()["time"]) #look for the shared library in the same directory as the model json if not specified From 022be2b0e80b13d236b71580159a1c28c7345933 Mon Sep 17 00:00:00 2001 From: Piero Viscone Date: Fri, 13 Sep 2024 16:56:50 +0200 Subject: [PATCH 03/12] swapped arguments --- conifer/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conifer/model.py b/conifer/model.py index 83f2e61..d079624 100644 --- a/conifer/model.py +++ b/conifer/model.py @@ -532,7 +532,7 @@ def make_model(ensembleDict, config=None): backend = get_backend(backend) return backend.make_model(ensembleDict, config) -def load_model(filename, shared_library=None, new_config=None): +def load_model(filename, new_config=None, shared_library=None): ''' Load a Model from JSON file From d1e8490102e50f8596a0d1952364acb707d0b833 Mon Sep 17 00:00:00 2001 From: Piero Viscone Date: Fri, 13 Sep 2024 16:57:37 +0200 Subject: [PATCH 04/12] added shared_l;ibrary docstring --- conifer/model.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conifer/model.py b/conifer/model.py index d079624..1bbc7e6 100644 --- a/conifer/model.py +++ b/conifer/model.py @@ -542,6 +542,8 @@ def load_model(filename, new_config=None, shared_library=None): filename to load from new_config: dictionary (optional) if provided, override the configuration specified in the JSON file + shared_library: string (optional) + path to the shared library to load for the model. If not provided, the shared library will be looked for in the same directory as the JSON file, using the timestamp of the last metadata entry available ''' with open(filename, 'r') as json_file: js = json.load(json_file) From 9298f9289b3002305012ab8e764a1f6d4707356f Mon Sep 17 00:00:00 2001 From: Piero Viscone Date: Fri, 13 Sep 2024 16:58:36 +0200 Subject: [PATCH 05/12] try .so loading just in a new config is not provided --- conifer/model.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/conifer/model.py b/conifer/model.py index 1bbc7e6..6210e22 100644 --- a/conifer/model.py +++ b/conifer/model.py @@ -564,16 +564,17 @@ def load_model(filename, new_config=None, shared_library=None): model = make_model(js, config) model._metadata = metadata + model._metadata - try: - if config["backend"] in ["cpp","xilinxhls"]: - import importlib - last_timestamp=int(model._metadata[-2]._to_dict()["time"]) - #look for the shared library in the same directory as the model json if not specified - shared_library_path=os.path.join(os.path.dirname(filename), f'conifer_bridge_{last_timestamp}.so') if shared_library is None else shared_library - spec = importlib.util.spec_from_file_location(f'conifer_bridge_{last_timestamp}', shared_library_path) - model.bridge = importlib.util.module_from_spec(spec).BDT(filename) - spec.loader.exec_module(model.bridge) - except Exception as e: - logger.warn("Was not able to load the shared library. Run model.compile(): ", e) + if new_config is None: + try: + if config["backend"] in ["cpp","xilinxhls"]: + import importlib + last_timestamp=int(model._metadata[-2]._to_dict()["time"]) + #look for the shared library in the same directory as the model json if not specified + shared_library_path=os.path.join(os.path.dirname(filename), f'conifer_bridge_{last_timestamp}.so') if shared_library is None else shared_library + spec = importlib.util.spec_from_file_location(f'conifer_bridge_{last_timestamp}', shared_library_path) + model.bridge = importlib.util.module_from_spec(spec).BDT(filename) + spec.loader.exec_module(model.bridge) + except Exception as e: + print("An existing shared library was either not found or could not be loaded. Run model.compile(): ", e) return model From 1bf4bff17507f7f07251bfbde7bf97015901356b Mon Sep 17 00:00:00 2001 From: Piero Viscone Date: Fri, 13 Sep 2024 17:13:34 +0200 Subject: [PATCH 06/12] moved .so loading to the backends --- conifer/backends/cpp/writer.py | 11 +++++++---- conifer/backends/xilinxhls/writer.py | 11 +++++++---- conifer/model.py | 15 +++++++-------- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/conifer/backends/cpp/writer.py b/conifer/backends/cpp/writer.py index df6cdc5..3f7e1b3 100644 --- a/conifer/backends/cpp/writer.py +++ b/conifer/backends/cpp/writer.py @@ -24,6 +24,12 @@ class CPPModel(ModelBase): def __init__(self, ensembleDict, config, metadata=None): super(CPPModel, self).__init__(ensembleDict, config, metadata) self.config = CPPConfig(config) + + def load_shared_library(self, model_json, shared_library): + import importlib + spec = importlib.util.spec_from_file_location(os.path.basename(shared_library).split(".so")[0], shared_library) + self.bridge = importlib.util.module_from_spec(spec).BDT(model_json) + spec.loader.exec_module(self.bridge) @copydocstring(ModelBase.write) def write(self): @@ -99,10 +105,7 @@ def compile(self): try: logger.debug(f'Importing conifer_bridge_{self._stamp} from conifer_bridge_{self._stamp}.so') - import importlib.util - spec = importlib.util.spec_from_file_location(f'conifer_bridge_{self._stamp}', f'./conifer_bridge_{self._stamp}.so') - self.bridge = importlib.util.module_from_spec(spec).BDT(f"{cfg.project_name}.json") - spec.loader.exec_module(self.bridge) + self.load_shared_library(f"{cfg.project_name}.json", f"./conifer_bridge_{self._stamp}.so") except ImportError: os.chdir(curr_dir) raise Exception("Can't import pybind11 bridge, is it compiled?") diff --git a/conifer/backends/xilinxhls/writer.py b/conifer/backends/xilinxhls/writer.py index e9b28f4..290bbdc 100644 --- a/conifer/backends/xilinxhls/writer.py +++ b/conifer/backends/xilinxhls/writer.py @@ -512,6 +512,12 @@ def decision_function(self, X, trees=False): y = y.reshape(y.shape[0]) return y + def load_shared_library(self, model_json, shared_library): + import importlib + spec = importlib.util.spec_from_file_location(os.path.basename(shared_library).split(".so")[0], shared_library) + self.bridge = importlib.util.module_from_spec(spec).BDT(model_json) + spec.loader.exec_module(self.bridge) + @copydocstring(ModelBase.compile) def compile(self): self.write() @@ -534,10 +540,7 @@ def compile(self): try: logger.debug(f'Importing conifer_bridge_{self._stamp} from conifer_bridge_{self._stamp}.so') - import importlib.util - spec = importlib.util.spec_from_file_location(f'conifer_bridge_{self._stamp}', f'./conifer_bridge_{self._stamp}.so') - self.bridge = importlib.util.module_from_spec(spec) - spec.loader.exec_module(self.bridge) + self.load_shared_library(f"{cfg.project_name}.json", f"./conifer_bridge_{self._stamp}.so") except ImportError: os.chdir(curr_dir) raise Exception("Can't import pybind11 bridge, is it compiled?") diff --git a/conifer/model.py b/conifer/model.py index 6210e22..93e06f2 100644 --- a/conifer/model.py +++ b/conifer/model.py @@ -532,6 +532,9 @@ def make_model(ensembleDict, config=None): backend = get_backend(backend) return backend.make_model(ensembleDict, config) +def load_shared_library(self, model_json, shared_library): + pass + def load_model(filename, new_config=None, shared_library=None): ''' Load a Model from JSON file @@ -566,14 +569,10 @@ def load_model(filename, new_config=None, shared_library=None): if new_config is None: try: - if config["backend"] in ["cpp","xilinxhls"]: - import importlib - last_timestamp=int(model._metadata[-2]._to_dict()["time"]) - #look for the shared library in the same directory as the model json if not specified - shared_library_path=os.path.join(os.path.dirname(filename), f'conifer_bridge_{last_timestamp}.so') if shared_library is None else shared_library - spec = importlib.util.spec_from_file_location(f'conifer_bridge_{last_timestamp}', shared_library_path) - model.bridge = importlib.util.module_from_spec(spec).BDT(filename) - spec.loader.exec_module(model.bridge) + last_timestamp=int(model._metadata[-2]._to_dict()["time"]) + #look for the shared library in the same directory as the model json if not specified + shared_library_path=os.path.join(os.path.dirname(filename), f'conifer_bridge_{last_timestamp}.so') if shared_library is None else shared_library + model.load_shared_library(filename, shared_library_path) except Exception as e: print("An existing shared library was either not found or could not be loaded. Run model.compile(): ", e) From 9e843af391f87b0c32bc80453d91d48b7efd8c90 Mon Sep 17 00:00:00 2001 From: Piero Viscone Date: Fri, 13 Sep 2024 17:44:41 +0200 Subject: [PATCH 07/12] fix: moved load_shared_library to the right class --- conifer/model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/conifer/model.py b/conifer/model.py index 93e06f2..61dd92e 100644 --- a/conifer/model.py +++ b/conifer/model.py @@ -495,6 +495,9 @@ def _profile(self, what : Literal["scores", "thresholds"], ax=None): return ax + def load_shared_library(self, model_json, shared_library): + pass + class ModelMetaData: def __init__(self): self.version = version @@ -532,9 +535,6 @@ def make_model(ensembleDict, config=None): backend = get_backend(backend) return backend.make_model(ensembleDict, config) -def load_shared_library(self, model_json, shared_library): - pass - def load_model(filename, new_config=None, shared_library=None): ''' Load a Model from JSON file From 0bf9206270ef3070ea482a40872baa561e3b72b0 Mon Sep 17 00:00:00 2001 From: Piero Viscone Date: Fri, 13 Sep 2024 17:45:17 +0200 Subject: [PATCH 08/12] look for all the timestamps from the last to the first --- conifer/model.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/conifer/model.py b/conifer/model.py index 61dd92e..3118fd0 100644 --- a/conifer/model.py +++ b/conifer/model.py @@ -546,7 +546,8 @@ def load_model(filename, new_config=None, shared_library=None): new_config: dictionary (optional) if provided, override the configuration specified in the JSON file shared_library: string (optional) - path to the shared library to load for the model. If not provided, the shared library will be looked for in the same directory as the JSON file, using the timestamp of the last metadata entry available + path to the shared library (or to the directory where to look for the .so file) to load for the model. + If not provided, the shared library will be looked for in the same directory as the JSON file, using the timestamp of the last metadata entry available ''' with open(filename, 'r') as json_file: js = json.load(json_file) @@ -568,12 +569,23 @@ def load_model(filename, new_config=None, shared_library=None): model._metadata = metadata + model._metadata if new_config is None: + shared_library_path=None + if shared_library is None or not shared_library.endswith(".so"): + from glob import glob + shared_library_dirpath=os.path.abspath(os.path.dirname(filename)) if shared_library is None else shared_library + timestamps=[int(md._to_dict()["time"]) for md in model._metadata[-2::-1]] + so_files=glob(os.path.join(shared_library_dirpath, 'conifer_bridge_*.so')) + so_files=[os.path.basename(so_file) for so_file in so_files] + for timestamp in timestamps: + if f"conifer_bridge_{timestamp}.so" in so_files: + shared_library_path=os.path.join(shared_library_dirpath, f'conifer_bridge_{timestamp}.so') + break + elif shared_library.endswith(".so"): + shared_library_path=shared_library + try: - last_timestamp=int(model._metadata[-2]._to_dict()["time"]) - #look for the shared library in the same directory as the model json if not specified - shared_library_path=os.path.join(os.path.dirname(filename), f'conifer_bridge_{last_timestamp}.so') if shared_library is None else shared_library model.load_shared_library(filename, shared_library_path) - except Exception as e: - print("An existing shared library was either not found or could not be loaded. Run model.compile(): ", e) + except Exception: + print("An existing shared library was either not found or could not be loaded. Run model.compile()") return model From 8c961dd084896374cd011022dd845f9943a03bfc Mon Sep 17 00:00:00 2001 From: Piero Viscone Date: Fri, 13 Sep 2024 18:47:20 +0200 Subject: [PATCH 09/12] fixed xilinxhls backend --- conifer/backends/xilinxhls/writer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conifer/backends/xilinxhls/writer.py b/conifer/backends/xilinxhls/writer.py index 290bbdc..9b18897 100644 --- a/conifer/backends/xilinxhls/writer.py +++ b/conifer/backends/xilinxhls/writer.py @@ -515,7 +515,7 @@ def decision_function(self, X, trees=False): def load_shared_library(self, model_json, shared_library): import importlib spec = importlib.util.spec_from_file_location(os.path.basename(shared_library).split(".so")[0], shared_library) - self.bridge = importlib.util.module_from_spec(spec).BDT(model_json) + self.bridge = importlib.util.module_from_spec(spec) spec.loader.exec_module(self.bridge) @copydocstring(ModelBase.compile) From 9aaf2991748e96a17c79d535d20829d1e7689f03 Mon Sep 17 00:00:00 2001 From: Piero Viscone Date: Fri, 13 Sep 2024 18:55:20 +0200 Subject: [PATCH 10/12] Added test --- tests/test_save_load.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_save_load.py b/tests/test_save_load.py index c617ec8..e6c1ac4 100644 --- a/tests/test_save_load.py +++ b/tests/test_save_load.py @@ -15,6 +15,10 @@ def test_hls_save_load(hls_convert, train_skl): load_model.compile() y_hls_0, y_hls_1 = util.predict_skl(orig_model, X, y, load_model) np.testing.assert_array_equal(y_hls_0, y_hls_1) + # Re-load without recompiling to check if the shared library is loaded correctly + load_model2 = conifer.model.load_model(f'{orig_model.config.output_dir}/{orig_model.config.project_name}.json') + _, y_hls_2 = util.predict_skl(orig_model, X, y, load_model2) + np.testing.assert_array_equal(y_hls_0, y_hls_2) def test_hdl_save_load(vhdl_convert, train_skl): orig_model = vhdl_convert From 7118a0ab1ad81cfdfef5367e82f2e71d58ab9524 Mon Sep 17 00:00:00 2001 From: Piero Viscone Date: Fri, 13 Sep 2024 23:15:32 +0200 Subject: [PATCH 11/12] shared_library bool or str --- conifer/model.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/conifer/model.py b/conifer/model.py index 3118fd0..dae2ff9 100644 --- a/conifer/model.py +++ b/conifer/model.py @@ -535,7 +535,7 @@ def make_model(ensembleDict, config=None): backend = get_backend(backend) return backend.make_model(ensembleDict, config) -def load_model(filename, new_config=None, shared_library=None): +def load_model(filename, new_config=None, shared_library=True): ''' Load a Model from JSON file @@ -545,9 +545,14 @@ def load_model(filename, new_config=None, shared_library=None): filename to load from new_config: dictionary (optional) if provided, override the configuration specified in the JSON file - shared_library: string (optional) - path to the shared library (or to the directory where to look for the .so file) to load for the model. - If not provided, the shared library will be looked for in the same directory as the JSON file, using the timestamp of the last metadata entry available + shared_library: string|bool (optional) + If True, the shared library will be looked for in the same directory as the JSON file, using the timestamp of the last metadata entry available + If False, the shared library will not be loaded + If a string, it could be: + - path to the shared library to load for the model + - path to the directory where to look for the .so file, using the timestamp of the last metadata entry available + + No shared library will be loaded if a new configuration is provided ''' with open(filename, 'r') as json_file: js = json.load(json_file) @@ -568,9 +573,11 @@ def load_model(filename, new_config=None, shared_library=None): model = make_model(js, config) model._metadata = metadata + model._metadata - if new_config is None: + if new_config is None and shared_library is not False: shared_library_path=None - if shared_library is None or not shared_library.endswith(".so"): + if shared_library.endswith(".so"): + shared_library_path=shared_library + else: from glob import glob shared_library_dirpath=os.path.abspath(os.path.dirname(filename)) if shared_library is None else shared_library timestamps=[int(md._to_dict()["time"]) for md in model._metadata[-2::-1]] @@ -580,8 +587,6 @@ def load_model(filename, new_config=None, shared_library=None): if f"conifer_bridge_{timestamp}.so" in so_files: shared_library_path=os.path.join(shared_library_dirpath, f'conifer_bridge_{timestamp}.so') break - elif shared_library.endswith(".so"): - shared_library_path=shared_library try: model.load_shared_library(filename, shared_library_path) From c00753db1ba598e120d3cd65478a2ed4e3e7091d Mon Sep 17 00:00:00 2001 From: Piero Viscone Date: Fri, 13 Sep 2024 23:22:03 +0200 Subject: [PATCH 12/12] fix: type check + None -> True --- conifer/model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/conifer/model.py b/conifer/model.py index dae2ff9..4b16c0a 100644 --- a/conifer/model.py +++ b/conifer/model.py @@ -549,7 +549,7 @@ def load_model(filename, new_config=None, shared_library=True): If True, the shared library will be looked for in the same directory as the JSON file, using the timestamp of the last metadata entry available If False, the shared library will not be loaded If a string, it could be: - - path to the shared library to load for the model + - path to the shared library to load - path to the directory where to look for the .so file, using the timestamp of the last metadata entry available No shared library will be loaded if a new configuration is provided @@ -575,11 +575,11 @@ def load_model(filename, new_config=None, shared_library=True): if new_config is None and shared_library is not False: shared_library_path=None - if shared_library.endswith(".so"): + if isinstance(shared_library, str) and shared_library.endswith(".so"): shared_library_path=shared_library else: from glob import glob - shared_library_dirpath=os.path.abspath(os.path.dirname(filename)) if shared_library is None else shared_library + shared_library_dirpath=os.path.abspath(os.path.dirname(filename)) if shared_library is True else os.path.abspath(shared_library) timestamps=[int(md._to_dict()["time"]) for md in model._metadata[-2::-1]] so_files=glob(os.path.join(shared_library_dirpath, 'conifer_bridge_*.so')) so_files=[os.path.basename(so_file) for so_file in so_files]