mlcflow 1.3.2__tar.gz → 1.3.3__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.
- {mlcflow-1.3.2 → mlcflow-1.3.3}/PKG-INFO +5 -5
- {mlcflow-1.3.2 → mlcflow-1.3.3}/README.md +4 -4
- mlcflow-1.3.3/VERSION +1 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/automation/script/lint.py +0 -2
- {mlcflow-1.3.2 → mlcflow-1.3.3}/automation/script/meta_schema.py +0 -1
- {mlcflow-1.3.2 → mlcflow-1.3.3}/automation/script/module.py +34 -15
- {mlcflow-1.3.2 → mlcflow-1.3.3}/automation/script/remote_run.py +9 -2
- mlcflow-1.3.3/automation/script/slurm_run.py +284 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/mlc/main.py +49 -3
- {mlcflow-1.3.2 → mlcflow-1.3.3}/mlc/meta_schema.py +0 -1
- {mlcflow-1.3.2 → mlcflow-1.3.3}/mlc/script_action.py +173 -29
- {mlcflow-1.3.2 → mlcflow-1.3.3}/mlcflow.egg-info/PKG-INFO +5 -5
- {mlcflow-1.3.2 → mlcflow-1.3.3}/mlcflow.egg-info/SOURCES.txt +2 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/mlcflow.egg-info/entry_points.txt +7 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/pyproject.toml +7 -0
- mlcflow-1.3.3/tests/test_slurm_run.py +158 -0
- mlcflow-1.3.2/VERSION +0 -1
- {mlcflow-1.3.2 → mlcflow-1.3.3}/LICENSE.md +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/automation/COPYRIGHT.txt +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/automation/__init__.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/automation/script/README.md +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/automation/script/__init__.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/automation/script/apptainer.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/automation/script/cache_utils.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/automation/script/compat.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/automation/script/doc.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/automation/script/docker.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/automation/script/docker_utils.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/automation/script/experiment.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/automation/script/help.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/automation/script/meta.json +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/automation/script/script_utils.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/automation/script/validate.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/automation/utils.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/mlc/__init__.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/mlc/__main__.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/mlc/action.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/mlc/action_factory.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/mlc/cache_action.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/mlc/cfg_action.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/mlc/error_codes.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/mlc/experiment_action.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/mlc/index.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/mlc/item.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/mlc/logger.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/mlc/repo.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/mlc/repo_action.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/mlc/utils.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/mlcflow.egg-info/dependency_links.txt +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/mlcflow.egg-info/requires.txt +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/mlcflow.egg-info/top_level.txt +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/setup.cfg +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/tests/test_action_invalid_meta_entries.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/tests/test_cache_mark_tmp.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/tests/test_mlcflow_unix_installer.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/tests/test_repo_action_extra_git_args.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/tests/test_script_action_apptainer.py +0 -0
- {mlcflow-1.3.2 → mlcflow-1.3.3}/tests/test_unix_installer_venv.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mlcflow
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.3
|
|
4
4
|
Summary: An automation interface tailored for CPU/GPU benchmarking
|
|
5
5
|
Author-email: MLCommons <systems@mlcommons.org>
|
|
6
6
|
License:
|
|
@@ -206,10 +206,10 @@ Dynamic: license-file
|
|
|
206
206
|
[](https://github.com/mlcommons/mlcflow/blob/main/LICENSE.md)
|
|
207
207
|
[](https://pepy.tech/project/mlcflow)
|
|
208
208
|
|
|
209
|
-
[](https://github.com/mlcommons/mlcflow/actions/workflows/test-mlc-core-actions.yaml)
|
|
210
|
+
[](https://github.com/mlcommons/mlperf-automations/actions/workflows/test-mlc-script-features.yml)
|
|
211
|
+
[](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-resnet50.yml)
|
|
212
|
+
|
|
213
213
|
|
|
214
214
|
|
|
215
215
|
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.
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
[](https://github.com/mlcommons/mlcflow/blob/main/LICENSE.md)
|
|
5
5
|
[](https://pepy.tech/project/mlcflow)
|
|
6
6
|
|
|
7
|
-
[](https://github.com/mlcommons/mlcflow/actions/workflows/test-mlc-core-actions.yaml)
|
|
8
|
+
[](https://github.com/mlcommons/mlperf-automations/actions/workflows/test-mlc-script-features.yml)
|
|
9
|
+
[](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-resnet50.yml)
|
|
10
|
+
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
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.
|
mlcflow-1.3.3/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.3.3
|
|
@@ -98,7 +98,6 @@ TOP_LEVEL_KEY_ORDER = [
|
|
|
98
98
|
"tags",
|
|
99
99
|
"tags_help",
|
|
100
100
|
"private",
|
|
101
|
-
"min_mlc_version",
|
|
102
101
|
"mlc_compat",
|
|
103
102
|
|
|
104
103
|
# Cache
|
|
@@ -174,7 +173,6 @@ SECTION_GROUPS = [
|
|
|
174
173
|
"tags",
|
|
175
174
|
"tags_help",
|
|
176
175
|
"private",
|
|
177
|
-
"min_mlc_version",
|
|
178
176
|
"mlc_compat"]),
|
|
179
177
|
("# Cache",
|
|
180
178
|
["cache",
|
|
@@ -611,9 +611,8 @@ class ScriptAutomation(Automation):
|
|
|
611
611
|
meta = script_item.meta
|
|
612
612
|
path = script_item.path
|
|
613
613
|
|
|
614
|
-
# Check mlcflow version requirements
|
|
615
|
-
#
|
|
616
|
-
# min_mlc_version (legacy): single threshold, always blocks
|
|
614
|
+
# Check mlcflow version requirements (mlc_compat: multi-entry,
|
|
615
|
+
# per-entry messages, warn or block)
|
|
617
616
|
script_alias = meta.get('alias', '')
|
|
618
617
|
try:
|
|
619
618
|
from script.compat import get_installed_version, check_mlc_compat, format_compat_notice
|
|
@@ -635,18 +634,6 @@ class ScriptAutomation(Automation):
|
|
|
635
634
|
)}
|
|
636
635
|
else:
|
|
637
636
|
self.logger.warning(notice)
|
|
638
|
-
else:
|
|
639
|
-
# Fall back to legacy min_mlc_version (single threshold, always
|
|
640
|
-
# blocks)
|
|
641
|
-
min_mlc_version = meta.get('min_mlc_version', '').strip()
|
|
642
|
-
if min_mlc_version and current_mlc_version:
|
|
643
|
-
if utils.compare_versions(
|
|
644
|
-
current_mlc_version, min_mlc_version) < 0:
|
|
645
|
-
return {'return': 1, 'error': (
|
|
646
|
-
'This script requires mlcflow >= {} (installed: {}) '
|
|
647
|
-
'— please upgrade: pip install --upgrade mlcflow'.format(
|
|
648
|
-
min_mlc_version, current_mlc_version)
|
|
649
|
-
)}
|
|
650
637
|
except Exception as e:
|
|
651
638
|
# Never let a compat-check failure block script execution
|
|
652
639
|
self.logger.debug('mlc_compat check skipped: {}'.format(e))
|
|
@@ -4539,6 +4526,38 @@ pip install mlcflow
|
|
|
4539
4526
|
from script.remote_run import remote_run
|
|
4540
4527
|
return remote_run(self, i)
|
|
4541
4528
|
|
|
4529
|
+
############################################################
|
|
4530
|
+
def slurm_run(self, i):
|
|
4531
|
+
from script.slurm_run import slurm_run
|
|
4532
|
+
return slurm_run(self, i)
|
|
4533
|
+
|
|
4534
|
+
############################################################
|
|
4535
|
+
def slurm_docker(self, i):
|
|
4536
|
+
from script.slurm_run import slurm_docker
|
|
4537
|
+
return slurm_docker(self, i)
|
|
4538
|
+
|
|
4539
|
+
############################################################
|
|
4540
|
+
def slurm_apptainer(self, i):
|
|
4541
|
+
from script.slurm_run import slurm_apptainer
|
|
4542
|
+
return slurm_apptainer(self, i)
|
|
4543
|
+
|
|
4544
|
+
############################################################
|
|
4545
|
+
def slurm_experiment(self, i):
|
|
4546
|
+
from script.slurm_run import slurm_experiment
|
|
4547
|
+
return slurm_experiment(self, i)
|
|
4548
|
+
|
|
4549
|
+
############################################################
|
|
4550
|
+
def remote_slurm(self, i):
|
|
4551
|
+
from script.remote_run import remote_run
|
|
4552
|
+
i['remote_action'] = 'slurm-run'
|
|
4553
|
+
return remote_run(self, i)
|
|
4554
|
+
|
|
4555
|
+
############################################################
|
|
4556
|
+
def remote_slurm_experiment(self, i):
|
|
4557
|
+
from script.remote_run import remote_run
|
|
4558
|
+
i['remote_action'] = 'slurm-experiment'
|
|
4559
|
+
return remote_run(self, i)
|
|
4560
|
+
|
|
4542
4561
|
##########################################################################
|
|
4543
4562
|
|
|
4544
4563
|
def _available_variations(self, i):
|
|
@@ -320,8 +320,15 @@ def regenerate_script_cmd(i):
|
|
|
320
320
|
# docker_run_cmd_prefix = i.get('docker_run_cmd_prefix', '')
|
|
321
321
|
|
|
322
322
|
# Regenerate command from dictionary input
|
|
323
|
-
|
|
324
|
-
'
|
|
323
|
+
_remote_action_cmd = {
|
|
324
|
+
'docker': 'mlcd',
|
|
325
|
+
'experiment': 'mlce',
|
|
326
|
+
'slurm-run': 'mlcsr',
|
|
327
|
+
'slurm-experiment': 'mlcse',
|
|
328
|
+
'slurm-docker': 'mlcsd',
|
|
329
|
+
'slurm-apptainer': 'mlcsa',
|
|
330
|
+
}
|
|
331
|
+
run_cmd = _remote_action_cmd.get(remote_action, 'mlcr')
|
|
325
332
|
|
|
326
333
|
skip_input_for_fake_run = remote_run_settings.get(
|
|
327
334
|
'skip_input_for_fake_run', [])
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shlex
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
5
|
+
import copy
|
|
6
|
+
from utils import is_true, prune_input
|
|
7
|
+
from script.script_utils import *
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def slurm_run(self_module, i, slurm_action='run'):
|
|
11
|
+
"""
|
|
12
|
+
Run MLC scripts on a SLURM cluster node via srun.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
self_module: Reference to the current module for internal calls.
|
|
16
|
+
i: Dictionary containing input parameters for the slurm execution.
|
|
17
|
+
slurm_action: The MLC action to use on the node ('run', 'docker',
|
|
18
|
+
'apptainer', 'experiment'). Defaults to 'run'.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
Dictionary with the result of the operation. Keys:
|
|
22
|
+
- 'return': 0 on success, >0 on error.
|
|
23
|
+
- 'error': Error message (if any).
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
# Extract and handle basic inputs
|
|
27
|
+
quiet = i.get('quiet', False)
|
|
28
|
+
logger = self_module.logger
|
|
29
|
+
env = i.get('env', {})
|
|
30
|
+
|
|
31
|
+
# SLURM-specific options with reasonable defaults
|
|
32
|
+
slurm_partition = i.get('slurm_partition', '')
|
|
33
|
+
slurm_nodes = i.get('slurm_nodes', '1')
|
|
34
|
+
slurm_ntasks = i.get('slurm_ntasks', '1')
|
|
35
|
+
slurm_ntasks_per_node = i.get('slurm_ntasks_per_node', '')
|
|
36
|
+
slurm_cpus_per_task = i.get('slurm_cpus_per_task', '')
|
|
37
|
+
slurm_gpus = i.get('slurm_gpus', '')
|
|
38
|
+
slurm_gpus_per_node = i.get('slurm_gpus_per_node', '')
|
|
39
|
+
slurm_gpus_per_task = i.get('slurm_gpus_per_task', '')
|
|
40
|
+
slurm_mem = i.get('slurm_mem', '')
|
|
41
|
+
slurm_mem_per_cpu = i.get('slurm_mem_per_cpu', '')
|
|
42
|
+
slurm_mem_per_gpu = i.get('slurm_mem_per_gpu', '')
|
|
43
|
+
slurm_time = i.get('slurm_time', '')
|
|
44
|
+
slurm_job_name = i.get('slurm_job_name', '')
|
|
45
|
+
slurm_output = i.get('slurm_output', '')
|
|
46
|
+
slurm_error = i.get('slurm_error', '')
|
|
47
|
+
slurm_account = i.get('slurm_account', '')
|
|
48
|
+
slurm_qos = i.get('slurm_qos', '')
|
|
49
|
+
slurm_constraint = i.get('slurm_constraint', '')
|
|
50
|
+
slurm_exclusive = i.get('slurm_exclusive', False)
|
|
51
|
+
slurm_export = i.get('slurm_export', 'ALL')
|
|
52
|
+
slurm_srun_extra_args = i.get('slurm_srun_extra_args', '')
|
|
53
|
+
slurm_python_venv = i.get('slurm_python_venv', 'mlcflow')
|
|
54
|
+
slurm_pull_mlc_repos = i.get('slurm_pull_mlc_repos', False)
|
|
55
|
+
slurm_pre_run_cmds = i.get('slurm_pre_run_cmds', [])
|
|
56
|
+
slurm_post_run_cmds = i.get('slurm_post_run_cmds', [])
|
|
57
|
+
|
|
58
|
+
# Normalize str → list so a single command string doesn't get iterated
|
|
59
|
+
# char-by-char
|
|
60
|
+
if isinstance(slurm_pre_run_cmds, str):
|
|
61
|
+
slurm_pre_run_cmds = [slurm_pre_run_cmds] if slurm_pre_run_cmds else []
|
|
62
|
+
if isinstance(slurm_post_run_cmds, str):
|
|
63
|
+
slurm_post_run_cmds = [
|
|
64
|
+
slurm_post_run_cmds] if slurm_post_run_cmds else []
|
|
65
|
+
|
|
66
|
+
# Check that srun is available before proceeding
|
|
67
|
+
if not shutil.which('srun'):
|
|
68
|
+
return {
|
|
69
|
+
'return': 1,
|
|
70
|
+
'error': "srun not found in PATH -- are you on a SLURM login node?"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Validate and normalize integer SLURM parameters
|
|
74
|
+
int_params = {
|
|
75
|
+
'slurm_nodes': slurm_nodes,
|
|
76
|
+
'slurm_ntasks': slurm_ntasks,
|
|
77
|
+
'slurm_ntasks_per_node': slurm_ntasks_per_node,
|
|
78
|
+
'slurm_cpus_per_task': slurm_cpus_per_task,
|
|
79
|
+
}
|
|
80
|
+
for param_name, param_val in int_params.items():
|
|
81
|
+
if param_val != '':
|
|
82
|
+
try:
|
|
83
|
+
int(param_val)
|
|
84
|
+
except (ValueError, TypeError):
|
|
85
|
+
return {
|
|
86
|
+
'return': 1,
|
|
87
|
+
'error': f'Invalid value for {param_name}: {param_val!r} (must be an integer)'
|
|
88
|
+
}
|
|
89
|
+
slurm_nodes = str(int(slurm_nodes)) if slurm_nodes else ''
|
|
90
|
+
slurm_ntasks = str(int(slurm_ntasks)) if slurm_ntasks else ''
|
|
91
|
+
slurm_ntasks_per_node = str(
|
|
92
|
+
int(slurm_ntasks_per_node)) if slurm_ntasks_per_node else ''
|
|
93
|
+
slurm_cpus_per_task = str(
|
|
94
|
+
int(slurm_cpus_per_task)) if slurm_cpus_per_task else ''
|
|
95
|
+
|
|
96
|
+
prune_result = prune_input(
|
|
97
|
+
{'input': i, 'extra_keys_starts_with': ['slurm_']})
|
|
98
|
+
if prune_result['return'] > 0:
|
|
99
|
+
return prune_result
|
|
100
|
+
|
|
101
|
+
run_input = prune_result['new_input']
|
|
102
|
+
|
|
103
|
+
cur_dir = os.getcwd()
|
|
104
|
+
|
|
105
|
+
r = self_module._select_script(i)
|
|
106
|
+
if r['return'] > 0:
|
|
107
|
+
return r
|
|
108
|
+
|
|
109
|
+
script = r['script']
|
|
110
|
+
|
|
111
|
+
meta, script_path = script.meta, script.path
|
|
112
|
+
tags, script_alias, script_uid = meta.get(
|
|
113
|
+
"tags", []), meta.get(
|
|
114
|
+
'alias', ''), meta.get(
|
|
115
|
+
'uid', '')
|
|
116
|
+
|
|
117
|
+
r = self_module.update_run_state_for_selected_script_and_variations(
|
|
118
|
+
script, i)
|
|
119
|
+
if r['return'] > 0:
|
|
120
|
+
return r
|
|
121
|
+
|
|
122
|
+
run_state = self_module.run_state
|
|
123
|
+
|
|
124
|
+
env = self_module.env
|
|
125
|
+
state = self_module.state
|
|
126
|
+
|
|
127
|
+
i_copy = copy.deepcopy(i)
|
|
128
|
+
i_copy['run_cmd'] = run_input
|
|
129
|
+
i_copy['slurm_action'] = slurm_action
|
|
130
|
+
|
|
131
|
+
r = regenerate_script_cmd(i_copy)
|
|
132
|
+
if r['return'] > 0:
|
|
133
|
+
return r
|
|
134
|
+
|
|
135
|
+
script_run_cmd = r['run_cmd_string']
|
|
136
|
+
|
|
137
|
+
if quiet:
|
|
138
|
+
script_run_cmd += ' --quiet'
|
|
139
|
+
|
|
140
|
+
# Build the commands to run inside srun
|
|
141
|
+
run_cmds = []
|
|
142
|
+
|
|
143
|
+
# Bootstrap mlcflow on the node
|
|
144
|
+
quoted_venv = shlex.quote(slurm_python_venv)
|
|
145
|
+
run_cmds.append(
|
|
146
|
+
f'curl -sSL https://raw.githubusercontent.com/mlcommons/mlcflow/refs/heads/dev/docs/install/mlcflow_unix_installer.sh | bash -s -- --yes --venv-dir {quoted_venv}')
|
|
147
|
+
run_cmds.append(f'. {quoted_venv}/bin/activate')
|
|
148
|
+
|
|
149
|
+
if is_true(slurm_pull_mlc_repos):
|
|
150
|
+
run_cmds.append('mlc pull repo')
|
|
151
|
+
|
|
152
|
+
# Pre-run commands (after mlcflow is available)
|
|
153
|
+
run_cmds.extend(slurm_pre_run_cmds)
|
|
154
|
+
|
|
155
|
+
# The actual script command
|
|
156
|
+
run_cmds.append(script_run_cmd)
|
|
157
|
+
|
|
158
|
+
# Post-run commands
|
|
159
|
+
run_cmds.extend(slurm_post_run_cmds)
|
|
160
|
+
|
|
161
|
+
# Join all commands with && so failure stops execution
|
|
162
|
+
combined_cmd = ' && '.join(run_cmds)
|
|
163
|
+
|
|
164
|
+
# Build srun command
|
|
165
|
+
srun_args = []
|
|
166
|
+
srun_args.append('srun')
|
|
167
|
+
|
|
168
|
+
if slurm_partition:
|
|
169
|
+
srun_args.extend(['--partition', slurm_partition])
|
|
170
|
+
if slurm_nodes:
|
|
171
|
+
srun_args.extend(['--nodes', str(slurm_nodes)])
|
|
172
|
+
if slurm_ntasks:
|
|
173
|
+
srun_args.extend(['--ntasks', str(slurm_ntasks)])
|
|
174
|
+
if slurm_ntasks_per_node:
|
|
175
|
+
srun_args.extend(['--ntasks-per-node', str(slurm_ntasks_per_node)])
|
|
176
|
+
if slurm_cpus_per_task:
|
|
177
|
+
srun_args.extend(['--cpus-per-task', str(slurm_cpus_per_task)])
|
|
178
|
+
if slurm_gpus:
|
|
179
|
+
srun_args.extend(['--gpus', str(slurm_gpus)])
|
|
180
|
+
if slurm_gpus_per_node:
|
|
181
|
+
srun_args.extend(['--gpus-per-node', str(slurm_gpus_per_node)])
|
|
182
|
+
if slurm_gpus_per_task:
|
|
183
|
+
srun_args.extend(['--gpus-per-task', str(slurm_gpus_per_task)])
|
|
184
|
+
if slurm_mem:
|
|
185
|
+
srun_args.extend(['--mem', str(slurm_mem)])
|
|
186
|
+
if slurm_mem_per_cpu:
|
|
187
|
+
srun_args.extend(['--mem-per-cpu', str(slurm_mem_per_cpu)])
|
|
188
|
+
if slurm_mem_per_gpu:
|
|
189
|
+
srun_args.extend(['--mem-per-gpu', str(slurm_mem_per_gpu)])
|
|
190
|
+
if slurm_time:
|
|
191
|
+
srun_args.extend(['--time', str(slurm_time)])
|
|
192
|
+
if slurm_job_name:
|
|
193
|
+
srun_args.extend(['--job-name', slurm_job_name])
|
|
194
|
+
if slurm_output:
|
|
195
|
+
srun_args.extend(['--output', slurm_output])
|
|
196
|
+
if slurm_error:
|
|
197
|
+
srun_args.extend(['--error', slurm_error])
|
|
198
|
+
if slurm_account:
|
|
199
|
+
srun_args.extend(['--account', slurm_account])
|
|
200
|
+
if slurm_qos:
|
|
201
|
+
srun_args.extend(['--qos', slurm_qos])
|
|
202
|
+
if slurm_constraint:
|
|
203
|
+
srun_args.extend(['--constraint', slurm_constraint])
|
|
204
|
+
if is_true(slurm_exclusive):
|
|
205
|
+
srun_args.append('--exclusive')
|
|
206
|
+
if slurm_export:
|
|
207
|
+
srun_args.extend(['--export', slurm_export])
|
|
208
|
+
|
|
209
|
+
# Append any extra srun arguments the user provided
|
|
210
|
+
if slurm_srun_extra_args:
|
|
211
|
+
srun_args.extend(shlex.split(slurm_srun_extra_args))
|
|
212
|
+
|
|
213
|
+
# Wrap the combined command in bash -c for srun
|
|
214
|
+
srun_args.extend(['bash', '-c', combined_cmd])
|
|
215
|
+
|
|
216
|
+
logger.info(f'Running on SLURM: {shlex.join(srun_args)}')
|
|
217
|
+
|
|
218
|
+
rc = subprocess.call(srun_args)
|
|
219
|
+
|
|
220
|
+
if rc != 0:
|
|
221
|
+
return {'return': 1,
|
|
222
|
+
'error': f'srun exited with return code {rc}'}
|
|
223
|
+
|
|
224
|
+
return {'return': 0}
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def regenerate_script_cmd(i):
|
|
228
|
+
"""
|
|
229
|
+
Rebuild the mlcr/mlcd/mlce/mlca command string from the pruned input dict.
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
i_run_cmd = i['run_cmd']
|
|
233
|
+
slurm_action = i.get('slurm_action', 'run')
|
|
234
|
+
|
|
235
|
+
action_to_cmd = {
|
|
236
|
+
'run': 'mlcr',
|
|
237
|
+
'docker': 'mlcd',
|
|
238
|
+
'apptainer': 'mlca',
|
|
239
|
+
'experiment': 'mlce',
|
|
240
|
+
}
|
|
241
|
+
run_cmd = action_to_cmd.get(slurm_action, 'mlcr')
|
|
242
|
+
|
|
243
|
+
def rebuild_flags(command_dict, prefix):
|
|
244
|
+
command_line = ""
|
|
245
|
+
keys = sorted(command_dict.keys(), key=lambda x: x != "tags")
|
|
246
|
+
|
|
247
|
+
for key in keys:
|
|
248
|
+
full_key = f"{prefix}.{key}" if prefix else key
|
|
249
|
+
value = command_dict[key]
|
|
250
|
+
|
|
251
|
+
if isinstance(value, dict):
|
|
252
|
+
if value:
|
|
253
|
+
command_line += rebuild_flags(value, full_key)
|
|
254
|
+
elif isinstance(value, list):
|
|
255
|
+
if value:
|
|
256
|
+
list_values = ",".join(
|
|
257
|
+
shlex.quote(str(item)) for item in value)
|
|
258
|
+
command_line += f" --{full_key},={list_values}"
|
|
259
|
+
else:
|
|
260
|
+
if full_key in ['s', 'v']:
|
|
261
|
+
command_line += f" -{full_key}"
|
|
262
|
+
else:
|
|
263
|
+
command_line += f" --{full_key}={shlex.quote(str(value))}"
|
|
264
|
+
|
|
265
|
+
return command_line
|
|
266
|
+
|
|
267
|
+
run_cmd += rebuild_flags(i_run_cmd, "")
|
|
268
|
+
|
|
269
|
+
return {'return': 0, 'run_cmd_string': run_cmd}
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def slurm_docker(self_module, i):
|
|
273
|
+
"""Run an MLC docker script on a SLURM cluster node via srun."""
|
|
274
|
+
return slurm_run(self_module, i, slurm_action='docker')
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def slurm_apptainer(self_module, i):
|
|
278
|
+
"""Run an MLC apptainer script on a SLURM cluster node via srun."""
|
|
279
|
+
return slurm_run(self_module, i, slurm_action='apptainer')
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def slurm_experiment(self_module, i):
|
|
283
|
+
"""Run an MLC experiment on a SLURM cluster node via srun."""
|
|
284
|
+
return slurm_run(self_module, i, slurm_action='experiment')
|
|
@@ -240,6 +240,34 @@ def mlcrd():
|
|
|
240
240
|
mlc_expand_short("remote-docker")
|
|
241
241
|
|
|
242
242
|
|
|
243
|
+
def mlcsr():
|
|
244
|
+
mlc_expand_short("slurm-run")
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def mlcse():
|
|
248
|
+
mlc_expand_short("slurm-experiment")
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def mlcsd():
|
|
252
|
+
mlc_expand_short("slurm-docker")
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def mlcsa():
|
|
256
|
+
mlc_expand_short("slurm-apptainer")
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def mlcrs():
|
|
260
|
+
mlc_expand_short("remote-slurm")
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def mlcres():
|
|
264
|
+
mlc_expand_short("remote-slurm-experiment")
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def mlcrse():
|
|
268
|
+
mlc_expand_short("remote-slurm-experiment")
|
|
269
|
+
|
|
270
|
+
|
|
243
271
|
def mlce():
|
|
244
272
|
mlc_expand_short("experiment")
|
|
245
273
|
|
|
@@ -374,7 +402,10 @@ def build_parser(pre_args):
|
|
|
374
402
|
# Script-only
|
|
375
403
|
for action in ['docker', 'docker-run', 'apptainer', 'apptainer-run',
|
|
376
404
|
'experiment', 'remote-run', 'remote-experiment',
|
|
377
|
-
'remote-docker', '
|
|
405
|
+
'remote-docker', 'remote-slurm', 'remote-slurm-experiment',
|
|
406
|
+
'slurm-run',
|
|
407
|
+
'slurm-docker', 'slurm-apptainer', 'slurm-experiment',
|
|
408
|
+
'doc', 'lint']:
|
|
378
409
|
p = subparsers.add_parser(action, add_help=False)
|
|
379
410
|
p.add_argument('target', choices=['script', 'run'])
|
|
380
411
|
p.add_argument(
|
|
@@ -416,7 +447,10 @@ def build_run_args(args):
|
|
|
416
447
|
|
|
417
448
|
if args.command in ['docker', 'docker-run', 'apptainer', 'apptainer-run',
|
|
418
449
|
'experiment', 'remote-run', 'remote-experiment',
|
|
419
|
-
'remote-docker', '
|
|
450
|
+
'remote-docker', 'remote-slurm', 'remote-slurm-experiment',
|
|
451
|
+
'slurm-run',
|
|
452
|
+
'slurm-docker', 'slurm-apptainer', 'slurm-experiment',
|
|
453
|
+
'doc', 'lint'] and args.target == "run":
|
|
420
454
|
# run_args['target'] = 'script' #dont modify this as script might have
|
|
421
455
|
# target as in input
|
|
422
456
|
args.target = "script"
|
|
@@ -552,7 +586,19 @@ def main():
|
|
|
552
586
|
if not pre_args.action and not pre_args.target:
|
|
553
587
|
help_text += main.__doc__
|
|
554
588
|
elif pre_args.action and not pre_args.target:
|
|
555
|
-
|
|
589
|
+
script_only_actions = {
|
|
590
|
+
a.replace("-", "_")
|
|
591
|
+
for a in [
|
|
592
|
+
'docker', 'docker-run', 'apptainer', 'apptainer-run',
|
|
593
|
+
'experiment', 'remote-run', 'remote-experiment',
|
|
594
|
+
'remote-docker', 'remote-slurm', 'remote-slurm-experiment',
|
|
595
|
+
'slurm-run', 'slurm-docker', 'slurm-apptainer', 'slurm-experiment',
|
|
596
|
+
'doc', 'lint',
|
|
597
|
+
]
|
|
598
|
+
}
|
|
599
|
+
if pre_args.action in script_only_actions:
|
|
600
|
+
pre_args.target = 'script'
|
|
601
|
+
elif pre_args.action not in ['script', 'cache', 'repo']:
|
|
556
602
|
logger.error(f"Invalid target {pre_args.action}")
|
|
557
603
|
raise Exception(f"""Invalid target {pre_args.action}""")
|
|
558
604
|
else:
|
|
@@ -300,36 +300,17 @@ Main Script Meta:""")
|
|
|
300
300
|
self, module_path)
|
|
301
301
|
|
|
302
302
|
try:
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
run_args) # Pass args to the run method
|
|
306
|
-
elif function_name == "docker":
|
|
307
|
-
result = automation_instance.docker(
|
|
308
|
-
run_args) # Pass args to the run method
|
|
309
|
-
elif function_name == "apptainer":
|
|
310
|
-
result = automation_instance.apptainer(
|
|
311
|
-
run_args) # Pass args to the apptainer method
|
|
312
|
-
elif function_name == "test":
|
|
313
|
-
result = automation_instance.test(
|
|
314
|
-
run_args) # Pass args to the run method
|
|
315
|
-
elif function_name == "experiment":
|
|
316
|
-
result = automation_instance.experiment(
|
|
317
|
-
run_args) # Pass args to the experiment method
|
|
318
|
-
elif function_name == "remote_run":
|
|
319
|
-
result = automation_instance.remote_run(
|
|
320
|
-
run_args) # Pass args to the experiment method
|
|
321
|
-
elif function_name == "help":
|
|
322
|
-
result = automation_instance.help(
|
|
323
|
-
run_args) # Pass args to the help method
|
|
324
|
-
elif function_name == "doc":
|
|
325
|
-
result = automation_instance.doc(
|
|
326
|
-
run_args) # Pass args to the doc method
|
|
327
|
-
elif function_name == "lint":
|
|
328
|
-
result = automation_instance.lint(
|
|
329
|
-
run_args) # Pass args to the lint method
|
|
330
|
-
else:
|
|
303
|
+
method = getattr(automation_instance, function_name, None)
|
|
304
|
+
if method is None:
|
|
331
305
|
return {
|
|
332
|
-
'return': 1,
|
|
306
|
+
'return': 1,
|
|
307
|
+
'error': (
|
|
308
|
+
f"Action '{function_name}' is not supported by the loaded "
|
|
309
|
+
f"automation engine at '{module_path}'. "
|
|
310
|
+
"You may need a newer version of mlcflow."
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
result = method(run_args)
|
|
333
314
|
except ScriptExecutionError:
|
|
334
315
|
raise
|
|
335
316
|
except Exception as exc:
|
|
@@ -734,6 +715,169 @@ Main Script Meta:""")
|
|
|
734
715
|
run_args["remote_action"] = "docker"
|
|
735
716
|
return self.call_script_module_function("remote_run", run_args)
|
|
736
717
|
|
|
718
|
+
def slurm_run(self, run_args):
|
|
719
|
+
"""
|
|
720
|
+
################################################################################
|
|
721
|
+
Target: Script
|
|
722
|
+
Action: slurm-run
|
|
723
|
+
################################################################################
|
|
724
|
+
|
|
725
|
+
The `slurm-run` action runs an MLC script on a SLURM cluster node via srun.
|
|
726
|
+
|
|
727
|
+
Flags Available:
|
|
728
|
+
|
|
729
|
+
1. --slurm_partition:
|
|
730
|
+
SLURM partition to submit to
|
|
731
|
+
2. --slurm_nodes:
|
|
732
|
+
Number of nodes (default: 1)
|
|
733
|
+
3. --slurm_ntasks:
|
|
734
|
+
Number of tasks (default: 1)
|
|
735
|
+
4. --slurm_ntasks_per_node:
|
|
736
|
+
Number of tasks per node
|
|
737
|
+
5. --slurm_cpus_per_task:
|
|
738
|
+
Number of CPUs per task
|
|
739
|
+
6. --slurm_gpus:
|
|
740
|
+
Total number of GPUs
|
|
741
|
+
7. --slurm_gpus_per_node:
|
|
742
|
+
Number of GPUs per node
|
|
743
|
+
8. --slurm_gpus_per_task:
|
|
744
|
+
Number of GPUs per task
|
|
745
|
+
9. --slurm_mem:
|
|
746
|
+
Total memory (e.g., 16G)
|
|
747
|
+
10. --slurm_mem_per_cpu:
|
|
748
|
+
Memory per CPU (e.g., 4G)
|
|
749
|
+
11. --slurm_mem_per_gpu:
|
|
750
|
+
Memory per GPU (e.g., 8G)
|
|
751
|
+
12. --slurm_time:
|
|
752
|
+
Time limit (e.g., 01:00:00)
|
|
753
|
+
13. --slurm_job_name:
|
|
754
|
+
Job name
|
|
755
|
+
14. --slurm_output:
|
|
756
|
+
Output file pattern (e.g., slurm-%j.out)
|
|
757
|
+
15. --slurm_error:
|
|
758
|
+
Error file pattern (e.g., slurm-%j.err)
|
|
759
|
+
16. --slurm_account:
|
|
760
|
+
Account to charge
|
|
761
|
+
17. --slurm_qos:
|
|
762
|
+
Quality of service
|
|
763
|
+
18. --slurm_constraint:
|
|
764
|
+
Node feature constraint
|
|
765
|
+
19. --slurm_exclusive:
|
|
766
|
+
Request exclusive node access (default: False)
|
|
767
|
+
20. --slurm_export:
|
|
768
|
+
Environment export mode (default: ALL)
|
|
769
|
+
21. --slurm_srun_extra_args:
|
|
770
|
+
Additional arguments to pass directly to srun
|
|
771
|
+
22. --slurm_python_venv:
|
|
772
|
+
Python virtual environment name on the node (default: mlcflow)
|
|
773
|
+
23. --slurm_pull_mlc_repos:
|
|
774
|
+
Pull MLC repos on the node before running
|
|
775
|
+
24. --slurm_pre_run_cmds:
|
|
776
|
+
Commands to run on the node before the main script
|
|
777
|
+
25. --slurm_post_run_cmds:
|
|
778
|
+
Commands to run on the node after the main script
|
|
779
|
+
|
|
780
|
+
Example Command:
|
|
781
|
+
|
|
782
|
+
mlc slurm-run script --tags=detect,os
|
|
783
|
+
mlcsr detect,os --slurm_partition=gpu --slurm_gpus=1 --slurm_time=01:00:00
|
|
784
|
+
|
|
785
|
+
"""
|
|
786
|
+
return self.call_script_module_function("slurm_run", run_args)
|
|
787
|
+
|
|
788
|
+
def slurm_docker(self, run_args):
|
|
789
|
+
"""
|
|
790
|
+
################################################################################
|
|
791
|
+
Target: Script
|
|
792
|
+
Action: slurm-docker
|
|
793
|
+
################################################################################
|
|
794
|
+
|
|
795
|
+
The `slurm-docker` action runs an MLC docker script on a SLURM cluster node
|
|
796
|
+
via srun. Accepts the same --slurm_* flags as slurm-run.
|
|
797
|
+
|
|
798
|
+
Example Command:
|
|
799
|
+
|
|
800
|
+
mlc slurm-docker script --tags=detect,os
|
|
801
|
+
mlcsd detect,os --slurm_partition=gpu --slurm_gpus=1
|
|
802
|
+
|
|
803
|
+
"""
|
|
804
|
+
return self.call_script_module_function("slurm_docker", run_args)
|
|
805
|
+
|
|
806
|
+
def slurm_apptainer(self, run_args):
|
|
807
|
+
"""
|
|
808
|
+
################################################################################
|
|
809
|
+
Target: Script
|
|
810
|
+
Action: slurm-apptainer
|
|
811
|
+
################################################################################
|
|
812
|
+
|
|
813
|
+
The `slurm-apptainer` action runs an MLC apptainer script on a SLURM cluster
|
|
814
|
+
node via srun. Accepts the same --slurm_* flags as slurm-run.
|
|
815
|
+
|
|
816
|
+
Example Command:
|
|
817
|
+
|
|
818
|
+
mlc slurm-apptainer script --tags=detect,os
|
|
819
|
+
mlcsa detect,os --slurm_partition=gpu --slurm_gpus=1
|
|
820
|
+
|
|
821
|
+
"""
|
|
822
|
+
return self.call_script_module_function("slurm_apptainer", run_args)
|
|
823
|
+
|
|
824
|
+
def slurm_experiment(self, run_args):
|
|
825
|
+
"""
|
|
826
|
+
################################################################################
|
|
827
|
+
Target: Script
|
|
828
|
+
Action: slurm-experiment
|
|
829
|
+
################################################################################
|
|
830
|
+
|
|
831
|
+
The `slurm-experiment` action runs an MLC experiment on a SLURM cluster node
|
|
832
|
+
via srun. Accepts the same --slurm_* flags as slurm-run.
|
|
833
|
+
|
|
834
|
+
Example Command:
|
|
835
|
+
|
|
836
|
+
mlc slurm-experiment script --tags=detect,os
|
|
837
|
+
mlcse detect,os --slurm_partition=gpu
|
|
838
|
+
|
|
839
|
+
"""
|
|
840
|
+
return self.call_script_module_function("slurm_experiment", run_args)
|
|
841
|
+
|
|
842
|
+
def remote_slurm(self, run_args):
|
|
843
|
+
"""
|
|
844
|
+
################################################################################
|
|
845
|
+
Target: Script
|
|
846
|
+
Action: remote-slurm
|
|
847
|
+
################################################################################
|
|
848
|
+
|
|
849
|
+
The `remote-slurm` action connects to a remote machine via SSH and runs an
|
|
850
|
+
MLC slurm-run script there. Accepts all --remote_* flags from remote-run as
|
|
851
|
+
well as all --slurm_* flags from slurm-run.
|
|
852
|
+
|
|
853
|
+
Example Command:
|
|
854
|
+
|
|
855
|
+
mlc remote-slurm script --tags=detect,os --remote_host=mycluster.example.com
|
|
856
|
+
mlcrs detect,os --remote_host=mycluster.example.com --slurm_partition=gpu
|
|
857
|
+
|
|
858
|
+
"""
|
|
859
|
+
return self.call_script_module_function("remote_slurm", run_args)
|
|
860
|
+
|
|
861
|
+
def remote_slurm_experiment(self, run_args):
|
|
862
|
+
"""
|
|
863
|
+
################################################################################
|
|
864
|
+
Target: Script
|
|
865
|
+
Action: remote-slurm-experiment
|
|
866
|
+
################################################################################
|
|
867
|
+
|
|
868
|
+
The `remote-slurm-experiment` action connects to a remote machine via SSH
|
|
869
|
+
and runs an MLC slurm-experiment script there. Accepts all --remote_* flags
|
|
870
|
+
from remote-run as well as all --slurm_* flags from slurm-run.
|
|
871
|
+
|
|
872
|
+
Example Command:
|
|
873
|
+
|
|
874
|
+
mlc remote-slurm-experiment script --tags=detect,os --remote_host=mycluster.example.com
|
|
875
|
+
mlcres detect,os --remote_host=mycluster.example.com --slurm_partition=gpu
|
|
876
|
+
|
|
877
|
+
"""
|
|
878
|
+
return self.call_script_module_function(
|
|
879
|
+
"remote_slurm_experiment", run_args)
|
|
880
|
+
|
|
737
881
|
|
|
738
882
|
class ScriptExecutionError(Exception):
|
|
739
883
|
def __init__(self, message, script_name=None, repo_alias=None,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mlcflow
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.3
|
|
4
4
|
Summary: An automation interface tailored for CPU/GPU benchmarking
|
|
5
5
|
Author-email: MLCommons <systems@mlcommons.org>
|
|
6
6
|
License:
|
|
@@ -206,10 +206,10 @@ Dynamic: license-file
|
|
|
206
206
|
[](https://github.com/mlcommons/mlcflow/blob/main/LICENSE.md)
|
|
207
207
|
[](https://pepy.tech/project/mlcflow)
|
|
208
208
|
|
|
209
|
-
[](https://github.com/mlcommons/mlcflow/actions/workflows/test-mlc-core-actions.yaml)
|
|
210
|
+
[](https://github.com/mlcommons/mlperf-automations/actions/workflows/test-mlc-script-features.yml)
|
|
211
|
+
[](https://github.com/mlcommons/mlcflow/actions/workflows/mlperf-inference-resnet50.yml)
|
|
212
|
+
|
|
213
213
|
|
|
214
214
|
|
|
215
215
|
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.
|
|
@@ -21,6 +21,7 @@ automation/script/meta_schema.py
|
|
|
21
21
|
automation/script/module.py
|
|
22
22
|
automation/script/remote_run.py
|
|
23
23
|
automation/script/script_utils.py
|
|
24
|
+
automation/script/slurm_run.py
|
|
24
25
|
automation/script/validate.py
|
|
25
26
|
mlc/__init__.py
|
|
26
27
|
mlc/__main__.py
|
|
@@ -50,4 +51,5 @@ tests/test_cache_mark_tmp.py
|
|
|
50
51
|
tests/test_mlcflow_unix_installer.py
|
|
51
52
|
tests/test_repo_action_extra_git_args.py
|
|
52
53
|
tests/test_script_action_apptainer.py
|
|
54
|
+
tests/test_slurm_run.py
|
|
53
55
|
tests/test_unix_installer_venv.py
|
|
@@ -8,5 +8,12 @@ mlcp = mlc.main:mlcp
|
|
|
8
8
|
mlcr = mlc.main:mlcr
|
|
9
9
|
mlcrd = mlc.main:mlcrd
|
|
10
10
|
mlcre = mlc.main:mlcre
|
|
11
|
+
mlcres = mlc.main:mlcres
|
|
11
12
|
mlcrr = mlc.main:mlcrr
|
|
13
|
+
mlcrs = mlc.main:mlcrs
|
|
14
|
+
mlcrse = mlc.main:mlcrse
|
|
15
|
+
mlcsa = mlc.main:mlcsa
|
|
16
|
+
mlcsd = mlc.main:mlcsd
|
|
17
|
+
mlcse = mlc.main:mlcse
|
|
18
|
+
mlcsr = mlc.main:mlcsr
|
|
12
19
|
mlct = mlc.main:mlct
|
|
@@ -59,3 +59,10 @@ mlct = "mlc.main:mlct"
|
|
|
59
59
|
mlcp = "mlc.main:mlcp"
|
|
60
60
|
mlcre = "mlc.main:mlcre"
|
|
61
61
|
mlcrd = "mlc.main:mlcrd"
|
|
62
|
+
mlcsr = "mlc.main:mlcsr"
|
|
63
|
+
mlcse = "mlc.main:mlcse"
|
|
64
|
+
mlcsd = "mlc.main:mlcsd"
|
|
65
|
+
mlcsa = "mlc.main:mlcsa"
|
|
66
|
+
mlcrs = "mlc.main:mlcrs"
|
|
67
|
+
mlcres = "mlc.main:mlcres"
|
|
68
|
+
mlcrse = "mlc.main:mlcrse"
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for slurm_run.py command construction and remote_run.py mapping.
|
|
3
|
+
"""
|
|
4
|
+
import sys
|
|
5
|
+
import os
|
|
6
|
+
import unittest
|
|
7
|
+
|
|
8
|
+
# Ensure the automation directory is on sys.path so slurm_run / remote_run
|
|
9
|
+
# can be imported without a full mlcflow install.
|
|
10
|
+
_automation_dir = os.path.join(
|
|
11
|
+
os.path.dirname(__file__), '..', 'automation'
|
|
12
|
+
)
|
|
13
|
+
_automation_dir = os.path.abspath(_automation_dir)
|
|
14
|
+
if _automation_dir not in sys.path:
|
|
15
|
+
sys.path.insert(0, _automation_dir)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
# remote_run.regenerate_script_cmd — remote_action → mlc command mapping
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
class TestRemoteRunMapping(unittest.TestCase):
|
|
22
|
+
def _regenerate(self, remote_action, run_cmd=None):
|
|
23
|
+
from script.remote_run import regenerate_script_cmd
|
|
24
|
+
i = {
|
|
25
|
+
'remote_action': remote_action,
|
|
26
|
+
'run_cmd': run_cmd or {'tags': 'detect,os'},
|
|
27
|
+
'remote_run_settings': {},
|
|
28
|
+
'fake_run': False,
|
|
29
|
+
}
|
|
30
|
+
result = regenerate_script_cmd(i)
|
|
31
|
+
self.assertEqual(result['return'], 0)
|
|
32
|
+
return result['run_cmd_string']
|
|
33
|
+
|
|
34
|
+
def test_docker_maps_to_mlcd(self):
|
|
35
|
+
self.assertTrue(self._regenerate('docker').startswith('mlcd'))
|
|
36
|
+
|
|
37
|
+
def test_experiment_maps_to_mlce(self):
|
|
38
|
+
self.assertTrue(self._regenerate('experiment').startswith('mlce'))
|
|
39
|
+
|
|
40
|
+
def test_slurm_run_maps_to_mlcsr(self):
|
|
41
|
+
self.assertTrue(self._regenerate('slurm-run').startswith('mlcsr'))
|
|
42
|
+
|
|
43
|
+
def test_slurm_experiment_maps_to_mlcse(self):
|
|
44
|
+
# Regression test for blocker #1: was falling through to 'mlcr'
|
|
45
|
+
self.assertTrue(self._regenerate(
|
|
46
|
+
'slurm-experiment').startswith('mlcse'))
|
|
47
|
+
|
|
48
|
+
def test_slurm_docker_maps_to_mlcsd(self):
|
|
49
|
+
self.assertTrue(self._regenerate('slurm-docker').startswith('mlcsd'))
|
|
50
|
+
|
|
51
|
+
def test_slurm_apptainer_maps_to_mlcsa(self):
|
|
52
|
+
self.assertTrue(self._regenerate(
|
|
53
|
+
'slurm-apptainer').startswith('mlcsa'))
|
|
54
|
+
|
|
55
|
+
def test_unknown_action_defaults_to_mlcr(self):
|
|
56
|
+
self.assertTrue(self._regenerate('unknown-action').startswith('mlcr'))
|
|
57
|
+
|
|
58
|
+
def test_run_action_defaults_to_mlcr(self):
|
|
59
|
+
self.assertTrue(self._regenerate('run').startswith('mlcr'))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
# slurm_run.regenerate_script_cmd — slurm_action → mlc command mapping
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
class TestSlurmRunCmdGeneration(unittest.TestCase):
|
|
66
|
+
def _regenerate(self, slurm_action, run_cmd=None):
|
|
67
|
+
from script.slurm_run import regenerate_script_cmd
|
|
68
|
+
i = {
|
|
69
|
+
'slurm_action': slurm_action,
|
|
70
|
+
'run_cmd': run_cmd or {'tags': 'detect,os'},
|
|
71
|
+
}
|
|
72
|
+
result = regenerate_script_cmd(i)
|
|
73
|
+
self.assertEqual(result['return'], 0)
|
|
74
|
+
return result['run_cmd_string']
|
|
75
|
+
|
|
76
|
+
def test_run_maps_to_mlcr(self):
|
|
77
|
+
self.assertTrue(self._regenerate('run').startswith('mlcr'))
|
|
78
|
+
|
|
79
|
+
def test_docker_maps_to_mlcd(self):
|
|
80
|
+
self.assertTrue(self._regenerate('docker').startswith('mlcd'))
|
|
81
|
+
|
|
82
|
+
def test_apptainer_maps_to_mlca(self):
|
|
83
|
+
self.assertTrue(self._regenerate('apptainer').startswith('mlca'))
|
|
84
|
+
|
|
85
|
+
def test_experiment_maps_to_mlce(self):
|
|
86
|
+
self.assertTrue(self._regenerate('experiment').startswith('mlce'))
|
|
87
|
+
|
|
88
|
+
def test_unknown_defaults_to_mlcr(self):
|
|
89
|
+
self.assertTrue(self._regenerate('unknown').startswith('mlcr'))
|
|
90
|
+
|
|
91
|
+
def test_tags_included_in_output(self):
|
|
92
|
+
cmd = self._regenerate('run', run_cmd={'tags': 'detect,os'})
|
|
93
|
+
self.assertIn('detect,os', cmd)
|
|
94
|
+
|
|
95
|
+
def test_flag_included_in_output(self):
|
|
96
|
+
cmd = self._regenerate(
|
|
97
|
+
'run',
|
|
98
|
+
run_cmd={
|
|
99
|
+
'tags': 'detect,os',
|
|
100
|
+
'verbose': True})
|
|
101
|
+
self.assertIn('--verbose', cmd)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# ---------------------------------------------------------------------------
|
|
105
|
+
# slurm_run string normalization (pre/post run cmds)
|
|
106
|
+
# ---------------------------------------------------------------------------
|
|
107
|
+
class TestSlurmRunInputNormalization(unittest.TestCase):
|
|
108
|
+
"""
|
|
109
|
+
Test that str slurm_pre_run_cmds / slurm_post_run_cmds are wrapped in a
|
|
110
|
+
list instead of being iterated character-by-character.
|
|
111
|
+
The easiest way to test this is to call slurm_run with a mock that
|
|
112
|
+
immediately returns, and inspect the srun_args built.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
def test_pre_run_cmds_str_is_normalized(self):
|
|
116
|
+
"""A single str command must not be extended char-by-char."""
|
|
117
|
+
import shutil
|
|
118
|
+
from unittest.mock import patch, MagicMock
|
|
119
|
+
|
|
120
|
+
mock_self = MagicMock()
|
|
121
|
+
mock_self._select_script.return_value = {
|
|
122
|
+
'return': 0,
|
|
123
|
+
'script': MagicMock(
|
|
124
|
+
meta={'tags': [], 'alias': 'detect-os', 'uid': '0' * 16},
|
|
125
|
+
path='/fake/path'
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
mock_self.update_run_state_for_selected_script_and_variations.return_value = {
|
|
129
|
+
'return': 0}
|
|
130
|
+
mock_self.run_state = {}
|
|
131
|
+
mock_self.env = {}
|
|
132
|
+
mock_self.state = {}
|
|
133
|
+
mock_self.logger = MagicMock()
|
|
134
|
+
|
|
135
|
+
captured_args = []
|
|
136
|
+
|
|
137
|
+
def fake_call(args):
|
|
138
|
+
captured_args.extend(args)
|
|
139
|
+
return 0
|
|
140
|
+
|
|
141
|
+
with patch('script.slurm_run.shutil.which', return_value='/usr/bin/srun'), \
|
|
142
|
+
patch('script.slurm_run.subprocess.call', side_effect=fake_call):
|
|
143
|
+
from script.slurm_run import slurm_run
|
|
144
|
+
result = slurm_run(mock_self, {
|
|
145
|
+
'tags': 'detect,os',
|
|
146
|
+
'slurm_pre_run_cmds': 'module load cuda',
|
|
147
|
+
'env': {},
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
# The bash -c combined_cmd should contain 'module load cuda' as a
|
|
151
|
+
# complete token, not individual characters.
|
|
152
|
+
bash_c_cmd = captured_args[-1] # last element after 'bash', '-c'
|
|
153
|
+
self.assertIn('module load cuda', bash_c_cmd,
|
|
154
|
+
"Pre-run command string was expanded char-by-char instead of as a whole")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
if __name__ == '__main__':
|
|
158
|
+
unittest.main()
|
mlcflow-1.3.2/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1.3.2
|
|
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
|
|
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
|
|
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
|