mlcflow 1.2.0__tar.gz → 1.2.2__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.
Files changed (29) hide show
  1. {mlcflow-1.2.0 → mlcflow-1.2.2}/PKG-INFO +1 -1
  2. mlcflow-1.2.2/mlc/__init__.py +44 -0
  3. {mlcflow-1.2.0 → mlcflow-1.2.2}/mlc/index.py +13 -7
  4. {mlcflow-1.2.0 → mlcflow-1.2.2}/mlc/main.py +163 -23
  5. {mlcflow-1.2.0 → mlcflow-1.2.2}/mlc/repo_action.py +8 -3
  6. {mlcflow-1.2.0 → mlcflow-1.2.2}/mlc/script_action.py +188 -70
  7. {mlcflow-1.2.0 → mlcflow-1.2.2}/mlc/utils.py +2 -0
  8. {mlcflow-1.2.0 → mlcflow-1.2.2}/mlcflow.egg-info/PKG-INFO +1 -1
  9. {mlcflow-1.2.0 → mlcflow-1.2.2}/mlcflow.egg-info/entry_points.txt +4 -0
  10. {mlcflow-1.2.0 → mlcflow-1.2.2}/pyproject.toml +5 -1
  11. mlcflow-1.2.0/mlc/__init__.py +0 -5
  12. {mlcflow-1.2.0 → mlcflow-1.2.2}/LICENSE.md +0 -0
  13. {mlcflow-1.2.0 → mlcflow-1.2.2}/README.md +0 -0
  14. {mlcflow-1.2.0 → mlcflow-1.2.2}/mlc/__main__.py +0 -0
  15. {mlcflow-1.2.0 → mlcflow-1.2.2}/mlc/action.py +0 -0
  16. {mlcflow-1.2.0 → mlcflow-1.2.2}/mlc/action_factory.py +0 -0
  17. {mlcflow-1.2.0 → mlcflow-1.2.2}/mlc/cache_action.py +0 -0
  18. {mlcflow-1.2.0 → mlcflow-1.2.2}/mlc/cfg_action.py +0 -0
  19. {mlcflow-1.2.0 → mlcflow-1.2.2}/mlc/error_codes.py +0 -0
  20. {mlcflow-1.2.0 → mlcflow-1.2.2}/mlc/experiment_action.py +0 -0
  21. {mlcflow-1.2.0 → mlcflow-1.2.2}/mlc/item.py +0 -0
  22. {mlcflow-1.2.0 → mlcflow-1.2.2}/mlc/logger.py +0 -0
  23. {mlcflow-1.2.0 → mlcflow-1.2.2}/mlc/meta_schema.py +0 -0
  24. {mlcflow-1.2.0 → mlcflow-1.2.2}/mlc/repo.py +0 -0
  25. {mlcflow-1.2.0 → mlcflow-1.2.2}/mlcflow.egg-info/SOURCES.txt +0 -0
  26. {mlcflow-1.2.0 → mlcflow-1.2.2}/mlcflow.egg-info/dependency_links.txt +0 -0
  27. {mlcflow-1.2.0 → mlcflow-1.2.2}/mlcflow.egg-info/requires.txt +0 -0
  28. {mlcflow-1.2.0 → mlcflow-1.2.2}/mlcflow.egg-info/top_level.txt +0 -0
  29. {mlcflow-1.2.0 → mlcflow-1.2.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mlcflow
3
- Version: 1.2.0
3
+ Version: 1.2.2
4
4
  Summary: An automation interface tailored for CPU/GPU benchmarking
5
5
  Author-email: MLCommons <systems@mlcommons.org>
6
6
  License:
@@ -0,0 +1,44 @@
1
+ from .action import access
2
+ import os
3
+ import subprocess
4
+
5
+
6
+ def _get_version():
7
+ """Read version from VERSION file or package metadata, and append git commit hash if available."""
8
+ pkg_dir = os.path.dirname(os.path.abspath(__file__))
9
+ root_dir = os.path.dirname(pkg_dir)
10
+
11
+ # Read VERSION file (works in dev/source tree)
12
+ version = None
13
+ for vpath in [os.path.join(root_dir, "VERSION"),
14
+ os.path.join(pkg_dir, "VERSION")]:
15
+ if os.path.isfile(vpath):
16
+ with open(vpath) as f:
17
+ version = f.read().strip()
18
+ break
19
+
20
+ # Fall back to installed package metadata
21
+ if not version:
22
+ try:
23
+ from importlib.metadata import version as pkg_version
24
+ version = pkg_version("mlcflow")
25
+ except Exception:
26
+ version = "0.0.0"
27
+
28
+ # Append git short commit hash if in a git repo
29
+ try:
30
+ commit = subprocess.check_output(
31
+ ["git", "-C", root_dir, "rev-parse", "--short", "HEAD"],
32
+ stderr=subprocess.DEVNULL, text=True
33
+ ).strip()
34
+ if commit:
35
+ version = f"{version}+{commit}"
36
+ except Exception:
37
+ pass
38
+
39
+ return version
40
+
41
+
42
+ __version__ = _get_version()
43
+
44
+ __all__ = ['access']
@@ -347,20 +347,21 @@ class Index:
347
347
  logger.debug(
348
348
  f"Removed {removed_count} item(s) from {ft} index")
349
349
 
350
- def _delete_by_uid(self, folder_type, uid, alias):
350
+ def _delete_index_entries(self, folder_type, key, value):
351
351
  """
352
- Delete old index entry using UID (prevents duplicates).
352
+ Remove index entries matching for the same path or same UID.
353
353
  """
354
- # logger.debug(f"Deleting and updating index entry for the script {alias} with UID {uid}")
354
+ # logger.debug(f"Deleting index entries in {folder_type} where {key} == {value}")
355
355
  self.indices[folder_type] = [
356
356
  item for item in self.indices[folder_type]
357
- if item["uid"] != uid
357
+ if item.get(key) != value
358
358
  ]
359
359
 
360
360
  def _process_config_file(
361
361
  self, config_file, folder_type, folder_path, repo):
362
362
  """
363
- Process a single configuration file (meta.json or meta.yaml) and add its data to the corresponding index.
363
+ Process a single configuration file (meta.json or meta.yaml) and
364
+ add its data to the corresponding index when the configuration file appears to be changed.
364
365
 
365
366
  Args:
366
367
  config_file (str): Path to the configuration file.
@@ -410,8 +411,13 @@ class Index:
410
411
  raise ValueError(
411
412
  f"Meta validation failed for {config_file}. Fix the above error(s) and try again.")
412
413
 
413
- # Validate and add to indices
414
- self._delete_by_uid(folder_type, unique_id, alias)
414
+ # Remove stale entry for the same meta file path if exists
415
+ self._delete_index_entries(folder_type, "path", folder_path)
416
+
417
+ # Remove index entry with the same UID for other meta file if
418
+ # exists
419
+ self._delete_index_entries(folder_type, "uid", unique_id)
420
+
415
421
  self.indices[folder_type].append({
416
422
  "uid": unique_id,
417
423
  "tags": tags,
@@ -78,6 +78,127 @@ class Automation:
78
78
 
79
79
 
80
80
  mlc_run_cmd = None
81
+ _current_target = None
82
+
83
+
84
+ def get_version_info():
85
+ """Return mlcflow version string with commit hash."""
86
+ try:
87
+ from . import __version__
88
+ return f"mlcflow {__version__}"
89
+ except ImportError:
90
+ pass
91
+ try:
92
+ from importlib.metadata import version
93
+ return f"mlcflow {version('mlcflow')}"
94
+ except Exception:
95
+ return "mlcflow (unknown version)"
96
+
97
+
98
+ def _get_repo_hashes():
99
+ """Get git info for all repos. Returns list of (alias, branch, hash, has_local_changes)."""
100
+ import subprocess
101
+ if default_parent is None:
102
+ return []
103
+ results = []
104
+ for repo in default_parent.repos:
105
+ alias = os.path.basename(repo.path)
106
+ git_dir = os.path.join(repo.path, '.git')
107
+ if not os.path.isdir(git_dir):
108
+ continue
109
+ try:
110
+ commit = subprocess.check_output(
111
+ ["git", "-C", repo.path, "rev-parse", "--short", "HEAD"],
112
+ stderr=subprocess.DEVNULL, text=True
113
+ ).strip()
114
+ branch = subprocess.check_output(
115
+ ["git", "-C", repo.path, "rev-parse", "--abbrev-ref", "HEAD"],
116
+ stderr=subprocess.DEVNULL, text=True
117
+ ).strip()
118
+ # Only tracked file changes (ignore untracked files)
119
+ dirty = subprocess.check_output(
120
+ ["git", "-C", repo.path, "status", "--porcelain", "-uno"],
121
+ stderr=subprocess.DEVNULL, text=True
122
+ ).strip()
123
+ results.append((alias, branch, commit, bool(dirty)))
124
+ except Exception:
125
+ pass
126
+ return results
127
+
128
+
129
+ def _report_error(e):
130
+ import traceback
131
+ from .script_action import ScriptExecutionError
132
+
133
+ # Log the error with target context
134
+ etype = type(e).__name__ if not isinstance(e, ScriptExecutionError) else ''
135
+ prefix = f'{etype}: ' if etype else ''
136
+ if _current_target:
137
+ logger.error(f"Error during '{_current_target}' action: {prefix}{e}")
138
+ else:
139
+ logger.error(f"{prefix}{e}")
140
+
141
+ # Show the last traceback frame from outside the mlcflow package
142
+ tb = traceback.extract_tb(e.__traceback__)
143
+ if tb:
144
+ _mlc_pkg_dir = os.path.dirname(os.path.abspath(__file__))
145
+ # Prefer the last frame outside the mlcflow package
146
+ last = tb[-1]
147
+ for frame in reversed(tb):
148
+ if not os.path.abspath(frame.filename).startswith(_mlc_pkg_dir):
149
+ last = frame
150
+ break
151
+ logger.error(f" at {last.filename}:{last.lineno} in {last.name}")
152
+
153
+ # For script execution errors, show actionable info
154
+ if isinstance(e, ScriptExecutionError):
155
+ script_name = e.script_name
156
+ repo_alias = e.repo_alias
157
+ run_args = e.run_args
158
+
159
+ if script_name:
160
+ # Build rerun command with user-facing inputs only
161
+ rerun_parts = ["mlcr", script_name]
162
+ _skip_keys = {
163
+ 'mlc_run_cmd', 'tags', 'details', 'path_only', 'p',
164
+ 'action', 'target', 'rebuild', 'env', 'script_tags',
165
+ 'run_cmd', 'run_final_cmds', 'skip_run_cmd', 'run_cmd_prefix',
166
+ 'add_deps_recursive', 'add_deps', 'file_path',
167
+ 'quiet', 'real_run', 'fake_run_deps', 'keep_detached',
168
+ 'pass_user_group', 'use_host_group_id', 'use_host_user_id',
169
+ 'extra_run_args', 'port_maps', 'mounts', 'pre_run_cmds',
170
+ 'docker_run_deps',
171
+ }
172
+ for k, v in run_args.items():
173
+ if k in _skip_keys:
174
+ continue
175
+ if isinstance(v, (dict, list)):
176
+ continue
177
+ if k.startswith('MLC_') or k.startswith('mlc_'):
178
+ continue
179
+ rerun_parts.append(f"--{k}={v}")
180
+ rerun_cmd = " ".join(rerun_parts)
181
+ logger.error(f"Failed script: {script_name}")
182
+ logger.error(f"To rerun just the failed part: {rerun_cmd}")
183
+
184
+ if e.version_info_file:
185
+ logger.error(f"Dependency versions: {e.version_info_file}")
186
+
187
+ # Derive issues URL from repo alias
188
+ issues_url = 'https://github.com/mlcommons/mlperf-automations/issues'
189
+ if repo_alias and '@' in repo_alias:
190
+ issues_url = 'https://github.com/' + \
191
+ repo_alias.replace('@', '/') + '/issues'
192
+ logger.error(
193
+ f"Please file an issue at {issues_url} with the full console log.")
194
+
195
+ # Show version and repo commit hashes for debugging
196
+ logger.error(f"{get_version_info()}")
197
+ repo_hashes = _get_repo_hashes()
198
+ if repo_hashes:
199
+ for alias, branch, commit, dirty in repo_hashes:
200
+ marker = " (local changes)" if dirty else ""
201
+ logger.error(f" {alias}: {branch} {commit}{marker}")
81
202
 
82
203
 
83
204
  def mlc_expand_short(action, target="script"):
@@ -90,17 +211,13 @@ def mlc_expand_short(action, target="script"):
90
211
  # Call the main function
91
212
  try:
92
213
  main()
214
+ except KeyboardInterrupt:
215
+ print("\nInterrupted.")
216
+ sys.exit(1)
93
217
  except SystemExit:
94
218
  raise
95
219
  except Exception as e:
96
- import traceback
97
- tb = traceback.extract_tb(e.__traceback__)
98
- if tb:
99
- last = tb[-1]
100
- logger.error(f"{e}")
101
- logger.error(f" at {last.filename}:{last.lineno} in {last.name}")
102
- else:
103
- logger.error(f"{e}")
220
+ _report_error(e)
104
221
  sys.exit(1)
105
222
 
106
223
 
@@ -112,14 +229,22 @@ def mlcd():
112
229
  mlc_expand_short("docker")
113
230
 
114
231
 
115
- def mlcdr():
116
- mlc_expand_short("docker")
232
+ def mlca():
233
+ mlc_expand_short("apptainer")
117
234
 
118
235
 
119
236
  def mlcrr():
120
237
  mlc_expand_short("remote-run")
121
238
 
122
239
 
240
+ def mlcre():
241
+ mlc_expand_short("remote-experiment")
242
+
243
+
244
+ def mlcrd():
245
+ mlc_expand_short("remote-docker")
246
+
247
+
123
248
  def mlce():
124
249
  mlc_expand_short("experiment")
125
250
 
@@ -142,6 +267,13 @@ def process_console_output(res, target, action, run_args):
142
267
  if not run_args.get('path_only'):
143
268
  logger.warning(
144
269
  f"""No {target} entry found for the specified input: {run_args}!""")
270
+ logger.info(
271
+ "Tip: Run 'mlc pull repo' to fetch the latest upstream changes.")
272
+ repo_hashes = _get_repo_hashes()
273
+ for alias, branch, commit, dirty in repo_hashes:
274
+ if dirty:
275
+ logger.warning(
276
+ f"Repo '{alias}' ({branch}) has local changes - 'mlc pull repo' may fail. Commit or stash changes first.")
145
277
  else:
146
278
  for item in res['list']:
147
279
  if run_args.get('path_only'):
@@ -245,8 +377,9 @@ def build_parser(pre_args):
245
377
  reindex_parser.add_argument('extra', nargs=argparse.REMAINDER)
246
378
 
247
379
  # Script-only
248
- for action in ['docker', 'docker-run',
249
- 'experiment', 'remote-run', 'doc', 'lint']:
380
+ for action in ['docker', 'docker-run', 'apptainer',
381
+ 'experiment', 'remote-run', 'remote-experiment',
382
+ 'remote-docker', 'doc', 'lint']:
250
383
  p = subparsers.add_parser(action, add_help=False)
251
384
  p.add_argument('target', choices=['script', 'run'])
252
385
  p.add_argument(
@@ -286,8 +419,9 @@ def build_run_args(args):
286
419
  if args.command in ['pull', 'rm', 'add', 'find'] and args.target == "repo":
287
420
  run_args['repo'] = args.details
288
421
 
289
- if args.command in ['docker', 'docker-run', 'experiment',
290
- 'remote-run', 'doc', 'lint'] and args.target == "run":
422
+ if args.command in ['docker', 'docker-run', 'apptainer', 'experiment',
423
+ 'remote-run', 'remote-experiment',
424
+ 'remote-docker', 'doc', 'lint'] and args.target == "run":
291
425
  # run_args['target'] = 'script' #dont modify this as script might have
292
426
  # target as in input
293
427
  args.target = "script"
@@ -386,6 +520,12 @@ def main():
386
520
  check_raw_arguments_for_non_ascii()
387
521
  convert_hyphen_to_underscore_in_args()
388
522
 
523
+ # Handle version before argparse to avoid --version conflicting with
524
+ # script arguments like --version=3.4
525
+ if len(sys.argv) >= 2 and sys.argv[1] in ('--version', '-V', 'version'):
526
+ print(get_version_info())
527
+ sys.exit(0)
528
+
389
529
  pre_parser = build_pre_parser()
390
530
  pre_args, remaining_args = pre_parser.parse_known_args()
391
531
 
@@ -404,7 +544,8 @@ def main():
404
544
  if pre_args.help and not "tags" in run_args:
405
545
  help_text = ""
406
546
  if pre_args.target == "run":
407
- if pre_args.action.startswith("docker"):
547
+ if pre_args.action.startswith(
548
+ "docker") or pre_args.action == "apptainer":
408
549
  pre_args.target = "script"
409
550
  else:
410
551
  logger.error(
@@ -456,6 +597,9 @@ def main():
456
597
  logging.error("Error: No command specified.")
457
598
  sys.exit(1)
458
599
 
600
+ global _current_target
601
+ _current_target = args.target
602
+
459
603
  action = get_action(args.target, default_parent)
460
604
 
461
605
  if not action or not hasattr(action, args.command):
@@ -477,15 +621,11 @@ def main():
477
621
  if __name__ == '__main__':
478
622
  try:
479
623
  main()
624
+ except KeyboardInterrupt:
625
+ print("\nInterrupted.")
626
+ sys.exit(1)
480
627
  except SystemExit:
481
628
  raise
482
629
  except Exception as e:
483
- import traceback
484
- tb = traceback.extract_tb(e.__traceback__)
485
- if tb:
486
- last = tb[-1]
487
- logger.error(f"{e}")
488
- logger.error(f" at {last.filename}:{last.lineno} in {last.name}")
489
- else:
490
- logger.error(f"{e}")
630
+ _report_error(e)
491
631
  sys.exit(1)
@@ -425,9 +425,14 @@ class RepoAction(Action):
425
425
  f"meta.yaml not found in {repo_path}. Repo pulled but not registered in MLC repos. Skipping...")
426
426
  return {"return": 0}
427
427
 
428
- with open(meta_file_path, 'r') as meta_file:
429
- meta_data = yaml.safe_load(meta_file)
430
- meta_data["path"] = repo_path
428
+ try:
429
+ with open(meta_file_path, 'r') as meta_file:
430
+ meta_data = yaml.safe_load(meta_file)
431
+ meta_data["path"] = repo_path
432
+ except yaml.YAMLError as e:
433
+ logger.error(f"Error loading YAML configuration: {e}")
434
+ return {"return": 1,
435
+ "error": f"Syntax error in {meta_file_path}: {e}"}
431
436
 
432
437
  r = self.register_repo(repo_path, meta_data, ignore_on_conflict)
433
438
  if r['return'] > 0:
@@ -1,3 +1,4 @@
1
+ import re
1
2
  from .action import Action
2
3
  import os
3
4
  import sys
@@ -8,6 +9,7 @@ from .index import Index
8
9
  from . import utils
9
10
  from .logger import logger
10
11
 
12
+
11
13
  class ScriptAction(Action):
12
14
  """
13
15
  ####################################################################################################################
@@ -35,6 +37,7 @@ class ScriptAction(Action):
35
37
 
36
38
  """
37
39
  parent = None
40
+
38
41
  def __init__(self, parent=None):
39
42
  self.parent = parent
40
43
  self.__dict__.update(vars(parent))
@@ -59,7 +62,7 @@ class ScriptAction(Action):
59
62
  return res
60
63
 
61
64
  find = search
62
-
65
+
63
66
  def rm(self, i):
64
67
  """
65
68
  ####################################################################################################################
@@ -67,7 +70,7 @@ class ScriptAction(Action):
67
70
  Action: Remove(rm)
68
71
  ####################################################################################################################
69
72
 
70
- The `remove` (`rm`) action deletes one or more scripts from MLC repositories.
73
+ The `remove` (`rm`) action deletes one or more scripts from MLC repositories.
71
74
 
72
75
  Example Command:
73
76
 
@@ -86,14 +89,14 @@ class ScriptAction(Action):
86
89
  Action: Show
87
90
  ####################################################################################################################
88
91
 
89
- The `show` action retrieves the path and metadata of the searched script in MLC repositories.
92
+ The `show` action retrieves the path and metadata of the searched script in MLC repositories.
90
93
 
91
94
  Example Command:
92
95
 
93
96
  mlc show script --tags=detect,os
94
97
 
95
98
  Example Output:
96
-
99
+
97
100
  arjun@intel-spr-i9:~$ mlc show script --tags=detect,os
98
101
  [2025-02-14 02:56:16,604 main.py:1404 INFO] - Showing script with tags: detect,os
99
102
  Location: /home/arjun/MLC/repos/gateoverflow@mlperf-automations/script/detect-os:
@@ -102,7 +105,7 @@ class ScriptAction(Action):
102
105
  alias: detect-os
103
106
  description: Detects the operating system and platform information
104
107
  tags: ['detect-os', 'detect', 'os', 'info']
105
- new_env_keys: ['MLC_HOST_OS_*', '+MLC_HOST_OS_*', 'MLC_HOST_PLATFORM_*', 'MLC_HOST_PYTHON_*', 'MLC_HOST_SYSTEM_NAME',
108
+ new_env_keys: ['MLC_HOST_OS_*', '+MLC_HOST_OS_*', 'MLC_HOST_PLATFORM_*', 'MLC_HOST_PYTHON_*', 'MLC_HOST_SYSTEM_NAME',
106
109
  'MLC_RUN_STATE_DOCKER', '+PATH']
107
110
  new_state_keys: ['os_uname_*']
108
111
  ......................................................
@@ -117,7 +120,14 @@ class ScriptAction(Action):
117
120
  if res['return'] > 0:
118
121
  return res
119
122
  logger.info(f"Showing script with tags: {run_args.get('tags')}")
120
- script_meta_keys_to_show = ["uid", "alias", "description", "tags", "new_env_keys", "new_state_keys", "cache"]
123
+ script_meta_keys_to_show = [
124
+ "uid",
125
+ "alias",
126
+ "description",
127
+ "tags",
128
+ "new_env_keys",
129
+ "new_state_keys",
130
+ "cache"]
121
131
  for item in res['list']:
122
132
  print(f"""Location: {item.path}:
123
133
  Main Script Meta:""")
@@ -128,9 +138,10 @@ Main Script Meta:""")
128
138
  print(" Input mapping:")
129
139
  utils.printd(item.meta["input_mapping"], begin_spaces=8)
130
140
  print("......................................................")
131
- print(f"""For full script meta, see meta file at {os.path.join(item.path, "meta.yaml")}""")
141
+ print(
142
+ f"""For full script meta, see meta file at {os.path.join(item.path, "meta.yaml")}""")
132
143
  print("")
133
-
144
+
134
145
  return {'return': 0}
135
146
 
136
147
  def add(self, i):
@@ -140,17 +151,17 @@ Main Script Meta:""")
140
151
  Action: Add
141
152
  ####################################################################################################################
142
153
 
143
- The `add` action creates a new script in a registered MLC repository.
154
+ The `add` action creates a new script in a registered MLC repository.
144
155
 
145
156
  Syntax:
146
157
 
147
158
  mlc add script <user@repo>:new_script --tags=benchmark
148
159
 
149
160
  Options:
150
- --template_tags: A comma-separated list of tags to create a new MLC script based on existing templates.
161
+ --template_tags: A comma-separated list of tags to create a new MLC script based on existing templates.
151
162
 
152
163
  Example Output:
153
-
164
+
154
165
  arjun@intel-spr-i9:~$ mlc add script gateoverflow@mlperf-automations --tags=benchmark --template_tags=app,mlperf,inference
155
166
  More than one script found for None:
156
167
  1. /home/arjun/MLC/repos/gateoverflow@mlperf-automations/script/app-mlperf-inference-mlcommons-python
@@ -188,7 +199,7 @@ Main Script Meta:""")
188
199
  ii['src_tags'] = i.get("template_tags", "template,generic")
189
200
  ii['dest'] = item
190
201
  ii['tags'] = i.get('tags', [])
191
- res = self.cp(ii)
202
+ res = self.cp(ii)
192
203
 
193
204
  return res
194
205
 
@@ -208,7 +219,8 @@ Main Script Meta:""")
208
219
  module_name = os.path.splitext(os.path.basename(script_path))[0]
