We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Model saving is calling on `git` which causes an issue when the `subprocess` library is from the system
**Describe the bug**
When the `subprocess` library is called from the system installation of python, `git` invocation does not work, since there is no git repo for the system python installation. 

**To Reproduce**
Steps to reproduce the behavior:
1. Start GaNDLF training
2. See the following error:
```bash
$ > cat ${error_log}
[SNIP!]
  File "/geode2/home/u070/patis/BigRed200/projects/gandlf_mine/GANDLF/compute/training_loop.py", line 290, in training_loop
    save_model(
  File "/geode2/home/u070/patis/BigRed200/projects/gandlf_mine/GANDLF/utils/modelio.py", line 159, in save_model
    subprocess.check_output(["git", "rev-parse", "HEAD"])
  File "/N/soft/sles15/deeplearning/Python-3.10.10/Lib/subprocess.py", line 421, in check_output
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
  File "/N/soft/sles15/deeplearning/Python-3.10.10/Lib/subprocess.py", line 503, in run
    with Popen(*popenargs, **kwargs) as process:
  File "/N/soft/sles15/deeplearning/Python-3.10.10/Lib/subprocess.py", line 971, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/N/soft/sles15/deeplearning/Python-3.10.10/Lib/subprocess.py", line 1847, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
```

**Expected behavior**
The model saving should work.

**Screenshots**
N.A.

**GaNDLF Version**
<!-- Put the output of the following command:
python -c 'import GANDLF as g;print(g.__version__)'
-->
0.0.17-dev

**Desktop (please complete the following information):**
N.A.

