| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent eb5ee79 commit f14123d
6 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -265,6 +265,10 @@ def required_tpu(self): | |||
| 265 | 265 | one_device_strategy = NamedDistribution( | |
| 266 | 266 | "OneDeviceCPU", one_device_strategy.OneDeviceStrategy("/cpu:0"), | |
| 267 | 267 | None) | |
| 268 | + tpu_strategy_single_iteration = NamedDistribution( | ||
| 269 | + "TPUSingleIteration", | ||
| 270 | + tpu_strategy.TPUStrategy(iterations_per_step=1), | ||
| 271 | + required_tpu=True) | ||
| 268 | 272 | tpu_strategy = NamedDistribution( | |
| 269 | 273 | "TPU", tpu_strategy.TPUStrategy(), required_tpu=True) | |
| 270 | 274 | mirrored_strategy_with_gpu_and_cpu = NamedDistribution( | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -44,13 +44,16 @@ class MinimizeLossStepTest(test.TestCase, parameterized.TestCase): | |||
| 44 | 44 | combinations.distributions_and_v1_optimizers(), | |
| 45 | 45 | combinations.combine(mode=["graph"], use_callable_loss=[True, False]) | |
| 46 | 46 | + 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])) | ||
| 54 | 57 | def testTrainNetwork(self, distribution, optimizer_fn, use_callable_loss, | |
| 55 | 58 | is_tpu): | |
| 56 | 59 | with distribution.scope(): | |
@@ -101,7 +104,8 @@ def run_step(): | |||
| 101 | 104 | distribution=[combinations.tpu_strategy], | |
| 102 | 105 | optimizer_fn=[ | |
| 103 | 106 | 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, | ||
| 105 | 109 | ], | |
| 106 | 110 | mode=["graph"], | |
| 107 | 111 | is_tpu=[True])) | |
@@ -171,21 +175,37 @@ def get_expected_variables(optimizer_fn, num_parameter_devices): | |||
| 171 | 175 | set(created_variables)) | |
| 172 | 176 | ||
| 173 | 177 | @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]))) | ||
| 179 | 197 | def testTrainNetworkWithBatchNorm(self, distribution, optimizer_fn, momentum, | |
| 180 | - renorm): | ||
| 198 | + renorm, is_tpu, | ||
| 199 | + update_ops_in_cross_tower_mode): | ||
| 181 | 200 | """Verifies that moving mean updates are reduced across towers.""" | |
| 182 | 201 | with distribution.scope(): | |
| 183 | 202 | num_towers = len(distribution.worker_devices) | |
| 184 | 203 | model_fn, dataset_fn, batchnorm = batchnorm_example( | |
| 185 | 204 | optimizer_fn, | |
| 186 | 205 | batch_per_epoch=num_towers, | |
| 187 | 206 | momentum=momentum, | |
| 188 | - renorm=renorm) | ||
| 207 | + renorm=renorm, | ||
| 208 | + update_ops_in_tower_mode=not update_ops_in_cross_tower_mode) | ||
| 189 | 209 | ||
| 190 | 210 | # Disable prefetching since that makes the specific input on each device | |
| 191 | 211 | # to be non deterministic, and this test relies on specific input being | |
@@ -196,16 +216,18 @@ def testTrainNetworkWithBatchNorm(self, distribution, optimizer_fn, momentum, | |||
| 196 | 216 | dataset_fn).make_one_shot_iterator() | |
| 197 | 217 | ||
| 198 | 218 | 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) | ||
| 206 | 226 | ||
| 207 | 227 | if not context.executing_eagerly(): | |
| 208 | 228 | with self.test_session() as sess: | |
| 229 | + if is_tpu: | ||
| 230 | + sess.run(tpu.initialize_system()) | ||
| 209 | 231 | run_step = sess.make_callable(run_step()) | |
| 210 | 232 | self.evaluate(variables_lib.global_variables_initializer()) | |
| 211 | 233 | ||
@@ -229,22 +251,40 @@ def averaged_batch_mean(i): | |||
| 229 | 251 | expected_moving_mean - averaged_batch_mean(i)) * (1.0 - momentum)) | |
| 230 | 252 | self.assertNear(expected_moving_means[i], moving_means[i], 0.0001) | |
| 231 | 253 | ||
| 254 | + if is_tpu: | ||
| 255 | + with self.test_session() as sess: | ||
| 256 | + sess.run(tpu.shutdown_system()) | ||
| 257 | + | ||
| 232 | 258 | @combinations.generate( | |
| 233 | 259 | combinations.times( | |
| 234 | 260 | 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]))) | ||
| 246 | 286 | def testMeanVsSum(self, distribution, optimizer_fn, loss_reduction, | |
| 247 | - use_callable_loss): | ||
| 287 | + use_callable_loss, is_tpu): | ||
| 248 | 288 | with distribution.scope(): | |
| 249 | 289 | all_vars = [] | |
| 250 | 290 | ||
@@ -280,12 +320,13 @@ def run_step(): | |||
| 280 | 320 | ||
| 281 | 321 | if not context.executing_eagerly(): | |
| 282 | 322 | with self.test_session() as sess: | |
| 323 | + if is_tpu: | ||
| 324 | + sess.run(tpu.initialize_system()) | ||
| 283 | 325 | run_step = sess.make_callable(run_step()) | |
| 284 | 326 | self.evaluate(variables_lib.global_variables_initializer()) | |
| 285 | 327 | ||
| 286 | 328 | run_step() | |
| 287 | 329 | ||
| 288 | - self.assertEqual(distribution.num_towers, len(all_vars)) | ||
| 289 | 330 | v = all_vars[0] | |
| 290 | 331 | self.assertTrue(all([v is vi for vi in all_vars[1:]])) | |
| 291 | 332 | weight = numpy.squeeze(self.evaluate(distribution.fetch(v))) | |
@@ -312,6 +353,10 @@ def run_step(): | |||
| 312 | 353 | # One of the mean loss reductions. | |
| 313 | 354 | self.assertNear(weight, 2 + 10.6, 0.0001) | |
| 314 | 355 | ||
| 356 | + if is_tpu: | ||
| 357 | + with self.test_session() as sess: | ||
| 358 | + sess.run(tpu.shutdown_system()) | ||
| 359 | + | ||
| 315 | 360 | ||
| 316 | 361 | if __name__ == "__main__": | |
| 317 | 362 | test.main() | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -22,6 +22,7 @@ | |||
| 22 | 22 | from tensorflow.contrib.distribute.python import step_fn | |
| 23 | 23 | from tensorflow.python.data.ops import dataset_ops | |
| 24 | 24 | from tensorflow.python.framework import constant_op | |
| 25 | + from tensorflow.python.framework import ops | ||
| 25 | 26 | from tensorflow.python.layers import core | |
| 26 | 27 | from tensorflow.python.layers import normalization | |
| 27 | 28 | from tensorflow.python.ops import array_ops | |
@@ -59,7 +60,7 @@ def dataset_fn(): | |||
| 59 | 60 | # TODO(isaprykin): map_and_batch with drop_remainder causes shapes to be | |
| 60 | 61 | # fully defined for TPU. Remove this when XLA supports dynamic shapes. | |
| 61 | 62 | 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)) | ||
| 63 | 64 | ||
| 64 | 65 | # An Optimizer instance is created either outside or inside model_fn. | |
| 65 | 66 | outer_optimizer = None | |
@@ -68,11 +69,10 @@ def dataset_fn(): | |||
| 68 | 69 | ||
| 69 | 70 | layer = core.Dense(1, use_bias=use_bias) | |
| 70 | 71 | ||
| 71 | - def model_fn(xs): | ||
| 72 | + def model_fn(x): | ||
| 72 | 73 | """A very simple model written by the user.""" | |
| 73 | 74 | ||
| 74 | 75 | def loss_fn(): | |
| 75 | - x = math_ops.reduce_mean(xs, keepdims=True) | ||
| 76 | 76 | y = array_ops.reshape(layer(x), []) - constant_op.constant(1.) | |
| 77 | 77 | return y * y | |
| 78 | 78 | ||
@@ -89,7 +89,8 @@ def loss_fn(): | |||
| 89 | 89 | def batchnorm_example(optimizer_fn, | |
| 90 | 90 | batch_per_epoch=1, | |
| 91 | 91 | momentum=0.9, | |
| 92 | - renorm=False): | ||
| 92 | + renorm=False, | ||
| 93 | + update_ops_in_tower_mode=False): | ||
| 93 | 94 | """Example of non-distribution-aware legacy code with batch normalization.""" | |
| 94 | 95 | ||
| 95 | 96 | def dataset_fn(): | |
@@ -103,12 +104,19 @@ def dataset_fn(): | |||
| 103 | 104 | optimizer = optimizer_fn() | |
| 104 | 105 | batchnorm = normalization.BatchNormalization( | |
| 105 | 106 | renorm=renorm, momentum=momentum, fused=False) | |
| 107 | + layer = core.Dense(1, use_bias=False) | ||
| 106 | 108 | ||
| 107 | 109 | def model_fn(x): | |
| 110 | + """A model that uses batchnorm.""" | ||
| 108 | 111 | ||
| 109 | 112 | 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`. | ||
| 112 | 120 | return loss | |
| 113 | 121 | ||
| 114 | 122 | # Callable loss. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -33,7 +33,6 @@ | |||
| 33 | 33 | from tensorflow.python.util import nest | |
| 34 | 34 | ||
| 35 | 35 | ||
| 36 | - # TODO(isaprykin): Consider whether inheriting is really appropriate. | ||
| 37 | 36 | class TPUStrategy(one_device_strategy.OneDeviceStrategy): | |
| 38 | 37 | """Experimental TPU distribution strategy implementation.""" | |
| 39 | 38 | ||
@@ -73,7 +72,6 @@ def _call_for_each_tower(self, fn, *args, **kwargs): | |||
| 73 | 72 | def infeed_input(i): | |
| 74 | 73 | """Get input, split it and then enqueue.""" | |
| 75 | 74 | iteration_inputs = [f.get(i) for f in feeds()] | |
| 76 | - | ||
| 77 | 75 | infeed_inputs = [[inputs_per_core[core_id] | |
| 78 | 76 | for inputs_per_core in iteration_inputs] | |
| 79 | 77 | for core_id in range(self._num_cores_per_host)] | |
@@ -117,3 +115,14 @@ def iterate_on_tpu(): | |||
| 117 | 115 | iterate_on_tpu, [], num_shards=self._num_cores_per_host) | |
| 118 | 116 | ||
| 119 | 117 | 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 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -672,11 +672,12 @@ def make_initializable_iterator(self): | |||
| 672 | 672 | return MultiWorkerDataIterator(iterators, self._worker_device_map) | |
| 673 | 673 | ||
| 674 | 674 | ||
| 675 | - class PerIteration(object): | ||
| 676 | - """Holds input for multiple iterations at once.""" | ||
| 675 | + class _PerKey(object): | ||
| 676 | + """Holds data associated by keys.""" | ||
| 677 | 677 | ||
| 678 | - def __init__(self, index): | ||
| 679 | - self._index = index | ||
| 678 | + def __init__(self, *index): | ||
| 679 | + # pylint: disable=protected-access | ||
| 680 | + self._index = list(index) | ||
| 680 | 681 | ||
| 681 | 682 | def get(self, iteration): | |
| 682 | 683 | return array_ops.gather(self._index, iteration) | |
@@ -687,6 +688,24 @@ def get_shape(self): | |||
| 687 | 688 | def get_dtype(self): | |
| 688 | 689 | return self._index[-1][-1].dtype | |
| 689 | 690 | ||
| 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 | + | ||
| 690 | 709 | ||
| 691 | 710 | class MultiIterator(object): | |
| 692 | 711 | """Iterator that returns results of multiple get_next()s.""" | |
@@ -697,11 +716,31 @@ def __init__(self, dataset_iterator, iterations, batches_per_iteration): | |||
| 697 | 716 | self._batches_per_iteration = batches_per_iteration | |
| 698 | 717 | ||
| 699 | 718 | 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 | ||
| 705 | 744 | ||
| 706 | 745 | @property | |
| 707 | 746 | def initializer(self): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -816,6 +816,7 @@ def reduce(self, method_string, value, destinations=None): | |||
| 816 | 816 | # TODO(josh11b): Return an unwrapped value if colocate_with is a | |
| 817 | 817 | # single device. | |
| 818 | 818 | _require_cross_tower_context(self) | |
| 819 | + assert method_string in ("sum", "mean") | ||
| 819 | 820 | return self._reduce(method_string, value, destinations) | |
| 820 | 821 | ||
| 821 | 822 | def _reduce(self, method_string, value, destinations): | |
| Back | FazBrowse Home | New Git URL |
0 commit comments