209
220
  spec = importlib.util.spec_from_file_location(module_name, script_path)
210
221
  if spec is None or spec.loader is None:
211
- raise ImportError(f"Cannot create a module spec for: {script_path}")
222
+ raise ImportError(
223
+ f"Cannot create a module spec for: {script_path}")
212
224
 
213
225
  module = importlib.util.module_from_spec(spec)
214
226
  spec.loader.exec_module(module)
@@ -219,11 +231,12 @@ Main Script Meta:""")
219
231
  self.action_type = "script"
220
232
  repos_folder = self.repos_path
221
233
 
222
- # Import script submodule
234
+ # Import script submodule
223
235
  script_path = self.find_target_folder("script")
224
236
  if not script_path:
225
- logger.warning("Script automation not found. Automatically pulling mlcommons@mlperf-automations repository...")
226
-
237
+ logger.warning(
238
+ "Script automation not found. Automatically pulling mlcommons@mlperf-automations repository...")
239
+
227
240
  # Use the access method to pull the required repository
228
241
  result = self.access({
229
242
  "automation": "repo",
@@ -239,11 +252,15 @@ Main Script Meta:""")
239
252
  # Try to find the script path again after pulling
240
253
  script_path = self.find_target_folder("script")
241
254
  if not script_path:
242
- return {'return': 1, 'error': f"""Script automation still not found after pulling mlcommons@mlperf-automations --branch=dev."""}
255
+ return {
256
+ 'return': 1, 'error': f"""Script automation still not found after pulling mlcommons@mlperf-automations --branch=dev."""}
243
257
  else:
