Skip to content

Commit 29f1e5e

Browse files
Kiuk Chungfacebook-github-bot
authored andcommitted
(torchx/config) remove profiles from .torchxconfig, remove hierarchical loading, and move config loading to cli from runner (#260)
Summary: Pull Request resolved: #260 1. Removes profiles from .torchxconfig (also removes .cfg suffix from section) 2. Removes hierarchical loading (only picks up .torchxconfig from CWD - project dir) 3. Removes config application from runner and moves it to CLI only Reviewed By: d4l3k Differential Revision: D31674537 fbshipit-source-id: 937c3375771316b2bf2f1d65a560d7311031d4fa
1 parent 19925a0 commit 29f1e5e

File tree

4 files changed

+78
-67
lines changed

4 files changed

+78
-67
lines changed

torchx/cli/cmd_run.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import torchx.specs as specs
1616
from pyre_extensions import none_throws
1717
from torchx.cli.cmd_base import SubCommand
18-
from torchx.runner import Runner, get_runner
18+
from torchx.runner import Runner, config, get_runner
1919
from torchx.schedulers import get_default_scheduler_name, get_scheduler_factories
2020
from torchx.specs.finder import (
2121
ComponentNotFoundException,
@@ -53,6 +53,7 @@ def _parse_run_config(arg: str, scheduler_opts: specs.runopts) -> specs.RunConfi
5353
option_type = option.opt_type
5454
typed_value = _convert_to_option_type(value, option_type)
5555
conf.set(key, typed_value)
56+
5657
return conf
5758

5859

@@ -114,7 +115,9 @@ def add_arguments(self, subparser: argparse.ArgumentParser) -> None:
114115
def _run(self, runner: Runner, args: argparse.Namespace) -> Optional[str]:
115116
run_opts = get_runner().run_opts()
116117
scheduler_opts = run_opts[args.scheduler]
117-
scheduler_args = _parse_run_config(args.scheduler_args, scheduler_opts)
118+
cfg = _parse_run_config(args.scheduler_args, scheduler_opts)
119+
config.apply(scheduler=args.scheduler, cfg=cfg)
120+
118121
if len(args.conf_args) < 1:
119122
none_throws(self._subparser).error(
120123
"the following arguments are required: conf_file, conf_args"
@@ -129,7 +132,7 @@ def _run(self, runner: Runner, args: argparse.Namespace) -> Optional[str]:
129132
conf_file,
130133
conf_args,
131134
args.scheduler,
132-
scheduler_args,
135+
cfg,
133136
dryrun=args.dryrun,
134137
)
135138
except (ComponentValidationException, ComponentNotFoundException) as e:

torchx/runner/api.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
from typing import Any, Dict, Iterable, List, Optional, Tuple, Type, Union
1414

1515
from pyre_extensions import none_throws
16-
from torchx.runner import config
1716
from torchx.runner.events import log_event
1817
from torchx.schedulers import get_schedulers
1918
from torchx.schedulers.api import Scheduler
@@ -262,9 +261,6 @@ def dryrun(
262261
)
263262

264263
cfg = cfg or RunConfig()
265-
# TODO enable profiles - https://github.com/pytorch/torchx/issues/248
266-
config.apply(scheduler=scheduler, cfg=cfg, profile="default")
267-
268264
sched = self._scheduler(scheduler)
269265
sched._validate(app, scheduler)
270266
dryrun_info = sched.submit_dryrun(app, cfg)

torchx/runner/config.py

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def dump(
7272
7373
::
7474
75-
[default.kubernetes.cfg]
75+
[kubernetes]
7676
namespace = default
7777
queue = #FIXME (str)Volcano queue to schedule job in
7878
@@ -89,7 +89,7 @@ def dump(
8989
for sched_name in scheds:
9090
sched = _get_scheduler(sched_name)
9191

92-
section = f"default.{sched_name}.cfg"
92+
section = f"{sched_name}"
9393
config.add_section(section)
9494

9595
for opt_name, opt in sched.run_opts():
@@ -114,33 +114,51 @@ def dump(
114114
config.write(f, space_around_delimiters=True)
115115

116116

117-
def apply(scheduler: str, cfg: RunConfig, profile: str = "default") -> None:
117+
def apply(scheduler: str, cfg: RunConfig, dirs: Optional[List[str]] = None) -> None:
118118
"""
119-
Loads .torchxconfig files from predefined locations according
120-
to a load hierarchy and applies the loaded configs into the
121-
given ``runcfg``. The load hierarchy is as follows (in order of precedence):
119+
Loads a ``.torchxconfig`` INI file from the specified directories in
120+
preceding order and applies the run configs for the scheduler onto
121+
the given ``cfg``.
122122
123-
#. ``runcfg`` given to this function
124-
#. configs loaded from ``$HOME/.torchxconfig``
125-
#. configs loaded from ``$CWD/.torchxconfig``
123+
If no ``dirs`` is specified, then it looks for ``.torchxconfig`` in the
124+
current working directory. If a specified directory does not have ``.torchxconfig``
125+
then it is ignored.
126126
127-
Note that load hierarchy does NOT overwrite, but rather adds.
128-
That is, the configs already present in ``runcfg`` are not
129-
overridden during the load.
127+
Note that the configs already present in the given ``cfg`` take precedence
128+
over the ones in the config file and only new configs are added. The same holds
129+
true for the configs loaded in list order.
130+
131+
For instance if ``cfg = {"foo": "bar"}`` and the config file is:
132+
133+
::
134+
135+
# dir_1/.torchxconfig
136+
[local_cwd]
137+
foo = baz
138+
hello = world
139+
140+
# dir_2/.torchxconfig
141+
[local_cwd]
142+
hello = bob
143+
144+
145+
Then after the method call, ``cfg = {"foo": "bar", "hello": "world"}``.
130146
"""
131-
lookup_dirs = [Path.home(), Path.cwd()]
132147

133-
for d in lookup_dirs:
134-
configfile = d / ".torchxconfig"
148+
if not dirs:
149+
dirs = [str(Path.cwd())]
150+
151+
for d in dirs:
152+
configfile = Path(d) / ".torchxconfig"
135153
if configfile.exists():
136154
log.info(f"loading configs from {configfile}")
137155
with open(str(configfile), "r") as f:
138-
load(scheduler, f, cfg, profile)
156+
load(scheduler, f, cfg)
139157

140158

141-
def load(scheduler: str, f: TextIO, cfg: RunConfig, profile: str = "default") -> None:
159+
def load(scheduler: str, f: TextIO, cfg: RunConfig) -> None:
142160
"""
143-
loads the section ``[{profile}.scheduler_args.{scheduler}]`` from the given
161+
loads the section ``[{scheduler}]`` from the given
144162
configfile ``f`` (in .INI format) into the provided ``runcfg``, only adding
145163
configs that are NOT currently in the given ``runcfg`` (e.g. does not
146164
override existing values in ``runcfg``). If no section is found, does nothing.
@@ -151,7 +169,7 @@ def load(scheduler: str, f: TextIO, cfg: RunConfig, profile: str = "default") ->
151169

152170
runopts = _get_scheduler(scheduler).run_opts()
153171

154-
section = f"{profile}.{scheduler}.cfg"
172+
section = f"{scheduler}"
155173
if config.has_section(section):
156174
for name, value in config.items(section):
157175
if name in cfg.cfgs:

torchx/runner/test/config_test.py

Lines changed: 35 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -102,35 +102,27 @@ def run_opts(self) -> runopts:
102102
return opts
103103

104104

105-
_CONFIG = """[default.local_cwd.cfg]
105+
_CONFIG = """[local_cwd]
106106
log_dir = /home/bob/logs
107107
prepend_cwd = True
108-
109-
[test.local_cwd.cfg]
110-
log_dir = None
111-
prepend_cwd = False
112-
113-
[alpha.local_cwd.cfg]
114-
log_dir = /tmp/logs
115108
"""
116109

117-
_CONFIG_INVALID = """[default.test.cfg]
110+
_CONFIG_INVALID = """[test]
118111
a_run_opt_that = does_not_exist
119112
s = option_that_exists
120113
"""
121114

122-
_TEAM_CONFIG = """[default.test.cfg]
115+
_TEAM_CONFIG = """[test]
123116
s = team_default
124117
i = 50
125118
f = 1.2
126119
"""
127120

128-
_MY_CONFIG = """[default.test.cfg]
121+
_MY_CONFIG = """[test]
129122
s = my_default
130123
i = 100
131124
"""
132125

133-
PATH_HOME = "torchx.runner.config.Path.home"
134126
PATH_CWD = "torchx.runner.config.Path.cwd"
135127
TORCHX_GET_SCHEDULERS = "torchx.runner.config.get_schedulers"
136128

@@ -159,45 +151,50 @@ def _write(self, filename: str, content: str) -> Path:
159151

160152
def test_load(self) -> None:
161153
cfg = RunConfig()
162-
load(profile="default", scheduler="local_cwd", f=StringIO(_CONFIG), cfg=cfg)
154+
load(scheduler="local_cwd", f=StringIO(_CONFIG), cfg=cfg)
163155
self.assertEqual("/home/bob/logs", cfg.get("log_dir"))
164156
self.assertEqual(True, cfg.get("prepend_cwd"))
165157

166-
cfg = RunConfig()
167-
load(profile="test", scheduler="local_cwd", f=StringIO(_CONFIG), cfg=cfg)
168-
self.assertEqual(None, cfg.get("log_dir"))
169-
self.assertEqual(False, cfg.get("prepend_cwd"))
170-
171-
cfg = RunConfig()
172-
load(profile="alpha", scheduler="local_cwd", f=StringIO(_CONFIG), cfg=cfg)
173-
self.assertEqual("/tmp/logs", cfg.get("log_dir"))
174-
self.assertEqual(None, cfg.get("prepend_cwd"))
175-
176158
def test_no_override_load(self) -> None:
177159
cfg = RunConfig()
178160
cfg.set("log_dir", "/foo/bar")
179161
cfg.set("debug", 1)
180162

181-
load(profile="test", scheduler="local_cwd", f=StringIO(_CONFIG), cfg=cfg)
163+
load(scheduler="local_cwd", f=StringIO(_CONFIG), cfg=cfg)
182164
self.assertEqual("/foo/bar", cfg.get("log_dir"))
183165
self.assertEqual(1, cfg.get("debug"))
184-
self.assertEqual(False, cfg.get("prepend_cwd"))
166+
self.assertEqual(True, cfg.get("prepend_cwd"))
185167

186168
@patch(
187169
TORCHX_GET_SCHEDULERS,
188170
return_value={"test": TestScheduler()},
189171
)
190-
def test_apply(self, _) -> None:
172+
def test_apply_default(self, _) -> None:
191173
with patch(PATH_CWD, return_value=Path(self.test_dir)):
192-
with patch(PATH_HOME, return_value=Path(self.test_dir) / "home" / "bob"):
193-
cfg = RunConfig()
194-
cfg.set("s", "runtime_value")
174+
cfg = RunConfig()
175+
cfg.set("s", "runtime_value")
176+
177+
apply(scheduler="test", cfg=cfg)
195178

196-
apply(profile="default", scheduler="test", cfg=cfg)
179+
self.assertEqual("runtime_value", cfg.get("s"))
180+
self.assertEqual(50, cfg.get("i"))
181+
self.assertEqual(1.2, cfg.get("f"))
197182

198-
self.assertEqual("runtime_value", cfg.get("s"))
199-
self.assertEqual(100, cfg.get("i"))
200-
self.assertEqual(1.2, cfg.get("f"))
183+
@patch(
184+
TORCHX_GET_SCHEDULERS,
185+
return_value={"test": TestScheduler()},
186+
)
187+
def test_apply_dirs(self, _) -> None:
188+
cfg = RunConfig()
189+
cfg.set("s", "runtime_value")
190+
apply(
191+
scheduler="test",
192+
cfg=cfg,
193+
dirs=[str(Path(self.test_dir) / "home" / "bob"), self.test_dir],
194+
)
195+
self.assertEqual("runtime_value", cfg.get("s"))
196+
self.assertEqual(100, cfg.get("i"))
197+
self.assertEqual(1.2, cfg.get("f"))
201198

202199
def test_dump_invalid_scheduler(self) -> None:
203200
with self.assertRaises(ValueError):
@@ -215,7 +212,7 @@ def test_dump_only_required(self, _) -> None:
215212

216213
cfg = RunConfig()
217214
sfile.seek(0)
218-
load(profile="default", scheduler="test", f=sfile, cfg=cfg)
215+
load(scheduler="test", f=sfile, cfg=cfg)
219216

220217
self.assertFalse(cfg.cfgs)
221218

@@ -226,7 +223,6 @@ def test_dump_only_required(self, _) -> None:
226223
def test_load_invalid_runopt(self, _) -> None:
227224
cfg = RunConfig()
228225
load(
229-
profile="default",
230226
scheduler="test",
231227
f=StringIO(_CONFIG_INVALID),
232228
cfg=cfg,
@@ -241,7 +237,6 @@ def test_load_invalid_runopt(self, _) -> None:
241237
def test_load_no_section(self) -> None:
242238
cfg = RunConfig()
243239
load(
244-
profile="default",
245240
scheduler="local_cwd",
246241
f=StringIO(),
247242
cfg=cfg,
@@ -250,9 +245,8 @@ def test_load_no_section(self) -> None:
250245
self.assertFalse(cfg.cfgs)
251246

252247
load(
253-
profile="default",
254248
scheduler="local_cwd",
255-
f=StringIO("[default.scheduler_args.local_cwd]\n"),
249+
f=StringIO("[scheduler_args.local_cwd]\n"),
256250
cfg=cfg,
257251
)
258252
# still empty
@@ -269,7 +263,7 @@ def test_dump_and_load_all_runopt_types(self, _) -> None:
269263
sfile.seek(0)
270264

271265
cfg = RunConfig()
272-
load(profile="default", scheduler="test", f=sfile, cfg=cfg)
266+
load(scheduler="test", f=sfile, cfg=cfg)
273267

274268
# all runopts in the TestScheduler have defaults, just check against those
275269
for opt_name, opt in TestScheduler().run_opts():
@@ -282,11 +276,11 @@ def test_dump_and_load_all_registered_schedulers(self) -> None:
282276

283277
sfile = StringIO()
284278
dump(sfile)
285-
print(sfile.getvalue())
279+
286280
for sched_name, sched in get_schedulers(session_name="_").items():
287281
sfile.seek(0) # reset the file pos
288282
cfg = RunConfig()
289-
load(profile="default", scheduler=sched_name, f=sfile, cfg=cfg)
283+
load(scheduler=sched_name, f=sfile, cfg=cfg)
290284

291285
for opt_name, _ in sched.run_opts():
292286
self.assertTrue(opt_name in cfg.cfgs)

0 commit comments

Comments
 (0)