Unverified Commit 765206cb authored by liuzhe-lz's avatar liuzhe-lz Committed by GitHub
Browse files

Create experiment from Python code (#3111)

parent 1a999d70
...@@ -6,6 +6,8 @@ import os ...@@ -6,6 +6,8 @@ import os
import threading import threading
from enum import Enum from enum import Enum
_logger = logging.getLogger(__name__)
class CommandType(Enum): class CommandType(Enum):
# in # in
...@@ -32,8 +34,7 @@ try: ...@@ -32,8 +34,7 @@ try:
_in_file = open(3, 'rb') _in_file = open(3, 'rb')
_out_file = open(4, 'wb') _out_file = open(4, 'wb')
except OSError: except OSError:
_msg = 'IPC pipeline not exists, maybe you are importing tuner/assessor from trial code?' _logger.debug('IPC pipeline not exists')
logging.getLogger(__name__).warning(_msg)
def send(command, data): def send(command, data):
...@@ -46,7 +47,7 @@ def send(command, data): ...@@ -46,7 +47,7 @@ def send(command, data):
_lock.acquire() _lock.acquire()
data = data.encode('utf8') data = data.encode('utf8')
msg = b'%b%014d%b' % (command.value, len(data), data) msg = b'%b%014d%b' % (command.value, len(data), data)
logging.getLogger(__name__).debug('Sending command, data: [%s]', msg) _logger.debug('Sending command, data: [%s]', msg)
_out_file.write(msg) _out_file.write(msg)
_out_file.flush() _out_file.flush()
finally: finally:
...@@ -58,14 +59,14 @@ def receive(): ...@@ -58,14 +59,14 @@ def receive():
Returns a tuple of command (CommandType) and payload (str) Returns a tuple of command (CommandType) and payload (str)
""" """
header = _in_file.read(16) header = _in_file.read(16)
logging.getLogger(__name__).debug('Received command, header: [%s]', header) _logger.debug('Received command, header: [%s]', header)
if header is None or len(header) < 16: if header is None or len(header) < 16:
# Pipe EOF encountered # Pipe EOF encountered
logging.getLogger(__name__).debug('Pipe EOF encountered') _logger.debug('Pipe EOF encountered')
return None, None return None, None
length = int(header[2:]) length = int(header[2:])
data = _in_file.read(length) data = _in_file.read(length)
command = CommandType(header[:2]) command = CommandType(header[:2])
data = data.decode('utf8') data = data.decode('utf8')
logging.getLogger(__name__).debug('Received command, data: [%s]', data) _logger.debug('Received command, data: [%s]', data)
return command, data return command, data
# Copyright (c) Microsoft Corporation. # Copyright (c) Microsoft Corporation.
# Licensed under the MIT license. # Licensed under the MIT license.
import os
import copy import copy
import functools import functools
from enum import Enum, unique from enum import Enum, unique
...@@ -9,8 +8,6 @@ import json_tricks ...@@ -9,8 +8,6 @@ import json_tricks
from schema import And from schema import And
from . import parameter_expressions from . import parameter_expressions
from .runtime.common import init_logger
from .runtime.env_vars import dispatcher_env_vars
to_json = functools.partial(json_tricks.dumps, allow_nan=True) to_json = functools.partial(json_tricks.dumps, allow_nan=True)
...@@ -120,16 +117,6 @@ def convert_dict2tuple(value): ...@@ -120,16 +117,6 @@ def convert_dict2tuple(value):
return value return value
def init_dispatcher_logger():
"""
Initialize dispatcher logging configuration
"""
logger_file_path = 'dispatcher.log'
if dispatcher_env_vars.NNI_LOG_DIRECTORY is not None:
logger_file_path = os.path.join(dispatcher_env_vars.NNI_LOG_DIRECTORY, logger_file_path)
init_logger(logger_file_path, dispatcher_env_vars.NNI_LOG_LEVEL)
def json2space(x, oldy=None, name=NodeType.ROOT): def json2space(x, oldy=None, name=NodeType.ROOT):
""" """
Change search space from json format to hyperopt format Change search space from json format to hyperopt format
......
...@@ -47,4 +47,4 @@ ignore-patterns=test* ...@@ -47,4 +47,4 @@ ignore-patterns=test*
# List of members which are set dynamically and missed by pylint inference # List of members which are set dynamically and missed by pylint inference
generated-members=numpy.*,torch.*,tensorflow.* generated-members=numpy.*,torch.*,tensorflow.*
ignored-modules=tensorflow ignored-modules=tensorflow,_winapi,msvcrt
...@@ -74,6 +74,7 @@ dependencies = [ ...@@ -74,6 +74,7 @@ dependencies = [
'websockets', 'websockets',
'filelock', 'filelock',
'prettytable', 'prettytable',
'dataclasses ; python_version < "3.7"',
'numpy < 1.19.4 ; sys_platform == "win32"', 'numpy < 1.19.4 ; sys_platform == "win32"',
'numpy < 1.20 ; sys_platform != "win32" and python_version < "3.7"', 'numpy < 1.20 ; sys_platform != "win32" and python_version < "3.7"',
'numpy ; sys.platform != "win32" and python_version >= "3.7"' 'numpy ; sys.platform != "win32" and python_version >= "3.7"'
......
...@@ -143,8 +143,8 @@ testCases: ...@@ -143,8 +143,8 @@ testCases:
config: config:
maxTrialNum: 4 maxTrialNum: 4
trialConcurrency: 4 trialConcurrency: 4
launchCommand: python3 -c 'from nni.experiment import Experiment; exp = Experiment(); exp.start_experiment("$configFile")' launchCommand: python3 -c 'from nni.experiment import ExternalExperiment as Experiment; exp = Experiment(); exp.start_experiment("$configFile")'
stopCommand: python3 -c 'from nni.experiment import Experiment; exp = Experiment(); exp.connect_experiment("http://localhost:8080/"); exp.stop_experiment()' stopCommand: python3 -c 'from nni.experiment import ExternalExperiment as Experiment; exp = Experiment(); exp.connect_experiment("http://localhost:8080/"); exp.stop_experiment()'
validator: validator:
class: NnicliValidator class: NnicliValidator
platform: linux darwin platform: linux darwin
......
...@@ -110,8 +110,8 @@ testCases: ...@@ -110,8 +110,8 @@ testCases:
config: config:
maxTrialNum: 4 maxTrialNum: 4
trialConcurrency: 4 trialConcurrency: 4
launchCommand: python3 -c 'from nni.experiment import Experiment; exp = Experiment(); exp.start_experiment("$configFile")' launchCommand: python3 -c 'from nni.experiment import ExternalExperiment as Experiment; exp = Experiment(); exp.start_experiment("$configFile")'
stopCommand: python3 -c 'from nni.experiment import Experiment; exp = Experiment(); exp.connect_experiment("http://localhost:8080/"); exp.stop_experiment()' stopCommand: python3 -c 'from nni.experiment import ExternalExperiment as Experiment; exp = Experiment(); exp.connect_experiment("http://localhost:8080/"); exp.stop_experiment()'
validator: validator:
class: NnicliValidator class: NnicliValidator
platform: linux darwin platform: linux darwin
......
...@@ -47,8 +47,8 @@ testCases: ...@@ -47,8 +47,8 @@ testCases:
config: config:
maxTrialNum: 4 maxTrialNum: 4
trialConcurrency: 4 trialConcurrency: 4
launchCommand: python3 -c 'from nni.experiment import Experiment; exp = Experiment(); exp.start_experiment("$configFile")' launchCommand: python3 -c 'from nni.experiment import ExternalExperiment as Experiment; exp = Experiment(); exp.start_experiment("$configFile")'
stopCommand: python3 -c 'from nni.experiment import Experiment; exp = Experiment(); exp.connect_experiment("http://localhost:8080/"); exp.stop_experiment()' stopCommand: python3 -c 'from nni.experiment import ExternalExperiment as Experiment; exp = Experiment(); exp.connect_experiment("http://localhost:8080/"); exp.stop_experiment()'
validator: validator:
class: NnicliValidator class: NnicliValidator
platform: linux darwin platform: linux darwin
......
...@@ -6,7 +6,7 @@ from os import remove ...@@ -6,7 +6,7 @@ from os import remove
import subprocess import subprocess
import json import json
import requests import requests
from nni.experiment import Experiment from nni.experiment import ExternalExperiment as Experiment
from nni.tools.nnictl.updater import load_search_space from nni.tools.nnictl.updater import load_search_space
from utils import METRICS_URL, GET_IMPORTED_DATA_URL from utils import METRICS_URL, GET_IMPORTED_DATA_URL
......
...@@ -17,9 +17,10 @@ class ExperimentStartupInfo { ...@@ -17,9 +17,10 @@ class ExperimentStartupInfo {
private logDir: string = ''; private logDir: string = '';
private logLevel: string = ''; private logLevel: string = '';
private readonly: boolean = false; private readonly: boolean = false;
private dispatcherPipe: string | null = null;
private platform: string = ''; private platform: string = '';
public setStartupInfo(newExperiment: boolean, experimentId: string, basePort: number, platform: string, logDir?: string, logLevel?: string, readonly?: boolean): void { public setStartupInfo(newExperiment: boolean, experimentId: string, basePort: number, platform: string, logDir?: string, logLevel?: string, readonly?: boolean, dispatcherPipe?: string): void {
assert(!this.initialized); assert(!this.initialized);
assert(experimentId.trim().length > 0); assert(experimentId.trim().length > 0);
this.newExperiment = newExperiment; this.newExperiment = newExperiment;
...@@ -41,6 +42,10 @@ class ExperimentStartupInfo { ...@@ -41,6 +42,10 @@ class ExperimentStartupInfo {
if (readonly !== undefined) { if (readonly !== undefined) {
this.readonly = readonly; this.readonly = readonly;
} }
if (dispatcherPipe != undefined && dispatcherPipe.length > 0) {
this.dispatcherPipe = dispatcherPipe;
}
} }
public getExperimentId(): string { public getExperimentId(): string {
...@@ -84,6 +89,11 @@ class ExperimentStartupInfo { ...@@ -84,6 +89,11 @@ class ExperimentStartupInfo {
return this.readonly; return this.readonly;
} }
public getDispatcherPipe(): string | null {
assert(this.initialized);
return this.dispatcherPipe;
}
} }
function getExperimentId(): string { function getExperimentId(): string {
...@@ -107,16 +117,20 @@ function getExperimentStartupInfo(): ExperimentStartupInfo { ...@@ -107,16 +117,20 @@ function getExperimentStartupInfo(): ExperimentStartupInfo {
} }
function setExperimentStartupInfo( function setExperimentStartupInfo(
newExperiment: boolean, experimentId: string, basePort: number, platform: string, logDir?: string, logLevel?: string, readonly?: boolean): void { newExperiment: boolean, experimentId: string, basePort: number, platform: string, logDir?: string, logLevel?: string, readonly?: boolean, dispatcherPipe?: string): void {
component.get<ExperimentStartupInfo>(ExperimentStartupInfo) component.get<ExperimentStartupInfo>(ExperimentStartupInfo)
.setStartupInfo(newExperiment, experimentId, basePort, platform, logDir, logLevel, readonly); .setStartupInfo(newExperiment, experimentId, basePort, platform, logDir, logLevel, readonly, dispatcherPipe);
} }
function isReadonly(): boolean { function isReadonly(): boolean {
return component.get<ExperimentStartupInfo>(ExperimentStartupInfo).isReadonly(); return component.get<ExperimentStartupInfo>(ExperimentStartupInfo).isReadonly();
} }
function getDispatcherPipe(): string | null {
return component.get<ExperimentStartupInfo>(ExperimentStartupInfo).getDispatcherPipe();
}
export { export {
ExperimentStartupInfo, getBasePort, getExperimentId, isNewExperiment, getPlatform, getExperimentStartupInfo, ExperimentStartupInfo, getBasePort, getExperimentId, isNewExperiment, getPlatform, getExperimentStartupInfo,
setExperimentStartupInfo, isReadonly setExperimentStartupInfo, isReadonly, getDispatcherPipe
}; };
...@@ -126,7 +126,10 @@ class Logger { ...@@ -126,7 +126,10 @@ class Logger {
*/ */
private log(level: string, param: any[]): void { private log(level: string, param: any[]): void {
if (!this.readonly) { if (!this.readonly) {
const logContent = `[${(new Date()).toLocaleString()}] ${level} ${format(param)}\n`; const time = new Date();
const localTime = new Date(time.getTime() - time.getTimezoneOffset() * 60000);
const timeStr = localTime.toISOString().slice(0, -5).replace('T', ' ');
const logContent = `[${timeStr}] ${level} ${format(param)}\n`;
if (this.writable && this.bufferSerialEmitter) { if (this.writable && this.bufferSerialEmitter) {
const buffer: WritableStreamBuffer = new WritableStreamBuffer(); const buffer: WritableStreamBuffer = new WritableStreamBuffer();
buffer.write(logContent); buffer.write(logContent);
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
import * as assert from 'assert'; import * as assert from 'assert';
import { ChildProcess } from 'child_process'; import { ChildProcess } from 'child_process';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import * as net from 'net';
import { Readable, Writable } from 'stream'; import { Readable, Writable } from 'stream';
import { NNIError } from '../common/errors'; import { NNIError } from '../common/errors';
import { getLogger, Logger } from '../common/log'; import { getLogger, Logger } from '../common/log';
...@@ -62,10 +63,10 @@ class IpcInterface { ...@@ -62,10 +63,10 @@ class IpcInterface {
* @param proc the process to wrap * @param proc the process to wrap
* @param acceptCommandTypes set of accepted commands for this process * @param acceptCommandTypes set of accepted commands for this process
*/ */
constructor(proc: ChildProcess, acceptCommandTypes: Set<string>) { constructor(outStream: Writable, inStream: Readable, acceptCommandTypes: Set<string>) {
this.acceptCommandTypes = acceptCommandTypes; this.acceptCommandTypes = acceptCommandTypes;
this.outgoingStream = <Writable>proc.stdio[ipcOutgoingFd]; this.outgoingStream = outStream;
this.incomingStream = <Readable>proc.stdio[ipcIncomingFd]; this.incomingStream = inStream;
this.eventEmitter = new EventEmitter(); this.eventEmitter = new EventEmitter();
this.readBuffer = Buffer.alloc(0); this.readBuffer = Buffer.alloc(0);
...@@ -132,7 +133,14 @@ class IpcInterface { ...@@ -132,7 +133,14 @@ class IpcInterface {
* @param process_ the tuner process * @param process_ the tuner process
*/ */
function createDispatcherInterface(process: ChildProcess): IpcInterface { function createDispatcherInterface(process: ChildProcess): IpcInterface {
return new IpcInterface(process, new Set([...CommandType.TUNER_COMMANDS, ...CommandType.ASSESSOR_COMMANDS])); const outStream = <Writable>process.stdio[ipcOutgoingFd];
const inStream = <Readable>process.stdio[ipcIncomingFd];
return new IpcInterface(outStream, inStream, new Set([...CommandType.TUNER_COMMANDS, ...CommandType.ASSESSOR_COMMANDS]));
} }
export { IpcInterface, createDispatcherInterface, encodeCommand, decodeCommand }; function createDispatcherPipeInterface(pipePath: string): IpcInterface {
const client = net.createConnection(pipePath);
return new IpcInterface(client, client, new Set([...CommandType.TUNER_COMMANDS, ...CommandType.ASSESSOR_COMMANDS]));
}
export { IpcInterface, createDispatcherInterface, createDispatcherPipeInterface, encodeCommand, decodeCommand };
...@@ -9,7 +9,7 @@ import { Deferred } from 'ts-deferred'; ...@@ -9,7 +9,7 @@ import { Deferred } from 'ts-deferred';
import * as component from '../common/component'; import * as component from '../common/component';
import { DataStore, MetricDataRecord, MetricType, TrialJobInfo } from '../common/datastore'; import { DataStore, MetricDataRecord, MetricType, TrialJobInfo } from '../common/datastore';
import { NNIError } from '../common/errors'; import { NNIError } from '../common/errors';
import { getExperimentId } from '../common/experimentStartupInfo'; import { getExperimentId, getDispatcherPipe } from '../common/experimentStartupInfo';
import { getLogger, Logger } from '../common/log'; import { getLogger, Logger } from '../common/log';
import { import {
ExperimentParams, ExperimentProfile, Manager, ExperimentStatus, ExperimentParams, ExperimentProfile, Manager, ExperimentStatus,
...@@ -24,7 +24,7 @@ import { ...@@ -24,7 +24,7 @@ import {
INITIALIZE, INITIALIZED, KILL_TRIAL_JOB, NEW_TRIAL_JOB, NO_MORE_TRIAL_JOBS, PING, INITIALIZE, INITIALIZED, KILL_TRIAL_JOB, NEW_TRIAL_JOB, NO_MORE_TRIAL_JOBS, PING,
REPORT_METRIC_DATA, REQUEST_TRIAL_JOBS, SEND_TRIAL_JOB_PARAMETER, TERMINATE, TRIAL_END, UPDATE_SEARCH_SPACE, IMPORT_DATA REPORT_METRIC_DATA, REQUEST_TRIAL_JOBS, SEND_TRIAL_JOB_PARAMETER, TERMINATE, TRIAL_END, UPDATE_SEARCH_SPACE, IMPORT_DATA
} from './commands'; } from './commands';
import { createDispatcherInterface, IpcInterface } from './ipcInterface'; import { createDispatcherInterface, createDispatcherPipeInterface, IpcInterface } from './ipcInterface';
/** /**
* NNIManager which implements Manager interface * NNIManager which implements Manager interface
...@@ -71,6 +71,11 @@ class NNIManager implements Manager { ...@@ -71,6 +71,11 @@ class NNIManager implements Manager {
this.criticalError(NNIError.FromError(err, 'Job metrics error: ')); this.criticalError(NNIError.FromError(err, 'Job metrics error: '));
}); });
}; };
const pipe = getDispatcherPipe();
if (pipe !== null) {
this.dispatcher = createDispatcherPipeInterface(pipe);
}
} }
public updateExperimentProfile(experimentProfile: ExperimentProfile, updateType: ProfileUpdateType): Promise<void> { public updateExperimentProfile(experimentProfile: ExperimentProfile, updateType: ProfileUpdateType): Promise<void> {
...@@ -694,7 +699,7 @@ class NNIManager implements Manager { ...@@ -694,7 +699,7 @@ class NNIManager implements Manager {
} }
private async onTrialJobMetrics(metric: TrialJobMetric): Promise<void> { private async onTrialJobMetrics(metric: TrialJobMetric): Promise<void> {
this.log.debug(`NNIManager received trial job metrics: ${metric}`); this.log.debug(`NNIManager received trial job metrics: ${JSON.stringify(metric)}`);
if (this.trialJobs.has(metric.id)){ if (this.trialJobs.has(metric.id)){
await this.dataStore.storeMetricData(metric.id, metric.data); await this.dataStore.storeMetricData(metric.id, metric.data);
if (this.dispatcher === undefined) { if (this.dispatcher === undefined) {
......
...@@ -30,9 +30,9 @@ import { DLTSTrainingService } from './training_service/dlts/dltsTrainingService ...@@ -30,9 +30,9 @@ import { DLTSTrainingService } from './training_service/dlts/dltsTrainingService
function initStartupInfo( function initStartupInfo(
startExpMode: string, experimentId: string, basePort: number, platform: string, startExpMode: string, experimentId: string, basePort: number, platform: string,
logDirectory: string, experimentLogLevel: string, readonly: boolean): void { logDirectory: string, experimentLogLevel: string, readonly: boolean, dispatcherPipe: string): void {
const createNew: boolean = (startExpMode === ExperimentStartUpMode.NEW); const createNew: boolean = (startExpMode === ExperimentStartUpMode.NEW);
setExperimentStartupInfo(createNew, experimentId, basePort, platform, logDirectory, experimentLogLevel, readonly); setExperimentStartupInfo(createNew, experimentId, basePort, platform, logDirectory, experimentLogLevel, readonly, dispatcherPipe);
} }
async function initContainer(foreground: boolean, platformMode: string, logFileName?: string): Promise<void> { async function initContainer(foreground: boolean, platformMode: string, logFileName?: string): Promise<void> {
...@@ -163,7 +163,9 @@ if (!('true' || 'false').includes(readonlyArg.toLowerCase())) { ...@@ -163,7 +163,9 @@ if (!('true' || 'false').includes(readonlyArg.toLowerCase())) {
} }
const readonly = readonlyArg.toLowerCase() == 'true' ? true : false; const readonly = readonlyArg.toLowerCase() == 'true' ? true : false;
initStartupInfo(startMode, experimentId, port, mode, logDir, logLevel, readonly); const dispatcherPipe: string = parseArg(['--dispatcher_pipe']);
initStartupInfo(startMode, experimentId, port, mode, logDir, logLevel, readonly, dispatcherPipe);
mkDirP(getLogDir()) mkDirP(getLogDir())
.then(async () => { .then(async () => {
......
...@@ -279,6 +279,11 @@ ...@@ -279,6 +279,11 @@
version "7.0.3" version "7.0.3"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636"
"@types/lockfile@^1.0.0":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/lockfile/-/lockfile-1.0.1.tgz#434a3455e89843312f01976e010c60f1bcbd56f7"
integrity sha512-65WZedEm4AnOsBDdsapJJG42MhROu3n4aSSiu87JXF/pSdlubxZxp3S1yz3kTfkJ2KBPud4CpjoHVAptOm9Zmw==
"@types/mime@*": "@types/mime@*":
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.0.tgz#5a7306e367c539b9f6543499de8dd519fac37a8b" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.0.tgz#5a7306e367c539b9f6543499de8dd519fac37a8b"
......
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