lemonade-sdk 7.0.0__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.
Potentially problematic release.
This version of lemonade-sdk might be problematic. Click here for more details.
- lemonade/__init__.py +5 -0
- lemonade/api.py +125 -0
- lemonade/cache.py +85 -0
- lemonade/cli.py +135 -0
- lemonade/common/__init__.py +0 -0
- lemonade/common/analyze_model.py +26 -0
- lemonade/common/build.py +223 -0
- lemonade/common/cli_helpers.py +139 -0
- lemonade/common/exceptions.py +98 -0
- lemonade/common/filesystem.py +368 -0
- lemonade/common/labels.py +61 -0
- lemonade/common/onnx_helpers.py +176 -0
- lemonade/common/plugins.py +10 -0
- lemonade/common/printing.py +110 -0
- lemonade/common/status.py +490 -0
- lemonade/common/system_info.py +390 -0
- lemonade/common/tensor_helpers.py +83 -0
- lemonade/common/test_helpers.py +28 -0
- lemonade/profilers/__init__.py +1 -0
- lemonade/profilers/memory_tracker.py +257 -0
- lemonade/profilers/profiler.py +55 -0
- lemonade/sequence.py +363 -0
- lemonade/state.py +159 -0
- lemonade/tools/__init__.py +1 -0
- lemonade/tools/adapter.py +104 -0
- lemonade/tools/bench.py +284 -0
- lemonade/tools/huggingface_bench.py +267 -0
- lemonade/tools/huggingface_load.py +520 -0
- lemonade/tools/humaneval.py +258 -0
- lemonade/tools/llamacpp.py +261 -0
- lemonade/tools/llamacpp_bench.py +154 -0
- lemonade/tools/management_tools.py +273 -0
- lemonade/tools/mmlu.py +327 -0
- lemonade/tools/ort_genai/__init__.py +0 -0
- lemonade/tools/ort_genai/oga.py +1129 -0
- lemonade/tools/ort_genai/oga_bench.py +142 -0
- lemonade/tools/perplexity.py +146 -0
- lemonade/tools/prompt.py +228 -0
- lemonade/tools/quark/__init__.py +0 -0
- lemonade/tools/quark/quark_load.py +172 -0
- lemonade/tools/quark/quark_quantize.py +439 -0
- lemonade/tools/report/__init__.py +0 -0
- lemonade/tools/report/llm_report.py +203 -0
- lemonade/tools/report/table.py +739 -0
- lemonade/tools/server/__init__.py +0 -0
- lemonade/tools/server/serve.py +1354 -0
- lemonade/tools/server/tool_calls.py +146 -0
- lemonade/tools/tool.py +374 -0
- lemonade/version.py +1 -0
- lemonade_install/__init__.py +1 -0
- lemonade_install/install.py +774 -0
- lemonade_sdk-7.0.0.dist-info/METADATA +116 -0
- lemonade_sdk-7.0.0.dist-info/RECORD +61 -0
- lemonade_sdk-7.0.0.dist-info/WHEEL +5 -0
- lemonade_sdk-7.0.0.dist-info/entry_points.txt +4 -0
- lemonade_sdk-7.0.0.dist-info/licenses/LICENSE +201 -0
- lemonade_sdk-7.0.0.dist-info/licenses/NOTICE.md +21 -0
- lemonade_sdk-7.0.0.dist-info/top_level.txt +3 -0
- lemonade_server/cli.py +260 -0
- lemonade_server/model_manager.py +98 -0
- lemonade_server/server_models.json +142 -0
|
@@ -0,0 +1,739 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
import re
|
|
4
|
+
from typing import Tuple, Dict, List
|
|
5
|
+
import textwrap
|
|
6
|
+
from tabulate import tabulate
|
|
7
|
+
import lemonade.common.build as build
|
|
8
|
+
import lemonade.common.filesystem as fs
|
|
9
|
+
from lemonade.cache import Keys
|
|
10
|
+
from lemonade.tools.huggingface_bench import HuggingfaceBench
|
|
11
|
+
from lemonade.tools.llamacpp_bench import LlamaCppBench
|
|
12
|
+
from lemonade.tools.mmlu import AccuracyMMLU
|
|
13
|
+
from lemonade.tools.ort_genai.oga_bench import OgaBench
|
|
14
|
+
|
|
15
|
+
# List of python packages for which to log the version
|
|
16
|
+
PYTHON_PACKAGES = ["onnxruntime", "transformers", "lemonade-sdk", "voe"]
|
|
17
|
+
|
|
18
|
+
# Key value in local build data dict
|
|
19
|
+
SW_VERSIONS = "sw_versions"
|
|
20
|
+
|
|
21
|
+
# Map datatype aliases to common names
|
|
22
|
+
dtype_aliases = {
|
|
23
|
+
"fp32": "float32",
|
|
24
|
+
"fp16": "float16",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
################################################################################
|
|
29
|
+
# HELPER FUNCTIONS
|
|
30
|
+
################################################################################
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _to_list(x) -> List:
|
|
34
|
+
"""Puts item in list if it is not already a list"""
|
|
35
|
+
if isinstance(x, list):
|
|
36
|
+
return x
|
|
37
|
+
return [x]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _wrap(text: str, width: int) -> str:
|
|
41
|
+
"""Wraps text cleanly to specified width"""
|
|
42
|
+
return "\n".join(textwrap.wrap(text, width=width))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _merge_join(str1, str2) -> str:
|
|
46
|
+
"""Joins a pair of strings with \n as long as both are non-empty, else skips the \n"""
|
|
47
|
+
return str1 + ("\n" if str1 and str2 else "") + str2
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
################################################################################
|
|
51
|
+
# CLASSES THAT DESCRIBE TEXT TABLE COLUMNS
|
|
52
|
+
################################################################################
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# Table entry types
|
|
56
|
+
class TableColumn(ABC):
|
|
57
|
+
|
|
58
|
+
default_wrap = 80
|
|
59
|
+
|
|
60
|
+
@abstractmethod
|
|
61
|
+
def get_str(self, build_stats: dict, lean=False) -> str:
|
|
62
|
+
"""Method used to return the string that goes in the table column for this build"""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class SimpleStat(TableColumn):
|
|
66
|
+
"""These are for statistics already declared by the tool or basic build stats"""
|
|
67
|
+
|
|
68
|
+
def __init__(
|
|
69
|
+
self,
|
|
70
|
+
column_header,
|
|
71
|
+
stat,
|
|
72
|
+
format_str,
|
|
73
|
+
align="center",
|
|
74
|
+
omit_if_lean=False,
|
|
75
|
+
wrap=None,
|
|
76
|
+
):
|
|
77
|
+
self.column_header = column_header
|
|
78
|
+
self.stat = stat
|
|
79
|
+
self.format_str = format_str
|
|
80
|
+
self.align = align
|
|
81
|
+
self.omit_if_lean = omit_if_lean
|
|
82
|
+
self.wrap = wrap or self.default_wrap
|
|
83
|
+
|
|
84
|
+
def get_str(self, build_stats, lean=False):
|
|
85
|
+
if lean and self.omit_if_lean:
|
|
86
|
+
return None
|
|
87
|
+
data = build_stats.get(self.stat, None)
|
|
88
|
+
if data is None:
|
|
89
|
+
return ""
|
|
90
|
+
cell_str = "\n".join(
|
|
91
|
+
[_wrap(f"{x:{self.format_str}}", self.wrap) for x in _to_list(data)]
|
|
92
|
+
)
|
|
93
|
+
return cell_str
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class TimestampStat(SimpleStat):
|
|
97
|
+
"""These are for timestamp statistics already declared by the tool or basic build stats"""
|
|
98
|
+
|
|
99
|
+
def get_str(self, build_stats, lean=False):
|
|
100
|
+
if lean and self.omit_if_lean:
|
|
101
|
+
return None
|
|
102
|
+
data = build_stats.get(self.stat, None)
|
|
103
|
+
if data is None:
|
|
104
|
+
return "-"
|
|
105
|
+
cell_str = data.strftime(self.format_str)
|
|
106
|
+
return cell_str
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class MultiStat(TableColumn):
|
|
110
|
+
"""
|
|
111
|
+
These are for string-values statistics already declared by the tool or basic build stats.
|
|
112
|
+
One or more stats will be put in the same cell.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
def __init__(
|
|
116
|
+
self,
|
|
117
|
+
column_header,
|
|
118
|
+
stats,
|
|
119
|
+
format_str,
|
|
120
|
+
align="center",
|
|
121
|
+
omit_if_lean=False,
|
|
122
|
+
wrap=None,
|
|
123
|
+
):
|
|
124
|
+
self.column_header = column_header
|
|
125
|
+
self.stats = _to_list(stats)
|
|
126
|
+
self.format_str = format_str
|
|
127
|
+
self.align = align
|
|
128
|
+
self.omit_if_lean = omit_if_lean
|
|
129
|
+
self.wrap = wrap or self.default_wrap
|
|
130
|
+
|
|
131
|
+
def get_str(self, build_stats, lean=False):
|
|
132
|
+
if lean and self.omit_if_lean:
|
|
133
|
+
return None
|
|
134
|
+
cell_str_list = []
|
|
135
|
+
for stat in self.stats:
|
|
136
|
+
if stat is None:
|
|
137
|
+
cell_str_list.append("") # will be a blank line
|
|
138
|
+
else:
|
|
139
|
+
data = build_stats.get(stat, None)
|
|
140
|
+
if data is None:
|
|
141
|
+
cell_str_list.append("-") # missing value
|
|
142
|
+
else:
|
|
143
|
+
cell_str_list.append(_wrap(f"{data:{self.format_str}}", self.wrap))
|
|
144
|
+
cell_str = "\n".join(cell_str_list)
|
|
145
|
+
return cell_str
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class StatWithSD(TableColumn):
|
|
149
|
+
"""These are for statistics already declared by the tool that have an
|
|
150
|
+
accompanying standard deviation statistic"""
|
|
151
|
+
|
|
152
|
+
def __init__(
|
|
153
|
+
self,
|
|
154
|
+
column_header,
|
|
155
|
+
stat,
|
|
156
|
+
sd_stat,
|
|
157
|
+
format_str,
|
|
158
|
+
align="center",
|
|
159
|
+
omit_if_lean=False,
|
|
160
|
+
):
|
|
161
|
+
self.column_header = column_header
|
|
162
|
+
self.stat = stat
|
|
163
|
+
self.sd_stat = sd_stat
|
|
164
|
+
self.format_str = format_str
|
|
165
|
+
self.align = align
|
|
166
|
+
self.omit_if_lean = omit_if_lean
|
|
167
|
+
|
|
168
|
+
def get_str(self, build_stats, lean=False):
|
|
169
|
+
if lean and self.omit_if_lean:
|
|
170
|
+
return None
|
|
171
|
+
if not self.stat in build_stats:
|
|
172
|
+
return ""
|
|
173
|
+
data = build_stats[self.stat]
|
|
174
|
+
sd_data = build_stats.get(self.sd_stat, None)
|
|
175
|
+
if sd_data is None:
|
|
176
|
+
data = _to_list(data)
|
|
177
|
+
sd_data = [None] * len(data)
|
|
178
|
+
cell_str = "\n".join(
|
|
179
|
+
[
|
|
180
|
+
(
|
|
181
|
+
f"{x:{self.format_str}} +/- {sd_x:{self.format_str}}"
|
|
182
|
+
if not sd_x is None
|
|
183
|
+
else f"{x:{self.format_str}}"
|
|
184
|
+
)
|
|
185
|
+
for x, sd_x in zip(_to_list(data), _to_list(sd_data))
|
|
186
|
+
]
|
|
187
|
+
)
|
|
188
|
+
return cell_str
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class AdditionalStat(TableColumn):
|
|
192
|
+
"""These are for statistics not declared by the tool. A regular expression is defined
|
|
193
|
+
and all statistics matching the regular expression will be put in the cell"""
|
|
194
|
+
|
|
195
|
+
def __init__(
|
|
196
|
+
self,
|
|
197
|
+
column_header,
|
|
198
|
+
regexp,
|
|
199
|
+
lean_regexp,
|
|
200
|
+
format_str,
|
|
201
|
+
align="center",
|
|
202
|
+
omit_if_lean=False,
|
|
203
|
+
wrap=None,
|
|
204
|
+
):
|
|
205
|
+
self.column_header = column_header
|
|
206
|
+
self.regexp = regexp
|
|
207
|
+
self.lean_regexp = lean_regexp or regexp
|
|
208
|
+
self.format_str = format_str
|
|
209
|
+
self.align = align
|
|
210
|
+
self.omit_if_lean = omit_if_lean
|
|
211
|
+
self.wrap = wrap or self.default_wrap
|
|
212
|
+
|
|
213
|
+
def get_str(self, build_stats, lean=False):
|
|
214
|
+
if lean and self.omit_if_lean:
|
|
215
|
+
return None
|
|
216
|
+
# Find stats in build_stats that match the regexp for this column
|
|
217
|
+
regexp = self.lean_regexp if lean else self.regexp
|
|
218
|
+
stats = []
|
|
219
|
+
for stat in build_stats.keys():
|
|
220
|
+
if re.match(regexp, stat):
|
|
221
|
+
stats.append(stat)
|
|
222
|
+
# Construct the cell entry
|
|
223
|
+
cell_entry = []
|
|
224
|
+
for stat in stats:
|
|
225
|
+
if stat.endswith("_units"):
|
|
226
|
+
continue
|
|
227
|
+
units = build_stats.get(stat + "_units", None)
|
|
228
|
+
value = f"{build_stats[stat]:{self.format_str}}" + (
|
|
229
|
+
" " + units if not units is None else ""
|
|
230
|
+
)
|
|
231
|
+
cell_entry += [_wrap(stat, self.wrap), value]
|
|
232
|
+
return "\n".join(cell_entry)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
################################################################################
|
|
236
|
+
# ABSTRACT BASE CLASS FOR DEFINING A TABLE
|
|
237
|
+
################################################################################
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class Table(ABC):
|
|
241
|
+
|
|
242
|
+
table_descriptor = {}
|
|
243
|
+
|
|
244
|
+
def __init__(self):
|
|
245
|
+
self.all_builds = []
|
|
246
|
+
self.all_stats = []
|
|
247
|
+
self.lean = False
|
|
248
|
+
self.tools = None
|
|
249
|
+
self.merge_test_fn = lambda b1, b2: False
|
|
250
|
+
|
|
251
|
+
def find_builds(self, cache_dirs: List[str], model: str = None):
|
|
252
|
+
"""
|
|
253
|
+
Finds all the folder names of all the builds in the given list of cache directories.
|
|
254
|
+
Each (cache_dir, build_folder) tuple is appended to the all_builds list attribute.
|
|
255
|
+
If a model string is given, then builds will only be saved if the build folder name
|
|
256
|
+
contains the model string (case-insensitive).
|
|
257
|
+
"""
|
|
258
|
+
self.all_builds = []
|
|
259
|
+
for cache_dir in cache_dirs:
|
|
260
|
+
# Get all of the directories within the build cache
|
|
261
|
+
builds = fs.get_available_builds(cache_dir)
|
|
262
|
+
|
|
263
|
+
# Filter out any that don't have the right model in the name
|
|
264
|
+
if model is not None:
|
|
265
|
+
builds = [
|
|
266
|
+
(cache_dir, build_name)
|
|
267
|
+
for build_name in builds
|
|
268
|
+
if model.lower() in build_name.lower()
|
|
269
|
+
]
|
|
270
|
+
else:
|
|
271
|
+
builds = [(cache_dir, build_name) for build_name in builds]
|
|
272
|
+
|
|
273
|
+
self.all_builds += builds
|
|
274
|
+
|
|
275
|
+
def load_stats(self):
|
|
276
|
+
"""
|
|
277
|
+
Loads the build stats dict from each build into the all_stats list attribute,
|
|
278
|
+
one dict per build.
|
|
279
|
+
"""
|
|
280
|
+
self.all_stats = []
|
|
281
|
+
|
|
282
|
+
# Add results from all user-provided cache folders
|
|
283
|
+
for cache_dir, build_name in self.all_builds:
|
|
284
|
+
model_stats = fs.Stats(cache_dir, build_name).stats
|
|
285
|
+
|
|
286
|
+
if self.include_stats(model_stats):
|
|
287
|
+
self.post_process_stats(model_stats)
|
|
288
|
+
|
|
289
|
+
def include_stats(self, _model_stats: Dict) -> bool:
|
|
290
|
+
"""
|
|
291
|
+
Returns True if stats from this model build should be part of the table
|
|
292
|
+
"""
|
|
293
|
+
return True
|
|
294
|
+
|
|
295
|
+
def post_process_stats(self, model_stats: Dict) -> Dict:
|
|
296
|
+
"""
|
|
297
|
+
Create a dict of stats from these model stats and append to the all_stats list attribute.
|
|
298
|
+
Make any necessary modifications along the way
|
|
299
|
+
"""
|
|
300
|
+
evaluation_stats = {}
|
|
301
|
+
for key, value in model_stats.items():
|
|
302
|
+
# If a build or benchmark is still marked as "incomplete" at
|
|
303
|
+
# reporting time, it must have been killed by a timeout,
|
|
304
|
+
# out-of-memory (OOM), or some other uncaught exception
|
|
305
|
+
if (
|
|
306
|
+
key == fs.Keys.BUILD_STATUS or fs.Keys.TOOL_STATUS in key
|
|
307
|
+
) and value == build.FunctionStatus.INCOMPLETE:
|
|
308
|
+
value = build.FunctionStatus.KILLED
|
|
309
|
+
|
|
310
|
+
# Add stats ensuring that those are all in lower case
|
|
311
|
+
evaluation_stats[key.lower()] = value
|
|
312
|
+
|
|
313
|
+
self.all_stats.append(evaluation_stats)
|
|
314
|
+
|
|
315
|
+
def sort_stats(self):
|
|
316
|
+
"""Sorts the stats list used the class sort_key key function"""
|
|
317
|
+
self.all_stats.sort(key=self.sort_key)
|
|
318
|
+
|
|
319
|
+
def __str__(self) -> str:
|
|
320
|
+
"""Returns table as a string"""
|
|
321
|
+
#
|
|
322
|
+
# Construct headers and column alignment lists
|
|
323
|
+
#
|
|
324
|
+
headers = []
|
|
325
|
+
col_align = []
|
|
326
|
+
|
|
327
|
+
# First headers
|
|
328
|
+
first_columns = self.table_descriptor.get("first_columns", [])
|
|
329
|
+
for column in first_columns:
|
|
330
|
+
if not (self.lean and column.omit_if_lean):
|
|
331
|
+
headers.append(column.column_header)
|
|
332
|
+
col_align += (column.align,)
|
|
333
|
+
|
|
334
|
+
# Per tool headers
|
|
335
|
+
tool_columns = self.table_descriptor.get("tool_columns", {})
|
|
336
|
+
tools = self.tools or []
|
|
337
|
+
for tool in tools:
|
|
338
|
+
|
|
339
|
+
# Don't duplicate columns if tool has an alternate tool listed
|
|
340
|
+
if isinstance(tool_columns[tool], type):
|
|
341
|
+
referenced_tool = tool_columns[tool]
|
|
342
|
+
if referenced_tool in tools:
|
|
343
|
+
continue
|
|
344
|
+
# Use the column specification of the referenced tool
|
|
345
|
+
tool = referenced_tool
|
|
346
|
+
|
|
347
|
+
for column in tool_columns[tool]:
|
|
348
|
+
if not (self.lean and column.omit_if_lean):
|
|
349
|
+
headers.append(column.column_header)
|
|
350
|
+
col_align += (column.align,)
|
|
351
|
+
|
|
352
|
+
# Final headers
|
|
353
|
+
last_columns = self.table_descriptor.get("last_columns", [])
|
|
354
|
+
for column in last_columns:
|
|
355
|
+
if not (self.lean and column.omit_if_lean):
|
|
356
|
+
headers.append(column.column_header)
|
|
357
|
+
col_align += (column.align,)
|
|
358
|
+
|
|
359
|
+
#
|
|
360
|
+
# Construct table rows
|
|
361
|
+
#
|
|
362
|
+
rows = []
|
|
363
|
+
last_row = None
|
|
364
|
+
last_build_stats = None
|
|
365
|
+
for build_stats in self.all_stats:
|
|
366
|
+
row = []
|
|
367
|
+
|
|
368
|
+
# First columns
|
|
369
|
+
for entry in first_columns:
|
|
370
|
+
entry_str = entry.get_str(build_stats, self.lean)
|
|
371
|
+
if entry_str is not None:
|
|
372
|
+
row.append(entry_str)
|
|
373
|
+
|
|
374
|
+
# Per tool columns
|
|
375
|
+
for tool in tools:
|
|
376
|
+
|
|
377
|
+
if not isinstance(tool_columns[tool], list):
|
|
378
|
+
referenced_tool = tool_columns[tool]
|
|
379
|
+
if referenced_tool in tools:
|
|
380
|
+
continue
|
|
381
|
+
tool = referenced_tool
|
|
382
|
+
|
|
383
|
+
for entry in tool_columns[tool]:
|
|
384
|
+
entry_str = entry.get_str(build_stats, self.lean)
|
|
385
|
+
if entry_str is not None:
|
|
386
|
+
row.append(entry_str)
|
|
387
|
+
|
|
388
|
+
# Final columns
|
|
389
|
+
for entry in last_columns:
|
|
390
|
+
entry_str = entry.get_str(build_stats, self.lean)
|
|
391
|
+
if entry_str is not None:
|
|
392
|
+
row.append(entry_str)
|
|
393
|
+
|
|
394
|
+
# See if this row should be merged with the last row
|
|
395
|
+
if last_build_stats and self.merge_test_fn(last_build_stats, build_stats):
|
|
396
|
+
# Merge with last row
|
|
397
|
+
for col in range(0, len(first_columns)):
|
|
398
|
+
# If identical, don't duplicate
|
|
399
|
+
if last_row[col] != row[col]:
|
|
400
|
+
last_row[col] = _merge_join(last_row[col], row[col])
|
|
401
|
+
for col in range(len(first_columns), len(row) - len(last_columns)):
|
|
402
|
+
# Allow duplicates
|
|
403
|
+
last_row[col] = _merge_join(last_row[col], row[col])
|
|
404
|
+
for col in range(len(row) - len(last_columns), len(row)):
|
|
405
|
+
# If identical, don't duplicate
|
|
406
|
+
if last_row[col] != row[col]:
|
|
407
|
+
last_row[col] = _merge_join(last_row[col], row[col])
|
|
408
|
+
else:
|
|
409
|
+
rows.append(row)
|
|
410
|
+
last_row = row
|
|
411
|
+
last_build_stats = build_stats
|
|
412
|
+
|
|
413
|
+
if not rows:
|
|
414
|
+
rows = [["NO DATA"] + [" "] * (len(headers) - 1)]
|
|
415
|
+
|
|
416
|
+
return tabulate(rows, headers=headers, tablefmt="grid", colalign=col_align)
|
|
417
|
+
|
|
418
|
+
def create_csv_report(self):
|
|
419
|
+
|
|
420
|
+
# Find all keys and use as column headers
|
|
421
|
+
column_headers = set()
|
|
422
|
+
for build_stats in self.all_stats:
|
|
423
|
+
column_headers |= set(build_stats.keys())
|
|
424
|
+
|
|
425
|
+
# Sort all columns alphabetically
|
|
426
|
+
column_headers = sorted(column_headers)
|
|
427
|
+
|
|
428
|
+
# Fill in blanks for each build
|
|
429
|
+
report: List[Dict] = []
|
|
430
|
+
for build_stats in self.all_stats:
|
|
431
|
+
result = {k: "-" for k in column_headers}
|
|
432
|
+
for k, v in build_stats.items():
|
|
433
|
+
result[k] = v
|
|
434
|
+
report.append(result)
|
|
435
|
+
|
|
436
|
+
return report, column_headers
|
|
437
|
+
|
|
438
|
+
@staticmethod
|
|
439
|
+
def sort_key(build_stats: dict) -> Tuple:
|
|
440
|
+
"""Sort by timestamp. If timestamp is missing, then list first."""
|
|
441
|
+
return (
|
|
442
|
+
build_stats[fs.Keys.TIMESTAMP].strftime("%Y%m%d%H:%M:%S")
|
|
443
|
+
if fs.Keys.TIMESTAMP in build_stats
|
|
444
|
+
else "-"
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
@staticmethod
|
|
448
|
+
def get_report_name(prefix: str = "") -> str:
|
|
449
|
+
"""
|
|
450
|
+
Returns the name of the .csv report
|
|
451
|
+
"""
|
|
452
|
+
day = datetime.now().day
|
|
453
|
+
month = datetime.now().month
|
|
454
|
+
year = datetime.now().year
|
|
455
|
+
date_key = f"{year}-{str(month).zfill(2)}-{str(day).zfill(2)}"
|
|
456
|
+
return f"{prefix}{date_key}.csv"
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
################################################################################
|
|
460
|
+
# TABLE CLASS FOR BASIC LEMONADE REPORT
|
|
461
|
+
################################################################################
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
class LemonadeTable(Table):
|
|
465
|
+
|
|
466
|
+
table_descriptor = {
|
|
467
|
+
"first_columns": [
|
|
468
|
+
TimestampStat("Timestamp", fs.Keys.TIMESTAMP, "%Y-%m-%d\n%H:%M:%S"),
|
|
469
|
+
SimpleStat("Build Name", fs.Keys.BUILD_NAME, "s"),
|
|
470
|
+
SimpleStat("Tools\nSequence", fs.Keys.SELECTED_SEQUENCE_OF_TOOLS, "s"),
|
|
471
|
+
SimpleStat("Build\nStatus", fs.Keys.BUILD_STATUS, "s"),
|
|
472
|
+
],
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
################################################################################
|
|
477
|
+
# TABLE CLASS FOR LEMONADE PERFORMANCE REPORT
|
|
478
|
+
################################################################################
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
class LemonadePerfTable(Table):
|
|
482
|
+
|
|
483
|
+
table_descriptor = {
|
|
484
|
+
"first_columns": [
|
|
485
|
+
TimestampStat("Timestamp", fs.Keys.TIMESTAMP, "%Y-%m-%d\n%H:%M:%S"),
|
|
486
|
+
# SimpleStat("Timestamp", fs.Keys.TIMESTAMP, "s"),
|
|
487
|
+
MultiStat(
|
|
488
|
+
"Model\n\nDevice\nData Type",
|
|
489
|
+
[
|
|
490
|
+
Keys.CHECKPOINT,
|
|
491
|
+
None,
|
|
492
|
+
Keys.DEVICE,
|
|
493
|
+
Keys.DTYPE,
|
|
494
|
+
None,
|
|
495
|
+
Keys.LOCAL_MODEL_FOLDER,
|
|
496
|
+
],
|
|
497
|
+
"s",
|
|
498
|
+
wrap=35,
|
|
499
|
+
),
|
|
500
|
+
],
|
|
501
|
+
"tool_columns": {
|
|
502
|
+
OgaBench: [
|
|
503
|
+
SimpleStat(_wrap("Prompt Len (Tokens)", 8), Keys.PROMPT_TOKENS, "d"),
|
|
504
|
+
StatWithSD(
|
|
505
|
+
_wrap("Time to First Token (sec)", 8),
|
|
506
|
+
Keys.SECONDS_TO_FIRST_TOKEN,
|
|
507
|
+
Keys.STD_DEV_SECONDS_TO_FIRST_TOKEN,
|
|
508
|
+
".2f",
|
|
509
|
+
),
|
|
510
|
+
StatWithSD(
|
|
511
|
+
_wrap("Tokens per Second", 8),
|
|
512
|
+
Keys.TOKEN_GENERATION_TOKENS_PER_SECOND,
|
|
513
|
+
Keys.STD_DEV_TOKENS_PER_SECOND,
|
|
514
|
+
".2f",
|
|
515
|
+
),
|
|
516
|
+
SimpleStat(
|
|
517
|
+
_wrap("Memory Used (GB)", 8), Keys.MAX_MEMORY_USED_GBYTE, ".3f"
|
|
518
|
+
),
|
|
519
|
+
],
|
|
520
|
+
HuggingfaceBench: OgaBench,
|
|
521
|
+
LlamaCppBench: OgaBench,
|
|
522
|
+
AccuracyMMLU: [
|
|
523
|
+
AdditionalStat(
|
|
524
|
+
"MMLU",
|
|
525
|
+
fs.Keys.AVERAGE_MMLU_ACCURACY + "|^mmlu_",
|
|
526
|
+
fs.Keys.AVERAGE_MMLU_ACCURACY,
|
|
527
|
+
".2f",
|
|
528
|
+
)
|
|
529
|
+
],
|
|
530
|
+
},
|
|
531
|
+
"last_columns": [
|
|
532
|
+
SimpleStat(
|
|
533
|
+
"System Info",
|
|
534
|
+
fs.Keys.SYSTEM_INFO,
|
|
535
|
+
"s",
|
|
536
|
+
"left",
|
|
537
|
+
omit_if_lean=True,
|
|
538
|
+
wrap=50,
|
|
539
|
+
),
|
|
540
|
+
SimpleStat(
|
|
541
|
+
"Software Versions",
|
|
542
|
+
SW_VERSIONS,
|
|
543
|
+
"s",
|
|
544
|
+
"left",
|
|
545
|
+
omit_if_lean=True,
|
|
546
|
+
wrap=45,
|
|
547
|
+
),
|
|
548
|
+
],
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
basic_build_stats = [
|
|
552
|
+
Keys.CHECKPOINT,
|
|
553
|
+
Keys.DEVICE,
|
|
554
|
+
Keys.DTYPE,
|
|
555
|
+
Keys.RYZEN_AI_VERSION_INFO,
|
|
556
|
+
fs.Keys.TIMESTAMP,
|
|
557
|
+
fs.Keys.SYSTEM_INFO,
|
|
558
|
+
]
|
|
559
|
+
|
|
560
|
+
def __init__(
|
|
561
|
+
self,
|
|
562
|
+
device: str = None,
|
|
563
|
+
dtype: str = None,
|
|
564
|
+
model: str = None,
|
|
565
|
+
days: int = None,
|
|
566
|
+
merge: bool = False,
|
|
567
|
+
lean: bool = False,
|
|
568
|
+
):
|
|
569
|
+
super().__init__()
|
|
570
|
+
self.device = device
|
|
571
|
+
if dtype:
|
|
572
|
+
dtype = dtype.lower()
|
|
573
|
+
dtype = dtype_aliases.get(dtype, dtype)
|
|
574
|
+
self.dtype = dtype
|
|
575
|
+
self.model = model
|
|
576
|
+
self.days = days
|
|
577
|
+
self.lean = lean
|
|
578
|
+
self.tools_found_set = set()
|
|
579
|
+
self.include_stats_filter = {
|
|
580
|
+
Keys.DEVICE: self.device,
|
|
581
|
+
Keys.CHECKPOINT: self.model,
|
|
582
|
+
}
|
|
583
|
+
if merge:
|
|
584
|
+
self.merge_test_fn = LemonadePerfTable.matching_builds
|
|
585
|
+
|
|
586
|
+
def load_stats(self):
|
|
587
|
+
super().load_stats()
|
|
588
|
+
self.tools = list(self.tools_found_set)
|
|
589
|
+
self.tools.sort(key=lambda tool_class: tool_class.unique_name)
|
|
590
|
+
|
|
591
|
+
def include_stats(self, model_stats) -> bool:
|
|
592
|
+
"""
|
|
593
|
+
Returns True if the build was successful and matches
|
|
594
|
+
the criteria specified, else returns False.
|
|
595
|
+
"""
|
|
596
|
+
# Filter out builds that are incomplete
|
|
597
|
+
if (
|
|
598
|
+
not model_stats.get(fs.Keys.BUILD_STATUS, None)
|
|
599
|
+
== build.FunctionStatus.SUCCESSFUL
|
|
600
|
+
):
|
|
601
|
+
return False
|
|
602
|
+
|
|
603
|
+
# Filter out build if it doesn't match specified dtype
|
|
604
|
+
if self.dtype:
|
|
605
|
+
build_dtype = model_stats.get(Keys.DTYPE, "").lower()
|
|
606
|
+
build_dtype = dtype_aliases.get(build_dtype, build_dtype)
|
|
607
|
+
if self.dtype not in build_dtype:
|
|
608
|
+
return False
|
|
609
|
+
|
|
610
|
+
# Filter out build if it doesn't match specified device or model
|
|
611
|
+
for key, value in self.include_stats_filter.items():
|
|
612
|
+
if value is not None:
|
|
613
|
+
model_value = model_stats.get(key, "")
|
|
614
|
+
if model_value is None or value.lower() not in model_value.lower():
|
|
615
|
+
return False
|
|
616
|
+
|
|
617
|
+
# Filter out build if it is too old
|
|
618
|
+
if not self.days is None:
|
|
619
|
+
build_day = model_stats[fs.Keys.TIMESTAMP]
|
|
620
|
+
today = datetime.now(timezone.utc)
|
|
621
|
+
delta = today - build_day
|
|
622
|
+
if delta.days > self.days:
|
|
623
|
+
return False
|
|
624
|
+
|
|
625
|
+
# All tests passed
|
|
626
|
+
return True
|
|
627
|
+
|
|
628
|
+
def post_process_stats(self, model_stats) -> bool:
|
|
629
|
+
tool_columns = self.table_descriptor["tool_columns"]
|
|
630
|
+
data = {}
|
|
631
|
+
|
|
632
|
+
for tool in tool_columns.keys():
|
|
633
|
+
tool_status_key = fs.Keys.TOOL_STATUS + ":" + tool.unique_name
|
|
634
|
+
if (
|
|
635
|
+
model_stats.get(tool_status_key, None)
|
|
636
|
+
== build.FunctionStatus.SUCCESSFUL
|
|
637
|
+
):
|
|
638
|
+
# Successful build of this tool, so remember this tool
|
|
639
|
+
self.tools_found_set.add(tool)
|
|
640
|
+
|
|
641
|
+
# Extract the declared tool stats
|
|
642
|
+
data = data | {
|
|
643
|
+
stat: model_stats.get(stat, None) for stat in tool().status_stats
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
# Find if there are any additional stats for this tool
|
|
647
|
+
# First see if this tool refers to another tool
|
|
648
|
+
if not isinstance(tool_columns[tool], list):
|
|
649
|
+
tool = tool_columns[tool]
|
|
650
|
+
regexp_list = [
|
|
651
|
+
stat.regexp
|
|
652
|
+
for stat in tool_columns[tool]
|
|
653
|
+
if isinstance(stat, AdditionalStat)
|
|
654
|
+
]
|
|
655
|
+
match_expr = "(?:% s)" % "|".join(regexp_list)
|
|
656
|
+
additional_stats = [
|
|
657
|
+
stat for stat in model_stats.keys() if re.match(match_expr, stat)
|
|
658
|
+
]
|
|
659
|
+
data = data | {stat: model_stats[stat] for stat in additional_stats}
|
|
660
|
+
|
|
661
|
+
if not data:
|
|
662
|
+
# No matching tools successfully completed in this build
|
|
663
|
+
return
|
|
664
|
+
|
|
665
|
+
#
|
|
666
|
+
# Add basic build stats
|
|
667
|
+
#
|
|
668
|
+
for key in self.basic_build_stats:
|
|
669
|
+
data[key] = model_stats.get(key, "")
|
|
670
|
+
|
|
671
|
+
# Create a new entry with Driver Versions and relevant Python Packages
|
|
672
|
+
sw_versions = [
|
|
673
|
+
key + ": " + value
|
|
674
|
+
for key, value in data[fs.Keys.SYSTEM_INFO]["Driver Versions"].items()
|
|
675
|
+
]
|
|
676
|
+
sw_versions += [
|
|
677
|
+
pkg
|
|
678
|
+
for pkg in data[fs.Keys.SYSTEM_INFO]["Python Packages"]
|
|
679
|
+
if any(name in pkg for name in PYTHON_PACKAGES)
|
|
680
|
+
]
|
|
681
|
+
if isinstance(data[Keys.RYZEN_AI_VERSION_INFO], dict):
|
|
682
|
+
sw_versions += [
|
|
683
|
+
"Ryzen AI: " + value
|
|
684
|
+
for key, value in data[Keys.RYZEN_AI_VERSION_INFO].items()
|
|
685
|
+
if value is not None and "artifacts" in value
|
|
686
|
+
]
|
|
687
|
+
data[SW_VERSIONS] = sw_versions
|
|
688
|
+
|
|
689
|
+
# Exclude Python Packages and Driver Versions from System Info
|
|
690
|
+
system_info = [
|
|
691
|
+
key + ": " + str(value)
|
|
692
|
+
for key, value in data[fs.Keys.SYSTEM_INFO].items()
|
|
693
|
+
if key not in ["Python Packages", "Driver Versions"]
|
|
694
|
+
]
|
|
695
|
+
data[fs.Keys.SYSTEM_INFO] = system_info
|
|
696
|
+
|
|
697
|
+
self.all_stats.append(data)
|
|
698
|
+
|
|
699
|
+
@staticmethod
|
|
700
|
+
def sort_key(build_stats: dict) -> Tuple:
|
|
701
|
+
return tuple(
|
|
702
|
+
build_stats[key]
|
|
703
|
+
for key in [
|
|
704
|
+
Keys.CHECKPOINT,
|
|
705
|
+
Keys.DEVICE,
|
|
706
|
+
Keys.DTYPE,
|
|
707
|
+
fs.Keys.SYSTEM_INFO,
|
|
708
|
+
SW_VERSIONS,
|
|
709
|
+
fs.Keys.TIMESTAMP,
|
|
710
|
+
]
|
|
711
|
+
)
|
|
712
|
+
|
|
713
|
+
@staticmethod
|
|
714
|
+
def matching_builds(build_stats_1: Dict, build_stats_2: Dict) -> bool:
|
|
715
|
+
"""
|
|
716
|
+
Returns true if the two builds have matching model, device, datatype,
|
|
717
|
+
system info and SW versions
|
|
718
|
+
"""
|
|
719
|
+
merge_key_list = [
|
|
720
|
+
Keys.CHECKPOINT,
|
|
721
|
+
Keys.DEVICE,
|
|
722
|
+
Keys.DTYPE,
|
|
723
|
+
fs.Keys.SYSTEM_INFO,
|
|
724
|
+
SW_VERSIONS,
|
|
725
|
+
]
|
|
726
|
+
dict_1 = {key: build_stats_1[key] for key in merge_key_list}
|
|
727
|
+
dict_2 = {key: build_stats_2[key] for key in merge_key_list}
|
|
728
|
+
|
|
729
|
+
return dict_1 == dict_2
|
|
730
|
+
|
|
731
|
+
@staticmethod
|
|
732
|
+
def get_report_name() -> str:
|
|
733
|
+
current_time = datetime.now(timezone.utc)
|
|
734
|
+
timestamp = current_time.strftime("%Y-%m-%d-%H%M%S")
|
|
735
|
+
return f"{timestamp}_perf.csv"
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
# This file was originally licensed under Apache 2.0. It has been modified.
|
|
739
|
+
# Modifications Copyright (c) 2025 AMD
|