FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Generalize the input to TPU distribution strategy. Add cross-shard-r… · codeplaysoftware/tensorflow@f14123d · GitHub

Repository navigation

Commit f14123d

Browse files
authored andcommitted
Generalize the input to TPU distribution strategy. Add cross-shard-replica sum.
TPUStrategy passes tests in minimize_loss_test. That caused me to add a capability to have `iterations x cores` inputs of any structure. I also resolved a big number of small issues and uncovered more things to resolve that are documented as todos. PiperOrigin-RevId: 195696833
1 parent eb5ee79 commit f14123d

6 files changed

Lines changed: 158 additions & 52 deletions

File tree

‎tensorflow/contrib/distribute/python/combinations.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,10 @@ def required_tpu(self):
265265
one_device_strategy = NamedDistribution(
266266
"OneDeviceCPU", one_device_strategy.OneDeviceStrategy("/cpu:0"),
267267
None)
268+
tpu_strategy_single_iteration = NamedDistribution(
269+
"TPUSingleIteration",
270+
tpu_strategy.TPUStrategy(iterations_per_step=1),
271+
required_tpu=True)
268272
tpu_strategy = NamedDistribution(
269273
"TPU", tpu_strategy.TPUStrategy(), required_tpu=True)
270274
mirrored_strategy_with_gpu_and_cpu = NamedDistribution(

‎tensorflow/contrib/distribute/python/minimize_loss_test.py‎

Lines changed: 80 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,16 @@ class MinimizeLossStepTest(test.TestCase, parameterized.TestCase):
4444
combinations.distributions_and_v1_optimizers(),
4545
combinations.combine(mode=["graph"], use_callable_loss=[True, False])
4646
+ combinations.combine(mode=["eager"], use_callable_loss=[True]),
47-
combinations.combine(is_tpu=[False])) +
48-
combinations.combine(
49-
distribution=[combinations.tpu_strategy],
50-
optimizer_fn=[combinations.adam_optimizer_v1_fn],
51-
mode=["graph"],
52-
use_callable_loss=[False],
53-
is_tpu=[True]))
47+
combinations.combine(is_tpu=[False])) + combinations.combine(
48+
distribution=[combinations.tpu_strategy],
49+
optimizer_fn=[
50+
combinations.adam_optimizer_v1_fn,
51+
# TODO(isaprykin): Make Adam v2 work with while_loops
52+
# and TPUs.
53+
],
54+
mode=["graph"],
55+
use_callable_loss=[False],
56+
is_tpu=[True]))
5457
def testTrainNetwork(self, distribution, optimizer_fn, use_callable_loss,
5558
is_tpu):
5659
with distribution.scope():
@@ -101,7 +104,8 @@ def run_step():
101104
distribution=[combinations.tpu_strategy],
102105
optimizer_fn=[
103106
combinations.adam_optimizer_v1_fn,
104-
combinations.gradient_descent_optimizer_v1_fn
107+
combinations.gradient_descent_optimizer_v1_fn,
108+
combinations.gradient_descent_optimizer_v2_fn,
105109
],
106110
mode=["graph"],
107111
is_tpu=[True]))
@@ -171,21 +175,37 @@ def get_expected_variables(optimizer_fn, num_parameter_devices):
171175
set(created_variables))
172176

173177
@combinations.generate(
174-
combinations.times(combinations.distributions_and_v1_optimizers(),
175-
combinations.combine(
176-
mode=["graph", "eager"],
177-
momentum=[0.8, 0.9, 0.99],
178-
renorm=[False, True])))
178+
combinations.times(
179+
combinations.combine(momentum=[0.8, 0.9, 0.99], renorm=[False, True]),
180+
combinations.times(
181+
combinations.distributions_and_v1_optimizers(),
182+
combinations.combine(
183+
mode=["graph", "eager"],
184+
is_tpu=[False],
185+
# TODO(isaprykin): Allow False here. Currently subsequent
186+
# towers will re-execute UPDATE_OPS of previous towers.
187+
update_ops_in_cross_tower_mode=[True])) +
188+
combinations.combine(
189+
distribution=[combinations.tpu_strategy_single_iteration],
190+
optimizer_fn=[
191+
combinations.gradient_descent_optimizer_v1_fn,
192+
combinations.gradient_descent_optimizer_v2_fn
193+
],
194+
mode=["graph"],
195+
is_tpu=[True],
196+
update_ops_in_cross_tower_mode=[False])))
179197
def testTrainNetworkWithBatchNorm(self, distribution, optimizer_fn, momentum,
180-
renorm):
198+
renorm, is_tpu,
199+
update_ops_in_cross_tower_mode):
181200
"""Verifies that moving mean updates are reduced across towers."""
182201
with distribution.scope():
183202
num_towers = len(distribution.worker_devices)
184203
model_fn, dataset_fn, batchnorm = batchnorm_example(
185204
optimizer_fn,
186205
batch_per_epoch=num_towers,
187206
momentum=momentum,
188-
renorm=renorm)
207+
renorm=renorm,
208+
update_ops_in_tower_mode=not update_ops_in_cross_tower_mode)
189209