244
- # If pull failed, return the original error with additional info
245
- logger.error(f"Failed to pull mlcommons@mlperf-automations repository: {result.get('error', 'Unknown error')}")
246
- return {'return': 1, 'error': f"""Script automation not found and failed to automatically pull mlcommons@mlperf-automations --branch=dev. Please run "mlc pull repo mlcommons@mlperf-automations --branch=dev" manually: {result.get('error', 'Unknown error')}"""}
258
+ # If pull failed, return the original error with additional
259
+ # info
260
+ logger.error(
261
+ f"Failed to pull mlcommons@mlperf-automations repository: {result.get('error', 'Unknown error')}")
262
+ return {
263
+ 'return': 1, 'error': f"""Script automation not found and failed to automatically pull mlcommons@mlperf-automations --branch=dev. Please run "mlc pull repo mlcommons@mlperf-automations --branch=dev" manually: {result.get('error', 'Unknown error')}"""}
247
264
 
248
265
  module_path = os.path.join(script_path, "module.py")
249
266
  module = self.dynamic_import_module(module_path)
@@ -254,45 +271,89 @@ Main Script Meta:""")
254
271
  params = inspect.signature(ctor).parameters
255
272
 
256
273
  if 'run_args' in params:
257
- automation_instance = module.ScriptAutomation(self, module_path, run_args)
258
- else:
259
- automation_instance = module.ScriptAutomation(self, module_path)
260
-
261
- if function_name == "run":
262
- result = automation_instance.run(run_args) # Pass args to the run method
263
- elif function_name == "docker":
264
- result = automation_instance.docker(run_args) # Pass args to the run method
265
- elif function_name == "test":
266
- result = automation_instance.test(run_args) # Pass args to the run method
267
- elif function_name == "experiment":
268
- result = automation_instance.experiment(run_args) # Pass args to the experiment method
269
- elif function_name == "remote_run":
270
- result = automation_instance.remote_run(run_args) # Pass args to the experiment method
271
- elif function_name == "help":
272
- result = automation_instance.help(run_args) # Pass args to the help method
273
- elif function_name == "doc":
274
- result = automation_instance.doc(run_args) # Pass args to the doc method
275
- elif function_name == "lint":
276
- result = automation_instance.lint(run_args) # Pass args to the lint method
274
+ automation_instance = module.ScriptAutomation(
275
+ self, module_path, run_args)
277
276
  else:
278
- return {'return': 1, 'error': f'Function {function_name} is not supported'}
279
-
277
+ automation_instance = module.ScriptAutomation(
278
+ self, module_path)
279
+
280
+ try:
281
+ if function_name == "run":
282
+ result = automation_instance.run(
283
+ run_args) # Pass args to the run method
284
+ elif function_name == "docker":
285
+ result = automation_instance.docker(
286
+ run_args) # Pass args to the run method
287
+ elif function_name == "test":
288
+ result = automation_instance.test(
289
+ run_args) # Pass args to the run method
290
+ elif function_name == "experiment":
291
+ result = automation_instance.experiment(
292
+ run_args) # Pass args to the experiment method
293
+ elif function_name == "remote_run":
294
+ result = automation_instance.remote_run(
295
+ run_args) # Pass args to the experiment method
296
+ elif function_name == "help":
297
+ result = automation_instance.help(
298
+ run_args) # Pass args to the help method
299
+ elif function_name == "doc":
300
+ result = automation_instance.doc(
301
+ run_args) # Pass args to the doc method
302
+ elif function_name == "lint":
303
+ result = automation_instance.lint(
304
+ run_args) # Pass args to the lint method
305
+ else:
306
+ return {
307
+ 'return': 1, 'error': f'Function {function_name} is not supported'}
308
+ except ScriptExecutionError:
309
+ raise
310
+ except Exception as exc:
311
+ _repo_match = re.search(r'/repos/([^/]+)/', module_path)
312
+ _repo_alias = _repo_match.group(1) if _repo_match else None
313
+ _script_name = run_args.get('tags', run_args.get('details'))
314
+ raise ScriptExecutionError(
315
+ f"Script {function_name} execution failed in {module_path}." +
316
+ "\nError : " + f"{type(exc).__name__}: {exc}",
317
+ script_name=_script_name, repo_alias=_repo_alias, module_path=module_path,
318
+ run_args=run_args) from exc
319
+
280
320
  if result['return'] > 0:
281
321
  error = result.get('error', "")
282
- raise ScriptExecutionError(f"Script {function_name} execution failed in {module_path}. \nError : {error}")
283
-
284
- if str(run_args.get("mlc_output")).lower() in ["on", "true", "yes", "1"]:
322
+ _name_match = re.search(r'name\s*=\s*([^,)]+)', error)
323
+ _script_name = _name_match.group(1).strip() if _name_match else run_args.get(
324
+ 'tags', run_args.get('details'))
325
+ _repo_match = re.search(r'/repos/([^/]+)/', module_path)
326
+ _repo_alias = _repo_match.group(1) if _repo_match else None
327
+ # Dump dependency version info to file for debugging
328
+ _version_info_file = None
329
+ _version_info = result.get('version_info', [])
330
+ if _version_info:
331
+ _version_info_file = os.path.join(
332
+ os.getcwd(), 'mlc-error-version-info.json')
333
+ try:
334
+ with open(_version_info_file, 'w') as _vf:
335
+ json.dump(_version_info, _vf, indent=2)
336
+ except Exception:
337
+ _version_info_file = None
338
+ raise ScriptExecutionError(
339
+ f"Script {function_name} execution failed in {module_path}. \nError : {error}",
340
+ script_name=_script_name, repo_alias=_repo_alias, module_path=module_path,
341
+ run_args=run_args, version_info_file=_version_info_file)
342
+
343
+ if str(run_args.get("mlc_output")).lower() in [
344
+ "on", "true", "yes", "1"]:
285
345
  with open("tmp-state.json", "w") as f:
286
346
  json.dump(result['new_state'], f, indent=2)
287
347
 
288
348
  with open("tmp-run-env.out", "w") as f:
289
- for key,val in result['new_env'].items():
349
+ for key, val in result['new_env'].items():
290
350
  f.write(f"""{key}="{val}"\n""")
291
351
 
292
352
  return result
293
353
  else:
294
354
  logger.info("ScriptAutomation class not found in the script.")
295
- return {'return': 1, 'error': 'ScriptAutomation class not found in the script.'}
355
+ return {'return': 1,
356
+ 'error': 'ScriptAutomation class not found in the script.'}
296
357
 
297
358
  def docker(self, run_args):
298
359
  return self.docker_run(run_args)
@@ -304,13 +365,13 @@ Main Script Meta:""")
304
365
  Action: Docker