**Additional context**
This is seen for the IU BigRed cluster. 

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `GANDLF/utils/modelio.py`
Content:
```
1 import hashlib
2 import os
3 import subprocess
4 from typing import Any, Dict
5 
6 import torch
7 
8 from ..version import __version__
9 from .generic import get_unique_timestamp
10 
11 # these are the base keys for the model dictionary to save
12 model_dict_full = {
13     "epoch": 0,
14     "model_state_dict": None,
15     "optimizer_state_dict": None,
16     "loss": None,
17     "timestamp": None,
18     "timestamp_hash": None,
19     "git_hash": None,
20     "version": None,
21 }
22 
23 model_dict_required = {
24     "model_state_dict": None,
25     "optimizer_state_dict": None,
26 }
27 
28 best_model_path_end = "_best.pth.tar"
29 latest_model_path_end = "_latest.pth.tar"
30 initial_model_path_end = "_initial.pth.tar"
31 
32 
33 def optimize_and_save_model(model, params, path, onnx_export=True):
34     """
35     Perform post-training optimization and save it to a file.
36 
37     Args:
38         model (torch.nn.Module): Trained torch model.
39         params (dict): The parameter dictionary.
40         path (str): The path to save the model dictionary to.
41         onnx_export (bool): Whether to export to ONNX and OpenVINO.
42     """
43     # Check if ONNX export is enabled in the parameter dictionary
44     onnx_export = params["model"].get("onnx_export", onnx_export)
45 
46     # Check for incompatible topologies and disable ONNX export
47     # Customized imagenet_vgg no longer supported for ONNX export
48     if onnx_export:
49         architecture = params["model"]["architecture"]
50         if architecture in ["sdnet", "brain_age"] or "imagenet_vgg" in architecture:
51             onnx_export = False
52 
53     if not onnx_export:
54         # Print a warning if ONNX export is disabled and not already warned
55         if "onnx_print" not in params:
56             print("WARNING: Current model is not supported by ONNX/OpenVINO!")
57             params["onnx_print"] = True
58         return
59     else:
60         try:
61             print("Optimizing the best model.")
62             num_channel = params["model"]["num_channels"]
63             model_dimension = params["model"]["dimension"]
64             input_shape = params["patch_size"]
65             onnx_path = path
66             if not onnx_path.endswith(".onnx"):
67                 onnx_path = onnx_path.replace("pth.tar", "onnx")
68 
69             if model_dimension == 2:
70                 dummy_input = torch.randn(
71                     (1, num_channel, input_shape[0], input_shape[1])
72                 )
73             else:
74                 dummy_input = torch.randn(
75                     (1, num_channel, input_shape[0], input_shape[1], input_shape[2])
76                 )
77 
78             # Export the model to ONNX format
79             with torch.no_grad():
80                 torch.onnx.export(
81                     model.to("cpu"),
82                     dummy_input.to("cpu"),
83                     onnx_path,
84                     opset_version=11,
85                     export_params=True,
86                     verbose=True,
87                     input_names=["input"],
88                     output_names=["output"],
89                 )
90         except RuntimeWarning:
91             print("WARNING: Cannot export to ONNX model.")
92             return
93 
94         # Check if OpenVINO is present and try to convert the ONNX model
95         openvino_present = False
96         try:
97             import openvino as ov
98             from openvino.tools.mo import convert_model
99             from openvino.runtime import get_version
100 
101             openvino_present = False
102             # check for the correct openvino version to prevent inadvertent api breaks
103             if "2023.0.1" in get_version():
104                 openvino_present = True
105         except ImportError:
106             print("WARNING: OpenVINO is not present.")
107 
108         if openvino_present:
109             xml_path = onnx_path.replace("onnx", "xml")
110             bin_path = onnx_path.replace("onnx", "bin")
111             try:
112                 if model_dimension == 2:
113                     ov_model = convert_model(
114                         onnx_path,
115                         input_shape=(1, num_channel, input_shape[0], input_shape[1]),
116                     )
117                 else:
118                     ov_model = convert_model(
119                         onnx_path,
120                         input_shape=(
121                             1,
122                             num_channel,
123                             input_shape[0],
124                             input_shape[1],
125                             input_shape[2],
126                         ),
127                     )
128                 ov.runtime.serialize(ov_model, xml_path=xml_path, bin_path=bin_path)
129             except Exception as e:
130                 print("WARNING: OpenVINO Model Optimizer IR conversion failed: " + e)
131 
132 
133 def save_model(
134     model_dict: Dict[str, Any],
135     model: torch.nn.Module,
136     params: Dict[str, Any],
137     path: str,
138     onnx_export: bool = True,
139 ):
140     """
141     Save the model dictionary to a file.
142 
143     Args:
144         model_dict (dict): Model dictionary to save.
145         model (torch.nn.Module): Trained torch model.
146         params (dict): The parameter dictionary.
147         path (str): The path to save the model dictionary to.
148         onnx_export (bool): Whether to export to ONNX and OpenVINO.
149     """
150     model_dict["timestamp"] = get_unique_timestamp()
151     model_dict["timestamp_hash"] = hashlib.sha256(
152         str(model_dict["timestamp"]).encode("utf-8")
153     ).hexdigest()
154     model_dict["version"] = __version__
155     model_dict["parameters"] = params
156 
157     try:
158         model_dict["git_hash"] = (
159             subprocess.check_output(["git", "rev-parse", "HEAD"])
160             .decode("ascii")
161             .strip()
162         )
163     except subprocess.CalledProcessError:
164         model_dict["git_hash"] = None
165 
166     torch.save(model_dict, path)
167 
168     # post-training optimization
169     optimize_and_save_model(model, params, path, onnx_export=onnx_export)
170 
171 
172 def load_model(
173     path: str, device: torch.device, full_sanity_check: bool = True
174 ) -> Dict[str, Any]:
175     """
176     Load a model dictionary from a file.
177 
178     Args:
179         path (str): The path to save the model dictionary to.
180         device (torch.device): The device to run the model on.
181         full_sanity_check (bool): Whether to run full sanity checking on the model.
182 
183     Returns:
184         dict: Model dictionary containing model parameters and metadata.
185     """
186     model_dict = torch.load(path, map_location=device)
187 
188     # check if the model dictionary is complete
189     if full_sanity_check:
190         incomplete_keys = [
191             key for key in model_dict_full.keys() if key not in model_dict.keys()
192         ]
193         if len(incomplete_keys) > 0:
194             raise RuntimeWarning(
195                 "Model dictionary is incomplete; the following keys are missing:",
196                 incomplete_keys,
197             )
198 
199     # check if required keys are absent, and if so raise an error
200     incomplete_required_keys = [
201         key for key in model_dict_required.keys() if key not in model_dict.keys()
202     ]
203     if len(incomplete_required_keys) > 0:
204         raise KeyError(
205             "Model dictionary is incomplete; the following keys are missing:",
206             incomplete_required_keys,
207         )
208 
209     return model_dict
210 
211 
212 def load_ov_model(path: str, device: str = "CPU"):
213     """
214     Load an OpenVINO IR model from an .xml file.
215 
216     Args:
217         path (str): The path to the OpenVINO .xml file.
218         device (str): The device to run inference, can be "CPU", "GPU" or "MULTI:CPU,GPU". Default to be "CPU".
219 
220     Returns:
221         exec_net (OpenVINO executable net): executable OpenVINO model.
222         input_blob (str): Input name.
223         output_blob (str): Output name.
224     """
225 
226     try:
227         from openvino import runtime as ov
228     except ImportError:
229         raise ImportError("OpenVINO inference engine is not configured correctly.")
230 
231     core = ov.Core()
232     if device.lower() == "cuda":
233         device = "GPU"
234 
235     if device == "GPU":
236         core.set_property({"CACHE_DIR": os.path.dirname(os.path.abspath(path))})
237 
238     model = core.read_model(model=path, weights=path.replace("xml", "bin"))
239     compiled_model = core.compile_model(model=model, device_name=device.upper())
240     input_layer = compiled_model.inputs
241     output_layer = compiled_model.outputs
242 
243     return compiled_model, input_layer, output_layer
244
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/GANDLF/utils/modelio.py b/GANDLF/utils/modelio.py
--- a/GANDLF/utils/modelio.py
+++ b/GANDLF/utils/modelio.py
@@ -155,13 +155,14 @@
     model_dict["parameters"] = params
 
     try:
+        # this will try to encode the git hash of the current GaNDLF codebase, and reverts to "None" if not found
         model_dict["git_hash"] = (
-            subprocess.check_output(["git", "rev-parse", "HEAD"])
+            subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=os.getcwd())
             .decode("ascii")
             .strip()
         )
-    except subprocess.CalledProcessError:
-        model_dict["git_hash"] = None
+    except (subprocess.CalledProcessError, FileNotFoundError):
+        model_dict["git_hash"] = "None"
 
     torch.save(model_dict, path)

{"golden_diff": "diff --git a/GANDLF/utils/modelio.py b/GANDLF/utils/modelio.py\n--- a/GANDLF/utils/modelio.py\n+++ b/GANDLF/utils/modelio.py\n@@ -155,13 +155,14 @@\n     model_dict[\"parameters\"] = params\n \n     try:\n+        # this will try to encode the git hash of the current GaNDLF codebase, and reverts to \"None\" if not found\n         model_dict[\"git_hash\"] = (\n-            subprocess.check_output([\"git\", \"rev-parse\", \"HEAD\"])\n+            subprocess.check_output([\"git\", \"rev-parse\", \"HEAD\"], cwd=os.getcwd())\n             .decode(\"ascii\")\n             .strip()\n         )\n-    except subprocess.CalledProcessError:\n-        model_dict[\"git_hash\"] = None\n+    except (subprocess.CalledProcessError, FileNotFoundError):\n+        model_dict[\"git_hash\"] = \"None\"\n \n     torch.save(model_dict, path)\n", "issue": "Model saving is calling on `git` which causes an issue when the `subprocess` library is from the system\n**Describe the bug**\r\nWhen the `subprocess` library is called from the system installation of python, `git` invocation does not work, since there is no git repo for the system python installation. \r\n\r\n**To Reproduce**\r\nSteps to reproduce the behavior:\r\n1. Start GaNDLF training\r\n2. See the following error:\r\n```bash\r\n$ > cat ${error_log}\r\n[SNIP!]\r\n  File \"/geode2/home/u070/patis/BigRed200/projects/gandlf_mine/GANDLF/compute/training_loop.py\", line 290, in training_loop\r\n    save_model(\r\n  File \"/geode2/home/u070/patis/BigRed200/projects/gandlf_mine/GANDLF/utils/modelio.py\", line 159, in save_model\r\n    subprocess.check_output([\"git\", \"rev-parse\", \"HEAD\"])\r\n  File \"/N/soft/sles15/deeplearning/Python-3.10.10/Lib/subprocess.py\", line 421, in check_output\r\n    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,\r\n  File \"/N/soft/sles15/deeplearning/Python-3.10.10/Lib/subprocess.py\", line 503, in run\r\n    with Popen(*popenargs, **kwargs) as process:\r\n  File \"/N/soft/sles15/deeplearning/Python-3.10.10/Lib/subprocess.py\", line 971, in __init__\r\n    self._execute_child(args, executable, preexec_fn, close_fds,\r\n  File \"/N/soft/sles15/deeplearning/Python-3.10.10/Lib/subprocess.py\", line 1847, in _execute_child\r\n    raise child_exception_type(errno_num, err_msg, err_filename)\r\n```\r\n\r\n**Expected behavior**\r\nThe model saving should work.\r\n\r\n**Screenshots**\r\nN.A.\r\n\r\n**GaNDLF Version**\r\n<!-- Put the output of the following command:\r\npython -c 'import GANDLF as g;print(g.__version__)'\r\n-->\r\n0.0.17-dev\r\n\r\n**Desktop (please complete the following information):**\r\nN.A.\r\n\r\n**Additional context**\r\nThis is seen for the IU BigRed cluster. \n", "before_files": [{"content": "import hashlib\nimport os\nimport subprocess\nfrom typing import Any, Dict\n\nimport torch\n\nfrom ..version import __version__\nfrom .generic import get_unique_timestamp\n\n# these are the base keys for the model dictionary to save\nmodel_dict_full = {\n    \"epoch\": 0,\n    \"model_state_dict\": None,\n    \"optimizer_state_dict\": None,\n    \"loss\": None,\n    \"timestamp\": None,\n    \"timestamp_hash\": None,\n    \"git_hash\": None,\n    \"version\": None,\n}\n\nmodel_dict_required = {\n    \"model_state_dict\": None,\n    \"optimizer_state_dict\": None,\n}\n\nbest_model_path_end = \"_best.pth.tar\"\nlatest_model_path_end = \"_latest.pth.tar\"\ninitial_model_path_end = \"_initial.pth.tar\"\n\n\ndef optimize_and_save_model(model, params, path, onnx_export=True):\n    \"\"\"\n    Perform post-training optimization and save it to a file.\n\n    Args:\n        model (torch.nn.Module): Trained torch model.\n        params (dict): The parameter dictionary.\n        path (str): The path to save the model dictionary to.\n        onnx_export (bool): Whether to export to ONNX and OpenVINO.\n    \"\"\"\n    # Check if ONNX export is enabled in the parameter dictionary\n    onnx_export = params[\"model\"].get(\"onnx_export\", onnx_export)\n\n    # Check for incompatible topologies and disable ONNX export\n    # Customized imagenet_vgg no longer supported for ONNX export\n    if onnx_export:\n        architecture = params[\"model\"][\"architecture\"]\n        if architecture in [\"sdnet\", \"brain_age\"] or \"imagenet_vgg\" in architecture:\n            onnx_export = False\n\n    if not onnx_export:\n        # Print a warning if ONNX export is disabled and not already warned\n        if \"onnx_print\" not in params:\n            print(\"WARNING: Current model is not supported by ONNX/OpenVINO!\")\n            params[\"onnx_print\"] = True\n        return\n    else:\n        try:\n            print(\"Optimizing the best model.\")\n            num_channel = params[\"model\"][\"num_channels\"]\n            model_dimension = params[\"model\"][\"dimension\"]\n            input_shape = params[\"patch_size\"]\n            onnx_path = path\n            if not onnx_path.endswith(\".onnx\"):\n                onnx_path = onnx_path.replace(\"pth.tar\", \"onnx\")\n\n            if model_dimension == 2:\n                dummy_input = torch.randn(\n                    (1, num_channel, input_shape[0], input_shape[1])\n                )\n            else:\n                dummy_input = torch.randn(\n                    (1, num_channel, input_shape[0], input_shape[1], input_shape[2])\n                )\n\n            # Export the model to ONNX format\n            with torch.no_grad():\n                torch.onnx.export(\n                    model.to(\"cpu\"),\n                    dummy_input.to(\"cpu\"),\n                    onnx_path,\n                    opset_version=11,\n                    export_params=True,\n                    verbose=True,\n                    input_names=[\"input\"],\n                    output_names=[\"output\"],\n                )\n        except RuntimeWarning:\n            print(\"WARNING: Cannot export to ONNX model.\")\n            return\n\n        # Check if OpenVINO is present and try to convert the ONNX model\n        openvino_present = False\n        try:\n            import openvino as ov\n            from openvino.tools.mo import convert_model\n            from openvino.runtime import get_version\n\n            openvino_present = False\n            # check for the correct openvino version to prevent inadvertent api breaks\n            if \"2023.0.1\" in get_version():\n                openvino_present = True\n        except ImportError:\n            print(\"WARNING: OpenVINO is not present.\")\n\n        if openvino_present:\n            xml_path = onnx_path.replace(\"onnx\", \"xml\")\n            bin_path = onnx_path.replace(\"onnx\", \"bin\")\n            try:\n                if model_dimension == 2:\n                    ov_model = convert_model(\n                        onnx_path,\n                        input_shape=(1, num_channel, input_shape[0], input_shape[1]),\n                    )\n                else:\n                    ov_model = convert_model(\n                        onnx_path,\n                        input_shape=(\n                            1,\n                            num_channel,\n                            input_shape[0],\n                            input_shape[1],\n                            input_shape[2],\n                        ),\n                    )\n                ov.runtime.serialize(ov_model, xml_path=xml_path, bin_path=bin_path)\n            except Exception as e:\n                print(\"WARNING: OpenVINO Model Optimizer IR conversion failed: \" + e)\n\n\ndef save_model(\n    model_dict: Dict[str, Any],\n    model: torch.nn.Module,\n    params: Dict[str, Any],\n    path: str,\n    onnx_export: bool = True,\n):\n    \"\"\"\n    Save the model dictionary to a file.\n\n    Args:\n        model_dict (dict): Model dictionary to save.\n        model (torch.nn.Module): Trained torch model.\n        params (dict): The parameter dictionary.\n        path (str): The path to save the model dictionary to.\n        onnx_export (bool): Whether to export to ONNX and OpenVINO.\n    \"\"\"\n    model_dict[\"timestamp\"] = get_unique_timestamp()\n    model_dict[\"timestamp_hash\"] = hashlib.sha256(\n        str(model_dict[\"timestamp\"]).encode(\"utf-8\")\n    ).hexdigest()\n    model_dict[\"version\"] = __version__\n    model_dict[\"parameters\"] = params\n\n    try:\n        model_dict[\"git_hash\"] = (\n            subprocess.check_output([\"git\", \"rev-parse\", \"HEAD\"])\n            .decode(\"ascii\")\n            .strip()\n        )\n    except subprocess.CalledProcessError:\n        model_dict[\"git_hash\"] = None\n\n    torch.save(model_dict, path)\n\n    # post-training optimization\n    optimize_and_save_model(model, params, path, onnx_export=onnx_export)\n\n\ndef load_model(\n    path: str, device: torch.device, full_sanity_check: bool = True\n) -> Dict[str, Any]:\n    \"\"\"\n    Load a model dictionary from a file.\n\n    Args:\n        path (str): The path to save the model dictionary to.\n        device (torch.device): The device to run the model on.\n        full_sanity_check (bool): Whether to run full sanity checking on the model.\n\n    Returns:\n        dict: Model dictionary containing model parameters and metadata.\n    \"\"\"\n    model_dict = torch.load(path, map_location=device)\n\n    # check if the model dictionary is complete\n    if full_sanity_check:\n        incomplete_keys = [\n            key for key in model_dict_full.keys() if key not in model_dict.keys()\n        ]\n        if len(incomplete_keys) > 0:\n            raise RuntimeWarning(\n                \"Model dictionary is incomplete; the following keys are missing:\",\n                incomplete_keys,\n            )\n\n    # check if required keys are absent, and if so raise an error\n    incomplete_required_keys = [\n        key for key in model_dict_required.keys() if key not in model_dict.keys()\n    ]\n    if len(incomplete_required_keys) > 0:\n        raise KeyError(\n            \"Model dictionary is incomplete; the following keys are missing:\",\n            incomplete_required_keys,\n        )\n\n    return model_dict\n\n\ndef load_ov_model(path: str, device: str = \"CPU\"):\n    \"\"\"\n    Load an OpenVINO IR model from an .xml file.\n\n    Args:\n        path (str): The path to the OpenVINO .xml file.\n        device (str): The device to run inference, can be \"CPU\", \"GPU\" or \"MULTI:CPU,GPU\". Default to be \"CPU\".\n\n    Returns:\n        exec_net (OpenVINO executable net): executable OpenVINO model.\n        input_blob (str): Input name.\n        output_blob (str): Output name.\n    \"\"\"\n\n    try:\n        from openvino import runtime as ov\n    except ImportError:\n        raise ImportError(\"OpenVINO inference engine is not configured correctly.\")\n\n    core = ov.Core()\n    if device.lower() == \"cuda\":\n        device = \"GPU\"\n\n    if device == \"GPU\":\n        core.set_property({\"CACHE_DIR\": os.path.dirname(os.path.abspath(path))})\n\n    model = core.read_model(model=path, weights=path.replace(\"xml\", \"bin\"))\n    compiled_model = core.compile_model(model=model, device_name=device.upper())\n    input_layer = compiled_model.inputs\n    output_layer = compiled_model.outputs\n\n    return compiled_model, input_layer, output_layer\n", "path": "GANDLF/utils/modelio.py"}], "after_files": [{"content": "import hashlib\nimport os\nimport subprocess\nfrom typing import Any, Dict\n\nimport torch\n\nfrom ..version import __version__\nfrom .generic import get_unique_timestamp\n\n# these are the base keys for the model dictionary to save\nmodel_dict_full = {\n    \"epoch\": 0,\n    \"model_state_dict\": None,\n    \"optimizer_state_dict\": None,\n    \"loss\": None,\n    \"timestamp\": None,\n    \"timestamp_hash\": None,\n    \"git_hash\": None,\n    \"version\": None,\n}\n\nmodel_dict_required = {\n    \"model_state_dict\": None,\n    \"optimizer_state_dict\": None,\n}\n\nbest_model_path_end = \"_best.pth.tar\"\nlatest_model_path_end = \"_latest.pth.tar\"\ninitial_model_path_end = \"_initial.pth.tar\"\n\n\ndef optimize_and_save_model(model, params, path, onnx_export=True):\n    \"\"\"\n    Perform post-training optimization and save it to a file.\n\n    Args:\n        model (torch.nn.Module): Trained torch model.\n        params (dict): The parameter dictionary.\n        path (str): The path to save the model dictionary to.\n        onnx_export (bool): Whether to export to ONNX and OpenVINO.\n    \"\"\"\n    # Check if ONNX export is enabled in the parameter dictionary\n    onnx_export = params[\"model\"].get(\"onnx_export\", onnx_export)\n\n    # Check for incompatible topologies and disable ONNX export\n    # Customized imagenet_vgg no longer supported for ONNX export\n    if onnx_export:\n        architecture = params[\"model\"][\"architecture\"]\n        if architecture in [\"sdnet\", \"brain_age\"] or \"imagenet_vgg\" in architecture:\n            onnx_export = False\n\n    if not onnx_export:\n        # Print a warning if ONNX export is disabled and not already warned\n        if \"onnx_print\" not in params:\n            print(\"WARNING: Current model is not supported by ONNX/OpenVINO!\")\n            params[\"onnx_print\"] = True\n        return\n    else:\n        try:\n            print(\"Optimizing the best model.\")\n            num_channel = params[\"model\"][\"num_channels\"]\n            model_dimension = params[\"model\"][\"dimension\"]\n            input_shape = params[\"patch_size\"]\n            onnx_path = path\n            if not onnx_path.endswith(\".onnx\"):\n                onnx_path = onnx_path.replace(\"pth.tar\", \"onnx\")\n\n            if model_dimension == 2:\n                dummy_input = torch.randn(\n                    (1, num_channel, input_shape[0], input_shape[1])\n                )\n            else:\n                dummy_input = torch.randn(\n                    (1, num_channel, input_shape[0], input_shape[1], input_shape[2])\n                )\n\n            # Export the model to ONNX format\n            with torch.no_grad():\n                torch.onnx.export(\n                    model.to(\"cpu\"),\n                    dummy_input.to(\"cpu\"),\n                    onnx_path,\n                    opset_version=11,\n                    export_params=True,\n                    verbose=True,\n                    input_names=[\"input\"],\n                    output_names=[\"output\"],\n                )\n        except RuntimeWarning:\n            print(\"WARNING: Cannot export to ONNX model.\")\n            return\n\n        # Check if OpenVINO is present and try to convert the ONNX model\n        openvino_present = False\n        try:\n            import openvino as ov\n            from openvino.tools.mo import convert_model\n            from openvino.runtime import get_version\n\n            openvino_present = False\n            # check for the correct openvino version to prevent inadvertent api breaks\n            if \"2023.0.1\" in get_version():\n                openvino_present = True\n        except ImportError:\n            print(\"WARNING: OpenVINO is not present.\")\n\n        if openvino_present:\n            xml_path = onnx_path.replace(\"onnx\", \"xml\")\n            bin_path = onnx_path.replace(\"onnx\", \"bin\")\n            try:\n                if model_dimension == 2:\n                    ov_model = convert_model(\n                        onnx_path,\n                        input_shape=(1, num_channel, input_shape[0], input_shape[1]),\n                    )\n                else:\n                    ov_model = convert_model(\n                        onnx_path,\n                        input_shape=(\n                            1,\n                            num_channel,\n                            input_shape[0],\n                            input_shape[1],\n                            input_shape[2],\n                        ),\n                    )\n                ov.runtime.serialize(ov_model, xml_path=xml_path, bin_path=bin_path)\n            except Exception as e:\n                print(\"WARNING: OpenVINO Model Optimizer IR conversion failed: \" + e)\n\n\ndef save_model(\n    model_dict: Dict[str, Any],\n    model: torch.nn.Module,\n    params: Dict[str, Any],\n    path: str,\n    onnx_export: bool = True,\n):\n    \"\"\"\n    Save the model dictionary to a file.\n\n    Args:\n        model_dict (dict): Model dictionary to save.\n        model (torch.nn.Module): Trained torch model.\n        params (dict): The parameter dictionary.\n        path (str): The path to save the model dictionary to.\n        onnx_export (bool): Whether to export to ONNX and OpenVINO.\n    \"\"\"\n    model_dict[\"timestamp\"] = get_unique_timestamp()\n    model_dict[\"timestamp_hash\"] = hashlib.sha256(\n        str(model_dict[\"timestamp\"]).encode(\"utf-8\")\n    ).hexdigest()\n    model_dict[\"version\"] = __version__\n    model_dict[\"parameters\"] = params\n\n    try:\n        # this will try to encode the git hash of the current GaNDLF codebase, and reverts to \"None\" if not found\n        model_dict[\"git_hash\"] = (\n            subprocess.check_output([\"git\", \"rev-parse\", \"HEAD\"], cwd=os.getcwd())\n            .decode(\"ascii\")\n            .strip()\n        )\n    except (subprocess.CalledProcessError, FileNotFoundError):\n        model_dict[\"git_hash\"] = \"None\"\n\n    torch.save(model_dict, path)\n\n    # post-training optimization\n    optimize_and_save_model(model, params, path, onnx_export=onnx_export)\n\n\ndef load_model(\n    path: str, device: torch.device, full_sanity_check: bool = True\n) -> Dict[str, Any]:\n    \"\"\"\n    Load a model dictionary from a file.\n\n    Args:\n        path (str): The path to save the model dictionary to.\n        device (torch.device): The device to run the model on.\n        full_sanity_check (bool): Whether to run full sanity checking on the model.\n\n    Returns:\n        dict: Model dictionary containing model parameters and metadata.\n    \"\"\"\n    model_dict = torch.load(path, map_location=device)\n\n    # check if the model dictionary is complete\n    if full_sanity_check:\n        incomplete_keys = [\n            key for key in model_dict_full.keys() if key not in model_dict.keys()\n        ]\n        if len(incomplete_keys) > 0:\n            raise RuntimeWarning(\n                \"Model dictionary is incomplete; the following keys are missing:\",\n                incomplete_keys,\n            )\n\n    # check if required keys are absent, and if so raise an error\n    incomplete_required_keys = [\n        key for key in model_dict_required.keys() if key not in model_dict.keys()\n    ]\n    if len(incomplete_required_keys) > 0:\n        raise KeyError(\n            \"Model dictionary is incomplete; the following keys are missing:\",\n            incomplete_required_keys,\n        )\n\n    return model_dict\n\n\ndef load_ov_model(path: str, device: str = \"CPU\"):\n    \"\"\"\n    Load an OpenVINO IR model from an .xml file.\n\n    Args:\n        path (str): The path to the OpenVINO .xml file.\n        device (str): The device to run inference, can be \"CPU\", \"GPU\" or \"MULTI:CPU,GPU\". Default to be \"CPU\".\n\n    Returns:\n        exec_net (OpenVINO executable net): executable OpenVINO model.\n        input_blob (str): Input name.\n        output_blob (str): Output name.\n    \"\"\"\n\n    try:\n        from openvino import runtime as ov\n    except ImportError:\n        raise ImportError(\"OpenVINO inference engine is not configured correctly.\")\n\n    core = ov.Core()\n    if device.lower() == \"cuda\":\n        device = \"GPU\"\n\n    if device == \"GPU\":\n        core.set_property({\"CACHE_DIR\": os.path.dirname(os.path.abspath(path))})\n\n    model = core.read_model(model=path, weights=path.replace(\"xml\", \"bin\"))\n    compiled_model = core.compile_model(model=model, device_name=device.upper())\n    input_layer = compiled_model.inputs\n    output_layer = compiled_model.outputs\n\n    return compiled_model, input_layer, output_layer\n", "path": "GANDLF/utils/modelio.py"}]}