From 18faddf6f370b685e11f3d5f48ebb188e5976c28 Mon Sep 17 00:00:00 2001 From: gabrpham_amdeng Date: Thu, 30 Oct 2025 09:48:35 -0500 Subject: [PATCH] Added support for configuring PPT1 power cap - Updated python integration test to account for PPT1 support changes - Updated set/reset power-cap input format - Adjusted python API and updated C++ API test Signed-off-by: gabrpham_amdeng Change-Id: Ia9d02868b6e91c88c10a9772d9e2d9f37c3c352f --- CHANGELOG.md | 34 +- amdsmi_cli/amdsmi_commands.py | 136 +++++--- amdsmi_cli/amdsmi_helpers.py | 50 ++- amdsmi_cli/amdsmi_parser.py | 38 ++- docs/reference/amdsmi-py-api.md | 40 ++- include/amd_smi/amdsmi.h | 33 ++ include/amd_smi/impl/amd_smi_utils.h | 2 +- py-interface/__init__.py | 1 + py-interface/amdsmi_interface.py | 31 +- py-interface/amdsmi_wrapper.py | 30 +- rocm_smi/include/rocm_smi/rocm_smi.h | 39 ++- rocm_smi/src/rocm_smi.cc | 60 +++- src/amd_smi/amd_smi.cc | 44 ++- src/amd_smi/amd_smi_utils.cc | 4 +- .../functional/power_cap_read_write.cc | 290 ++++++++++-------- .../functional/power_cap_read_write.h | 2 +- tests/python_unittest/integration_test.py | 2 +- 17 files changed, 591 insertions(+), 245 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 728dc727d6..d4ccd0834b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,9 +55,41 @@ Full documentation for amd_smi_lib is available at [https://rocm.docs.amd.com/pr - `amd-smi static --vram` & `amdsmi_get_gpu_vram_info()` now support the following types: - DDR5, LPDDR4, LPDDR5, and HBM3E +- **Added support for PPT1 power limit information**. + - Support has been added for querying and setting the PPT (Package Power Tracking) limits + - There are two PPT limits, PPT0 has lower limit and tracks a filtered version of the input power and PPT1 has higher limit but tracks the raw input power. This is to catch spikes in the raw data. + - See the Changed section for changes made to the `set` and `static` commands regarding support for PPT1. + ### Changed -- N/A +- **`amd-smi set --power-cap` now requires sepcification of the power cap type**. + - command now takes the form: `amd-smi set --power-cap ` + - acceptable power cap types are "ppt0" and "ppt1" + + ```console + $ sudo amd-smi set --power-cap ppt1 1150 + GPU: 0 + POWERCAP: Successfully set ppt1 power cap to 1150W + ... + ``` + +- **`amd-smi static --limit` now has a PPT1 section when PPT1 is available**. + + ```console + $ amd-smi static --limit + GPU: 0 + LIMIT: + PPT0: + MAX_POWER_LIMIT: 1000 + MIN_POWER_LIMIT: 0 + SOCKET_POWER_LIMIT: 1000 + PPT1: + MAX_POWER_LIMIT: 1300 + MIN_POWER_LIMIT: 1100 + SOCKET_POWER_LIMIT: 1250 + SLOWDOWN_EDGE_TEMPERATURE: N/A + ... + ``` ### Removed diff --git a/amdsmi_cli/amdsmi_commands.py b/amdsmi_cli/amdsmi_commands.py index bc8e3ae0f8..1d200adc61 100644 --- a/amdsmi_cli/amdsmi_commands.py +++ b/amdsmi_cli/amdsmi_commands.py @@ -606,20 +606,36 @@ class AMDSMICommands(): if 'limit' in current_platform_args: if args.limit: # Power limits + + power_limit_types = {} + for power_type in amdsmi_interface.AmdSmiPowerCapType: + # Strip 'AMDSMI_POWER_CAP_TYPE_' prefix and convert to lowercase + key = power_type.name.replace('AMDSMI_POWER_CAP_TYPE_', '').lower() + power_limit_types[key] = "N/A" + try: power_limit_error = False - power_cap_info = amdsmi_interface.amdsmi_get_power_cap_info(args.gpu) - max_power_limit = power_cap_info['max_power_cap'] - max_power_limit = self.helpers.convert_SI_unit(max_power_limit, AMDSMIHelpers.SI_Unit.MICRO) - min_power_limit = power_cap_info['min_power_cap'] - min_power_limit = self.helpers.convert_SI_unit(min_power_limit, AMDSMIHelpers.SI_Unit.MICRO) - socket_power_limit = power_cap_info['power_cap'] - socket_power_limit = self.helpers.convert_SI_unit(socket_power_limit, AMDSMIHelpers.SI_Unit.MICRO) + power_cap_types = amdsmi_interface.amdsmi_get_supported_power_cap(args.gpu) + for sensor in power_cap_types['sensor_inds']: + power_cap_info = amdsmi_interface.amdsmi_get_power_cap_info(args.gpu, sensor) + max_power_limit = power_cap_info['max_power_cap'] + max_power_limit = self.helpers.convert_SI_unit(max_power_limit, AMDSMIHelpers.SI_Unit.MICRO) + min_power_limit = power_cap_info['min_power_cap'] + min_power_limit = self.helpers.convert_SI_unit(min_power_limit, AMDSMIHelpers.SI_Unit.MICRO) + socket_power_limit = power_cap_info['power_cap'] + socket_power_limit = self.helpers.convert_SI_unit(socket_power_limit, AMDSMIHelpers.SI_Unit.MICRO) + ppt = { + "max_power_limit" : max_power_limit, + "min_power_limit" : min_power_limit, + "socket_power_limit" : socket_power_limit + } + + sensor_name = power_cap_types['sensor_types'][sensor] + # Strip 'AMDSMI_POWER_CAP_TYPE_' prefix and convert to lowercase + sensor_key = sensor_name.name.replace('AMDSMI_POWER_CAP_TYPE_', '').lower() + power_limit_types[sensor_key] = ppt except amdsmi_exception.AmdSmiLibraryException as e: power_limit_error = True - max_power_limit = "N/A" - min_power_limit = "N/A" - socket_power_limit = "N/A" logging.debug("Failed to get power cap info for gpu %s | %s", gpu_id, e.get_error_info()) # Edge temperature limits @@ -740,9 +756,8 @@ class AMDSMICommands(): limit_info = {} # Power limits - limit_info['max_power'] = max_power_limit - limit_info['min_power'] = min_power_limit - limit_info['socket_power'] = socket_power_limit + limit_info['ppt0'] = power_limit_types['ppt0'] + limit_info['ppt1'] = power_limit_types['ppt1'] # Shutdown limits limit_info['slowdown_edge_temperature'] = slowdown_temp_edge_limit @@ -4909,9 +4924,13 @@ class AMDSMICommands(): self.logger.clear_multiple_devices_output() return # Universal args - if isinstance(args.power_cap, int): + if isinstance(args.power_cap, tuple): + pwr_type = args.power_cap.pwr_type + pwr_type_as_int = (0 if pwr_type == "ppt0" else 1 if pwr_type == "ppt1" else None) + pwr_type = pwr_type.upper() + requested_power_cap = args.power_cap.watts try: - power_cap_info = amdsmi_interface.amdsmi_get_power_cap_info(args.gpu) + power_cap_info = amdsmi_interface.amdsmi_get_power_cap_info(args.gpu, pwr_type_as_int) logging.debug(f"Power cap info for gpu {gpu_id} | {power_cap_info}") min_power_cap = power_cap_info["min_power_cap"] min_power_cap = self.helpers.convert_SI_unit(min_power_cap, AMDSMIHelpers.SI_Unit.MICRO) @@ -4923,29 +4942,29 @@ class AMDSMICommands(): min_power_cap = "N/A" max_power_cap = "N/A" current_power_cap = "N/A" - self.logger.store_output(args.gpu, 'powercap', f"[{e.get_error_info(detailed=False)}] Unable to set power cap to {args.power_cap}W") + self.logger.store_output(args.gpu, 'powercap', f"[{e.get_error_info(detailed=False)}] Unable to set {pwr_type} power cap to {requested_power_cap}W") self.logger.print_output() self.logger.clear_multiple_devices_output() return - if args.power_cap == current_power_cap: - self.logger.store_output(args.gpu, 'powercap', f"Power cap is already set to {args.power_cap}W") + if requested_power_cap == current_power_cap: + self.logger.store_output(args.gpu, 'powercap', f"{pwr_type} power cap is already set to {requested_power_cap}W") elif current_power_cap == 0: - self.logger.store_output(args.gpu, 'powercap', f"Unable to set power cap to {args.power_cap}W, current value is {current_power_cap}W") - elif args.power_cap >= min_power_cap and args.power_cap <= max_power_cap: + self.logger.store_output(args.gpu, 'powercap', f"Unable to set {pwr_type} power cap to {requested_power_cap}W, current value is {current_power_cap}W") + elif requested_power_cap >= min_power_cap and requested_power_cap <= max_power_cap and requested_power_cap > 0: try: - new_power_cap = self.helpers.convert_SI_unit(args.power_cap, AMDSMIHelpers.SI_Unit.BASE, + new_power_cap = self.helpers.convert_SI_unit(requested_power_cap, AMDSMIHelpers.SI_Unit.BASE, AMDSMIHelpers.SI_Unit.MICRO) - amdsmi_interface.amdsmi_set_power_cap(args.gpu, 0, new_power_cap) + amdsmi_interface.amdsmi_set_power_cap(args.gpu, pwr_type_as_int, new_power_cap) except amdsmi_exception.AmdSmiLibraryException as e: if e.get_error_code() == amdsmi_interface.amdsmi_wrapper.AMDSMI_STATUS_NO_PERM: raise PermissionError('Command requires elevation') from e - self.logger.store_output(args.gpu, 'powercap', f"[{e.get_error_info(detailed=False)}] Unable to set power cap to {args.power_cap}W") + self.logger.store_output(args.gpu, 'powercap', f"[{e.get_error_info(detailed=False)}] Unable to set {pwr_type} power cap to {requested_power_cap}W") self.logger.print_output() self.logger.clear_multiple_devices_output() return - self.logger.store_output(args.gpu, 'powercap', f"Successfully set power cap to {args.power_cap}W") + self.logger.store_output(args.gpu, 'powercap', f"Successfully set {pwr_type} power cap to {requested_power_cap}W") else: # setting power cap to 0 will return the current power cap so the technical minimum value is 1 if min_power_cap == 0: @@ -5469,32 +5488,66 @@ class AMDSMICommands(): self.logger.clear_multiple_devices_output() return if args.power_cap: + power_limit_types = {} + for power_type in amdsmi_interface.AmdSmiPowerCapType: + # Strip 'AMDSMI_POWER_CAP_TYPE_' prefix and convert to lowercase + key = power_type.name.replace('AMDSMI_POWER_CAP_TYPE_', '').lower() + power_limit_types[key] = "N/A" + try: - power_cap_info = amdsmi_interface.amdsmi_get_power_cap_info(args.gpu) - logging.debug(f"Power cap info for gpu {gpu_id} | {power_cap_info}") - default_power_cap_in_w = power_cap_info["default_power_cap"] - default_power_cap_in_w = self.helpers.convert_SI_unit(default_power_cap_in_w, AMDSMIHelpers.SI_Unit.MICRO) - current_power_cap_in_w = power_cap_info["power_cap"] - current_power_cap_in_w = self.helpers.convert_SI_unit(current_power_cap_in_w, AMDSMIHelpers.SI_Unit.MICRO) + power_cap_types = amdsmi_interface.amdsmi_get_supported_power_cap(args.gpu) + for sensor in power_cap_types['sensor_inds']: + power_cap_info = amdsmi_interface.amdsmi_get_power_cap_info(args.gpu, sensor) + logging.debug(f"Power cap info for gpu {gpu_id} ppt{sensor} | {power_cap_info}") + default_power_cap_in_w = power_cap_info["default_power_cap"] + default_power_cap_in_w = self.helpers.convert_SI_unit(default_power_cap_in_w, AMDSMIHelpers.SI_Unit.MICRO) + current_power_cap_in_w = power_cap_info["power_cap"] + current_power_cap_in_w = self.helpers.convert_SI_unit(current_power_cap_in_w, AMDSMIHelpers.SI_Unit.MICRO) + sensor_name = power_cap_types['sensor_types'][sensor] + # Strip 'AMDSMI_POWER_CAP_TYPE_' prefix and convert to lowercase + sensor_key = sensor_name.name.replace('AMDSMI_POWER_CAP_TYPE_', '').lower() + power_limit_types[sensor_key] = (default_power_cap_in_w, current_power_cap_in_w) except amdsmi_exception.AmdSmiLibraryException as e: self.logger.store_output(args.gpu, 'powercap', f"[{e.get_error_info(detailed=False)}] Unable to reset power cap to default") self.logger.print_output() self.logger.clear_multiple_devices_output() return - if current_power_cap_in_w == default_power_cap_in_w: - self.logger.store_output(args.gpu, 'powercap', f"Power cap is already set to {default_power_cap_in_w}W") + # TODO Make agnostic to number of power cap types + final_output = {"ppt0": "", "ppt1": ""} + if power_limit_types['ppt0'] == "N/A": + final_output['ppt0'] = f"PPT0 Power cap information is not available" + elif power_limit_types['ppt0'][1] == power_limit_types['ppt0'][0]: + final_output['ppt0'] = f"PPT0 Power cap is already set to {power_limit_types['ppt0'][0]}W" else: try: - default_power_cap_in_uw = self.helpers.convert_SI_unit(default_power_cap_in_w, - AMDSMIHelpers.SI_Unit.BASE, - AMDSMIHelpers.SI_Unit.MICRO) - amdsmi_interface.amdsmi_set_power_cap(args.gpu, 0, default_power_cap_in_uw) + default_ppt0_power_cap_in_uw = self.helpers.convert_SI_unit(power_limit_types['ppt0'][0], + AMDSMIHelpers.SI_Unit.BASE, + AMDSMIHelpers.SI_Unit.MICRO) + amdsmi_interface.amdsmi_set_power_cap(args.gpu, 0, default_ppt0_power_cap_in_uw) except amdsmi_exception.AmdSmiLibraryException as e: if e.get_error_code() == amdsmi_interface.amdsmi_wrapper.AMDSMI_STATUS_NO_PERM: raise PermissionError('Command requires elevation') from e - raise ValueError(f"Unable to reset power cap to {default_power_cap_in_w} on GPU {gpu_id}") from e - self.logger.store_output(args.gpu, 'powercap', f"Successfully set power cap to {default_power_cap_in_w}W") + raise ValueError(f"Unable to reset PPT0 power cap to {power_limit_types['ppt0'][0]} on GPU {gpu_id}") from e + final_output['ppt0'] = f"Successfully reset PPT0 power cap to {power_limit_types['ppt0'][0]}W" + + if power_limit_types['ppt1'] == "N/A": + final_output['ppt1'] = f"PPT1 Power cap information is not available" + elif power_limit_types['ppt1'][1] == power_limit_types['ppt1'][0]: + final_output['ppt1'] = f"PPT1 Power cap is already set to {power_limit_types['ppt1'][0]}W" + else: + try: + default_ppt1_power_cap_in_uw = self.helpers.convert_SI_unit(power_limit_types['ppt1'][0], + AMDSMIHelpers.SI_Unit.BASE, + AMDSMIHelpers.SI_Unit.MICRO) + amdsmi_interface.amdsmi_set_power_cap(args.gpu, 1, default_ppt1_power_cap_in_uw) + except amdsmi_exception.AmdSmiLibraryException as e: + if e.get_error_code() == amdsmi_interface.amdsmi_wrapper.AMDSMI_STATUS_NO_PERM: + raise PermissionError('Command requires elevation') from e + raise ValueError(f"Unable to reset PPT1 power cap to {power_limit_types['ppt1'][0]} on GPU {gpu_id}") from e + final_output['ppt1'] = f"Successfully reset PPT1 power cap to {power_limit_types['ppt1'][0]}W" + + self.logger.store_output(args.gpu, 'powercap', final_output) self.logger.print_output() self.logger.clear_multiple_devices_output() return @@ -5822,7 +5875,8 @@ class AMDSMICommands(): if args.power_usage and not args.default_output: # Get Current Power Cap try: - power_cap_info = amdsmi_interface.amdsmi_get_power_cap_info(args.gpu) + # assume that we're always asking for ppt0 for quick checks like this + power_cap_info = amdsmi_interface.amdsmi_get_power_cap_info(args.gpu, 0) monitor_values['max_power'] = power_cap_info['power_cap'] # Get current power cap (`power_cap`) socket is set to # `max_power_cap`, is the maximum value it can be set to monitor_values['max_power'] = self.helpers.convert_SI_unit(monitor_values['max_power'], AMDSMIHelpers.SI_Unit.MICRO) @@ -7340,9 +7394,9 @@ class AMDSMICommands(): gpu_info_dict.update({"temp": temperature}) - # rest of power usage info + # rest of power usage info; Will assume we're always trying to get PPT0 for now try: - power_cap_info = amdsmi_interface.amdsmi_get_power_cap_info(processor) + power_cap_info = amdsmi_interface.amdsmi_get_power_cap_info(processor, 0) socket_power_limit = self.helpers.convert_SI_unit(power_cap_info['power_cap'], AMDSMIHelpers.SI_Unit.MICRO) power_usage = {"current_power": current_power, "power_limit": socket_power_limit} except amdsmi_exception.AmdSmiLibraryException as e: diff --git a/amdsmi_cli/amdsmi_helpers.py b/amdsmi_cli/amdsmi_helpers.py index fa103a8d28..5e394b0f15 100755 --- a/amdsmi_cli/amdsmi_helpers.py +++ b/amdsmi_cli/amdsmi_helpers.py @@ -815,26 +815,50 @@ class AMDSMIHelpers(): def get_power_caps(self): device_handles = amdsmi_interface.amdsmi_get_processor_handles() - power_cap_min = amdsmi_interface.MaxUIntegerTypes.UINT64_T # start out at max and min and then find real min and max - power_cap_max = 0 + power_limit_types = { + 'ppt0': { + 'power_cap_min': amdsmi_interface.MaxUIntegerTypes.UINT64_T, + 'power_cap_max': 0 + }, + 'ppt1': { + 'power_cap_min': amdsmi_interface.MaxUIntegerTypes.UINT64_T, + 'power_cap_max': 0 + } + } + for dev in device_handles: try: - power_cap_info = amdsmi_interface.amdsmi_get_power_cap_info(dev) - if power_cap_info['max_power_cap'] > power_cap_max: - power_cap_max = power_cap_info['max_power_cap'] - if power_cap_info['min_power_cap'] < power_cap_max: - power_cap_min = power_cap_info['min_power_cap'] - except amdsmi_interface.AmdSmiLibraryException as e: + power_cap_types = amdsmi_interface.amdsmi_get_supported_power_cap(dev) + for sensor in power_cap_types['sensor_inds']: + power_cap_info = amdsmi_interface.amdsmi_get_power_cap_info(dev, sensor) + if power_cap_info['max_power_cap'] > power_limit_types[f'ppt{sensor}']['power_cap_max']: + power_limit_types[f'ppt{sensor}']['power_cap_max'] = power_cap_info['max_power_cap'] + if power_cap_info['min_power_cap'] < power_limit_types[f'ppt{sensor}']['power_cap_min']: + power_limit_types[f'ppt{sensor}']['power_cap_min'] = power_cap_info['min_power_cap'] + except (amdsmi_interface.AmdSmiLibraryException, KeyError) as e: logging.debug(f"AMDSMIHelpers.get_power_caps - Unable to get power cap info for device {dev}: {str(e)}") continue # If we never found a real min or max, set them to N/A - if power_cap_min == amdsmi_interface.MaxUIntegerTypes.UINT64_T: - power_cap_min = "N/A" - if power_cap_max == 0: - power_cap_max = "N/A" + for ppt_key in ['ppt0', 'ppt1']: + if power_limit_types[ppt_key]['power_cap_min'] == amdsmi_interface.MaxUIntegerTypes.UINT64_T: + power_limit_types[ppt_key]['power_cap_min'] = "N/A" + if power_limit_types[ppt_key]['power_cap_max'] == 0: + power_limit_types[ppt_key]['power_cap_max'] = "N/A" - return (power_cap_min, power_cap_max) + ppt0_power_cap_max = self.format_power_cap(power_limit_types['ppt0']['power_cap_min']) + ppt0_power_cap_min = self.format_power_cap(power_limit_types['ppt0']['power_cap_max']) + ppt1_power_cap_max = self.format_power_cap(power_limit_types['ppt1']['power_cap_max']) + ppt1_power_cap_min = self.format_power_cap(power_limit_types['ppt1']['power_cap_min']) + + return (ppt0_power_cap_min, ppt0_power_cap_min, ppt1_power_cap_max, ppt1_power_cap_min) + + + def format_power_cap(self, value): + if value != "N/A": + converted = self.convert_SI_unit(value, AMDSMIHelpers.SI_Unit.MICRO) + return f"{converted} W" + return value def get_soc_pstates(self): diff --git a/amdsmi_cli/amdsmi_parser.py b/amdsmi_cli/amdsmi_parser.py index 82d26d9de6..466e34b176 100644 --- a/amdsmi_cli/amdsmi_parser.py +++ b/amdsmi_cli/amdsmi_parser.py @@ -288,6 +288,30 @@ class AMDSMIParser(argparse.ArgumentParser): return AMDSMIFreqArgs + def _power_cap_options(self): + """Custom action for setting power cap options""" + output_format = self.helpers.get_output_format() + + class AMDSMIPowerCapArgs(argparse.Action): + def __call__(self, parser: AMDSMIParser, namespace: argparse.Namespace, + values: list, option_string: Optional[str] = None) -> None: + if len(values) != 2: + raise amdsmi_cli_exceptions.AmdSmiInvalidParameterException(sys.argv[1], values, output_format) + + power_cap_type = values[0] + power_cap_value = values[1] + + if power_cap_type not in ['ppt0', 'ppt1']: + raise amdsmi_cli_exceptions.AmdSmiInvalidParameterException(sys.argv[1], power_cap_type, output_format) + + if not power_cap_value.isdigit(): + raise amdsmi_cli_exceptions.AmdSmiInvalidParameterValueException(sys.argv[1], power_cap_value, output_format) + + power_cap_args = collections.namedtuple('power_cap_args', ['pwr_type', 'watts']) + setattr(namespace, self.dest, power_cap_args(power_cap_type, int(power_cap_value))) + return AMDSMIPowerCapArgs + + def _check_folder_path(self): """ Argument action validator: Returns a path to folder from the folder path provided. @@ -1235,14 +1259,8 @@ class AMDSMIParser(argparse.ArgumentParser): xgmi_plpd_help_info = ", ".join(self.helpers.get_xgmi_plpd_policies()) set_xgmi_plpd_help = f"Set the GPU XGMI per-link power down policy using policy id, an integer. Valid id's include:\n\t{xgmi_plpd_help_info}" set_clock_freq_help = "Set one or more sclk (aka gfxclk), mclk, fclk, pcie, or socclk frequency levels.\n\tUse `amd-smi static --clock` to find acceptable levels.\n\tUse `amd-smi static --bus` to find acceptable pcie levels." - power_cap_min, power_cap_max = self.helpers.get_power_caps() - if power_cap_max != "N/A": - power_cap_max = self.helpers.convert_SI_unit(power_cap_max, AMDSMIHelpers.SI_Unit.MICRO) - power_cap_max = str(power_cap_max) + ' W' - if power_cap_min != "N/A": - power_cap_min = self.helpers.convert_SI_unit(power_cap_min, AMDSMIHelpers.SI_Unit.MICRO) - power_cap_min = str(power_cap_min) + ' W' - set_power_cap_help = f"Set power capacity limit:\n\tmin cap: {power_cap_min}, max cap: {power_cap_max}" + ppt0_power_cap_min, ppt0_power_cap_max, ppt1_power_cap_min, ppt1_power_cap_max = self.helpers.get_power_caps() + set_power_cap_help = f"Set either PPT0 or PPT1 power capacity limit:\n\tex: amd-smi set -o ppt0 1300\n\tPPT0 min cap: {ppt0_power_cap_min}, PPT0 max cap: {ppt0_power_cap_max}\n\tPPT1 min cap: {ppt1_power_cap_min}, PPT1 max cap: {ppt1_power_cap_max}" set_clk_limit_help = "Sets the sclk (aka gfxclk) or mclk minimum and maximum frequencies. \n\tex: amd-smi set -L (sclk | mclk) (min | max) value" set_process_isolation_help = "Enable or disable the GPU process isolation on a per partition basis:\n 0 for disable and 1 for enable.\n" @@ -1281,7 +1299,7 @@ class AMDSMIParser(argparse.ArgumentParser): required=False, help=set_compute_partition_help, metavar=('TYPE/INDEX')) set_value_exclusive_group.add_argument('-M', '--memory-partition', action='store', choices=self.helpers.get_memory_partition_types(), type=str.upper, required=False, help=set_memory_partition_help, metavar='PARTITION') # Power cap is enabled on guest, maintain order - set_value_exclusive_group.add_argument('-o', '--power-cap', action='store', type=lambda value: self._positive_int(value, '--power-cap'), required=False, help=set_power_cap_help, metavar='WATTS') + set_value_exclusive_group.add_argument('-o', '--power-cap', action=self._power_cap_options(), nargs=2, required=False, help=set_power_cap_help, metavar=('PWR_TYPE', 'WATTS')) if self.helpers.is_baremetal(): set_value_exclusive_group.add_argument('-p', '--soc-pstate', action='store', required=False, type=lambda value: self._not_negative_int(value, '--soc-pstate'), help=set_soc_pstate_help, metavar='POLICY_ID') set_value_exclusive_group.add_argument('-x', '--xgmi-plpd', action='store', required=False, type=lambda value: self._not_negative_int(value, '--xgmi-plpd'), help=set_xgmi_plpd_help, metavar='POLICY_ID') @@ -1338,7 +1356,7 @@ class AMDSMIParser(argparse.ArgumentParser): reset_profile_help = "Reset power profile back to default" reset_xgmierr_help = "Reset XGMI error counts" reset_perf_det_help = "Disable performance determinism" - reset_power_cap_help = "Reset power capacity limit to max capable" + reset_power_cap_help = "Reset the PPT0 and PPT1 power capacity limit to max capable" reset_gpu_clean_local_data_help = "Clean up local data in LDS/GPRs on a per partition basis" reset_gpu_driver_help = "Reset (reload) AMD GPU driver" diff --git a/docs/reference/amdsmi-py-api.md b/docs/reference/amdsmi-py-api.md index ca66acc6e1..54649ffb3b 100644 --- a/docs/reference/amdsmi-py-api.md +++ b/docs/reference/amdsmi-py-api.md @@ -434,6 +434,7 @@ on the given GPU. It is not supported on virtual machine guest Input parameters: * `processor_handle` device which to query +* `sensor_ind` The Package Power Tracking (PPT) type to query Output: Dictionary with fields @@ -460,7 +461,7 @@ try: print("No GPUs on machine") else: for device in devices: - power_cap_info = amdsmi_get_power_cap_info(device) + power_cap_info = amdsmi_get_power_cap_info(device, 0) print(power_cap_info['power_cap']) print(power_cap_info['dpm_cap']) print(power_cap_info['default_power_cap']) @@ -470,6 +471,43 @@ except AmdSmiException as e: print(e) ``` +### amdsmi_get_supported_power_cap + +Description: Returns dictionary of Package Power Tracking (PPT) types as currently +configured on the given GPU. It is not supported on virtual machine guest + +Input parameters: + +* `processor_handle` device which to query + +Output: Dictionary with fields + +Field | Description | Units +---|--- +`sensor_inds` | List of integer indices of the supported ppt types. 0 indicates PPT0 and 1 indicates PPT1. Should be used as input for `amdsmi_get_power_cap_info` and `amdsmi_set_power_cap_info`. +`sensor_types` | Enum `AmdSmiPowerCapType` that corresponds to the ppt types that are supported on the device. + +Exceptions that can be thrown by `amdsmi_get_supported_power_cap` function: + +* `AmdSmiLibraryException` +* `AmdSmiParameterException` + +Example: + +```python +try: + devices = amdsmi_get_processor_handles() + if len(devices) == 0: + print("No GPUs on machine") + else: + for device in devices: + power_cap_types = amdsmi_get_supported_power_cap(device) + print(power_cap_types['sensor_inds']) + print(power_cap_types['sensor_types']) +except AmdSmiException as e: + print(e) +``` + ### amdsmi_get_gpu_vram_info Description: Returns dictionary of vram information for the given GPU. diff --git a/include/amd_smi/amdsmi.h b/include/amd_smi/amdsmi.h index 33c4ab4696..d3b2bcdff2 100644 --- a/include/amd_smi/amdsmi.h +++ b/include/amd_smi/amdsmi.h @@ -833,6 +833,16 @@ typedef struct { uint64_t reserved[3]; } amdsmi_power_cap_info_t; +/** + * @brief Power Cap Package Power Tracking (PPT) type + * + * @cond @tag{gpu_bm_linux} @tag{host} @endcond + */ +typedef enum { + AMDSMI_POWER_CAP_TYPE_PPT0, //!< PPT0 power cap; lower limit, filtered input + AMDSMI_POWER_CAP_TYPE_PPT1, //!< PPT1 power cap; higher limit, raw input +} amdsmi_power_cap_type_t; + /** * @brief VBios Information * @@ -3277,6 +3287,29 @@ amdsmi_status_t amdsmi_set_gpu_power_profile(amdsmi_processor_handle processor_handle, uint32_t reserved, amdsmi_power_profile_preset_masks_t profile); +/** + * @brief Query the supported power cap sensors and their types for a device. + * + * @ingroup tagPowerControl + * + * @platform{gpu_bm_linux} @platform{host} + * + * @details This function returns the list of supported power cap sensors for the given device, + * including their sensor indices and types (e.g., PPT0, PPT1). + * + * @param[in] processor_handle A processor handle. + * @param[out] sensor_count Pointer to a uint32_t that will be set to the number of supported sensors. + * @param[out] sensor_inds Pointer to an array of uint32_t to be filled with sensor indices. + * The array must be allocated by the caller with enough space. + * @param[out] sensor_types Pointer to an array of amdsmi_power_cap_type_t to be filled with sensor types. + * The array must be allocated by the caller with enough space. + * + * @return ::amdsmi_status_t | ::AMDSMI_STATUS_SUCCESS on success, non-zero on fail. + */ +amdsmi_status_t +amdsmi_get_supported_power_cap(amdsmi_processor_handle processor_handle, uint32_t *sensor_count, + uint32_t *sensor_inds, amdsmi_power_cap_type_t *sensor_types); + /** * @brief Get the socket power. * diff --git a/include/amd_smi/impl/amd_smi_utils.h b/include/amd_smi/impl/amd_smi_utils.h index a3be8194f3..cd2ec9d919 100644 --- a/include/amd_smi/impl/amd_smi_utils.h +++ b/include/amd_smi/impl/amd_smi_utils.h @@ -45,7 +45,7 @@ extern "C" { amdsmi_status_t smi_amdgpu_find_hwmon_dir(amd::smi::AMDSmiGPUDevice* device, std::string* full_path); amdsmi_status_t smi_amdgpu_get_board_info(amd::smi::AMDSmiGPUDevice* device, amdsmi_board_info_t *info); -amdsmi_status_t smi_amdgpu_get_power_cap(amd::smi::AMDSmiGPUDevice* device, int *cap); +amdsmi_status_t smi_amdgpu_get_power_cap(amd::smi::AMDSmiGPUDevice* device, uint32_t sensor_ind, int *cap); amdsmi_status_t smi_amdgpu_get_ranges(amd::smi::AMDSmiGPUDevice* device, amdsmi_clk_type_t domain, int *max_freq, int *min_freq, int *num_dpm, int *sleep_state_freq); amdsmi_status_t smi_amdgpu_get_enabled_blocks(amd::smi::AMDSmiGPUDevice* device, uint64_t *enabled_blocks); amdsmi_status_t smi_amdgpu_get_bad_page_info(amd::smi::AMDSmiGPUDevice* device, uint32_t *num_pages, amdsmi_retired_page_record_t *info); diff --git a/py-interface/__init__.py b/py-interface/__init__.py index fbffcb11ff..0d9a5554bc 100644 --- a/py-interface/__init__.py +++ b/py-interface/__init__.py @@ -168,6 +168,7 @@ from .amdsmi_interface import amdsmi_get_xgmi_plpd from .amdsmi_interface import amdsmi_clean_gpu_local_data from .amdsmi_interface import amdsmi_get_gpu_process_isolation from .amdsmi_interface import amdsmi_set_gpu_process_isolation +from .amdsmi_interface import amdsmi_get_supported_power_cap # # Physical State Queries from .amdsmi_interface import amdsmi_get_gpu_fan_rpms diff --git a/py-interface/amdsmi_interface.py b/py-interface/amdsmi_interface.py index 986072753c..82ad0ad3b3 100644 --- a/py-interface/amdsmi_interface.py +++ b/py-interface/amdsmi_interface.py @@ -605,6 +605,11 @@ class AmdSmiAffinityScope(IntEnum): SOCKET_SCOPE = amdsmi_wrapper.AMDSMI_AFFINITY_SCOPE_SOCKET +class AmdSmiPowerCapType(IntEnum): + PPT0 = amdsmi_wrapper.AMDSMI_POWER_CAP_TYPE_PPT0 + PPT1 = amdsmi_wrapper.AMDSMI_POWER_CAP_TYPE_PPT1 + + class AmdSmiEventReader: def __init__( self, @@ -2154,9 +2159,32 @@ def amdsmi_get_gpu_kfd_info( return kfd_info +def amdsmi_get_supported_power_cap( + processor_handle: processor_handle_t) ->Dict[str, Any]: + if not isinstance(processor_handle, amdsmi_wrapper.amdsmi_processor_handle): + raise AmdSmiParameterException( + processor_handle, amdsmi_wrapper.amdsmi_processor_handle + ) + CONST_AMDSMI_MAX_POWER_SENSORS = 2 + + sensor_count = ctypes.c_uint32() + sensor_ind = (ctypes.c_uint32 * CONST_AMDSMI_MAX_POWER_SENSORS)() + sensor_types = (amdsmi_wrapper.amdsmi_power_cap_type_t * CONST_AMDSMI_MAX_POWER_SENSORS)() + + _check_res( + amdsmi_wrapper.amdsmi_get_supported_power_cap( + processor_handle, ctypes.byref(sensor_count), sensor_ind, sensor_types + ) + ) + + return { + "sensor_inds": [sensor_ind[i] for i in range(sensor_count.value)], + "sensor_types": [AmdSmiPowerCapType(sensor_types[i]) for i in range(sensor_count.value)] + } def amdsmi_get_power_cap_info( processor_handle: processor_handle_t, + sensor_ind: int = 0 ) -> Dict[str, Any]: if not isinstance(processor_handle, amdsmi_wrapper.amdsmi_processor_handle): raise AmdSmiParameterException( @@ -2166,7 +2194,7 @@ def amdsmi_get_power_cap_info( power_cap_info = amdsmi_wrapper.amdsmi_power_cap_info_t() _check_res( amdsmi_wrapper.amdsmi_get_power_cap_info( - processor_handle, ctypes.c_uint32(0), ctypes.byref(power_cap_info) + processor_handle, sensor_ind, ctypes.byref(power_cap_info) ) ) @@ -2176,7 +2204,6 @@ def amdsmi_get_power_cap_info( "min_power_cap": power_cap_info.min_power_cap, "max_power_cap": power_cap_info.max_power_cap} - def _get_name_value(num, data) -> List[Dict[str, int]]: """ Extracts a list of name-value pairs from a ctypes array buffer. diff --git a/py-interface/amdsmi_wrapper.py b/py-interface/amdsmi_wrapper.py index dde097cdbf..49082865d0 100644 --- a/py-interface/amdsmi_wrapper.py +++ b/py-interface/amdsmi_wrapper.py @@ -1034,6 +1034,15 @@ struct_amdsmi_power_cap_info_t._fields_ = [ ] amdsmi_power_cap_info_t = struct_amdsmi_power_cap_info_t + +# values for enumeration 'amdsmi_power_cap_type_t' +amdsmi_power_cap_type_t__enumvalues = { + 0: 'AMDSMI_POWER_CAP_TYPE_PPT0', + 1: 'AMDSMI_POWER_CAP_TYPE_PPT1', +} +AMDSMI_POWER_CAP_TYPE_PPT0 = 0 +AMDSMI_POWER_CAP_TYPE_PPT1 = 1 +amdsmi_power_cap_type_t = ctypes.c_uint32 # enum class struct_amdsmi_vbios_info_t(Structure): pass @@ -2550,6 +2559,9 @@ amdsmi_set_power_cap.argtypes = [amdsmi_processor_handle, uint32_t, uint64_t] amdsmi_set_gpu_power_profile = _libraries['libamd_smi.so'].amdsmi_set_gpu_power_profile amdsmi_set_gpu_power_profile.restype = amdsmi_status_t amdsmi_set_gpu_power_profile.argtypes = [amdsmi_processor_handle, uint32_t, amdsmi_power_profile_preset_masks_t] +amdsmi_get_supported_power_cap = _libraries['libamd_smi.so'].amdsmi_get_supported_power_cap +amdsmi_get_supported_power_cap.restype = amdsmi_status_t +amdsmi_get_supported_power_cap.argtypes = [amdsmi_processor_handle, ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(amdsmi_power_cap_type_t)] amdsmi_get_cpu_socket_power = _libraries['libamd_smi.so'].amdsmi_get_cpu_socket_power amdsmi_get_cpu_socket_power.restype = amdsmi_status_t amdsmi_get_cpu_socket_power.argtypes = [amdsmi_processor_handle, ctypes.POINTER(ctypes.c_uint32)] @@ -3219,7 +3231,8 @@ __all__ = \ 'AMDSMI_MEM_TYPE_GTT', 'AMDSMI_MEM_TYPE_LAST', 'AMDSMI_MEM_TYPE_VIS_VRAM', 'AMDSMI_MEM_TYPE_VRAM', 'AMDSMI_MM_UVD', 'AMDSMI_MM_VCE', 'AMDSMI_MM_VCN', - 'AMDSMI_MM__MAX', 'AMDSMI_PROCESSOR_TYPE_AMD_APU', + 'AMDSMI_MM__MAX', 'AMDSMI_POWER_CAP_TYPE_PPT0', + 'AMDSMI_POWER_CAP_TYPE_PPT1', 'AMDSMI_PROCESSOR_TYPE_AMD_APU', 'AMDSMI_PROCESSOR_TYPE_AMD_CPU', 'AMDSMI_PROCESSOR_TYPE_AMD_CPU_CORE', 'AMDSMI_PROCESSOR_TYPE_AMD_GPU', @@ -3466,11 +3479,12 @@ __all__ = \ 'amdsmi_get_processor_handles_by_type', 'amdsmi_get_processor_info', 'amdsmi_get_processor_type', 'amdsmi_get_soc_pstate', 'amdsmi_get_socket_handles', - 'amdsmi_get_socket_info', 'amdsmi_get_temp_metric', - 'amdsmi_get_threads_per_core', 'amdsmi_get_utilization_count', - 'amdsmi_get_violation_status', 'amdsmi_get_xgmi_info', - 'amdsmi_get_xgmi_plpd', 'amdsmi_gpu_block_t', - 'amdsmi_gpu_cache_info_t', 'amdsmi_gpu_control_counter', + 'amdsmi_get_socket_info', 'amdsmi_get_supported_power_cap', + 'amdsmi_get_temp_metric', 'amdsmi_get_threads_per_core', + 'amdsmi_get_utilization_count', 'amdsmi_get_violation_status', + 'amdsmi_get_xgmi_info', 'amdsmi_get_xgmi_plpd', + 'amdsmi_gpu_block_t', 'amdsmi_gpu_cache_info_t', + 'amdsmi_gpu_control_counter', 'amdsmi_gpu_counter_group_supported', 'amdsmi_gpu_create_counter', 'amdsmi_gpu_destroy_counter', 'amdsmi_gpu_driver_reload', 'amdsmi_gpu_metrics_t', 'amdsmi_gpu_read_counter', @@ -3489,8 +3503,8 @@ __all__ = \ 'amdsmi_od_vddc_point_t', 'amdsmi_od_volt_curve_t', 'amdsmi_od_volt_freq_data_t', 'amdsmi_p2p_capability_t', 'amdsmi_pcie_bandwidth_t', 'amdsmi_pcie_info_t', - 'amdsmi_power_cap_info_t', 'amdsmi_power_info_t', - 'amdsmi_power_profile_preset_masks_t', + 'amdsmi_power_cap_info_t', 'amdsmi_power_cap_type_t', + 'amdsmi_power_info_t', 'amdsmi_power_profile_preset_masks_t', 'amdsmi_power_profile_status_t', 'amdsmi_proc_info_t', 'amdsmi_process_handle_t', 'amdsmi_process_info_t', 'amdsmi_processor_handle', 'amdsmi_range_t', diff --git a/rocm_smi/include/rocm_smi/rocm_smi.h b/rocm_smi/include/rocm_smi/rocm_smi.h index 847a4d0092..9474233b4f 100644 --- a/rocm_smi/include/rocm_smi/rocm_smi.h +++ b/rocm_smi/include/rocm_smi/rocm_smi.h @@ -668,6 +668,17 @@ typedef enum { typedef rsmi_power_profile_preset_masks_t rsmi_power_profile_preset_masks; /// \endcond +/** + * @brief Power Cap Package Power Tracking (PPT) type + */ +typedef enum { + RSMI_POWER_CAP_TYPE_PPT0, //!< PPT0 power cap; lower limit, filtered input + RSMI_POWER_CAP_TYPE_PPT1, //!< PPT1 power cap; higher limit, raw input +} rsmi_power_cap_type_t; +/// \cond Ignore in docs. +typedef rsmi_power_cap_type_t rsmi_power_cap_type; +/// \endcond + /** * @brief This enum is used to identify different GPU blocks. */ @@ -2510,6 +2521,9 @@ rsmi_dev_power_cap_get(uint32_t dv_ind, uint32_t sensor_ind, uint64_t *cap); * * @param[in] dv_ind a device index * + * @param[in] sensor_ind a 0-based sensor index. Normally, this will be 0. + * If a device has more than one sensor, it could be greater than 0. + * * @param[inout] default_cap a pointer to a uint64_t that indicates the default * power cap, in microwatts * If this parameter is nullptr, this function will return @@ -2523,7 +2537,7 @@ rsmi_dev_power_cap_get(uint32_t dv_ind, uint32_t sensor_ind, uint64_t *cap); * @retval ::RSMI_STATUS_INVALID_ARGS the provided arguments are not valid */ rsmi_status_t -rsmi_dev_power_cap_default_get(uint32_t dv_ind, uint64_t *default_cap); +rsmi_dev_power_cap_default_get(uint32_t dv_ind, uint32_t sensor_ind, uint64_t *default_cap); /** * @brief Get the range of valid values for the power cap @@ -2612,6 +2626,29 @@ rsmi_dev_power_cap_set(uint32_t dv_ind, uint32_t sensor_ind, uint64_t cap); rsmi_status_t rsmi_dev_power_profile_set(uint32_t dv_ind, uint32_t reserved, rsmi_power_profile_preset_masks_t profile); + + /** + * @brief Query the supported power cap sensors and their types for a device. + * + * @ingroup tagPowerControl + * + * @platform{gpu_bm_linux} @platform{host} + * + * @details This function returns the number of supported power cap sensors for the given device, + * including their sensor indices and types (e.g., PPT0, PPT1). + * + * @param[in] dv_ind A device index. + * @param[out] sensor_count Pointer to a uint32_t that will be set to the number of supported sensors. + * @param[out] sensor_inds Pointer to an array of uint32_t to be filled with sensor indices. + * The array must be allocated by the caller with enough space. + * @param[out] sensor_types Pointer to an array of rsmi_power_cap_type_t to be filled with sensor types. + * The array must be allocated by the caller with enough space. + * + * @return ::rsmi_status_t | ::RSMI_STATUS_SUCCESS on success, non-zero on fail. + */ +rsmi_status_t +rsmi_dev_supported_power_cap_get(uint32_t dv_ind, uint32_t *sensor_count, + uint32_t *sensor_inds, rsmi_power_cap_type_t *sensor_types); /** @} */ // end of PowerCont /*****************************************************************************/ diff --git a/rocm_smi/src/rocm_smi.cc b/rocm_smi/src/rocm_smi.cc index 1b193f9924..70eaab6253 100644 --- a/rocm_smi/src/rocm_smi.cc +++ b/rocm_smi/src/rocm_smi.cc @@ -3970,19 +3970,19 @@ rsmi_dev_energy_count_get(uint32_t dv_ind, uint64_t *power, } rsmi_status_t -rsmi_dev_power_cap_default_get(uint32_t dv_ind, uint64_t *default_cap) { +rsmi_dev_power_cap_default_get(uint32_t dv_ind, uint32_t sensor_ind, uint64_t *default_cap) { TRY std::ostringstream ss; ss << __PRETTY_FUNCTION__ << "| ======= start ======="; LOG_TRACE(ss); - uint32_t sensor_ind = 1; // power sysfs files have 1-based indices - CHK_SUPPORT_SUBVAR_ONLY(default_cap, sensor_ind) + uint32_t sensor_ind_adjust = sensor_ind + 1; // power sysfs files have 1-based indices + CHK_SUPPORT_SUBVAR_ONLY(default_cap, sensor_ind_adjust) rsmi_status_t ret; DEVICE_MUTEX - ret = get_dev_mon_value(amd::smi::kMonPowerCapDefault, dv_ind, sensor_ind, default_cap); + ret = get_dev_mon_value(amd::smi::kMonPowerCapDefault, dv_ind, sensor_ind_adjust, default_cap); return ret; CATCH @@ -4105,6 +4105,58 @@ rsmi_dev_power_profile_set(uint32_t dv_ind, uint32_t dummy, CATCH } +rsmi_status_t +rsmi_dev_supported_power_cap_get(uint32_t dv_ind, uint32_t *sensor_count, + uint32_t *sensor_inds, rsmi_power_cap_type_t *sensor_types) { + TRY + std::ostringstream ss; + ss << __PRETTY_FUNCTION__ << "| ======= start ======="; + LOG_TRACE(ss); + + if (!sensor_count || !sensor_inds || !sensor_types) { + return RSMI_STATUS_INVALID_ARGS; + } + GET_DEV_FROM_INDX + DEVICE_MUTEX + + const uint8_t RSMI_MAX_POWER_CAP_SENSORS = 2; + uint32_t sensor_count_local = 0; + // try to read the power*_label file. If the read succeeds, that indicates that exists and update the sensor count and sensor indices accordingly + for (uint32_t i = 0; i < RSMI_MAX_POWER_CAP_SENSORS; ++i) { + // skip if monitor doesn't exist (e.g., in virtualized/partitioned environments) + if (dev->monitor() == nullptr) { + continue; + } + + std::string val; + // sensor_ind for power starts at 1 + uint32_t adjusted_index = i + 1; + rsmi_status_t ret = amd::smi::ErrnoToRsmiStatus(dev->monitor()->readMonitor(amd::smi::kMonPowerLabel, adjusted_index, &val)); + if (ret != RSMI_STATUS_SUCCESS) { + // if the read fails, then the sensor does not exist or is otherwise inaccessible for some reason + continue; + } + // if val is PPT, then sensor_type is PPT0. If val is PPT1 then sensor_type is PPT1 + if (val == "PPT") { + sensor_types[sensor_count_local] = RSMI_POWER_CAP_TYPE_PPT0; + } + else if (val == "PPT1") { + sensor_types[sensor_count_local] = RSMI_POWER_CAP_TYPE_PPT1; + } + sensor_inds[sensor_count_local] = i; + sensor_count_local++; + } + + // If no sensors were found, return not supported + if (sensor_count_local == 0) { + return RSMI_STATUS_NOT_SUPPORTED; + } + + *sensor_count = sensor_count_local; + return RSMI_STATUS_SUCCESS; + CATCH +} + rsmi_status_t rsmi_dev_memory_total_get(uint32_t dv_ind, rsmi_memory_type_t mem_type, uint64_t *total) { diff --git a/src/amd_smi/amd_smi.cc b/src/amd_smi/amd_smi.cc index 42aaa21f21..4de244f22c 100644 --- a/src/amd_smi/amd_smi.cc +++ b/src/amd_smi/amd_smi.cc @@ -3435,39 +3435,21 @@ amdsmi_get_power_cap_info(amdsmi_processor_handle processor_handle, int power_cap = 0; int dpm = 0; - auto smi_power_cap_status = smi_amdgpu_get_power_cap(gpudevice, &power_cap); - if ((smi_power_cap_status == AMDSMI_STATUS_SUCCESS) && !set_ret_success) - set_ret_success = true; - info->power_cap = power_cap; + auto smi_power_cap_status = rsmi_wrapper(rsmi_dev_power_cap_get, processor_handle, 0, + sensor_ind, &(info->power_cap)); + status = smi_amdgpu_get_ranges(gpudevice, AMDSMI_CLK_TYPE_GFX, NULL, NULL, &dpm, NULL); - if ((status == AMDSMI_STATUS_SUCCESS) && !set_ret_success) - set_ret_success = true; info->dpm_cap = dpm; - if (smi_power_cap_status != AMDSMI_STATUS_SUCCESS) { - status = rsmi_wrapper(rsmi_dev_power_cap_get, processor_handle, 0, - sensor_ind, &(info->power_cap)); - if ((status == AMDSMI_STATUS_SUCCESS) && !set_ret_success) - set_ret_success = true; - } - // Get other information from rocm-smi status = rsmi_wrapper(rsmi_dev_power_cap_default_get, processor_handle, 0, - &(info->default_power_cap)); - - if ((status == AMDSMI_STATUS_SUCCESS) && !set_ret_success) - set_ret_success = true; - + sensor_ind, &(info->default_power_cap)); status = rsmi_wrapper(rsmi_dev_power_cap_range_get, processor_handle, 0, sensor_ind, &(info->max_power_cap), &(info->min_power_cap)); - - if ((status == AMDSMI_STATUS_SUCCESS) && !set_ret_success) - set_ret_success = true; - - return set_ret_success ? AMDSMI_STATUS_SUCCESS : AMDSMI_STATUS_NOT_SUPPORTED; + return smi_power_cap_status; } amdsmi_status_t @@ -3478,6 +3460,19 @@ amdsmi_set_power_cap(amdsmi_processor_handle processor_handle, sensor_ind, cap); } +amdsmi_status_t +amdsmi_get_supported_power_cap(amdsmi_processor_handle processor_handle, uint32_t *sensor_count, + uint32_t *sensor_inds, amdsmi_power_cap_type_t *sensor_types) { + AMDSMI_CHECK_INIT(); + if (!sensor_count || !sensor_inds || !sensor_types) { + return AMDSMI_STATUS_INVAL; + } + + return rsmi_wrapper(rsmi_dev_supported_power_cap_get, processor_handle, 0, + sensor_count, sensor_inds, + reinterpret_cast(sensor_types)); +} + amdsmi_status_t amdsmi_get_gpu_power_profile_presets(amdsmi_processor_handle processor_handle, uint32_t sensor_ind, @@ -4482,7 +4477,8 @@ amdsmi_get_power_info(amdsmi_processor_handle processor_handle, amdsmi_power_inf } int power_limit = 0; - amdsmi_status_t status2 = smi_amdgpu_get_power_cap(gpu_device, &power_limit); + // default the sensor_ind here to 0 + amdsmi_status_t status2 = smi_amdgpu_get_power_cap(gpu_device, 0, &power_limit); if (status2 == AMDSMI_STATUS_SUCCESS) { info->power_limit = power_limit; } diff --git a/src/amd_smi/amd_smi_utils.cc b/src/amd_smi/amd_smi_utils.cc index f399d9f4e2..22230f7693 100644 --- a/src/amd_smi/amd_smi_utils.cc +++ b/src/amd_smi/amd_smi_utils.cc @@ -243,7 +243,7 @@ amdsmi_status_t smi_amdgpu_get_board_info(amd::smi::AMDSmiGPUDevice* device, amd return AMDSMI_STATUS_SUCCESS; } -amdsmi_status_t smi_amdgpu_get_power_cap(amd::smi::AMDSmiGPUDevice* device, int *cap) +amdsmi_status_t smi_amdgpu_get_power_cap(amd::smi::AMDSmiGPUDevice* device, uint32_t sensor_ind, int *cap) { constexpr int DATA_SIZE = 16; char val[DATA_SIZE]; @@ -257,7 +257,7 @@ amdsmi_status_t smi_amdgpu_get_power_cap(amd::smi::AMDSmiGPUDevice* device, int if (ret) return ret; - fullpath += "/power1_cap"; + fullpath += "/power" + std::to_string(sensor_ind + 1) + "_cap"; std::ifstream file(fullpath.c_str(), std::ifstream::in); if (!file.is_open()) { return AMDSMI_STATUS_API_FAILED; diff --git a/tests/amd_smi_test/functional/power_cap_read_write.cc b/tests/amd_smi_test/functional/power_cap_read_write.cc index 1d599e662f..6725ada332 100644 --- a/tests/amd_smi_test/functional/power_cap_read_write.cc +++ b/tests/amd_smi_test/functional/power_cap_read_write.cc @@ -66,7 +66,7 @@ void TestPowerCapReadWrite::Close() { TestBase::Close(); } -void TestPowerCapReadWrite::SetCheckPowerCap(std::string msg, uint32_t dv_ind, uint64_t &curr_cap, +void TestPowerCapReadWrite::SetCheckPowerCap(std::string msg, uint32_t dv_ind, uint32_t sensor_ind, uint64_t &curr_cap, uint64_t &new_cap, amdsmi_status_t &ret) { amdsmi_status_t ret_expected; amdsmi_power_cap_info_t info; @@ -81,7 +81,7 @@ void TestPowerCapReadWrite::SetCheckPowerCap(std::string msg, uint32_t dv_ind, u std::cout << "[Before Set] Setting new cap to " << new_cap << "..." << std::endl; } start = clock(); - ret = amdsmi_set_power_cap(processor_handles_[dv_ind], 0, new_cap); + ret = amdsmi_set_power_cap(processor_handles_[dv_ind], sensor_ind, new_cap); end = clock(); cpu_time_used = (static_cast(end - start)) * 1000000UL / CLOCKS_PER_SEC; @@ -98,7 +98,7 @@ void TestPowerCapReadWrite::SetCheckPowerCap(std::string msg, uint32_t dv_ind, u return; } - ret = amdsmi_get_power_cap_info(processor_handles_[dv_ind], 0, &info); + ret = amdsmi_get_power_cap_info(processor_handles_[dv_ind], sensor_ind, &info); CHK_ERR_ASRT(ret) curr_cap = info.power_cap; @@ -133,170 +133,190 @@ void TestPowerCapReadWrite::Run(void) { for (uint32_t dv_ind = 0; dv_ind < num_monitor_devs(); ++dv_ind) { PrintDeviceHeader(processor_handles_[dv_ind]); + // verify amdsmi_get_supported_power_cap_info() works amdsmi_power_cap_info_t info; - // Verify api support checking functionality is working - ret = amdsmi_get_power_cap_info(processor_handles_[dv_ind], 0, nullptr); + uint32_t sensor_count = 0; + uint32_t sensor_inds[2]; + amdsmi_power_cap_type_t sensor_types[2]; + ret = amdsmi_get_supported_power_cap(processor_handles_[dv_ind], &sensor_count, sensor_inds, nullptr); ASSERT_EQ(ret, AMDSMI_STATUS_INVAL); - ret = amdsmi_get_power_cap_info(processor_handles_[dv_ind], 0, &info); - if (ret == AMDSMI_STATUS_NOT_SUPPORTED) { - std::cout << "\t**amdsmi_get_power_cap_info(): Not supported on this machine" << std::endl; - ASSERT_EQ(ret, AMDSMI_STATUS_NOT_SUPPORTED); - continue; - } - min_cap = info.min_power_cap; - max_cap = info.max_power_cap; - default_cap = info.default_power_cap; - curr_cap = info.power_cap; - orig_cap = curr_cap; - - new_cap = (max_cap + min_cap)/2; - IF_VERB(STANDARD) { - std::cout << "[Before Set] Default Power Cap: " << default_cap << " uW" << std::endl; - std::cout << "[Before Set] Current Power Cap: " << curr_cap << " uW" << std::endl; - std::cout << "[Before Set] Power Cap Range [max to min]: " - << max_cap << " uW to " << min_cap << " uW" << std::endl; - std::cout << "[Before Set] Setting new cap to " << new_cap << "..." << std::endl; + ret = amdsmi_get_supported_power_cap(processor_handles_[dv_ind], &sensor_count, sensor_inds, sensor_types); + if (ret != AMDSMI_STATUS_SUCCESS) { + ASSERT_EQ(ret, AMDSMI_STATUS_NOT_SUPPORTED); + std::cout << "\t**amdsmi_get_supported_power_cap(): No supported Package Power Tracking Types on this machine" << std::endl; + continue; } - // Check if power cap is within the range - // skip the test otherwise - if (new_cap < min_cap || new_cap > max_cap || curr_cap == 0) { - std::cout << "\t** Requested Power cap (" << new_cap - << " uW) cannot be changed for device #" << dv_ind << "." - << "\nCurrent Power Cap: " << curr_cap - << " uW, Min Power Cap: " << min_cap - << " uW, Max Power Cap: " << max_cap - << " uW.\n[WARN] If current power cap is 0 uW, this means we cannot change" - << " this device's current power cap. Skipping test for this device." - << std::endl; - continue; - } - ret = AMDSMI_STATUS_SUCCESS; - SetCheckPowerCap("Setting to Average Power Cap", dv_ind, curr_cap, new_cap, ret); - if (ret == AMDSMI_STATUS_NOT_SUPPORTED) { - continue; - } - IF_VERB(STANDARD) { - if (!new_cap) - std::cout << "\t** Power cap requested (" << new_cap - << " uW) is failed to set for " << dv_ind << std::endl; - } + for (uint32_t i = 0; i < sensor_count; ++i) { + std::cout << "\tPower Cap Sensor Index: " << sensor_inds[i] + << ", Type: ppt" << (sensor_types[i]) << std::endl; - if (min_cap > 0) { - new_cap = min_cap; + amdsmi_power_cap_info_t info; + // Verify api support checking functionality is working + ret = amdsmi_get_power_cap_info(processor_handles_[dv_ind], sensor_inds[i], nullptr); + ASSERT_EQ(ret, AMDSMI_STATUS_INVAL); + + ret = amdsmi_get_power_cap_info(processor_handles_[dv_ind], sensor_inds[i], &info); + if (ret == AMDSMI_STATUS_NOT_SUPPORTED) { + std::cout << "\t**amdsmi_get_power_cap_info(): Not supported on this machine" << std::endl; + ASSERT_EQ(ret, AMDSMI_STATUS_NOT_SUPPORTED); + continue; + } + min_cap = info.min_power_cap; + max_cap = info.max_power_cap; + default_cap = info.default_power_cap; + curr_cap = info.power_cap; + orig_cap = curr_cap; + + new_cap = (max_cap + min_cap)/2; + IF_VERB(STANDARD) { + std::cout << "[Before Set] Default Power Cap: " << default_cap << " uW" << std::endl; + std::cout << "[Before Set] Current Power Cap: " << curr_cap << " uW" << std::endl; + std::cout << "[Before Set] Power Cap Range [max to min]: " + << max_cap << " uW to " << min_cap << " uW" << std::endl; + std::cout << "[Before Set] Setting new cap to " << new_cap << "..." << std::endl; + } + + // Check if power cap is within the range + // skip the test otherwise + if (new_cap < min_cap || new_cap > max_cap || curr_cap == 0) { + std::cout << "\t** Requested Power cap (" << new_cap + << " uW) cannot be changed for device #" << dv_ind << "." + << "\nCurrent Power Cap: " << curr_cap + << " uW, Min Power Cap: " << min_cap + << " uW, Max Power Cap: " << max_cap + << " uW.\n[WARN] If current power cap is 0 uW, this means we cannot change" + << " this device's current power cap. Skipping test for this device." + << std::endl; + continue; + } ret = AMDSMI_STATUS_SUCCESS; - SetCheckPowerCap("Setting to Min Power Cap", dv_ind, curr_cap, new_cap, ret); + SetCheckPowerCap("Setting to Average Power Cap", dv_ind, sensor_inds[i], curr_cap, new_cap, ret); + if (ret == AMDSMI_STATUS_NOT_SUPPORTED) { + continue; + } IF_VERB(STANDARD) { if (!new_cap) std::cout << "\t** Power cap requested (" << new_cap << " uW) is failed to set for " << dv_ind << std::endl; } - new_cap = uint64_t(min_cap - 1); + if (min_cap > 0) { + new_cap = min_cap; + ret = AMDSMI_STATUS_SUCCESS; + SetCheckPowerCap("Setting to Min Power Cap", dv_ind, sensor_inds[i], curr_cap, new_cap, ret); + IF_VERB(STANDARD) { + if (!new_cap) + std::cout << "\t** Power cap requested (" << new_cap + << " uW) is failed to set for " << dv_ind << std::endl; + } + + new_cap = uint64_t(min_cap - 1); + ret = AMDSMI_STATUS_INVAL; + SetCheckPowerCap("Setting to Min Power Cap - 1", dv_ind, sensor_inds[i], curr_cap, new_cap, ret); + if (ret != AMDSMI_STATUS_INVAL) { + IF_VERB(STANDARD) { + if (!new_cap) + std::cout << "\t** Power cap requested (" << new_cap + << " uW) is failed to set for " << dv_ind << std::endl; + } + } + + new_cap = uint64_t(static_cast(min_cap) * 0.10F); + ret = AMDSMI_STATUS_INVAL; + SetCheckPowerCap("Setting to Min Power Cap * 0.10", dv_ind, sensor_inds[i], curr_cap, new_cap, ret); + if (ret != AMDSMI_STATUS_INVAL) { + IF_VERB(STANDARD) { + if (!new_cap) + std::cout << "\t** Power cap requested (" << new_cap << " uW) is failed to set for " + << dv_ind << std::endl; + } + } + } else { + std::cout << "\tPower cap requested is less than or equal to 0, skipping test for device #" + << dv_ind << std::endl; + } + + new_cap = max_cap; + ret = AMDSMI_STATUS_SUCCESS; + SetCheckPowerCap("Setting to Max Power Cap", dv_ind, sensor_inds[i], curr_cap, new_cap, ret); + IF_VERB(STANDARD) { + if (!new_cap) + std::cout << "\t** Power cap requested (" << new_cap + << " uW) is failed to set for " << dv_ind << std::endl; + } + + new_cap = uint64_t(max_cap + 1); ret = AMDSMI_STATUS_INVAL; - SetCheckPowerCap("Setting to Min Power Cap - 1", dv_ind, curr_cap, new_cap, ret); + SetCheckPowerCap("Setting to Max Power Cap + 1", dv_ind, sensor_inds[i], curr_cap, new_cap, ret); if (ret != AMDSMI_STATUS_INVAL) { IF_VERB(STANDARD) { if (!new_cap) std::cout << "\t** Power cap requested (" << new_cap - << " uW) is failed to set for " << dv_ind << std::endl; + << " uW) failed to set for " << dv_ind << std::endl; } } - new_cap = uint64_t(static_cast(min_cap) * 0.10F); + new_cap = uint64_t(max_cap * 10); ret = AMDSMI_STATUS_INVAL; - SetCheckPowerCap("Setting to Min Power Cap * 0.10", dv_ind, curr_cap, new_cap, ret); + SetCheckPowerCap("Setting to Max Power Cap * 10", dv_ind, sensor_inds[i], curr_cap, new_cap, ret); if (ret != AMDSMI_STATUS_INVAL) { IF_VERB(STANDARD) { - if (!new_cap) - std::cout << "\t** Power cap requested (" << new_cap << " uW) is failed to set for " - << dv_ind << std::endl; + if (!new_cap) + std::cout << "\t** Power cap requested (" << new_cap + << " uW) is failed to set for " << dv_ind << std::endl; } } - } else { - std::cout << "\tPower cap requested is less than or equal to 0, skipping test for device #" - << dv_ind << std::endl; - } - new_cap = max_cap; - ret = AMDSMI_STATUS_SUCCESS; - SetCheckPowerCap("Setting to Max Power Cap", dv_ind, curr_cap, new_cap, ret); - IF_VERB(STANDARD) { - if (!new_cap) - std::cout << "\t** Power cap requested (" << new_cap - << " uW) is failed to set for " << dv_ind << std::endl; - } - - new_cap = uint64_t(max_cap + 1); - ret = AMDSMI_STATUS_INVAL; - SetCheckPowerCap("Setting to Max Power Cap + 1", dv_ind, curr_cap, new_cap, ret); - if (ret != AMDSMI_STATUS_INVAL) { + // Reset to default power cap -> which is typically the same as the max power cap IF_VERB(STANDARD) { - if (!new_cap) - std::cout << "\t** Power cap requested (" << new_cap - << " uW) failed to set for " << dv_ind << std::endl; + std::cout << "Setting to default power Cap" << std::endl; + std::cout << "[Before Set] Current Power Cap: " << curr_cap << " uW" << std::endl; + std::cout << "[Before Set] Default Power Cap (default_cap): " + << default_cap << "..." << std::endl; } - } + ret = amdsmi_set_power_cap(processor_handles_[dv_ind], sensor_inds[i], default_cap); + CHK_ERR_ASRT(ret) + + ret = amdsmi_get_power_cap_info(processor_handles_[dv_ind], sensor_inds[i], &info); + CHK_ERR_ASRT(ret) + curr_cap = info.power_cap; - new_cap = uint64_t(max_cap * 10); - ret = AMDSMI_STATUS_INVAL; - SetCheckPowerCap("Setting to Max Power Cap * 10", dv_ind, curr_cap, new_cap, ret); - if (ret != AMDSMI_STATUS_INVAL) { IF_VERB(STANDARD) { - if (!new_cap) - std::cout << "\t** Power cap requested (" << new_cap - << " uW) is failed to set for " << dv_ind << std::endl; + std::cout << "[After Set] Current Power Cap: " << curr_cap << " uW" << std::endl; + std::cout << "[After Set] Requested Power Cap (default_cap): " << default_cap << " uW" + << std::endl; + std::cout << "[After Set] Power Cap Range [max to min]: " << max_cap << " uW to " + << min_cap << " uW" << std::endl; } + // Confirm in watts the values are equal + ASSERT_EQ(default_cap/MICRO_CONVERSION, curr_cap/MICRO_CONVERSION); + + // Reset to system's original power cap before the test started + IF_VERB(STANDARD) { + std::cout << "Resetting Power Cap to original power cap" << std::endl; + std::cout << "[Before Reset] Current Power Cap: " << curr_cap << " uW" << std::endl; + std::cout << "[Before Reset] Original Power Cap (orig_cap): " + << orig_cap << "..." << std::endl; + } + ret = amdsmi_set_power_cap(processor_handles_[dv_ind], sensor_inds[i], orig_cap); + CHK_ERR_ASRT(ret) + + ret = amdsmi_get_power_cap_info(processor_handles_[dv_ind], sensor_inds[i], &info); + CHK_ERR_ASRT(ret) + curr_cap = info.power_cap; + + IF_VERB(STANDARD) { + std::cout << "[After Reset] Current Power Cap: " << curr_cap << " uW" << std::endl; + std::cout << "[After Reset] Requested Power Cap (orig_cap): " << orig_cap << " uW" + << std::endl; + std::cout << "[After Reset] Power Cap Range [max to min]: " << max_cap << " uW to " + << min_cap << " uW" << std::endl; + } + + // Confirm in watts the values are equal + ASSERT_EQ(orig_cap/MICRO_CONVERSION, curr_cap/MICRO_CONVERSION); } - - // Reset to default power cap -> which is typically the same as the max power cap - IF_VERB(STANDARD) { - std::cout << "Setting to default power Cap" << std::endl; - std::cout << "[Before Set] Current Power Cap: " << curr_cap << " uW" << std::endl; - std::cout << "[Before Set] Default Power Cap (default_cap): " - << default_cap << "..." << std::endl; - } - ret = amdsmi_set_power_cap(processor_handles_[dv_ind], 0, default_cap); - CHK_ERR_ASRT(ret) - - ret = amdsmi_get_power_cap_info(processor_handles_[dv_ind], 0, &info); - CHK_ERR_ASRT(ret) - curr_cap = info.power_cap; - - IF_VERB(STANDARD) { - std::cout << "[After Set] Current Power Cap: " << curr_cap << " uW" << std::endl; - std::cout << "[After Set] Requested Power Cap (default_cap): " << default_cap << " uW" - << std::endl; - std::cout << "[After Set] Power Cap Range [max to min]: " << max_cap << " uW to " - << min_cap << " uW" << std::endl; - } - // Confirm in watts the values are equal - ASSERT_EQ(default_cap/MICRO_CONVERSION, curr_cap/MICRO_CONVERSION); - - // Reset to system's original power cap before the test started - IF_VERB(STANDARD) { - std::cout << "Resetting Power Cap to original power cap" << std::endl; - std::cout << "[Before Reset] Current Power Cap: " << curr_cap << " uW" << std::endl; - std::cout << "[Before Reset] Original Power Cap (orig_cap): " - << orig_cap << "..." << std::endl; - } - ret = amdsmi_set_power_cap(processor_handles_[dv_ind], 0, orig_cap); - CHK_ERR_ASRT(ret) - - ret = amdsmi_get_power_cap_info(processor_handles_[dv_ind], 0, &info); - CHK_ERR_ASRT(ret) - curr_cap = info.power_cap; - - IF_VERB(STANDARD) { - std::cout << "[After Reset] Current Power Cap: " << curr_cap << " uW" << std::endl; - std::cout << "[After Reset] Requested Power Cap (orig_cap): " << orig_cap << " uW" - << std::endl; - std::cout << "[After Reset] Power Cap Range [max to min]: " << max_cap << " uW to " - << min_cap << " uW" << std::endl; - } - - // Confirm in watts the values are equal - ASSERT_EQ(orig_cap/MICRO_CONVERSION, curr_cap/MICRO_CONVERSION); } } diff --git a/tests/amd_smi_test/functional/power_cap_read_write.h b/tests/amd_smi_test/functional/power_cap_read_write.h index 675a453c07..bde0f394ec 100644 --- a/tests/amd_smi_test/functional/power_cap_read_write.h +++ b/tests/amd_smi_test/functional/power_cap_read_write.h @@ -29,7 +29,7 @@ class TestPowerCapReadWrite : public TestBase { public: TestPowerCapReadWrite(); - void SetCheckPowerCap(std::string msg, uint32_t dv_ind, uint64_t &curr_cap, + void SetCheckPowerCap(std::string msg, uint32_t dv_ind, uint32_t sensor_ind, uint64_t &curr_cap, uint64_t &new_cap, amdsmi_status_t &ret); // @Brief: Destructor for test case of TestPowerCapReadWrite diff --git a/tests/python_unittest/integration_test.py b/tests/python_unittest/integration_test.py index 578a92417a..6c9a12bd98 100755 --- a/tests/python_unittest/integration_test.py +++ b/tests/python_unittest/integration_test.py @@ -733,7 +733,7 @@ class TestAmdSmiPythonInterface(unittest.TestCase): power_info['power_limit'])) try: print("\n###Test amdsmi_get_power_cap_info \n") - power_cap_info = amdsmi.amdsmi_get_power_cap_info(processors[i]) + power_cap_info = amdsmi.amdsmi_get_power_cap_info(processors[i], 0) except amdsmi.AmdSmiLibraryException as e: self._check_exception(e) continue