305
366
  ####################################################################################################################
306
367
 
307
- The `docker` action runs scripts inside a containerized environment.
368
+ The `docker` action runs scripts inside a containerized environment.
308
369
 
309
370
  An MLCFlow script can be executed inside a Docker container using either of the following syntaxes:
310
371
 
311
- 1. Docker Run: mlc docker run --tags=<script tags> <run flags> (e.g., mlc docker run --tags=detect,os --docker_dt
372
+ 1. Docker Run: mlc docker run --tags=<script tags> <run flags> (e.g., mlc docker run --tags=detect,os --docker_dt
312
373
  --docker_cache=no)
313
- 2. Docker Script: mlc docker script --tags=<script tags> <run flags> (e.g., mlc docker script --tags=detect,os
374
+ 2. Docker Script: mlc docker script --tags=<script tags> <run flags> (e.g., mlc docker script --tags=detect,os
314
375
  --docker_dt --docker_cache=no)
315
376
 
316
377
  Flags Available:
@@ -342,7 +403,7 @@ Main Script Meta:""")
342
403
  Action: remote-run
343
404
  ####################################################################################################################
344
405
 
345
- The `remote-run` action runs a shell command on a remote machine via ssh connection.
406
+ The `remote-run` action runs a shell command on a remote machine via ssh connection.
346
407
 
347
408
 
348
409
  Flags Available:
@@ -359,7 +420,6 @@ Main Script Meta:""")
359
420
  """
360
421
  return self.call_script_module_function("remote_run", run_args)
361
422
 
362
-
363
423
  def run(self, run_args):
364
424
  """
365
425
  ####################################################################################################################
@@ -367,7 +427,7 @@ Main Script Meta:""")
367
427
  Action: Run
368
428
  ####################################################################################################################
369
429
 
370
- The `run` action executes a script from an MLC repository.
430
+ The `run` action executes a script from an MLC repository.
371
431
 
372
432
  Example Command:
373
433
 
@@ -378,14 +438,13 @@ Main Script Meta:""")
378
438
 
379
439
  1. -j: Displays the output in JSON format.
380
440
  2. Instead of using `mlc run script --tags=`, you can simply use `mlcr`.
381
- 3. *<Individual script inputs>: The `mlcr` command can accept additional inputs defined in the script's `input_mappings` metadata.
441
+ 3. *<Individual script inputs>: The `mlcr` command can accept additional inputs defined in the script's `input_mappings` metadata.
382
442
 
383
443
  """
384
444
  if not run_args.get('tags') and not run_args.get('details'):
385
445
  return self.call_script_module_function("help", run_args)
386
446
  return self.call_script_module_function("run", run_args)
387
447
 
388
-
389
448
  def test(self, run_args):
390
449
  """
391
450
  ####################################################################################################################
@@ -393,7 +452,7 @@ Main Script Meta:""")
393
452
  Action: test
394
453
  ####################################################################################################################
395
454
 
396
- The `test` action validates scripts that are configured with a `tests` section in `meta.yaml`.
455
+ The `test` action validates scripts that are configured with a `tests` section in `meta.yaml`.
397
456
 
398
457
  Example Command:
399
458
 
@@ -402,7 +461,6 @@ Main Script Meta:""")
402
461
  """
403
462
  return self.call_script_module_function("test", run_args)
404
463
 
405
-
406
464
  def doc(self, run_args):
407
465
  """
