/* Copyright 2022 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/device_compiler.h" #include #include #include #include #include #include #include #include #include #include "absl/algorithm/container.h" #include "absl/log/check.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "absl/synchronization/notification.h" #include "absl/time/clock.h" #include "absl/time/time.h" #include "tensorflow/cc/framework/scope.h" #include "tensorflow/cc/ops/function_ops.h" #include "tensorflow/cc/ops/math_ops.h" #include "tensorflow/compiler/jit/device_compilation_cluster_signature.h" #include "tensorflow/compiler/jit/device_compiler_client.h" #include "tensorflow/compiler/jit/tests/device_compiler_test_helper.h" #include "tensorflow/compiler/jit/tf_graph_to_hlo_compiler.pb.h" #include "tensorflow/compiler/jit/xla_compile_util.h" #include "tensorflow/compiler/jit/xla_device_compiler_client.h" #include "tensorflow/compiler/tf2xla/xla_argument.h" #include "tensorflow/compiler/tf2xla/xla_compiler.h" #include "xla/client/client_library.h" #include "xla/client/local_client.h" #include "xla/hlo/builder/xla_computation.h" #include "xla/stream_executor/platform_manager.h" #include "xla/tsl/lib/core/status_test_util.h" #include "xla/tsl/lib/strings/proto_serialization.h" #include "xla/tsl/platform/statusor.h" #include "tensorflow/core/framework/fake_input.h" #include "tensorflow/core/framework/function.h" #include "tensorflow/core/framework/graph_to_functiondef.h" #include "tensorflow/core/framework/node_def_builder.h" #include "tensorflow/core/framework/resource_base.h" #include "tensorflow/core/kernels/ops_testutil.h" #include "tensorflow/core/lib/core/status_test_util.h" #include "tensorflow/core/platform/env.h" #include "tensorflow/core/platform/errors.h" #include "tensorflow/core/platform/status.h" #include "tensorflow/core/platform/status_matchers.h" #include "tensorflow/core/platform/statusor.h" #include "tensorflow/core/platform/test.h" #include "tsl/platform/path.h" namespace tensorflow { namespace { using ::testing::_; using ::testing::ElementsAreArray; using ::testing::EndsWith; using ::testing::Eq; using ::testing::EqualsProto; using ::testing::IsTrue; using ::testing::NotNull; using ::testing::Return; using ::testing::SizeIs; using ::testing::StrEq; using ::testing::UnorderedElementsAre; using ::testing::status::IsOk; using XlaDeviceCompiler = DeviceCompiler; using XlaDeviceExecutablePersistor = DeviceExecutablePersistor; using Signature = DeviceCompilationClusterSignature; xla::LocalClient* GetLocalClient() { // TODO(b/255826209): Figure out how to run this test with the CPU client as // well. auto platform = se::PlatformManager::PlatformWithName("cuda").value(); return xla::ClientLibrary::GetOrCreateLocalClient(platform).value(); } XlaDeviceCompiler* CreateXlaDeviceCompiler(bool enable_persistence = false) { auto xla_compiler_client = std::make_unique(GetLocalClient()); auto xla_persistor = std::make_unique( XlaDeviceExecutablePersistor::Config{ enable_persistence ? testing::TmpDir() : "", false, "xla"}, DeviceType(DEVICE_GPU_XLA_JIT)); return new XlaDeviceCompiler(std::move(xla_persistor), std::move(xla_compiler_client)); } absl::StatusOr> SampleGraphAddXY() { std::unique_ptr graph(new Graph(OpRegistry::Global())); Scope scope = Scope::NewRootScope().ExitOnError(); auto a = ops::_Arg(scope.WithOpName("A"), DT_INT32, 0); auto b = ops::_Arg(scope.WithOpName("B"), DT_INT32, 1); auto c = ops::Add(scope.WithOpName("C"), a, b); auto d = ops::_Retval(scope.WithOpName("D"), c, 0); TF_RETURN_IF_ERROR(scope.ToGraph(graph.get())); return graph; } absl::StatusOr SampleFuntionAddXY(const std::string& name) { TF_ASSIGN_OR_RETURN(auto graph, SampleGraphAddXY()); FunctionDef fdef; TF_RETURN_IF_ERROR(GraphToFunctionDef(*graph, name, &fdef)); return fdef; } std::vector SampleArgsForAddXY() { std::vector args(2); args[0].kind = XlaCompiler::Argument::kParameter; args[0].type = DT_INT32; args[0].shape = TensorShape({2}); args[1].kind = XlaCompiler::Argument::kParameter; args[1].type = DT_INT32; args[1].shape = TensorShape({2}); return args; } class MockXlaDeviceExecutablePersistor : public DeviceExecutablePersistor { public: MockXlaDeviceExecutablePersistor() : DeviceExecutablePersistor( Config{testing::TmpDir(), false, "xla"}, DeviceType(DEVICE_CPU_XLA_JIT)) {} MOCK_METHOD(absl::Status, TryToPersistExecutable, (uint64_t, const std::string&, const XlaCompiler::Options&, const XlaCompiler::CompilationResult&, const xla::LocalExecutable&, (DeviceCompilerClient*)), (const, override)); }; class MockDeviceCompilationProfiler : public DeviceCompilationProfiler { public: MOCK_METHOD(bool, ShouldCompileCluster, (const NameAttrList& function, DeviceCompileMode compile_mode, int64_t current_request_count), (override)); MOCK_METHOD(absl::Status, RegisterCompilation, (const NameAttrList& function, int64_t compile_time_us, bool used_persistent_cache), (override)); }; class DeviceCompilerTest : public ::testing::Test { protected: void SetUp() override { flib_def_ = std::make_unique( OpRegistry::Global(), FunctionDefLibrary()); TF_ASSERT_OK_AND_ASSIGN(auto fdef, SampleFuntionAddXY("foo")); TF_ASSERT_OK(flib_def_->AddFunctionDef(fdef)); profiler_ = new DeviceCompilationProfiler(); profiler_ref_ = std::make_unique(profiler_); mock_profiler_ = new MockDeviceCompilationProfiler(); mock_profiler_ref_ = std::make_unique(mock_profiler_); xla_device_compiler_ = CreateXlaDeviceCompiler(); xla_device_compiler_ref_ = std::make_unique(xla_device_compiler_); auto listener = std::make_unique(); listener_ = listener.get(); RegisterXlaActivityListener(std::move(listener)); } XlaCompiler::Options GetDefaultXlaOptions() { XlaCompiler::Options options; options.device_type = DeviceType(DEVICE_GPU_XLA_JIT); options.client = xla_device_compiler_->client(); options.flib_def = flib_def_.get(); return options; } absl::StatusOr> BuildSampleXlaExecutable() { TF_ASSIGN_OR_RETURN(auto graph, SampleGraphAddXY()); auto args = SampleArgsForAddXY(); // Compiles the graph. XlaCompiler compiler(GetDefaultXlaOptions()); XlaCompiler::CompilationResult compilation_result; TF_RETURN_IF_ERROR(compiler.CompileGraph(XlaCompiler::CompileOptions(), "graph", std::move(graph), args, &compilation_result)); return xla_device_compiler_->compiler_client()->BuildExecutable( GetDefaultXlaOptions(), compilation_result); } std::unique_ptr flib_def_; JitCompilationListener* listener_; DeviceCompilationProfiler* profiler_; std::unique_ptr profiler_ref_; MockDeviceCompilationProfiler* mock_profiler_; std::unique_ptr mock_profiler_ref_; XlaDeviceCompiler* xla_device_compiler_; std::unique_ptr xla_device_compiler_ref_; }; TEST_F(DeviceCompilerTest, CompileStrictSuccess) { const XlaCompiler::CompilationResult* compilation_result = nullptr; xla::LocalExecutable* xla_executable = nullptr; XlaCompiler::Options options = GetDefaultXlaOptions(); NameAttrList fn; fn.set_name("foo"); TF_EXPECT_OK(xla_device_compiler_->CompileIfNeeded( options, fn, SampleArgsForAddXY(), XlaCompiler::CompileOptions{}, DeviceCompileMode::kStrict, profiler_, &compilation_result, &xla_executable)); EXPECT_TRUE(compilation_result != nullptr); EXPECT_TRUE(xla_executable != nullptr); } TEST_F(DeviceCompilerTest, CompileShouldCompileClusterFalse) { const XlaCompiler::CompilationResult* compilation_result = nullptr; xla::LocalExecutable* xla_executable = nullptr; XlaCompiler::Options options = GetDefaultXlaOptions(); NameAttrList fn; fn.set_name("foo"); // Using a mock here since it's difficult to have ShouldCompileCluster() // return false. EXPECT_CALL(*mock_profiler_, ShouldCompileCluster(_, DeviceCompileMode::kLazy, 1)) .WillOnce(Return(false)); TF_EXPECT_OK(xla_device_compiler_->CompileIfNeeded( options, fn, SampleArgsForAddXY(), XlaCompiler::CompileOptions{}, DeviceCompileMode::kLazy, mock_profiler_, &compilation_result, &xla_executable)); EXPECT_TRUE(compilation_result == nullptr); EXPECT_TRUE(xla_executable == nullptr); } TEST_F(DeviceCompilerTest, CompileCacheHit) { const XlaCompiler::CompilationResult* compilation_result = nullptr; xla::LocalExecutable* xla_executable = nullptr; XlaCompiler::Options options = GetDefaultXlaOptions(); NameAttrList fn; fn.set_name("foo"); TF_EXPECT_OK(xla_device_compiler_->CompileIfNeeded( options, fn, SampleArgsForAddXY(), XlaCompiler::CompileOptions{}, DeviceCompileMode::kStrict, profiler_, &compilation_result, &xla_executable)); EXPECT_TRUE(compilation_result != nullptr); EXPECT_TRUE(xla_executable != nullptr); const XlaCompiler::CompilationResult* new_compilation_result = nullptr; xla::LocalExecutable* new_xla_executable = nullptr; // Request compiling the same function again. TF_EXPECT_OK(xla_device_compiler_->CompileIfNeeded( options, fn, SampleArgsForAddXY(), XlaCompiler::CompileOptions{}, DeviceCompileMode::kStrict, profiler_, &new_compilation_result, &new_xla_executable)); // new_compilation_result and new_xla_executable should point to the // compilation_result and executable returned after the first compilation // request. EXPECT_EQ(compilation_result, new_compilation_result); EXPECT_EQ(xla_executable, new_xla_executable); } TEST_F(DeviceCompilerTest, CompileAsyncSuccess) { const XlaCompiler::CompilationResult* compilation_result = nullptr; xla::LocalExecutable* xla_executable = nullptr; XlaCompiler::Options options = GetDefaultXlaOptions(); NameAttrList fn; fn.set_name("foo"); // Using a mock here to determine when the async compilation finishes. This is // to avoid using absl::SleepFor(). // `RegisterCompilation` is the last call that happens just before the async // compilation completes. We use the completion of this call to determine when // the compilation finshes to verify expected behavior. absl::Notification done; EXPECT_CALL(*mock_profiler_, ShouldCompileCluster(_, DeviceCompileMode::kAsync, 1)) .WillOnce(Return(true)); EXPECT_CALL(*mock_profiler_, RegisterCompilation(_, _, false)) .WillOnce([&done] { done.Notify(); return absl::OkStatus(); }); auto args = SampleArgsForAddXY(); TF_EXPECT_OK(xla_device_compiler_->CompileIfNeeded( options, fn, args, XlaCompiler::CompileOptions{}, DeviceCompileMode::kAsync, mock_profiler_, &compilation_result, &xla_executable)); // compilation_result and xla_executable aren't available immediately after // requesting compilation in asynchronous mode. EXPECT_TRUE(compilation_result == nullptr); EXPECT_TRUE(xla_executable == nullptr); // Check if an appropriate entry is made in xla_cache. auto xla_cache = xla_device_compiler_->cache(); TF_ASSERT_OK_AND_ASSIGN(auto signature, Signature::Build(fn, args)); auto cache_value = xla_cache->Lookup(signature); EXPECT_TRUE(cache_value); EXPECT_TRUE(cache_value->compile_state != DeviceCompileState::kUncompiled); // Wait for async compilation to complete. done.WaitForNotification(); cache_value = xla_cache->Lookup(signature); EXPECT_TRUE(cache_value); EXPECT_TRUE(cache_value->compile_state == DeviceCompileState::kCompiled); EXPECT_TRUE(cache_value->compilation_result != nullptr); EXPECT_TRUE(cache_value->executable != nullptr); EXPECT_TRUE(cache_value->compilation_status.ok()); } TEST_F(DeviceCompilerTest, CompilePersistentCacheEnabled) { auto xla_device_compiler = CreateXlaDeviceCompiler(/*enable_persistence=*/true); core::ScopedUnref xla_device_compiler_ref(xla_device_compiler); NameAttrList fn; fn.set_name("foo"); auto args = SampleArgsForAddXY(); XlaCompiler::Options options = GetDefaultXlaOptions(); const XlaCompiler::CompilationResult* compilation_result = nullptr; xla::LocalExecutable* xla_executable = nullptr; TF_EXPECT_OK(xla_device_compiler->CompileIfNeeded( options, fn, args, XlaCompiler::CompileOptions{}, DeviceCompileMode::kStrict, profiler_, &compilation_result, &xla_executable)); EXPECT_TRUE(compilation_result != nullptr); EXPECT_TRUE(xla_executable != nullptr); // Check if device_compiler was able to load the executable from the // persistent cache. std::vector activity_history = listener_->GetListenerHistory(); EXPECT_EQ(activity_history.size(), 1); EXPECT_EQ(activity_history[0].cluster_name(), fn.name()); EXPECT_EQ(activity_history[0].compile_count(), 1); EXPECT_FALSE(activity_history[0].used_persistent_cache()); listener_->ClearListenerHistory(); // Create another DeviceCompiler object pointing to the same persistent cache // directory. It should load the executable instead of building it. auto xla_device_compiler_2 = CreateXlaDeviceCompiler(/*enable_persistence=*/true); core::ScopedUnref xla_device_compiler_ref_2(xla_device_compiler_2); auto profiler = new DeviceCompilationProfiler(); core::ScopedUnref profiler_ref(profiler); const XlaCompiler::CompilationResult* compilation_result_2 = nullptr; xla::LocalExecutable* xla_executable_2 = nullptr; TF_EXPECT_OK(xla_device_compiler_2->CompileIfNeeded( options, fn, args, XlaCompiler::CompileOptions{}, DeviceCompileMode::kStrict, profiler, &compilation_result_2, &xla_executable_2)); EXPECT_TRUE(compilation_result_2 != nullptr); EXPECT_TRUE(xla_executable_2 != nullptr); activity_history = listener_->GetListenerHistory(); EXPECT_EQ(activity_history.size(), 1); EXPECT_EQ(activity_history[0].cluster_name(), fn.name()); EXPECT_EQ(activity_history[0].compile_count(), 1); // Verify that the executable was loaded instead of built. EXPECT_TRUE(activity_history[0].used_persistent_cache()); } TEST_F(DeviceCompilerTest, CompileFailedToLoadFromPersistentCache) { auto xla_device_compiler = CreateXlaDeviceCompiler(/*enable_persistence=*/true); core::ScopedUnref xla_device_compiler_ref(xla_device_compiler); NameAttrList fn; fn.set_name("foo"); auto args = SampleArgsForAddXY(); XlaCompiler::Options options = GetDefaultXlaOptions(); const XlaCompiler::CompilationResult* compilation_result = nullptr; xla::LocalExecutable* xla_executable = nullptr; // Persist an executable. TF_EXPECT_OK(xla_device_compiler->CompileIfNeeded( options, fn, args, XlaCompiler::CompileOptions{}, DeviceCompileMode::kStrict, profiler_, &compilation_result, &xla_executable)); // Corrupt the file which contains the serialized executable. std::vector files; TF_ASSERT_OK(Env::Default()->GetChildren(testing::TmpDir(), &files)); std::string const* serialized_executable_filename = nullptr; for (const auto& file : files) { if (absl::StartsWith(file, "xla__")) { serialized_executable_filename = &file; break; } } EXPECT_TRUE(serialized_executable_filename != nullptr); std::string serialized_executable_filepath = io::JoinPath(testing::TmpDir(), *serialized_executable_filename); std::unique_ptr serialized_executable_file; TF_ASSERT_OK(Env::Default()->NewWritableFile(serialized_executable_filepath, &serialized_executable_file)); TF_ASSERT_OK(serialized_executable_file->Append("Garbage.")); TF_ASSERT_OK(serialized_executable_file->Close()); // Create another DeviceCompiler object pointing to the same persistent cache // directory. It should error out while loading the executable from the // corrupt file. auto xla_device_compiler_2 = CreateXlaDeviceCompiler(/*enable_persistence=*/true); core::ScopedUnref xla_device_compiler_ref_2(xla_device_compiler_2); const XlaCompiler::CompilationResult* compilation_result_2 = nullptr; xla::LocalExecutable* xla_executable_2 = nullptr; EXPECT_FALSE(xla_device_compiler_2 ->CompileIfNeeded(options, fn, args, XlaCompiler::CompileOptions{}, DeviceCompileMode::kStrict, profiler_, &compilation_result_2, &xla_executable_2) .ok()); EXPECT_TRUE(compilation_result_2 == nullptr); EXPECT_TRUE(xla_executable_2 == nullptr); } TEST_F(DeviceCompilerTest, CompileStrictPersistentCacheFailedToPersist) { auto xla_compiler_client = std::make_unique(GetLocalClient()); auto xla_persistor = std::make_unique(); auto xla_device_compiler = new XlaDeviceCompiler( std::move(xla_persistor), std::move(xla_compiler_client)); core::ScopedUnref xla_device_compiler_ref(xla_device_compiler); NameAttrList fn; fn.set_name("foo"); auto args = SampleArgsForAddXY(); XlaCompiler::Options options = GetDefaultXlaOptions(); const XlaCompiler::CompilationResult* compilation_result = nullptr; xla::LocalExecutable* xla_executable = nullptr; auto persistor = absl::down_cast( xla_device_compiler->persistor()); TF_ASSERT_OK_AND_ASSIGN(auto signature, Signature::Build(fn, args)); EXPECT_CALL(*persistor, TryToPersistExecutable(Signature::Hash()(signature), signature.HumanString(), _, _, _, _)) .WillOnce(Return(absl::FailedPreconditionError("Random error."))); EXPECT_THAT(xla_device_compiler->CompileIfNeeded( options, fn, args, XlaCompiler::CompileOptions{}, DeviceCompileMode::kStrict, profiler_, &compilation_result, &xla_executable), absl_testing::StatusIs(error::FAILED_PRECONDITION, ::testing::HasSubstr("Random error."))); EXPECT_TRUE(compilation_result == nullptr); EXPECT_TRUE(xla_executable == nullptr); } class DeviceCompilerTestWithDump : public DeviceCompilerTest { protected: explicit DeviceCompilerTestWithDump() { dump_dir_ = tsl::io::JoinPath( testing::TmpDir(), absl::StrCat("dump_test_", absl::ToUnixNanos(absl::Now()))); CHECK_OK(Env::Default()->RecursivelyCreateDir(dump_dir_)); setenv("TF_GRAPH_TO_HLO_COMPILER_DUMP_DIR", dump_dir_.c_str(), 1); } ~DeviceCompilerTestWithDump() override { unsetenv("TF_GRAPH_TO_HLO_COMPILER_DUMP_DIR"); } std::string dump_dir_; }; TEST_F(DeviceCompilerTestWithDump, CompileStrictDebugInformationDumpWorks) { // We create a new XlaDeviceCompiler here so that we ensure that the cached // result is not used and `CompileStrict` is called. auto xla_device_compiler = CreateXlaDeviceCompiler(/*enable_persistence=*/false); ASSERT_THAT(xla_device_compiler, NotNull()); core::ScopedUnref xla_device_compiler_ref(xla_device_compiler); // Now we run the compilation. We only care that `CompileStrict` has been // actually called, and we don't care about whether it succeeded. const XlaCompiler::CompilationResult* compilation_result = nullptr; xla::LocalExecutable* xla_executable = nullptr; XlaCompiler::Options options = GetDefaultXlaOptions(); XlaCompiler::CompileOptions compile_options; NameAttrList fn; fn.set_name("foo"); xla_device_compiler ->CompileIfNeeded(options, fn, SampleArgsForAddXY(), compile_options, DeviceCompileMode::kStrict, profiler_, &compilation_result, &xla_executable) .IgnoreError(); // Check the directory structure. std::vector dump_dir_files; EXPECT_THAT(Env::Default()->GetChildren(dump_dir_, &dump_dir_files), IsOk()); EXPECT_THAT(dump_dir_files, SizeIs(1)); absl::string_view dump_subdir = dump_dir_files[0]; std::vector dump_subdir_files; EXPECT_THAT( Env::Default()->GetChildren(tsl::io::JoinPath(dump_dir_, dump_subdir), &dump_subdir_files), IsOk()); // `options.pb` and one pb for the compile call arguments. EXPECT_THAT(dump_subdir_files, UnorderedElementsAre("options.pb", EndsWith(".pb"))); // First check the options file. std::string options_file = tsl::io::JoinPath(dump_dir_, dump_subdir, "options.pb"); std::string options_contents; EXPECT_THAT(ReadFileToString(Env::Default(), options_file, &options_contents), IsOk()); TfGraphToHloCompilerOptions options_proto; EXPECT_THAT(options_proto.ParseFromString(options_contents), IsTrue()); EXPECT_THAT(absl::StrCat(tsl::DeterministicProtoHash64(options_proto)), StrEq(dump_subdir)); EXPECT_THAT(options_proto.device_type(), StrEq(options.device_type.type_string())); EXPECT_THAT(options_proto.flib_def(), EqualsProto(options.flib_def->ToProto())); EXPECT_THAT(options_proto.graph_def_version(), Eq(options.graph_def_version)); // Now check the compile call arguments file. absl::string_view compile_call_args_file_name = (dump_subdir_files[0] == "options.pb" ? dump_subdir_files[1] : dump_subdir_files[0]); std::string compile_call_args_file = tsl::io::JoinPath(dump_dir_, dump_subdir, compile_call_args_file_name); std::string compile_call_args_contents; EXPECT_THAT(ReadFileToString(Env::Default(), compile_call_args_file, &compile_call_args_contents), IsOk()); TfGraphToHloCompilerCompileCallArgs compile_call_args_proto; EXPECT_THAT( compile_call_args_proto.ParseFromString(compile_call_args_contents), IsTrue()); EXPECT_THAT(compile_call_args_proto.compile_options(), StrEq(compile_options.DebugString())); EXPECT_THAT(compile_call_args_proto.function(), EqualsProto(fn)); std::vector xla_arguments_from_proto; absl::c_transform(compile_call_args_proto.xla_args(), std::back_inserter(xla_arguments_from_proto), [](const tf2xla::XlaArgumentProto& proto) -> XlaArgument { auto arg = XlaArgument::FromProto(proto); CHECK_OK(arg); // Crash OK return arg.value(); }); EXPECT_THAT(xla_arguments_from_proto, ElementsAreArray(SampleArgsForAddXY())); } TEST_F(OpsTestBase, CompileSingleOpSuccess) { TF_EXPECT_OK(NodeDefBuilder("identity_op", "Identity") .Input(FakeInput(DT_FLOAT)) .Attr("T", DT_FLOAT) .Finalize(node_def())); TF_EXPECT_OK(InitOp()); AddInputFromArray(TensorShape({1, 2}), {6.9, 4.2}); TF_EXPECT_OK(RunOpKernel()); auto xla_device_compiler = CreateXlaDeviceCompiler(); core::ScopedUnref xla_device_compiler_ref(xla_device_compiler); auto profiler = new DeviceCompilationProfiler(); core::ScopedUnref profiler_ref(profiler); const XlaCompiler::CompilationResult* compilation_result = nullptr; xla::LocalExecutable* xla_executable = nullptr; XlaOpRegistry::RegisterCompilationKernels(); auto flib_def = std::make_unique( OpRegistry::Global(), FunctionDefLibrary()); XlaCompiler::Options options; options.device_type = DeviceType(DEVICE_GPU_XLA_JIT); options.client = GetLocalClient(); options.flib_def = flib_def.get(); std::vector args(1); args[0].kind = XlaCompiler::Argument::kConstant; args[0].type = DT_FLOAT; args[0].shape = TensorShape({1, 2}); args[0].constant_value = GetInput(0); args[0].initialized = true; NameAttrList fn; fn.set_name("foo"); TF_EXPECT_OK(xla_device_compiler->CompileSingleOpIfNeeded( options, args, XlaCompiler::CompileOptions{}, context_.get(), profiler, &compilation_result, &xla_executable)); EXPECT_TRUE(compilation_result != nullptr); EXPECT_TRUE(xla_executable != nullptr); } TEST_F(DeviceCompilerTest, Finalize) { XlaCompiler::Options options = GetDefaultXlaOptions(); NameAttrList fn; fn.set_name("foo"); const XlaCompiler::CompilationResult* compilation_result = nullptr; xla::LocalExecutable* xla_executable = nullptr; TF_EXPECT_OK(xla_device_compiler_->CompileIfNeeded( options, fn, SampleArgsForAddXY(), XlaCompiler::CompileOptions{}, DeviceCompileMode::kStrict, profiler_, &compilation_result, &xla_executable)); ASSERT_TRUE(compilation_result != nullptr); // Cast to `ResourceBase` to verify that the `Finalize` implementation // overrides the base class's. static_cast(xla_device_compiler_)->Finalize(); TF_EXPECT_OK(xla_device_compiler_->CompileIfNeeded( options, fn, SampleArgsForAddXY(), XlaCompiler::CompileOptions{}, DeviceCompileMode::kStrict, profiler_, &compilation_result, &xla_executable)); ASSERT_TRUE(compilation_result != nullptr); } } // namespace } // namespace tensorflow