Skip to content
This repository has been archived by the owner on Sep 18, 2024. It is now read-only.

Commit

Permalink
Update ci with new built-in tuner and assessor (#359)
Browse files Browse the repository at this point in the history
* fix sdk's unittest and add medianstop, batchtuner to ci

* fix sdk's unittest and add medianstop, batchtuner to ci

* remove debug info

* update azure-pipelines

* remove useless code

* add some checks

* fix pylint

* update ci test

* update ci
  • Loading branch information
Crysple authored and QuanluZhang committed Nov 22, 2018
1 parent 76277db commit 7035f3e
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 92 deletions.
4 changes: 2 additions & 2 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
displayName: 'Integration tests'
- script: |
cd test
PATH=$HOME/.local/bin:$PATH python3 sdk_tuner_test.py
PATH=$HOME/.local/bin:$PATH python3 sdk_test.py
displayName: 'Built-in tuner tests'
- job: 'Install_through_source_code'
Expand All @@ -43,5 +43,5 @@ jobs:
displayName: 'Integration tests'
- script: |
cd test
PATH=$HOME/.local/bin:$PATH python3 sdk_tuner_test.py
PATH=$HOME/.local/bin:$PATH python3 sdk_test.py
displayName: 'Built-in tuner tests'
5 changes: 3 additions & 2 deletions src/sdk/pynni/tests/test_assessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import nni.protocol
from nni.protocol import CommandType, send, receive
from nni.assessor import Assessor, AssessResult
from nni.msg_dispatcher import MsgDispatcher

from io import BytesIO
import json
Expand Down Expand Up @@ -66,14 +67,14 @@ def test_assessor(self):
send(CommandType.ReportMetricData, '{"trial_job_id":"B","type":"PERIODICAL","sequence":0,"value":2}')
send(CommandType.ReportMetricData, '{"trial_job_id":"A","type":"PERIODICAL","sequence":1,"value":3}')
send(CommandType.TrialEnd, '{"trial_job_id":"A","event":"SYS_CANCELED"}')
send(CommandType.ReportMetricData, '{"trial_job_id":"B","type":"FINAL","sequence":0,"value":1}')
send(CommandType.TrialEnd, '{"trial_job_id":"B","event":"SUCCEEDED"}')
send(CommandType.NewTrialJob, 'null')
_restore_io()

assessor = NaiveAssessor()
dispatcher = MsgDispatcher(None, assessor)
try:
assessor.run()
dispatcher.run()
except Exception as e:
self.assertIs(type(e), AssertionError)
self.assertEqual(e.args[0], 'Unsupported command: CommandType.NewTrialJob')
Expand Down
4 changes: 3 additions & 1 deletion src/sdk/pynni/tests/test_tuner.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import nni.protocol
from nni.protocol import CommandType, send, receive
from nni.tuner import Tuner
from nni.msg_dispatcher import MsgDispatcher

from io import BytesIO
import json
Expand Down Expand Up @@ -87,8 +88,9 @@ def test_tuner(self):
_restore_io()

tuner = NaiveTuner()
dispatcher = MsgDispatcher(tuner)
try:
tuner.run()
dispatcher.run()
except Exception as e:
self.assertIs(type(e), AssertionError)
self.assertEqual(e.args[0], 'Unsupported command: CommandType.KillTrialJob')
Expand Down
28 changes: 23 additions & 5 deletions test/naive_test.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,39 @@
#!/usr/bin/env python3
# Copyright (c) Microsoft Corporation
# All rights reserved.
#
# MIT License
#
# Permission is hereby granted, free of charge,
# to any person obtaining a copy of this software and associated
# documentation files (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and
# to permit persons to whom the Software is furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
# BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import json
import subprocess
import sys
import time
import traceback

from utils import check_experiment_status, fetch_experiment_config, read_last_line, remove_files, setup_experiment
from utils import check_experiment_status, fetch_nni_log_path, read_last_line, remove_files, setup_experiment

GREEN = '\33[32m'
RED = '\33[31m'
CLEAR = '\33[0m'

EXPERIMENT_URL = 'http://localhost:8080/api/v1/nni/experiment'

def run(installed = True):

def run():
'''run naive integration test'''
to_remove = ['tuner_search_space.json', 'tuner_result.txt', 'assessor_result.txt']
to_remove = list(map(lambda file: 'naive_test/' + file, to_remove))
remove_files(to_remove)
Expand All @@ -25,7 +43,7 @@ def run(installed = True):

print('Spawning trials...')

nnimanager_log_path = fetch_experiment_config(EXPERIMENT_URL)
nnimanager_log_path = fetch_nni_log_path(EXPERIMENT_URL)
current_trial = 0

for _ in range(60):
Expand Down
90 changes: 90 additions & 0 deletions test/sdk_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Copyright (c) Microsoft Corporation
# All rights reserved.
#
# MIT License
#
# Permission is hereby granted, free of charge,
# to any person obtaining a copy of this software and associated
# documentation files (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and
# to permit persons to whom the Software is furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
# BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import subprocess
import sys
import time
import traceback

from utils import get_yml_content, dump_yml_content, setup_experiment, fetch_nni_log_path, check_experiment_status

GREEN = '\33[32m'
RED = '\33[31m'
CLEAR = '\33[0m'

TUNER_LIST = ['BatchTuner', 'TPE', 'Random', 'Anneal', 'Evolution']
ASSESSOR_LIST = ['Medianstop']
EXPERIMENT_URL = 'http://localhost:8080/api/v1/nni/experiment'


def switch(dispatch_type, dispatch_name):
'''Change dispatch in config.yml'''
config_path = 'sdk_test/local.yml'
experiment_config = get_yml_content(config_path)
experiment_config[dispatch_type.lower()] = {
'builtin' + dispatch_type + 'Name': dispatch_name,
'classArgs': {
'optimize_mode': 'maximize'
}
}
dump_yml_content(config_path, experiment_config)

def test_builtin_dispatcher(dispatch_type, dispatch_name):
'''test a dispatcher whose type is dispatch_type and name is dispatch_name'''
switch(dispatch_type, dispatch_name)

print('Testing %s...' % dispatch_name)
proc = subprocess.run(['nnictl', 'create', '--config', 'sdk_test/local.yml'])
assert proc.returncode == 0, '`nnictl create` failed with code %d' % proc.returncode

nnimanager_log_path = fetch_nni_log_path(EXPERIMENT_URL)

for _ in range(10):
time.sleep(3)
# check if experiment is done
experiment_status = check_experiment_status(nnimanager_log_path)
if experiment_status:
break

assert experiment_status, 'Failed to finish in 30 sec'

def run(dispatch_type):
'''test all dispatchers whose type is dispatch_type'''
assert dispatch_type in ['Tuner', 'Assessor'], 'Unsupported dispatcher type: %s' % (dispatch_type)
dipsatcher_list = TUNER_LIST if dispatch_type == 'Tuner' else ASSESSOR_LIST
for dispatcher_name in dipsatcher_list:
try:
test_builtin_dispatcher(dispatch_type, dispatcher_name)
print(GREEN + 'Test %s %s: TEST PASS' % (dispatcher_name, dispatch_type) + CLEAR)
except Exception as error:
print(RED + 'Test %s %s: TEST FAIL' % (dispatcher_name, dispatch_type) + CLEAR)
print('%r' % error)
traceback.print_exc()
raise error
finally:
subprocess.run(['nnictl', 'stop'])


if __name__ == '__main__':
installed = (sys.argv[-1] != '--preinstall')
setup_experiment(installed)

run('Tuner')
run('Assessor')
6 changes: 5 additions & 1 deletion test/sdk_tuner_test/local.yml → test/sdk_test/local.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
assessor:
builtinAssessorName: Medianstop
classArgs:
optimize_mode: maximize
authorName: nni
experimentName: test_builtin_tuner
experimentName: test_sdk
maxExecDuration: 1h
maxTrialNum: 2
searchSpacePath: search_space.json
Expand Down
File renamed without changes.
File renamed without changes.
75 changes: 0 additions & 75 deletions test/sdk_tuner_test.py

This file was deleted.

36 changes: 30 additions & 6 deletions test/utils.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,42 @@
# Copyright (c) Microsoft Corporation
# All rights reserved.
#
# MIT License
#
# Permission is hereby granted, free of charge,
# to any person obtaining a copy of this software and associated
# documentation files (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and
# to permit persons to whom the Software is furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
# BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import contextlib
import json
import os
import subprocess
import requests
import traceback
import yaml

EXPERIMENT_DONE_SIGNAL = '"Experiment done"'

def read_last_line(file_name):
'''read last line of a file and return None if file not found'''
try:
*_, last_line = open(file_name)
return last_line.strip()
except (FileNotFoundError, ValueError):
return None

def remove_files(file_list):
'''remove a list of files'''
for file_path in file_list:
with contextlib.suppress(FileNotFoundError):
os.remove(file_path)
Expand All @@ -30,7 +51,8 @@ def dump_yml_content(file_path, content):
with open(file_path, 'w') as file:
file.write(yaml.dump(content, default_flow_style=False))

def setup_experiment(installed = True):
def setup_experiment(installed=True):
'''setup the experiment if nni is not installed'''
if not installed:
os.environ['PATH'] = os.environ['PATH'] + ':' + os.environ['PWD']
sdk_path = os.path.abspath('../src/sdk/pynni')
Expand All @@ -42,7 +64,8 @@ def setup_experiment(installed = True):
pypath = ':'.join([sdk_path, cmd_path])
os.environ['PYTHONPATH'] = pypath

def fetch_experiment_config(experiment_url):
def fetch_nni_log_path(experiment_url):
'''get nni's log path from nni's experiment url'''
experiment_profile = requests.get(experiment_url)
experiment_id = json.loads(experiment_profile.text)['id']
experiment_path = os.path.join(os.environ['HOME'], 'nni/experiments', experiment_id)
Expand All @@ -51,8 +74,9 @@ def fetch_experiment_config(experiment_url):
return nnimanager_log_path

def check_experiment_status(nnimanager_log_path):
'''check if the experiment is done successfully'''
assert os.path.exists(nnimanager_log_path), 'Experiment starts failed'
cmds = ['cat', nnimanager_log_path, '|', 'grep', EXPERIMENT_DONE_SIGNAL]
completed_process = subprocess.run(' '.join(cmds), shell = True)
return completed_process.returncode == 0
completed_process = subprocess.run(' '.join(cmds), shell=True)

return completed_process.returncode == 0

0 comments on commit 7035f3e

Please sign in to comment.