Skip to content

Commit fb176ed

Browse files
authored
Merge pull request #615 from mlcommons/dev
Merge dev
2 parents 47843ef + 7541b24 commit fb176ed

File tree

40 files changed

+1187
-164
lines changed

40 files changed

+1187
-164
lines changed

.github/workflows/build_wheel_off.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
name: Build wheel and release into PYPI (off now)
22

3-
43
on:
54
push:
65
branches:

.github/workflows/test-mlperf-inference-tvm-resnet50.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
pip install mlcflow
3030
- name: Pull MLOps repository
3131
env:
32-
REPO: ${{ github.event.pull_request.head.repo.html_url }
32+
REPO: ${{ github.event.pull_request.head.repo.html_url }}
3333
BRANCH: ${{ github.event.pull_request.head.ref }}
3434
run: |
3535
mlc pull repo "$REPO" --branch="$BRANCH"

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.0.2
1+
1.1.0

automation/script/docker.py

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ def dockerfile(self_module, input_params):
5555
'script_variation_tags': variation_tags
5656
}
5757
docker_settings = metadata.get('docker', {})
58+
docker_settings_default_env = docker_settings.get('default_env', {})
59+
for key in docker_settings_default_env:
60+
env.setdefault(key, docker_settings_default_env[key])
61+
5862
state_data['docker'] = docker_settings
5963
add_deps_recursive = input_params.get('add_deps_recursive', {})
6064

@@ -93,6 +97,11 @@ def dockerfile(self_module, input_params):
9397
# Set Docker-specific configurations
9498
docker_settings = state_data['docker']
9599

100+
if is_true(docker_settings.get('pass_docker_to_script', False)):
101+
input_params['docker'] = True
102+
r = self_module.run(input_params)
103+
return r
104+
96105
if not docker_settings.get('run', True) and not input_params.get(
97106
'docker_run_override', False):
98107
logger.info("Docker 'run' is set to False in meta.json")
@@ -245,12 +254,6 @@ def docker_run(self_module, i):
245254
r = prune_input({'input': i, 'extra_keys_starts_with': ['docker_']})
246255
f_run_cmd = r['new_input']
247256

248-
# Regenerate Dockerfile if required
249-
if regenerate_docker_file:
250-
r = dockerfile(self_module, i)
251-
if r['return'] > 0:
252-
return r
253-
254257
# Save current directory and prepare to search for scripts
255258
cur_dir = os.getcwd()
256259
r = self_module.search(i.copy())
@@ -276,11 +279,7 @@ def docker_run(self_module, i):
276279
image_repo = i.get('docker_image_repo', '')
277280
add_deps_recursive = i.get('add_deps_recursive', {})
278281

279-
# Ensure Docker is available
280-
r = self_module.action_object.access(
281-
{'action': 'run', 'automation': 'script', 'tags': "get,docker"})
282-
if r['return'] > 0:
283-
return r
282+
input_i = copy.deepcopy(i)
284283

285284
# Process each artifact
286285
for artifact in sorted(lst, key=lambda x: x.meta.get('alias', '')):
@@ -301,6 +300,10 @@ def docker_run(self_module, i):
301300
folder_path_env_keys = meta.get('folder_path_env_keys', [])
302301

