lemonade-sdk 8.1.8__tar.gz → 8.1.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of lemonade-sdk might be problematic. Click here for more details.

Files changed (84) hide show
  1. {lemonade_sdk-8.1.8/src/lemonade_sdk.egg-info → lemonade_sdk-8.1.9}/PKG-INFO +2 -2
  2. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/setup.py +1 -1
  3. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/cli.py +47 -1
  4. lemonade_sdk-8.1.9/src/lemonade/profilers/agt_power.py +437 -0
  5. lemonade_sdk-8.1.9/src/lemonade/profilers/hwinfo_power.py +429 -0
  6. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/llamacpp/utils.py +15 -4
  7. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/oga/load.py +15 -2
  8. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/server/llamacpp.py +3 -12
  9. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/server/static/js/chat.js +481 -242
  10. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/server/static/js/models.js +106 -29
  11. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/server/static/js/shared.js +4 -2
  12. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/server/static/styles.css +114 -68
  13. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/server/static/webapp.html +19 -23
  14. lemonade_sdk-8.1.9/src/lemonade/version.py +1 -0
  15. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9/src/lemonade_sdk.egg-info}/PKG-INFO +2 -2
  16. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade_sdk.egg-info/SOURCES.txt +2 -0
  17. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade_sdk.egg-info/requires.txt +3 -1
  18. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade_server/server_models.json +24 -6
  19. lemonade_sdk-8.1.8/src/lemonade/version.py +0 -1
  20. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/LICENSE +0 -0
  21. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/NOTICE.md +0 -0
  22. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/README.md +0 -0
  23. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/pyproject.toml +0 -0
  24. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/setup.cfg +0 -0
  25. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/__init__.py +0 -0
  26. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/api.py +0 -0
  27. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/cache.py +0 -0
  28. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/common/__init__.py +0 -0
  29. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/common/build.py +0 -0
  30. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/common/cli_helpers.py +0 -0
  31. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/common/exceptions.py +0 -0
  32. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/common/filesystem.py +0 -0
  33. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/common/inference_engines.py +0 -0
  34. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/common/network.py +0 -0
  35. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/common/printing.py +0 -0
  36. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/common/status.py +0 -0
  37. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/common/system_info.py +0 -0
  38. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/common/test_helpers.py +0 -0
  39. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/profilers/__init__.py +0 -0
  40. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/profilers/memory_tracker.py +0 -0
  41. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/profilers/profiler.py +0 -0
  42. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/sequence.py +0 -0
  43. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/state.py +0 -0
  44. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/__init__.py +0 -0
  45. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/accuracy.py +0 -0
  46. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/adapter.py +0 -0
  47. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/bench.py +0 -0
  48. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/huggingface/bench.py +0 -0
  49. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/huggingface/load.py +0 -0
  50. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/huggingface/utils.py +0 -0
  51. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/humaneval.py +0 -0
  52. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/llamacpp/bench.py +0 -0
  53. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/llamacpp/load.py +0 -0
  54. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/management_tools.py +0 -0
  55. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/mmlu.py +0 -0
  56. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/oga/__init__.py +0 -0
  57. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/oga/bench.py +0 -0
  58. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/oga/utils.py +0 -0
  59. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/perplexity.py +0 -0
  60. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/prompt.py +0 -0
  61. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/report/__init__.py +0 -0
  62. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/report/llm_report.py +0 -0
  63. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/report/table.py +0 -0
  64. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/server/__init__.py +0 -0
  65. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/server/serve.py +0 -0
  66. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/server/static/favicon.ico +0 -0
  67. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/server/static/js/model-settings.js +0 -0
  68. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/server/tool_calls.py +0 -0
  69. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/server/tray.py +0 -0
  70. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/server/utils/port.py +0 -0
  71. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/server/utils/system_tray.py +0 -0
  72. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/server/utils/thread.py +0 -0
  73. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/server/webapp.py +0 -0
  74. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/server/wrapped_server.py +0 -0
  75. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade/tools/tool.py +0 -0
  76. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade_install/__init__.py +0 -0
  77. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade_install/install.py +0 -0
  78. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade_sdk.egg-info/dependency_links.txt +0 -0
  79. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade_sdk.egg-info/entry_points.txt +0 -0
  80. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade_sdk.egg-info/top_level.txt +0 -0
  81. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade_server/cli.py +0 -0
  82. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade_server/model_manager.py +0 -0
  83. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade_server/pydantic_models.py +0 -0
  84. {lemonade_sdk-8.1.8 → lemonade_sdk-8.1.9}/src/lemonade_server/settings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lemonade-sdk
3
- Version: 8.1.8
3
+ Version: 8.1.9
4
4
  Summary: Lemonade SDK: Your LLM Aide for Validation and Deployment
5
5
  Author-email: lemonade@amd.com
6
6
  Requires-Python: >=3.10, <3.14
@@ -16,7 +16,7 @@ Requires-Dist: numpy
16
16
  Requires-Dist: fasteners
17
17
  Requires-Dist: GitPython>=3.1.40
18
18
  Requires-Dist: psutil>=6.1.1
19
- Requires-Dist: wmi
19
+ Requires-Dist: wmi; platform_system == "Windows"
20
20
  Requires-Dist: py-cpuinfo
21
21
  Requires-Dist: pytz
22
22
  Requires-Dist: zstandard
@@ -35,7 +35,7 @@ setup(
35
35
  "fasteners",
36
36
  "GitPython>=3.1.40",
37
37
  "psutil>=6.1.1",
38
- "wmi",
38
+ "wmi; platform_system == 'Windows'",
39
39
  "py-cpuinfo",
40
40
  "pytz",
41
41
  "zstandard",
@@ -12,6 +12,41 @@ from lemonade.sequence import Sequence
12
12
  from lemonade.tools.management_tools import Cache, Version, SystemInfo
13
13
  from lemonade.state import State
14
14
 
15
+
16
+ def get_available_profilers(warn_missing=False):
17
+ """Get list of available profilers, with conditional imports for optional dependencies.
18
+
19
+ Args:
20
+ warn_missing: If True, print warnings for missing profilers. If False, fail silently.
21
+ """
22
+ profilers = [MemoryTracker]
23
+
24
+ try:
25
+ from lemonade.profilers.hwinfo_power import HWINFOPowerProfiler
26
+
27
+ profilers.append(HWINFOPowerProfiler)
28
+ except ImportError:
29
+ if warn_missing:
30
+ print(
31
+ "Warning: HWINFOPowerProfiler not available. "
32
+ "Install lemonade with dev extras: "
33
+ "pip install lemonade-sdk[dev]"
34
+ )
35
+ try:
36
+ from lemonade.profilers.agt_power import AGTPowerProfiler
37
+
38
+ profilers.append(AGTPowerProfiler)
39
+ except ImportError:
40
+ if warn_missing:
41
+ print(
42
+ "Warning: AGTPowerProfiler not available. "
43
+ "Install lemonade with dev extras: "
44
+ "pip install lemonade-sdk[dev]"
45
+ )
46
+
47
+ return profilers
48
+
49
+
15
50
  from lemonade.tools.huggingface.load import HuggingfaceLoad
16
51
  from lemonade.tools.huggingface.bench import HuggingfaceBench
17
52
  from lemonade.tools.oga.load import OgaLoad
@@ -51,7 +86,7 @@ def main():
51
86
  ]
52
87
 
53
88
  # List the available profilers
54
- profilers = [MemoryTracker]
89
+ profilers = get_available_profilers()
55
90
 
56
91
  # Define the argument parser
57
92
  parser = cli.CustomArgumentParser(
@@ -85,6 +120,17 @@ https://github.com/lemonade-sdk/lemonade/blob/main/docs/README.md""",
85
120
  parser, tools, cli_name="lemonade"
86
121
  )
87
122
 