190210
# Disable prefetching since that makes the specific input on each device
191211
# to be non deterministic, and this test relies on specific input being
@@ -196,16 +216,18 @@ def testTrainNetworkWithBatchNorm(self, distribution, optimizer_fn, momentum,
196216
dataset_fn).make_one_shot_iterator()
197217

198218
def run_step():
199-
return control_flow_ops.group(
200-
distribution.unwrap(
201-
distribution.call_for_each_tower(
202-
model_fn,
203-
iterator.get_next(),
204-
run_concurrently=batchnorm.built)) +
205-
ops.get_collection(ops.GraphKeys.UPDATE_OPS))
219+
fetches = distribution.unwrap(
220+
distribution.call_for_each_tower(
221+
model_fn, iterator.get_next(),
222+
run_concurrently=batchnorm.built))
223+
if update_ops_in_cross_tower_mode:
224+
fetches += ops.get_collection(ops.GraphKeys.UPDATE_OPS)
225+
return control_flow_ops.group(fetches)
206226

207227
if not context.executing_eagerly():
208228
with self.test_session() as sess:
229+
if is_tpu:
230+
sess.run(tpu.initialize_system())
209231
run_step = sess.make_callable(run_step())
210232
self.evaluate(variables_lib.global_variables_initializer())
211233

@@ -229,22 +251,40 @@ def averaged_batch_mean(i):
229251
expected_moving_mean - averaged_batch_mean(i)) * (1.0 - momentum))
230252
self.assertNear(expected_moving_means[i], moving_means[i], 0.0001)
231253

254+
if is_tpu:
255+
with self.test_session() as sess:
256+
sess.run(tpu.shutdown_system())
257+
232258
@combinations.generate(
233259
combinations.times(
234260
combinations.combine(
235-
distribution=[combinations.one_device_strategy,
236-
combinations.mirrored_strategy_with_gpu_and_cpu,
237-
combinations.mirrored_strategy_with_two_gpus],
238-
optimizer_fn=[combinations.gradient_descent_optimizer_v1_fn,
239-
combinations.gradient_descent_optimizer_v2_fn],
240-
loss_reduction=[losses_impl.Reduction.SUM,
241-
losses_impl.Reduction.MEAN,
242-
losses_impl.Reduction.SUM_OVER_BATCH_SIZE,
243-
losses_impl.Reduction.SUM_OVER_NONZERO_WEIGHTS]),
244-
combinations.combine(mode=["graph"], use_callable_loss=[True, False])
245-
+ combinations.combine(mode=["eager"], use_callable_loss=[True])))
261+
optimizer_fn=[
262+
combinations.gradient_descent_optimizer_v1_fn,
263+
combinations.gradient_descent_optimizer_v2_fn
264+
],
265+
loss_reduction=[
266+
losses_impl.Reduction.SUM, losses_impl.Reduction.MEAN,
267+
losses_impl.Reduction.SUM_OVER_BATCH_SIZE,
268+
losses_impl.Reduction.SUM_OVER_NONZERO_WEIGHTS
269+
]),
270+
combinations.times(
271+
combinations.combine(
272+
distribution=[
273+
combinations.one_device_strategy,
274+
combinations.mirrored_strategy_with_gpu_and_cpu,
275+
combinations.mirrored_strategy_with_two_gpus
276+
],
277+
is_tpu=[False]),
278+
combinations.combine(
279+
mode=["graph"], use_callable_loss=[True, False]) +
280+
combinations.combine(mode=["eager"], use_callable_loss=[True])) +
281+
combinations.combine(
282+
distribution=[combinations.tpu_strategy_single_iteration],
283+
is_tpu=[True],
284+
mode=["graph"],
285+
use_callable_loss=[True, False])))
246286
def testMeanVsSum(self, distribution, optimizer_fn, loss_reduction,
247-
use_callable_loss):
287+
use_callable_loss, is_tpu):
248288
with distribution.scope():
249289
all_vars = []
250290

@@ -280,12 +320,13 @@ def run_step():
280320

281321
if not context.executing_eagerly():
282322
with self.test_session() as sess:
323+
if is_tpu:
324+
sess.run(tpu.initialize_system())
283325
run_step = sess.make_callable(run_step())
284326
self.evaluate(variables_lib.global_variables_initializer())
285327

286328
run_step()
287329

288-
self.assertEqual(distribution.num_towers, len(all_vars))
289330
v = all_vars[0]
290331
self.assertTrue(all([v is vi for vi in all_vars[1:]]))
291332
weight = numpy.squeeze(self.evaluate(distribution.fetch(v)))
@@ -312,6 +353,10 @@ def run_step():
312353
# One of the mean loss reductions.
313354
self.assertNear(weight, 2 + 10.6, 0.0001)
314355

356+
if is_tpu:
357+
with self.test_session() as sess:
358+
sess.run(tpu.shutdown_system())
359+
315360

316361
if __name__ == "__main__":
317362
test.main()

‎tensorflow/contrib/distribute/python/single_loss_example.py‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from tensorflow.contrib.distribute.python import step_fn
2323
from tensorflow.python.data.ops import dataset_ops
2424
from tensorflow.python.framework import constant_op
25+
from tensorflow.python.framework import ops
2526
from tensorflow.python.layers import core
2627
from tensorflow.python.layers import normalization
2728
from tensorflow.python.ops import array_ops
@@ -59,7 +60,7 @@ def dataset_fn():
5960
# TODO(isaprykin): map_and_batch with drop_remainder causes shapes to be
6061
# fully defined for TPU. Remove this when XLA supports dynamic shapes.
6162
return dataset.apply(
62-
batching.map_and_batch(lambda x: x, batch_size=2, drop_remainder=True))
63+
batching.map_and_batch(lambda x: x, batch_size=1, drop_remainder=True))
6364

6465
# An Optimizer instance is created either outside or inside model_fn.
6566
outer_optimizer = None
@@ -68,11 +69,10 @@ def dataset_fn():
6869

6970
layer = core.Dense(1, use_bias=use_bias)
7071

71-
def model_fn(xs):
72+
def model_fn(x):
7273
"""A very simple model written by the user."""
7374

7475
def loss_fn():
75-
x = math_ops.reduce_mean(xs, keepdims=True)
7676
y = array_ops.reshape(layer(x), []) - constant_op.constant(1.)
7777
return y * y
7878

@@ -89,7 +89,8 @@ def loss_fn():
8989
def batchnorm_example(optimizer_fn,
9090
batch_per_epoch=1,
9191
momentum=0.9,
92-
renorm=False):
92+
renorm=False,
93+
update_ops_in_tower_mode=False):
9394
"""Example of non-distribution-aware legacy code with batch normalization."""
9495

9596
def dataset_fn():
@@ -103,12 +104,19 @@ def dataset_fn():
103104
optimizer = optimizer_fn()
104105
batchnorm = normalization.BatchNormalization(
105106
renorm=renorm, momentum=momentum, fused=False)
107+
layer = core.Dense(1, use_bias=False)
106108

107109
def model_fn(x):
110+
"""A model that uses batchnorm."""
108111

109112
def loss_fn():
110-
y = math_ops.reduce_sum(batchnorm(x, training=True), axis=1)
111-
loss = math_ops.reduce_mean(y - constant_op.constant(1.))
113+
y = batchnorm(x, training=True)
114+
with ops.control_dependencies(
115+
ops.get_collection(ops.GraphKeys.UPDATE_OPS)
116+
if update_ops_in_tower_mode else []):
117+
loss = math_ops.reduce_mean(
118+
math_ops.reduce_sum(layer(y)) - constant_op.constant(1.))
119+
# `x` and `y` will be fetched by the gradient computation, but not `loss`.
112120
return loss
113121

114122
# Callable loss.

‎tensorflow/contrib/distribute/python/tpu_strategy.py‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
from tensorflow.python.util import nest
3434

3535

36-
# TODO(isaprykin): Consider whether inheriting is really appropriate.
3736
class TPUStrategy(one_device_strategy.OneDeviceStrategy):
3837
"""Experimental TPU distribution strategy implementation."""
3938