408
466
  ####################################################################################################################
@@ -410,7 +468,7 @@ Main Script Meta:""")
410
468
  Action: doc
411
469
  ####################################################################################################################
412
470
 
413
- The `doc` action creates automatic README for scripts from the contents in `meta.yaml`.
471
+ The `doc` action creates automatic README for scripts from the contents in `meta.yaml`.
414
472
 
415
473
  Example Command:
416
474
 
@@ -426,7 +484,7 @@ Main Script Meta:""")
426
484
  Action: lint
427
485
  ####################################################################################################################
428
486
 
429
- The `lint` action automatically formats the contents in `meta.yaml`.
487
+ The `lint` action automatically formats the contents in `meta.yaml`.
430
488
 
431
489
  Example Command:
432
490
 
@@ -435,12 +493,11 @@ Main Script Meta:""")
435
493
  """
436
494
  return self.call_script_module_function("lint", run_args)
437
495
 
438
-
439
496
  def help(self, run_args):
440
- # Internal function to call the help function in script automation module.py
497
+ # Internal function to call the help function in script automation
498
+ # module.py
441
499
  return self.call_script_module_function("help", run_args)
442
500
 
443
-
444
501
  def list(self, args):
445
502
  """
446
503
  ####################################################################################################################
@@ -448,7 +505,7 @@ Main Script Meta:""")
448
505
  Action: List
