Unverified Commit 2a28a578 authored by fishyds's avatar fishyds Committed by GitHub
Browse files

Merge branch V0.2 to Master (#143)

* webui logpath and document (#135)

* Add webui document and logpath as a href

* fix tslint

* fix comments by Chengmin

* Pai training service bug fix and enhancement (#136)

* Add NNI installation scripts

* Update pai script, update NNI_out_dir

* Update NNI dir in nni sdk local.py

* Create .nni folder in nni sdk local.py

* Add check before creating .nni folder

* Fix typo for PAI_INSTALL_NNI_SHELL_FORMAT

* Improve annotation (#138)

* Improve annotation

* Minor bugfix

* Selectively install through pip (#139)

Selectively install through pip 
* update setup.py

* fix paiTrainingService bugs (#137)

* fix nnictl bug

* add hdfs host validation

* fix bugs

* fix dockerfile

* fix install.sh

* update install.sh

* fix dockerfile

* Set timeout for HDFSUtility exists function

* remove unused TODO

* fix sdk

* add optional for outputDir and dataDir

* refactor dockerfile.base

* Remove unused import in hdfsclientUtility

* Add documentation for NNI PAI mode experiment (#141)

* Add documentation for NNI PAI mode

* Fix typo based on PR comments

* Exit with subprocess return code of trial keeper

* Remove additional exit code

* Fix typo based on PR comments

* update doc for smac tuner (#140)

* Revert "Selectively install through pip (#139)" due to potential pip install issue (#142)

* Revert "Selectively install through pip (#139)"

This reverts commit 1d174836.

* Add exit code of subprocess for trial_keeper

* Update README, add link to PAImode doc
parent 36b583b7
...@@ -32,23 +32,6 @@ pre.hyperpar{ ...@@ -32,23 +32,6 @@ pre.hyperpar{
float: right; float: right;
margin-right: 30px; margin-right: 30px;
} }
/* the pagination of next and prev */
.ant-pagination-prev .ant-pagination-item-link:after{
content: "Previous";
display: block;
font-size: 14px;
color: #333;
padding-left: 5px;
padding-right: 5px;
}
.ant-pagination-next .ant-pagination-item-link:after {
content: "NEXT";
display: block;
font-size: 14px;
color: #333;
padding-left: 5px;
padding-right: 5px;
}
Button.tableButton{ Button.tableButton{
background: #3c8dbc; background: #3c8dbc;
border-color: #3c8dbc; border-color: #3c8dbc;
......
This source diff could not be displayed because it is too large. You can view the blob instead.
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
...@@ -69,6 +69,7 @@ def _generate_file_search_space(path, module): ...@@ -69,6 +69,7 @@ def _generate_file_search_space(path, module):
def expand_annotations(src_dir, dst_dir): def expand_annotations(src_dir, dst_dir):
"""Expand annotations in user code. """Expand annotations in user code.
Return dst_dir if annotation detected; return src_dir if not.
src_dir: directory path of user code (str) src_dir: directory path of user code (str)
dst_dir: directory to place generated files (str) dst_dir: directory to place generated files (str)
""" """
...@@ -77,6 +78,8 @@ def expand_annotations(src_dir, dst_dir): ...@@ -77,6 +78,8 @@ def expand_annotations(src_dir, dst_dir):
if dst_dir[-1] == '/': if dst_dir[-1] == '/':
dst_dir = dst_dir[:-1] dst_dir = dst_dir[:-1]
annotated = False
for src_subdir, dirs, files in os.walk(src_dir): for src_subdir, dirs, files in os.walk(src_dir):
assert src_subdir.startswith(src_dir) assert src_subdir.startswith(src_dir)
dst_subdir = src_subdir.replace(src_dir, dst_dir, 1) dst_subdir = src_subdir.replace(src_dir, dst_dir, 1)
...@@ -86,17 +89,25 @@ def expand_annotations(src_dir, dst_dir): ...@@ -86,17 +89,25 @@ def expand_annotations(src_dir, dst_dir):
src_path = os.path.join(src_subdir, file_name) src_path = os.path.join(src_subdir, file_name)
dst_path = os.path.join(dst_subdir, file_name) dst_path = os.path.join(dst_subdir, file_name)
if file_name.endswith('.py'): if file_name.endswith('.py'):
_expand_file_annotations(src_path, dst_path) annotated |= _expand_file_annotations(src_path, dst_path)
else: else:
shutil.copyfile(src_path, dst_path) shutil.copyfile(src_path, dst_path)
for dir_name in dirs: for dir_name in dirs:
os.makedirs(os.path.join(dst_subdir, dir_name), exist_ok=True) os.makedirs(os.path.join(dst_subdir, dir_name), exist_ok=True)
return dst_dir if annotated else src_dir
def _expand_file_annotations(src_path, dst_path): def _expand_file_annotations(src_path, dst_path):
with open(src_path) as src, open(dst_path, 'w') as dst: with open(src_path) as src, open(dst_path, 'w') as dst:
try: try:
dst.write(code_generator.parse(src.read())) annotated_code = code_generator.parse(src.read())
if annotated_code is None:
shutil.copyfile(src_path, dst_path)
return False
dst.write(annotated_code)
return True
except Exception as exc: # pylint: disable=broad-except except Exception as exc: # pylint: disable=broad-except
if exc.args: if exc.args:
raise RuntimeError(src_path + ' ' + '\n'.join(exc.args)) raise RuntimeError(src_path + ' ' + '\n'.join(exc.args))
......
...@@ -161,6 +161,7 @@ class Transformer(ast.NodeTransformer): ...@@ -161,6 +161,7 @@ class Transformer(ast.NodeTransformer):
def __init__(self): def __init__(self):
self.stack = [] self.stack = []
self.last_line = 0 self.last_line = 0
self.annotated = False
def visit(self, node): def visit(self, node):
if isinstance(node, (ast.expr, ast.stmt)): if isinstance(node, (ast.expr, ast.stmt)):
...@@ -190,8 +191,9 @@ class Transformer(ast.NodeTransformer): ...@@ -190,8 +191,9 @@ class Transformer(ast.NodeTransformer):
def _visit_string(self, node): def _visit_string(self, node):
string = node.value.s string = node.value.s
if string.startswith('@nni.'):
if not string.startswith('@nni.'): self.annotated = True
else:
return node # not an annotation, ignore it return node # not an annotation, ignore it
if string.startswith('@nni.report_intermediate_result(') \ if string.startswith('@nni.report_intermediate_result(') \
...@@ -216,7 +218,7 @@ class Transformer(ast.NodeTransformer): ...@@ -216,7 +218,7 @@ class Transformer(ast.NodeTransformer):
def parse(code): def parse(code):
"""Annotate user code. """Annotate user code.
Return annotated code (str). Return annotated code (str) if annotation detected; return None if not.
code: original user code (str) code: original user code (str)
""" """
try: try:
...@@ -224,11 +226,15 @@ def parse(code): ...@@ -224,11 +226,15 @@ def parse(code):
except Exception: except Exception:
raise RuntimeError('Bad Python code') raise RuntimeError('Bad Python code')
transformer = Transformer()
try: try:
Transformer().visit(ast_tree) transformer.visit(ast_tree)
except AssertionError as exc: except AssertionError as exc:
raise RuntimeError('%d: %s' % (ast_tree.last_line, exc.args[0])) raise RuntimeError('%d: %s' % (ast_tree.last_line, exc.args[0]))
if not transformer.annotated:
return None
last_future_import = -1 last_future_import = -1
import_nni = ast.Import(names=[ast.alias(name='nni', asname=None)]) import_nni = ast.Import(names=[ast.alias(name='nni', asname=None)])
nodes = ast_tree.body nodes = ast_tree.body
......
...@@ -76,7 +76,7 @@ class SearchSpaceGenerator(ast.NodeVisitor): ...@@ -76,7 +76,7 @@ class SearchSpaceGenerator(ast.NodeVisitor):
else: else:
# generate the missing name automatically # generate the missing name automatically
assert len(node.args) > 0, 'Smart parameter expression has no argument' assert len(node.args) > 0, 'Smart parameter expression has no argument'
name = '#' + str(node.args[-1].lineno) name = '__line' + str(node.args[-1].lineno)
specified_name = False specified_name = False
if func in ('choice', 'function_choice'): if func in ('choice', 'function_choice'):
......
...@@ -27,6 +27,7 @@ import ast ...@@ -27,6 +27,7 @@ import ast
import json import json
import os import os
import shutil import shutil
import tempfile
from unittest import TestCase, main from unittest import TestCase, main
...@@ -43,12 +44,18 @@ class AnnotationTestCase(TestCase): ...@@ -43,12 +44,18 @@ class AnnotationTestCase(TestCase):
self.assertEqual(search_space, json.load(f)) self.assertEqual(search_space, json.load(f))
def test_code_generator(self): def test_code_generator(self):
expand_annotations('testcase/usercode', '_generated') code_dir = expand_annotations('testcase/usercode', '_generated')
self.assertEqual(code_dir, '_generated')
self._assert_source_equal('testcase/annotated/mnist.py', '_generated/mnist.py') self._assert_source_equal('testcase/annotated/mnist.py', '_generated/mnist.py')
self._assert_source_equal('testcase/annotated/dir/simple.py', '_generated/dir/simple.py') self._assert_source_equal('testcase/annotated/dir/simple.py', '_generated/dir/simple.py')
with open('testcase/usercode/nonpy.txt') as src, open('_generated/nonpy.txt') as dst: with open('testcase/usercode/nonpy.txt') as src, open('_generated/nonpy.txt') as dst:
assert src.read() == dst.read() assert src.read() == dst.read()
def test_annotation_detecting(self):
dir_ = 'testcase/usercode/non_annotation'
code_dir = expand_annotations(dir_, tempfile.mkdtemp())
self.assertEqual(code_dir, dir_)
def _assert_source_equal(self, src1, src2): def _assert_source_equal(self, src1, src2):
with open(src1) as f1, open(src2) as f2: with open(src1) as f1, open(src2) as f2:
ast1 = ast.dump(ast.parse(f1.read())) ast1 = ast.dump(ast.parse(f1.read()))
......
import nni
def bar():
"""I'm doc string"""
return nni.report_final_result(0)
...@@ -3,15 +3,15 @@ ...@@ -3,15 +3,15 @@
"_type": "choice", "_type": "choice",
"_value": [ 0, 1, 2, 3 ] "_value": [ 0, 1, 2, 3 ]
}, },
"handwrite/#5/function_choice": { "handwrite/__line5/function_choice": {
"_type": "choice", "_type": "choice",
"_value": [ 0, 1, 2 ] "_value": [ 0, 1, 2 ]
}, },
"handwrite/#8/qlognormal": { "handwrite/__line8/qlognormal": {
"_type": "qlognormal", "_type": "qlognormal",
"_value": [ 1.2, 3, 4.5 ] "_value": [ 1.2, 3, 4.5 ]
}, },
"handwrite/#13/choice": { "handwrite/__line13/choice": {
"_type": "choice", "_type": "choice",
"_value": [ 0, 1 ] "_value": [ 0, 1 ]
}, },
......
import nni
def bar():
"""I'm doc string"""
return nni.report_final_result(0)
...@@ -74,8 +74,8 @@ pai_trial_schema = { ...@@ -74,8 +74,8 @@ pai_trial_schema = {
'cpuNum': And(int, lambda x: 0 <= x <= 99999), 'cpuNum': And(int, lambda x: 0 <= x <= 99999),
'memoryMB': int, 'memoryMB': int,
'image': str, 'image': str,
'dataDir': Regex(r'hdfs://(([0-9]{1,3}.){3}[0-9]{1,3})(:[0-9]{2,5})?(/.*)?'), Optional('dataDir'): Regex(r'hdfs://(([0-9]{1,3}.){3}[0-9]{1,3})(:[0-9]{2,5})?(/.*)?'),
'outputDir': Regex(r'hdfs://(([0-9]{1,3}.){3}[0-9]{1,3})(:[0-9]{2,5})?(/.*)?') Optional('outputDir'): Regex(r'hdfs://(([0-9]{1,3}.){3}[0-9]{1,3})(:[0-9]{2,5})?(/.*)?')
} }
} }
......
...@@ -26,7 +26,6 @@ import string ...@@ -26,7 +26,6 @@ import string
from subprocess import Popen, PIPE, call from subprocess import Popen, PIPE, call
import tempfile import tempfile
from nni_annotation import * from nni_annotation import *
import random
from .launcher_utils import validate_all_content from .launcher_utils import validate_all_content
from .rest_utils import rest_put, rest_post, check_rest_server, check_rest_server_quick, check_response from .rest_utils import rest_put, rest_post, check_rest_server, check_rest_server_quick, check_response
from .url_utils import cluster_metadata_url, experiment_url from .url_utils import cluster_metadata_url, experiment_url
...@@ -80,6 +79,7 @@ def set_trial_config(experiment_config, port): ...@@ -80,6 +79,7 @@ def set_trial_config(experiment_config, port):
if check_response(response): if check_response(response):
return True return True
else: else:
print('Error message is {}'.format(response.text))
with open(STDERR_FULL_PATH, 'a+') as fout: with open(STDERR_FULL_PATH, 'a+') as fout:
fout.write(json.dumps(json.loads(response.text), indent=4, sort_keys=True, separators=(',', ':'))) fout.write(json.dumps(json.loads(response.text), indent=4, sort_keys=True, separators=(',', ':')))
return False return False
...@@ -110,7 +110,7 @@ def set_pai_config(experiment_config, port): ...@@ -110,7 +110,7 @@ def set_pai_config(experiment_config, port):
pai_config_data = dict() pai_config_data = dict()
pai_config_data['pai_config'] = experiment_config['paiConfig'] pai_config_data['pai_config'] = experiment_config['paiConfig']
response = rest_put(cluster_metadata_url(port), json.dumps(pai_config_data), 20) response = rest_put(cluster_metadata_url(port), json.dumps(pai_config_data), 20)
err_message = '' err_message = None
if not response or not response.status_code == 200: if not response or not response.status_code == 200:
if response is not None: if response is not None:
err_message = response.text err_message = response.text
...@@ -173,6 +173,7 @@ def set_experiment(experiment_config, mode, port): ...@@ -173,6 +173,7 @@ def set_experiment(experiment_config, mode, port):
else: else:
with open(STDERR_FULL_PATH, 'a+') as fout: with open(STDERR_FULL_PATH, 'a+') as fout:
fout.write(json.dumps(json.loads(response.text), indent=4, sort_keys=True, separators=(',', ':'))) fout.write(json.dumps(json.loads(response.text), indent=4, sort_keys=True, separators=(',', ':')))
print_error('Setting experiment error, error message is {}'.format(response.text))
return None return None
def launch_experiment(args, experiment_config, mode, webuiport, experiment_id=None): def launch_experiment(args, experiment_config, mode, webuiport, experiment_id=None):
...@@ -189,13 +190,11 @@ def launch_experiment(args, experiment_config, mode, webuiport, experiment_id=No ...@@ -189,13 +190,11 @@ def launch_experiment(args, experiment_config, mode, webuiport, experiment_id=No
nni_config.set_config('restServerPid', rest_process.pid) nni_config.set_config('restServerPid', rest_process.pid)
# Deal with annotation # Deal with annotation
if experiment_config.get('useAnnotation'): if experiment_config.get('useAnnotation'):
path = os.path.join(tempfile.gettempdir(), 'nni', 'annotation', ''.join(random.sample(string.ascii_letters + string.digits, 8))) path = os.path.join(tempfile.gettempdir(), 'nni', 'annotation')
if os.path.isdir(path): path = tempfile.mkdtemp(dir=path)
shutil.rmtree(path) code_dir = expand_annotations(experiment_config['trial']['codeDir'], path)
os.makedirs(path) experiment_config['trial']['codeDir'] = code_dir
expand_annotations(experiment_config['trial']['codeDir'], path) search_space = generate_search_space(code_dir)
experiment_config['trial']['codeDir'] = path
search_space = generate_search_space(experiment_config['trial']['codeDir'])
experiment_config['searchSpace'] = json.dumps(search_space) experiment_config['searchSpace'] = json.dumps(search_space)
assert search_space, ERROR_INFO % 'Generated search space is empty' assert search_space, ERROR_INFO % 'Generated search space is empty'
elif experiment_config.get('searchSpacePath'): elif experiment_config.get('searchSpacePath'):
...@@ -254,7 +253,8 @@ def launch_experiment(args, experiment_config, mode, webuiport, experiment_id=No ...@@ -254,7 +253,8 @@ def launch_experiment(args, experiment_config, mode, webuiport, experiment_id=No
if config_result: if config_result:
print_normal('Success!') print_normal('Success!')
else: else:
print_error('Failed! Error is: {}'.format(err_msg)) if err_msg:
print_error('Failed! Error is: {}'.format(err_msg))
try: try:
cmds = ['pkill', '-P', str(rest_process.pid)] cmds = ['pkill', '-P', str(rest_process.pid)]
call(cmds) call(cmds)
......
...@@ -28,7 +28,7 @@ DEFAULT_REST_PORT = 51189 ...@@ -28,7 +28,7 @@ DEFAULT_REST_PORT = 51189
HOME_DIR = os.path.join(os.environ['HOME'], 'nni') HOME_DIR = os.path.join(os.environ['HOME'], 'nni')
LOG_DIR = os.path.join(HOME_DIR, 'trial-keeper', 'log') LOG_DIR = os.environ['NNI_OUTPUT_DIR']
STDOUT_FULL_PATH = os.path.join(LOG_DIR, 'stdout') STDOUT_FULL_PATH = os.path.join(LOG_DIR, 'stdout')
......
...@@ -41,9 +41,12 @@ class TrialMetricsReader(): ...@@ -41,9 +41,12 @@ class TrialMetricsReader():
Read metrics data from a trial job Read metrics data from a trial job
''' '''
def __init__(self, rest_port = DEFAULT_REST_PORT): def __init__(self, rest_port = DEFAULT_REST_PORT):
self.offset_filename = os.path.join(NNI_SYS_DIR, '.nni', 'metrics_offset') metrics_base_dir = os.path.join(NNI_SYS_DIR, '.nni')
self.metrics_filename = os.path.join(NNI_SYS_DIR, '.nni', 'metrics') self.offset_filename = os.path.join(metrics_base_dir, 'metrics_offset')
self.metrics_filename = os.path.join(metrics_base_dir, 'metrics')
self.rest_port = rest_port self.rest_port = rest_port
if not os.path.exists(metrics_base_dir):
os.makedirs(metrics_base_dir)
def _metrics_file_is_empty(self): def _metrics_file_is_empty(self):
if not os.path.isfile(self.metrics_filename): if not os.path.isfile(self.metrics_filename):
......
...@@ -64,6 +64,9 @@ def main_loop(args): ...@@ -64,6 +64,9 @@ def main_loop(args):
print('copy directory failed!') print('copy directory failed!')
except Exception as exception: except Exception as exception:
print(exception) print(exception)
## Exit as the retCode of subprocess(trial)
exit(retCode)
break break
else: else:
print('subprocess pid: {} is still alive'.format(process.pid)) print('subprocess pid: {} is still alive'.format(process.pid))
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment