mlcflow 1.2.3__tar.gz → 1.2.4__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.3 → mlcflow-1.2.4}/PKG-INFO +6 -7
  2. {mlcflow-1.2.3 → mlcflow-1.2.4}/README.md +5 -6
  3. {mlcflow-1.2.3 → mlcflow-1.2.4}/mlc/action.py +26 -9
  4. {mlcflow-1.2.3 → mlcflow-1.2.4}/mlc/cache_action.py +92 -25
  5. {mlcflow-1.2.3 → mlcflow-1.2.4}/mlc/main.py +4 -2
  6. {mlcflow-1.2.3 → mlcflow-1.2.4}/mlc/repo_action.py +111 -7
  7. {mlcflow-1.2.3 → mlcflow-1.2.4}/mlcflow.egg-info/PKG-INFO +6 -7
  8. {mlcflow-1.2.3 → mlcflow-1.2.4}/mlcflow.egg-info/SOURCES.txt +2 -1
  9. {mlcflow-1.2.3 → mlcflow-1.2.4}/pyproject.toml +1 -1
  10. mlcflow-1.2.4/tests/test_cache_mark_tmp.py +73 -0
  11. {mlcflow-1.2.3 → mlcflow-1.2.4}/LICENSE.md +0 -0
  12. {mlcflow-1.2.3 → mlcflow-1.2.4}/mlc/__init__.py +0 -0
  13. {mlcflow-1.2.3 → mlcflow-1.2.4}/mlc/__main__.py +0 -0
  14. {mlcflow-1.2.3 → mlcflow-1.2.4}/mlc/action_factory.py +0 -0
  15. {mlcflow-1.2.3 → mlcflow-1.2.4}/mlc/cfg_action.py +0 -0
  16. {mlcflow-1.2.3 → mlcflow-1.2.4}/mlc/error_codes.py +0 -0
  17. {mlcflow-1.2.3 → mlcflow-1.2.4}/mlc/experiment_action.py +0 -0
  18. {mlcflow-1.2.3 → mlcflow-1.2.4}/mlc/index.py +0 -0
  19. {mlcflow-1.2.3 → mlcflow-1.2.4}/mlc/item.py +0 -0
  20. {mlcflow-1.2.3 → mlcflow-1.2.4}/mlc/logger.py +0 -0
  21. {mlcflow-1.2.3 → mlcflow-1.2.4}/mlc/meta_schema.py +0 -0
  22. {mlcflow-1.2.3 → mlcflow-1.2.4}/mlc/repo.py +0 -0
  23. {mlcflow-1.2.3 → mlcflow-1.2.4}/mlc/script_action.py +0 -0
  24. {mlcflow-1.2.3 → mlcflow-1.2.4}/mlc/utils.py +0 -0
  25. {mlcflow-1.2.3 → mlcflow-1.2.4}/mlcflow.egg-info/dependency_links.txt +0 -0
  26. {mlcflow-1.2.3 → mlcflow-1.2.4}/mlcflow.egg-info/entry_points.txt +0 -0
  27. {mlcflow-1.2.3 → mlcflow-1.2.4}/mlcflow.egg-info/requires.txt +0 -0
  28. {mlcflow-1.2.3 → mlcflow-1.2.4}/mlcflow.egg-info/top_level.txt +0 -0
  29. {mlcflow-1.2.3 → mlcflow-1.2.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mlcflow
3
- Version: 1.2.3
3
+ Version: 1.2.4
4
4
  Summary: An automation interface tailored for CPU/GPU benchmarking
5
5
  Author-email: MLCommons <systems@mlcommons.org>
6
6
  License:
@@ -204,10 +204,10 @@ Dynamic: license-file
204
204
  [![License](https://img.shields.io/badge/License-Apache%202.0-green)](https://github.com/mlcommons/mlcflow/blob/main/LICENSE.md)
205
205
  [![Downloads](https://static.pepy.tech/badge/mlcflow)](https://pepy.tech/project/mlcflow)
206
206
 
207
- [![MLC core actions test](https://github.com/mlcommons/mlcflow/actions/workflows/test-mlc-core-actions.yaml/badge.svg)](https://github.com/mlcommons/mlcflow/actions/workflows/test-mlc-core-actions.yaml)
208
- [![MLC script automation features test](https://github.com/mlcommons/mlperf-automations/actions/workflows/test-mlc-script-features.yml/badge.svg?cache-bust=1)](https://github.com/mlcommons/mlperf-automations/actions/workflows/test-mlc-script-features.yml)
209
- [![MLPerf inference resnet50](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-resnet50.yml/badge.svg)](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-resnet50.yml)
210
- [![MLPerf inference bert (deepsparse, tf, onnxruntime, pytorch)](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-bert.yml/badge.svg)](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-bert.yml)
207
+ [![MLC core actions test](https://github.com/mlcommons/mlcflow/actions/workflows/test-mlc-core-actions.yaml/badge.svg?branch=dev)](https://github.com/mlcommons/mlcflow/actions/workflows/test-mlc-core-actions.yaml)
208
+ [![MLC script automation features test](https://github.com/mlcommons/mlperf-automations/actions/workflows/test-mlc-script-features.yml/badge.svg?branch=dev)](https://github.com/mlcommons/mlperf-automations/actions/workflows/test-mlc-script-features.yml)
209
+ [![MLPerf inference resnet50](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-resnet50.yml/badge.svg?branch=dev)](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-resnet50.yml)
210
+ [![MLPerf inference bert (deepsparse, tf, onnxruntime, pytorch)](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-bert.yml/badge.svg?branch=dev)](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-bert.yml)
211
211
 
212
212
 
213
213
  MLCFlow is a versatile CLI and Python interface developed by MLCommons in collaboration with a dedicated team of volunteers (see [Contributors](https://github.com/mlcommons/mlcflow/blob/main/CONTRIBUTORS.md)). It serves as a streamlined replacement for the [CMind](https://github.com/mlcommons/ck/tree/master/cm) tool, designed to drive the automation workflows of MLPerf benchmarks more efficiently. You can use this tool for any of your workflow automation tasks.
@@ -259,7 +259,7 @@ Each target has its own set of specific actions to tailor automation workflows a
259
259
  | Target | Action |
260
260
  |--------|-----------------|
261
261
  | script | run, find/search, rm, mv, cp, add, test, docker-run, show, experiment, doc |
262
- | cache | find/search, rm, show |
262
+ | cache | find/search, rm, show, list, prune, mark-tmp |
263
263
  | repo | pull, search, rm, list, find/search , add |
264
264
 
265
265
 
@@ -356,4 +356,3 @@ classDiagram
356
356
  Item o-- Repo
357
357
  Automation o-- Action
358
358
  ```
359
-
@@ -3,10 +3,10 @@
3
3
  [![License](https://img.shields.io/badge/License-Apache%202.0-green)](https://github.com/mlcommons/mlcflow/blob/main/LICENSE.md)
4
4
  [![Downloads](https://static.pepy.tech/badge/mlcflow)](https://pepy.tech/project/mlcflow)
5
5
 
6
- [![MLC core actions test](https://github.com/mlcommons/mlcflow/actions/workflows/test-mlc-core-actions.yaml/badge.svg)](https://github.com/mlcommons/mlcflow/actions/workflows/test-mlc-core-actions.yaml)
7
- [![MLC script automation features test](https://github.com/mlcommons/mlperf-automations/actions/workflows/test-mlc-script-features.yml/badge.svg?cache-bust=1)](https://github.com/mlcommons/mlperf-automations/actions/workflows/test-mlc-script-features.yml)
8
- [![MLPerf inference resnet50](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-resnet50.yml/badge.svg)](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-resnet50.yml)
9
- [![MLPerf inference bert (deepsparse, tf, onnxruntime, pytorch)](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-bert.yml/badge.svg)](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-bert.yml)
6
+ [![MLC core actions test](https://github.com/mlcommons/mlcflow/actions/workflows/test-mlc-core-actions.yaml/badge.svg?branch=dev)](https://github.com/mlcommons/mlcflow/actions/workflows/test-mlc-core-actions.yaml)
7
+ [![MLC script automation features test](https://github.com/mlcommons/mlperf-automations/actions/workflows/test-mlc-script-features.yml/badge.svg?branch=dev)](https://github.com/mlcommons/mlperf-automations/actions/workflows/test-mlc-script-features.yml)
8
+ [![MLPerf inference resnet50](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-resnet50.yml/badge.svg?branch=dev)](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-resnet50.yml)
9
+ [![MLPerf inference bert (deepsparse, tf, onnxruntime, pytorch)](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-bert.yml/badge.svg?branch=dev)](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-bert.yml)
10
10
 
11
11
 
12
12
  MLCFlow is a versatile CLI and Python interface developed by MLCommons in collaboration with a dedicated team of volunteers (see [Contributors](https://github.com/mlcommons/mlcflow/blob/main/CONTRIBUTORS.md)). It serves as a streamlined replacement for the [CMind](https://github.com/mlcommons/ck/tree/master/cm) tool, designed to drive the automation workflows of MLPerf benchmarks more efficiently. You can use this tool for any of your workflow automation tasks.
@@ -58,7 +58,7 @@ Each target has its own set of specific actions to tailor automation workflows a
58
58
  | Target | Action |
59
59
  |--------|-----------------|
60
60
  | script | run, find/search, rm, mv, cp, add, test, docker-run, show, experiment, doc |
61
- | cache | find/search, rm, show |
61
+ | cache | find/search, rm, show, list, prune, mark-tmp |
62
62
  | repo | pull, search, rm, list, find/search , add |
63
63
 
64
64
 
@@ -155,4 +155,3 @@ classDiagram
155
155
  Item o-- Repo
156
156
  Automation o-- Action
157
157
  ```
158
-
@@ -185,6 +185,15 @@ class Action:
185
185
  self._index = Index(self.repos_path, self.repos)
186
186
  return self._index
187
187
 
188
+ def _item_from_index_entry(self, res, target_name):
189
+ """Create an Item from an index entry and skip entries with invalid meta."""
190
+ it = Item(res['path'], res['repo'])
191
+ if not isinstance(it.meta, dict):
192
+ logger.warning(
193
+ f"Skipping {target_name} item at {it.path}: missing or invalid meta")
194
+ return None
195
+ return it
196
+
188
197
  def __init__(self):
189
198
  setup_logging(log_path=os.getcwd(), log_file='.mlc-log.txt')
190
199
  self.logger = logger
@@ -728,8 +737,13 @@ class Action:
728
737
  # For targets like cache, sometimes user would need to clear the entire cache folder present in the system
729
738
  # this helps to fetch entire data pertaining to particular target
730
739
  if fetch_all:
740
+ if not target_index:
741
+ return {'return': 0, 'list': result}
742
+
731
743
  for res in target_index:
732
- result.append(Item(res['path'], res['repo']))
744
+ it = self._item_from_index_entry(res, target)
745
+ if it:
746
+ result.append(it)
733
747
  return {'return': 0, 'list': result}
734
748
 
735
749
  if not uid and not alias and i.get('details'):
@@ -775,14 +789,16 @@ class Action:
775
789
  for res in target_index:
776
790
  if (res["uid"] == uid or (alias and res["alias"] == alias)) and (
777
791
  not item_repo or item_repo == res['repo']):
778
- it = Item(res['path'], res['repo'])
779
- result.append(it)
780
- found = True
792
+ it = self._item_from_index_entry(res, target)
793
+ if it:
794
+ result.append(it)
795
+ found = True
781
796
  if not found and folder_name:
782
797
  for res in target_index:
783
798
  if os.path.basename(res["path"]) == folder_name:
784
- it = Item(res['path'], res['repo'])
785
- result.append(it)
799
+ it = self._item_from_index_entry(res, target)
800
+ if it:
801
+ result.append(it)
786
802
  else:
787
803
  tags = i.get("tags")
788
804
  if tags:
@@ -803,11 +819,12 @@ class Action:
803
819
  n_tags = [p[1:] for p in n_tags_]
804
820
  p_tags = list(set(tags_to_match) - set(n_tags_))
805
821
  for res in target_index:
806
- c_tags = res["tags"]
822
+ c_tags = res.get("tags") or []
807
823
  if (exact_tags_match and set(p_tags) == set(c_tags)) or (not exact_tags_match and set(
808
824
  p_tags).issubset(set(c_tags)) and set(n_tags).isdisjoint(set(c_tags))):
809
- it = Item(res['path'], res['repo'])
810
- result.append(it)
825
+ it = self._item_from_index_entry(res, target)
826
+ if it:
827
+ result.append(it)
811
828
  return {'return': 0, 'list': result}
812
829
 
813
830
  find = search
@@ -5,6 +5,7 @@ import time
5
5
  from . import utils
6
6
  from .logger import logger
7
7
 
8
+
8
9
  class CacheAction(Action):
9
10
  """
10
11
  ####################################################################################################################
@@ -15,14 +16,16 @@ class CacheAction(Action):
15
16
  2. show
16
17
  3. list
17
18
  4. remove(rm)
19
+ 5. prune
20
+ 6. mark-tmp
18
21
 
19
22
  """
20
23
 
21
24
  def __init__(self, parent=None):
22
- #super().__init__(parent)
25
+ # super().__init__(parent)
23
26
  self.parent = parent
24
27
  self.__dict__.update(vars(parent))
25
-
28
+
26
29
  def search(self, i):
27
30
  """
28
31
  ####################################################################################################################
@@ -30,11 +33,11 @@ class CacheAction(Action):
30
33
  Action: Find (Alias: Search)
31
34
  ####################################################################################################################
32
35
 
33
- The `find` (or `search`) action retrieves the path of cache generated by a particular script.
36
+ The `find` (or `search`) action retrieves the path of cache generated by a particular script.
34
37
 
35
38
  Syntax:
36
39
 
37
- mlc find cache --tags=<list_of_tag_used_to_run_the_particular_script>
40
+ mlc find cache --tags=<list_of_tag_used_to_run_the_particular_script>
38
41
 
39
42
  Example Command:
40
43
 
@@ -42,7 +45,7 @@ class CacheAction(Action):
42
45
 
43
46
  """
44
47
  i['target_name'] = "cache"
45
- #logger.debug(f"Searching for cache with input: {i}")
48
+ # logger.debug(f"Searching for cache with input: {i}")
46
49
  r = self.parent.search(i)
47
50
  if r['return'] > 0:
48
51
  return r
@@ -83,13 +86,13 @@ class CacheAction(Action):
83
86
 
84
87
  Syntax:
85
88
 
86
- mlc rm cache --tags=<list_of_tag_used_to_run_the_particular_script>
89
+ mlc rm cache --tags=<list_of_tag_used_to_run_the_particular_script>
87
90
 
88
91
  Options:
89
92
  1. `-f`: Force removes caches without confirmation. Without `-f`, the user will be prompted for confirmation before deletion.
90
93
 
91
94
  To remove all generated caches, use:
92
-
95
+
93
96
  mlc rm cache
94
97
 
95
98
  Example Command:
@@ -98,9 +101,68 @@ class CacheAction(Action):
98
101
 
99
102
  """
100
103
  i['target_name'] = "cache"
101
- #logger.debug(f"Removing cache with input: {i}")
104
+ # logger.debug(f"Removing cache with input: {i}")
102
105
  return self.parent.rm(i)
103
106
 
107
+ def mark_tmp(self, i):
108
+ """
109
+ ####################################################################################################################
110
+ Target: Cache
111
+ Action: Mark Tmp (mark-tmp)
112
+ ####################################################################################################################
113
+
114
+ The `mark-tmp` action marks one or more cache entries as `tmp` without removing the cache contents.
115
+
116
+ Syntax:
117
+
118
+ mlc mark-tmp cache --tags=<list_of_tags_used_to_run_the_particular_script>
119
+
120
+ Example Command:
121
+
122
+ mlc mark-tmp cache --tags=get,dataset,igbh
123
+
124
+ """
125
+ i['target_name'] = "cache"
126
+ res = self.search(i)
127
+ if res['return'] > 0:
128
+ return res
129
+
130
+ if len(res['list']) == 0:
131
+ logger.warning(f"No cache found for {i}")
132
+ return {'return': 0, "warnings": [
133
+ {"code": "EMPTY_TARGET", "description": f"No cache found for {i}"}]}
134
+
135
+ updated_count = 0
136
+
137
+ for item in res['list']:
138
+ tags = item.meta.get("tags", [])
139
+ if 'tmp' in tags:
140
+ continue
141
+
142
+ tags.append('tmp')
143
+ item.meta["tags"] = tags
144
+ meta_yaml_path = os.path.join(item.path, "meta.yaml")
145
+ meta_json_path = os.path.join(item.path, "meta.json")
146
+
147
+ if os.path.exists(meta_yaml_path):
148
+ save_result = utils.save_yaml(meta_yaml_path, meta=item.meta)
149
+ else:
150
+ save_result = utils.save_json(meta_json_path, meta=item.meta)
151
+
152
+ if save_result['return'] > 0:
153
+ return save_result
154
+
155
+ self.get_index().update(item.meta, "cache", item.path, item.repo)
156
+ updated_count += 1
157
+
158
+ logger.info(f"Marked {updated_count} cache item(s) as tmp")
159
+
160
+ return {
161
+ 'return': 0,
162
+ 'message': f"Marked {updated_count} cache item(s) as tmp",
163
+ 'list': res['list']
164
+ }
165
+
104
166
  def show(self, run_args):
105
167
  """
106
168
  ####################################################################################################################
@@ -109,9 +171,9 @@ class CacheAction(Action):
109
171
  ####################################################################################################################
110
172
 
111
173
  Retrieves the path and metadata of caches generated while running scripts through MLCFlow.
112
-
174
+
113
175
  Syntax:
114
-
176
+
115
177
  mlc show cache --tags=<list_of_tags_used_while_running_script>
116
178
 
117
179
  Example Command:
@@ -128,7 +190,11 @@ class CacheAction(Action):
128
190
  return res
129
191
 
130
192
  logger.info(f"Showing cache with tags: {run_args.get('tags')}")
131
- cached_meta_keys_to_show = ["uid", "tags", "dependent_cached_path", "associated_script_item"]
193
+ cached_meta_keys_to_show = [
194
+ "uid",
195
+ "tags",
196
+ "dependent_cached_path",
197
+ "associated_script_item"]
132
198
  cached_state_keys_to_show = ["new_env", "new_state", "version"]
133
199
  for item in res['list']:
134
200
  print(f"""Location: {item.path}:
@@ -137,7 +203,8 @@ Cache Meta:""")
137
203
  if key in item.meta:
138
204
  print(f""" {key}: {item.meta[key]}""")
139
205
  print("""Cached State:""")
140
- cached_state_meta_file = os.path.join(item.path, "mlc-cached-state.json")
206
+ cached_state_meta_file = os.path.join(
207
+ item.path, "mlc-cached-state.json")
141
208
  if not os.path.exists(cached_state_meta_file):
142
209
  continue
143
210
  try:
@@ -149,17 +216,16 @@ Cache Meta:""")
149
216
  print(f""" {key}:""", end="")
150
217
  if meta[key] and isinstance(meta[key], dict):
151
218
  print("")
152
- utils.printd(meta[key], yaml=False, sort_keys=True, begin_spaces=8)
219
+ utils.printd(
220
+ meta[key], yaml=False, sort_keys=True, begin_spaces=8)
153
221
  else:
154
222
  print(f""" {meta[key]}""")
155
223
  except json.JSONDecodeError as e:
156
224
  logger.error(f"Error decoding JSON: {e}")
157
225
  print("......................................................")
158
226
  print("")
159
-
160
- return {'return': 0}
161
227
 
162
-
228
+ return {'return': 0}
163
229
 
164
230
  def prune(self, args):
165
231
  """
@@ -176,12 +242,13 @@ Cache Meta:""")
176
242
 
177
243
  """
178
244
  self.action_type = "cache"
179
- run_args = {"fetch_all": True} # to fetch the details of all the caches generated
180
-
245
+ # to fetch the details of all the caches generated
246
+ run_args = {"fetch_all": True}
247
+
181
248
  res = self.search(run_args)
182
249
  if res['return'] > 0:
183
250
  return res
184
-
251
+
185
252
  for item in res['list']:
186
253
  expiration_time = item.meta.get('cache_expiration')
187
254
  if expiration_time is not None and expiration_time < time.time():
@@ -192,8 +259,6 @@ Cache Meta:""")
192
259
  self.rm(ii)
193
260
 
194
261
  return {'return': 0}
195
-
196
-
197
262
 
198
263
  def list(self, args):
199
264
  """
@@ -210,16 +275,18 @@ Cache Meta:""")
210
275
 
211
276
  """
212
277
  self.action_type = "cache"
213
- run_args = {"fetch_all": True} # to fetch the details of all the caches generated
214
-
278
+ # to fetch the details of all the caches generated
279
+ run_args = {"fetch_all": True}
280
+
215
281
  res = self.search(run_args)
216
282
  if res['return'] > 0:
217
283
  return res
218
-
284
+
219
285
  logger.info(f"Listing all the caches and their paths")
220
286
  print("......................................................")
221
287
  for item in res['list']:
222
- print(f"tags: {item.meta['tags'] if item.meta.get('tags') else 'None'}")
288
+ print(
289
+ f"tags: {item.meta['tags'] if item.meta.get('tags') else 'None'}")
223
290
  print(f"Location: {item.path}")
224
291
  print("......................................................")
225
292
 
@@ -348,7 +348,7 @@ def build_parser(pre_args):
348
348
 
349
349
  # General commands
350
350
  for action in ['run', 'pull', 'test', 'add', 'show', 'list',
351
- 'find', 'search', 'rm', 'cp', 'mv', 'help', 'prune']:
351
+ 'find', 'search', 'rm', 'cp', 'mv', 'help', 'prune', 'mark-tmp']:
352
352
  p = subparsers.add_parser(action, add_help=False)
353
353
  p.add_argument('target', choices=['repo', 'repos', 'script', 'cache'])
354
354
  p.add_argument(
@@ -495,7 +495,7 @@ def main():
495
495
  | Target | Actions |
496
496
  |---------|-----------------------------------------------------------|
497
497
  | script | run, find/search, rm, mv, cp, add, test, docker-run, show |
498
- | cache | find/search, rm, show |
498
+ | cache | find/search, rm, show, list, prune, mark-tmp |
499
499
  | repo | pull, search, rm, list, find/search |
500
500
 
501
501
  Example:
@@ -528,6 +528,8 @@ def main():
528
528
 
529
529
  pre_parser = build_pre_parser()
530
530
  pre_args, remaining_args = pre_parser.parse_known_args()
531
+ if pre_args.action:
532
+ pre_args.action = pre_args.action.replace("-", "_")
531
533
 
532
534
  parser = build_parser(pre_args)
533
535
  # Force full parsing for reindex command even without target, or if there
@@ -323,7 +323,7 @@ class RepoAction(Action):
323
323
  url).replace(".git", "")}
324
324
 
325
325
  def pull_repo(self, repo_url, branch=None, checkout=None, tag=None,
326
- pat=None, ssh=None, ignore_on_conflict=False, repo_path=None):
326
+ pat=None, ssh=None, ignore_on_conflict=False, repo_path=None, force=False):
327
327
 
328
328
  # Determine the checkout path from environment or default
329
329
  repo_base_path = self.repos_path # either the value will be from 'MLC_REPOS'
@@ -391,11 +391,104 @@ class RepoAction(Action):
391
391
  status_command, capture_output=True, text=True)
392
392
 
393
393
  if local_changes.stdout.strip():
394
+ if not force:
395
+ logger.warning(
396
+ "There are local changes in the repository. Please commit or stash them before checking out.")
397
+ print(local_changes.stdout.strip())
398
+ return {
399
+ "return": 0, "warning": f"Local changes detected in the already existing repository: {repo_path}, skipping the pull"}
400
+
394
401
  logger.warning(
395
- "There are local changes in the repository. Please commit or stash them before checking out.")
396
- print(local_changes.stdout.strip())
397
- return {
398
- "return": 0, "warning": f"Local changes detected in the already existing repository: {repo_path}, skipping the pull"}
402
+ "Local changes detected. Running force pull with temporary git stash.")
403
+ stash_created = False
404
+ try:
405
+ stash_before = subprocess.run(
406
+ ['git', '-C', repo_path, 'stash', 'list'],
407
+ capture_output=True,
408
+ text=True,
409
+ check=True
410
+ )
411
+ stash_res = subprocess.run(
412
+ ['git', '-C', repo_path, 'stash', 'push',
413
+ '-m', 'mlc pull repo --force'],
414
+ capture_output=True,
415
+ text=True,
416
+ check=True
417
+ )
418
+ stash_after = subprocess.run(
419
+ ['git', '-C', repo_path, 'stash', 'list'],
420
+ capture_output=True,
421
+ text=True,
422
+ check=True
423
+ )
424
+ stash_created = len(stash_after.stdout.splitlines()
425
+ ) > len(stash_before.stdout.splitlines())
426
+ except subprocess.CalledProcessError as e:
427
+ stash_error = (e.stderr or e.stdout or str(e)).strip()
428
+ return {
429
+ "return": 1,
430
+ "error": f"Force pull failed while stashing local changes in {repo_path}: {stash_error}"
431
+ }
432
+
433
+ logger.info(
434
+ "Pulling latest changes...")
435
+ try:
436
+ subprocess.run(
437
+ ['git', '-C', repo_path, 'pull'],
438
+ capture_output=True,
439
+ text=True,
440
+ check=True)
441
+ except subprocess.CalledProcessError as e:
442
+ pull_error = (e.stderr or e.stdout or str(e)).strip()
443
+ if stash_created:
444
+ return {
445
+ "return": 1,
446
+ "error": f"Force pull failed during git pull for {repo_path}. Local changes remain in stash. Please run `git -C {repo_path} stash apply` after resolving pull issues. Details: {pull_error}"
447
+ }
448
+ return {
449
+ "return": 1,
450
+ "error": f"Force pull failed during git pull for {repo_path}: {pull_error}"
451
+ }
452
+ logger.info("Repository successfully pulled.")
453
+
454
+ if stash_created:
455
+ try:
456
+ subprocess.run(
457
+ ['git', '-C', repo_path, 'stash', 'apply'],
458
+ capture_output=True,
459
+ text=True,
460
+ check=True)
461
+ subprocess.run(
462
+ ['git', '-C', repo_path, 'stash', 'drop'],
463
+ capture_output=True,
464
+ text=True,
465
+ check=True)
466
+ logger.info(
467
+ "Local changes restored successfully after force pull.")
468
+ except subprocess.CalledProcessError as apply_error:
469
+ apply_error_msg = (
470
+ apply_error.stderr or apply_error.stdout or str(apply_error)).strip()
471
+ try:
472
+ subprocess.run(
473
+ ['git', '-C', repo_path,
474
+ 'reset', '--hard', 'HEAD'],
475
+ capture_output=True,
476
+ text=True,
477
+ check=True)
478
+ except subprocess.CalledProcessError as reset_exception:
479
+ reset_error_msg = (
480
+ reset_exception.stderr or reset_exception.stdout or str(reset_exception)).strip()
481
+ return {
482
+ "return": 1,
483
+ "error": f"Stash apply conflicted and automatic rollback failed for {repo_path}: {reset_error_msg}. Original stash apply error: {apply_error_msg}"
484
+ }
485
+ logger.warning(
486
+ f"Stash apply reported conflicts after pull. Reverted partial stash apply. "
487
+ f"Please resolve manually with `git -C {repo_path} stash apply`.")
488
+ return {
489
+ "return": 0,
490
+ "warning": f"Force pull succeeded for {repo_path}, but stash apply had conflicts. Partial apply was reverted. Please apply the stash manually."
491
+ }
399
492
  else:
400
493
  logger.info(
401
494
  "No local changes detected. Pulling latest changes...")
@@ -468,6 +561,7 @@ class RepoAction(Action):
468
561
  - `--branch <branch_name>`: Checks out a specific branch **while cloning** a new repository.
469
562
  - `--tag <release_tag>`: Checks out a particular release tag.
470
563
  - `--pat <access_token>` or `--ssh`: Clones a private repository using a personal access token or SSH.
564
+ - `--force`: For existing repositories with local tracked changes, stashes changes before pull and reapplies them after pull.
471
565
 
472
566
  Example Output:
473
567
 
@@ -498,7 +592,7 @@ class RepoAction(Action):
498
592
  repo_object.path, os.W_OK):
499
593
  repo_folder_name = os.path.basename(repo_object.path)
500
594
  res = self.pull_repo(
501
- repo_folder_name, repo_path=repo_object.path)
595
+ repo_folder_name, repo_path=repo_object.path, force=run_args.get('force'))
502
596
  if res['return'] > 0:
503
597
  return res
504
598
  else:
@@ -508,12 +602,22 @@ class RepoAction(Action):
508
602
 
509
603
  pat = run_args.get('pat')
510
604
  ssh = run_args.get('ssh')
605
+ force = run_args.get('force')
606
+ ignore_on_conflict = run_args.get('ignore_on_conflict')
511
607
 
512
608
  if sum(bool(var) for var in [branch, checkout, tag]) > 1:
513
609
  return {
514
610
  "return": 1, "error": "Only one among the three flags(branch, checkout and tag) could be specified"}
515
611
 
516
- res = self.pull_repo(repo_url, branch, checkout, tag, pat, ssh)
612
+ res = self.pull_repo(
613
+ repo_url,
614
+ branch,
615
+ checkout,
616
+ tag,
617
+ pat,
618
+ ssh,
619
+ ignore_on_conflict=ignore_on_conflict,
620
+ force=force)
517
621
  if res['return'] > 0:
518
622
  return res
519
623
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mlcflow
3
- Version: 1.2.3
3
+ Version: 1.2.4
4
4
  Summary: An automation interface tailored for CPU/GPU benchmarking
5
5
  Author-email: MLCommons <systems@mlcommons.org>
6
6
  License:
@@ -204,10 +204,10 @@ Dynamic: license-file
204
204
  [![License](https://img.shields.io/badge/License-Apache%202.0-green)](https://github.com/mlcommons/mlcflow/blob/main/LICENSE.md)
205
205
  [![Downloads](https://static.pepy.tech/badge/mlcflow)](https://pepy.tech/project/mlcflow)
206
206
 
207
- [![MLC core actions test](https://github.com/mlcommons/mlcflow/actions/workflows/test-mlc-core-actions.yaml/badge.svg)](https://github.com/mlcommons/mlcflow/actions/workflows/test-mlc-core-actions.yaml)
208
- [![MLC script automation features test](https://github.com/mlcommons/mlperf-automations/actions/workflows/test-mlc-script-features.yml/badge.svg?cache-bust=1)](https://github.com/mlcommons/mlperf-automations/actions/workflows/test-mlc-script-features.yml)
209
- [![MLPerf inference resnet50](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-resnet50.yml/badge.svg)](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-resnet50.yml)
210
- [![MLPerf inference bert (deepsparse, tf, onnxruntime, pytorch)](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-bert.yml/badge.svg)](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-bert.yml)
207
+ [![MLC core actions test](https://github.com/mlcommons/mlcflow/actions/workflows/test-mlc-core-actions.yaml/badge.svg?branch=dev)](https://github.com/mlcommons/mlcflow/actions/workflows/test-mlc-core-actions.yaml)
208
+ [![MLC script automation features test](https://github.com/mlcommons/mlperf-automations/actions/workflows/test-mlc-script-features.yml/badge.svg?branch=dev)](https://github.com/mlcommons/mlperf-automations/actions/workflows/test-mlc-script-features.yml)
209
+ [![MLPerf inference resnet50](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-resnet50.yml/badge.svg?branch=dev)](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-resnet50.yml)
210
+ [![MLPerf inference bert (deepsparse, tf, onnxruntime, pytorch)](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-bert.yml/badge.svg?branch=dev)](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-bert.yml)
211
211
 
212
212
 
213
213
  MLCFlow is a versatile CLI and Python interface developed by MLCommons in collaboration with a dedicated team of volunteers (see [Contributors](https://github.com/mlcommons/mlcflow/blob/main/CONTRIBUTORS.md)). It serves as a streamlined replacement for the [CMind](https://github.com/mlcommons/ck/tree/master/cm) tool, designed to drive the automation workflows of MLPerf benchmarks more efficiently. You can use this tool for any of your workflow automation tasks.
@@ -259,7 +259,7 @@ Each target has its own set of specific actions to tailor automation workflows a
259
259
  | Target | Action |
260
260
  |--------|-----------------|
261
261
  | script | run, find/search, rm, mv, cp, add, test, docker-run, show, experiment, doc |
262
- | cache | find/search, rm, show |
262
+ | cache | find/search, rm, show, list, prune, mark-tmp |
263
263
  | repo | pull, search, rm, list, find/search , add |
264
264
 
265
265
 
@@ -356,4 +356,3 @@ classDiagram
356
356
  Item o-- Repo
357
357
  Automation o-- Action
358
358
  ```
359
-
@@ -23,4 +23,5 @@ mlcflow.egg-info/SOURCES.txt
23
23
  mlcflow.egg-info/dependency_links.txt
24
24
  mlcflow.egg-info/entry_points.txt
25
25
  mlcflow.egg-info/requires.txt
26
- mlcflow.egg-info/top_level.txt
26
+ mlcflow.egg-info/top_level.txt
27
+ tests/test_cache_mark_tmp.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mlcflow"
7
- version = "1.2.3"
7
+ version = "1.2.4"
8
8
 
9
9
  description = "An automation interface tailored for CPU/GPU benchmarking"
10
10
  authors = [
@@ -0,0 +1,73 @@
1
+ import json
2
+ import os
3
+ import subprocess
4
+ import sys
5
+ import tempfile
6
+ import unittest
7
+
8
+ from mlc.action import Action
9
+
10
+
11
+ REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
12
+
13
+
14
+ class MarkTmpCacheCliTest(unittest.TestCase):
15
+ def setUp(self):
16
+ self.temp_dir = tempfile.TemporaryDirectory()
17
+ self.addCleanup(self.temp_dir.cleanup)
18
+ self.previous_cwd = os.getcwd()
19
+ self.addCleanup(os.chdir, self.previous_cwd)
20
+ os.chdir(self.temp_dir.name)
21
+
22
+ self.previous_mlc_repos = os.environ.get("MLC_REPOS")
23
+ self.addCleanup(self._restore_env)
24
+ os.environ["MLC_REPOS"] = os.path.join(self.temp_dir.name, "repos")
25
+
26
+ action = Action()
27
+ action.parent = None
28
+ res = action.add({
29
+ "target_name": "cache",
30
+ "item": "sample-cache",
31
+ "tags": "get,dataset,igbh"
32
+ })
33
+ self.assertEqual(res["return"], 0)
34
+ self.cache_path = res["path"]
35
+
36
+ def _restore_env(self):
37
+ if self.previous_mlc_repos is None:
38
+ os.environ.pop("MLC_REPOS", None)
39
+ else:
40
+ os.environ["MLC_REPOS"] = self.previous_mlc_repos
41
+
42
+ def _run_cli(self):
43
+ env = os.environ.copy()
44
+ existing_pythonpath = env.get("PYTHONPATH")
45
+ env["PYTHONPATH"] = REPO_ROOT if not existing_pythonpath else REPO_ROOT + \
46
+ os.pathsep + existing_pythonpath
47
+ return subprocess.run(
48
+ [sys.executable, "-m", "mlc.main", "mark-tmp",
49
+ "cache", "--tags=get,dataset,igbh"],
50
+ cwd=self.temp_dir.name,
51
+ env=env,
52
+ capture_output=True,
53
+ text=True,
54
+ check=False
55
+ )
56
+
57
+ def test_mark_tmp_adds_tmp_tag_without_duplicates(self):
58
+ first_run = self._run_cli()
59
+ self.assertEqual(first_run.returncode, 0, msg=first_run.stderr)
60
+
61
+ second_run = self._run_cli()
62
+ self.assertEqual(second_run.returncode, 0, msg=second_run.stderr)
63
+
64
+ with open(os.path.join(self.cache_path, "meta.json"), "r") as file:
65
+ meta = json.load(file)
66
+
67
+ self.assertIn("tmp", meta["tags"])
68
+ self.assertEqual(meta["tags"].count("tmp"), 1)
69
+ self.assertTrue(os.path.isdir(self.cache_path))
70
+
71
+
72
+ if __name__ == "__main__":
73
+ unittest.main()
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
File without changes