/* Copyright 2023 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. ==============================================================================*/ #include "tensorflow/compiler/jit/variable_info_util.h" #include #include #include #include #include #include #include #include "absl/algorithm/container.h" #include "tensorflow/core/framework/op.h" #include "tensorflow/core/framework/op_kernel.h" #include "tensorflow/core/framework/resource_handle.h" #include "tensorflow/core/framework/resource_mgr.h" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/lib/core/errors.h" #include "tensorflow/core/lib/core/refcount.h" #include "tensorflow/core/platform/errors.h" #include "tsl/platform/status.h" namespace tensorflow { absl::Status GetVariableInfosFromInputs(ResourceMgr* rm, DeviceBase* dev, absl::Span inputs, absl::Span variable_indices, std::vector* result) { return GetVariableInfosFromInputs(rm, dev, inputs, variable_indices, nullptr, result); } absl::Status GetVariableInfosFromInputs(ResourceMgr* rm, DeviceBase* dev, absl::Span inputs, absl::Span variable_indices, const std::set* variables_updated, std::vector* result) { result->clear(); result->reserve(variable_indices.size()); for (int var_idx : variable_indices) { Var* variable = nullptr; if (inputs[var_idx]->NumElements() == 0) { return absl::InvalidArgumentError( absl::StrCat("Empty resource tensor passed at index ", var_idx, " to GetVariableInfosFromInputs.")); } const ResourceHandle& handle = inputs[var_idx]->flat()(0); if (handle.device() != dev->attributes().name()) { std::string definition_location = DefinitionLocationMsg(handle.definition_stack_trace()); return absl::InvalidArgumentError(absl::StrCat( "Trying to access resource ", handle.name(), definition_location, " located in device ", handle.device(), " from device ", dev->attributes().name(), "\n Cf. " "https://www.tensorflow.org/xla/" "known_issues#tfvariable_on_a_different_device")); } TF_RETURN_IF_ERROR(rm->LookupOrCreate( handle.container(), handle.name(), &variable, [](Var** ptr) { // This var is uninitialized for now. *ptr = new Var(DT_INVALID); return absl::OkStatus(); })); VariableInfo& variable_info = result->emplace_back( var_idx, handle.name(), variable, handle.definition_stack_trace()); if (variables_updated != nullptr && variables_updated->find(var_idx) == variables_updated->end()) { variable_info.set_read_only(); } } return absl::OkStatus(); } absl::Status LockVariables(absl::Span variables) { std::vector lock_order(variables.size()); std::iota(lock_order.begin(), lock_order.end(), 0); // VariableInfoComparator orders all empty VariableInfo instances as // equivalent so it looks like we may want to stable sort these to maintain a // deterministic order between the empty VariableInfo instances. However // since we're sorting by pointer value the sort is pretty non-deterministic // anyway so we don't bother using std::stable_sort for now. absl::c_sort(lock_order, [&](int a, int b) { if (variables[a]->var() && variables[b]->var()) { return variables[a]->var()->mu() < variables[b]->var()->mu(); } // Move all the empty VariableInfo instances to the end. return variables[a]->var() != nullptr; }); mutex* prev = nullptr; for (int i : lock_order) { Var* variable = variables[i]->var(); if (variable == nullptr) { // All empty VariableInfo instances are at the end of the order // so we're done. break; } mutex* mu = variable->mu(); if (prev == mu) { // It is an error to pass the same variable handle twice to the same XLA // cluster because we would not handle variable updates correctly. Any // locks we have already acquired will be released when the VariableInfo // objects are destroyed. // TODO(b/128495870) Add support for passing aliased resource variables. return absl::UnimplementedError( "Duplicate variable passed to XLA cluster"); } if (variables[i]->read_only()) { VLOG(4) << "Acquiring reader lock for variable " << reinterpret_cast(variable); mu->lock_shared(); variables[i]->set_shared_lock_held(); } else { VLOG(4) << "Acquiring lock for variable " << reinterpret_cast(variable); mu->lock(); variables[i]->set_lock_held(); } prev = mu; } VLOG(4) << "Finished acquiring variable locks."; return absl::OkStatus(); } absl::Status LockVariables(absl::Span variables) { std::vector variable_ptrs; variable_ptrs.reserve(variables.size()); for (auto& var : variables) { variable_ptrs.push_back(&var); } return LockVariables(absl::MakeSpan(variable_ptrs)); } absl::Status SnapshotResourceVariables( OpKernelContext* ctx, absl::Span variable_indices, absl::Span variable_infos, ResourceVarsSnapshot* result) { for (int i = 0, end = variable_indices.size(); i < end; i++) { Var* var = variable_infos[i].var(); (*result)[variable_indices[i]] = var ? std::make_optional(*var->tensor()) : std::nullopt; } return absl::OkStatus(); } std::vector GetResourceVariableIndicesFromContext(OpKernelContext* ctx) { std::vector out; for (int64_t i = 0; i < ctx->num_inputs(); i++) { if (ctx->input(i).dtype() == DT_RESOURCE) { out.push_back(i); } } return out; } absl::Status CreateVariableInfoLookup( absl::Span variable_args, absl::flat_hash_map& variable_info_lookup) { for (const VariableInfo& info : variable_args) { if (!(!info.var() || info.lock_held() || info.shared_lock_held())) { return absl::InternalError( "Need to hold the lock on resource variables " "before calling BuildXlaCompilerArguments"); } variable_info_lookup.emplace(info.index(), &info); } return absl::OkStatus(); } } // namespace tensorflow