@@ -73,7 +72,6 @@ def _call_for_each_tower(self, fn, *args, **kwargs):
7372
def infeed_input(i):
7473
"""Get input, split it and then enqueue."""
7574
iteration_inputs = [f.get(i) for f in feeds()]
76-
7775
infeed_inputs = [[inputs_per_core[core_id]
7876
for inputs_per_core in iteration_inputs]
7977
for core_id in range(self._num_cores_per_host)]
@@ -117,3 +115,14 @@ def iterate_on_tpu():
117115
iterate_on_tpu, [], num_shards=self._num_cores_per_host)
118116

119117
return control_flow_ops.group(tpu_result, enqueue_ops)
118+
119+
def _reduce(self, method_string, value, destinations):
120+
del destinations # TPU is graph mode only. Rely on implicit Send/Recv.
121+
if method_string == 'mean':
122+
# TODO(jhseu): Revisit once we support model-parallelism.
123+
value *= (1. / self._num_cores_per_host)
124+
return tpu_ops.cross_replica_sum(value)
125+
126+
@property
127+
def num_towers(self):
128+
return self._num_cores_per_host

‎tensorflow/contrib/distribute/python/values.py‎

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -672,11 +672,12 @@ def make_initializable_iterator(self):
672672
return MultiWorkerDataIterator(iterators, self._worker_device_map)
673673

674674

675-
class PerIteration(object):
676-
"""Holds input for multiple iterations at once."""
675+
class _PerKey(object):
676+
"""Holds data associated by keys."""
677677

678-
def __init__(self, index):
679-
self._index = index
678+
def __init__(self, *index):
679+
# pylint: disable=protected-access
680+
self._index = list(index)
680681

681682
def get(self, iteration):
682683
return array_ops.gather(self._index, iteration)
@@ -687,6 +688,24 @@ def get_shape(self):
687688
def get_dtype(self):
688689
return self._index[-1][-1].dtype
689690

691+
def __str__(self):
692+
return "%s:%s" % (self.__class__.__name__, self._index)
693+
694+
def __repr__(self):
695+
return "%s(%r)" % (self.__class__.__name__, self._index)
696+
697+
698+
class PerIteration(_PerKey):
699+
"""Holds input for multiple iterations at once."""
700+
701+
def __init__(self, *index):
702+
# pylint: disable=protected-access
703+
super(PerIteration, self).__init__(*[batch._index for batch in index])
704+
705+
706+
class Batches(_PerKey):
707+
pass
708+
690709

691710
class MultiIterator(object):
692711
"""Iterator that returns results of multiple get_next()s."""
@@ -697,11 +716,31 @@ def __init__(self, dataset_iterator, iterations, batches_per_iteration):
697716
self._batches_per_iteration = batches_per_iteration
698717

699718
def get_next(self, name=None):
700-
return PerIteration([[
701-
self._dataset_iterator.get_next(name=name)
702-
for _ in range(self._batches_per_iteration)
703-
]
704-
for _ in range(self._iterations)])
719+
"""Return PerIteration with `iterations x batches_per_iteration` inputs."""
720+
data = []
721+
for _ in range(self._batches_per_iteration):
722+
batch = []
723+
for _ in range(self._iterations):
724+
batch.append(self._dataset_iterator.get_next(name=name))
725+
data.append(batch)
726+
727+
# Here is an example. Suppose each get_next returns a tuple of two tensors.
728+
# For 3 `iterations` and 2 `batches_per_iteration`, the `data` is:
729+
# [[(a,z), (b,y), (c,x)], [(A,Z), (B,Y), (C,X)]]
730+
#
731+
# After the first `map_structure` it gets transformed to:
732+
# [(Batches(a, A), Batches(z, Z)),
733+
# (Batches(b, B), Batches(y, Y)),
734+
# (Batches(c, C), Batches(x, X))]
735+
#
736+
# After the second `map_structure` it gets transformed to a tuple of:
737+
# (PerIteration([Batches(a, A), Batches(b, B), Batches(c, C)]),
738+
# PerIteration([Batches(z, Z), Batches(y, Y), Batches(x, X)]))
739+
740+
data = nest.map_structure(Batches, *data)
741+
data = nest.map_structure(PerIteration, *data)
742+
743+
return data
705744

706745
@property
707746
def initializer(self):

‎tensorflow/python/training/distribute.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,7 @@ def reduce(self, method_string, value, destinations=None):
816816
# TODO(josh11b): Return an unwrapped value if colocate_with is a
817817
# single device.
818818
_require_cross_tower_context(self)
819+
assert method_string in ("sum", "mean")
819820
return self._reduce(method_string, value, destinations)
820821

821822
def _reduce(self, method_string, value, destinations):

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL