481 lines
16 KiB
Python
481 lines
16 KiB
Python
# Copyright 2021 The TensorFlow Authors. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
# ==============================================================================
|
|
"""Tests for the distributed values library."""
|
|
|
|
from absl.testing import parameterized
|
|
|
|
from tensorflow.python.distribute import combinations
|
|
from tensorflow.python.distribute import strategy_combinations
|
|
from tensorflow.python.distribute import test_util
|
|
from tensorflow.python.distribute import values_v2
|
|
from tensorflow.python.eager import def_function
|
|
from tensorflow.python.eager import test
|
|
from tensorflow.python.framework import dtypes
|
|
from tensorflow.python.framework import indexed_slices
|
|
from tensorflow.python.framework import ops
|
|
from tensorflow.python.framework import tensor
|
|
from tensorflow.python.ops import array_ops
|
|
from tensorflow.python.ops import resource_variable_ops
|
|
from tensorflow.python.ops import variables as variables_lib
|
|
|
|
|
|
class _VariableInterfaceTestBase(test.TestCase, parameterized.TestCase):
|
|
# This test verifies that DistributedVariable/AutoSyncVariable conforms to
|
|
# Variable and ResourceVariable interface, i.e. the methods and properties are
|
|
# all defined. It verifies methods and properties that have the same code path
|
|
# under different replicas/devices as well. It is not intended to verify
|
|
# methods and properties that behave differently under different
|
|
# replicas/devices; those should be covered separate tests.
|
|
|
|
def create_variable(self, initial_value=1., **kwargs):
|
|
raise NotImplementedError
|
|
|
|
@property
|
|
def devices(self):
|
|
return ["CPU:0", "CPU:1"]
|
|
|
|
# ==== Begin Variable interface ===
|
|
# Please follow the same order as methods and properties defined in
|
|
# tf.Variable.
|
|
|
|
def testStringify(self):
|
|
v = self.create_variable()
|
|
self.assertIsInstance(v.__str__(), str)
|
|
self.assertIsInstance(v.__repr__(), str)
|
|
|
|
def testDenseRead(self):
|
|
v = self.create_variable(1.)
|
|
self.assertEqual(v.value(), 1.)
|
|
self.assertEqual(v.read_value(), 1.)
|
|
|
|
def testShape(self):
|
|
v = self.create_variable([1.])
|
|
self.assertEqual(v.shape, (1,))
|
|
self.assertEqual(v.get_shape(), (1,))
|
|
v.set_shape((1,))
|
|
with self.assertRaisesRegex(ValueError, "not compatible"):
|
|
v.set_shape((1, 1))
|
|
|
|
@combinations.generate(combinations.combine(trainable=[True, False]))
|
|
def testTrainable(self, trainable):
|
|
v = self.create_variable(trainable=trainable)
|
|
self.assertEqual(v.trainable, trainable)
|
|
|
|
@combinations.generate(
|
|
combinations.combine(synchronization=[
|
|
variables_lib.VariableSynchronization.ON_READ,
|
|
variables_lib.VariableSynchronization.ON_WRITE,
|
|
variables_lib.VariableSynchronization.AUTO,
|
|
variables_lib.VariableSynchronization.NONE,
|
|
]))
|
|
def testSynchronization(self, synchronization):
|
|
v = self.create_variable(synchronization=synchronization)
|
|
self.assertEqual(v.synchronization, synchronization)
|
|
|
|
@combinations.generate(
|
|
combinations.combine(aggregation=[
|
|
variables_lib.VariableAggregation.MEAN,
|
|
variables_lib.VariableAggregation.SUM,
|
|
variables_lib.VariableAggregation.ONLY_FIRST_REPLICA,
|
|
variables_lib.VariableAggregation.NONE,
|
|
]))
|
|
def testAggregation(self, aggregation):
|
|
v = self.create_variable(aggregation=aggregation)
|
|
self.assertEqual(v.aggregation, aggregation)
|
|
|
|
@combinations.generate(combinations.combine(mode="graph"))
|
|
def testEval(self):
|
|
v = self.create_variable(1.)
|
|
with self.cached_session():
|
|
self.evaluate(variables_lib.global_variables_initializer())
|
|
self.assertEqual(v.eval(), 1.)
|
|
|
|
def testInitialValueEager(self):
|
|
v = self.create_variable(1.)
|
|
with self.assertRaises(RuntimeError):
|
|
v.initial_value # pylint: disable=pointless-statement
|
|
|
|
@combinations.generate(combinations.combine(mode="graph"))
|
|
def testInitialValueGraph(self):
|
|
v = self.create_variable(1.)
|
|
self.assertEqual(self.evaluate(v.initial_value), 1.)
|
|
|
|
def testConstraint(self):
|
|
v = self.create_variable(constraint=lambda x: x + 1.)
|
|
self.assertEqual(v.constraint(1.), 2.)
|
|
|
|
def testDenseUpdate(self):
|
|
v = self.create_variable(1.)
|
|
self.assertEqual(
|
|
v.assign(2., use_locking=True, name="assign", read_value=True), 2.)
|
|
self.assertIsNone(v.assign(3., read_value=False))
|
|
self.assertEqual(v, 3.)
|
|
self.assertEqual(
|
|
v.assign_add(1., use_locking=True, name="assign_add", read_value=True),
|
|
4.)
|
|
self.assertIsNone(v.assign_add(1., read_value=False))
|
|
self.assertEqual(v, 5.)
|
|
self.assertEqual(
|
|
v.assign_sub(1., use_locking=True, name="assign_sub", read_value=True),
|
|
4.)
|
|
self.assertIsNone(v.assign_sub(1., read_value=False))
|
|
self.assertEqual(v, 3.)
|
|
|
|
@def_function.function
|
|
def f():
|
|
self.assertIsInstance(v.assign(1., read_value=False), ops.Operation)
|
|
self.assertIsInstance(v.assign_add(1., read_value=False), ops.Operation)
|
|
self.assertIsInstance(v.assign_sub(1., read_value=False), ops.Operation)
|
|
|
|
f()
|
|
|
|
def testSparseUpdate(self):
|
|
v = self.create_variable([0., 0., 0.])
|
|
self.assertAllEqual(
|
|
v.scatter_add(
|
|
_make_index_slices(values=[1., 2.], indices=[0, 2]),
|
|
use_locking=True,
|
|
name="add"), [1., 0., 2.])
|
|
self.assertAllEqual(
|
|
v.scatter_div(
|
|
_make_index_slices(values=[4., 2.], indices=[0, 2]),
|
|
use_locking=True,
|
|
name="div"), [0.25, 0., 1.])
|
|
self.assertAllEqual(
|
|
v.scatter_max(
|
|
_make_index_slices(values=[1., 0.5], indices=[1, 2]),
|
|
use_locking=True,
|
|
name="max"), [0.25, 1., 1.])
|
|
self.assertAllEqual(
|
|
v.scatter_min(
|
|
_make_index_slices(values=[1., 0.5], indices=[0, 1]),
|
|
use_locking=True,
|
|
name="min"), [0.25, 0.5, 1.])
|
|
self.assertAllEqual(
|
|
v.scatter_mul(
|
|
_make_index_slices(values=[2., 0.5], indices=[0, 1]),
|
|
use_locking=True,
|
|
name="mul"), [0.5, 0.25, 1.])
|
|
self.assertAllEqual(
|
|
v.scatter_sub(
|
|
_make_index_slices(values=[2., 0.5], indices=[0, 1]),
|
|
use_locking=True,
|
|
name="sub"), [-1.5, -0.25, 1.])
|
|
self.assertAllEqual(
|
|
v.scatter_update(
|
|
_make_index_slices(values=[2., 0.5], indices=[0, 1]),
|
|
use_locking=True,
|
|
name="update"), [2., 0.5, 1.])
|
|
self.assertAllEqual(
|
|
v.batch_scatter_update(
|
|
_make_index_slices(values=[1., 1.5], indices=[0, 1]),
|
|
use_locking=True,
|
|
name="update"), [1., 1.5, 1.])
|
|
|
|
def testSparseNdUpdate(self):
|
|
v = self.create_variable([0., 0., 0., 0.])
|
|
self.assertAllEqual(
|
|
v.scatter_nd_sub([[3], [1]], [1., 2.], name="sub"), [0., -2., 0., -1.])
|
|
self.assertAllEqual(
|
|
v.scatter_nd_add([[2], [0]], [1., 2.], name="add"), [2., -2., 1., -1.])
|
|
self.assertAllEqual(
|
|
v.scatter_nd_update([[1], [3]], [3., 3.], name="update"),
|
|
[2., 3., 1., 3.])
|
|
|
|
def testSparseRead(self):
|
|
v = self.create_variable([[1., 2.], [3., 4.]])
|
|
self.assertAllEqual(
|
|
v.sparse_read([1, 0], name="read"), [[3., 4.], [1., 2.]])
|
|
self.assertAllEqual(
|
|
v.gather_nd([[1, 0], [0, 1]], name="gather_nd"), [3., 2.])
|
|
|
|
def testTensorConversion(self):
|
|
v = self.create_variable([1.])
|
|
self.assertEqual(ops.convert_to_tensor(v), [1.])
|
|
|
|
def testHash(self):
|
|
v = self.create_variable()
|
|
w = self.create_variable()
|
|
d = {}
|
|
with self.assertRaises(TypeError):
|
|
d[v] = 1
|
|
d[v.ref()] = 1
|
|
self.assertEqual(d[v.ref()], 1)
|
|
self.assertNotIn(w.ref(), d)
|
|
|
|
@combinations.generate(combinations.combine(mode="graph"))
|
|
def testHashGraph(self):
|
|
v = self.create_variable()
|
|
w = self.create_variable()
|
|
d = {v: 1}
|
|
self.assertEqual(d[v], 1)
|
|
self.assertNotIn(w, d)
|
|
|
|
def testEquality(self):
|
|
v = self.create_variable(1.)
|
|
w = self.create_variable(2.)
|
|
x = self.create_variable(1.)
|
|
self.assertEqual(v, x)
|
|
self.assertNotEqual(v, w)
|
|
|
|
@combinations.generate(combinations.combine(mode="graph"))
|
|
def testEqualityGraph(self):
|
|
# In legacy graph mode, tensor equality is object equality
|
|
v = self.create_variable(1.)
|
|
w = self.create_variable(1.)
|
|
self.assertNotEqual(v, w)
|
|
self.assertEqual(v, v)
|
|
|
|
def testIteration(self):
|
|
v = self.create_variable([1.])
|
|
self.assertEqual([1.], list(iter(v)))
|
|
|
|
def testProperties(self):
|
|
v = self.create_variable()
|
|
self.assertIsInstance(v.name, str)
|
|
# _shared_name is also part of the interface. E.g. it's used in optimizer to
|
|
# determine slot variable key.
|
|
self.assertIsInstance(v._shared_name, str)
|
|
self.assertIsNone(v.initializer)
|
|
self.assertIsInstance(v.device, str)
|
|
self.assertEqual(v.dtype, dtypes.float32)
|
|
with self.assertRaises(AttributeError):
|
|
v.op # pylint: disable=pointless-statement
|
|
with self.assertRaises(AttributeError):
|
|
v.graph # pylint: disable=pointless-statement
|
|
|
|
@combinations.generate(combinations.combine(mode="graph"))
|
|
def testPropertiesGraph(self):
|
|
v = self.create_variable()
|
|
self.assertIsInstance(v.initializer, ops.Operation)
|
|
self.assertIsInstance(v.op, ops.Operation)
|
|
self.assertIsInstance(v.graph, ops.Graph)
|
|
|
|
def testProtoConversion(self):
|
|
# to_proto and from_proto are not supported.
|
|
v = self.create_variable([1, 2])
|
|
with self.assertRaises(TypeError):
|
|
v.to_proto()
|
|
with self.assertRaises(TypeError):
|
|
v.from_proto(variable_def=None)
|
|
|
|
def testSaveSliceInfo(self):
|
|
v = self.create_variable()
|
|
slice_info = variables_lib.Variable.SaveSliceInfo()
|
|
v._set_save_slice_info(slice_info)
|
|
self.assertIs(v._get_save_slice_info(), slice_info)
|
|
# Some code accesses _save_slice_info directly without using the getter.
|
|
self.assertIs(v._save_slice_info, slice_info)
|
|
|
|
def testOperatorOverride(self):
|
|
v = self.create_variable(7)
|
|
self.assertEqual(v + 1, 8)
|
|
self.assertEqual(3 + v, 10)
|
|
self.assertEqual(v + v, 14)
|
|
self.assertEqual(v - 2, 5)
|
|
self.assertEqual(13 - v, 6)
|
|
self.assertEqual(v - v, 0)
|
|
self.assertEqual(v * 2, 14)
|
|
self.assertEqual(3 * v, 21)
|
|
self.assertEqual(v * v, 49)
|
|
self.assertEqual(v / 2, 3.5)
|
|
self.assertEqual(14 / v, 2.)
|
|
self.assertEqual(v // 2, 3)
|
|
self.assertEqual(15 // v, 2)
|
|
self.assertEqual(v % 2, 1)
|
|
self.assertEqual(16 % v, 2)
|
|
# pylint: disable=g-generic-assert
|
|
self.assertTrue(v < 12)
|
|
self.assertTrue(v <= 12)
|
|
self.assertFalse(v > 12)
|
|
self.assertFalse(v >= 12)
|
|
self.assertFalse(12 < v)
|
|
self.assertFalse(12 <= v)
|
|
self.assertTrue(12 > v)
|
|
self.assertTrue(12 >= v)
|
|
# pylint: enable=g-generic-assert
|
|
self.assertEqual(v & 3, 3)
|
|
self.assertEqual(11 & v, 3)
|
|
self.assertEqual(v | 8, 15)
|
|
self.assertEqual(16 | v, 23)
|
|
self.assertEqual(v ^ 3, 4)
|
|
self.assertEqual(11 ^ v, 12)
|
|
self.assertEqual(pow(v, 3), 343)
|
|
# TODO(b/178748613): pow(v, 3, 10) fails.
|
|
self.assertEqual(pow(2, v), 128)
|
|
self.assertEqual(-v, -7)
|
|
self.assertEqual(~v, ~7)
|
|
self.assertEqual(abs(v), 7)
|
|
|
|
def testSlice(self):
|
|
v = self.create_variable([1., 2., 3.])
|
|
self.assertEqual(v[1], 2.)
|
|
v[2].assign(4.)
|
|
self.assertAllEqual(v, [1., 2., 4.])
|
|
|
|
# ==== End Variable interface ===
|
|
|
|
# ==== Begin ResourceVariable interface ===
|
|
def testHandle(self):
|
|
v = self.create_variable()
|
|
self.assertIsInstance(v.handle, tensor.Tensor)
|
|
self.assertEqual(v.handle.dtype, dtypes.resource)
|
|
|
|
def testInGraphMode(self):
|
|
# This is protected but used in a lot of places internally.
|
|
v = self.create_variable()
|
|
self.assertFalse(v._in_graph_mode)
|
|
|
|
def testUniqueId(self):
|
|
# This is used in optimizer as part of slot variable key.
|
|
v = self.create_variable()
|
|
w = self.create_variable()
|
|
self.assertNotEqual(v._unique_id, w._unique_id)
|
|
|
|
def testIsResourceVariable(self):
|
|
v = self.create_variable()
|
|
self.assertTrue(resource_variable_ops.is_resource_variable(v))
|
|
# ==== End ResourceVariable interface ===
|
|
|
|
@combinations.generate(combinations.combine(mode="graph"))
|
|
def testAsGraphElement(self):
|
|
g = ops.Graph()
|
|
with g.as_default():
|
|
v = self.create_variable(1.)
|
|
g.finalize()
|
|
self.evaluate(v.initializer)
|
|
# _as_graph_element shouldn't create new operations.
|
|
self.assertEqual(self.evaluate(v._as_graph_element()), 1.)
|
|
|
|
|
|
class DistributedVariableInterfaceTest(_VariableInterfaceTestBase):
|
|
|
|
def create_variable(self, initial_value=1., **kwargs):
|
|
variables = []
|
|
for device in self.devices:
|
|
with ops.device(device):
|
|
variables.append(
|
|
variables_lib.Variable(initial_value, **kwargs))
|
|
return values_v2.DistributedVariable(variables)
|
|
|
|
|
|
# Prevent the base class from running.
|
|
del _VariableInterfaceTestBase
|
|
|
|
|
|
@combinations.generate(
|
|
combinations.combine(
|
|
strategy=[
|
|
strategy_combinations.tpu_strategy,
|
|
strategy_combinations.mirrored_strategy_with_two_cpus,
|
|
strategy_combinations.mirrored_strategy_with_two_gpus,
|
|
],
|
|
enable_packed_handle=[True, False],
|
|
tf_function=[combinations.tf_function, combinations.no_tf_function]))
|
|
class DistributedVariableTest(test.TestCase, parameterized.TestCase):
|
|
|
|
def create_variable(self, strategy, initial_value, enable_packed_handle,
|
|
**kwargs):
|
|
variables = []
|
|
for device in strategy.extended.parameter_devices:
|
|
with ops.device(device):
|
|
variables.append(variables_lib.Variable(initial_value, **kwargs))
|
|
return values_v2.DistributedVariable(
|
|
variables, enable_packed_handle=enable_packed_handle)
|
|
|
|
def assertReplica(self, distributed_var, values):
|
|
for var, value in zip(distributed_var._variables, values):
|
|
self.assertAllEqual(var, value)
|
|
|
|
def testRead(self, strategy, enable_packed_handle, tf_function):
|
|
v = self.create_variable(strategy, 0., enable_packed_handle)
|
|
|
|
with ops.device(strategy.extended.parameter_devices[0]):
|
|
v.assign(1.)
|
|
with ops.device(strategy.extended.parameter_devices[1]):
|
|
v.assign(2.)
|
|
|
|
@tf_function
|
|
def read_device0():
|
|
with ops.device(strategy.extended.parameter_devices[0]):
|
|
return v.read_value(), v.value()
|
|
|
|
@tf_function
|
|
def read_device1():
|
|
with ops.device(strategy.extended.parameter_devices[1]):
|
|
return v.read_value(), v.value()
|
|
|
|
@tf_function
|
|
def read_other_device():
|
|
with ops.device("CPU:0"):
|
|
return v.read_value(), v.value()
|
|
|
|
self.assertAllEqual(read_device0(), [1., 1.])
|
|
self.assertAllEqual(read_device1(), [2., 2.])
|
|
self.assertAllEqual(read_other_device(), [1., 1.])
|
|
|
|
def testAssign(self, strategy, enable_packed_handle, tf_function):
|
|
v = self.create_variable(strategy, 0., enable_packed_handle)
|
|
|
|
@tf_function
|
|
def update_device0():
|
|
with ops.device(strategy.extended.parameter_devices[0]):
|
|
v.assign(1.)
|
|
|
|
@tf_function
|
|
def update_device1():
|
|
with ops.device(strategy.extended.parameter_devices[1]):
|
|
v.assign(2.)
|
|
|
|
update_device0()
|
|
update_device1()
|
|
self.assertReplica(v, [1., 2.])
|
|
|
|
with ops.device("CPU:0"):
|
|
# Update the primary replica.
|
|
v.assign(3.)
|
|
self.assertReplica(v, [3., 2.])
|
|
|
|
def testStrategyRun(self, strategy, enable_packed_handle, tf_function):
|
|
if (test_util.is_tpu_strategy(strategy) and
|
|
tf_function is combinations.no_tf_function):
|
|
self.skipTest("tpu doesn't support eager")
|
|
v = self.create_variable(strategy, 0., enable_packed_handle)
|
|
|
|
@tf_function
|
|
def update(per_replica):
|
|
v.assign(per_replica)
|
|
|
|
@tf_function
|
|
def read():
|
|
return v.read_value()
|
|
|
|
strategy.run(
|
|
update, args=(test_util.create_per_replica(strategy, [1., 2.]),))
|
|
self.assertReplica(v, [1., 2.])
|
|
self.assertAllEqual(
|
|
test_util.gather(strategy, strategy.run(read)), [1., 2.])
|
|
|
|
|
|
def _make_index_slices(values, indices, dense_shape=None):
|
|
if dense_shape:
|
|
dense_shape = array_ops.identity(dense_shape)
|
|
return indexed_slices.IndexedSlices(
|
|
array_ops.identity(values), array_ops.identity(indices), dense_shape)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
test_util.main()
|