303302
docker_settings = meta.get('docker', {})
303+
docker_settings_default_env = docker_settings.get('default_env', {})
304+
for key in docker_settings_default_env:
305+
env.setdefault(key, docker_settings_default_env[key])
306+
304307
state['docker'] = docker_settings
305308
run_state = {
306309
'deps': [], 'fake_deps': [], 'parent': None,
@@ -360,6 +363,24 @@ def docker_run(self_module, i):
360363
logger.info("docker.run set to False in meta.yaml")
361364
continue
362365

366+
if is_true(docker_settings.get('pass_docker_to_script', False)):
367+
logger.info("Docker 'run' is passed to the run script")
368+
i['docker'] = True
369+
r = self_module.run(i)
370+
return r
371+
372+
# Regenerate Dockerfile if required
373+
if regenerate_docker_file:
374+
r = dockerfile(self_module, input_i)
375+
if r['return'] > 0:
376+
return r
377+
378+
# Ensure Docker is available
379+
r = self_module.action_object.access(
380+
{'action': 'run', 'automation': 'script', 'tags': "get,docker"})
381+
if r['return'] > 0:
382+
return r
383+
363384
r = self_module._update_env_from_input(env, i)
364385
if r['return'] > 0:
365386
return r

automation/script/docker_utils.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -215,14 +215,14 @@ def update_docker_environment(
215215
# Add host group ID if specified in the Docker settings and not on Windows
216216
if not is_false(docker_settings.get('pass_group_id')) and os.name != 'nt':
217217
env['+ MLC_DOCKER_BUILD_ARGS'].append(
218-
f"GID=\\\" $(id -g $USER) \\\""
218+
f"GID=\" $(id -g $USER) \""
219219
)
220220

221221
# Add host user ID if specified in the Docker settings and not on Windows
222222
if not is_false(docker_settings.get(
223223
'use_host_user_id')) and os.name != 'nt':
224224
env['+ MLC_DOCKER_BUILD_ARGS'].append(
225-
f"UID=\\\" $(id -u $USER) \\\""
225+
f"UID=\" $(id -u $USER) \""
226226
)
227227

228228
return {'return': 0}
@@ -285,7 +285,8 @@ def regenerate_script_cmd(i):
285285
# Check if the value is a string containing the specified paths
286286
if isinstance(value, str) and (
287287
os.path.join("local", "cache", "") in value or
288-
os.path.join("MLC", "repos", "") in value
288+
os.path.join("MLC", "repos", "") in value or
289+
"<<<" in value
289290
):
290291
del env[key]
291292

@@ -296,7 +297,8 @@ def regenerate_script_cmd(i):
296297
val for val in value
297298
if isinstance(val, str) and (
298299
os.path.join("local", "cache", "") in val or
299-
os.path.join("MLC", "repos", "") in val
300+
os.path.join("MLC", "repos", "") in val or
301+
"<<<" in value
300302
)
301303
]
302304

@@ -349,7 +351,7 @@ def rebuild_flags(
349351
continue
350352

351353
value = command_dict[key]
352-
quote = '\\"' if full_key in quote_keys else ""
354+
quote = '"' if full_key in quote_keys else ""
353355

354356
# Recursively process nested dictionaries.
355357
if isinstance(value, dict):
@@ -363,14 +365,15 @@ def rebuild_flags(
363365
# Process lists by concatenating values with commas.
364366
elif isinstance(value, list):
365367
list_values = ",".join(
366-
f"{quote}{str(item)}{quote}" for item in value)
368+
quote_if_needed(
369+
item, quote) for item in value)
367370
command_line += f" --{full_key},={list_values}"
368371
# Process scalar values.
369372
else:
370373
if full_key in ['s', 'v']:
371374
command_line += f" -{full_key}"
372375
else:
373-
command_line += f" --{full_key}={quote}{str(value)}{quote}"
376+
command_line += f" --{full_key}={quote_if_needed(value, quote)}"
374377

375378
return command_line
376379

automation/script/module.py

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,9 @@ def _run(self, i):
504504
env[key] = os.environ[key]
505505

506506
r = self._update_env_from_input(env, i)
507+
if env.get('MLC_OUTDIRNAME', '') != '':
508+
if not os.path.isabs(env['MLC_OUTDIRNAME']):
509+
env['MLC_OUTDIRNAME'] = os.path.abspath(env['MLC_OUTDIRNAME'])
507510

508511
#######################################################################
509512
# Check if we want to skip cache (either by skip_cache or by fake_run)
@@ -796,6 +799,7 @@ def _run(self, i):
796799
run_state['script_repo_git'] = script_item.repo.meta.get(
797800
'git', False)
798801
run_state['cache'] = meta.get('cache', False)
802+
run_state['cache_expiration'] = meta.get('cache_expiration', False)
799803

800804
if not recursion:
801805
run_state['script_entry_repo_to_report_errors'] = meta.get(
@@ -1748,12 +1752,7 @@ def _run(self, i):
17481752

17491753
tmp_curdir = os.getcwd()
17501754
if env.get('MLC_OUTDIRNAME', '') != '':
1751-
if os.path.isabs(env['MLC_OUTDIRNAME']) or recursion:
1752-
c_outdirname = env['MLC_OUTDIRNAME']
1753-
else:
1754-
c_outdirname = os.path.join(
1755-
env['MLC_TMP_CURRENT_PATH'], env['MLC_OUTDIRNAME'])
1756-
env['MLC_OUTDIRNAME'] = c_outdirname
1755+
c_outdirname = env['MLC_OUTDIRNAME']
17571756

17581757
if not fake_run: # prevent permission error inside docker runs
17591758
if not os.path.exists(c_outdirname):
@@ -2041,6 +2040,10 @@ def _run(self, i):
20412040
cached_path, dependent_cached_path):
20422041
cached_meta['dependent_cached_path'] = dependent_cached_path
20432042

2043+
if run_state.get('cache_expiration'): # convert to seconds
2044+
cached_meta['cache_expiration'] = parse_expiration(
2045+
run_state['cache_expiration'])
2046+
20442047
ii = {'action': 'update',
20452048
'target': 'cache',
20462049
'uid': cached_uid,
@@ -3151,9 +3154,8 @@ def native_run(self, i):
31513154
if os.name == 'nt':
31523155
script.append('set ' + k + '=' + v)
31533156
else:
3154-
if ' ' in v:
3155-
v = '"' + v + '"'
3156-
script.append('export ' + k + '=' + v)
3157+
safe_v = quote_if_needed(v)
3158+
script.append('export ' + k + '=' + safe_v)
31573159

31583160
script.append('')
31593161

@@ -4836,13 +4838,19 @@ def find_cached_script(i):
48364838

48374839
for cached_script in found_cached_scripts:
48384840
skip_cached_script = False
4841+
dependent_paths = []
48394842
dependent_cached_path = cached_script.meta.get(
4840-
'dependent_cached_path', '')
4843+
'dependent_cached_path')
48414844
if dependent_cached_path:
4845+
dependent_paths.append(dependent_cached_path)
4846+
dependent_cached_paths = cached_script.meta.get(
4847+
'dependent_cached_paths', '').split(':')
4848+
dependent_paths += [p for p in dependent_cached_paths if p]
4849+
for dep in dependent_paths:
48424850
if not os.path.exists(dependent_cached_path):
48434851
# TODO Need to restrict the below check to within container
48444852
# env
4845-
i['tmp_dep_cached_path'] = dependent_cached_path
4853+
i['tmp_dep_cached_path'] = dep
48464854
from script import docker_utils
48474855
r = docker_utils.get_container_path_script(i)
48484856
if not os.path.exists(r['value_env']):
@@ -4851,7 +4859,10 @@ def find_cached_script(i):
48514859
recursion_spaces +
48524860
' - Skipping cached entry as the dependent path {} is missing!'.format(r['value_env']))
48534861
skip_cached_script = True
4854-
continue
4862+
break
4863+
4864+
if skip_cached_script:
4865+
continue
48554866

48564867
os_info = self_obj.os_info
48574868

@@ -5047,7 +5058,7 @@ def update_env_with_values(env, fail_on_not_found=False, extra_env=None):
50475058
# No placeholders found
50485059
if not placeholders:
50495060
# Special handling for MLC_GIT_URL
5050-
if key == 'MLC_GIT_URL' and env.get('MLC_GIT_AUTH', "no") == "yes":
5061+
if key == 'MLC_GIT_URL' and is_true(env.get('MLC_GIT_AUTH')):
50515062
if env.get('MLC_GH_TOKEN',
50525063
'') and '@' not in env['MLC_GIT_URL']:
50535064
params = {"token": env['MLC_GH_TOKEN']}
@@ -5287,7 +5298,7 @@ def prepare_and_run_script_with_postprocessing(i, postprocess="postprocess"):
52875298
if r['return'] > 0:
52885299
return r
52895300

5290-
# Save file to run without CM
5301+
# Save file to run without MLC
52915302
if debug_script_tags != '' and all(
52925303
item in found_script_tags for item in debug_script_tags.split(',')):
52935304

@@ -5593,10 +5604,12 @@ def convert_env_to_script(env, os_info, start_script=None):
55935604
os_info['env_var'].replace(
55945605
'env_var', key)}"""
55955606

5607+
env_quote = os_info['env_quote']
55965608
# Replace placeholders in the platform-specific environment command
5609+
# and escapes any quote in the env value
55975610
env_command = os_info['set_env'].replace(
55985611
'${key}', key).replace(
5599-
'${value}', str(env_value))
5612+
'${value}', str(env_value).replace(env_quote, f"""\\{env_quote}"""))
56005613
script.append(env_command)
56015614

56025615
return script
@@ -5880,6 +5893,9 @@ def update_state_from_meta(meta, env, state, const, const_state, deps, post_deps
58805893
if meta.get('cache', '') != '':
58815894
run_state['cache'] = meta['cache']
58825895

5896+
if meta.get('cache_expiration', '') != '':
5897+
run_state['cache_expiration'] = meta['cache_expiration']
5898+
58835899
default_env = meta.get('default_env', {})
58845900
for key in default_env:
58855901
env.setdefault(key, default_env[key])

automation/utils.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ def get_host_os_info(i={}):
4545
info['bat_ext'] = '.bat'
4646
info['set_env'] = 'set ${key}=${value}'
4747
info['env_separator'] = ';'
48+
info['env_quote'] = '\''
4849
info['env_var'] = '%env_var%'
4950
info['bat_rem'] = 'rem ${rem}'
5051
info['run_local_bat'] = 'call ${bat_file}'
@@ -63,6 +64,7 @@ def get_host_os_info(i={}):
6364
info['bat_ext'] = '.sh'
6465
info['set_env'] = 'export ${key}="${value}"'
6566
info['env_separator'] = ':'
67+
info['env_quote'] = '"'
6668
info['env_var'] = '${env_var}'
6769
info['set_exec_file'] = 'chmod 755 "${file_name}"'
6870
info['bat_rem'] = '# ${rem}'
@@ -1101,3 +1103,44 @@ def print_json(i):
11011103
print(json.dumps(meta, indent=2))
11021104

11031105
return {'return': 0}
1106+
1107+
1108+
def parse_expiration(user_input: str) -> float:
1109+
import time
1110+
"""
1111+
Parse user input like '10m', '2h', '3d' into a UNIX timestamp.
1112+
"""
1113+
units = {
1114+
'm': 60, # minutes
1115+
'h': 3600, # hours
1116+
'd': 86400, # days
1117+
}
1118+
1119+
if not user_input:
1120+
raise ValueError("Expiration time cannot be empty")
1121+
1122+
unit = user_input[-1].lower()
1123+
if unit not in units:
1124+
raise ValueError(f"Unknown unit '{unit}', use m/h/d")
1125+
1126+
try:
1127+
value = int(user_input[:-1])
1128+
except ValueError:
1129+
raise ValueError(f"Invalid number in '{user_input}'")
1130+
1131+
seconds = value * units[unit]
1132+
return time.time() + seconds
1133+
1134+
1135+
def quote_if_needed(val: str, quote: str) -> str:
1136+
"""
1137+
Return the value, quoted with escaped quotes if it contains spaces
1138+
or quotes.
1139+
"""
1140+
s = str(val)
1141+
1142+
if " " in s or '"' in s or quote == '"':
1143+
# Escape all existing double quotes
1144+
s = s.replace('"', r'\"')
1145+
return f'"{s}"'
1146+
return s

0 commit comments

Comments
 (0)