Skip to content

Commit ae3efb5

Browse files
authored
fix estimator train_and_evaluate in multi-worker multi-gpu setting (#23)
1 parent 4178818 commit ae3efb5

6 files changed

Lines changed: 164 additions & 9 deletions

File tree

epl/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
from __future__ import division
1919
from __future__ import print_function
2020

21+
from tensorflow.python.eager.context import executing_eagerly
22+
2123
from epl.cluster import Cluster
2224
from epl.env import Env
2325
from epl.config import Config
@@ -35,6 +37,9 @@
3537

3638
def init(config=None):
3739
"""Init EPL."""
40+
if executing_eagerly():
41+
raise RuntimeError("Tensorflow eager mode is not supported by EPL now, " + \
42+
"please do not call tf.enable_eager_execution()")
3843
env = Env.get()
3944
env.reset()
4045
env.init(config)

epl/parallel/hooks.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from tensorflow.python.estimator import estimator
4040
from tensorflow.python.estimator import run_config
4141
from tensorflow.python.estimator import training
42+
from tensorflow.python.estimator.model_fn import ModeKeys
4243
from tensorflow.python.framework import constant_op
4344
from tensorflow.python.framework import device as pydev
4445
from tensorflow.python.framework import dtypes
@@ -913,17 +914,23 @@ def hook_call_model_fn(self, *args, **kwargs):
913914

914915
def _sync_signal():
915916
"""Sync a tensor among constructor workers."""
916-
all_devices = Env.get().cluster.virtual_devices[0].all_devices
917-
all_devices = [device for device in all_devices if 'GPU:0' in device]
917+
vd = Env.get().cluster.virtual_devices[0]
918+
all_devices = vd.all_devices
919+
local_devices = vd.local_devices
918920
if len(all_devices) <= 1:
919921
return
922+
920923
with ops.Graph().as_default():
921-
signal = constant_op.constant([1])
922-
with ops.device(Env.get().cluster.current_worker_chief_gpu()):
923-
comm = create_serial_communicator(name="BROADCAST_SIGNAL", devices=all_devices)
924-
sync_tensor = comm.broadcast([signal])
925-
with monitored_session.MonitoredTrainingSession() as sess:
926-
sess.run(sync_tensor)
924+
sync_ops = []
925+
comm = create_serial_communicator(name="BROADCAST_SIGNAL", devices=all_devices)
926+
for local_device in local_devices:
927+
with ops.device(local_device):
928+
signal = constant_op.constant([1])
929+
Graph.get().set_model_mode(ModeKeys.EVAL)
930+
sync_tensor = comm.broadcast([signal])
931+
sync_ops.append(sync_tensor)
932+
with session.Session() as sess:
933+
sess.run(sync_ops)
927934

928935

929936
def estimator_training_evaluate_hook():

epl/utils/launcher.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ def get_run_script(args):
152152
script += "export GPU_STATUS_FILE={}/GPU_STATUS_{}.json\n".format(task_dir, index)
153153
script += get_tf_config(args.num_workers, index, ports, args.machine_list)
154154
script += ' CUDA_VISIBLE_DEVICES=' + get_visible_devices(args.gpu_per_worker, offset)
155-
script += ' bash {}'.format(args.training_script) + ' ' + task_dir
155+
cmd = 'python' if args.training_script.endswith('.py') else 'bash'
156+
script += ' {} {}'.format(cmd, args.training_script) + ' ' + task_dir
156157
if args.debug and index == 0:
157158
print("Enable debug mode")
158159
else:

tests/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ test:
1111
CUDA_VISIBLE_DEVICES='' ./launch.sh header_test.py
1212
PYTHONPATH=../ $(PYTHON) -m epl.utils.launcher --num_workers=2 --gpu_per_worker=1 --debug=True test_launcher.sh
1313
PYTHONPATH=../ $(PYTHON) -m epl.utils.launcher --num_workers=2 --gpu_per_worker=1 --debug=True test_amp_parallel.sh
14+
CUDA_VISIBLE_DEVICES=$(GPU_1) ./launch.sh eager_test.py
1415
CUDA_VISIBLE_DEVICES=$(GPU_4) ./launch.sh strategy_new_test.py
1516
CUDA_VISIBLE_DEVICES=$(GPU_4) ./launch.sh auto_cluster_test.py
1617
CUDA_VISIBLE_DEVICES=$(GPU_2) ./launch.sh estimator_test.py

tests/eager_test.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Copyright 2021 Alibaba Group Holding Limited. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
# =============================================================================
15+
"""Eager mode test."""
16+
17+
from __future__ import absolute_import
18+
from __future__ import division
19+
from __future__ import print_function
20+
21+
22+
import tensorflow as tf
23+
from tensorflow.python.platform import test
24+
25+
import epl
26+
27+
28+
# pylint: disable=missing-docstring,unused-variable
29+
# pylint: disable=protected-access
30+
class EagerTest(test.TestCase):
31+
def test_eager_error(self):
32+
assert tf.executing_eagerly() is True
33+
with self.assertRaises(RuntimeError) as ctx:
34+
epl.init()
35+
self.assertTrue("Tensorflow eager mode is not supported by EPL now" in str(ctx.exception))
36+
37+
38+
# pylint: enable=missing-docstring,unused-variable
39+
# pylint: enable=protected-access
40+
41+
if __name__ == "__main__":
42+
tf.enable_eager_execution()
43+
test.main()

tests/estimator_dp_example.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Copyright 2021 Alibaba Group Holding Limited. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
# =============================================================================
15+
"""Test for estimator API."""
16+
17+
from __future__ import absolute_import
18+
from __future__ import division
19+
from __future__ import print_function
20+
21+
import os
22+
import shutil
23+
import tempfile
24+
import numpy as np
25+
26+
import tensorflow as tf
27+
28+
import epl
29+
from test_utils import fix_randomness
30+
31+
32+
# pylint: disable=missing-docstring,unused-variable
33+
# pylint: disable=protected-access
34+
fix_randomness()
35+
def model_fn(features, labels, mode):
36+
"""Model function."""
37+
with tf.variable_scope('lr_softmax'):
38+
weights = tf.get_variable('weights', initializer=tf.zeros([78, 10]))
39+
biases = tf.get_variable('biases', initializer=tf.zeros([10]))
40+
logits = tf.matmul(features, weights) + biases
41+
global_step = tf.train.get_or_create_global_step()
42+
loss = tf.losses.sparse_softmax_cross_entropy(labels=labels,
43+
logits=logits,
44+
weights=1.0)
45+
if mode == tf.estimator.ModeKeys.TRAIN:
46+
opt = tf.train.AdamOptimizer(0.1, name='adam')
47+
train_op = opt.minimize(loss, global_step=global_step, name='train')
48+
if epl.env.Env.get().cluster:
49+
epl.add_to_collection(loss, epl.GraphKeys.GLOBAL_MEAN_OBJECTS)
50+
return tf.estimator.EstimatorSpec(
51+
mode=mode,
52+
loss=loss,
53+
train_op=train_op)
54+
elif mode == tf.estimator.ModeKeys.EVAL:
55+
return tf.estimator.EstimatorSpec(
56+
mode=mode,
57+
loss=loss)
58+
else:
59+
raise ValueError(
60+
"Only TRAIN and EVAL modes are supported: %s" % (mode))
61+
62+
63+
def model_fn_replicate(features, labels, mode):
64+
with epl.replicate(device_count=1):
65+
return model_fn(features, labels, mode)
66+
67+
def input_fn(batch_size=2, estimator=True):
68+
"""input function."""
69+
num_x = np.random.randint(0, 10, (200, 78)).astype(dtype=np.float32)
70+
num_y = np.random.randint(0, 10, 200).astype(dtype=np.int32)
71+
dataset = tf.data.Dataset.from_tensor_slices((num_x, num_y)) \
72+
.batch(batch_size).repeat(10)
73+
if not estimator:
74+
iterator = dataset.make_initializable_iterator()
75+
tf.add_to_collection(tf.GraphKeys.TABLE_INITIALIZERS, iterator.initializer)
76+
return dataset
77+
78+
def _main():
79+
config = epl.Config()
80+
epl.init(config)
81+
model_dir = tempfile.mkdtemp()
82+
model_dir = os.path.join(model_dir, str(epl.Env.get().cluster.worker_index))
83+
run_config = tf.estimator.RunConfig(save_checkpoints_steps=5,
84+
model_dir=model_dir)
85+
estimator = tf.estimator.Estimator(model_fn=model_fn_replicate, config=run_config)
86+
hooks = []
87+
hooks.append(tf.train.StepCounterHook(every_n_steps=1))
88+
train_spec = tf.estimator.TrainSpec(input_fn=input_fn, max_steps=30,
89+
hooks=hooks)
90+
eval_spec = tf.estimator.EvalSpec(input_fn=input_fn, steps=10)
91+
tf.estimator.train_and_evaluate(estimator, train_spec, eval_spec)
92+
shutil.rmtree(model_dir)
93+
# pylint: enable=missing-docstring,unused-variable
94+
# pylint: enable=protected-access
95+
96+
if __name__ == "__main__":
97+
tf.logging.set_verbosity(tf.logging.INFO)
98+
_main()

0 commit comments

Comments
 (0)