/* Copyright 2021 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/c/experimental/grappler/grappler.h" #include #include #include #include #include #include #include "absl/log/check.h" #include "tensorflow/c/experimental/grappler/grappler_internal.h" #include "tensorflow/c/tf_buffer.h" #include "tensorflow/c/tf_buffer_internal.h" #include "tensorflow/c/tf_status.h" #include "xla/tsl/lib/core/status_test_util.h" #include "xla/tsl/protobuf/error_codes.pb.h" #include "tensorflow/core/framework/function.h" #include "tensorflow/core/framework/node_def.pb.h" #include "tensorflow/core/framework/op.h" #include "tensorflow/core/framework/op_def.pb.h" #include "tensorflow/core/framework/types.pb.h" #include "tensorflow/core/grappler/clusters/single_machine.h" #include "tensorflow/core/grappler/costs/op_performance_data.pb.h" #include "tensorflow/core/grappler/grappler_item.h" #include "tensorflow/core/grappler/inputs/trivial_test_graph_input_yielder.h" #include "tensorflow/core/grappler/optimizers/custom_graph_optimizer_registry.h" #include "tensorflow/core/platform/status.h" #include "tensorflow/core/platform/test.h" #include "tensorflow/core/platform/types.h" #include "tensorflow/core/protobuf/rewriter_config.pb.h" namespace tensorflow { namespace grappler { namespace { void optimize_func(void* optimizer, const TF_Buffer* graph_buf, const TF_GrapplerItem* item, TF_Buffer* optimized_graph_buf, TF_Status* tf_status) {} void PopulateDefaultParam(TP_OptimizerRegistrationParams* params) { params->struct_size = TP_OPTIMIZER_REGISTRATION_PARAMS_STRUCT_SIZE; params->optimizer_configs->struct_size = TP_OPTIMIZER_CONFIGS_STRUCT_SIZE; params->optimizer->struct_size = TP_OPTIMIZER_STRUCT_SIZE; params->optimizer->create_func = nullptr; params->optimizer->optimize_func = optimize_func; params->optimizer->destroy_func = nullptr; } TEST(Grappler, SuccessfulRegistration) { auto plugin_init = [](TP_OptimizerRegistrationParams* const params, TF_Status* const status) -> void { TF_SetStatus(status, TF_OK, ""); PopulateDefaultParam(params); params->device_type = "Success"; params->optimizer_configs->remapping = TF_TriState_Off; }; TF_ASSERT_OK(InitGraphPlugin(plugin_init)); ASSERT_EQ(PluginGraphOptimizerRegistry::CreateOptimizers( std::set{"Success"}) .size(), 1); ConfigList config = PluginGraphOptimizerRegistry::GetPluginConfigs( true, std::set{"Success"}); ASSERT_EQ(config.toggle_config["remapping"], RewriterConfig::OFF); } TEST(Grappler, MultiplePluginRegistration) { auto plugin_init_0 = [](TP_OptimizerRegistrationParams* const params, TF_Status* const status) -> void { TF_SetStatus(status, TF_OK, ""); PopulateDefaultParam(params); params->device_type = "Device0"; }; auto plugin_init_1 = [](TP_OptimizerRegistrationParams* const params, TF_Status* const status) -> void { TF_SetStatus(status, TF_OK, ""); PopulateDefaultParam(params); params->device_type = "Device1"; }; TF_ASSERT_OK(InitGraphPlugin(plugin_init_0)); TF_ASSERT_OK(InitGraphPlugin(plugin_init_1)); ASSERT_EQ(PluginGraphOptimizerRegistry::CreateOptimizers( std::set{"Device0", "Device1"}) .size(), 2); } TEST(Grappler, DeviceTypeNotSet) { auto plugin_init = [](TP_OptimizerRegistrationParams* const params, TF_Status* const status) -> void { TF_SetStatus(status, TF_OK, ""); PopulateDefaultParam(params); params->device_type = nullptr; }; absl::Status status = InitGraphPlugin(plugin_init); ASSERT_EQ(status.code(), tensorflow::error::FAILED_PRECONDITION); ASSERT_EQ( status.message(), "'device_type' field in TP_OptimizerRegistrationParams must be set."); } TEST(Grappler, OptimizeFuncNotSet) { auto plugin_init = [](TP_OptimizerRegistrationParams* const params, TF_Status* const status) -> void { TF_SetStatus(status, TF_OK, ""); PopulateDefaultParam(params); params->device_type = "FuncNotSet"; params->optimizer->optimize_func = nullptr; }; absl::Status status = InitGraphPlugin(plugin_init); ASSERT_EQ(status.code(), tensorflow::error::FAILED_PRECONDITION); ASSERT_EQ(status.message(), "'optimize_func' field in TP_Optimizer must be set."); } TEST(TF_GrapplerItem, NodesToPreserve) { GrapplerItem item; item.fetch = std::vector{"Conv", "BiasAdd"}; std::unordered_set nodes_preserved = item.NodesToPreserve(); TF_GrapplerItem* c_item = reinterpret_cast(&item); int list_total_size = 0; for (const std::string& s : nodes_preserved) { list_total_size += s.size(); } size_t storage_size = 0; int num_values = 0; TF_Status* status = TF_NewStatus(); TF_GetNodesToPreserveListSize(c_item, &num_values, &storage_size, status); EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); EXPECT_EQ(nodes_preserved.size(), num_values); EXPECT_EQ(list_total_size, storage_size); std::unique_ptr values(new char*[nodes_preserved.size()]); std::unique_ptr lens(new size_t[nodes_preserved.size()]); std::unique_ptr storage(new char[storage_size]); TF_GetNodesToPreserveList(c_item, values.get(), lens.get(), nodes_preserved.size(), storage.get(), storage_size, status); EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); for (size_t i = 0; i < nodes_preserved.size(); ++i) { EXPECT_EQ( nodes_preserved.find(std::string(static_cast(values[i]), lens[i])) != nodes_preserved.end(), true); } TF_DeleteStatus(status); } TEST(TF_GrapplerItem, FetchNodes) { GrapplerItem item; item.fetch = std::vector{"Conv", "BiasAdd"}; TF_GrapplerItem* c_item = reinterpret_cast(&item); int list_total_size = 0; for (const std::string& s : item.fetch) { list_total_size += s.size(); } size_t storage_size = 0; int num_values = 0; TF_Status* status = TF_NewStatus(); TF_GetFetchNodesListSize(c_item, &num_values, &storage_size, status); EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); EXPECT_EQ(item.fetch.size(), num_values); EXPECT_EQ(list_total_size, storage_size); std::unique_ptr values(new char*[item.fetch.size()]); std::unique_ptr lens(new size_t[item.fetch.size()]); std::unique_ptr storage(new char[storage_size]); TF_GetFetchNodesList(c_item, values.get(), lens.get(), item.fetch.size(), storage.get(), storage_size, status); EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); for (size_t i = 0; i < item.fetch.size(); ++i) { EXPECT_EQ(item.fetch[i].size(), lens[i]) << i; EXPECT_EQ(item.fetch[i], std::string(static_cast(values[i]), lens[i])) << i; } TF_DeleteStatus(status); } TEST(TF_GraphProperties, InputProperties) { std::unique_ptr cluster(new SingleMachine(5 * 60, 3, 0)); TF_ASSERT_OK(cluster->Provision()); TrivialTestGraphInputYielder fake_input(4, 1, 10, false, cluster->GetDeviceNames()); GrapplerItem item; CHECK(fake_input.NextItem(&item)); TF_Status* status = TF_NewStatus(); TF_GraphProperties* graph_properties = TF_NewGraphProperties(reinterpret_cast(&item)); TF_InferStatically(graph_properties, true, false, false, false, status); EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); for (const NodeDef& node : item.graph.node()) { if (node.op() == "AddN") { int num_values = 0; TF_GetInputPropertiesListSize(graph_properties, node.name().c_str(), &num_values, status); EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); EXPECT_EQ(num_values, 1); std::vector in_props_buf(num_values, TF_NewBuffer()); TF_GetInputPropertiesList(graph_properties, node.name().c_str(), in_props_buf.data(), num_values, status); EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); tensorflow::OpInfo::TensorProperties in_props; absl::Status s = tensorflow::BufferToMessage(in_props_buf[0], &in_props); TF_ASSERT_OK(s); EXPECT_EQ(DT_FLOAT, in_props.dtype()); EXPECT_FALSE(in_props.shape().unknown_rank()); EXPECT_EQ(2, in_props.shape().dim_size()); EXPECT_EQ(10, in_props.shape().dim(0).size()); EXPECT_EQ(1, in_props.shape().dim(1).size()); for (int i = 0; i < in_props_buf.size(); i++) TF_DeleteBuffer(in_props_buf[i]); } } TF_DeleteGraphProperties(graph_properties); TF_DeleteStatus(status); TF_ASSERT_OK(cluster->Shutdown()); } TEST(TF_GraphProperties, OutputProperties) { std::unique_ptr cluster(new SingleMachine(5 * 60, 3, 0)); TF_ASSERT_OK(cluster->Provision()); TrivialTestGraphInputYielder fake_input(4, 1, 10, false, cluster->GetDeviceNames()); GrapplerItem item; CHECK(fake_input.NextItem(&item)); TF_Status* status = TF_NewStatus(); TF_GraphProperties* graph_properties = TF_NewGraphProperties(reinterpret_cast(&item)); TF_InferStatically(graph_properties, true, false, false, false, status); EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); for (const NodeDef& node : item.graph.node()) { if (node.op() == "AddN") { int num_values = 0; TF_GetOutputPropertiesListSize(graph_properties, node.name().c_str(), &num_values, status); EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); EXPECT_EQ(num_values, 1); std::vector out_props_buf(num_values, TF_NewBuffer()); TF_GetOutputPropertiesList(graph_properties, node.name().c_str(), out_props_buf.data(), num_values, status); EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); tensorflow::OpInfo::TensorProperties out_props; absl::Status s = tensorflow::BufferToMessage(out_props_buf[0], &out_props); TF_ASSERT_OK(s); EXPECT_EQ(DT_FLOAT, out_props.dtype()); EXPECT_FALSE(out_props.shape().unknown_rank()); EXPECT_EQ(2, out_props.shape().dim_size()); EXPECT_EQ(10, out_props.shape().dim(0).size()); EXPECT_EQ(1, out_props.shape().dim(1).size()); for (int i = 0; i < out_props_buf.size(); i++) TF_DeleteBuffer(out_props_buf[i]); } } TF_DeleteStatus(status); TF_DeleteGraphProperties(graph_properties); TF_ASSERT_OK(cluster->Shutdown()); } TEST(TF_FunctionLibraryDefinition, LookUpOpDef) { TF_Buffer* g_buf = TF_NewBuffer(); TF_Buffer* op_buf = TF_NewBuffer(); TF_Status* status = TF_NewStatus(); GraphDef g_def; absl::Status s = MessageToBuffer(g_def, g_buf); TF_ASSERT_OK(s); TF_FunctionLibraryDefinition* func = TF_NewFunctionLibraryDefinition(g_buf, status); TF_LookUpOpDef(func, "Add", op_buf, status); std::string actual_string(reinterpret_cast(op_buf->data), op_buf->length); ASSERT_EQ(TF_OK, TF_GetCode(status)); const OpDef* expected_op_def; TF_ASSERT_OK(OpRegistry::Global()->LookUpOpDef("Add", &expected_op_def)); std::string expected_serialized; expected_op_def->SerializeToString(&expected_serialized); EXPECT_EQ(expected_serialized, actual_string); TF_DeleteBuffer(g_buf); TF_DeleteBuffer(op_buf); TF_DeleteStatus(status); TF_DeleteFunctionLibraryDefinition(func); } } // namespace } // namespace grappler } // namespace tensorflow