123
+ # Check if any profilers are being requested
124
+ requested_profilers = [
125
+ profiler.unique_name.replace("-", "_")
126
+ for profiler in profilers
127
+ if global_args.get(profiler.unique_name.replace("-", "_"), None) is not None
128
+ ]
129
+
130
+ # If profilers are requested, get the full list with warnings for missing ones
131
+ if requested_profilers:
132
+ get_available_profilers(warn_missing=True)
133
+
88
134
  profiler_instances = [
89
135
  profiler(global_args[profiler.unique_name.replace("-", "_")])
90
136
  for profiler in profilers
@@ -0,0 +1,437 @@
1
+ #
2
+ # This power profiler uses a tool that is not publicly available yet.
3
+ # Please see the power profiling documentation for download and install instructions.
4
+ #
5
+ # The power profiling functionality is currently not part of our continuous integration
6
+ # testing framework, primarily due to the setup overhead required from the above three items.
7
+ # We will revisit in the future if we face issues.
8
+ #
9
+
10
+ from datetime import datetime
11
+ import os
12
+ import platform
13
+ import textwrap
14
+ import time
15
+ import re
16
+ import subprocess
17
+ import matplotlib.pyplot as plt
18
+ import numpy as np
19
+ import lemonade.common.printing as printing
20
+ from lemonade.profilers import Profiler
21
+ from lemonade.tools.report.table import LemonadePerfTable, DictListStat
22
+ from lemonade.profilers.hwinfo_power import (
23
+ is_user_admin,
24
+ is_process_running,
25
+ read_data_from_csv,
26
+ )
27
+
28
+ AGT_PATH_ENV_VAR = "AGT_PATH"
29
+ DEFAULT_TRACK_POWER_INTERVAL_MS = 500
30
+ DEFAULT_TRACK_POWER_WARMUP_PERIOD = 5
31
+ POWER_USAGE_CSV_FILENAME = "power_usage_agt.csv"
32
+ POWER_USAGE_PNG_FILENAME = "power_usage_agt.png"
33
+
34
+
35
+ class Keys:
36
+ # Path to the file containing the power usage plot
37
+ POWER_USAGE_PLOT = "power_usage_plot_agt"
38
+ # Path to the file containing the power usage plot
39
+ POWER_USAGE_DATA = "power_usage_data_agt"
40
+ # Path to the file containing the power usage plot
41
+ POWER_USAGE_DATA_CSV = "power_usage_data_file_agt"
42
+ # Maximum power consumed by the APU processor package during the tools sequence
43
+ PEAK_PROCESSOR_PACKAGE_POWER = "peak_processor_package_power_agt"
44
+
45
+
46
+ # Add column to the Lemonade performance report table for the power data
47
+ LemonadePerfTable.table_descriptor["stat_columns"].append(
48
+ DictListStat(
49
+ "Power Usage (AGT)",
50
+ Keys.POWER_USAGE_DATA,
51
+ [
52
+ ("name", "{0}:"),
53
+ ("duration", "{0:.1f}s,"),
54
+ ("energy consumed", "{0:.1f} J"),
55
+ ],
56
+ )
57
+ )
58
+
59
+
60
+ class AGTPowerProfiler(Profiler):
61
+
62
+ unique_name = "power-agt"
63
+
64
+ # mapping from short name to full name of the measurement in the CSV file produced by AGT
65
+ columns_dict = {
66
+ "time": "Time Stamp",
67
+ "cpu_package_power": "CPU0 Power Correlation SOCKET Power ",
68
+ "npu_clock": "CPU0 Frequencies Actual Frequency NPUHCLK",
69
+ "gpu_clock": "CPU0 GFX GFX Freq Eff",
70
+ # for processors with classic and dense cores
71
+ "classic_cpu_usage": "CPU0 DPM Activity Monitors Busy Value CLASSIC_C0",
72
+ "dense_cpu_usage": "CPU0 DPM Activity Monitors Busy Value DENSE_C0",
73
+ # for multi CCD processors
74
+ "classic_cpu_usage_0": "CPU0 DPM Activity Monitors Busy Value CCD0_C0",
75
+ "classic_cpu_usage_1": "CPU0 DPM Activity Monitors Busy Value CCD1_C0",
76
+ #
77
+ "apu_stapm_value": "CPU0 INFRASTRUCTURE1 Value STAPM",
78
+ "apu_stapm_limit": "CPU0 INFRASTRUCTURE1 Limit STAPM",
79
+ "cpu_tdc_value": "CPU0 INFRASTRUCTURE1 Value TDC VDD",
80
+ "cpu_tdc_limit": "CPU0 INFRASTRUCTURE1 Limit TDC VDD",
81
+ "cpu_edc_value": "CPU0 EDC VDD Peak Telemetry Current ",
82
+ "cpu_edc_limit": "CPU0 EDC Effective VDD EDC Limit ",
83
+ "cpu_ppt_fast_value": "CPU0 INFRASTRUCTURE1 Value PPT FAST",
84
+ "cpu_ppt_fast_limit": "CPU0 INFRASTRUCTURE1 Limit PPT FAST",
85
+ "cpu_ppt_slow_value": "CPU0 INFRASTRUCTURE1 Value PPT SLOW",
86
+ "cpu_ppt_slow_limit": "CPU0 INFRASTRUCTURE1 Limit PPT SLOW",
87
+ "cpu_thermal_value_ccx0": "CPU0 INFRASTRUCTURE2 Value THM CCX0",
88
+ "cpu_thermal_limit_ccx0": "CPU0 INFRASTRUCTURE2 Limit THM CCX0",
89
+ "cpu_thermal_value_ccx1": "CPU0 INFRASTRUCTURE2 Value THM CCX1",
90
+ "cpu_thermal_limit_ccx1": "CPU0 INFRASTRUCTURE2 Limit THM CCX1",
91
+ "cpu_thermal_value_gfx": "CPU0 INFRASTRUCTURE2 Value THM GFX",
92
+ "cpu_thermal_limit_gfx": "CPU0 INFRASTRUCTURE2 Limit THM GFX",
93
+ "cpu_thermal_value_soc": "CPU0 INFRASTRUCTURE2 Value THM SOC",
94
+ "cpu_thermal_limit_soc": "CPU0 INFRASTRUCTURE2 Limit THM SOC",
95
+ "cpu_stt_value": "CPU0 INFRASTRUCTURE2 Value STT APU",
96
+ "cpu_stt_limit": "CPU0 INFRASTRUCTURE2 Limit STT APU",
97
+ }
98
+
99
+ @staticmethod
100
+ def time_to_seconds(time_str):
101
+
102
+ # Parse the time string
103
+ match = re.search(r"\b(\d{2}:\d{2}:\d{2}.\d{3})", time_str)
104
+ if match:
105
+ time_obj = datetime.strptime(match.group(), "%H:%M:%S.%f")
106
+ else:
107
+ raise ValueError(f"Could not parse {time_str}")
108
+
109
+ # Calculate the total seconds
110
+ total_seconds = (
111
+ time_obj.hour * 3600
112
+ + time_obj.minute * 60
113
+ + time_obj.second
114
+ + time_obj.microsecond / 1_000_000
115
+ )
116
+ return total_seconds
117
+
118
+ @staticmethod
119
+ def add_arguments_to_parser(parser):
120
+ parser.add_argument(
121
+ f"--{AGTPowerProfiler.unique_name}",
122
+ nargs="?",
123
+ metavar="WARMUP_PERIOD",
124
+ type=int,
125
+ default=None,
126
+ const=DEFAULT_TRACK_POWER_WARMUP_PERIOD,
127
+ help="Track power consumption using the AGT application and plot the results. "
128
+ "AGT is an internal AMD tool. "
129
+ "Optionally, set the warmup period in seconds "
130
+ f"(default: {DEFAULT_TRACK_POWER_WARMUP_PERIOD}). "
131
+ f"Set the {AGT_PATH_ENV_VAR} environment variable to point to the AGT executable. "
132
+ "This is a Windows only feature and Lemonade must be run from a CMD "
133
+ "window with Administrator privileges.",
134
+ )
135
+
136
+ def __init__(self, parser_arg_value):
137
+ super().__init__()
138
+ self.warmup_period = parser_arg_value
139
+ self.status_stats += [Keys.PEAK_PROCESSOR_PACKAGE_POWER, Keys.POWER_USAGE_PLOT]
140
+ self.tracking_active = False
141
+ self.build_dir = None
142
+ self.csv_path = None
143
+ self.agt_process = None
144
+ self.data = None
145
+
146
+ def start(self, build_dir):
147
+ if self.tracking_active:
148
+ raise RuntimeError("Cannot start power tracking while already tracking")
149
+
150
+ if platform.system() != "Windows":
151
+ raise RuntimeError("Power usage tracking is only enabled in Windows.")
152
+
153
+ # Check that user is running in Admin mode
154
+ if not is_user_admin():
155
+ raise RuntimeError(
156
+ "For power usage tracking, run Lemonade as an Administrator."
157
+ )
158
+
159
+ # Save the folder where data and plot will be stored
160
+ self.build_dir = build_dir
161
+
162
+ # The csv file where power data will be stored
163
+ self.csv_path = os.path.join(build_dir, POWER_USAGE_CSV_FILENAME)
164
+ if " " in self.csv_path:
165
+ raise RuntimeError(
166
+ "Can't log AGT data to a file with a <space> in the path. "
167
+ "Please use the `-d` flag to specify a Lemonade cache path with no spaces."
168
+ )
169
+
170
+ # Check the AGT environment variables exists
171
+ if AGT_PATH_ENV_VAR in os.environ:
172
+ agt_path = os.getenv(AGT_PATH_ENV_VAR)
173
+ else:
174
+ raise RuntimeError(
175
+ f"Set environment variable {AGT_PATH_ENV_VAR} to point to the AGT executable."
176
+ )
177
+
178
+ # Check the AGT executable exists
179
+ if not os.path.isfile(agt_path):
180
+ raise FileNotFoundError(agt_path)
181
+
182
+ # Check that executable is not already running
183
+ executable = agt_path.split(os.sep)[-1]
184
+ if is_process_running(executable):
185
+ raise RuntimeError(
186
+ f"{executable} is already running. Quit it and try again."
187
+ )
188
+
189
+ # Start AGT executable
190
+ try:
191
+ command = [
192
+ agt_path,
193
+ "-i=1",
194
+ "-unilog=PM",
195
+ "-unilogallgroups",
196
+ f"-unilogperiod={DEFAULT_TRACK_POWER_INTERVAL_MS}",
197
+ f"-unilogoutput={self.csv_path}",
198
+ ]
199
+ self.agt_process = subprocess.Popen(
200
+ command,
201
+ stdin=subprocess.PIPE,
202
+ stderr=subprocess.PIPE,
203
+ )
204
+ except OSError as e:
205
+ if "[WinError 740]" in str(e):
206
+ print(
207
+ "You may need to set Windows User Account Control to `Never notify`.\n"
208
+ )
209
+ raise
210
+ self.tracking_active = True
211
+ time.sleep(self.warmup_period)
212
+
213
+ def stop(self):
214
+ if self.tracking_active:
215
+ self.tracking_active = False
216
+ time.sleep(self.warmup_period)
217
+ self.agt_process.terminate()
218
+ self.agt_process.wait()
219
+
220
+ def generate_results(self, state, timestamp, start_times):
221
+ if self.agt_process is None:
222
+ return
223
+
224
+ if self.tracking_active:
225
+ self.stop()
226
+
227
+ df = read_data_from_csv(self.csv_path, self.columns_dict)
228
+ if df is None:
229
+ state.save_stat(Keys.POWER_USAGE_PLOT, "NONE")
230
+ return
231
+
232
+ # Remap csv data time to elapsed seconds (i.e., substract out initial time)
233
+ try:
234
+ initial_data_time = self.time_to_seconds(df["time"].iloc[0])
235
+ df["time"] = df["time"].apply(
236
+ lambda x: (self.time_to_seconds(x) - initial_data_time)
237
+ )
238
+ except ValueError as e:
239
+ printing.log_info(f"Badly formatted time data in {self.csv_path}: {e}.")
240
+ state.save_stat(Keys.POWER_USAGE_PLOT, "NONE")
241
+ return
242
+
243
+ # Make time 0 the time of the first tool starting (after the warmup period)
244
+ if start_times:
245
+ tool_start_times = sorted(start_times.values())
246
+ # First tool after warmup (if no tools, then will be time of start of cool down)
247
+ first_tool_time = tool_start_times[1]
248
+
249
+ # Map the measurement data so that zero in the measurement data aligns with
250
+ # the first_tool_time
251
+ #
252
+ # Find the difference between the timestamp first_tool_time and initial_data_time
253
+ # which is a count of seconds since midnight
254
+ #
255
+ # Find midnight prior to first_tool_time
256
+ t = time.localtime(first_tool_time)
257
+ since_midnight = (
258
+ t.tm_hour * 3600 + t.tm_min * 60 + t.tm_sec + (first_tool_time % 1)
259
+ )
260
+ delta = since_midnight - initial_data_time
261
+ df["time"] = df["time"] - delta
262
+
263
+ peak_power = max(df["cpu_package_power"])
264
+
265
+ # Scale value metrics with limits to percentages
266
+ for col_name in df.columns:
267
+ if "value" in col_name:
268
+ limit_col_name = col_name.replace("value", "limit")
269
+ if limit_col_name in df.columns:
270
+ df[col_name] = 100 * df[col_name] / df[limit_col_name]
271
+
272
+ # Create a figure
273
+ fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(16, 8))
274
+
275
+ if start_times:
276
+ tool_starts = sorted(start_times.items(), key=lambda item: item[1])
277
+ tool_name_list = [item[0] for item in tool_starts]
278
+
279
+ # Adjust to common time frame as power measurements
280
+ tool_start_list = [
281
+ max(df["time"].iloc[0], item[1] - first_tool_time)
282
+ for item in tool_starts
283
+ ]
284
+ tool_stop_list = tool_start_list[1:] + [df["time"].values[-1]]
285
+
286
+ # Extract power data time series
287
+ x_time = df["time"].to_numpy()
288
+ y_power = df["cpu_package_power"].to_numpy()
289
+
290
+ # Extract data for each stage in the build
291
+ self.data = []
292
+ for name, t0, tf in zip(tool_name_list, tool_start_list, tool_stop_list):
293
+ x = x_time[(x_time >= t0) * (x_time <= tf)]
294
+ x = np.insert(x, 0, t0)
295
+ x = np.insert(x, len(x), tf)
296
+ y = np.interp(x, x_time, y_power)
297
+ energy = np.trapz(y, x)
298
+ avg_power = energy / (tf - t0)
299
+ stage = {
300
+ "name": name,
301
+ "t": x.tolist(),
302
+ "power": y.tolist(),
303
+ "duration": float(tf - t0),
304
+ "energy consumed": float(energy),
305
+ "average power": float(avg_power),
306
+ }
307
+ self.data.append(stage)
308
+
309
+ for stage in self.data:
310
+ # Plot power usage time series
311
+ p = ax1.plot(
312
+ stage["t"],
313
+ stage["power"],
314
+ label=f"{stage['name']} ({stage['duration']:.1f}s, "
315
+ f"{stage['energy consumed']:0.1f} J)",
316
+ )
317
+ # Add a dashed line to show average power
318
+ ax1.plot(
319
+ [stage["t"][0], stage["t"][-1]],
320
+ [stage["average power"], stage["average power"]],
321
+ linestyle="--",
322
+ c=p[0].get_c(),
323
+ )
324
+ # Add average power text to plot
325
+ ax1.text(
326
+ stage["t"][0],
327
+ stage["average power"],
328
+ f"{stage['average power']:.1f} W ",
329
+ horizontalalignment="right",
330
+ verticalalignment="center",
331
+ c=p[0].get_c(),
332
+ )
333
+ else:
334
+ ax1.plot(
335
+ df["time"],
336
+ df["cpu_package_power"],
337
+ )
338
+ # Add title and labels to plots
339
+ ax1.set_ylabel(self.columns_dict["cpu_package_power"])
340
+ title_str = "AGT Stats\n" + "\n".join(textwrap.wrap(state.build_name, 60))
341
+ ax1.set_title(title_str)
342
+ ax1.legend()
343
+ ax1.grid(True)
344
+
345
+ # Create second plot
346
+ ax2.plot(
347
+ df["time"],
348
+ df["npu_clock"],
349
+ label=self.columns_dict["npu_clock"],
350
+ )
351
+ ax2.plot(
352
+ df["time"],
353
+ df["gpu_clock"],
354
+ label=self.columns_dict["gpu_clock"],
355
+ )
356
+ ax2.set_xlabel("Time [s]")
357
+ ax2.set_ylabel("Clock Frequency [MHz]")
358
+ ax2.legend(loc=2)
359
+ ax2.grid(True)
360
+ # Add second y-axis for percentage metrics
361
+ # Manually set colors to be different from first axis
362
+ ax2_twin = ax2.twinx()
363
+ if "classic_cpu_usage" in df.columns:
364
+ ax2_twin.plot(
365
+ df["time"],
366
+ df["classic_cpu_usage"],
367
+ label=self.columns_dict["classic_cpu_usage"],
368
+ c="g",
369
+ )
370
+ if "dense_cpu_usage" in df.columns:
371
+ ax2_twin.plot(
372
+ df["time"],
373
+ df["dense_cpu_usage"],
374
+ label=self.columns_dict["dense_cpu_usage"],
375
+ c="r",
376
+ )
377
+ if "classic_cpu_usage_0" in df.columns:
378
+ ax2_twin.plot(
379
+ df["time"],
380
+ df["classic_cpu_usage_0"],
381
+ label=self.columns_dict["classic_cpu_usage_0"],
382
+ c="g",
383
+ )
384
+ if "classic_cpu_usage_1" in df.columns:
385
+ ax2_twin.plot(
386
+ df["time"],
387
+ df["classic_cpu_usage_1"],
388
+ label=self.columns_dict["classic_cpu_usage_1"],
389
+ c="r",
390
+ )
391
+ ax2_twin.set_ylim([0, 100])
392
+ vals = ax2_twin.get_yticks()
393
+ ax2_twin.set_yticks(vals)
394
+ ax2_twin.set_yticklabels([f"{v:.0f}%" for v in vals])
395
+ ax2_twin.legend(loc=1)
396
+
397
+ # Create third plot (all remaining columns)
398
+ plot3_columns = [
399
+ "apu_stapm_value",
400
+ "cpu_tdc_value",
401
+ "cpu_edc_value",
402
+ "cpu_ppt_fast_value",
403
+ "cpu_ppt_slow_value",
404
+ "cpu_thermal_value_ccx0",
405
+ "cpu_thermal_value_ccx1",
406
+ "cpu_thermal_value_gfx",
407
+ "cpu_thermal_value_soc",
408
+ "cpu_stt_value",
409
+ ]
410
+ for col_str in plot3_columns:
411
+ if col_str in df.columns:
412
+ ax3.plot(
413
+ df["time"],
414
+ df[col_str],
415
+ label=self.columns_dict[col_str],
416
+ )
417
+ ax3.set_xlabel("Time [s]")
418
+ ax3.set_ylim([0, 100])
419
+ vals = ax3.get_yticks()
420
+ ax3.set_yticks(vals)
421
+ ax3.set_yticklabels([f"{v:.0f}%" for v in vals])
422
+ if len(ax3.lines):
423
+ ax3.legend()
424
+ ax3.grid(True)
425
+
426
+ # Save plot to current folder AND save to cache
427
+ plot_path = os.path.join(
428
+ self.build_dir, f"{timestamp}_{POWER_USAGE_PNG_FILENAME}"
429
+ )
430
+ fig.savefig(plot_path, dpi=300, bbox_inches="tight")
431
+ plot_path = os.path.join(os.getcwd(), f"{timestamp}_{POWER_USAGE_PNG_FILENAME}")
432
+ fig.savefig(plot_path, dpi=300, bbox_inches="tight")
433
+
434
+ state.save_stat(Keys.POWER_USAGE_PLOT, plot_path)
435
+ state.save_stat(Keys.POWER_USAGE_DATA, self.data)
436
+ state.save_stat(Keys.POWER_USAGE_DATA_CSV, self.csv_path)
437
+ state.save_stat(Keys.PEAK_PROCESSOR_PACKAGE_POWER, f"{peak_power:0.1f} W")