[tune] Change tune resource request syntax to be less confusing (#1764)

* update

* update examples

* Wed Mar 21 15:19:56 PDT 2018

* Wed Mar 21 15:21:32 PDT 2018

* Update train_a3c.py

* Update train.py

* fix resources accounting
This commit is contained in:
Eric Liang
2018-03-23 06:25:01 -07:00
committed by Philipp Moritz
parent 10dabce4d7
commit 72595cca0d
36 changed files with 134 additions and 101 deletions
+1 -1
View File
@@ -62,7 +62,7 @@ if __name__ == "__main__":
"run": args.run,
"checkpoint_freq": args.checkpoint_freq,
"local_dir": args.local_dir,
"resources": resources_to_json(args.resources),
"trial_resources": resources_to_json(args.trial_resources),
"stop": args.stop,
"config": dict(args.config, env=args.env),
"restore": args.restore,
@@ -4,9 +4,9 @@ cartpole-ppo:
stop:
episode_reward_mean: 200
time_total_s: 180
resources:
cpu: 3
driver_cpu_limit: 1
trial_resources:
cpu: 1
extra_cpu: 1
config:
num_workers: 2
num_sgd_iter:
@@ -1,9 +1,8 @@
hopper-ppo:
env: Hopper-v1
run: PPO
resources:
cpu: 65
trial_resources:
cpu: 1
gpu: 4
driver_cpu_limit: 1
driver_gpu_limit: 4
extra_cpu: 64
config: {"gamma": 0.995, "kl_coeff": 1.0, "num_sgd_iter": 20, "sgd_stepsize": .0001, "sgd_batchsize": 32768, "devices": ["/gpu:0", "/gpu:1", "/gpu:2", "/gpu:3"], "tf_session_args": {"device_count": {"GPU": 4}, "log_device_placement": false, "allow_soft_placement": true}, "timesteps_per_batch": 160000, "num_workers": 64}
@@ -1,9 +1,9 @@
humanoid-es:
env: Humanoid-v1
run: ES
resources:
cpu: 101
driver_cpu_limit: 1
trial_resources:
cpu: 1
extra_cpu: 100
stop:
episode_reward_mean: 6000
config:
@@ -3,9 +3,9 @@ humanoid-ppo-gae:
run: PPO
stop:
episode_reward_mean: 6000
resources:
cpu: 65
trial_resources:
cpu: 1
gpu: 4
driver_cpu_limit: 1
extra_cpu: 64
config: {"lambda": 0.95, "clip_param": 0.2, "kl_coeff": 1.0, "num_sgd_iter": 20, "sgd_stepsize": .0001, "sgd_batchsize": 32768, "horizon": 5000, "devices": ["/gpu:0", "/gpu:1", "/gpu:2", "/gpu:3"], "tf_session_args": {"device_count": {"GPU": 4}, "log_device_placement": false, "allow_soft_placement": true}, "timesteps_per_batch": 320000, "num_workers": 64, "model": {"free_log_std": true}, "write_logs": false}
@@ -3,8 +3,8 @@ humanoid-ppo:
run: PPO
stop:
episode_reward_mean: 6000
resources:
cpu: 65
trial_resources:
cpu: 1
gpu: 4
driver_cpu_limit: 1
extra_cpu: 64
config: {"kl_coeff": 1.0, "num_sgd_iter": 20, "sgd_stepsize": .0001, "sgd_batchsize": 32768, "devices": ["/gpu:0", "/gpu:1", "/gpu:2", "/gpu:3"], "tf_session_args": {"device_count": {"GPU": 4}, "log_device_placement": false, "allow_soft_placement": true}, "timesteps_per_batch": 320000, "num_workers": 64, "model": {"free_log_std": true}, "use_gae": false}
@@ -5,9 +5,9 @@ cartpole-ppo:
stop:
episode_reward_mean: 200
time_total_s: 180
resources:
cpu: 2
driver_cpu_limit: 1
trial_resources:
cpu: 1
extra_cpu: 1
config:
num_workers: 1
num_sgd_iter:
@@ -2,9 +2,9 @@
pendulum-ppo:
env: Pendulum-v0
run: PPO
resources:
cpu: 5
driver_cpu_limit: 1
trial_resources:
cpu: 1
extra_cpu: 4
config:
timesteps_per_batch: 2048
num_workers: 4
@@ -1,9 +1,9 @@
pong-a3c-pytorch-cnn:
env: PongDeterministic-v4
run: A3C
resources:
cpu: 17
driver_cpu_limit: 1
trial_resources:
cpu: 1
extra_cpu: 16
config:
num_workers: 16
batch_size: 20
@@ -1,9 +1,9 @@
pong-a3c:
env: PongDeterministic-v4
run: A3C
resources:
cpu: 17
driver_cpu_limit: 1
trial_resources:
cpu: 1
extra_cpu: 16
config:
num_workers: 16
batch_size: 20
@@ -4,11 +4,11 @@
pong-apex:
env: PongNoFrameskip-v4
run: APEX
resources:
cpu:
eval: 1 + spec.config.num_workers
driver_cpu_limit: 1
trial_resources:
cpu: 1
gpu: 1
extra_cpu:
eval: 4 + spec.config.num_workers
config:
target_network_update_freq: 50000
num_workers: 32
@@ -2,7 +2,7 @@
pong-deterministic-dqn:
env: PongDeterministic-v4
run: DQN
resources:
trial_resources:
cpu: 1
gpu: 1
stop:
@@ -8,10 +8,10 @@
pong-deterministic-ppo:
env: PongDeterministic-v4
run: PPO
resources:
cpu: 5
trial_resources:
cpu: 1
gpu: 1
driver_cpu_limit: 1
extra_cpu: 4
stop:
episode_reward_mean: 21
config:
@@ -4,7 +4,7 @@ cartpole-a3c:
stop:
episode_reward_mean: 200
time_total_s: 600
resources:
trial_resources:
cpu: 2
config:
num_workers: 4
@@ -4,7 +4,7 @@ cartpole-dqn:
stop:
episode_reward_mean: 200
time_total_s: 600
resources:
trial_resources:
cpu: 1
config:
n_step: 3
@@ -4,7 +4,7 @@ cartpole-es:
stop:
episode_reward_mean: 200
time_total_s: 300
resources:
trial_resources:
cpu: 2
config:
num_workers: 2
@@ -4,7 +4,7 @@ cartpole-ppo:
stop:
episode_reward_mean: 200
time_total_s: 300
resources:
trial_resources:
cpu: 1
config:
num_workers: 1
@@ -1,8 +1,8 @@
walker2d-v1-ppo:
env: Walker2d-v1
run: PPO
resources:
cpu: 65
trial_resources:
cpu: 1
gpu: 4
driver_cpu_limit: 1
extra_cpu: 64
config: {"kl_coeff": 1.0, "num_sgd_iter": 20, "sgd_stepsize": .0001, "sgd_batchsize": 32768, "devices": ["/gpu:0", "/gpu:1", "/gpu:2", "/gpu:3"], "tf_session_args": {"device_count": {"GPU": 4}, "log_device_placement": false, "allow_soft_placement": true}, "timesteps_per_batch": 320000, "num_workers": 64}
+19 -4
View File
@@ -15,24 +15,34 @@ def json_to_resources(data):
if type(data) is str:
data = json.loads(data)
for k in data:
if k in ["driver_cpu_limit", "driver_gpu_limit"]:
raise TuneError(
"The field `{}` is no longer supported. Use `extra_cpu` "
"or `extra_gpu` instead.".format(k))
if k not in Resources._fields:
raise TuneError(
"Unknown resource type {}, must be one of {}".format(
k, Resources._fields))
return Resources(
data.get("cpu", 1), data.get("gpu", 0),
data.get("driver_cpu_limit"), data.get("driver_gpu_limit"))
data.get("extra_cpu", 0), data.get("extra_gpu", 0))
def resources_to_json(resources):
if resources is None:
resources = Resources(cpu=1, gpu=0)
return {
"cpu": resources.cpu,
"gpu": resources.gpu,
"driver_cpu_limit": resources.driver_cpu_limit,
"driver_gpu_limit": resources.driver_gpu_limit,
"extra_cpu": resources.extra_cpu,
"extra_gpu": resources.extra_gpu,
}
def _tune_error(msg):
raise TuneError(msg)
def make_parser(**kwargs):
"""Returns a base argument parser for the ray.tune tool."""
@@ -56,7 +66,12 @@ def make_parser(**kwargs):
help="Algorithm-specific configuration (e.g. env, hyperparams), "
"specified in JSON.")
parser.add_argument(
"--resources", default='{"cpu": 1}', type=json_to_resources,
"--resources", help="Deprecated, use --trial-resources.",
type=lambda v: _tune_error(
"The `resources` argument is no longer supported. "
"Use `trial_resources` or --trial-resources instead."))
parser.add_argument(
"--trial-resources", default='{"cpu": 1}', type=json_to_resources,
help="Machine resources to allocate per trial, e.g. "
"'{\"cpu\": 64, \"gpu\": 8}'. Note that GPUs will not be assigned "
"unless you specify them here.")
@@ -68,7 +68,7 @@ if __name__ == "__main__":
"run": "my_class",
"stop": {"training_iteration": 1 if args.smoke_test else 99999},
"repeat": 20,
"resources": {"cpu": 1, "gpu": 0},
"trial_resources": {"cpu": 1, "gpu": 0},
"config": {
"width": lambda spec: 10 + int(90 * random.random()),
"height": lambda spec: int(100 * random.random()),
+1 -1
View File
@@ -79,7 +79,7 @@ if __name__ == "__main__":
"run": "my_class",
"stop": {"training_iteration": 2 if args.smoke_test else 99999},
"repeat": 10,
"resources": {"cpu": 1, "gpu": 0},
"trial_resources": {"cpu": 1, "gpu": 0},
"config": {
"factor_1": 4.0,
"factor_2": 1.0,
+1 -1
View File
@@ -50,7 +50,7 @@ if __name__ == "__main__":
"run": "PPO",
"env": "Humanoid-v1",
"repeat": 8,
"resources": {"cpu": 4, "gpu": 1},
"trial_resources": {"cpu": 4, "gpu": 1},
"config": {
"kl_coeff": 1.0,
"num_workers": 8,
+3 -3
View File
@@ -20,7 +20,7 @@ class Experiment(object):
empty dict.
config (dict): Algorithm-specific configuration
(e.g. env, hyperparams). Defaults to empty dict.
resources (dict): Machine resources to allocate per trial,
trial_resources (dict): Machine resources to allocate per trial,
e.g. ``{"cpu": 64, "gpu": 8}``. Note that GPUs will not be
assigned unless you specify them here. Defaults to 1 CPU and 0
GPUs.
@@ -36,13 +36,13 @@ class Experiment(object):
checkpointing is enabled. Defaults to 3.
"""
def __init__(self, name, run, stop=None, config=None,
resources=None, repeat=1, local_dir=None,
trial_resources=None, repeat=1, local_dir=None,
upload_dir="", checkpoint_freq=0, max_failures=3):
spec = {
"run": run,
"stop": stop or {},
"config": config or {},
"resources": resources or {"cpu": 1, "gpu": 0},
"trial_resources": trial_resources or {"cpu": 1, "gpu": 0},
"repeat": repeat,
"local_dir": local_dir or DEFAULT_RESULTS_DIR,
"upload_dir": upload_dir,
+22 -1
View File
@@ -130,7 +130,7 @@ class TrainableFunctionApiTest(unittest.TestCase):
def f():
run_experiments({"foo": {
"run": "PPO",
"resources": {"asdf": 1}
"trial_resources": {"asdf": 1}
}})
self.assertRaises(TuneError, f)
@@ -453,6 +453,27 @@ class TrialRunnerTest(unittest.TestCase):
except Exception as e:
self.assertIn("a class", str(e))
def testExtraResources(self):
ray.init(num_cpus=4, num_gpus=2)
runner = TrialRunner()
kwargs = {
"stopping_criterion": {"training_iteration": 1},
"resources": Resources(cpu=1, gpu=0, extra_cpu=3, extra_gpu=1),
}
trials = [
Trial("__fake", **kwargs),
Trial("__fake", **kwargs)]
for t in trials:
runner.add_trial(t)
runner.step()
self.assertEqual(trials[0].status, Trial.RUNNING)
self.assertEqual(trials[1].status, Trial.PENDING)
runner.step()
self.assertEqual(trials[0].status, Trial.TERMINATED)
self.assertEqual(trials[1].status, Trial.PENDING)
def testResourceScheduler(self):
ray.init(num_cpus=4, num_gpus=1)
runner = TrialRunner()
+1 -1
View File
@@ -62,7 +62,7 @@ class TuneServerSuite(unittest.TestCase):
spec = {
"run": "__fake",
"stop": {"training_iteration": 3},
"resources": dict(cpu=1, gpu=1),
"trial_resources": dict(cpu=1, gpu=1),
}
client.add_trial("test", spec)
runner.step()
+20 -25
View File
@@ -28,34 +28,32 @@ def date_str():
class Resources(
namedtuple("Resources", [
"cpu", "gpu", "driver_cpu_limit", "driver_gpu_limit"])):
namedtuple("Resources", ["cpu", "gpu", "extra_cpu", "extra_gpu"])):
"""Ray resources required to schedule a trial.
Attributes:
cpu (int): Number of CPUs required for the trial total.
gpu (int): Number of GPUs required for the trial total.
driver_cpu_limit (int): Max CPUs allocated to the driver.
Defaults to all of the required CPUs.
driver_gpu_limit (int): Max GPUs allocated to the driver.
Defaults to all of the required GPUs.
cpu (int): Number of CPUs to allocate to the trial.
gpu (int): Number of GPUs to allocate to the trial.
extra_cpu (int): Extra CPUs to reserve in case the trial needs to
launch additional Ray actors that use CPUs.
extra_gpu (int): Extra GPUs to reserve in case the trial needs to
launch additional Ray actors that use GPUs.
"""
__slots__ = ()
def __new__(cls, cpu, gpu, driver_cpu_limit=None, driver_gpu_limit=None):
if driver_cpu_limit is not None:
assert driver_cpu_limit <= cpu
else:
driver_cpu_limit = cpu
if driver_gpu_limit is not None:
assert driver_gpu_limit <= gpu
else:
driver_gpu_limit = gpu
def __new__(cls, cpu, gpu, extra_cpu=0, extra_gpu=0):
return super(Resources, cls).__new__(
cls, cpu, gpu, driver_cpu_limit, driver_gpu_limit)
cls, cpu, gpu, extra_cpu, extra_gpu)
def summary_string(self):
return "{} CPUs, {} GPUs".format(self.cpu, self.gpu)
return "{} CPUs, {} GPUs".format(
self.cpu + self.extra_cpu, self.gpu + self.extra_gpu)
def cpu_total(self):
return self.cpu + self.extra_cpu
def gpu_total(self):
return self.gpu + self.extra_gpu
class Trial(object):
@@ -66,9 +64,6 @@ class Trial(object):
Trials start in the PENDING state, and transition to RUNNING once started.
On error it transitions to ERROR, otherwise TERMINATED on success.
The driver for the trial will be allocated at most `driver_cpu_limit` and
`driver_gpu_limit` CPUs and GPUs.
"""
PENDING = "PENDING"
@@ -79,7 +74,7 @@ class Trial(object):
def __init__(
self, trainable_name, config=None, local_dir=DEFAULT_RESULTS_DIR,
experiment_tag=None, resources=Resources(cpu=1, gpu=0),
experiment_tag="", resources=Resources(cpu=1, gpu=0),
stopping_criterion=None, checkpoint_freq=0,
restore_path=None, upload_dir=None, max_failures=0):
"""Initialize a new trial.
@@ -347,8 +342,8 @@ class Trial(object):
trainable_cls = ray.tune.registry.get_registry().get(
ray.tune.registry.TRAINABLE_CLASS, self.trainable_name)
cls = ray.remote(
num_cpus=self.resources.driver_cpu_limit,
num_gpus=self.resources.driver_gpu_limit)(trainable_cls)
num_cpus=self.resources.cpu,
num_gpus=self.resources.gpu)(trainable_cls)
if not self.result_logger:
if not os.path.exists(self.local_dir):
os.makedirs(self.local_dir)
+7 -5
View File
@@ -181,7 +181,9 @@ class TrialRunner(object):
cpu_avail = self._avail_resources.cpu - self._committed_resources.cpu
gpu_avail = self._avail_resources.gpu - self._committed_resources.gpu
return resources.cpu <= cpu_avail and resources.gpu <= gpu_avail
return (
resources.cpu_total() <= cpu_avail and
resources.gpu_total() <= gpu_avail)
def _can_launch_more(self):
self._update_avail_resources()
@@ -265,13 +267,13 @@ class TrialRunner(object):
def _commit_resources(self, resources):
self._committed_resources = Resources(
self._committed_resources.cpu + resources.cpu,
self._committed_resources.gpu + resources.gpu)
self._committed_resources.cpu + resources.cpu_total(),
self._committed_resources.gpu + resources.gpu_total())
def _return_resources(self, resources):
self._committed_resources = Resources(
self._committed_resources.cpu - resources.cpu,
self._committed_resources.gpu - resources.gpu)
self._committed_resources.cpu - resources.cpu_total(),
self._committed_resources.gpu - resources.gpu_total())
assert self._committed_resources.cpu >= 0
assert self._committed_resources.gpu >= 0
+2 -2
View File
@@ -58,7 +58,7 @@ def generate_trials(unresolved_spec, output_path=''):
config=spec.get("config", {}),
local_dir=os.path.join(args.local_dir, output_path),
experiment_tag=experiment_tag,
resources=json_to_resources(spec.get("resources", {})),
resources=json_to_resources(spec.get("trial_resources", {})),
stopping_criterion=spec.get("stop", {}),
checkpoint_freq=args.checkpoint_freq,
restore_path=spec.get("restore"),
@@ -118,7 +118,7 @@ _MAX_RESOLUTION_PASSES = 20
def _format_vars(resolved_vars):
out = []
for path, value in sorted(resolved_vars.items()):
if path[0] in ["run", "env", "resources"]:
if path[0] in ["run", "env", "trial_resources"]:
continue # TrialRunner already has these in the experiment_tag
pieces = []
last_string = True