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.

Files changed (61) hide show
  1. lemonade/__init__.py +5 -0
  2. lemonade/api.py +125 -0
  3. lemonade/cache.py +85 -0
  4. lemonade/cli.py +135 -0
  5. lemonade/common/__init__.py +0 -0
  6. lemonade/common/analyze_model.py +26 -0
  7. lemonade/common/build.py +223 -0
  8. lemonade/common/cli_helpers.py +139 -0
  9. lemonade/common/exceptions.py +98 -0
  10. lemonade/common/filesystem.py +368 -0
  11. lemonade/common/labels.py +61 -0
  12. lemonade/common/onnx_helpers.py +176 -0
  13. lemonade/common/plugins.py +10 -0
  14. lemonade/common/printing.py +110 -0
  15. lemonade/common/status.py +490 -0
  16. lemonade/common/system_info.py +390 -0
  17. lemonade/common/tensor_helpers.py +83 -0
  18. lemonade/common/test_helpers.py +28 -0
  19. lemonade/profilers/__init__.py +1 -0
  20. lemonade/profilers/memory_tracker.py +257 -0
  21. lemonade/profilers/profiler.py +55 -0
  22. lemonade/sequence.py +363 -0
  23. lemonade/state.py +159 -0
  24. lemonade/tools/__init__.py +1 -0
  25. lemonade/tools/adapter.py +104 -0
  26. lemonade/tools/bench.py +284 -0
  27. lemonade/tools/huggingface_bench.py +267 -0
  28. lemonade/tools/huggingface_load.py +520 -0
  29. lemonade/tools/humaneval.py +258 -0
  30. lemonade/tools/llamacpp.py +261 -0
  31. lemonade/tools/llamacpp_bench.py +154 -0
  32. lemonade/tools/management_tools.py +273 -0
  33. lemonade/tools/mmlu.py +327 -0
  34. lemonade/tools/ort_genai/__init__.py +0 -0
  35. lemonade/tools/ort_genai/oga.py +1129 -0
  36. lemonade/tools/ort_genai/oga_bench.py +142 -0
  37. lemonade/tools/perplexity.py +146 -0
  38. lemonade/tools/prompt.py +228 -0
  39. lemonade/tools/quark/__init__.py +0 -0
  40. lemonade/tools/quark/quark_load.py +172 -0
  41. lemonade/tools/quark/quark_quantize.py +439 -0
  42. lemonade/tools/report/__init__.py +0 -0
  43. lemonade/tools/report/llm_report.py +203 -0
  44. lemonade/tools/report/table.py +739 -0
  45. lemonade/tools/server/__init__.py +0 -0
  46. lemonade/tools/server/serve.py +1354 -0
  47. lemonade/tools/server/tool_calls.py +146 -0
  48. lemonade/tools/tool.py +374 -0
  49. lemonade/version.py +1 -0
  50. lemonade_install/__init__.py +1 -0
  51. lemonade_install/install.py +774 -0
  52. lemonade_sdk-7.0.0.dist-info/METADATA +116 -0
  53. lemonade_sdk-7.0.0.dist-info/RECORD +61 -0
  54. lemonade_sdk-7.0.0.dist-info/WHEEL +5 -0
  55. lemonade_sdk-7.0.0.dist-info/entry_points.txt +4 -0
  56. lemonade_sdk-7.0.0.dist-info/licenses/LICENSE +201 -0
  57. lemonade_sdk-7.0.0.dist-info/licenses/NOTICE.md +21 -0
  58. lemonade_sdk-7.0.0.dist-info/top_level.txt +3 -0
  59. lemonade_server/cli.py +260 -0
  60. lemonade_server/model_manager.py +98 -0
  61. 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