lemonade-sdk 9.1.1__py3-none-any.whl
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.
- lemonade/__init__.py +5 -0
- lemonade/api.py +180 -0
- lemonade/cache.py +92 -0
- lemonade/cli.py +173 -0
- lemonade/common/__init__.py +0 -0
- lemonade/common/build.py +176 -0
- lemonade/common/cli_helpers.py +139 -0
- lemonade/common/exceptions.py +98 -0
- lemonade/common/filesystem.py +368 -0
- lemonade/common/inference_engines.py +408 -0
- lemonade/common/network.py +93 -0
- lemonade/common/printing.py +110 -0
- lemonade/common/status.py +471 -0
- lemonade/common/system_info.py +1411 -0
- lemonade/common/test_helpers.py +28 -0
- lemonade/profilers/__init__.py +1 -0
- lemonade/profilers/agt_power.py +437 -0
- lemonade/profilers/hwinfo_power.py +429 -0
- lemonade/profilers/memory_tracker.py +259 -0
- lemonade/profilers/profiler.py +58 -0
- lemonade/sequence.py +363 -0
- lemonade/state.py +159 -0
- lemonade/tools/__init__.py +1 -0
- lemonade/tools/accuracy.py +432 -0
- lemonade/tools/adapter.py +114 -0
- lemonade/tools/bench.py +302 -0
- lemonade/tools/flm/__init__.py +1 -0
- lemonade/tools/flm/utils.py +305 -0
- lemonade/tools/huggingface/bench.py +187 -0
- lemonade/tools/huggingface/load.py +235 -0
- lemonade/tools/huggingface/utils.py +359 -0
- lemonade/tools/humaneval.py +264 -0
- lemonade/tools/llamacpp/bench.py +255 -0
- lemonade/tools/llamacpp/load.py +222 -0
- lemonade/tools/llamacpp/utils.py +1260 -0
- lemonade/tools/management_tools.py +319 -0
- lemonade/tools/mmlu.py +319 -0
- lemonade/tools/oga/__init__.py +0 -0
- lemonade/tools/oga/bench.py +120 -0
- lemonade/tools/oga/load.py +804 -0
- lemonade/tools/oga/migration.py +403 -0
- lemonade/tools/oga/utils.py +462 -0
- lemonade/tools/perplexity.py +147 -0
- lemonade/tools/prompt.py +263 -0
- lemonade/tools/report/__init__.py +0 -0
- lemonade/tools/report/llm_report.py +203 -0
- lemonade/tools/report/table.py +899 -0
- lemonade/tools/server/__init__.py +0 -0
- lemonade/tools/server/flm.py +133 -0
- lemonade/tools/server/llamacpp.py +320 -0
- lemonade/tools/server/serve.py +2123 -0
- lemonade/tools/server/static/favicon.ico +0 -0
- lemonade/tools/server/static/index.html +279 -0
- lemonade/tools/server/static/js/chat.js +1059 -0
- lemonade/tools/server/static/js/model-settings.js +183 -0
- lemonade/tools/server/static/js/models.js +1395 -0
- lemonade/tools/server/static/js/shared.js +556 -0
- lemonade/tools/server/static/logs.html +191 -0
- lemonade/tools/server/static/styles.css +2654 -0
- lemonade/tools/server/static/webapp.html +321 -0
- lemonade/tools/server/tool_calls.py +153 -0
- lemonade/tools/server/tray.py +664 -0
- lemonade/tools/server/utils/macos_tray.py +226 -0
- lemonade/tools/server/utils/port.py +77 -0
- lemonade/tools/server/utils/thread.py +85 -0
- lemonade/tools/server/utils/windows_tray.py +408 -0
- lemonade/tools/server/webapp.py +34 -0
- lemonade/tools/server/wrapped_server.py +559 -0
- lemonade/tools/tool.py +374 -0
- lemonade/version.py +1 -0
- lemonade_install/__init__.py +1 -0
- lemonade_install/install.py +239 -0
- lemonade_sdk-9.1.1.dist-info/METADATA +276 -0
- lemonade_sdk-9.1.1.dist-info/RECORD +84 -0
- lemonade_sdk-9.1.1.dist-info/WHEEL +5 -0
- lemonade_sdk-9.1.1.dist-info/entry_points.txt +5 -0
- lemonade_sdk-9.1.1.dist-info/licenses/LICENSE +201 -0
- lemonade_sdk-9.1.1.dist-info/licenses/NOTICE.md +47 -0
- lemonade_sdk-9.1.1.dist-info/top_level.txt +3 -0
- lemonade_server/cli.py +805 -0
- lemonade_server/model_manager.py +758 -0
- lemonade_server/pydantic_models.py +159 -0
- lemonade_server/server_models.json +643 -0
- lemonade_server/settings.py +39 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def create_test_dir(
|
|
6
|
+
key: str,
|
|
7
|
+
base_dir: str = os.path.dirname(os.path.abspath(__file__)),
|
|
8
|
+
):
|
|
9
|
+
# Define paths to be used
|
|
10
|
+
cache_dir = os.path.join(base_dir, "generated", f"{key}_cache_dir")
|
|
11
|
+
corpus_dir = os.path.join(base_dir, "generated", "test_corpus")
|
|
12
|
+
|
|
13
|
+
# Delete folders if they exist and
|
|
14
|
+
if os.path.isdir(cache_dir):
|
|
15
|
+
shutil.rmtree(cache_dir)
|
|
16
|
+
if os.path.isdir(corpus_dir):
|
|
17
|
+
shutil.rmtree(corpus_dir)
|
|
18
|
+
os.makedirs(corpus_dir, exist_ok=True)
|
|
19
|
+
|
|
20
|
+
return cache_dir, corpus_dir
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def strip_dot_py(test_script_file: str) -> str:
|
|
24
|
+
return test_script_file.split(".")[0]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# This file was originally licensed under Apache 2.0. It has been modified.
|
|
28
|
+
# Modifications Copyright (c) 2025 AMD
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .profiler import Profiler
|
|
@@ -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")
|