Unverified Commit 3f6a8274 authored by liuzhe-lz's avatar liuzhe-lz Committed by GitHub
Browse files

Some string changes around experiment module (#4442)

parent d5ed88e4
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
from .exp_config import ExperimentConfig
from .experiment_config import ExperimentConfig
from .algorithm import AlgorithmConfig, CustomAlgorithmConfig
from .training_services import *
from .shared_storage import *
......@@ -59,7 +59,7 @@ class ConfigBase:
def __init__(self, **kwargs):
"""
There are two common ways to use the constructor,
directly writing Python code and unpacking from JSON(YAML) object:
directly writing kwargs and unpacking from JSON (YAML) object:
.. code-block:: python
......@@ -77,7 +77,7 @@ class ConfigBase:
then using ``hello_world=1``, ``helloWorld=1``, and ``_HELLOWORLD_=1`` in constructor
will all assign to the same field.
If ``kwargs`` contain extra keys, a `ValueError` will be raised.
If ``kwargs`` contain extra keys, `AttributeError` will be raised.
If ``kwargs`` do not have enough key, missing fields are silently set to `MISSING()`.
You can use ``utils.is_missing()`` to check them.
......@@ -90,7 +90,7 @@ class ConfigBase:
if args: # maybe a key is misspelled
class_name = type(self).__name__
fields = ', '.join(args.keys())
raise ValueError(f'{class_name} does not have field(s) {fields}')
raise AttributeError(f'{class_name} does not have field(s) {fields}')
# try to unpack nested config
for field in dataclasses.fields(self):
......@@ -135,7 +135,7 @@ class ConfigBase:
with open(path) as yaml_file:
data = yaml.safe_load(yaml_file)
if not isinstance(data, dict):
raise ValueError(f'Conent of config file {path} is not a dict/object')
raise TypeError(f'Conent of config file {path} is not a dict/object')
utils.set_base_path(Path(path).parent)
config = cls(**data)
utils.unset_base_path()
......@@ -143,10 +143,15 @@ class ConfigBase:
def canonical_copy(self):
"""
Create a canonicalized copy of the config, and validate it.
Create a "canonical" copy of the config, and validate it.
This function is mainly used internally by NNI.
Term explanation:
The config schema for end users is more flexible than the format NNI manager accepts,
so config classes have to deal with the conversion.
Here we call the converted format "canonical".
Returns
-------
type(self)
......@@ -186,15 +191,16 @@ class ConfigBase:
def _canonicalize(self, parents):
"""
The config schema for end users is more flexible than the format NNI manager accepts.
This method convert a config object to the constrained format accepted by NNI manager.
To be overrided by subclass.
Convert the config object to canonical format.
The default implementation will:
1. Resolve all ``PathLike`` fields to absolute path
2. Call ``_canonicalize()`` on all children config objects, including those inside list and dict
2. Call ``_canonicalize([self] + parents)`` on all children config objects, including those inside list and dict
Subclasses are recommended to call ``super()._canonicalize(parents)`` at the end of their overrided version.
If the subclass has nested config fields, be careful about where to call ``super()._canonicalize()``.
Parameters
----------
......@@ -212,6 +218,8 @@ class ConfigBase:
def _validate_canonical(self):
"""
To be overrided by subclass.
Validate legality of a canonical config object. It's caller's responsibility to ensure the config is canonical.
Raise exception if any problem found. This function does **not** return truth value.
......@@ -220,8 +228,6 @@ class ConfigBase:
1. Validate that all fields match their type hint
2. Call ``_validate_canonical()`` on children config objects, including those inside list and dict
Subclasses are recommended to to call ``super()._validate_canonical()``.
"""
utils.validate_type(self)
for field in dataclasses.fields(self):
......@@ -229,6 +235,10 @@ class ConfigBase:
_recursive_validate_child(value)
def __setattr__(self, name, value):
"""
To prevent typo, config classes forbid assigning to attribute that is not a config field,
unless it starts with underscore.
"""
if hasattr(self, name) or name.startswith('_'):
super().__setattr__(name, value)
return
......
......@@ -47,12 +47,16 @@ class ExperimentConfig(ConfigBase):
)
)
Fields commented as "training service field" acts like shortcut for all training services.
Users can either specify them here or inside training service config.
In latter case hybrid training services can have different settings.
.. _reference: https://nni.readthedocs.io/en/stable/reference/experiment_config.html
"""
# TODO:
# The behavior described below is expected but does not work,
# because some fields are consumed by TrialDispatcher outside environment service.
# Add the lines to docstr when we fix this issue.
# Fields commented as "training service field" acts like shortcut for all training services.
# Users can either specify them here or inside training service config.
# In latter case hybrid training services can have different settings.
experiment_name: Optional[str] = None
search_space_file: Optional[utils.PathLike] = None
......
......@@ -29,6 +29,10 @@ class RunMode(Enum):
- Background: stop NNI manager when Python script exits; do not print NNI manager log. (default)
- Foreground: stop NNI manager when Python script exits; print NNI manager log to stdout.
- Detach: do not stop NNI manager when Python script exits.
NOTE:
This API is non-stable and is likely to get refactored in next release.
NNI manager should treat log level more seriously so we can default to "foreground" without being too verbose.
"""
Background = 'background'
Foreground = 'foreground'
......
......@@ -91,6 +91,9 @@ def create_experiment(args):
run_mode = RunMode.Foreground if foreground else RunMode.Detach
exp.start(port, debug, run_mode)
_logger.info(f'To stop experiment run "nnictl stop {exp.id}" or "nnictl stop --all"')
_logger.info('Reference: https://nni.readthedocs.io/en/stable/Tutorial/Nnictl.html')
def resume_experiment(args):
exp_id = args.id
port = args.port
......
......@@ -145,7 +145,7 @@ def test_bad():
config.validate()
except Exception as e:
exc = e
assert isinstance(exc, ValueError), tag
assert exc is not None
if __name__ == '__main__':
test_good()
......
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