# Copyright 2020 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 TPU distributed values. Note that the tests are in values_test.py . """ from tensorflow.python.distribute import packed_distributed_variable as packed from tensorflow.python.distribute import tpu_replicated_variable from tensorflow.python.distribute import tpu_util from tensorflow.python.distribute import values from tensorflow.python.distribute import values_util from tensorflow.python.eager import context from tensorflow.python.eager import tape from tensorflow.python.framework import ops from tensorflow.python.ops import gen_resource_variable_ops from tensorflow.python.ops import math_ops from tensorflow.python.ops import variable_scope _scatter_error_msg = ("{op_name} is only supported for distributed " "variable (variable created within certain " "`tf.distribute.Strategy` scope) with NONE " " aggregation, got: {aggregation}.") class TPUVariableMixin(object): """Mixin for TPU variables.""" def __init__(self, *args, **kwargs): super(TPUVariableMixin, self).__init__(*args, **kwargs) # Handle ID is needed for `get_replicated_var_handle` to cache the variables # correctly since in eager mode different variables can have the same name. if ops.executing_eagerly_outside_functions(): self._handle_id = self._common_name + "_" + str(id(self._primary)) else: self._handle_id = self._common_name def __getattr__(self, name): if tpu_util.enclosing_tpu_context() is None: return super(TPUVariableMixin, self).__getattr__(name) else: raise AttributeError( f"`TPUVariableMixin.{name}` not accessible within a TPU context.") def get(self): if tpu_util.enclosing_tpu_context() is None: return super(TPUVariableMixin, self).get() else: raise NotImplementedError( "`TPUVariableMixin.get()` is not supported within a TPU context.") def _get_as_operand(self): return self.read_value() @property def handle(self): """The handle by which this variable can be accessed.""" # If we're in a tpu.rewrite(), return the replicated handle. tpu_context = tpu_util.enclosing_tpu_context() if tpu_context is None or context.executing_eagerly(): var = self._get_on_device_or_primary() if isinstance(var, packed.PackedVarAndDevice): return var.on_device_handle() else: return var.handle else: is_packed = self._packed_var is not None val = self._values if is_packed: val = [self._packed_var] return tpu_context.get_replicated_var_handle(self._common_name, self._handle_id, val, self._is_mirrored(), is_packed) @property def device(self): return self.handle.device def _read_variable_op(self): """Reads the value of this variable.""" if self.trainable: tape.variable_accessed(self) handle = self.handle if getattr(handle, "is_packed", False): # Add a device scope for a packed variable handle. with ops.device(self._get_on_device_or_primary().device): return gen_resource_variable_ops.read_variable_op(handle, self.dtype) else: return gen_resource_variable_ops.read_variable_op(handle, self.dtype) def read_value(self): if tpu_util.enclosing_tpu_context() is None: return super(TPUVariableMixin, self).read_value() else: return self._read_variable_op() def value(self): if tpu_util.enclosing_tpu_context() is None: return super(TPUVariableMixin, self).value() else: return self._read_variable_op() def _as_graph_element(self): if tpu_util.enclosing_tpu_context() is None: return super(TPUVariableMixin, self)._as_graph_element() # pylint: disable=protected-access else: return None @property def op(self): if values_util.is_saving_non_distributed(): return self._primary.op return values.DistributedVarOp(self._primary.op.name, self._primary.op.graph, self._primary.op.traceback, self._primary.op.type) def _dense_var_to_tensor(self, dtype=None, name=None, as_ref=False): """Converts a variable to a tensor.""" # pylint: disable=protected-access if tpu_util.enclosing_tpu_context() is None: return super(TPUVariableMixin, self)._dense_var_to_tensor( dtype=dtype, name=name, as_ref=as_ref) # pylint: enable=protected-access elif dtype is not None and dtype != self.dtype: return math_ops.cast(self.read_value(), dtype) else: return self.handle if as_ref else self.read_value() class TPUDistributedVariable(TPUVariableMixin, values.DistributedVariable): """DistributedVariable subclass for TPUStrategy.""" 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) return self._policy.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) return self._policy.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) return self._policy.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) return self._policy.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) return self._policy.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) return self._policy.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) return self._policy.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) return self._policy.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) return self._policy.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) return self._policy.scatter_update( self, sparse_delta, use_locking=use_locking, name=name) class TPUMirroredVariable(TPUVariableMixin, values.MirroredVariable): """Holds a map from replica to TPU variables whose values are kept in sync.""" def _is_replicated_or_sharded_to_logical_cores(self): """Returns whether each of the underlying variables is replicated or sharded to logical cores. If True, the handles of the underlying variables are not available outside a TPU context. """ return isinstance(self._primary, tpu_replicated_variable.TPUReplicatedVariable) @property def device(self): if (self._is_replicated_or_sharded_to_logical_cores() and tpu_util.enclosing_tpu_context() is None): return self._primary.device return super(TPUMirroredVariable, self).device def assign_sub(self, value, use_locking=False, name=None, read_value=True): tpu_context = tpu_util.enclosing_tpu_context() if (self._is_replicated_or_sharded_to_logical_cores() and tpu_context is None): assign_sub_fn = lambda v, *a, **ka: v.assign_sub(*a, **ka) return self._update( update_fn=assign_sub_fn, value=value, use_locking=use_locking, name=name, read_value=read_value) if (tpu_context and self.aggregation == variable_scope.VariableAggregation.NONE): return tpu_util.make_raw_assign_fn( gen_resource_variable_ops.assign_sub_variable_op)( self, value=value, use_locking=use_locking, name=name, read_value=read_value) return 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): tpu_context = tpu_util.enclosing_tpu_context() if (self._is_replicated_or_sharded_to_logical_cores() and tpu_context is None): assign_add_fn = lambda v, *a, **ka: v.assign_add(*a, **ka) return self._update( update_fn=assign_add_fn, value=value, use_locking=use_locking, name=name, read_value=read_value) if (tpu_context and self.aggregation == variable_scope.VariableAggregation.NONE): return tpu_util.make_raw_assign_fn( gen_resource_variable_ops.assign_add_variable_op)( self, value=value, use_locking=use_locking, name=name, read_value=read_value) return 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): tpu_context = tpu_util.enclosing_tpu_context() if (self._is_replicated_or_sharded_to_logical_cores() and tpu_context is None): assign_fn = lambda v, *a, **ka: v.assign(*a, **ka) return self._update( update_fn=assign_fn, value=value, use_locking=use_locking, name=name, read_value=read_value) if (tpu_util.enclosing_tpu_context() and self.aggregation == variable_scope.VariableAggregation.NONE): return tpu_util.make_raw_assign_fn( gen_resource_variable_ops.assign_variable_op)( self, value=value, use_locking=use_locking, name=name, read_value=read_value) return assign( self, value, use_locking=use_locking, name=name, read_value=read_value) def scatter_sub(self, *args, **kwargs): if values_util.is_saving_non_distributed(): return self._primary.scatter_sub(*args, **kwargs) raise NotImplementedError def scatter_add(self, *args, **kwargs): if values_util.is_saving_non_distributed(): return self._primary.scatter_add(*args, **kwargs) raise NotImplementedError def scatter_max(self, *args, **kwargs): if values_util.is_saving_non_distributed(): return self._primary.scatter_max(*args, **kwargs) raise NotImplementedError def scatter_min(self, *args, **kwargs): if values_util.is_saving_non_distributed(): return self._primary.scatter_min(*args, **kwargs) raise NotImplementedError def scatter_mul(self, *args, **kwargs): if values_util.is_saving_non_distributed(): return self._primary.scatter_mul(*args, **kwargs) raise NotImplementedError def scatter_div(self, *args, **kwargs): if values_util.is_saving_non_distributed(): return self._primary.scatter_div(*args, **kwargs) raise NotImplementedError def scatter_update(self, *args, **kwargs): if values_util.is_saving_non_distributed(): return self._primary.scatter_update(*args, **kwargs) raise NotImplementedError class TPULazyDistributedVariable(TPUDistributedVariable): """TPU Mirrored variable to be initialized lazily in a batch.""" def _initialize_if_uninitialized(self): if getattr(self, "_is_lazily_initialized", False): return self._lazy_scope.initialize_all() self._is_lazily_initialized = True def assign_sub(self, value, use_locking=False, name=None, read_value=True): self._initialize_if_uninitialized() return super().assign_sub( value, use_locking, name, read_value ) def assign_add(self, value, use_locking=False, name=None, read_value=True): self._initialize_if_uninitialized() return super().assign_add( value, use_locking, name, read_value ) def assign(self, value, use_locking=False, name=None, read_value=True): self._initialize_if_uninitialized() return super().assign( value, use_locking, name, read_value ) def read_value(self): self._initialize_if_uninitialized() return super().read_value() class TPUSyncOnReadVariable(TPUVariableMixin, values.SyncOnReadVariable): """Holds a map from replica to variables whose values are reduced on save.""" def assign_sub(self, *args, **kwargs): if tpu_util.enclosing_tpu_context() is None: return values.SyncOnReadVariable.assign_sub(self, *args, **kwargs) else: return tpu_util.make_raw_assign_fn( gen_resource_variable_ops.assign_sub_variable_op)(self, *args, **kwargs) def assign_add(self, *args, **kwargs): if tpu_util.enclosing_tpu_context() is None: return values.SyncOnReadVariable.assign_add(self, *args, **kwargs) else: return tpu_util.make_raw_assign_fn( gen_resource_variable_ops.assign_add_variable_op)(self, *args, **kwargs) def assign(self, *args, **kwargs): if tpu_util.enclosing_tpu_context() is None: return values.SyncOnReadVariable.assign(self, *args, **kwargs) else: return tpu_util.make_raw_assign_fn( gen_resource_variable_ops.assign_variable_op)(self, *args, **kwargs) # Common method between OnWrite and Mirrored variables. def assign_sub(var, value, use_locking=False, name=None, read_value=True): assign_sub_fn = tpu_util.make_raw_assign_fn( gen_resource_variable_ops.assign_sub_variable_op) return var._update( # pylint: disable=protected-access update_fn=assign_sub_fn, value=value, use_locking=use_locking, name=name, read_value=read_value) def assign_add(var, value, use_locking=False, name=None, read_value=True): assign_add_fn = tpu_util.make_raw_assign_fn( gen_resource_variable_ops.assign_add_variable_op) return var._update( # pylint: disable=protected-access update_fn=assign_add_fn, value=value, use_locking=use_locking, name=name, read_value=read_value) def assign(var, value, use_locking=False, name=None, read_value=True): assign_fn = tpu_util.make_raw_assign_fn( gen_resource_variable_ops.assign_variable_op) return var._update( # pylint: disable=protected-access update_fn=assign_fn, value=value, use_locking=use_locking, name=name, read_value=read_value) class TPUOnWritePolicy(values.OnWritePolicy): """Policy defined for `tf.VariableSynchronization.ON_WRITE` synchronization. This policy is created when `synchronization` is set to `tf.VariableSynchronization.AUTO` or `tf.VariableSynchronization.ON_WRITE`. """ def assign_sub(self, var, value, use_locking=False, name=None, read_value=True): if (tpu_util.enclosing_tpu_context() and var.aggregation == variable_scope.VariableAggregation.NONE): return tpu_util.make_raw_assign_fn( gen_resource_variable_ops.assign_sub_variable_op)( var, value=value, use_locking=use_locking, name=name, read_value=read_value) return 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): if (tpu_util.enclosing_tpu_context() and var.aggregation == variable_scope.VariableAggregation.NONE): return tpu_util.make_raw_assign_fn( gen_resource_variable_ops.assign_add_variable_op)( var, value=value, use_locking=use_locking, name=name, read_value=read_value) return 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): if (tpu_util.enclosing_tpu_context() and var.aggregation == variable_scope.VariableAggregation.NONE): return tpu_util.make_raw_assign_fn( gen_resource_variable_ops.assign_variable_op)( var, value=value, use_locking=use_locking, name=name, read_value=read_value) return assign( var, value, use_locking=use_locking, name=name, read_value=read_value) def _scatter_xxx(self, raw_scater_xxx_fn, op_name, var, sparse_delta, use_locking=False, name=None): scater_xxx_fn = tpu_util.make_raw_scatter_xxx_fn(raw_scater_xxx_fn) if tpu_util.enclosing_tpu_context(): if self._aggregation != variable_scope.VariableAggregation.NONE: raise NotImplementedError( _scatter_error_msg.format( op_name=op_name, aggregation=self._aggregation)) return scater_xxx_fn( var, sparse_delta=sparse_delta, use_locking=use_locking, name=name) else: return var._update( # pylint: disable=protected-access update_fn=scater_xxx_fn, value=sparse_delta, use_locking=use_locking, name=name) def scatter_sub(self, var, sparse_delta, use_locking=False, name=None): return self._scatter_xxx(gen_resource_variable_ops.resource_scatter_sub, "scatter_sub", var, sparse_delta, use_locking, name) def scatter_add(self, var, sparse_delta, use_locking=False, name=None): return self._scatter_xxx(gen_resource_variable_ops.resource_scatter_add, "scatter_add", var, sparse_delta, use_locking, name) def scatter_max(self, var, sparse_delta, use_locking=False, name=None): return self._scatter_xxx(gen_resource_variable_ops.resource_scatter_max, "scatter_max", var, sparse_delta, use_locking, name) def scatter_min(self, var, sparse_delta, use_locking=False, name=None): return self._scatter_xxx(gen_resource_variable_ops.resource_scatter_min, "scatter_min", var, sparse_delta, use_locking, name) def scatter_mul(self, var, sparse_delta, use_locking=False, name=None): return self._scatter_xxx(gen_resource_variable_ops.resource_scatter_mul, "scatter_mul", var, sparse_delta, use_locking, name) def scatter_div(self, var, sparse_delta, use_locking=False, name=None): return self._scatter_xxx(gen_resource_variable_ops.resource_scatter_div, "scatter_div", var, sparse_delta, use_locking, name) def scatter_update(self, var, sparse_delta, use_locking=False, name=None): return self._scatter_xxx(gen_resource_variable_ops.resource_scatter_update, "scatter_update", var, sparse_delta, use_locking, name) class TPUOnReadPolicy(values.OnReadPolicy): """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 assign_sub(self, var, *args, **kwargs): if tpu_util.enclosing_tpu_context() is None: return super(TPUOnReadPolicy, self).assign_sub(var, *args, **kwargs) else: return tpu_util.make_raw_assign_fn( gen_resource_variable_ops.assign_sub_variable_op)(var, *args, **kwargs) def assign_add(self, var, *args, **kwargs): if tpu_util.enclosing_tpu_context() is None: return super(TPUOnReadPolicy, self).assign_add(var, *args, **kwargs) else: return tpu_util.make_raw_assign_fn( gen_resource_variable_ops.assign_add_variable_op)(var, *args, **kwargs) def assign(self, var, *args, **kwargs): if tpu_util.enclosing_tpu_context() is None: return super(TPUOnReadPolicy, self).assign(var, *args, **kwargs) else: return tpu_util.make_raw_assign_fn( gen_resource_variable_ops.assign_variable_op)(var, *args, **kwargs) def scatter_sub(self, *args, **kwargs): raise NotImplementedError def scatter_add(self, *args, **kwargs): raise NotImplementedError def scatter_max(self, *args, **kwargs): raise NotImplementedError def scatter_min(self, *args, **kwargs): raise NotImplementedError def scatter_mul(self, *args, **kwargs): raise NotImplementedError def scatter_div(self, *args, **kwargs): raise NotImplementedError def scatter_update(self, *args, **kwargs): raise NotImplementedError