449
506
  ####################################################################################################################
450
507
 
451
- The `list` action displays all scripts and their paths from repositories registered in MLC.
508
+ The `list` action displays all scripts and their paths from repositories registered in MLC.
452
509
 
453
510
  Example Command:
454
511
 
@@ -456,16 +513,20 @@ Main Script Meta:""")
456
513
 
457
514
  """
458
515
  self.action_type = "script"
459
- run_args = {"fetch_all": True} # to fetch the details of all the scripts present in repos registered in mlc
460
-
516
+ # to fetch the details of all the scripts present in repos registered
517
+ # in mlc
518
+ run_args = {"fetch_all": True}
519
+
461
520
  res = self.search(run_args)
462
521
  if res['return'] > 0:
463
522
  return res
464
-
465
- logger.info(f"Listing all the scripts and their paths present in repos which are registered in MLC")
523
+
524
+ logger.info(
525
+ f"Listing all the scripts and their paths present in repos which are registered in MLC")
466
526
  print("......................................................")
467
527
  for item in res['list']:
468
- print(f"alias: {item.meta['alias'] if item.meta.get('alias') else 'None'}")
528
+ print(
529
+ f"alias: {item.meta['alias'] if item.meta.get('alias') else 'None'}")
469
530
  print(f"Location: {item.path}")
470
531
  print("......................................................")
471
532
 
@@ -488,6 +549,63 @@ Main Script Meta:""")
488
549
  """
489
550
  return self.call_script_module_function("experiment", run_args)
490
551
 
552
+ def remote_experiment(self, run_args):
553
+ """
554
+ ################################################################################################################################################
555
+ Target: Script
556
+ Action: remote-experiment
557
+ ################################################################################################################################################
558
+
559
+ The `remote-experiment` action runs an experiment on a remote machine via ssh connection.
560
+
561
+ Flags Available:
562
+
563
+ 1. --remote_host:
564
+ IP or hostname for the remote machine
565
+ 2. --remote_port:
566
+ ssh port for the remote machine
567
+
568
+ Example Command:
569
+
570
+ mlc remote-experiment script --tags=detect,os -j
571
+ mlcre detect,os -j
572
+
573
+ """
574
+ run_args["remote_action"] = "experiment"
575
+ return self.call_script_module_function("remote_run", run_args)
576
+
577
+ def remote_docker(self, run_args):
578
+ """
579
+ ################################################################################################################################################
580
+ Target: Script
581
+ Action: remote-docker
582
+ ################################################################################################################################################
583
+
584
+ The `remote-docker` action runs a script inside a Docker container on a remote machine via ssh connection.
585
+
586
+ Flags Available:
587
+
588
+ 1. --remote_host:
589
+ IP or hostname for the remote machine
590
+ 2. --remote_port:
591
+ ssh port for the remote machine
592
+
593
+ Example Command:
594
+
595
+ mlc remote-docker script --tags=detect,os -j
596
+ mlcrd detect,os -j
597
+
598
+ """
599
+ run_args["remote_action"] = "docker"
600
+ return self.call_script_module_function("remote_run", run_args)
601
+
602
+
491
603
  class ScriptExecutionError(Exception):
492
- # """Custom error for configuration issues."""
493
- pass
604
+ def __init__(self, message, script_name=None, repo_alias=None,
605
+ module_path=None, run_args=None, version_info_file=None):
606
+ super().__init__(message)
607
+ self.script_name = script_name
608
+ self.repo_alias = repo_alias
609
+ self.module_path = module_path
610
+ self.run_args = run_args or {}
611
+ self.version_info_file = version_info_file
@@ -11,6 +11,8 @@ import uuid
11
11
  import shutil
12
12
  import tarfile
13
13
  import zipfile
14
+ import logging
15
+ logger = logging.getLogger("mlc")
14
16
 
15
17
 
16
18
  def generate_temp_file(i):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mlcflow
3
- Version: 1.2.0
3
+ Version: 1.2.2
4
4
  Summary: An automation interface tailored for CPU/GPU benchmarking
5
5
  Author-email: MLCommons <systems@mlcommons.org>
6
6
  License:
@@ -1,8 +1,12 @@
1
1
  [console_scripts]
2
2
  mlc = mlc.main:main
3
+ mlca = mlc.main:mlca
3
4
  mlcd = mlc.main:mlcd
4
5
  mlce = mlc.main:mlce
6
+ mlcflow = mlc.main:main
5
7
  mlcp = mlc.main:mlcp
6
8
  mlcr = mlc.main:mlcr
9
+ mlcrd = mlc.main:mlcrd
10
+ mlcre = mlc.main:mlcre
7
11
  mlcrr = mlc.main:mlcrr
8
12
  mlct = mlc.main:mlct
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mlcflow"
7
- version = "1.2.0"
7
+ version = "1.2.2"
8
8
 
9
9
  description = "An automation interface tailored for CPU/GPU benchmarking"
10
10
  authors = [
@@ -45,9 +45,13 @@ py-modules = []
45
45
 
46
46
  [project.scripts]
47
47
  mlc = "mlc.main:main"
48
+ mlcflow = "mlc.main:main"
48
49
  mlcr = "mlc.main:mlcr"
49
50
  mlcrr = "mlc.main:mlcrr"
50
51
  mlcd = "mlc.main:mlcd"
52
+ mlca = "mlc.main:mlca"
51
53
  mlce = "mlc.main:mlce"
52
54
  mlct = "mlc.main:mlct"
53
55
  mlcp = "mlc.main:mlcp"
56
+ mlcre = "mlc.main:mlcre"
57
+ mlcrd = "mlc.main:mlcrd"
@@ -1,5 +0,0 @@
1
- __version__ = "0.1.0"
2
-
3
- from .action import access
4
-
5
- __all__ = ['access']
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes