1839 lines
70 KiB
Python
1839 lines
70 KiB
Python
# Copyright 2018 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.
|
|
# ==============================================================================
|
|
"""Various classes representing distributed values."""
|
|
|
|
import copy
|
|
from typing import Optional
|
|
import weakref
|
|
|
|
from tensorflow.core.protobuf import struct_pb2
|
|
from tensorflow.python.distribute import device_util
|
|
from tensorflow.python.distribute import distribute_lib
|
|
from tensorflow.python.distribute import packed_distributed_variable as packed
|
|
from tensorflow.python.distribute import reduce_util
|
|
from tensorflow.python.distribute import values_util
|
|
from tensorflow.python.eager import context
|
|
from tensorflow.python.eager import record
|
|
from tensorflow.python.framework import composite_tensor
|
|
from tensorflow.python.framework import device as pydev
|
|
from tensorflow.python.framework import dtypes
|
|
from tensorflow.python.framework import ops
|
|
from tensorflow.python.framework import tensor as tensor_lib
|
|
from tensorflow.python.framework import tensor_conversion_registry
|
|
from tensorflow.python.framework import tensor_shape
|
|
from tensorflow.python.framework import tensor_util
|
|
from tensorflow.python.framework import type_spec
|
|
from tensorflow.python.ops import array_ops
|
|
from tensorflow.python.ops import control_flow_ops
|
|
from tensorflow.python.ops import math_ops
|
|
from tensorflow.python.ops import resource_variable_ops
|
|
from tensorflow.python.ops import variable_scope as vs
|
|
from tensorflow.python.ops import variables as variables_lib
|
|
from tensorflow.python.saved_model import nested_structure_coder
|
|
from tensorflow.python.trackable import base as trackable
|
|
from tensorflow.python.training.saving import saveable_object
|
|
from tensorflow.python.types import core
|
|
from tensorflow.python.types import distribute as ds_types
|
|
from tensorflow.python.types import trace
|
|
|
|
|
|
def _on_write_update_replica(var, update_fn, value, **kwargs):
|
|
"""Updates variables with ON_WRITE synchronization in replica context."""
|
|
if var.aggregation == vs.VariableAggregation.NONE:
|
|
return update_fn(var._get_on_device_or_primary(), value, **kwargs) # pylint: disable=protected-access
|
|
|
|
if not distribute_lib.get_strategy().extended._use_merge_call(): # pylint: disable=protected-access
|
|
# Don't allow MEAN with non float dtype, since it may cause unexpected
|
|
# precision loss. Python3 and NumPy automatically upcast integers to
|
|
# float in division, but we should always preserve the type.
|
|
if var.aggregation == vs.VariableAggregation.MEAN and (
|
|
not var.dtype.is_floating) and tensor_util.is_tf_type(value):
|
|
raise ValueError(
|
|
"Cannot update non-float variables with "
|
|
"tf.VariableAggregation.MEAN aggregation in replica context. "
|
|
"Either change the variable dtype to float or update it in "
|
|
"cross-replica context.")
|
|
|
|
aggregated_value = apply_aggregation_replica_context(
|
|
value, var.aggregation, var)
|
|
values_util.mark_as_unsaveable()
|
|
|
|
return distribute_lib.get_replica_context()._update( # pylint: disable=protected-access
|
|
var,
|
|
update_fn,
|
|
args=(aggregated_value,),
|
|
kwargs=kwargs,
|
|
group=True)
|
|
|
|
else:
|
|
|
|
def merge_fn(strategy, value, **kwargs):
|
|
"""Aggregate values and update all variables in cross replica context."""
|
|
# Don't allow MEAN with non float dtype, since it may cause unexpected
|
|
# precision loss. Python3 and NumPy automatically upcast integers to
|
|
# float in division, but we should always preserve the type.
|
|
#
|
|
# Note that to be backward compatible we allow the case when the value
|
|
# is *always* the same on each replica. I.E. value is not a
|
|
# PerReplica. Refer to regroup() to see how values are grouped.
|
|
if var.aggregation == vs.VariableAggregation.MEAN and (
|
|
not var.dtype.is_floating) and isinstance(value, PerReplica):
|
|
raise ValueError(
|
|
"Cannot update non-float variables with "
|
|
"tf.VariableAggregation.MEAN aggregation in replica context. "
|
|
"Either change the variable dtype to float or update it in "
|
|
"cross-replica context.")
|
|
|
|
assert strategy == var.distribute_strategy
|
|
v = values_util.apply_aggregation(strategy, value, var.aggregation, var)
|
|
return var._update_cross_replica(update_fn, v, **kwargs) # pylint: disable=protected-access
|
|
|
|
return distribute_lib.get_replica_context().merge_call(
|
|
merge_fn, args=(value,), kwargs=kwargs)
|
|
|
|
|
|
def apply_aggregation_replica_context(value, aggregation, destinations):
|
|
"""Aggregate `value` to `destinations` as specified by `aggregation`."""
|
|
# if it is a python literal, return without aggregation
|
|
if isinstance(value, DistributedValues):
|
|
raise TypeError(
|
|
"Cannot use DistributedValues to update variables in replica context.")
|
|
if not tensor_util.is_tf_type(value):
|
|
return value
|
|
|
|
if aggregation == vs.VariableAggregation.ONLY_FIRST_REPLICA:
|
|
# Switch to cross-replica context to broadcast
|
|
def merge_fn(strategy, value):
|
|
return strategy.extended.broadcast_to(
|
|
strategy.experimental_local_results(value)[0],
|
|
destinations=destinations)
|
|
|
|
return distribute_lib.get_replica_context().merge_call(
|
|
merge_fn, args=(value,))
|
|
|
|
else:
|
|
reduce_op = reduce_util.ReduceOp.from_variable_aggregation(aggregation)
|
|
aggregated_value = distribute_lib.get_strategy( # pylint: disable=protected-access
|
|
).extended._replica_ctx_all_reduce(reduce_op, value)
|
|
return aggregated_value
|
|
|
|
|
|
class DistributedValues(ds_types.DistributedValues):
|
|
"""Base class for representing distributed values."""
|
|
|
|
def __init__(self, values):
|
|
"""Should only be called by subclass __init__."""
|
|
self._values = tuple(values)
|
|
|
|
def _get(self):
|
|
"""Returns the value for the current device or raises a ValueError."""
|
|
replica_id = values_util.get_current_replica_id_as_int()
|
|
if replica_id is None:
|
|
return self._get_cross_replica()
|
|
else:
|
|
return self._values[replica_id]
|
|
|
|
def _get_cross_replica(self):
|
|
raise NotImplementedError(
|
|
"DistributedValues._get_cross_replica should be implemented by "
|
|
"sub-classes which support cross-replica accesses. "
|
|
f"Type name is {type(self)}"
|
|
)
|
|
|
|
def _get_on_device_or_primary(self):
|
|
"""Returns value in same replica or device if possible, else the _primary."""
|
|
replica_id = values_util.get_current_replica_id_as_int()
|
|
if replica_id is None:
|
|
# Try to find a value on the current device.
|
|
current_device = device_util.canonicalize(device_util.current())
|
|
for value in self._values:
|
|
if device_util.canonicalize(value.device) == current_device:
|
|
return value
|
|
return self._primary
|
|
else:
|
|
return self._values[replica_id]
|
|
|
|
@property
|
|
def _primary(self):
|
|
"""Returns a representative component."""
|
|
return self._values[0]
|
|
|
|
@property
|
|
def _devices(self):
|
|
return tuple(v.device for v in self._values)
|
|
|
|
def __str__(self):
|
|
debug_str = ",\n".join(
|
|
" %d: %s" % (i, v) for i, v in enumerate(self._values))
|
|
return "%s:{\n%s\n}" % (self.__class__.__name__, debug_str)
|
|
|
|
def __repr__(self):
|
|
debug_repr = ",\n".join(
|
|
" %d: %r" % (i, v) for i, v in enumerate(self._values))
|
|
return "%s:{\n%s\n}" % (self.__class__.__name__, debug_repr)
|
|
|
|
|
|
# NOTE(josh11b,apassos): It would be great if we could inspect the values this was
|
|
# initialized with and use that to generate the overloaded operators here.
|
|
# Unfortunately, Python's rules for special methods don't allow this, see
|
|
# https://docs.python.org/3/reference/datamodel.html#special-method-names
|
|
# "if a class defines a method named __getitem__(), and x is an instance of
|
|
# this class, then x[i] is roughly equivalent to type(x).__getitem__(x, i)."
|
|
# In particular, these special methods don't go through __getattr__, and
|
|
# it will only use those methods if they are defined in the class, not the
|
|
# object.
|
|
class DistributedDelegate(DistributedValues):
|
|
"""A map from device to values; acts as the same type as the values."""
|
|
|
|
def __getattr__(self, name):
|
|
# The '_use_resource_variables' and the attrs starts with '_self' are used
|
|
# for restoring the saved_model proto, and '_attribute_sentinel' is used for
|
|
# Layer tracking. At the point these attrs are queried, the variable has not
|
|
# been initialized. Thus it should not query those of the underlying
|
|
# components.
|
|
if name.startswith("_self_") or name in ("_use_resource_variables",
|
|
"_attribute_sentinel",
|
|
"_distributed_container"):
|
|
return super(DistributedDelegate, self).__getattr__(name)
|
|
|
|
# This allows copy.copy(DistributedDelegate). When copying an object,
|
|
# copy.copy doesn't invoke its __init__ method, instead it makes a new
|
|
# empty object, then copies the attributes over. copy.copy looks for
|
|
# attributes like "__getstate__" in case the object implements its custom
|
|
# copying. Since DistributedDelegate doesn't have those attributes defined,
|
|
# __getattr__ will be invoked, which tries to access "_values" attributes,
|
|
# but that doesn't exist either because this is an empty object, and again
|
|
# __getattr__ is invoked, leading to an infinite recursion.
|
|
if name == "_values":
|
|
raise AttributeError()
|
|
|
|
# TODO(priyag): This needs to be made robust against pitfalls from mix use
|
|
# __getattr__ and @property. See b/120402273.
|
|
return getattr(self._get(), name)
|
|
|
|
@property
|
|
def values(self):
|
|
"""Returns the per replica values."""
|
|
return self._values
|
|
|
|
def _get_as_operand(self):
|
|
"""Returns the value for operations for the current device.
|
|
|
|
Some implementations, e.g. `TPUMirroredVariable`, are not able to return the
|
|
value type within a replica context. They can, however, return a value that
|
|
can be used by the operations below.
|
|
"""
|
|
return self._get()
|
|
|
|
# pylint: disable=multiple-statements
|
|
def __add__(self, o):
|
|
return self._get_as_operand() + o
|
|
|
|
def __radd__(self, o):
|
|
return o + self._get_as_operand()
|
|
|
|
def __sub__(self, o):
|
|
return self._get_as_operand() - o
|
|
|
|
def __rsub__(self, o):
|
|
return o - self._get_as_operand()
|
|
|
|
def __mul__(self, o):
|
|
return self._get_as_operand() * o
|
|
|
|
def __rmul__(self, o):
|
|
return o * self._get_as_operand()
|
|
|
|
def __truediv__(self, o):
|
|
return self._get_as_operand() / o
|
|
|
|
def __rtruediv__(self, o):
|
|
return o / self._get_as_operand()
|
|
|
|
def __floordiv__(self, o):
|
|
return self._get_as_operand() // o
|
|
|
|
def __rfloordiv__(self, o):
|
|
return o // self._get_as_operand()
|
|
|
|
def __mod__(self, o):
|
|
return self._get_as_operand() % o
|
|
|
|
def __rmod__(self, o):
|
|
return o % self._get_as_operand()
|
|
|
|
def __lt__(self, o):
|
|
return self._get_as_operand() < o
|
|
|
|
def __le__(self, o):
|
|
return self._get_as_operand() <= o
|
|
|
|
def __gt__(self, o):
|
|
return self._get_as_operand() > o
|
|
|
|
def __ge__(self, o):
|
|
return self._get_as_operand() >= o
|
|
|
|
def __and__(self, o):
|
|
return self._get_as_operand() & o
|
|
|
|
def __rand__(self, o):
|
|
return o & self._get_as_operand()
|
|
|
|
def __or__(self, o):
|
|
return self._get_as_operand() | o
|
|
|
|
def __ror__(self, o):
|
|
return o | self._get_as_operand()
|
|
|
|
def __xor__(self, o):
|
|
return self._get_as_operand() ^ o
|
|
|
|
def __rxor__(self, o):
|
|
return o ^ self._get_as_operand()
|
|
|
|
def __getitem__(self, o):
|
|
return self._get_as_operand()[o]
|
|
|
|
def __pow__(self, o, modulo=None):
|
|
return pow(self._get_as_operand(), o, modulo)
|
|
|
|
def __rpow__(self, o):
|
|
return pow(o, self._get_as_operand())
|
|
|
|
def __invert__(self):
|
|
return ~self._get_as_operand()
|
|
|
|
def __neg__(self):
|
|
return -self._get_as_operand()
|
|
|
|
def __abs__(self):
|
|
return abs(self._get_as_operand())
|
|
|
|
def __div__(self, o):
|
|
try:
|
|
return self._get_as_operand().__div__(o)
|
|
except AttributeError:
|
|
# See https://docs.python.org/3/library/constants.html#NotImplemented
|
|
return NotImplemented
|
|
|
|
def __rdiv__(self, o):
|
|
try:
|
|
return self._get_as_operand().__rdiv__(o)
|
|
except AttributeError:
|
|
# See https://docs.python.org/3/library/constants.html#NotImplemented
|
|
return NotImplemented
|
|
|
|
def __matmul__(self, o):
|
|
try:
|
|
return self._get_as_operand().__matmul__(o)
|
|
except AttributeError:
|
|
# See https://docs.python.org/3/library/constants.html#NotImplemented
|
|
return NotImplemented
|
|
|
|
def __rmatmul__(self, o):
|
|
try:
|
|
return self._get_as_operand().__rmatmul__(o)
|
|
except AttributeError:
|
|
# See https://docs.python.org/3/library/constants.html#NotImplemented
|
|
return NotImplemented
|
|
|
|
# TODO(josh11b): Even more operator overloads.
|
|
|
|
|
|
class PerReplica(DistributedValues, composite_tensor.CompositeTensor,
|
|
ds_types.PerReplica):
|
|
"""Holds a map from replica to unsynchronized values."""
|
|
|
|
@property
|
|
def _type_spec(self):
|
|
return PerReplicaSpec(
|
|
*(type_spec.type_spec_from_value(v) for v in self._values))
|
|
|
|
@property
|
|
def values(self):
|
|
"""Returns the per replica values."""
|
|
return self._values
|
|
|
|
|
|
def _per_replica_to_tensor(var, dtype=None, name=None, as_ref=False):
|
|
"""Converts a `PerReplica` to a `Tensor`."""
|
|
del name
|
|
if dtype is not None and not dtype.is_compatible_with(var.dtype):
|
|
raise ValueError(
|
|
"Incompatible type conversion requested to type {!r} for variable "
|
|
"of type {!r}".format(dtype.name, var.dtype.name))
|
|
if as_ref:
|
|
raise NotImplementedError(
|
|
"PerReplica doesn't support being used as a reference.")
|
|
if (distribute_lib.in_cross_replica_context() or
|
|
not distribute_lib.has_strategy()):
|
|
raise ValueError("It looks like you are using a PerReplica object while "
|
|
"not inside a replica context, which is not supported. "
|
|
"Try running your op or function inside a replica context "
|
|
"by using `strategy.run`")
|
|
else:
|
|
replica_id = values_util.get_current_replica_id_as_int()
|
|
return var.values[replica_id]
|
|
|
|
# Register a conversion function to provide a useful error message when users
|
|
# try to use PerReplica values in the wrong contexts
|
|
tensor_conversion_registry.register_tensor_conversion_function(
|
|
PerReplica, _per_replica_to_tensor)
|
|
|
|
|
|
class PerReplicaSpec(type_spec.TypeSpec):
|
|
"""Type specification for a `PerReplica`."""
|
|
|
|
__slots__ = ["_value_specs"]
|
|
|
|
value_type = property(lambda self: PerReplica)
|
|
|
|
def __init__(self, *value_specs):
|
|
self._value_specs = tuple(value_specs)
|
|
|
|
def _serialize(self):
|
|
return self._value_specs
|
|
|
|
@property
|
|
def _component_specs(self):
|
|
return self._value_specs
|
|
|
|
def _to_components(self, value):
|
|
replica_context = distribute_lib.get_replica_context()
|
|
if replica_context is not None and replica_context.num_replicas_in_sync > 1:
|
|
raise ValueError(
|
|
"Flattening a PerReplica to components is not supported in replica "
|
|
"context.")
|
|
return value._values # pylint: disable=protected-access
|
|
|
|
def _from_components(self, tensor_list):
|
|
return PerReplica(tensor_list)
|
|
|
|
|
|
nested_structure_coder.register_codec(
|
|
nested_structure_coder.BuiltInTypeSpecCodec(
|
|
PerReplicaSpec, struct_pb2.TypeSpecProto.PER_REPLICA_SPEC
|
|
)
|
|
)
|
|
|
|
|
|
# Note that unlike PerReplica, Mirrored values inherit from
|
|
# DistributedDelegate and so can be used directly in cross-replica mode.
|
|
# TODO(tomhennigan) Should this extend CompositeTensor?
|
|
class Mirrored(DistributedDelegate, ds_types.Mirrored):
|
|
"""Holds a map from replica to values which are kept in sync."""
|
|
|
|
def _get_cross_replica(self):
|
|
return self._get_on_device_or_primary()
|
|
|
|
def _as_graph_element(self):
|
|
obj = self._get()
|
|
conv_fn = getattr(obj, "_as_graph_element", None)
|
|
if conv_fn and callable(conv_fn):
|
|
return conv_fn()
|
|
return obj
|
|
|
|
def _is_mirrored(self):
|
|
return True
|
|
|
|
|
|
class DistributedVarOp(object):
|
|
"""A class that looks like `tf.Operation`."""
|
|
|
|
def __init__(self, name, graph, traceback, typ):
|
|
self.name = name
|
|
self.graph = graph
|
|
self.traceback = traceback
|
|
self.type = typ
|
|
|
|
def __eq__(self, o):
|
|
if not isinstance(o, self.__class__):
|
|
raise NotImplementedError
|
|
return (self.name == o.name and self.graph == o.graph and
|
|
self.traceback == o.traceback and self.type == o.type)
|
|
|
|
def __hash__(self):
|
|
return hash((self.name, self.graph, tuple(self.traceback), self.type))
|
|
|
|
|
|
# TODO(b/209081027): Remove this once Variable is a CompositeTensor.
|
|
class DistributedVariableTraceType(trace.TraceType):
|
|
"""TraceType of DistributedVariable objects."""
|
|
|
|
def __init__(self, distributed_variable):
|
|
self.distributed_variable = distributed_variable
|
|
self.components = (tuple(distributed_variable.shape.as_list()),
|
|
distributed_variable.dtype)
|
|
|
|
def is_subtype_of(self, other):
|
|
return self == other
|
|
|
|
def most_specific_common_supertype(self, others):
|
|
return self if all(self == other for other in others) else None
|
|
|
|
def placeholder_value(self, placeholder_context=None):
|
|
return self.distributed_variable
|
|
|
|
def to_tensors(self, value):
|
|
return []
|
|
|
|
def cast(self, value, _):
|
|
return value
|
|
|
|
def __hash__(self) -> int:
|
|
return hash(self.components)
|
|
|
|
def __eq__(self, other) -> bool:
|
|
if not isinstance(other, DistributedVariableTraceType):
|
|
return False
|
|
|
|
return self.components == other.components
|
|
|
|
|
|
class DistributedVariable(DistributedDelegate, variables_lib.Variable,
|
|
core.Tensor):
|
|
"""Holds a map from replica to variables."""
|
|
|
|
def __init__(self, strategy, values, aggregation, var_policy=None):
|
|
if (aggregation == variables_lib.VariableAggregation.MEAN and
|
|
not values[0].dtype.is_floating):
|
|
raise ValueError(
|
|
"creating distributed tf.Variable with aggregation=MEAN and a "
|
|
"non-floating dtype is not supported, please use a different "
|
|
"aggregation or dtype")
|
|
self._distribute_strategy = strategy
|
|
self._aggregation = aggregation
|
|
super(DistributedVariable, self).__init__(values)
|
|
self._common_name = self._primary.name.split(":")[0]
|
|
# Use a weakref to make it easy to map from the contained values
|
|
# to the container without introducing a reference cycle.
|
|
for v in values:
|
|
# ResourceVariable is a CompositeTensor. Attributes added to
|
|
# CompositeTensors will get lost through tf.nest packing and unpacking.
|
|
if isinstance(v, composite_tensor.CompositeTensor) and hasattr(
|
|
v, "handle"):
|
|
v.handle._distributed_container = weakref.ref(self) # pylint: disable=protected-access
|
|
else:
|
|
v._distributed_container = weakref.ref(self) # pylint: disable=protected-access
|
|
|
|
# Packed variable is used to reduce the overhead of function execution.
|
|
# For a DistributedVariable, only one variable handle is captured into a
|
|
# function graph. It's only supported in eager mode.
|
|
if ops.executing_eagerly_outside_functions() and getattr(
|
|
strategy, "_enable_packed_variable_in_eager_mode", False):
|
|
name = "%s/packed/" % self._common_name
|
|
if hasattr(values[0], "_vars"):
|
|
# Handle when the resource variables are "nested" underneath another
|
|
# layer of values, e.g., TPUReplicatedVariable, by packing all them
|
|
# together and pushing the packed var down a level
|
|
# pylint: disable=protected-access
|
|
packed_var = packed.PackedDistributedVariable(
|
|
sum((value._vars for value in values), []), name=name)
|
|
for value in values:
|
|
value._packed_var = packed_var
|
|
self._packed_var = None
|
|
# pylint: enable=protected-access
|
|
else:
|
|
self._packed_var = packed.PackedDistributedVariable(values, name=name)
|
|
else:
|
|
self._packed_var = None
|
|
|
|
# tf.keras keeps track of variables initialized using this attribute. When
|
|
# tf.keras gets the default session, it initializes all uninitialized vars.
|
|
# We need to make _keras_initialized a member of DistributedVariable because
|
|
# without this it will use `__getattr__` which will delegate to a component
|
|
# variable.
|
|
self._keras_initialized = False
|
|
# Typically, a `DistributedVariable`'s initializer is composed of the
|
|
# initializers of the components variables. However, in some cases, such as
|
|
# when restoring from a checkpoint, we may set the _initializer_op
|
|
# property on the entire `DistributedVariable`.
|
|
self._initializer_op = None
|
|
# Set a VariablePolicy which decides how we replicate/aggregate the given
|
|
# variable.
|
|
self._policy = var_policy
|
|
|
|
def __deepcopy__(self, memo):
|
|
"""Perform a deepcopy of the `DistributedVariable`.
|
|
|
|
Unlike the deepcopy of a regular tf.Variable, this keeps the original
|
|
strategy and devices of the `DistributedVariable`. To avoid confusion
|
|
with the behavior of deepcopy on a regular `Variable` (which does
|
|
copy into new devices), we only allow a deepcopy of a `DistributedVariable`
|
|
within its originating strategy scope.
|
|
|
|
Args:
|
|
memo: The memoization object for `deepcopy`.
|
|
|
|
Returns:
|
|
A deep copy of the current `DistributedVariable`.
|
|
|
|
Raises:
|
|
RuntimeError: If trying to deepcopy into a different strategy.
|
|
"""
|
|
with distribute_lib.enter_or_assert_strategy(self._distribute_strategy):
|
|
new_values = []
|
|
|
|
for value in self._values:
|
|
with ops.device(value.device):
|
|
new_values.append(copy.deepcopy(value, memo))
|
|
|
|
copied_variable = type(self)(
|
|
strategy=self._distribute_strategy,
|
|
values=new_values,
|
|
aggregation=self._aggregation,
|
|
var_policy=copy.deepcopy(self._policy, memo))
|
|
|
|
memo[id(self)] = copied_variable
|
|
|
|
return copied_variable
|
|
|
|
def _use_packed_variable(self):
|
|
# Don't use packed variable when under a SaveContext to avoid explicit
|
|
# device placement on variable consuming ops.
|
|
return self._packed_var is not None and (
|
|
not values_util.is_saving_non_distributed())
|
|
|
|
def is_initialized(self, name=None):
|
|
"""Identifies if all the component variables are initialized.
|
|
|
|
Args:
|
|
name: Name of the final `logical_and` op.
|
|
|
|
Returns:
|
|
The op that evaluates to True or False depending on if all the
|
|
component variables are initialized.
|
|
"""
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.is_initialized()
|
|
if self._use_packed_variable():
|
|
return self._packed_var.is_initialized()
|
|
result = self._primary.is_initialized()
|
|
# We iterate through the list of values except the last one to allow us to
|
|
# name the final `logical_and` op the same name that is passed by the user
|
|
# to the `is_initialized` op. For distributed variables, the
|
|
# `is_initialized` op is a `logical_and` op.
|
|
for v in self._values[1:-1]:
|
|
result = math_ops.logical_and(result, v.is_initialized())
|
|
result = math_ops.logical_and(
|
|
result, self._values[-1].is_initialized(), name=name)
|
|
return result
|
|
|
|
@property
|
|
def initializer(self):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.initializer
|
|
if self._initializer_op:
|
|
init_op = self._initializer_op
|
|
else:
|
|
# return grouped ops of all the var initializations of component values of
|
|
# the mirrored variable
|
|
init_op = control_flow_ops.group(
|
|
tuple(v.initializer for v in self._values))
|
|
return init_op
|
|
|
|
def initialized_value(self):
|
|
return self._get_on_device_or_primary().initialized_value()
|
|
|
|
def _is_mirrored(self):
|
|
return (self._policy is not None) and (self._policy._is_mirrored()) # pylint: disable=protected-access
|
|
|
|
@property
|
|
def initial_value(self):
|
|
return self._get_on_device_or_primary().initial_value
|
|
|
|
@property
|
|
def constraint(self):
|
|
return self._primary.constraint
|
|
|
|
@property
|
|
def graph(self):
|
|
return self._primary.graph
|
|
|
|
@property
|
|
def _shared_name(self):
|
|
return self._common_name
|
|
|
|
@property
|
|
def _unique_id(self):
|
|
return self._primary._unique_id # pylint: disable=protected-access
|
|
|
|
@property
|
|
def _graph_key(self):
|
|
"""Lets Optimizers know which graph this variable is from."""
|
|
return self._primary._graph_key # pylint: disable=protected-access
|
|
|
|
@property
|
|
def name(self):
|
|
return self._primary.name
|
|
|
|
@property
|
|
def dtype(self):
|
|
return self._primary.dtype
|
|
|
|
@property
|
|
def shape(self):
|
|
return self._primary.shape
|
|
|
|
@property
|
|
def synchronization(self):
|
|
return self._primary.synchronization
|
|
|
|
@property
|
|
def aggregation(self):
|
|
return self._aggregation
|
|
|
|
@property
|
|
def _packed_variable(self):
|
|
if self._use_packed_variable():
|
|
return self._packed_var
|
|
return None
|
|
|
|
@property
|
|
def handle(self):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.handle
|
|
replica_id = values_util.get_current_replica_id_as_int()
|
|
if replica_id is None:
|
|
raise ValueError(
|
|
"DistributedVariable.handle is not available outside the replica "
|
|
"context or a `tf.distribute.Strategy.update()` call.")
|
|
else:
|
|
if self._use_packed_variable():
|
|
return self._packed_var.handle
|
|
return self._values[replica_id].handle
|
|
|
|
def eval(self, session=None):
|
|
return self._get_on_device_or_primary().eval(session)
|
|
|
|
@property
|
|
def _save_slice_info(self):
|
|
return self._primary._save_slice_info # pylint: disable=protected-access
|
|
|
|
def _get_save_slice_info(self):
|
|
return self._primary._get_save_slice_info() # pylint: disable=protected-access
|
|
|
|
def _set_save_slice_info(self, save_slice_info):
|
|
for v in self._values:
|
|
v._set_save_slice_info(save_slice_info) # pylint: disable=protected-access
|
|
|
|
@property
|
|
def device(self):
|
|
return self._get_on_device_or_primary().device
|
|
|
|
@property
|
|
def trainable(self):
|
|
return self._primary.trainable
|
|
|
|
@property
|
|
def distribute_strategy(self):
|
|
return self._distribute_strategy
|
|
|
|
def get_shape(self) -> tensor_shape.TensorShape:
|
|
return self._primary.get_shape()
|
|
|
|
def to_proto(self, export_scope=None):
|
|
return self._primary.to_proto(export_scope=export_scope)
|
|
|
|
@property
|
|
def op(self) -> ops.Operation:
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.op
|
|
# We want cross-replica code that does some var.op.X calls
|
|
# to work (even if the current device isn't in self._devices), but
|
|
# other uses of var.op in a cross-replica context to fail.
|
|
if distribute_lib.in_cross_replica_context():
|
|
return DistributedVarOp(self._primary.op.name, self._primary.op.graph,
|
|
self._primary.op.traceback, self._primary.op.type)
|
|
return self._get().op
|
|
|
|
@property
|
|
def _in_graph_mode(self):
|
|
return self._primary._in_graph_mode # pylint: disable=protected-access
|
|
|
|
def _get_replica(self, replica_id):
|
|
"""Returns the value on a device with the given replica_id."""
|
|
value = self._values[replica_id]
|
|
if self._use_packed_variable():
|
|
return self._packed_var.on_device(value.device)
|
|
else:
|
|
return value
|
|
|
|
def _get(self):
|
|
"""Returns the value for the current device or raises a ValueError."""
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary
|
|
replica_id = values_util.get_current_replica_id_as_int()
|
|
if replica_id is None:
|
|
return self._get_cross_replica()
|
|
else:
|
|
return self._get_replica(replica_id)
|
|
|
|
def _get_on_device_or_primary(self):
|
|
"""Returns value in same replica or device if possible, else the _primary."""
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary
|
|
replica_id = values_util.get_current_replica_id_as_int()
|
|
if replica_id is None:
|
|
# Try to find a value on the current device.
|
|
current_device = device_util.canonicalize(device_util.current())
|
|
for i, value in enumerate(self._values):
|
|
if device_util.canonicalize(value.device) == current_device:
|
|
return self._get_replica(i)
|
|
return self._get_replica(0)
|
|
else:
|
|
return self._get_replica(replica_id)
|
|
|
|
def read_value(self):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.read_value()
|
|
with distribute_lib.enter_or_assert_strategy(self._distribute_strategy):
|
|
return array_ops.identity(self._get())
|
|
|
|
def value(self):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.value()
|
|
if self._policy:
|
|
return self._policy.value(self)
|
|
return self._get_on_device_or_primary().value()
|
|
|
|
def numpy(self):
|
|
if context.executing_eagerly():
|
|
return self.read_value().numpy()
|
|
else:
|
|
raise NotImplementedError("DistributedVariable.numpy() is only available "
|
|
"when eager execution is enabled.")
|
|
|
|
def assign_sub(self, value, use_locking=False, name=None, read_value=True):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.assign_sub(value, use_locking, name, read_value)
|
|
if self._policy:
|
|
return self._policy.assign_sub(
|
|
self,
|
|
value,
|
|
use_locking=use_locking,
|
|
name=name,
|
|
read_value=read_value)
|
|
return values_util.on_write_assign_sub(
|
|
self, value, use_locking=use_locking, name=name, read_value=read_value)
|
|
|
|
def assign_add(self, value, use_locking=False, name=None, read_value=True):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.assign_add(value, use_locking, name, read_value)
|
|
if self._policy:
|
|
return self._policy.assign_add(
|
|
self,
|
|
value,
|
|
use_locking=use_locking,
|
|
name=name,
|
|
read_value=read_value)
|
|
return values_util.on_write_assign_add(
|
|
self, value, use_locking=use_locking, name=name, read_value=read_value)
|
|
|
|
def assign(self, value, use_locking=False, name=None, read_value=True):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.assign(value, use_locking, name, read_value)
|
|
if self._policy:
|
|
return self._policy.assign(
|
|
self,
|
|
value,
|
|
use_locking=use_locking,
|
|
name=name,
|
|
read_value=read_value)
|
|
return values_util.on_write_assign(
|
|
self, value, use_locking=use_locking, name=name, read_value=read_value)
|
|
|
|
def scatter_sub(self, sparse_delta, use_locking=False, name=None):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.scatter_sub(sparse_delta, use_locking, name)
|
|
if self._policy:
|
|
return self._policy.scatter_sub(
|
|
self, sparse_delta, use_locking=use_locking, name=name)
|
|
return values_util.scatter_sub(
|
|
self, sparse_delta, use_locking=use_locking, name=name)
|
|
|
|
def scatter_add(self, sparse_delta, use_locking=False, name=None):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.scatter_add(sparse_delta, use_locking, name)
|
|
if self._policy:
|
|
return self._policy.scatter_add(
|
|
self, sparse_delta, use_locking=use_locking, name=name)
|
|
return values_util.scatter_add(
|
|
self, sparse_delta, use_locking=use_locking, name=name)
|
|
|
|
def scatter_mul(self, sparse_delta, use_locking=False, name=None):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.scatter_mul(sparse_delta, use_locking, name)
|
|
if self._policy:
|
|
return self._policy.scatter_mul(
|
|
self, sparse_delta, use_locking=use_locking, name=name)
|
|
return values_util.scatter_mul(
|
|
self, sparse_delta, use_locking=use_locking, name=name)
|
|
|
|
def scatter_div(self, sparse_delta, use_locking=False, name=None):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.scatter_div(sparse_delta, use_locking, name)
|
|
if self._policy:
|
|
return self._policy.scatter_div(
|
|
self, sparse_delta, use_locking=use_locking, name=name)
|
|
return values_util.scatter_div(
|
|
self, sparse_delta, use_locking=use_locking, name=name)
|
|
|
|
def scatter_min(self, sparse_delta, use_locking=False, name=None):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.scatter_min(sparse_delta, use_locking, name)
|
|
if self._policy:
|
|
return self._policy.scatter_min(
|
|
self, sparse_delta, use_locking=use_locking, name=name)
|
|
return values_util.scatter_min(
|
|
self, sparse_delta, use_locking=use_locking, name=name)
|
|
|
|
def scatter_max(self, sparse_delta, use_locking=False, name=None):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.scatter_max(sparse_delta, use_locking, name)
|
|
if self._policy:
|
|
return self._policy.scatter_max(
|
|
self, sparse_delta, use_locking=use_locking, name=name)
|
|
return values_util.scatter_max(
|
|
self, sparse_delta, use_locking=use_locking, name=name)
|
|
|
|
def scatter_update(self, sparse_delta, use_locking=False, name=None):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.scatter_update(sparse_delta, use_locking, name)
|
|
if self._policy:
|
|
return self._policy.scatter_update(
|
|
self, sparse_delta, use_locking=use_locking, name=name)
|
|
return values_util.scatter_update(
|
|
self, sparse_delta, use_locking=use_locking, name=name)
|
|
|
|
def __tf_tracing_type__(self, _):
|
|
return DistributedVariableTraceType(self)
|
|
|
|
def _gather_saveables_for_checkpoint(self):
|
|
"""Overrides Trackable method.
|
|
|
|
This allows both name-based and object-based save and restore of
|
|
DistributedVariables.
|
|
|
|
Returns:
|
|
A dictionary mapping attribute names to `SaveableObject` factories.
|
|
"""
|
|
|
|
def _saveable_factory(name=self._common_name):
|
|
return _DistributedVariableSaveable(self, self._primary, name)
|
|
|
|
return {trackable.VARIABLE_VALUE_KEY: _saveable_factory}
|
|
|
|
def _as_graph_element(self):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary._as_graph_element() # pylint: disable=protected-access
|
|
if self._policy:
|
|
return self._policy._as_graph_element(self) # pylint: disable=protected-access
|
|
|
|
raise NotImplementedError(
|
|
"DistributedVariable._as_graph_element requires a valid "
|
|
"VariablePolicy. Please set the policy via the `var_policy` argument "
|
|
"in the constructor, or override this method in sub-classes which "
|
|
"support cross-replica accesses. "
|
|
f"Type name is {type(self)}"
|
|
)
|
|
|
|
def _get_cross_replica(self):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary
|
|
if self._policy:
|
|
return self._policy._get_cross_replica(self) # pylint: disable=protected-access
|
|
|
|
raise NotImplementedError(
|
|
"DistributedVariable._get_cross_replica requires a valid "
|
|
"VariablePolicy. Please set the policy via the `var_policy` argument "
|
|
"in the constructor, or override this method in sub-classes which "
|
|
"support cross-replica accesses. "
|
|
f"Type name is {type(self)}"
|
|
)
|
|
|
|
def _update_cross_replica(self, update_fn, value, **kwargs):
|
|
"""Applies updates across replicas.
|
|
|
|
Args:
|
|
update_fn: A callable to pass to `strategy.extended.update` to update the
|
|
variable. It should has the same signature as `Variable.assign()`.
|
|
value: value to be passed to `update_fn`.
|
|
**kwargs: remaining arguments to `update_fn`.
|
|
|
|
Returns:
|
|
Updated variable or `tf.Operation`.
|
|
"""
|
|
values_util.mark_as_unsaveable()
|
|
return self.distribute_strategy.extended.update(
|
|
self, update_fn, args=(value,), kwargs=kwargs, group=True)
|
|
|
|
def _update_replica(self, update_fn, value, **kwargs):
|
|
"""Applies updates in one replica.
|
|
|
|
Args:
|
|
update_fn: A callable to update the variable. It should has the same
|
|
signature as `Variable.assign()`.
|
|
value: value to be passed to `update_fn`.
|
|
**kwargs: remaining arguments to `update_fn`.
|
|
|
|
Returns:
|
|
Updated variable or `tf.Operation`.
|
|
"""
|
|
if self._policy:
|
|
return self._policy._update_replica(self, update_fn, value, **kwargs) # pylint: disable=protected-access
|
|
raise NotImplementedError(
|
|
"DistributedVariable._update_replica requires a valid VariablePolicy. "
|
|
"Please set the policy via the `var_policy` argument in the "
|
|
"constructor, or override this method in sub-classes which support "
|
|
"cross-replica accesses. "
|
|
f"Type name is {type(self)}"
|
|
)
|
|
|
|
def _update(self, update_fn, value, **kwargs):
|
|
"""Applies updates depending on the context.
|
|
|
|
The method calls `_update_replica` in replica context,
|
|
`_update_cross_replica` in cross replica context, and `update_fn` in update
|
|
context.
|
|
|
|
If `read_value` is True, the method returns the updated Variable. If
|
|
`read_value` is False, the method returns the update `tf.Operation`.
|
|
|
|
Args:
|
|
update_fn: A callable to pass to `strategy.extended.update` to update the
|
|
variable. It should have the same signature as `Variable.assign()`.
|
|
value: value to be passed to `update_fn`.
|
|
**kwargs: keyword arguments to `update_fn`.
|
|
|
|
Returns:
|
|
Updated variable or `tf.Operation`.
|
|
|
|
"""
|
|
if values_util.is_saving_non_distributed():
|
|
return update_fn(self._primary, value, **kwargs)
|
|
with distribute_lib.enter_or_assert_strategy(self.distribute_strategy):
|
|
if distribute_lib.in_cross_replica_context():
|
|
update_replica_id = distribute_lib.get_update_replica_id()
|
|
if update_replica_id is not None:
|
|
replica_value = self._get_replica(update_replica_id)
|
|
return update_fn(replica_value, value, **kwargs)
|
|
return self._update_cross_replica(update_fn, value, **kwargs)
|
|
else:
|
|
values_util.assert_replica_context(self.distribute_strategy)
|
|
return self._update_replica(update_fn, value, **kwargs)
|
|
|
|
def _should_act_as_resource_variable(self):
|
|
"""Pass resource_variable_ops.is_resource_variable check."""
|
|
pass
|
|
|
|
def _dense_var_to_tensor(self, dtype=None, name=None, as_ref=False):
|
|
"""Converts a variable to a tensor."""
|
|
if values_util.is_saving_non_distributed():
|
|
return ops.convert_to_tensor(
|
|
self._primary, dtype=dtype, name=name, as_ref=as_ref)
|
|
with distribute_lib.enter_or_assert_strategy(self._distribute_strategy):
|
|
return ops.convert_to_tensor(
|
|
self._get(), dtype=dtype, name=name, as_ref=as_ref)
|
|
|
|
def __tf_tensor__(self,
|
|
dtype: Optional[dtypes.DType] = None,
|
|
name: Optional[str] = None) -> tensor_lib.Tensor:
|
|
return self._dense_var_to_tensor(dtype, name)
|
|
|
|
def _export_to_saved_model_graph(self,
|
|
object_map=None,
|
|
tensor_map=None,
|
|
options=None,
|
|
**kwargs):
|
|
# Initialize for self._primary first, so that obj_map[self._primary] and
|
|
# resource_map[self._primary.handle] contain mapped values.
|
|
resource_list = self._primary._export_to_saved_model_graph( # pylint:disable=protected-access
|
|
object_map=object_map,
|
|
tensor_map=tensor_map,
|
|
options=options,
|
|
**kwargs)
|
|
for v in [v for v in self._values if v != self._primary]:
|
|
if (options.experimental_variable_policy # pylint:disable=protected-access
|
|
._expand_distributed_variables()):
|
|
resource_list.extend(
|
|
v._export_to_saved_model_graph( # pylint:disable=protected-access
|
|
object_map=object_map,
|
|
tensor_map=tensor_map,
|
|
options=options,
|
|
**kwargs)) # pylint:disable=protected-access
|
|
else:
|
|
object_map[v] = object_map[self._primary]
|
|
tensor_map[v.handle] = tensor_map[self._primary.handle]
|
|
resource_list.append(v.handle)
|
|
object_map[self] = object_map[self._primary]
|
|
tensor_map[self] = tensor_map[self._primary.handle]
|
|
resource_list.append(self)
|
|
if self._packed_var is not None:
|
|
tensor_map[self._packed_var.packed_handle] = tensor_map[
|
|
self._primary.handle]
|
|
resource_list.append(self._packed_var.packed_handle)
|
|
return resource_list
|
|
|
|
def _copy_trackable_to_cpu(self, object_map):
|
|
"""For implementing `Trackable`."""
|
|
if self not in object_map:
|
|
# If not populated, initialize the cpu copy first.
|
|
op_device = pydev.DeviceSpec.from_string(self.device).replace(
|
|
device_type="CPU", device_index=0).to_string()
|
|
with ops.device(op_device):
|
|
new_var = resource_variable_ops.UninitializedVariable(
|
|
trainable=self.trainable,
|
|
shape=self.shape,
|
|
dtype=self.dtype,
|
|
name=self._shared_name,
|
|
distribute_strategy=self._distribute_strategy,
|
|
aggregation=self._aggregation) # pylint: disable=protected-access
|
|
object_map[self] = new_var
|
|
|
|
# Then copy value of self to the copy.
|
|
destination_var = object_map[self]
|
|
with ops.device(destination_var.device):
|
|
destination_var.assign(self.read_value())
|
|
|
|
def _write_object_proto(self, proto, options):
|
|
"""Update a SavedObject proto for the caller.
|
|
|
|
If a DistributedVariable object supports this method, it will be called when
|
|
saving with a pre-built `SavedObject` proto representing the object, plus an
|
|
instance of `SaveOptions`. This method is then free to modify that proto
|
|
instance.
|
|
|
|
`DistributedVariable` with `AUTO` or `ON_WRITE` synchronization optionally
|
|
write out information about their components to the
|
|
`experimental_distributed_variable_components` field of a
|
|
`SavedVariable` (depending on the `SaveOptions` variable policy).
|
|
|
|
Args:
|
|
proto: A pre-built `SavedObject` proto for this object. It is assumed this
|
|
will be a `SavedVariable` instance.
|
|
options: A `SaveOptions` instance.
|
|
"""
|
|
resource_variable_ops.write_object_proto_for_resource_variable(
|
|
self, proto, options)
|
|
# Set protos in the saved model such that distributed variables are
|
|
# correctly restored on COMPOSITE devices (otherwise, task:0/TPU:0).
|
|
values_util.write_object_proto(self, proto, options)
|
|
|
|
@property
|
|
def is_distributed_variable(self):
|
|
return True
|
|
|
|
def __tf_experimental_restore_capture__(
|
|
self, concrete_function, internal_capture):
|
|
graph = concrete_function.graph
|
|
# Add given distributed variable to captures with given placeholder.
|
|
graph.replace_capture(self, internal_capture)
|
|
record.record_operation(
|
|
"captured_value", [internal_capture], [self],
|
|
backward_function=lambda x: [x],
|
|
forward_function=lambda x: [x])
|
|
return self
|
|
|
|
|
|
# We extend from `saveable_object.SaveableObject` instead of
|
|
# `saveable_object_util.ResourceVariableSaveable` since we need to read the
|
|
# value of ONREAD variables when saving. `SaveableObject` provides a way to
|
|
# specify the function to run to get the value of the variable or tensor at
|
|
# saving time. We can use this for both ON_READ and ON_WRITE variables.
|
|
# TODO(b/164586507): Consolidate ON_WRITE and ON_READ saving/restoring logic
|
|
# if possible.
|
|
class _DistributedVariableSaveable(saveable_object.SaveableObject):
|
|
"""Class for defining how to restore a DistributedVariable."""
|
|
|
|
def __init__(self, distributed_variable, primary_variable, name):
|
|
self._distributed_variable = distributed_variable
|
|
if not self._distributed_variable._policy:
|
|
raise ValueError(
|
|
"The VariablePolicy of the argument `distributed_variable` must be "
|
|
"set to create a _DistributedVariableSaveable. Please set it via "
|
|
"the `var_policy` argument in the constructor of DistributedVariable."
|
|
)
|
|
tensor, spec = distributed_variable._policy.get_saveable(
|
|
distributed_variable, primary_variable, name)
|
|
super(_DistributedVariableSaveable, self).__init__(tensor, spec, name)
|
|
|
|
def restore(self, restored_tensors, restored_shapes):
|
|
"""Restore the same value into all variables."""
|
|
tensor, = restored_tensors
|
|
return self._distributed_variable._policy.get_restore_ops( # pylint: disable=protected-access
|
|
self._distributed_variable, tensor)
|
|
|
|
|
|
class _MirroredSaveable(saveable_object.SaveableObject):
|
|
"""Class for defining how to restore a MirroredVariable."""
|
|
|
|
def __init__(self, mirrored_variable, primary_variable, name):
|
|
self._mirrored_variable = mirrored_variable
|
|
tensor, spec = values_util.get_on_write_saveable(self._mirrored_variable,
|
|
primary_variable, name)
|
|
super(_MirroredSaveable, self).__init__(tensor, spec, name)
|
|
|
|
def restore(self, restored_tensors, restored_shapes):
|
|
"""Restore the same value into all variables."""
|
|
tensor, = restored_tensors
|
|
return values_util.get_on_write_restore_ops(self._mirrored_variable, tensor)
|
|
|
|
|
|
class MirroredVariable(DistributedVariable, Mirrored):
|
|
"""Holds a map from replica to variables whose values are kept in sync."""
|
|
|
|
def _is_mirrored(self):
|
|
return Mirrored._is_mirrored(self) # Use correct parent class.
|
|
|
|
def _update_replica(self, update_fn, value, **kwargs):
|
|
return _on_write_update_replica(self, update_fn, value, **kwargs)
|
|
|
|
def scatter_min(self, *args, **kwargs):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.scatter_min(*args, **kwargs)
|
|
if (self._aggregation != vs.VariableAggregation.ONLY_FIRST_REPLICA and
|
|
self._aggregation != vs.VariableAggregation.NONE):
|
|
raise NotImplementedError(
|
|
values_util.scatter_error_msg.format(
|
|
op_name="scatter_min", aggregation=self._aggregation))
|
|
return super(MirroredVariable, self).scatter_min(*args, **kwargs)
|
|
|
|
def scatter_max(self, *args, **kwargs):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.scatter_max(*args, **kwargs)
|
|
if (self._aggregation != vs.VariableAggregation.ONLY_FIRST_REPLICA and
|
|
self._aggregation != vs.VariableAggregation.NONE):
|
|
raise NotImplementedError(
|
|
values_util.scatter_error_msg.format(
|
|
op_name="scatter_max", aggregation=self._aggregation))
|
|
return super(MirroredVariable, self).scatter_max(*args, **kwargs)
|
|
|
|
def scatter_update(self, *args, **kwargs):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.scatter_update(*args, **kwargs)
|
|
if (self._aggregation != vs.VariableAggregation.ONLY_FIRST_REPLICA and
|
|
self._aggregation != vs.VariableAggregation.NONE):
|
|
raise NotImplementedError(
|
|
values_util.scatter_error_msg.format(
|
|
op_name="scatter_update", aggregation=self._aggregation))
|
|
return super(MirroredVariable, self).scatter_update(*args, **kwargs)
|
|
|
|
def _get_cross_replica(self):
|
|
# Return identity, to avoid directly exposing the variable to the user and
|
|
# allowing it to be modified by mistake.
|
|
return array_ops.identity(Mirrored._get_cross_replica(self))
|
|
|
|
def _as_graph_element(self):
|
|
return self._get_on_device_or_primary()._as_graph_element() # pylint: disable=protected-access
|
|
|
|
def _gather_saveables_for_checkpoint(self):
|
|
"""Overrides Trackable method.
|
|
|
|
This allows both name-based and object-based save and restore of
|
|
MirroredVariables.
|
|
|
|
Returns:
|
|
A dictionary mapping attribute names to `SaveableObject` factories.
|
|
"""
|
|
|
|
def _saveable_factory(name=self._common_name):
|
|
return _MirroredSaveable(self, self._primary, name)
|
|
|
|
return {trackable.VARIABLE_VALUE_KEY: _saveable_factory}
|
|
|
|
def _dense_var_to_tensor(self, dtype=None, name=None, as_ref=False):
|
|
"""Converts a variable to a tensor."""
|
|
# TODO(b/154017756): Make _dense_var_to_tensor consistent between ON_READ
|
|
# and ON_WRITE.
|
|
# Try to avoid assignments to and other mutations of MirroredVariable
|
|
# state except through a DistributionStrategy.extended.update() or any of
|
|
# the `assign*` and `scatter*` calls.
|
|
if as_ref:
|
|
# A TF 1.x case where the variable is a boolean variable and used like:
|
|
# tf.cond(v, true_fn, false_fn).
|
|
raise ValueError(
|
|
"You may be using variable created under distribute strategy in TF "
|
|
"1.x control flows. Try explicitly converting the variable to Tensor "
|
|
"using variable.read_value(), or switch to TF 2.x.")
|
|
return ops.convert_to_tensor(
|
|
self._get(), dtype=dtype, name=name, as_ref=as_ref)
|
|
|
|
|
|
class _SyncOnReadSaveable(saveable_object.SaveableObject):
|
|
"""Class for defining how to restore a SyncOnReadVariable."""
|
|
|
|
def __init__(self, sync_on_read_variable, name):
|
|
self._sync_on_read_variable = sync_on_read_variable
|
|
tensor, spec = values_util.get_on_read_saveable(
|
|
sync_on_read_variable, sync_on_read_variable._primary, name)
|
|
|
|
super(_SyncOnReadSaveable, self).__init__(tensor, spec, name)
|
|
|
|
def restore(self, restored_tensors, restored_shapes):
|
|
"""Restore the same value into all variables."""
|
|
tensor, = restored_tensors
|
|
return values_util.get_on_read_restore_ops(
|
|
self._sync_on_read_variable, tensor,
|
|
self._sync_on_read_variable.aggregation)
|
|
|
|
|
|
class SyncOnReadVariable(DistributedVariable):
|
|
"""Holds a map from replica to variables whose values are reduced on save."""
|
|
|
|
def _update_replica(self, update_fn, value, **kwargs):
|
|
return update_fn(self._get_on_device_or_primary(), value, **kwargs)
|
|
|
|
def _get(self):
|
|
"""Returns the value of SyncOnReadVariable based on surrounding context.
|
|
|
|
If called under a non-default replica-context, returns the corresponding
|
|
variable on that replica.
|
|
If called under default replica-context or cross-replica context, returns
|
|
the synced value.
|
|
"""
|
|
with distribute_lib.enter_or_assert_strategy(self._distribute_strategy):
|
|
return super(SyncOnReadVariable, self)._get()
|
|
|
|
# TODO(b/154017756): Make assign behavior in cross replica context consistent
|
|
# with MirroredVariable.
|
|
def assign_sub(self, value, use_locking=False, name=None, read_value=True):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.assign_sub(value, use_locking, name, read_value)
|
|
with distribute_lib.enter_or_assert_strategy(self._distribute_strategy):
|
|
if (distribute_lib.in_cross_replica_context() and
|
|
not values_util.in_replica_update_context()):
|
|
values_util.mark_as_unsaveable()
|
|
return values_util.on_read_assign_sub_cross_replica(
|
|
self, value, read_value=read_value)
|
|
else:
|
|
return super(SyncOnReadVariable,
|
|
self).assign_sub(value, use_locking, name, read_value)
|
|
|
|
def assign_add(self, value, use_locking=False, name=None, read_value=True):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.assign_add(value, use_locking, name, read_value)
|
|
with distribute_lib.enter_or_assert_strategy(self._distribute_strategy):
|
|
if (distribute_lib.in_cross_replica_context() and
|
|
not values_util.in_replica_update_context()):
|
|
values_util.mark_as_unsaveable()
|
|
return values_util.on_read_assign_add_cross_replica(
|
|
self, value, read_value=read_value)
|
|
else:
|
|
return super(SyncOnReadVariable,
|
|
self).assign_add(value, use_locking, name, read_value)
|
|
|
|
def assign(self, value, use_locking=False, name=None, read_value=True):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.assign(value, use_locking, name, read_value)
|
|
with distribute_lib.enter_or_assert_strategy(self._distribute_strategy):
|
|
if (distribute_lib.in_cross_replica_context() and
|
|
not values_util.in_replica_update_context()):
|
|
values_util.mark_as_unsaveable()
|
|
return values_util.on_read_assign_cross_replica(
|
|
self, value, read_value=read_value)
|
|
else:
|
|
return super(SyncOnReadVariable, self).assign(value, use_locking, name,
|
|
read_value)
|
|
|
|
def _scatter_not_implemented(self, method):
|
|
raise NotImplementedError(
|
|
f"Variables with `synchronization=ON_READ` doesn't support `{method}`")
|
|
|
|
def scatter_sub(self, *args, **kwargs):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.scatter_sub(*args, **kwargs)
|
|
self._scatter_not_implemented("scatter_sub")
|
|
|
|
def scatter_add(self, *args, **kwargs):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.scatter_add(*args, **kwargs)
|
|
self._scatter_not_implemented("scatter_add")
|
|
|
|
def scatter_mul(self, *args, **kwargs):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.scatter_mul(*args, **kwargs)
|
|
self._scatter_not_implemented("scatter_mul")
|
|
|
|
def scatter_div(self, *args, **kwargs):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.scatter_div(*args, **kwargs)
|
|
self._scatter_not_implemented("scatter_div")
|
|
|
|
def scatter_min(self, *args, **kwargs):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.scatter_min(*args, **kwargs)
|
|
self._scatter_not_implemented("scatter_min")
|
|
|
|
def scatter_max(self, *args, **kwargs):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.scatter_max(*args, **kwargs)
|
|
self._scatter_not_implemented("scatter_max")
|
|
|
|
def scatter_update(self, *args, **kwargs):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.scatter_update(*args, **kwargs)
|
|
self._scatter_not_implemented("scatter_update")
|
|
|
|
def value(self):
|
|
if distribute_lib.in_variable_sync_on_read_context():
|
|
raise NotImplementedError(
|
|
"call `variable.value()` inside variable_sync_on_read_context is not "
|
|
"supported")
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary.value()
|
|
with distribute_lib.enter_or_assert_strategy(self._distribute_strategy):
|
|
if (distribute_lib.in_cross_replica_context() and
|
|
not values_util.in_replica_update_context()):
|
|
if self._aggregation == vs.VariableAggregation.ONLY_FIRST_REPLICA:
|
|
return self._get_replica(0).value()
|
|
return self._get_cross_replica()
|
|
else:
|
|
# _get_on_device_or_primary() returns a Variable.
|
|
return self._get_on_device_or_primary().value()
|
|
|
|
def read_value(self):
|
|
if distribute_lib.in_variable_sync_on_read_context():
|
|
raise NotImplementedError(
|
|
"call `variable.read_value()` inside variable_sync_on_read_context is"
|
|
" not supported")
|
|
return super().read_value()
|
|
|
|
def _get_cross_replica(self):
|
|
if self._aggregation == vs.VariableAggregation.ONLY_FIRST_REPLICA:
|
|
# Consider returning a tensor value here to make the return value of
|
|
# _get_cross_replica consistent.
|
|
return self._get_replica(0)
|
|
if self._aggregation == vs.VariableAggregation.SUM:
|
|
values_util.mark_as_unsaveable()
|
|
with distribute_lib.enter_or_assert_strategy(self._distribute_strategy):
|
|
return self._distribute_strategy.reduce(
|
|
reduce_util.ReduceOp.from_variable_aggregation(self._aggregation),
|
|
self,
|
|
axis=None)
|
|
|
|
def _as_graph_element(self):
|
|
if values_util.is_saving_non_distributed():
|
|
return self._primary._as_graph_element() # pylint: disable=protected-access
|
|
# pylint: disable=protected-access
|
|
with distribute_lib.enter_or_assert_strategy(self._distribute_strategy):
|
|
if distribute_lib.in_cross_replica_context():
|
|
return ops.convert_to_tensor(self._get_cross_replica())
|
|
return self._get()._as_graph_element()
|
|
|
|
def _gather_saveables_for_checkpoint(self):
|
|
"""Overrides Trackable method.
|
|
|
|
This allows both name-based and object-based save and restore of
|
|
`SyncOnReadVariable`s.
|
|
|
|
Returns:
|
|
A dictionary mapping attribute names to `SaveableObject` factories.
|
|
"""
|
|
|
|
def _saveable_factory(name=self._common_name):
|
|
return _SyncOnReadSaveable(self, name)
|
|
|
|
return {trackable.VARIABLE_VALUE_KEY: _saveable_factory}
|
|
|
|
def _dense_var_to_tensor(self, dtype=None, name=None, as_ref=False):
|
|
"""Converts a SyncOnReadVariable to a tensor."""
|
|
if values_util.is_saving_non_distributed():
|
|
return ops.convert_to_tensor(
|
|
self._primary, dtype=dtype, name=name, as_ref=as_ref)
|
|
with distribute_lib.enter_or_assert_strategy(self._distribute_strategy):
|
|
replica_context = distribute_lib.get_replica_context()
|
|
if (replica_context is not None and
|
|
distribute_lib.in_variable_sync_on_read_context()):
|
|
if self._aggregation == vs.VariableAggregation.ONLY_FIRST_REPLICA:
|
|
return ops.convert_to_tensor(
|
|
self._get_replica(0), dtype=dtype, name=name, as_ref=as_ref)
|
|
if self._aggregation == vs.VariableAggregation.SUM:
|
|
values_util.mark_as_unsaveable()
|
|
# pylint: disable=protected-access
|
|
reduced = (
|
|
replica_context.strategy.extended._replica_ctx_all_reduce(
|
|
reduce_util.ReduceOp.from_variable_aggregation(
|
|
self._aggregation),
|
|
self._get().read_value()))
|
|
return ops.convert_to_tensor(
|
|
reduced, dtype=dtype, name=name, as_ref=as_ref)
|
|
|
|
return ops.convert_to_tensor(
|
|
self._get(), dtype=dtype, name=name, as_ref=as_ref)
|
|
|
|
|
|
# Register a conversion functions which reads the value of the variable,
|
|
# allowing instances of the class to be used as tensors.
|
|
# DistributedVariable
|
|
def _tensor_conversion_distributed_var(var,
|
|
dtype=None,
|
|
name=None,
|
|
as_ref=False):
|
|
return var._dense_var_to_tensor(dtype=dtype, name=name, as_ref=as_ref) # pylint: disable=protected-access
|
|
|
|
|
|
tensor_conversion_registry.register_tensor_conversion_function(
|
|
DistributedVariable, _tensor_conversion_distributed_var)
|
|
|
|
|
|
# MirroredVariables
|
|
def _tensor_conversion_mirrored(var, dtype=None, name=None, as_ref=False):
|
|
return var._dense_var_to_tensor(dtype=dtype, name=name, as_ref=as_ref) # pylint: disable=protected-access
|
|
|
|
|
|
tensor_conversion_registry.register_tensor_conversion_function(
|
|
MirroredVariable, _tensor_conversion_mirrored)
|
|
|
|
|
|
# Mirrored Values
|
|
def _tensor_conversion_mirrored_val(value, dtype=None, name=None, as_ref=False):
|
|
return ops.convert_to_tensor(
|
|
value._get(), dtype=dtype, name=name, as_ref=as_ref) # pylint: disable=protected-access
|
|
|
|
|
|
tensor_conversion_registry.register_tensor_conversion_function(
|
|
Mirrored, _tensor_conversion_mirrored_val)
|
|
|
|
|
|
# SyncOnReadVariables
|
|
def _tensor_conversion_sync_on_read(var, dtype=None, name=None, as_ref=False):
|
|
return var._dense_var_to_tensor(dtype=dtype, name=name, as_ref=as_ref) # pylint: disable=protected-access
|
|
|
|
|
|
tensor_conversion_registry.register_tensor_conversion_function(
|
|
SyncOnReadVariable, _tensor_conversion_sync_on_read)
|
|
|
|
|
|
class VariablePolicy(object):
|
|
"""Policy defining synchronization and aggregation of a distributed variable.
|
|
|
|
Given `synchronization` and `aggregation` parameters set on a `tf.Variable`
|
|
during variable creation within `tf.distribute` scope, `tf.distribute` creates
|
|
an appropriate policy object and assigns it to the distributed variable. All
|
|
variable operations are delegated to the respective policy object.
|
|
"""
|
|
|
|
def __init__(self, aggregation):
|
|
self._aggregation = aggregation
|
|
|
|
def value(self):
|
|
raise NotImplementedError(
|
|
"VariablePolicy.value should be overridden by sub-classes. "
|
|
f"Type name is {type(self)}"
|
|
)
|
|
|
|
def _is_mirrored(self):
|
|
raise NotImplementedError(
|
|
"VariablePolicy._is_mirrored should be overridden by sub-classes. "
|
|
f"Type name is {type(self)}"
|
|
)
|
|
|
|
def _as_graph_element(self, _):
|
|
raise NotImplementedError(
|
|
"VariablePolicy._as_graph_element should be overridden by sub-classes. "
|
|
f"Type name is {type(self)}"
|
|
)
|
|
|
|
def _get_cross_replica(self, var):
|
|
raise NotImplementedError(
|
|
"VariablePolicy._get_cross_replica should be overridden by"
|
|
f" sub-classes. Type name is {type(self)}"
|
|
)
|
|
|
|
def _update_replica(self, var, update_fn, value, **kwargs):
|
|
raise NotImplementedError(
|
|
"VariablePolicy._update_replica should be overridden by sub-classes. "
|
|
f"Type name is {type(self)}"
|
|
)
|
|
|
|
|
|
class OnReadPolicy(VariablePolicy):
|
|
"""Policy defined for `tf.VariableSynchronization.ON_READ` synchronization.
|
|
|
|
This policy is created when `synchronization` is set to
|
|
`tf.VariableSynchronization.ON_READ` and `aggregation` is set to any of the
|
|
values allowed by the `tf.VariableAggregation` enum such as `NONE`, `SUM`,
|
|
`MEAN` or `ONLY_FIRST_REPLICA`when creating a `tf.Variable` in `tf.distribute`
|
|
scope.
|
|
"""
|
|
|
|
def _is_mirrored(self):
|
|
return False
|
|
|
|
def value(self, var):
|
|
with distribute_lib.enter_or_assert_strategy(var.distribute_strategy):
|
|
if (distribute_lib.in_cross_replica_context() and
|
|
not values_util.in_replica_update_context()):
|
|
if self._aggregation == vs.VariableAggregation.ONLY_FIRST_REPLICA:
|
|
return var._get_replica(0).value() # pylint: disable=protected-access
|
|
return var._get_cross_replica() # pylint: disable=protected-access
|
|
else:
|
|
return var._get_on_device_or_primary().value() # pylint: disable=protected-access
|
|
|
|
def _as_graph_element(self, var):
|
|
with distribute_lib.enter_or_assert_strategy(var.distribute_strategy):
|
|
if distribute_lib.in_cross_replica_context():
|
|
return ops.convert_to_tensor(var._get_cross_replica()) # pylint: disable=protected-access
|
|
return var._get()._as_graph_element() # pylint: disable=protected-access
|
|
|
|
def _get_cross_replica(self, var):
|
|
if self._aggregation == vs.VariableAggregation.ONLY_FIRST_REPLICA:
|
|
return var._get_replica(0) # pylint: disable=protected-access
|
|
if self._aggregation == vs.VariableAggregation.SUM:
|
|
values_util.mark_as_unsaveable()
|
|
with distribute_lib.enter_or_assert_strategy(var.distribute_strategy):
|
|
return var.distribute_strategy.reduce(
|
|
reduce_util.ReduceOp.from_variable_aggregation(self._aggregation),
|
|
var,
|
|
axis=None)
|
|
|
|
def _update_replica(self, var, update_fn, value, **kwargs):
|
|
return update_fn(var._get_on_device_or_primary(), value, **kwargs) # pylint: disable=protected-access
|
|
|
|
def _scatter_not_implemented(self, method):
|
|
raise NotImplementedError(f"ON_READ variables doesn't support `{method}` "
|
|
"in cross replica context")
|
|
|
|
def assign_sub(self,
|
|
var,
|
|
value,
|
|
use_locking=False,
|
|
name=None,
|
|
read_value=True):
|
|
"""Subtracts a value from this variable."""
|
|
with distribute_lib.enter_or_assert_strategy(var.distribute_strategy):
|
|
if (distribute_lib.in_cross_replica_context() and
|
|
not values_util.in_replica_update_context()):
|
|
values_util.mark_as_unsaveable()
|
|
return values_util.on_read_assign_sub_cross_replica(
|
|
var, value, read_value=read_value)
|
|
else:
|
|
return values_util.on_write_assign_sub(
|
|
var,
|
|
value,
|
|
use_locking=use_locking,
|
|
name=name,
|
|
read_value=read_value)
|
|
|
|
def assign_add(self,
|
|
var,
|
|
value,
|
|
use_locking=False,
|
|
name=None,
|
|
read_value=True):
|
|
"""Adds a value to this variable."""
|
|
with distribute_lib.enter_or_assert_strategy(var.distribute_strategy):
|
|
if (distribute_lib.in_cross_replica_context() and
|
|
not values_util.in_replica_update_context()):
|
|
values_util.mark_as_unsaveable()
|
|
return values_util.on_read_assign_add_cross_replica(
|
|
var, value, read_value=read_value)
|
|
else:
|
|
return values_util.on_write_assign_add(
|
|
var,
|
|
value,
|
|
use_locking=use_locking,
|
|
name=name,
|
|
read_value=read_value)
|
|
|
|
def assign(self, var, value, use_locking=False, name=None, read_value=True):
|
|
with distribute_lib.enter_or_assert_strategy(var.distribute_strategy):
|
|
if (distribute_lib.in_cross_replica_context() and
|
|
not values_util.in_replica_update_context()):
|
|
values_util.mark_as_unsaveable()
|
|
return values_util.on_read_assign_cross_replica(
|
|
var, value, read_value=read_value)
|
|
else:
|
|
return values_util.on_write_assign(
|
|
var,
|
|
value,
|
|
use_locking=use_locking,
|
|
name=name,
|
|
read_value=read_value)
|
|
|
|
def scatter_sub(self, *args, **kwargs):
|
|
del args, kwargs
|
|
self._scatter_not_implemented("scatter_sub")
|
|
|
|
def scatter_add(self, *args, **kwargs):
|
|
del args, kwargs
|
|
self._scatter_not_implemented("scatter_add")
|
|
|
|
def scatter_mul(self, *args, **kwargs):
|
|
del args, kwargs
|
|
self._scatter_not_implemented("scatter_mul")
|
|
|
|
def scatter_div(self, *args, **kwargs):
|
|
del args, kwargs
|
|
self._scatter_not_implemented("scatter_div")
|
|
|
|
def scatter_min(self, *args, **kwargs):
|
|
del args, kwargs
|
|
self._scatter_not_implemented("scatter_min")
|
|
|
|
def scatter_max(self, *args, **kwargs):
|
|
del args, kwargs
|
|
self._scatter_not_implemented("scatter_max")
|
|
|
|
def scatter_update(self, *args, **kwargs):
|
|
del args, kwargs
|
|
self._scatter_not_implemented("scatter_update")
|
|
|
|
def get_saveable(self, var, primary_var, name):
|
|
"""Create a saveable object for the given variable."""
|
|
return values_util.get_on_read_saveable(var, primary_var, name)
|
|
|
|
def get_restore_ops(self, var, tensor):
|
|
"""Restore the same value into all variables."""
|
|
return values_util.get_on_read_restore_ops(var, tensor, self._aggregation)
|
|
|
|
|
|
class OnWritePolicy(VariablePolicy):
|
|
"""Policy defined for `tf.VariableSynchronization.ON_WRITE` synchronization.
|
|
|
|
This policy is created when the following `synchronization` and `aggregation`
|
|
parameters are specified when creating a `tf.Variable` in `tf.distribute`
|
|
scope and `synchronization` is equal to `tf.VariableSynchronization.ON_WRITE`
|
|
or `tf.VariableSynchronization.AUTO`.
|
|
"""
|
|
|
|
def _is_mirrored(self):
|
|
return True
|
|
|
|
def value(self, var):
|
|
return var._get_on_device_or_primary().value() # pylint: disable=protected-access
|
|
|
|
def _as_graph_element(self, var):
|
|
return var._get_on_device_or_primary()._as_graph_element() # pylint: disable=protected-access
|
|
|
|
def _get_cross_replica(self, var):
|
|
# Return identity, to avoid directly exposing the variable to the user and
|
|
# allowing it to be modified by mistake.
|
|
return array_ops.identity(var._get_on_device_or_primary()) # pylint: disable=protected-access
|
|
|
|
def _update_replica(self, var, update_fn, value, **kwargs):
|
|
if var.aggregation == variables_lib.VariableAggregation.NONE:
|
|
return update_fn(var._get_on_device_or_primary(), value, **kwargs) # pylint: disable=protected-access
|
|
return _on_write_update_replica(var, update_fn, value, **kwargs)
|
|
|
|
def assign(self, var, value, use_locking=False, name=None, read_value=True):
|
|
return values_util.on_write_assign(
|
|
var, value, use_locking=use_locking, name=name, read_value=read_value)
|
|
|
|
def assign_add(self,
|
|
var,
|
|
value,
|
|
use_locking=False,
|
|
name=None,
|
|
read_value=True):
|
|
return values_util.on_write_assign_add(
|
|
var, value, use_locking=use_locking, name=name, read_value=read_value)
|
|
|
|
def assign_sub(self,
|
|
var,
|
|
value,
|
|
use_locking=False,
|
|
name=None,
|
|
read_value=True):
|
|
return values_util.on_write_assign_sub(
|
|
var, value, use_locking=use_locking, name=name, read_value=read_value)
|
|
|
|
def scatter_sub(self, var, sparse_delta, use_locking=False, name=None):
|
|
return values_util.scatter_sub(
|
|
var, sparse_delta, use_locking=use_locking, name=name)
|
|
|
|
def scatter_add(self, var, sparse_delta, use_locking=False, name=None):
|
|
return values_util.scatter_add(
|
|
var, sparse_delta, use_locking=use_locking, name=name)
|
|
|
|
def scatter_mul(self, var, sparse_delta, use_locking=False, name=None):
|
|
return values_util.scatter_mul(
|
|
var, sparse_delta, use_locking=use_locking, name=name)
|
|
|
|
def scatter_div(self, var, sparse_delta, use_locking=False, name=None):
|
|
return values_util.scatter_div(
|
|
var, sparse_delta, use_locking=use_locking, name=name)
|
|
|
|
def scatter_min(self, var, sparse_delta, use_locking=False, name=None):
|
|
if (self._aggregation != vs.VariableAggregation.ONLY_FIRST_REPLICA and
|
|
self._aggregation != vs.VariableAggregation.NONE):
|
|
raise NotImplementedError(
|
|
values_util.scatter_error_msg.format(
|
|
op_name="scatter_min", aggregation=self._aggregation))
|
|
return values_util.scatter_min(
|
|
var, sparse_delta, use_locking=use_locking, name=name)
|
|
|
|
def scatter_max(self, var, sparse_delta, use_locking=False, name=None):
|
|
if (self._aggregation != vs.VariableAggregation.ONLY_FIRST_REPLICA and
|
|
self._aggregation != vs.VariableAggregation.NONE):
|
|
raise NotImplementedError(
|
|
values_util.scatter_error_msg.format(
|
|
op_name="scatter_max", aggregation=self._aggregation))
|
|
return values_util.scatter_max(
|
|
var, sparse_delta, use_locking=use_locking, name=name)
|
|
|
|
def scatter_update(self, var, sparse_delta, use_locking=False, name=None):
|
|
if (self._aggregation != vs.VariableAggregation.ONLY_FIRST_REPLICA and
|
|
self._aggregation != vs.VariableAggregation.NONE):
|
|
raise NotImplementedError(
|
|
values_util.scatter_error_msg.format(
|
|
op_name="scatter_update", aggregation=self._aggregation))
|
|
return values_util.scatter_update(
|
|
var, sparse_delta, use_locking=use_locking, name=name)
|
|
|
|
def get_saveable(self, var, primary_var, name):
|
|
"""Saveable ops for AUTO variables."""
|
|
return values_util.get_on_write_saveable(var, primary_var, name)
|
|
|
|
def get_restore_ops(self, var, tensor):
|
|
return values_util.get_on_write_restore_ops(var, tensor)
|
|
|
|
|
|
class PerWorkerResource():
|
|
"""A per-worker CapturableResource class for non-ParameterServer strategy.
|
|
|
|
Resources that populate `host_to_resources` should be instances of classes
|
|
subclassing CapturableResource, although currently it's only used and tested
|
|
for StaticHashTable with TPUStrategy.
|
|
"""
|
|
|
|
def __init__(self, strategy, host_to_resources):
|
|
distribute_lib.distribution_strategy_input_api_counter.get_cell(
|
|
"PerWorkerResource", "TPUDistributedLookupTable").increase_by(1)
|
|
self._strategy = strategy
|
|
self._host_to_resources = host_to_resources
|
|
|
|
def __getattribute__(self, name):
|
|
if name not in ("__init__", "__getattribute__", "_host_to_resources",
|
|
"_strategy", "local_resource"):
|
|
return getattr(self.local_resource(), name)
|
|
return super(PerWorkerResource, self).__getattribute__(name)
|
|
|
|
def __setattr__(self, name, value):
|
|
if name not in ("_strategy", "_host_to_resources"):
|
|
return setattr(self.local_resource(), name, value)
|
|
return super(PerWorkerResource, self).__setattr__(name, value)
|
|
|
|
def local_resource(self):
|
|
"""Returns the resource on the local worker."""
|
|
current_device = device_util.canonicalize(device_util.current())
|
|
host_device = device_util.canonicalize(
|
|
device_util.get_host_for_device(current_device))
|
|
return self._host_to_resources.get(
|
|
host_device,
|
|
self._host_to_resources[next(iter(self._host_to_resources))])
|