Files
tensorflow--tensorflow/tensorflow/compiler/jit/kernels/xla_ops.cc
T
wehub-resource-sync 8a852e4b4e
cffconvert / validate (push) Has been skipped
License Check / license-check (push) Failing after 2s
chore: import upstream snapshot with attribution
2026-07-13 12:14:16 +08:00

1047 lines
43 KiB
C++

/* Copyright 2017 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/kernels/xla_ops.h"
#include <cstdint>
#include <functional>
#include <memory>
#include <numeric>
#include <optional>
#include <set>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include "absl/base/const_init.h"
#include "absl/base/thread_annotations.h"
#include "absl/container/flat_hash_map.h"
#include "absl/container/node_hash_map.h"
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/synchronization/mutex.h"
#include "absl/types/span.h"
#include "tensorflow/compiler/jit/device_compilation_profiler.h"
#include "tensorflow/compiler/jit/device_compiler.h"
#include "tensorflow/compiler/jit/encapsulate_subgraphs_pass.h"
#include "tensorflow/compiler/jit/flags.h"
#include "tensorflow/compiler/jit/pjrt_compile_util.h"
#include "tensorflow/compiler/jit/variable_info.h"
#include "tensorflow/compiler/jit/variable_info_util.h"
#include "tensorflow/compiler/jit/xla_activity.pb.h"
#include "tensorflow/compiler/jit/xla_activity_listener.h"
#include "tensorflow/compiler/jit/xla_compile_util.h"
#include "tensorflow/compiler/jit/xla_compiler_options_util.h"
#include "tensorflow/compiler/jit/xla_host_recv_device_context.h"
#include "tensorflow/compiler/jit/xla_host_send_device_context.h"
#include "tensorflow/compiler/jit/xla_launch_util.h"
#include "tensorflow/compiler/jit/xla_platform_info.h"
#include "tensorflow/compiler/tf2xla/tf2xla_util.h"
#include "tensorflow/compiler/tf2xla/xla_compiler.h"
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "xla/client/local_client.h"
#include "xla/executable_run_options.h"
#include "xla/hlo/ir/hlo_input_output_alias_config.h"
#include "xla/pjrt/pjrt_client.h"
#include "xla/service/executable.h"
#include "xla/service/gpu/gpu_executable_run_options.h"
#include "xla/stream_executor/host/host_platform_id.h"
#include "xla/tsl/concurrency/async_value_ref.h"
#include "xla/tsl/protobuf/error_codes.pb.h"
#include "tensorflow/core/framework/allocator.h"
#include "tensorflow/core/framework/control_flow.h"
#include "tensorflow/core/framework/device.h"
#include "tensorflow/core/framework/function.h"
#include "tensorflow/core/framework/node_def_util.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/op_requires.h"
#include "tensorflow/core/framework/rendezvous.h"
#include "tensorflow/core/framework/tensor.h"
#include "tensorflow/core/framework/tensor_shape.h"
#include "tensorflow/core/framework/types.h"
#include "tensorflow/core/framework/types.pb.h"
#include "tensorflow/core/lib/monitoring/counter.h"
#include "tensorflow/core/platform/env.h"
#include "tensorflow/core/platform/errors.h"
#include "tensorflow/core/platform/mutex.h"
#include "tensorflow/core/platform/refcount.h"
#include "tensorflow/core/platform/stream_executor_no_cuda.h"
#include "tensorflow/core/platform/threadpool.h"
#include "tensorflow/core/platform/tstring.h"
#include "tensorflow/core/platform/types.h"
#include "tensorflow/core/util/device_name_utils.h"
#include "tensorflow/core/util/stream_executor_util.h"
#include "tsl/platform/thread_annotations.h"
#include "tsl/profiler/lib/traceme.h"
// OP_REQUIRES_OK_RETURN is the same as OP_REQUIRES_OK except that
// in error case, it returns RET instead of void.
#define OP_REQUIRES_OK_RETURN(CTX, RET, ...) \
do { \
::tensorflow::Status _s(__VA_ARGS__); \
if (!TF_PREDICT_TRUE(_s.ok())) { \
(CTX)->CtxFailureWithWarning(__FILE__, __LINE__, _s); \
return RET; \
} \
} while (0)
namespace tensorflow {
namespace {
using XlaDeviceCompiler =
DeviceCompiler<xla::LocalExecutable, xla::LocalClient>;
using PjRtDeviceCompiler =
DeviceCompiler<xla::PjRtLoadedExecutable, xla::PjRtClient>;
auto* xla_launch_counter = monitoring::Counter<1>::New(
"/tensorflow/core/xla_launch_counter",
"The number of times a XlaLaunch is called.", "device");
// A closure describing how to run a compiled version of a TensorFlow function.
//
// It may seem unusual to stick the resource variable snapshots in this class.
// This is necessary: we need to use the snapshots observed by the compiler as
// the initial values for the resource variables (and cannot snapshot them again
// during execution) because otherwise we risk observing a different snapshot
// with shapes different from what we compiled for.
template <typename ExecutableType, typename ClientType>
class ExecutableClosure {
public:
explicit ExecutableClosure(
ClientType* client, ExecutableType* executable,
const XlaCompiler::CompilationResult* compilation_result,
ResourceVarsSnapshot resource_var_snapshots, int num_constant_args)
: client_(client),
executable_(executable),
compilation_result_(compilation_result),
resource_var_snapshots_(std::move(resource_var_snapshots)),
num_constant_args_(num_constant_args) {}
ExecutableClosure(ExecutableClosure&&) = default;
ExecutableClosure& operator=(ExecutableClosure&&) = default;
ClientType* client() const { return client_; }
ExecutableType* executable() const { return executable_; }
const XlaCompiler::CompilationResult* compilation_result() const {
return compilation_result_;
}
const ResourceVarsSnapshot& resource_var_snapshots() const {
return resource_var_snapshots_;
}
int num_constant_args() const { return num_constant_args_; }
private:
ClientType* client_;
ExecutableType* executable_;
const XlaCompiler::CompilationResult* compilation_result_;
ResourceVarsSnapshot resource_var_snapshots_;
int num_constant_args_;
ExecutableClosure(const ExecutableClosure&) = delete;
void operator=(const ExecutableClosure&) = delete;
};
// This maintains a mapping from a globally unique ID to ExecutableClosure
// instances.
template <typename ExecutableType, typename ClientType>
class ExecutableClosureStore {
public:
ExecutableClosureStore() : key_counter_(0) {}
using KeyT = std::string;
KeyT Produce(ExecutableClosure<ExecutableType, ClientType> result) {
mutex_lock l(mutex_);
KeyT key = absl::StrCat(key_counter_++);
bool insert_successful = closures_.emplace(key, std::move(result)).second;
DCHECK(insert_successful);
(void)insert_successful;
return key;
}
ExecutableClosure<ExecutableType, ClientType> Consume(const KeyT& key) {
mutex_lock l(mutex_);
auto it = closures_.find(key);
DCHECK(it != closures_.end());
ExecutableClosure<ExecutableType, ClientType> value = std::move(it->second);
closures_.erase(it);
return value;
}
static ExecutableClosureStore* Global() {
static ExecutableClosureStore* instance = new ExecutableClosureStore;
return instance;
}
private:
mutex mutex_;
int64_t key_counter_ TF_GUARDED_BY(mutex_);
absl::flat_hash_map<KeyT, ExecutableClosure<ExecutableType, ClientType>>
closures_ TF_GUARDED_BY(mutex_);
ExecutableClosureStore(const ExecutableClosureStore&) = delete;
void operator=(const ExecutableClosureStore&) = delete;
};
using XlaExecutableClosure =
ExecutableClosure<xla::LocalExecutable, xla::LocalClient>;
using XlaExecutableClosureStore =
ExecutableClosureStore<xla::LocalExecutable, xla::LocalClient>;
using PjRtExecutableClosure =
ExecutableClosure<xla::PjRtLoadedExecutable, xla::PjRtClient>;
using PjRtExecutableClosureStore =
ExecutableClosureStore<xla::PjRtLoadedExecutable, xla::PjRtClient>;
se::Stream* GetStream(OpKernelContext* ctx) {
return ctx->op_device_context() ? ctx->op_device_context()->stream()
: nullptr;
}
XlaComputationLaunchContext GetLaunchContext(
const XlaPlatformInfo& platform_info, OpKernelContext* ctx,
xla::LocalClient* client,
stream_executor::DeviceAddressAllocator* allocator) {
se::Stream* stream = GetStream(ctx);
int device_ordinal = stream ? stream->parent()->device_ordinal()
: client->default_device_ordinal();
XlaComputationLaunchContext launch_context(
client, allocator, device_ordinal,
/*allocate_xla_tensors=*/platform_info.is_on_xla_device(),
/*use_multiple_streams=*/platform_info.UseMultipleStreams());
return launch_context;
}
absl::Status GetTaskName(const absl::string_view device_name,
std::string* task_name) {
std::string ignored;
if (!DeviceNameUtils::SplitDeviceName(device_name, task_name, &ignored)) {
return absl::InvalidArgumentError(
absl::StrCat("Unable to parse device name: ", device_name));
}
return absl::OkStatus();
}
// Provide SendDeviceMemoryFunction for XLA host callbacks. This callback
// handles transferring from device to host.
xla::SendDeviceMemoryFunction GetSendDeviceMemoryFunction(
OpKernelContext* ctx, const std::string& program_key) {
return
[ctx, program_key](
int64_t channel_id, se::Stream* stream, const xla::Shape& shape,
const stream_executor::DeviceAddressBase& device_memory_base,
const absl::flat_hash_map<std::string, std::string>& frontend_attrs)
-> absl::StatusOr<tsl::AsyncValueRef<std::unique_ptr<se::Event>>> {
auto iter = frontend_attrs.find("_xla_host_transfer_rendezvous");
// Generate the Rendezvous key.
const std::string& rendezvous_key_base =
absl::StrCat(program_key, iter->second);
const std::string& src_device = ctx->device()->name();
std::string task_prefix;
TF_RETURN_IF_ERROR(GetTaskName(src_device, &task_prefix));
const std::string dst_device =
absl::StrCat(task_prefix, "/device:CPU:0");
const std::string& rendezvous_key =
Rendezvous::CreateKey(src_device, /*src_incarnation=*/1, dst_device,
rendezvous_key_base, FrameAndIter(0, 0));
VLOG(2) << "Rendezvous Key for receiving at host: " << rendezvous_key;
RendezvousInterface::ParsedKey parsed_key;
TF_RETURN_IF_ERROR(Rendezvous::ParseKey(rendezvous_key, &parsed_key));
TF_ASSIGN_OR_RETURN(auto event, stream->parent()->CreateEvent());
tsl::AsyncValueRef<std::unique_ptr<se::Event>> done_event =
tsl::MakeConstructedAsyncValueRef<std::unique_ptr<se::Event>>(
std::move(event));
Rendezvous::Args args;
// Rendezvous::Args owns the device context pointer.
args.device_context = new XlaHostRecvDeviceContext(
stream, device_memory_base, shape, done_event);
Tensor host_tensor;
TF_RETURN_IF_ERROR(
ctx->rendezvous()->Send(parsed_key, args, host_tensor, false));
return std::move(done_event);
};
}
// Provide RecvDeviceMemoryFunction for XLA host callbacks. This callback
// handles transferring from host to device.
xla::RecvDeviceMemoryFunction GetRecvDeviceMemoryFunction(
OpKernelContext* ctx, const std::string& program_key) {
return
[ctx, program_key](
int64_t channel_id, se::Stream* stream, const xla::Shape& shape,
stream_executor::DeviceAddressBase* device_memory_base,
const absl::flat_hash_map<std::string, std::string>& frontend_attrs)
-> absl::StatusOr<tsl::AsyncValueRef<std::unique_ptr<se::Event>>> {
auto iter = frontend_attrs.find("_xla_host_transfer_rendezvous");
// Generate the Rendezvous key.
const std::string& rendezvous_key_base =
absl::StrCat(program_key, iter->second);
const std::string& dst_device = ctx->device()->name();
std::string task_prefix;
TF_RETURN_IF_ERROR(GetTaskName(dst_device, &task_prefix));
const std::string src_device =
absl::StrCat(task_prefix, "/device:CPU:0");
const std::string& rendezvous_key =
Rendezvous::CreateKey(src_device, /*src_incarnation=*/1, dst_device,
rendezvous_key_base, FrameAndIter(0, 0));
VLOG(2) << "Rendezvous Key for sending from host: " << rendezvous_key;
RendezvousInterface::ParsedKey parsed_key;
TF_RETURN_IF_ERROR(Rendezvous::ParseKey(rendezvous_key, &parsed_key));
TF_ASSIGN_OR_RETURN(auto event, stream->parent()->CreateEvent());
tsl::AsyncValueRef<std::unique_ptr<se::Event>> done_event =
tsl::MakeConstructedAsyncValueRef<std::unique_ptr<se::Event>>(
std::move(event));
Rendezvous::Args args;
// Rendezvous::Args owns the device context pointer.
args.device_context = new XlaHostSendDeviceContext(
stream, device_memory_base, shape, done_event);
Tensor device_tensor;
bool is_dead;
TF_RETURN_IF_ERROR(ctx->rendezvous()->Recv(
parsed_key, args, &device_tensor, /*is_dead=*/&is_dead));
return std::move(done_event);
};
}
absl::StatusOr<xla::ExecutionOutput> RunExecutable(
const XlaPlatformInfo& platform_info,
const XlaComputationLaunchContext& launch_context,
std::vector<xla::ExecutionInput> execution_inputs,
xla::ExecutableRunOptions run_options, xla::LocalExecutable* executable,
OpKernelContext* ctx, stream_executor::DeviceAddressAllocator* allocator) {
VLOG(2) << "Executing Xla Computation.";
Env* env = Env::Default();
auto start_time = env->NowMicros();
se::Stream* stream = GetStream(ctx);
run_options.set_stream(GetStream(ctx));
run_options.set_allocator(allocator);
run_options.set_intra_op_thread_pool(&ctx->eigen_cpu_device());
run_options.set_rng_seed(GetXLARandomSeed());
absl::StatusOr<xla::ExecutionOutput> execution_output;
bool run_synchronous =
!stream || platform_info.platform_id() == se::host::kHostPlatformId;
if (run_synchronous) {
execution_output =
executable->Run(std::move(execution_inputs), run_options);
} else {
execution_output =
executable->RunAsync(std::move(execution_inputs), run_options);
}
auto elapsed = env->NowMicros() - start_time;
VLOG(2) << "Elapsed time for Xla Executable Run: " << elapsed << "us";
return execution_output;
}
absl::StatusOr<
std::pair<std::vector<XlaCompiler::Argument>, ResourceVarsSnapshot>>
GetXlaCompilerArgsAndSnapshotVariables(
absl::Span<const int> variable_indices,
absl::Span<const int> must_be_constant_idxs,
absl::Span<const Tensor* const> inputs, OpKernelContext* ctx) {
std::pair<std::vector<XlaCompiler::Argument>, ResourceVarsSnapshot> result;
std::vector<VariableInfo> variable_infos;
TF_RETURN_IF_ERROR(
GetVariableInfosFromInputs(ctx->resource_manager(), ctx->device(), inputs,
variable_indices, &variable_infos));
TF_RETURN_IF_ERROR(LockVariables(absl::MakeSpan(variable_infos)));
TF_RETURN_IF_ERROR(SnapshotResourceVariables(ctx, variable_indices,
variable_infos, &result.second));
TF_ASSIGN_OR_RETURN(result.first,
XlaComputationLaunchContext::BuildXlaCompilerArguments(
must_be_constant_idxs, inputs, variable_infos,
static_cast<Device*>(ctx->device())));
return result;
}
absl::Status CompileToLocalExecutable(
OpKernelContext* ctx, const NameAttrList& function, bool has_ref_vars,
const XlaPlatformInfo& platform_info,
const std::vector<XlaCompiler::Argument>& args,
DeviceCompileMode compile_mode, bool may_alias_resource_update,
xla::LocalClient** client,
const XlaCompiler::CompilationResult** compilation_result,
xla::LocalExecutable** executable) {
// We store information about the JIT-compiled XLA computation
// in the ResourceMgr.
ResourceMgr* rm = ctx->resource_manager();
if (!rm) {
return absl::InternalError("No resource manager.");
}
TF_ASSIGN_OR_RETURN(DeviceType compilation_device_type,
GetCompilationDeviceType(platform_info.device_type()));
XlaDeviceCompiler* xla_device_compiler;
TF_RETURN_IF_ERROR(rm->LookupOrCreate<XlaDeviceCompiler>(
rm->default_container(), "xla_device_compiler", &xla_device_compiler,
[&](XlaDeviceCompiler** xla_device_compiler) {
return BuildXlaDeviceCompiler(ctx->device(), ctx->function_library(),
platform_info, compilation_device_type,
xla_device_compiler);
}));
DeviceCompilationProfiler* profiler;
TF_RETURN_IF_ERROR(rm->LookupOrCreate<DeviceCompilationProfiler>(
rm->default_container(), "device_compilation_profiler", &profiler,
[](DeviceCompilationProfiler** profiler) {
*profiler = new DeviceCompilationProfiler();
return absl::OkStatus();
}));
// Hold the reference to the XLA device compiler and profiler during
// evaluation. (We could probably free them sooner because the ResourceMgr
// will retain references, but this is more obviously correct.)
core::ScopedUnref xla_device_compiler_ref(xla_device_compiler);
core::ScopedUnref profiler_ref(profiler);
*client = static_cast<xla::LocalClient*>(xla_device_compiler->client());
XlaCompiler::Options options = GenerateCompilerOptions(
*xla_device_compiler, *ctx->function_library(), ctx->device(),
GetStream(ctx), platform_info, has_ref_vars);
XlaCompiler::CompileOptions compile_options =
GenerateCompileOptions(has_ref_vars, may_alias_resource_update);
return xla_device_compiler->CompileIfNeeded(
options, function, args, compile_options, compile_mode, profiler,
compilation_result, executable);
}
absl::Status GetUpdatedVariables(
const OpKernelContext* ctx, absl::Span<const Tensor* const> inputs,
absl::Span<const int> variable_indices,
const XlaCompiler::CompilationResult& compilation_result,
std::vector<VariableInfo>* variable_infos) {
std::set<int> variables_updated;
for (const auto& resource_update : compilation_result.resource_updates) {
if (resource_update.modified) {
variables_updated.insert(resource_update.input_index);
}
}
return GetVariableInfosFromInputs(ctx->resource_manager(), ctx->device(),
inputs, variable_indices,
&variables_updated, variable_infos);
}
// Get-or-create thread pool for a given collective.
static thread::ThreadPool* GetOrCreateThreadPoolForCollective(
const XlaCompilationResult::CollectiveInfo& collective_info) {
static absl::Mutex m(absl::kConstInit);
static auto& thread_pool_cache ABSL_GUARDED_BY(m) =
*new absl::node_hash_map<XlaCompilationResult::CollectiveInfo,
thread::ThreadPool>();
absl::MutexLock l(m);
auto it = thread_pool_cache.find(collective_info);
if (it == thread_pool_cache.end()) {
// Create & cache thread pool.
auto inserted_it = thread_pool_cache.emplace(
std::piecewise_construct, std::forward_as_tuple(collective_info),
std::forward_as_tuple(Env::Default(), "xla_collective_thread_pool",
collective_info.group_size));
return &inserted_it.first->second;
}
return &it->second;
}
void RunInThreadPoolIfCollectivesPresent(
const XlaCompiler::CompilationResult& compilation_result,
std::function<void()> execution_fn) {
// If we are using collectives, we need to run in a separate threadpool.
if (compilation_result.collective_info.has_value()) {
GetOrCreateThreadPoolForCollective(*compilation_result.collective_info)
->Schedule(execution_fn);
} else {
// Otherwise, just run normally: we merely "pretend" to be asynchronous.
execution_fn();
}
}
} // namespace
XlaLocalLaunchBase::XlaLocalLaunchBase(OpKernelConstruction* ctx,
const std::vector<int>& constants,
const std::vector<int>& resources,
const NameAttrList& function,
bool has_ref_vars)
: AsyncOpKernel(ctx),
constants_(constants),
resources_(resources),
function_(function),
platform_info_(XlaPlatformInfoFromDevice(ctx->device())),
has_ref_vars_(has_ref_vars) {}
void XlaLocalLaunchBase::ComputeAsync(OpKernelContext* ctx, DoneCallback done) {
VLOG(1) << "XlaLocalLaunchOpBase::Compute "
<< Canonicalize(function_.name(), AttrSlice(&function_.attr()));
xla_launch_counter->GetCell(platform_info_.device_type().type_string())
->IncrementBy(1);
std::vector<const Tensor*> inputs = InputsFromContext(ctx);
std::vector<XlaCompiler::Argument> xla_compiler_args;
const XlaCompiler::CompilationResult* compilation_result;
xla::LocalClient* client; // Not owned.
xla::LocalExecutable* executable; // Not owned.
xla::PjRtClient* pjrt_client; // Not owned.
xla::PjRtLoadedExecutable* pjrt_executable; // Not owned.
// Note that here we assume the shape of the variables don't change between
// compilation and execution. The locks on the variables are released before
// compilation so that we can achieve parallel compilation of different batch
// sizes during warm-up.
{
// Creating a scope so that the locks on the variables are released when
// variable_infos goes out of scope.
std::vector<VariableInfo> variable_infos;
std::set<int> variables_updated;
// Here we only need to reader-lock the variables, so we pass an empty
// variables_updated set here.
absl::Status status = GetVariableInfosFromInputs(
ctx->resource_manager(), ctx->device(), inputs, resources_,
&variables_updated, &variable_infos);
OP_REQUIRES_OK_ASYNC(ctx, status, done);
status = LockVariables(absl::MakeSpan(variable_infos));
OP_REQUIRES_OK_ASYNC(ctx, status, done);
auto status_or_xla_compiler_args =
XlaComputationLaunchContext::BuildXlaCompilerArguments(
constants_, inputs, variable_infos,
static_cast<Device*>(ctx->device()));
OP_REQUIRES_OK_ASYNC(ctx, status_or_xla_compiler_args.status(), done);
xla_compiler_args = std::move(status_or_xla_compiler_args.value());
}
bool use_pjrt = GetXlaOpsCommonFlags()
->tf_xla_use_device_api.IsEnabledInXlaLaunchForDevice(
platform_info_.device_type());
if (use_pjrt) {
VLOG(2) << "Compiling using PJRT";
absl::Status status = CompileToPjRtLoadedExecutable(
*ctx, platform_info_, function_, xla_compiler_args,
DeviceCompileMode::kStrict, has_ref_vars_,
/*may_alias_resource_update=*/true, &compilation_result, &pjrt_client,
&pjrt_executable);
OP_REQUIRES_OK_ASYNC(ctx, status, done);
VLOG(2) << "Compiled using PJRT: " << status;
VLOG(2) << "pjrt_executable != nullptr: " << (pjrt_executable != nullptr);
VLOG(2) << "compilation_result != nullptr: "
<< (compilation_result != nullptr);
VLOG(2) << "Executing using PJRT.";
auto run_pjrt_cluster = [ctx, pjrt_client, pjrt_executable,
compilation_result, done, inputs,
resources = resources_]() {
// Separate scope so that VariableInfo locks are released before done() is
// called.
{
std::vector<VariableInfo> variable_infos;
OP_REQUIRES_OK_ASYNC(
ctx,
GetUpdatedVariables(ctx, inputs, resources, *compilation_result,
&variable_infos),
done);
OP_REQUIRES_OK_ASYNC(ctx, LockVariables(absl::MakeSpan(variable_infos)),
done);
OP_REQUIRES_OK_ASYNC(
ctx,
RunPjRtExecutable(inputs, variable_infos, *compilation_result,
pjrt_client, pjrt_executable, ctx),
done);
}
VLOG(2) << "Done executing with PJRT.";
done();
};
RunInThreadPoolIfCollectivesPresent(*compilation_result, run_pjrt_cluster);
return;
}
absl::Status status = CompileToLocalExecutable(
ctx, function_, /*has_ref_vars=*/has_ref_vars_, platform_info_,
xla_compiler_args, DeviceCompileMode::kStrict,
/*may_alias_resource_update=*/true, &client, &compilation_result,
&executable);
OP_REQUIRES_OK_ASYNC(ctx, status, done);
// Continuation of the execution, may be run in a different thread.
auto run_xla_cluster = [ctx, client, executable, compilation_result, done,
inputs, resources = resources_]() {
// Separate scope so that VariableInfo locks are released before done is
// called.
{
auto platform_info = XlaPlatformInfoFromDevice(ctx->device());
std::vector<VariableInfo> variable_infos;
OP_REQUIRES_OK_ASYNC(
ctx,
GetUpdatedVariables(ctx, inputs, resources, *compilation_result,
&variable_infos),
done);
OP_REQUIRES_OK_ASYNC(ctx, LockVariables(absl::MakeSpan(variable_infos)),
done);
absl::flat_hash_map<int, const Tensor*> resource_var_ptrs;
for (int i = 0; i < resources.size(); i++) {
resource_var_ptrs[resources[i]] = variable_infos[i].var()->tensor();
}
std::shared_ptr<stream_executor::DeviceAddressAllocator> allocator =
GetAllocator(ctx->device(), GetStream(ctx), platform_info);
XlaComputationLaunchContext launch_context =
GetLaunchContext(platform_info, ctx, client, allocator.get());
const xla::HloInputOutputAliasConfig& input_output_alias =
executable->executable()->module().input_output_alias_config();
absl::StatusOr<std::vector<xla::ExecutionInput>> execution_inputs =
launch_context.PopulateInputs(
ctx, compilation_result, resource_var_ptrs,
/*missing_ctx_input_prefix=*/0, input_output_alias);
OP_REQUIRES_OK_ASYNC(ctx, execution_inputs.status(), done);
xla::gpu::GpuExecutableRunOptions gpu_options;
xla::DeviceAssignment device_assignment;
xla::ExecutableRunOptions run_options;
if (compilation_result->collective_info.has_value()) {
OP_REQUIRES_OK_ASYNC(ctx,
ResolveDeviceAssignment(
ctx, *compilation_result->collective_info,
run_options, device_assignment, gpu_options),
done);
}
// Hardcode run id to always be zero: TF distributed strategy
// differentiates between subsequent runs using dependency edges. This
// is safe, as only TF dist-strat can produce distributed ops, and we
// can rely on TF dist-strat invariants.
xla::RunId run_id(0);
run_options.set_run_id(run_id);
absl::StatusOr<xla::ExecutionOutput> execution_output = RunExecutable(
platform_info, launch_context, std::move(*execution_inputs),
run_options, executable, ctx, allocator.get());
OP_REQUIRES_ASYNC(ctx, execution_output.ok(), execution_output.status(),
done);
OP_REQUIRES_OK_ASYNC(
ctx,
launch_context.PopulateOutputs(
ctx, compilation_result, execution_output->ConsumeResult(),
/*missing_ctx_input_prefix=*/0, absl::MakeSpan(variable_infos),
input_output_alias, resource_var_ptrs),
done);
VLOG(1) << "Done";
}
done();
};
RunInThreadPoolIfCollectivesPresent(*compilation_result, run_xla_cluster);
}
namespace {
// Helper static functions to construct parameters for
// XlaLocalLaunchBase constructor from OpKernelConstruction.
std::vector<int> ConstantsVector(OpKernelConstruction* ctx) {
DataTypeVector constant_types;
OP_REQUIRES_OK_RETURN(ctx, std::vector<int>(),
ctx->GetAttr("Tconstants", &constant_types));
std::vector<int> constants(constant_types.size());
std::iota(constants.begin(), constants.end(), 0);
return constants;
}
std::vector<int> ResourcesVector(OpKernelConstruction* ctx) {
DataTypeVector constant_types;
OP_REQUIRES_OK_RETURN(ctx, std::vector<int>(),
ctx->GetAttr("Tconstants", &constant_types));
DataTypeVector arg_types;
OP_REQUIRES_OK_RETURN(ctx, std::vector<int>(),
ctx->GetAttr("Targs", &arg_types));
int num_resources;
OP_REQUIRES_OK_RETURN(ctx, std::vector<int>(),
ctx->GetAttr("Nresources", &num_resources));
std::vector<int> resources(num_resources);
std::iota(resources.begin(), resources.end(),
constant_types.size() + arg_types.size());
return resources;
}
NameAttrList FunctionAttr(OpKernelConstruction* ctx) {
const NameAttrList* func;
OP_REQUIRES_OK_RETURN(ctx, NameAttrList(), ctx->GetAttr("function", &func));
return *func;
}
std::vector<int> VectorAttr(OpKernelConstruction* ctx,
absl::string_view attr_name) {
std::vector<int> vec;
OP_REQUIRES_OK_RETURN(ctx, std::vector<int>(), ctx->GetAttr(attr_name, &vec));
return vec;
}
bool MustCompileAttr(OpKernelConstruction* ctx) {
bool must_compile;
OP_REQUIRES_OK_RETURN(ctx, false,
ctx->GetAttr("must_compile", &must_compile));
return must_compile;
}
bool HasRefVars(OpKernelConstruction* ctx) {
bool has_ref_vars;
OP_REQUIRES_OK_RETURN(ctx, false,
ctx->GetAttr(kXlaHasReferenceVarsAttr, &has_ref_vars));
return has_ref_vars;
}
class XlaLaunchV2Op : public XlaLocalLaunchBase {
public:
explicit XlaLaunchV2Op(OpKernelConstruction* ctx)
: XlaLocalLaunchBase(ctx, VectorAttr(ctx, "constants"),
VectorAttr(ctx, "resources"), FunctionAttr(ctx),
/*has_ref_vars=*/true) {}
};
} // namespace
XlaLocalLaunchOp::XlaLocalLaunchOp(OpKernelConstruction* ctx)
: XlaLocalLaunchBase(ctx, ConstantsVector(ctx), ResourcesVector(ctx),
FunctionAttr(ctx), /*has_ref_vars=*/true) {}
XlaLocalLaunchOp::~XlaLocalLaunchOp() {
VLOG(1) << "XlaLocalLaunchOp destroyed";
}
XlaCompileOp::XlaCompileOp(OpKernelConstruction* ctx)
: OpKernel(ctx),
constants_(ConstantsVector(ctx)),
resources_(ResourcesVector(ctx)),
function_(FunctionAttr(ctx)),
platform_info_(XlaPlatformInfoFromDevice(ctx->device())),
must_compile_(MustCompileAttr(ctx)),
has_ref_vars_(HasRefVars(ctx)) {}
void XlaCompileOp::Compute(OpKernelContext* ctx) {
VLOG(3) << "XlaCompileOp " << def().name()
<< (must_compile_ ? "(must-compile)" : "");
const XlaCompiler::CompilationResult* kernel = nullptr;
xla::LocalClient* client = nullptr;
xla::LocalExecutable* executable = nullptr;
xla::PjRtClient* pjrt_client = nullptr;
xla::PjRtLoadedExecutable* pjrt_executable = nullptr;
ResourceVarsSnapshot variables_snapshot;
std::vector<const Tensor*> inputs = InputsFromContext(ctx);
bool cannot_compile_cluster;
{
mutex_lock guard(cannot_compile_cluster_mu_);
cannot_compile_cluster = cannot_compile_cluster_;
}
DeviceCompileMode compile_mode = [&] {
if (must_compile_) {
return DeviceCompileMode::kStrict;
}
return GetXlaOpsCommonFlags()->tf_xla_async_compilation
? DeviceCompileMode::kAsync
: DeviceCompileMode::kLazy;
}();
bool use_pjrt =
GetXlaOpsCommonFlags()
->tf_xla_use_device_api.IsEnabledInXlaCompileAndRunForDevice(
platform_info_.device_type());
if (GetXlaOpsCommonFlags()->tf_xla_always_defer_compilation ||
cannot_compile_cluster) {
executable = nullptr;
} else {
auto args_and_variables_snapshot = GetXlaCompilerArgsAndSnapshotVariables(
resources_, constants_, inputs, ctx);
OP_REQUIRES_OK(ctx, args_and_variables_snapshot.status());
const std::vector<XlaCompiler::Argument>& args =
args_and_variables_snapshot->first;
variables_snapshot = std::move(args_and_variables_snapshot->second);
// Do not alias resource updates as locking variables in XlaCompile and
// unlocking them in XlaRun may lead to deadlocks.
absl::Status status;
if (use_pjrt) {
VLOG(2) << "Using PJRT for compilation. Function name: "
<< function_.name();
status = CompileToPjRtLoadedExecutable(
*ctx, platform_info_, function_, args, compile_mode, has_ref_vars_,
/*may_alias_resource_update=*/false, &kernel, &pjrt_client,
&pjrt_executable);
} else {
status = CompileToLocalExecutable(
ctx, function_, has_ref_vars_, platform_info_, args, compile_mode,
/*may_alias_resource_update=*/false, &client, &kernel, &executable);
}
if (compile_mode != DeviceCompileMode::kLazy ||
status.code() != error::UNIMPLEMENTED) {
OP_REQUIRES_OK(ctx, status);
}
if (status.code() == error::UNIMPLEMENTED) {
LOG(WARNING) << "Compilation failed:" << status
<< ". Falling back to TF function call.";
BroadcastOptimizationRemark(
XlaOptimizationRemark::UNIMPLEMENTED_OPERATION, status.ToString())
.IgnoreError();
executable = nullptr;
pjrt_executable = nullptr;
mutex_lock guard(cannot_compile_cluster_mu_);
cannot_compile_cluster_ = true;
}
}
AllocatorAttributes host_alloc_attrs;
host_alloc_attrs.set_gpu_compatible(true);
host_alloc_attrs.set_on_host(true);
Allocator* cpu_allocator = ctx->device()->GetAllocator(host_alloc_attrs);
// Async compilation returns nullptr executable without an error.
if (!executable && !pjrt_executable) {
DCHECK(!must_compile_);
Tensor compilation_key(cpu_allocator, DT_STRING, TensorShape({}));
Tensor compilation_successful(cpu_allocator, DT_BOOL, TensorShape({}));
compilation_successful.scalar<bool>()() = false;
ctx->set_output(0, compilation_key);
ctx->set_output(1, compilation_successful);
return;
}
// Each execution of an XlaCompile op creates a new ExecutableClosure, even
// if it didn't have to compile the cluster because of a compilation-cache
// hit. This is because we at least need new snapshots of the resource
// variables.
Tensor compilation_key(cpu_allocator, DT_STRING, TensorShape({}));
if (use_pjrt) {
PjRtExecutableClosureStore::KeyT key =
PjRtExecutableClosureStore::Global()->Produce(PjRtExecutableClosure(
pjrt_client, pjrt_executable, kernel, std::move(variables_snapshot),
constants_.size()));
compilation_key.flat<tstring>()(0) = key;
VLOG(2) << "Compiled with PJRT. compilation_key: " << key;
} else {
XlaExecutableClosureStore::KeyT key =
XlaExecutableClosureStore::Global()->Produce(XlaExecutableClosure(
client, executable, kernel, std::move(variables_snapshot),
constants_.size()));
compilation_key.flat<tstring>()(0) = key;
VLOG(2) << "Compiled with XLA. compilation_key: " << key;
}
Tensor compilation_successful(cpu_allocator, DT_BOOL, TensorShape({}));
compilation_successful.flat<bool>()(0) = true;
ctx->set_output(0, compilation_key);
ctx->set_output(1, compilation_successful);
}
XlaRunOp::XlaRunOp(OpKernelConstruction* ctx)
: OpKernel(ctx), platform_info_(XlaPlatformInfoFromDevice(ctx->device())) {}
void XlaRunOp::Compute(OpKernelContext* ctx) {
VLOG(3) << "XlaRunOp " << def().name();
Tensor key_tensor = ctx->input(ctx->num_inputs() - 1);
bool use_pjrt =
GetXlaOpsCommonFlags()
->tf_xla_use_device_api.IsEnabledInXlaCompileAndRunForDevice(
platform_info_.device_type());
if (use_pjrt) {
const PjRtExecutableClosureStore::KeyT& key = key_tensor.flat<tstring>()(0);
PjRtExecutableClosure closure =
PjRtExecutableClosureStore::Global()->Consume(key);
// Fetch inputs from the OpKernelContext. Inputs are the same as the ones
// for XlaCompile, except that the must-be-constant inputs that appear in
// the beginning are stripped off and the closure key is appended as the
// last input. So the inputs look like: input tensors, resource variables,
// closure key tensor.
std::vector<const Tensor*> inputs = InputsFromContext(ctx);
absl::flat_hash_map<int, const Tensor*> variable_snapshots;
for (const auto& [variable_index, variable_tensor] :
closure.resource_var_snapshots()) {
variable_snapshots.emplace(variable_index, variable_tensor.has_value()
? &variable_tensor.value()
: nullptr);
}
{
absl::StatusOr<std::vector<VariableInfo>> updated_variables =
GatherVariableInfo(ctx, *closure.compilation_result(),
closure.num_constant_args());
OP_REQUIRES_OK(ctx, updated_variables.status());
OP_REQUIRES_OK(ctx, LockVariables(absl::MakeSpan(*updated_variables)));
OP_REQUIRES_OK(
ctx, RunPjRtExecutable(closure.num_constant_args(), inputs,
variable_snapshots, *updated_variables,
*closure.compilation_result(),
closure.client(), closure.executable(), ctx));
}
OP_REQUIRES_OK(ctx, absl::OkStatus());
return;
}
const XlaExecutableClosureStore::KeyT& key = key_tensor.flat<tstring>()(0);
XlaExecutableClosure closure =
XlaExecutableClosureStore::Global()->Consume(key);
std::shared_ptr<stream_executor::DeviceAddressAllocator> allocator =
GetAllocator(ctx->device(), GetStream(ctx), platform_info_);
XlaComputationLaunchContext launch_context =
GetLaunchContext(platform_info_, ctx, closure.client(), allocator.get());
// We're missing the must-be-constant inputs, tell `PopulateInputs`
// about this. We don't actually need these inputs because they've
// already been baked into the compiled kernel.
const xla::HloInputOutputAliasConfig& input_output_alias =
closure.executable()->executable()->module().input_output_alias_config();
absl::StatusOr<std::vector<xla::ExecutionInput>> execution_inputs;
absl::flat_hash_map<int, const Tensor*> snapshot_ptrs;
{
tsl::profiler::TraceMe hlo_module_activity(
[&] {
return absl::StrCat(
"Populate Inputs (",
closure.compilation_result()->xla_input_shapes.size(), ")");
},
tsl::profiler::TraceMeLevel::kInfo);
for (const auto& [variable_index, variable_tensor] :
closure.resource_var_snapshots()) {
snapshot_ptrs.emplace(variable_index, variable_tensor.has_value()
? &variable_tensor.value()
: nullptr);
}
execution_inputs = launch_context.PopulateInputs(
ctx, closure.compilation_result(), snapshot_ptrs,
/*missing_ctx_input_prefix=*/closure.num_constant_args(),
input_output_alias);
OP_REQUIRES_OK(ctx, execution_inputs.status());
}
xla::ExecutableRunOptions run_options;
// Host callbacks used for HLO send/recv.
xla::SendDeviceMemoryFunction send_function =
GetSendDeviceMemoryFunction(ctx, key);
run_options.set_send_device_memory_function(&send_function);
xla::RecvDeviceMemoryFunction recv_function =
GetRecvDeviceMemoryFunction(ctx, key);
run_options.set_recv_device_memory_function(&recv_function);
absl::StatusOr<xla::ExecutionOutput> execution_output = RunExecutable(
platform_info_, launch_context, std::move(*execution_inputs), run_options,
closure.executable(), ctx, allocator.get());
OP_REQUIRES(ctx, execution_output.ok(), execution_output.status());
tsl::profiler::TraceMe hlo_module_activity(
[&] {
return absl::StrCat("Populate Outputs (", ctx->num_outputs(), ")");
},
tsl::profiler::TraceMeLevel::kInfo);
absl::StatusOr<std::vector<VariableInfo>> variable_infos = GatherVariableInfo(
ctx, *closure.compilation_result(), closure.num_constant_args());
OP_REQUIRES_OK(ctx, variable_infos.status());
OP_REQUIRES_OK(ctx, LockVariables(absl::MakeSpan(*variable_infos)));
OP_REQUIRES_OK(
ctx,
launch_context.PopulateOutputs(
ctx, closure.compilation_result(), execution_output->ConsumeResult(),
/*missing_ctx_input_prefix=*/closure.num_constant_args(),
absl::MakeSpan(*variable_infos), input_output_alias, snapshot_ptrs));
}
XlaMergeOp::XlaMergeOp(OpKernelConstruction* ctx) : OpKernel(ctx) {}
void XlaMergeOp::Compute(OpKernelContext* ctx) {
VLOG(3) << "XlaMergeOp " << def().name();
int i = 0;
if (ctx->has_input(i) || ctx->has_input(++i)) {
ctx->set_output(0, ctx->input(i));
}
}
REGISTER_KERNEL_BUILDER(Name("XlaLaunch").Device(DEVICE_CPU), XlaLocalLaunchOp);
REGISTER_KERNEL_BUILDER(Name("XlaLaunchV2").Device(DEVICE_CPU), XlaLaunchV2Op);
REGISTER_KERNEL_BUILDER(Name("XlaLaunch")
.Device(DEVICE_GPU)
.HostMemory("constants")
.HostMemory("resources"),
XlaLocalLaunchOp);
REGISTER_KERNEL_BUILDER(Name("_XlaCompile").Device(DEVICE_CPU), XlaCompileOp);
REGISTER_KERNEL_BUILDER(Name("_XlaCompile")
.Device(DEVICE_GPU)
.HostMemory("constants")
.HostMemory("key")
.HostMemory("compilation_successful")
.HostMemory("resources"),
XlaCompileOp);
REGISTER_KERNEL_BUILDER(Name("_XlaCompile")
.Device(DEVICE_DEFAULT)
.HostMemory("constants")
.HostMemory("key")
.HostMemory("compilation_successful")
.HostMemory("resources"),
XlaCompileOp);
REGISTER_KERNEL_BUILDER(Name("_XlaRun").Device(DEVICE_CPU), XlaRunOp);
REGISTER_KERNEL_BUILDER(Name("_XlaRun").Device(DEVICE_GPU).HostMemory("key"),
XlaRunOp);
REGISTER_KERNEL_BUILDER(
Name("_XlaRun").Device(DEVICE_DEFAULT).HostMemory("key"), XlaRunOp);
REGISTER_KERNEL_BUILDER(Name("_XlaMerge").Device(DEVICE_CPU), XlaMergeOp);
REGISTER_KERNEL_BUILDER(Name("_XlaMerge").Device(DEVICE_GPU), XlaMergeOp);
REGISTER_KERNEL_BUILDER(Name("_XlaMerge").Device(DEVICE_DEFAULT), XlaMergeOp);
} // namespace tensorflow