diff --git a/CHANGELOG.md b/CHANGELOG.md index fe8eed7903..c5f07a893b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -139,6 +139,8 @@ GPU: 0 ### Changed +- **Removed initialization requirements for `amdsmi_get_lib_version()` and added `amdsmi_get_rocm_version()` to the python API & CLI**. + - **Added an additional argument `sensor_ind` to `amdsmi_get_power_info()`**. This change breaks previous C API calls and will require a change Python API now accepts `sensor_ind` as an optional argument, does not imapact previous usage diff --git a/amdsmi_cli/CMakeLists.txt b/amdsmi_cli/CMakeLists.txt index d0cba22c50..96a9c4d4ba 100644 --- a/amdsmi_cli/CMakeLists.txt +++ b/amdsmi_cli/CMakeLists.txt @@ -22,7 +22,6 @@ add_custom_command( ${PY_PACKAGE_DIR}/amdsmi_logger.py ${PY_PACKAGE_DIR}/amdsmi_parser.py ${PY_PACKAGE_DIR}/amdsmi_cli_exceptions.py - ${PY_PACKAGE_DIR}/rocm_version.py ${PY_PACKAGE_DIR}/BDF.py ${PY_PACKAGE_DIR}/README.md ${PY_PACKAGE_DIR}/Release_Notes.md @@ -36,7 +35,6 @@ add_custom_command( COMMAND ln -Pf ${CMAKE_CURRENT_SOURCE_DIR}/amdsmi_logger.py ${PY_PACKAGE_DIR}/ COMMAND ln -Pf ${CMAKE_CURRENT_SOURCE_DIR}/amdsmi_parser.py ${PY_PACKAGE_DIR}/ COMMAND ln -Pf ${CMAKE_CURRENT_SOURCE_DIR}/amdsmi_cli_exceptions.py ${PY_PACKAGE_DIR}/ - COMMAND ln -Pf ${CMAKE_CURRENT_SOURCE_DIR}/rocm_version.py ${PY_PACKAGE_DIR}/ COMMAND ln -Pf ${CMAKE_CURRENT_SOURCE_DIR}/BDF.py ${PY_PACKAGE_DIR}/ COMMAND ln -Pf ${CMAKE_CURRENT_SOURCE_DIR}/README.md ${PY_PACKAGE_DIR}/ COMMAND ln -Pf ${CMAKE_CURRENT_SOURCE_DIR}/Release_Notes.md ${PY_PACKAGE_DIR}/) @@ -54,7 +52,6 @@ add_custom_target( ${PY_PACKAGE_DIR}/amdsmi_logger.py ${PY_PACKAGE_DIR}/amdsmi_parser.py ${PY_PACKAGE_DIR}/amdsmi_cli_exceptions.py - ${PY_PACKAGE_DIR}/rocm_version.py ${PY_PACKAGE_DIR}/BDF.py ${PY_PACKAGE_DIR}/README.md ${PY_PACKAGE_DIR}/Release_Notes.md) diff --git a/amdsmi_cli/amdsmi_commands.py b/amdsmi_cli/amdsmi_commands.py index 34e23768a3..806102af45 100644 --- a/amdsmi_cli/amdsmi_commands.py +++ b/amdsmi_cli/amdsmi_commands.py @@ -19,6 +19,7 @@ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +import argparse import logging import sys import threading @@ -32,7 +33,6 @@ from _version import __version__ from amdsmi_helpers import AMDSMIHelpers from amdsmi_logger import AMDSMILogger from amdsmi_cli_exceptions import AmdSmiRequiredCommandException, AmdSmiInvalidParameterException -from rocm_version import get_rocm_version from amdsmi import amdsmi_interface from amdsmi import amdsmi_exception @@ -95,36 +95,14 @@ class AMDSMICommands(): exit_flag = True if exit_flag: - try: - amdsmi_lib_version = amdsmi_interface.amdsmi_get_lib_version() - amdsmi_lib_version_str = f"{amdsmi_lib_version['year']}.{amdsmi_lib_version['major']}.{amdsmi_lib_version['minor']}.{amdsmi_lib_version['release']}" - except amdsmi_exception.AmdSmiLibraryException as e: - amdsmi_lib_version_str = e.get_error_info() - - self.logger.output['tool'] = 'AMDSMI Tool' - self.logger.output['version'] = f'{__version__}' - self.logger.output['amdsmi_library_version'] = f'{amdsmi_lib_version_str}' - self.logger.output['rocm_version'] = f'{get_rocm_version()}' - - if self.logger.is_human_readable_format(): - human_readable_output = f"AMDSMI Tool: {__version__} | " \ - f"AMDSMI Library version: {amdsmi_lib_version_str} | " \ - f"ROCm version: {get_rocm_version()}" - # Custom human readable handling for version - if self.logger.destination == 'stdout': - print(human_readable_output) - else: - with self.logger.destination.open('a', encoding="utf-8") as output_file: - output_file.write(human_readable_output + '\n') - elif self.logger.is_json_format() or self.logger.is_csv_format(): - self.logger.print_output() - + version_args = argparse.Namespace() + version_args.gpu_version = False + version_args.cpu_version = False + self.version(version_args) sys.exit(-1) - def version(self, args, gpu_version=None, cpu_version=None): - """Print Version String Args: @@ -136,17 +114,23 @@ class AMDSMICommands(): if cpu_version: args.cpu_version = cpu_version # if no args are given, display everything - if not args.gpu_version and not args.cpu_version: + if args.gpu_version is None and args.cpu_version is None: args.gpu_version = True args.cpu_version = True try: amdsmi_lib_version = amdsmi_interface.amdsmi_get_lib_version() amdsmi_lib_version_str = f"{amdsmi_lib_version['year']}.{amdsmi_lib_version['major']}.{amdsmi_lib_version['minor']}.{amdsmi_lib_version['release']}" - rocm_version_str = get_rocm_version() except amdsmi_exception.AmdSmiLibraryException as e: amdsmi_lib_version_str = e.get_error_info() + try: + rocm_lib_status, rocm_version_str = amdsmi_interface.amdsmi_get_rocm_version() + if rocm_lib_status is not True: + rocm_version_str = "N/A" + except amdsmi_exception.AmdSmiLibraryException as e: + rocm_version_str = e.get_error_info() + self.logger.output['tool'] = 'AMDSMI Tool' self.logger.output['version'] = f'{__version__}' self.logger.output['amdsmi_library_version'] = f'{amdsmi_lib_version_str}' @@ -984,7 +968,7 @@ class AMDSMICommands(): static_dict['clock'] = clk_dict else: - raise amdsmi_exception.AmdSmiParameterException(args.clock, list[str]) + raise amdsmi_exception.AmdSmiParameterException(args.clock, 'list[str]') # if original_clock_args is a boolean, set it back to the original value if isinstance(original_clock_args, bool): args.clock = original_clock_args @@ -4218,6 +4202,7 @@ class AMDSMICommands(): self.logger.store_output(args.gpu, 'perfdeterminism', f"Successfully enabled performance determinism and set GFX clock frequency to {args.perf_determinism}") if args.compute_partition: + attempted_to_set = "N/A" try: (accelerator_set_choices, accelerator_profiles) = self.helpers.get_accelerator_choices_types_indices() logging.debug("args.compute_partition: %s; Accelerator_set_choices: %s", str(args.compute_partition), str(json.dumps(accelerator_set_choices, indent=4))) @@ -4474,6 +4459,9 @@ class AMDSMICommands(): amdsmi_clk_type = amdsmi_interface.AmdSmiClkType.GFX elif clk_type == "mclk": amdsmi_clk_type = amdsmi_interface.AmdSmiClkType.MEM + else: + raise ValueError(f"Invalid clock type {clk_type} for {gpu_string}") + clk_tuple = amdsmi_interface.amdsmi_get_clock_info(args.gpu, amdsmi_clk_type) if lim_type == "min": diff --git a/amdsmi_cli/amdsmi_helpers.py b/amdsmi_cli/amdsmi_helpers.py index 11269c2886..67a4f09d46 100644 --- a/amdsmi_cli/amdsmi_helpers.py +++ b/amdsmi_cli/amdsmi_helpers.py @@ -29,9 +29,8 @@ import re import multiprocessing import json -from typing import List, Union from enum import Enum -from typing import Set +from typing import List, Set, Union from amdsmi_init import * from BDF import BDF @@ -98,9 +97,11 @@ class AMDSMIHelpers(): def increment_set_count(self): self._count_of_sets_called += 1 + def get_set_count(self): return self._count_of_sets_called + def is_virtual_os(self): return self._is_virtual_os @@ -173,6 +174,16 @@ class AMDSMIHelpers(): return AMDSMI_INIT_FLAG & amdsmi_interface.amdsmi_wrapper.AMDSMI_INIT_AMD_CPUS + def get_rocm_version(self): + try: + rocm_lib_status, rocm_version = amdsmi_interface.amdsmi_get_rocm_version() + if rocm_lib_status is not True: + return "N/A" + return rocm_version + except amdsmi_interface.AmdSmiLibraryException as e: + return "N/A" + + def get_cpu_choices(self): """Return dictionary of possible CPU choices and string of the output: Dictionary will be in format: cpus[ID]: Device Handle) diff --git a/amdsmi_cli/amdsmi_parser.py b/amdsmi_cli/amdsmi_parser.py index f88c787e4d..9ecd86a8e0 100644 --- a/amdsmi_cli/amdsmi_parser.py +++ b/amdsmi_cli/amdsmi_parser.py @@ -33,7 +33,6 @@ from pathlib import Path from _version import __version__ from amdsmi_helpers import AMDSMIHelpers -from rocm_version import get_rocm_version import amdsmi_cli_exceptions @@ -95,7 +94,7 @@ class AMDSMIParser(argparse.ArgumentParser): version_string = f"Version: {__version__}" platform_string = f"Platform: {self.helpers.os_info()}" - rocm_version = get_rocm_version() + rocm_version = self.helpers.get_rocm_version() rocm_version_string = f"ROCm version: {rocm_version}" program_name = 'amd-smi' diff --git a/amdsmi_cli/rocm_version.py b/amdsmi_cli/rocm_version.py deleted file mode 100644 index 2c948a2ff7..0000000000 --- a/amdsmi_cli/rocm_version.py +++ /dev/null @@ -1,29 +0,0 @@ -import os -import ctypes -from pathlib import Path - -# Get the ROCm version for rocm-core library -def get_rocm_version(): - try: - librocm_core_file = Path(__file__).resolve().parent.parent.parent / "lib" / "librocm-core.so" - if not librocm_core_file.is_file(): - return "N/A" - - # python binding - librocm_core = ctypes.CDLL(librocm_core_file) - VerErrors = ctypes.c_uint32 - get_rocm_core_version = librocm_core.getROCmVersion - get_rocm_core_version.restype = VerErrors - get_rocm_core_version.argtypes = [ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_uint32),ctypes.POINTER(ctypes.c_uint32)] - - # call the function - major = ctypes.c_uint32() - minor = ctypes.c_uint32() - patch = ctypes.c_uint32() - - if get_rocm_core_version(ctypes.byref(major), ctypes.byref(minor),ctypes.byref(patch)) == 0: - return "%d.%d.%d" % (major.value, minor.value, patch.value) - return "N/A" - except: - return "N/A" - diff --git a/docs/reference/amdsmi-py-api.md b/docs/reference/amdsmi-py-api.md index 845dbe119a..1d019573d2 100644 --- a/docs/reference/amdsmi-py-api.md +++ b/docs/reference/amdsmi-py-api.md @@ -3541,28 +3541,6 @@ except AmdSmiException as e: print(e) ``` -### amdsmi_get_lib_version - -Description: Get the build version information for the currently running build of AMDSMI. - -Output: amdsmi build version - -Exceptions that can be thrown by `amdsmi_get_lib_version` function: - -* `AmdSmiLibraryException` -* `AmdSmiRetryException` -* `AmdSmiParameterException` - -Example: - -```python -try: - version = amdsmi_get_lib_version() - print(version) -except AmdSmiException as e: - print(e) -``` - ### amdsmi_topo_get_numa_node_number Description: Retrieve the NUMA CPU node number for a device @@ -5092,3 +5070,49 @@ try: except AmdSmiException as e: print(e) ``` + +## No amdsmi_init APIs + +### amdsmi_get_lib_version + +Description: Get the build version information for the currently running build of AMDSMI. This function doesn't require amdsmi library init. + +Output: amdsmi build version + +Exceptions that can be thrown by `amdsmi_get_lib_version` function: + +* `AmdSmiLibraryException` +* `AmdSmiRetryException` +* `AmdSmiParameterException` + +Example: + +```python +try: + version = amdsmi_get_lib_version() + print(version) +except AmdSmiException as e: + print(e) +``` + +### amdsmi_get_rocm_version + +Description: This function attempts to retrieve the ROCm version by loading the `librocm-core.so` shared library. This function doesn't require amdsmi library init. + +Output: Tuple (bool, str) containing rocm_load_status and version_message + +Exceptions that can be thrown by `amdsmi_get_rocm_version` function: + +* `AmdSmiLibraryException` + +Example: + +```python +try: + import amdsmi + rocm_load_status, version_message = amdsmi_get_rocm_version() + print(f"ROCm load status: {rocm_load_status}") + print(f"ROCm version msg: {version_message}") +except AmdSmiException as e: + print(e) +``` diff --git a/py-interface/__init__.py b/py-interface/__init__.py index 72d5961b9c..b1cb885070 100644 --- a/py-interface/__init__.py +++ b/py-interface/__init__.py @@ -9,7 +9,7 @@ # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. -# +#`` # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR @@ -207,9 +207,6 @@ from .amdsmi_interface import amdsmi_get_gpu_vram_vendor from .amdsmi_interface import amdsmi_get_gpu_subsystem_id from .amdsmi_interface import amdsmi_get_gpu_subsystem_name -# # Version information -from .amdsmi_interface import amdsmi_get_lib_version - # # Hardware topology query from .amdsmi_interface import amdsmi_topo_get_numa_node_number from .amdsmi_interface import amdsmi_topo_get_link_weight @@ -239,6 +236,11 @@ from .amdsmi_interface import amdsmi_get_gpu_pm_metrics_info # # Virtualization Mode Detection from .amdsmi_interface import amdsmi_get_gpu_virtualization_mode_info +# # Functions where library initialization is not needed +# # Version information +from .amdsmi_interface import amdsmi_get_lib_version +from .amdsmi_interface import amdsmi_get_rocm_version + # # Enums from .amdsmi_interface import AmdSmiInitFlags from .amdsmi_interface import AmdSmiContainerTypes diff --git a/py-interface/amdsmi_interface.py b/py-interface/amdsmi_interface.py index 91c18858c2..ba9cad6f6d 100644 --- a/py-interface/amdsmi_interface.py +++ b/py-interface/amdsmi_interface.py @@ -18,19 +18,33 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import ctypes -import re import json import logging -from typing import Union, Any, Dict, List -from enum import IntEnum +import math +import os +import re +import sys from collections.abc import Iterable +from enum import IntEnum +from pathlib import Path +from time import asctime, localtime, time +from typing import Any, Dict, List, Tuple, Union from . import amdsmi_wrapper from .amdsmi_exception import * -import sys -import math -from time import localtime, asctime, time -import json + + +### Non Library Specific Constants ### +class MaxUIntegerTypes(IntEnum): + UINT8_T = 0xFF + UINT16_T = 0xFFFF + UINT32_T = 0xFFFFFFFF + UINT64_T = 0xFFFFFFFFFFFFFFFF + +NO_OF_32BITS = (sys.getsizeof(ctypes.c_uint32) * 8) +NO_OF_64BITS = (sys.getsizeof(ctypes.c_uint64) * 8) +KILO = math.pow(10, 3) +############################### MAX_NUM_PROCESSES = 1024 @@ -72,6 +86,7 @@ AMDSMI_MAX_NUM_XGMI_PHYSICAL_LINK = 64 AMDSMI_GPU_UUID_SIZE = 38 MAX_AMDSMI_NAME_LENGTH = 64 MAX_EVENT_NOTIFICATION_MSG_SIZE = 96 +_AMDSMI_STRING_LENGTH = 80 class AmdSmiInitFlags(IntEnum): @@ -257,6 +272,7 @@ class AmdSmiEvtNotificationType(IntEnum): GPU_POST_RESET = amdsmi_wrapper.AMDSMI_EVT_NOTIF_GPU_POST_RESET RING_HANG = amdsmi_wrapper.AMDSMI_EVT_NOTIF_RING_HANG + class AmdSmiTemperatureMetric(IntEnum): CURRENT = amdsmi_wrapper.AMDSMI_TEMP_CURRENT MAX = amdsmi_wrapper.AMDSMI_TEMP_MAX @@ -511,11 +527,6 @@ class AmdSmiEventReader: self.stop() -_AMDSMI_MAX_DRIVER_VERSION_LENGTH = 80 -_AMDSMI_GPU_UUID_SIZE = 38 -_AMDSMI_STRING_LENGTH = 80 - - def _format_bad_page_info(bad_page_info, bad_page_count: ctypes.c_uint32) -> List[Dict]: """ Format bad page info data retrieved. @@ -618,6 +629,7 @@ def _make_amdsmi_bdf_from_list(bdf): amdsmi_bdf.struct_amdsmi_bdf_t.domain_number = bdf[0] return amdsmi_bdf + def _pad_hex_value(value, length): """ Pad a hexadecimal value with a given length of zeros @@ -633,11 +645,6 @@ def _pad_hex_value(value, length): return '0x' + value[2:].zfill(length) return value -class MaxUIntegerTypes(IntEnum): - UINT8_T = 0xFF - UINT16_T = 0xFFFF - UINT32_T = 0xFFFFFFFF - UINT64_T = 0xFFFFFFFFFFFFFFFF def _validate_if_max_uint(value, uint_type: MaxUIntegerTypes, isActivity=False, isBool=False): return_val = "N/A" @@ -661,7 +668,6 @@ def _validate_if_max_uint(value, uint_type: MaxUIntegerTypes, isActivity=False, else: return return_val - def amdsmi_get_socket_handles() -> List[amdsmi_wrapper.amdsmi_socket_handle]: """ Function that gets socket handles. Wraps the same named function call. @@ -719,7 +725,6 @@ def amdsmi_get_cpusocket_handles() -> List[amdsmi_wrapper.amdsmi_socket_handle]: ] return cpu_handles - def amdsmi_get_socket_info(socket_handle): if not isinstance(socket_handle, amdsmi_wrapper.amdsmi_socket_handle): raise AmdSmiParameterException( @@ -747,7 +752,6 @@ def amdsmi_get_processor_info(processor_handle): return processor_info.value.decode() - def amdsmi_get_processor_handles() -> List[amdsmi_wrapper.amdsmi_processor_handle]: socket_handles = amdsmi_get_socket_handles() devices = [] @@ -799,7 +803,6 @@ def amdsmi_get_cpucore_handles() -> List[amdsmi_wrapper.amdsmi_processor_handle] return core_handles - def amdsmi_get_cpu_hsmp_proto_ver( processor_handle: amdsmi_wrapper.amdsmi_processor_handle, ) -> int: @@ -1502,10 +1505,6 @@ def amdsmi_get_hsmp_metrics_table_version( return metric_tbl_version.value -NO_OF_32BITS = (sys.getsizeof(ctypes.c_uint32) * 8) -NO_OF_64BITS = (sys.getsizeof(ctypes.c_uint64) * 8) -KILO = math.pow(10, 3) - # Get 2's complement of 32 bit unsigned integer def check_msb_32(num): msb = 1 << (NO_OF_32BITS - 1) @@ -2265,10 +2264,10 @@ def amdsmi_get_gpu_device_uuid(processor_handle: amdsmi_wrapper.amdsmi_processor processor_handle, amdsmi_wrapper.amdsmi_processor_handle ) - uuid = ctypes.create_string_buffer(_AMDSMI_GPU_UUID_SIZE) + uuid = ctypes.create_string_buffer(AMDSMI_GPU_UUID_SIZE) uuid_length = ctypes.c_uint32() - uuid_length.value = _AMDSMI_GPU_UUID_SIZE + uuid_length.value = AMDSMI_GPU_UUID_SIZE _check_res( amdsmi_wrapper.amdsmi_get_gpu_device_uuid( @@ -2288,7 +2287,7 @@ def amdsmi_get_gpu_driver_info( ) length = ctypes.c_int() - length.value = _AMDSMI_MAX_DRIVER_VERSION_LENGTH + length.value = AMDSMI_MAX_DRIVER_VERSION_LENGTH info = amdsmi_wrapper.amdsmi_driver_info_t() _check_res( @@ -4451,6 +4450,7 @@ def amdsmi_get_gpu_metrics_header_info( "content_revision": header_info.content_revision } + def amdsmi_get_link_topology_nearest( processor_handle: amdsmi_wrapper.amdsmi_processor_handle, link_type: AmdSmiLinkType, @@ -4491,3 +4491,80 @@ def amdsmi_get_gpu_virtualization_mode_info( return { "mode": AmdSmiVirtualizationMode(mode.value) } + +### Non C-Lib APIs ### + +def amdsmi_get_rocm_version()-> Tuple[bool, str]: + """ + Get the ROCm version for the rocm-core library. + + This function attempts to retrieve the ROCm version by loading the `librocm-core.so` shared library + and calling its `getROCmVersion` function. The version is returned as a string in the format "major.minor.patch". + + Returns: + Tuple[bool, str]: A tuple containing a boolean and a string. + - The boolean indicates whether the operation was successful. + - The string contains the ROCm version if successful, or an error message if not. + + Raises: + Exception: If there is an error loading the shared library or calling the function. + + Example: + rocm_lib_status, version_message = amdsmi_get_rocm_version() + if rocm_lib_status: + print(f"ROCm version: {version_message}") + else: + print(f"Error: {version_message}") + """ + # librocm-core.so can be located in found using several different methods. + # Look for it with below priority: + # 1. ROCM_HOME/ROCM_PATH environment variables + # - ROCM_HOME/lib + # - ROCM_PATH/lib (usually set to /opt/rocm/) + # 2. Decided by the linker + # - LD_LIBRARY_PATH env var + # - defined path in /etc/ld.so.conf.d/ + # 3. Relative to amdsmi_wrapper.py in /opt/rocm/share/amd_smi + # - parent directory + + try: + possible_locations = list() + # 1. + rocm_path = os.getenv("ROCM_HOME", os.getenv("ROCM_PATH")) + if rocm_path: + possible_locations.append(os.path.join(rocm_path, "lib/librocm-core.so")) + + # Check if /opt/rocm/lib/librocm-core.so exists and add it to the list + if os.path.exists("/opt/rocm/lib/librocm-core.so"): + possible_locations.append("/opt/rocm/lib/librocm-core.so") + # 2. + possible_locations.append("librocm-core.so") + # 3. + librocm_core_parent_dir = Path(__file__).resolve().parent.parent.parent / "lib" / "librocm-core.so" + possible_locations.append(librocm_core_parent_dir) + + for librocm_core_file_path in possible_locations: + try: + librocm_core = ctypes.CDLL(librocm_core_file_path) + VerErrors = ctypes.c_uint32 + get_rocm_core_version = librocm_core.getROCmVersion + get_rocm_core_version.restype = VerErrors + get_rocm_core_version.argtypes = [ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_uint32),ctypes.POINTER(ctypes.c_uint32)] + + # call the function + major = ctypes.c_uint32() + minor = ctypes.c_uint32() + patch = ctypes.c_uint32() + + if get_rocm_core_version(ctypes.byref(major), ctypes.byref(minor),ctypes.byref(patch)) == 0: + return True, f"{major.value}.{minor.value}.{patch.value}" + else: + return False, "Failed to unpack ROCm version" + except OSError as e: + err = e + continue + + # If we hit here, we were unable to find the librocm-core.so file + return False, "Could not find librocm-core.so" + except Exception as e: + return False, f"Unable to detect ROCm installation, Unknown Error: {e}"