diff --git a/cmake/features.cmake b/cmake/features.cmake index 00cf42a28f8..12e14863d8a 100644 --- a/cmake/features.cmake +++ b/cmake/features.cmake @@ -123,10 +123,11 @@ endif() ie_dependent_option(NGRAPH_ONNX_FRONTEND_ENABLE "Enable ONNX FrontEnd" ON "protoc_available" OFF) ie_dependent_option(NGRAPH_PDPD_FRONTEND_ENABLE "Enable PaddlePaddle FrontEnd" ON "protoc_available" OFF) ie_option(NGRAPH_IR_FRONTEND_ENABLE "Enable IR FrontEnd" ON) +ie_dependent_option(NGRAPH_TF_FRONTEND_ENABLE "Enable TensorFlow FrontEnd" ON "protoc_available" OFF) ie_dependent_option(NGRAPH_USE_PROTOBUF_LITE "Compiles and links with protobuf-lite" ON "NGRAPH_ONNX_FRONTEND_ENABLE" OFF) ie_dependent_option(NGRAPH_USE_SYSTEM_PROTOBUF "Use system protobuf" OFF - "NGRAPH_ONNX_FRONTEND_ENABLE OR NGRAPH_PDPD_FRONTEND_ENABLE" OFF) + "NGRAPH_ONNX_FRONTEND_ENABLE OR NGRAPH_PDPD_FRONTEND_ENABLE OR NGRAPH_TF_FRONTEND_ENABLE" OFF) ie_dependent_option(NGRAPH_UNIT_TEST_ENABLE "Enables ngraph unit tests" ON "ENABLE_TESTS;NOT ANDROID" OFF) ie_dependent_option(NGRAPH_UNIT_TEST_BACKENDS_ENABLE "Control the building of unit tests using backends" ON "NGRAPH_UNIT_TEST_ENABLE" OFF) diff --git a/cmake/templates/OpenVINOConfig.cmake.in b/cmake/templates/OpenVINOConfig.cmake.in index eb903e14889..0bfecbd5e64 100644 --- a/cmake/templates/OpenVINOConfig.cmake.in +++ b/cmake/templates/OpenVINOConfig.cmake.in @@ -169,9 +169,11 @@ set(${CMAKE_FIND_PACKAGE_NAME}_Runtime_FOUND ON) set(${CMAKE_FIND_PACKAGE_NAME}_ONNX_FOUND @NGRAPH_ONNX_FRONTEND_ENABLE@) set(${CMAKE_FIND_PACKAGE_NAME}_PaddlePaddle_FOUND @NGRAPH_PDPD_FRONTEND_ENABLE@) +set(${CMAKE_FIND_PACKAGE_NAME}_TensorFlow_FOUND @NGRAPH_TF_FRONTEND_ENABLE@) set(${CMAKE_FIND_PACKAGE_NAME}_Frontend_ONNX_FOUND ${${CMAKE_FIND_PACKAGE_NAME}_ONNX_FOUND}) set(${CMAKE_FIND_PACKAGE_NAME}_Frontend_PaddlePaddle_FOUND ${${CMAKE_FIND_PACKAGE_NAME}_PaddlePaddle_FOUND}) +set(${CMAKE_FIND_PACKAGE_NAME}_Frontend_TensorFlow_FOUND ${${CMAKE_FIND_PACKAGE_NAME}_TensorFlow_FOUND}) set(${CMAKE_FIND_PACKAGE_NAME}_Frontend_IR_FOUND ${${CMAKE_FIND_PACKAGE_NAME}_IR_FOUND}) # if no components specified, only Runtime is provided @@ -185,7 +187,7 @@ endif() foreach(target openvino::runtime openvino::runtime::c openvino::core openvino::frontend::manager openvino::frontend::onnx - openvino::frontend::paddlepaddle) + openvino::frontend::paddlepaddle openvino::frontend::tensorflow) if(TARGET ${target} AND _ov_as_external_package) _ov_target_no_deprecation_error(${target}) endif() @@ -205,3 +207,4 @@ endif() unset(${CMAKE_FIND_PACKAGE_NAME}_PaddlePaddle_FOUND) unset(${CMAKE_FIND_PACKAGE_NAME}_ONNX_FOUND) +unset(${CMAKE_FIND_PACKAGE_NAME}_TensorFlow_FOUND) diff --git a/cmake/templates/ngraphConfig.cmake.in b/cmake/templates/ngraphConfig.cmake.in index c5b467f2dd3..b42f2e8abec 100644 --- a/cmake/templates/ngraphConfig.cmake.in +++ b/cmake/templates/ngraphConfig.cmake.in @@ -70,6 +70,12 @@ if(TARGET openvino::frontend::paddlepaddle AND NOT TARGET ngraph::paddlepaddle_n INTERFACE_LINK_LIBRARIES openvino::frontend::paddlepaddle) endif() +if(TARGET openvino::frontend::tensorflow AND NOT TARGET ngraph::tensorflow_ngraph_frontend) + add_library(ngraph::tensorflow_ngraph_frontend INTERFACE IMPORTED) + set_target_properties(ngraph::tensorflow_ngraph_frontend PROPERTIES + INTERFACE_LINK_LIBRARIES openvino::frontend::tensorflow) +endif() + set(ngraph_ngraph_FOUND ON) set(NGRAPH_LIBRARIES ngraph::ngraph) @@ -88,6 +94,7 @@ if(ngraph_onnx_importer_FOUND) endif() set(ngraph_paddlepaddle_frontend_FOUND ${OpenVINO_Frontend_PaddlePaddle_FOUND}) +set(ngraph_tensorflow_frontend_FOUND ${OpenVINO_Frontend_TensorFlow_FOUND}) set(ngraph_ir_frontend_FOUND ${OpenVINO_Frontend_IR_FOUND}) check_required_components(ngraph) diff --git a/model-optimizer/mo/main.py b/model-optimizer/mo/main.py index 88e5886261d..60117df3baf 100644 --- a/model-optimizer/mo/main.py +++ b/model-optimizer/mo/main.py @@ -121,6 +121,7 @@ def get_moc_frontends(argv: argparse.Namespace): # Set which frontend to use by default, values should be 'new' or 'legacy' frontend_defaults = { 'onnx': 'legacy', + 'tf': 'legacy' } # Disable MOC frontend if default is set to legacy and no user override if frontend_defaults.get(moc_front_end.get_name()) == 'legacy' and not use_new_frontend: diff --git a/ngraph/frontend/CMakeLists.txt b/ngraph/frontend/CMakeLists.txt index 5fc5c2b1fb2..6b8bddcb310 100644 --- a/ngraph/frontend/CMakeLists.txt +++ b/ngraph/frontend/CMakeLists.txt @@ -17,3 +17,7 @@ endif() if (NGRAPH_IR_FRONTEND_ENABLE) add_subdirectory(ir) endif() + +if (NGRAPH_TF_FRONTEND_ENABLE) + add_subdirectory(tensorflow) +endif() diff --git a/ngraph/frontend/tensorflow/CMakeLists.txt b/ngraph/frontend/tensorflow/CMakeLists.txt new file mode 100644 index 00000000000..dd17033a5cc --- /dev/null +++ b/ngraph/frontend/tensorflow/CMakeLists.txt @@ -0,0 +1,99 @@ +# Copyright (C) 2018-2021 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +# + +set(TARGET_NAME "tensorflow_ngraph_frontend") + +file(GLOB_RECURSE LIBRARY_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) +file(GLOB_RECURSE LIBRARY_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/*.hpp) +file(GLOB_RECURSE LIBRARY_PUBLIC_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp) + +set(${TARGET_NAME}_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) + +# Create named folders for the sources within the .vcproj +# Empty name lists them directly under the .vcproj + +source_group("src" FILES ${LIBRARY_SRC}) +source_group("include" FILES ${LIBRARY_HEADERS}) +source_group("public include" FILES ${LIBRARY_PUBLIC_HEADERS}) + +set(PROTO_SRCS) +set(PROTO_HDRS) + +# Generate protobuf file on build time for each '.proto' file in src/proto +file(GLOB proto_files ${CMAKE_CURRENT_SOURCE_DIR}/src/proto/*.proto) + +foreach(INFILE ${proto_files}) + get_filename_component(FILE_DIR ${INFILE} DIRECTORY) + get_filename_component(FILE_WE ${INFILE} NAME_WE) + set(OUTPUT_PB_SRC ${CMAKE_CURRENT_BINARY_DIR}/${FILE_WE}.pb.cc) + set(OUTPUT_PB_HEADER ${CMAKE_CURRENT_BINARY_DIR}/${FILE_WE}.pb.h) + set(GENERATED_PROTO ${INFILE}) + add_custom_command( + OUTPUT "${OUTPUT_PB_SRC}" "${OUTPUT_PB_HEADER}" + COMMAND ${PROTOC_EXECUTABLE} ARGS --cpp_out ${CMAKE_CURRENT_BINARY_DIR} -I ${FILE_DIR} ${FILE_WE}.proto + DEPENDS ${PROTOC_EXECUTABLE} ${GENERATED_PROTO} + COMMENT "Running C++ protocol buffer compiler (${PROTOC_EXECUTABLE}) on ${GENERATED_PROTO}" + VERBATIM + COMMAND_EXPAND_LISTS) + list(APPEND PROTO_SRCS "${OUTPUT_PB_SRC}") + list(APPEND PROTO_HDRS "${OUTPUT_PB_HEADER}") +endforeach() + +add_custom_target(${TARGET_NAME}_proto DEPENDS ${PROTO_SRCS} ${PROTO_HDRS}) + +set_source_files_properties(${PROTO_SRCS} ${PROTO_HDRS} PROPERTIES GENERATED TRUE) + +# Disable all warnings for generated code +set_source_files_properties(${PROTO_SRCS} ${PROTO_HDRS} PROPERTIES COMPILE_OPTIONS -w) + +# Create shared library +add_library(${TARGET_NAME} SHARED ${LIBRARY_SRC} ${LIBRARY_HEADERS} ${LIBRARY_PUBLIC_HEADERS} ${PROTO_SRCS} ${PROTO_HDRS}) +add_library(openvino::frontend::tensorflow ALIAS ${TARGET_NAME}) + +add_dependencies(${TARGET_NAME} tensorflow_ngraph_frontend_proto) + +ov_ncc_naming_style(FOR_TARGET ${TARGET_NAME} + INCLUDE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include" + ADDITIONAL_INCLUDE_DIRECTORIES + $) + +target_include_directories(${TARGET_NAME} + PUBLIC + $ + $ + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_BINARY_DIR}) + +target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${Protobuf_INCLUDE_DIRS} + ${CMAKE_CURRENT_BINARY_DIR}) + +if(COMMAND ie_add_vs_version_file) + ie_add_vs_version_file(NAME ${TARGET_NAME} + FILEDESCRIPTION "FrontEnd to load and convert TensorFlow file format") +endif() + +link_system_libraries(${TARGET_NAME} PRIVATE ${Protobuf_LITE_LIBRARIES}) + +target_link_libraries(${TARGET_NAME} PRIVATE frontend_manager::static + PRIVATE ngraph::builder inference_engine_transformations libprotobuf openvino::util) + +add_clang_format_target(${TARGET_NAME}_clang FOR_TARGETS ${TARGET_NAME} + EXCLUDE_PATTERNS ${PROTO_SRCS} ${PROTO_HDRS}) + +set_target_properties(${TARGET_NAME} PROPERTIES EXPORT_NAME frontend::tensorflow) + +# TODO: add install commands once TensorFlow frontend is complete +#install(TARGETS ${TARGET_NAME} EXPORT OpenVINOTargets +# RUNTIME DESTINATION ${IE_CPACK_RUNTIME_PATH} COMPONENT ngraph +# ARCHIVE DESTINATION ${IE_CPACK_ARCHIVE_PATH} COMPONENT ngraph +# LIBRARY DESTINATION ${IE_CPACK_LIBRARY_PATH} COMPONENT ngraph) + +#install(DIRECTORY ${${TARGET_NAME}_INCLUDE_DIR}/tensorflow_frontend +# DESTINATION ${FRONTEND_INSTALL_INCLUDE} +# COMPONENT ngraph_dev +# FILES_MATCHING PATTERN "*.hpp") +# +#export(TARGETS ${TARGET_NAME} NAMESPACE openvino:: +# APPEND FILE "${CMAKE_BINARY_DIR}/OpenVINOTargets.cmake") diff --git a/ngraph/frontend/tensorflow/include/tensorflow_frontend/decoder.hpp b/ngraph/frontend/tensorflow/include/tensorflow_frontend/decoder.hpp new file mode 100644 index 00000000000..46bc8963d8f --- /dev/null +++ b/ngraph/frontend/tensorflow/include/tensorflow_frontend/decoder.hpp @@ -0,0 +1,45 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include +#include + +namespace ov { +namespace frontend { + +class TF_API DecoderBase { +public: + /// \brief Get attribute value by name and requested type + /// + /// \param name Attribute name + /// \param type_info Attribute type information + /// \return Shared pointer to appropriate value if it exists, 'nullptr' otherwise + virtual std::shared_ptr get_attribute(const std::string& name, + const VariantTypeInfo& type_info) const = 0; + + /// \brief Get a number of inputs + virtual size_t get_input_size() const = 0; + + /// \brief Get a producer name and its output port index + /// + /// \param input_port_idx Input port index by which data is consumed + /// \param producer_name A producer name + /// \return producer_output_port_index Output port index from which data is generated + virtual void get_input_node(size_t input_port_idx, + std::string& producer_name, + size_t& producer_output_port_index) const = 0; + + /// \brief Get operation type + virtual const std::string& get_op_type() const = 0; + + /// \brief Get node name + virtual const std::string& get_op_name() const = 0; + + /// \brief Destructor + virtual ~DecoderBase() = default; +}; +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/include/tensorflow_frontend/exceptions.hpp b/ngraph/frontend/tensorflow/include/tensorflow_frontend/exceptions.hpp new file mode 100644 index 00000000000..d8c9744bff5 --- /dev/null +++ b/ngraph/frontend/tensorflow/include/tensorflow_frontend/exceptions.hpp @@ -0,0 +1,35 @@ +// Copyright (C) 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// +#pragma once + +#include +#include + +namespace ov { +namespace frontend { +namespace tf { + +class NodeContext; + +class OpValidationFailureTF : public ngraph::frontend::OpValidationFailure { +public: + OpValidationFailureTF(const CheckLocInfo& check_loc_info, const NodeContext& node, const std::string& explanation) + : OpValidationFailure(check_loc_info, get_error_msg_prefix_tf(node), explanation) {} + +private: + static std::string get_error_msg_prefix_tf(const NodeContext& node); +}; +} // namespace tf +} // namespace frontend + +/// \brief Macro to check whether a boolean condition holds. +/// \param node_context Object of NodeContext class +/// \param cond Condition to check +/// \param ... Additional error message info to be added to the error message via the `<<` +/// stream-insertion operator. Note that the expressions here will be evaluated lazily, +/// i.e., only if the `cond` evalutes to `false`. +/// \throws ::ov::OpValidationFailureTF if `cond` is false. +#define TF_OP_VALIDATION_CHECK(node_context, ...) \ + NGRAPH_CHECK_HELPER(::ov::frontend::tf::OpValidationFailureTF, (node_context), __VA_ARGS__) +} // namespace ov diff --git a/ngraph/frontend/tensorflow/include/tensorflow_frontend/frontend.hpp b/ngraph/frontend/tensorflow/include/tensorflow_frontend/frontend.hpp new file mode 100644 index 00000000000..301ec254346 --- /dev/null +++ b/ngraph/frontend/tensorflow/include/tensorflow_frontend/frontend.hpp @@ -0,0 +1,84 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ov { +namespace frontend { +namespace tf { +class NodeContext; +} +} // namespace frontend +} // namespace ov + +namespace ov { +namespace frontend { +class TF_API FrontEndTF : public ngraph::frontend::FrontEnd { +public: + using CreatorFunction = std::function<::ov::OutputVector(const ::ov::frontend::tf::NodeContext&)>; + using TranslatorDictionaryType = std::map; + +private: + TranslatorDictionaryType m_op_translators; + +public: + FrontEndTF(); + + /// \brief Completely convert the model + /// \return fully converted nGraph function + std::shared_ptr convert(ngraph::frontend::InputModel::Ptr model) const override; + + /// \brief Completely convert the remaining, not converted part of a function. + /// \param partiallyConverted partially converted nGraph function + void convert(std::shared_ptr partiallyConverted) const override; + + /// \brief Convert only those parts of the model that can be converted leaving others + /// as-is. Converted parts are not normalized by additional transformations; normalize + /// function or another form of convert function should be called to finalize the + /// conversion process. + /// \param model Input model + /// \return partially converted nGraph function + std::shared_ptr convert_partially(ngraph::frontend::InputModel::Ptr model) const override; + + /// \brief Convert operations with one-to-one mapping with decoding nodes. + /// Each decoding node is an nGraph node representing a single FW operation node with + /// all attributes represented in FW-independent way. + /// \param model Input model + /// \return nGraph function after decoding + std::shared_ptr decode(ngraph::frontend::InputModel::Ptr model) const override; + + /// \brief Runs normalization passes on function that was loaded with partial conversion + /// \param function partially converted nGraph function + void normalize(std::shared_ptr function) const override; + + /// \brief Gets name of this FrontEnd. Can be used by clients + std::string get_name() const override { + return "tf"; + } + +protected: + /// \brief Check if FrontEndTensorflow can recognize model from given parts + bool supported_impl(const std::vector>& variants) const override; + + ngraph::frontend::InputModel::Ptr load_impl( + const std::vector>& variants) const override; + +private: + void translate_graph(const std::shared_ptr& model, + const std::string& model_name, + bool fail_fast, + bool no_conversion, + std::shared_ptr& ng_function) const; +}; +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/include/tensorflow_frontend/graph_iterator.hpp b/ngraph/frontend/tensorflow/include/tensorflow_frontend/graph_iterator.hpp new file mode 100644 index 00000000000..43f26f04d77 --- /dev/null +++ b/ngraph/frontend/tensorflow/include/tensorflow_frontend/graph_iterator.hpp @@ -0,0 +1,36 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include +#include + +namespace ov { +namespace frontend { +/// Abstract representation for an input model graph that gives nodes in topologically sorted order +class TF_API GraphIterator { +public: + using Ptr = std::shared_ptr; + + /// \brief Get a number of operation nodes in the graph + virtual size_t size() const = 0; + + /// \brief Set iterator to the start position + virtual void reset() = 0; + + /// \brief Move to the next node in the graph + virtual void next() = 0; + + /// \brief Returns true if iterator goes out of the range of available nodes + virtual bool is_end() const = 0; + + /// \brief Return a pointer to a decoder of the current node + virtual std::shared_ptr get_decoder() const = 0; + + /// \brief Destructor + virtual ~GraphIterator() = default; +}; +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/include/tensorflow_frontend/model.hpp b/ngraph/frontend/tensorflow/include/tensorflow_frontend/model.hpp new file mode 100644 index 00000000000..e1dfad29a6f --- /dev/null +++ b/ngraph/frontend/tensorflow/include/tensorflow_frontend/model.hpp @@ -0,0 +1,43 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include +#include +#include +#include + +namespace ov { +namespace frontend { + +class OpPlaceTF; +class TensorPlaceTF; + +class TF_API InputModelTF : public ngraph::frontend::InputModel { + friend class FrontEndTF; + class InputModelTFImpl; + std::shared_ptr _impl; + + std::vector> get_op_places() const; + std::map> get_tensor_places() const; + std::map> get_tensor_values() const; + +public: + explicit InputModelTF(const GraphIterator::Ptr& graph_iterator); + + std::vector get_inputs() const override; + std::vector get_outputs() const override; + ngraph::frontend::Place::Ptr get_place_by_tensor_name(const std::string& tensorName) const override; + void override_all_outputs(const std::vector& outputs) override; + void override_all_inputs(const std::vector& inputs) override; + void extract_subgraph(const std::vector& inputs, + const std::vector& outputs) override; + void set_partial_shape(ngraph::frontend::Place::Ptr place, const ov::PartialShape&) override; + ov::PartialShape get_partial_shape(ngraph::frontend::Place::Ptr place) const override; + void set_element_type(ngraph::frontend::Place::Ptr place, const ov::element::Type&) override; + void set_tensor_value(ngraph::frontend::Place::Ptr place, const void* value) override; +}; +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/include/tensorflow_frontend/place.hpp b/ngraph/frontend/tensorflow/include/tensorflow_frontend/place.hpp new file mode 100644 index 00000000000..7547c380916 --- /dev/null +++ b/ngraph/frontend/tensorflow/include/tensorflow_frontend/place.hpp @@ -0,0 +1,176 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include +#include + +namespace ov { +namespace frontend { + +class TensorPlaceTF; +class OpPlaceTF; + +class PlaceTF : public ngraph::frontend::Place { +public: + PlaceTF(const ngraph::frontend::InputModel& input_model, const std::vector& names) + : m_input_model(input_model), + m_names(names) {} + + explicit PlaceTF(const ngraph::frontend::InputModel& input_model) + : PlaceTF(input_model, std::vector{}) {} + + ~PlaceTF() override = default; + + bool is_input() const override; + bool is_output() const override; + bool is_equal(Ptr another) const override { + return this == another.get(); + } + + std::vector get_names() const override { + return m_names; + } + +private: + const ngraph::frontend::InputModel& m_input_model; + std::vector m_names; +}; + +class InPortPlaceTF : public PlaceTF { +public: + explicit InPortPlaceTF(const ngraph::frontend::InputModel& input_model) : PlaceTF(input_model) {} + + void set_op(const std::weak_ptr& op) { + m_op = op; + } + void set_source_tensor(const std::weak_ptr& source_tensor); + + // Internal usage + std::shared_ptr get_source_tensor_tf() const; + std::shared_ptr get_op(); + + // External usage + std::vector get_consuming_operations() const override; + Ptr get_producing_operation() const override; + ngraph::frontend::Place::Ptr get_source_tensor() const override; + Ptr get_producing_port() const override; + + bool is_equal_data(Ptr another) const override; + +private: + std::weak_ptr m_source_tensor; + std::weak_ptr m_op; +}; + +class OutPortPlaceTF : public PlaceTF { +public: + explicit OutPortPlaceTF(const ngraph::frontend::InputModel& input_model) : PlaceTF(input_model) {} + + void set_op(const std::weak_ptr& op) { + m_op = op; + } + void set_target_tensor(const std::weak_ptr& target_tensor); + + std::shared_ptr get_target_tensor_tf() const; + + // External usage + std::vector get_consuming_operations() const override; + ngraph::frontend::Place::Ptr get_producing_operation() const override; + std::vector get_consuming_ports() const override; + Ptr get_target_tensor() const override; + bool is_equal_data(Ptr another) const override; + +private: + std::weak_ptr m_op; + std::weak_ptr m_target_tensor; +}; + +class OpPlaceTF : public PlaceTF { +public: + OpPlaceTF(const ngraph::frontend::InputModel& input_model, std::shared_ptr op_decoder); + + void add_in_port(const std::shared_ptr& input, const std::string& name); + void add_out_port(const std::shared_ptr& output, int idx); + + // Internal usage + const std::vector>& get_output_ports() const; + const std::map>>& get_input_ports() const; + std::shared_ptr get_input_port_tf(const std::string& inputName, int inputPortIndex) const; + std::shared_ptr get_decoder() const; + + // External API methods + std::vector get_consuming_ports() const override; + + Ptr get_output_port() const override; + Ptr get_output_port(int outputPortIndex) const override; + + Ptr get_input_port() const override; + Ptr get_input_port(int inputPortIndex) const override; + Ptr get_input_port(const std::string& inputName) const override; + Ptr get_input_port(const std::string& inputName, int inputPortIndex) const override; + + std::vector get_consuming_operations() const override; + std::vector get_consuming_operations(int outputPortIndex) const override; + + Ptr get_producing_operation() const override; + Ptr get_producing_operation(int inputPortIndex) const override; + Ptr get_producing_operation(const std::string& inputName) const override; + Ptr get_producing_operation(const std::string& inputName, int inputPortIndex) const override; + + Ptr get_source_tensor() const override; + Ptr get_source_tensor(int inputPortIndex) const override; + Ptr get_source_tensor(const std::string& inputName) const override; + Ptr get_source_tensor(const std::string& inputName, int inputPortIndex) const override; + + Ptr get_target_tensor() const override; + Ptr get_target_tensor(int outputPortIndex) const override; + +private: + std::shared_ptr m_op_decoder; + std::map>> m_input_ports; + std::vector> m_output_ports; +}; + +class TensorPlaceTF : public PlaceTF { +public: + TensorPlaceTF(const ngraph::frontend::InputModel& input_model, + const ov::PartialShape& pshape, + ov::element::Type type, + const std::vector& names); + + void add_producing_port(const std::shared_ptr& out_port); + void add_consuming_port(const std::shared_ptr& in_port); + + // Internal usage + const PartialShape& get_partial_shape() const { + return m_pshape; + } + const element::Type& get_element_type() const { + return m_type; + } + void set_partial_shape(const PartialShape& pshape) { + m_pshape = pshape; + } + void set_element_type(const element::Type& type) { + m_type = type; + } + + // External usage + Ptr get_producing_operation() const override; + std::vector get_consuming_operations() const override; + std::vector get_consuming_ports() const override; + Ptr get_producing_port() const override; + bool is_equal_data(Ptr another) const override; + +private: + PartialShape m_pshape; + element::Type m_type; + + std::vector> m_producing_ports; + std::vector> m_consuming_ports; +}; +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/include/tensorflow_frontend/utility.hpp b/ngraph/frontend/tensorflow/include/tensorflow_frontend/utility.hpp new file mode 100644 index 00000000000..a617d2a3393 --- /dev/null +++ b/ngraph/frontend/tensorflow/include/tensorflow_frontend/utility.hpp @@ -0,0 +1,15 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include + +#ifdef tensorflow_ngraph_frontend_EXPORTS +# define TF_API NGRAPH_HELPER_DLL_EXPORT +#else +# define TF_API NGRAPH_HELPER_DLL_IMPORT +#endif // tensorflow_ngraph_frontend_EXPORTS + +#define NGRAPH_VLOG(I) std::ostringstream() diff --git a/ngraph/frontend/tensorflow/src/decoder_proto.cpp b/ngraph/frontend/tensorflow/src/decoder_proto.cpp new file mode 100644 index 00000000000..9bfb8386fa5 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/decoder_proto.cpp @@ -0,0 +1,127 @@ +// Copyright (C) 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "decoder_proto.hpp" + +#include "node_context.hpp" + +namespace ov { +namespace frontend { +namespace tf { + +const std::map<::tensorflow::DataType, ov::element::Type>& TYPE_MAP() { + static const std::map<::tensorflow::DataType, ov::element::Type> type_map{ + {::tensorflow::DataType::DT_BOOL, ov::element::boolean}, + {::tensorflow::DataType::DT_INT16, ov::element::i16}, + {::tensorflow::DataType::DT_INT32, ov::element::i32}, + {::tensorflow::DataType::DT_INT64, ov::element::i64}, + {::tensorflow::DataType::DT_HALF, ov::element::f16}, + {::tensorflow::DataType::DT_FLOAT, ov::element::f32}, + {::tensorflow::DataType::DT_DOUBLE, ov::element::f64}, + {::tensorflow::DataType::DT_UINT8, ov::element::u8}, + {::tensorflow::DataType::DT_INT8, ov::element::i8}, + {::tensorflow::DataType::DT_BFLOAT16, ov::element::bf16}}; + return type_map; +} + +std::shared_ptr DecoderTFProto::get_attribute(const std::string& name, + const VariantTypeInfo& type_info) const { + auto attrs = decode_attribute_helper(name); + if (attrs.empty()) { + return nullptr; + } + + if (type_info == VariantWrapper::get_type_info_static()) { + return std::make_shared>(attrs[0].s()); + } else if (type_info == VariantWrapper::get_type_info_static()) { + return std::make_shared>(attrs[0].i()); + } else if (type_info == VariantWrapper>::get_type_info_static()) { + std::vector longs; + longs.reserve(attrs[0].list().i_size()); + for (size_t idx = 0; idx < attrs[0].list().i_size(); ++idx) { + longs.push_back(attrs[0].list().i(idx)); + } + return std::make_shared>>(longs); + } else if (type_info == VariantWrapper::get_type_info_static()) { + return std::make_shared>(static_cast(attrs[0].i())); + } else if (type_info == VariantWrapper>::get_type_info_static()) { + std::vector ints; + ints.reserve(attrs[0].list().i_size()); + for (size_t idx = 0; idx < attrs[0].list().i_size(); ++idx) { + ints.push_back(static_cast(attrs[0].list().i(idx))); + } + return std::make_shared>>(ints); + } else if (type_info == VariantWrapper::get_type_info_static()) { + return std::make_shared>(attrs[0].f()); + } else if (type_info == VariantWrapper>::get_type_info_static()) { + std::vector floats; + floats.reserve(attrs[0].list().i_size()); + for (size_t idx = 0; idx < attrs[0].list().i_size(); ++idx) { + floats.push_back(attrs[0].list().f(idx)); + } + return std::make_shared>>(floats); + } else if (type_info == VariantWrapper::get_type_info_static()) { + auto data_type = attrs[0].type(); + return std::make_shared>(TYPE_MAP().at(data_type)); + } else if (type_info == VariantWrapper::get_type_info_static()) { + return std::make_shared>(attrs[0].b()); + } else if (type_info == VariantWrapper<::tensorflow::DataType>::get_type_info_static()) { + return std::make_shared>(attrs[0].type()); + } else if (type_info == VariantWrapper<::tensorflow::TensorProto>::get_type_info_static()) { + return std::make_shared>(attrs[0].tensor()); + } else if (type_info == VariantWrapper<::ov::PartialShape>::get_type_info_static()) { + std::vector dims; + auto tf_shape = attrs[0].shape(); + for (int i = 0; i < tf_shape.dim_size(); i++) { + dims.push_back(tf_shape.dim(i).size()); + } + auto pshape = ov::PartialShape(dims); + return std::make_shared>(pshape); + } + + // type is not supported by decoder + return nullptr; +} + +size_t DecoderTFProto::get_input_size() const { + return m_node_def->input_size(); +} + +void DecoderTFProto::get_input_node(size_t input_port_idx, + std::string& producer_name, + size_t& producer_output_port_index) const { + // TODO: handle body graph nodes with a couple of columns + std::string producer_port_name = m_node_def->input(input_port_idx); + auto delim_pos = producer_port_name.find(':'); + if (delim_pos != std::string::npos) { + producer_name = producer_port_name.substr(0, delim_pos); + producer_output_port_index = std::stoi(producer_port_name.substr(delim_pos)); + return; + } + producer_name = producer_port_name; + producer_output_port_index = 0; +} + +const std::string& DecoderTFProto::get_op_type() const { + return m_node_def->op(); +} + +const std::string& DecoderTFProto::get_op_name() const { + return m_node_def->name(); +} + +std::vector<::tensorflow::AttrValue> DecoderTFProto::decode_attribute_helper(const std::string& name) const { + auto attr_map = m_node_def->attr(); + FRONT_END_GENERAL_CHECK(attr_map.contains(name), + "An error occurred while parsing the ", + name, + " attribute of ", + this->get_op_type(), + "node"); + auto value = m_node_def->attr().at(name); + return {value}; +} +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/decoder_proto.hpp b/ngraph/frontend/tensorflow/src/decoder_proto.hpp new file mode 100644 index 00000000000..e1d620c4efb --- /dev/null +++ b/ngraph/frontend/tensorflow/src/decoder_proto.hpp @@ -0,0 +1,45 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "attr_value.pb.h" +#include "node_def.pb.h" +#include "types.pb.h" + +namespace ov { +namespace frontend { +namespace tf { + +class DecoderTFProto : public DecoderBase { +public: + explicit DecoderTFProto(const ::tensorflow::NodeDef* node_def) : m_node_def(node_def) {} + + std::shared_ptr get_attribute(const std::string& name, + const VariantTypeInfo& type_info) const override; + + size_t get_input_size() const override; + + void get_input_node(size_t input_port_idx, + std::string& producer_name, + size_t& producer_output_port_index) const override; + + const std::string& get_op_type() const override; + + const std::string& get_op_name() const override; + +private: + std::vector<::tensorflow::AttrValue> decode_attribute_helper(const std::string& name) const; + const ::tensorflow::NodeDef* m_node_def; +}; +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/exceptions.cpp b/ngraph/frontend/tensorflow/src/exceptions.cpp new file mode 100644 index 00000000000..f74796a3974 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/exceptions.cpp @@ -0,0 +1,19 @@ +// Copyright (C) 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include + +#include "node_context.hpp" + +namespace ov { +namespace frontend { +namespace tf { +std::string OpValidationFailureTF::get_error_msg_prefix_tf(const tf::NodeContext& node) { + std::stringstream ss; + ss << "While validating node '" << node.get_op_type() << '\''; + return ss.str(); +} +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/frontend.cpp b/ngraph/frontend/tensorflow/src/frontend.cpp new file mode 100644 index 00000000000..cf61f4916c2 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/frontend.cpp @@ -0,0 +1,350 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include +#include + +#include "op_table.hpp" +#include "tf_framework_node.hpp" +#include "utils.hpp" + +using namespace ::ov::frontend; +using namespace ::ov::frontend::tf; + +namespace { +void translate_framework_node(const std::shared_ptr& node, + const FrontEndTF::TranslatorDictionaryType& op_translators) { + auto type = node->get_op_type(); + + const auto& TRANSLATE_OP_MAP = op_translators; + auto translator_it = TRANSLATE_OP_MAP.find(type); + FRONT_END_OP_CONVERSION_CHECK(translator_it != TRANSLATE_OP_MAP.end(), "No translator found for ", type, " node."); + + ov::OutputVector ng_inputs; + NamedInputs named_inputs; + size_t input_port_idx = 0; + for (const auto& input : node->input_values()) { + ng_inputs.push_back(input); + named_inputs[input_port_idx++] = {input}; + } + + NodeContext node_ctx(*node->get_decoder(), named_inputs); + auto new_node_outputs = translator_it->second(node_ctx); + SetTracingInfo(node_ctx.get_name(), new_node_outputs.front()); + + auto new_output = new_node_outputs.begin(); + auto old_outputs = node->outputs(); + auto old_output = old_outputs.begin(); + + for (; new_output != new_node_outputs.end() && old_output != old_outputs.end(); ++old_output, ++new_output) { + old_output->replace(*new_output); + } +} +} // namespace + +FrontEndTF::FrontEndTF() : m_op_translators(tf::op::get_supported_ops()) {} + +void FrontEndTF::translate_graph(const std::shared_ptr& model, + const std::string& model_name, + bool fail_fast, + bool no_conversion, + std::shared_ptr& ng_function) const { + // a map from operation names to generated nGraph Output + tf::OpMap ng_op_map; + + ov::ParameterVector params; + ov::ResultVector results; + const auto& operation_places = model->get_op_places(); + const auto& model_inputs = model->get_inputs(); + const auto& model_outputs = model->get_outputs(); + const auto& model_frozen_inputs = model->get_tensor_values(); + + std::map> translate_map; + + const auto& TRANSLATE_OP_MAP = m_op_translators; + if (no_conversion) { + const std::set required_types{"Placeholder", "_Retval", "NoOp"}; + for (const auto& name : required_types) { + translate_map.emplace(name, TRANSLATE_OP_MAP.at(name)); + } + } else { + translate_map.insert(TRANSLATE_OP_MAP.begin(), TRANSLATE_OP_MAP.end()); + } + + // fill ng_op_map with Constant outputs for frozen inputs + for (const auto& frozen_input : model_frozen_inputs) { + const auto& frozen_input_name = frozen_input.first; + const auto& frozen_input_value = frozen_input.second; + FRONT_END_GENERAL_CHECK(ng_op_map.count(frozen_input_name) == 0, + "Input with frozen value has been already met: " + frozen_input_name); + ng_op_map[frozen_input_name] = {frozen_input_value}; + } + + // create parameter nodes for all tensor places corresponding to inputs + for (const auto& input_place : model_inputs) { + FRONT_END_GENERAL_CHECK(input_place->get_names().size() == 1, "Input place must have one name."); + auto input_name = input_place->get_names()[0]; + if (ng_op_map.count(input_name)) { + // probably this input is frozen + continue; + } + const auto& input_tensor_place = std::dynamic_pointer_cast(input_place); + auto input_shape = input_tensor_place->get_partial_shape(); + auto input_type = input_tensor_place->get_element_type(); + + auto input_ng_output = ConstructNgNode(input_name, input_type, input_shape); + auto input_ng_node = std::dynamic_pointer_cast(input_ng_output.get_node_shared_ptr()); + params.push_back(input_ng_node); + ng_op_map[input_name] = {input_ng_output}; + } + + // create the nGraph ops from TensorFlow ops + for (const auto& operation_place : operation_places) { + auto operation_decoder = operation_place->get_decoder(); + auto operation_name = operation_place->get_names()[0]; + + // output for parameter nodes has been already generated + if (ng_op_map.count(operation_name)) { + continue; + } + + // prepare a list of nGraph node inputs for each node + ov::OutputVector ng_inputs; + ::ov::frontend::tf::NamedInputs named_inputs; + for (size_t input_port_idx = 0; input_port_idx < operation_decoder->get_input_size(); ++input_port_idx) { + std::string producer_name; + size_t producer_port_idx; + try { + operation_decoder->get_input_node(input_port_idx, producer_name, producer_port_idx); + } catch (const std::exception& e) { + FRONT_END_THROW("[ ERROR ] Exception happened when preparing input " + std::to_string(input_port_idx) + + " for op '" + operation_decoder->get_op_name() + "', expected input name: '" + + producer_name + "', expected input port index: " + std::to_string(producer_port_idx) + + '\n'); + } + // TODO: re-implement the logic below once Place graph structure is implemented + // Using Place graph structure (OpPlace, In/OutPortPlace places and their connections) can give + // names of ports and operations that can be used for further check about existence in ng_op_map + + // check if output vector for places have been already defined and the order of this check is important + // it moves from places corresponding to input port of the current operation node to output port of original + // producers + if (ng_op_map.count(std::to_string(input_port_idx) + ":" + operation_name)) { + const auto& input_outputs_vector = ng_op_map.at(std::to_string(input_port_idx) + ":" + operation_name); + FRONT_END_GENERAL_CHECK(input_outputs_vector.size() == 1, + "Input created with pruning must have one output"); + ng_inputs.push_back(input_outputs_vector.at(0)); + named_inputs[input_port_idx] = {input_outputs_vector.at(0)}; + } else if (ng_op_map.count(producer_name + ":" + std::to_string(producer_port_idx))) { + const auto& input_outputs_vector = + ng_op_map.at(producer_name + ":" + std::to_string(producer_port_idx)); + FRONT_END_GENERAL_CHECK(input_outputs_vector.size() == 1, + "Input created with pruning must have one output"); + ng_inputs.push_back(input_outputs_vector.at(0)); + named_inputs[input_port_idx] = {input_outputs_vector.at(0)}; + } else if (ng_op_map.count(producer_name)) { + const auto& input_outputs_vector = ng_op_map.at(producer_name); + FRONT_END_GENERAL_CHECK(input_outputs_vector.size() > producer_port_idx, + "Input created with pruning must have one output"); + ng_inputs.push_back(input_outputs_vector.at(producer_port_idx)); + named_inputs[input_port_idx] = {input_outputs_vector.at(producer_port_idx)}; + } else { + FRONT_END_GENERAL_CHECK(false, + "No input is found for node \"" + operation_name + "\" by port" + + std::to_string(producer_port_idx)); + } + } + + // generate nGraph node output vector for the current operation node + ov::OutputVector ng_outputs; + try { + FRONT_END_OP_CONVERSION_CHECK(translate_map.count(operation_decoder->get_op_type()), + "No translator found for " + operation_decoder->get_op_type() + " node."); + auto op_fun = &(translate_map[operation_decoder->get_op_type()]); + // NodeContext node_context(ng_inputs, operation_decoder, model_inputs); + // TODO: Check why NodeContextNew doesn't have ngOutputVector ng_inputs input in constructor + ::ov::frontend::tf::NodeContext node_context(*operation_decoder, named_inputs); + // generate nGraph node output vector using translator for given operation type + ng_outputs = (*op_fun)(node_context); + } catch (...) { + if (fail_fast) { + // re-throw any exception + throw; + } else { + auto ng_node = std::make_shared(operation_decoder, + ng_inputs, + operation_place->get_output_ports().size()); + SetTracingInfo(operation_name, ng_node); + ng_outputs = ng_node->outputs(); + } + } + + // register nGraph node outputs in the map for new operation node + for (const auto& output : ng_outputs) { + if (auto result = std::dynamic_pointer_cast(output.get_node_shared_ptr())) { + // do not add RetVal type operation to ng_op_map + results.push_back(result); + } else { + auto param = std::dynamic_pointer_cast(output.get_node_shared_ptr()); + if (param && operation_decoder->get_op_type() != "Identity") { + params.push_back(param); + } + ng_op_map[operation_name].push_back(output); + } + } + } + + // create Result nodes for all model outputs + for (const auto& model_output : model_outputs) { + auto model_output_tensor_place = std::dynamic_pointer_cast(model_output); + auto model_output_name = model_output_tensor_place->get_names()[0]; + std::string operation_name; + std::string port_type; + size_t port_index; + ov::frontend::tf::extract_operation_name_and_port(model_output_name, operation_name, port_index, port_type); + + if (port_type == "none") { + for (const auto& node_output : ng_op_map[operation_name]) { + results.push_back(std::make_shared(node_output)); + } + } else if (port_type == "out") { + const auto& node_outputs = ng_op_map[operation_name]; + FRONT_END_GENERAL_CHECK(node_outputs.size() > port_index, + "Output port with index " + std::to_string(port_index) + " of " + operation_name + + "node specified as custom output does not exist"); + results.push_back(std::make_shared(node_outputs[port_index])); + } else if (port_type == "in") { + // TODO: avoid this traversing by having a map for OpPlace objects, for example + std::shared_ptr operation_place = nullptr; + for (const auto& op_place : operation_places) { + FRONT_END_GENERAL_CHECK(!op_place->get_names().empty(), "No names for OpPlace found."); + if (op_place->get_names()[0] == operation_name) { + operation_place = op_place; + } + } + FRONT_END_GENERAL_CHECK(operation_place, "There is no operation place with a name: " + operation_name); + auto operation_decoder = operation_place->get_decoder(); + + // get to know a producer node and by which its output port data is generated + std::string producer_name; + size_t producer_port_idx; + try { + operation_decoder->get_input_node(port_index, producer_name, producer_port_idx); + } catch (const std::exception& e) { + FRONT_END_THROW("[ ERROR ] Exception happened when preparing input " + std::to_string(port_index) + + " for op '" + operation_decoder->get_op_name() + "', expected input name: '" + + producer_name + "', expected input port index: " + std::to_string(producer_port_idx) + + '\n'); + } + + // add Result node for this producer output port + const auto& node_outputs = ng_op_map[producer_name]; + FRONT_END_GENERAL_CHECK(node_outputs.size() > producer_port_idx, + "Output port with index " + std::to_string(producer_port_idx) + " of " + + producer_name + "node specified as custom output does not exist"); + results.push_back(std::make_shared(node_outputs[producer_port_idx])); + } + } + + // find all terminal nodes in ngraph graph to complete list of results + if (results.empty()) { + for (const auto& node_output_vector : ng_op_map) { + for (const auto& output : node_output_vector.second) { + if (output.get_target_inputs().empty() && + !std::dynamic_pointer_cast(output.get_node_shared_ptr())) { + results.push_back(std::make_shared(output)); + } + } + } + } + + // TODO: reorder results and params according to indices given in RT info (if any) + + // create the nGraph function + ng_function = std::make_shared(results, params, model_name); + + NGRAPH_VLOG(5) << "Done with translations"; +} + +/// \brief Check if FrontEndTensorflow can recognize model from given parts +bool FrontEndTF::supported_impl(const std::vector>& variants) const { + // TODO: Support other TensorFlow formats: SavedModel, .meta, checkpoint, pbtxt + if (variants.size() != 1) + return false; + + // Validating first path, it must contain a model + if (ov::is_type>(variants[0])) { + std::string suffix = ".pb"; + std::string model_path = ov::as_type_ptr>(variants[0])->get(); + if (ov::util::ends_with(model_path, suffix.c_str())) { + return true; + } + } + return false; +} + +ngraph::frontend::InputModel::Ptr FrontEndTF::load_impl( + const std::vector>& variants) const { + // TODO: Support other TensorFlow formats: SavedModel, .meta, checkpoint, pbtxt + if (variants.size() == 1) { + // a case when binary protobuf format is provided + if (ov::is_type>(variants[0])) { + std::string suffix = ".pb"; + std::string model_path = ov::as_type_ptr>(variants[0])->get(); + if (ov::util::ends_with(model_path, suffix.c_str())) { + return std::make_shared( + std::make_shared<::ov::frontend::tf::GraphIteratorProto>(model_path)); + } + } + } + return nullptr; +} + +std::shared_ptr FrontEndTF::convert(ngraph::frontend::InputModel::Ptr model) const { + auto model_tf = std::dynamic_pointer_cast(model); + + std::shared_ptr f; + translate_graph(model_tf, "here_should_be_a_graph_name", true, false, f); + normalize(f); + + // TODO: check that nGraph function does not contain operations which are not in the opset + + return f; +} + +std::shared_ptr FrontEndTF::convert_partially(ngraph::frontend::InputModel::Ptr model) const { + auto model_tf = std::dynamic_pointer_cast(model); + std::shared_ptr f; + translate_graph(model_tf, "here_should_be_a_graph_name", false, false, f); + normalize(f); + return f; +} + +std::shared_ptr FrontEndTF::decode(ngraph::frontend::InputModel::Ptr model) const { + auto model_tf = std::dynamic_pointer_cast(model); + std::shared_ptr f; + translate_graph(model_tf, "here_should_be_a_graph_name", false, true, f); + return f; +} + +void FrontEndTF::convert(std::shared_ptr partiallyConverted) const { + for (const auto& node : partiallyConverted->get_ordered_ops()) { + if (ov::is_type(node)) { + translate_framework_node(std::dynamic_pointer_cast(node), m_op_translators); + } + } + for (const auto& result : partiallyConverted->get_results()) { + result->validate_and_infer_types(); + } + + normalize(partiallyConverted); +} + +void FrontEndTF::normalize(std::shared_ptr function) const { + ov::pass::Manager manager; + // TODO: switch on TransposeSinking once it is ready + // manager.register_pass(); + manager.run_passes(function); +} diff --git a/ngraph/frontend/tensorflow/src/graph_iterator_proto.hpp b/ngraph/frontend/tensorflow/src/graph_iterator_proto.hpp new file mode 100644 index 00000000000..d4004d10f2c --- /dev/null +++ b/ngraph/frontend/tensorflow/src/graph_iterator_proto.hpp @@ -0,0 +1,61 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include +#include +#include + +#include "decoder_proto.hpp" +#include "graph.pb.h" +#include "node_def.pb.h" + +namespace ov { +namespace frontend { +namespace tf { +class GraphIteratorProto : public GraphIterator { + std::vector m_nodes; + size_t node_index = 0; + std::shared_ptr<::tensorflow::GraphDef> m_graph_def; + +public: + template + GraphIteratorProto(const std::basic_string& path) : m_graph_def(std::make_shared<::tensorflow::GraphDef>()) { + std::ifstream pb_stream(path, std::ios::in | std::ifstream::binary); + + FRONT_END_GENERAL_CHECK(pb_stream && pb_stream.is_open(), "Model file does not exist"); + FRONT_END_GENERAL_CHECK(m_graph_def->ParseFromIstream(&pb_stream), "Model cannot be parsed"); + + m_nodes.resize(m_graph_def->node_size()); + for (size_t i = 0; i < m_nodes.size(); ++i) + m_nodes[i] = &m_graph_def->node(i); + } + + /// Set iterator to the start position + void reset() override { + node_index = 0; + } + + size_t size() const override { + return m_nodes.size(); + } + + /// Moves to the next node in the graph + void next() override { + node_index++; + } + + bool is_end() const override { + return node_index >= m_nodes.size(); + } + + /// Return NodeContext for the current node that iterator points to + std::shared_ptr get_decoder() const override { + return std::make_shared(m_nodes[node_index]); + } +}; +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/model.cpp b/ngraph/frontend/tensorflow/src/model.cpp new file mode 100644 index 00000000000..6ab98998236 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/model.cpp @@ -0,0 +1,387 @@ +// Copyright (C) 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "graph_iterator_proto.hpp" +#include "ngraph_conversions.hpp" +#include "node_context.hpp" +#include "utils.hpp" + +using namespace google; + +namespace ov { +namespace frontend { +namespace tf { +void extract_operation_name_and_port(const std::string& port_name, + std::string& operation_name, + size_t& port_index, + std::string& port_type) { + constexpr char delimeter[] = ":"; + auto pos = port_name.find(delimeter); + if (pos == std::string::npos) { + operation_name = port_name; + port_type = "none"; + port_index = 0; + return; + } + + FRONT_END_GENERAL_CHECK((0 < pos) && (pos + 1 < port_name.length()), "Incorrect port name specified: " + port_name); + + auto left_part = port_name.substr(0, pos); + auto right_part = port_name.substr(pos + 1, port_name.length() - pos); + + if (left_part.find_first_not_of("0123456789") == std::string::npos) { + port_type = "in"; + operation_name = right_part; + port_index = std::atoi(left_part.c_str()); + } else if (right_part.find_first_not_of("0123456789") == std::string::npos) { + port_type = "out"; + operation_name = left_part; + port_index = std::atoi(right_part.c_str()); + } else { + FRONT_END_GENERAL_CHECK(false, "Incorrect port name specified: " + port_name); + } +} +} // namespace tf +class InputModelTF::InputModelTFImpl { +public: + InputModelTFImpl(const GraphIterator::Ptr& graph_iterator, const ngraph::frontend::InputModel& input_model); + std::vector getInputs() const; + std::vector getOutputs() const; + ngraph::frontend::Place::Ptr getPlaceByTensorName(const std::string& tensorName) const; + void overrideAllOutputs(const std::vector& outputs); + void overrideAllInputs(const std::vector& inputs); + void extractSubgraph(const std::vector& inputs, + const std::vector& outputs); + void setPartialShape(ngraph::frontend::Place::Ptr place, const ov::PartialShape&); + ov::PartialShape getPartialShape(ngraph::frontend::Place::Ptr place) const; + void setElementType(ngraph::frontend::Place::Ptr place, const ov::element::Type&); + void setTensorValue(ngraph::frontend::Place::Ptr place, const void* value); + + std::vector> get_op_places() const; + std::map> get_tensor_places() const { + return m_tensor_places; + } + std::map> get_tensor_values() const { + return m_tensor_values; + }; + +private: + void loadPlaces(); + std::vector> determine_cut_nodes() const; + + std::vector> m_op_places; + std::map> m_op_places_map; + mutable std::map> m_tensor_places; + std::vector m_inputs; + std::vector m_outputs; + std::map> m_tensor_values; + + std::shared_ptr m_graph_iterator; + const ngraph::frontend::InputModel& m_input_model; + + // shows if some nodes might be deleted from graph + bool m_graph_changed = false; +}; + +void InputModelTF::InputModelTFImpl::loadPlaces() { + std::set all_op_names; + std::set op_names_with_consumers; + + m_inputs.clear(); + for (; !m_graph_iterator->is_end(); m_graph_iterator->next()) { + auto node_decoder = m_graph_iterator->get_decoder(); + auto op_name = node_decoder->get_op_name(); + auto op_type = node_decoder->get_op_type(); + auto op_place = std::make_shared(m_input_model, node_decoder); + all_op_names.insert(op_name); + m_op_places.push_back(op_place); + m_op_places_map[op_name] = op_place; + if (op_type == "Placeholder") { + auto pshape = std::dynamic_pointer_cast>( + node_decoder->get_attribute("shape", VariantWrapper::get_type_info_static())); + auto type = std::dynamic_pointer_cast>( + node_decoder->get_attribute("dtype", VariantWrapper::get_type_info_static())); + std::vector names = {op_name}; + auto tensor_place = std::make_shared(m_input_model, pshape->get(), type->get(), names); + m_tensor_places[op_name] = tensor_place; + m_inputs.push_back(tensor_place); + } + for (size_t input_port_idx = 0; input_port_idx < node_decoder->get_input_size(); ++input_port_idx) { + std::string producer_op_name; + size_t producer_output_port_idx; + try { + node_decoder->get_input_node(input_port_idx, producer_op_name, producer_output_port_idx); + op_names_with_consumers.insert(producer_op_name); + } catch (const std::exception& e) { + FRONT_END_THROW("[ ERROR ] Exception happened when preparing input " + std::to_string(input_port_idx) + + " for op '" + node_decoder->get_op_name() + "', expected input name: '" + + producer_op_name + + "', expected input port index: " + std::to_string(producer_output_port_idx)); + } + } + } + std::set op_names_without_consumers; + std::set_difference(all_op_names.begin(), + all_op_names.end(), + op_names_with_consumers.begin(), + op_names_with_consumers.end(), + std::inserter(op_names_without_consumers, op_names_without_consumers.begin())); + m_graph_iterator->reset(); + + m_outputs.clear(); + for (auto& output_name : op_names_without_consumers) { + std::vector output_names = {output_name}; + auto output_place = + std::make_shared(m_input_model, ov::PartialShape({}), ov::element::undefined, output_names); + m_tensor_places[output_name] = output_place; + m_outputs.push_back(output_place); + } +} + +std::vector> InputModelTF::InputModelTFImpl::get_op_places() const { + if (m_graph_changed) { + return determine_cut_nodes(); + } + return m_op_places; +} + +std::vector> InputModelTF::InputModelTFImpl::determine_cut_nodes() const { + std::queue> decoders_queue; + std::unordered_set visited; + std::vector> new_ops; + for (const auto& output_place : m_outputs) { + FRONT_END_GENERAL_CHECK(output_place->get_names().size() > 0, "TensorPlace must have at least one name."); + auto output_place_name = output_place->get_names()[0]; + std::string operation_name; + size_t port_idx; + std::string port_type; + tf::extract_operation_name_and_port(output_place_name, operation_name, port_idx, port_type); + if (!visited.count(operation_name)) { + visited.insert(operation_name); + FRONT_END_GENERAL_CHECK(m_op_places_map.count(operation_name), + "Custom specified output is incorrect: " + output_place_name); + auto output_operation_place = m_op_places_map.at(operation_name); + FRONT_END_GENERAL_CHECK(output_operation_place, + "There is not operation place in the map: " + operation_name); + new_ops.push_back(output_operation_place); + decoders_queue.push(output_operation_place->get_decoder()); + } + } + while (!decoders_queue.empty()) { + auto operation_decoder = decoders_queue.front(); + decoders_queue.pop(); + auto current_operation_name = operation_decoder->get_op_name(); + for (size_t input_port_idx = 0; input_port_idx < operation_decoder->get_input_size(); ++input_port_idx) { + std::string producer_name; + size_t producer_output_port_idx; + try { + operation_decoder->get_input_node(input_port_idx, producer_name, producer_output_port_idx); + } catch (const std::exception& e) { + FRONT_END_THROW("[ ERROR ] Exception happened when preparing input " + std::to_string(input_port_idx) + + " for op '" + operation_decoder->get_op_name() + "', expected input name: '" + + producer_name + + "', expected input port index: " + std::to_string(producer_output_port_idx) + '\n'); + } + + // TODO: re-implement the logic below using Place graph structure (with OpPlace, In/OutPortPlace + // connections) and based on check if Place->is_input() decide to leave a node or not + + // is_input is a flag to leave producer operation node or not. + // this producing node is not left if consumer is pruned by its input port, + // the producer node is pruned by its output port or the producer becomes new input + // 1. check if the current node is pruned by its input port + bool is_input = false; + std::string input_port_name = std::to_string(input_port_idx) + ":" + current_operation_name; + if (m_tensor_places.find(input_port_name) != m_tensor_places.end()) { + const auto& tensor_place = m_tensor_places[input_port_name]; + is_input = is_input || (tensor_place->is_input() ? true : false); + } + + // 2. check if the producer node is pruned by its output port + std::string output_port_name = producer_name + ":" + std::to_string(producer_output_port_idx); + if (m_tensor_places.find(output_port_name) != m_tensor_places.end()) { + const auto& tensor_place = m_tensor_places[output_port_name]; + is_input = is_input || (tensor_place->is_input() ? true : false); + } + + // 3. check if the current node is an input + FRONT_END_GENERAL_CHECK(m_op_places_map.count(producer_name), + "There is no operation node with name: " + producer_name); + const auto& producer_operation_place = m_op_places_map.at(producer_name); + if (m_tensor_places.find(producer_name) != m_tensor_places.end()) { + const auto& tensor_place = m_tensor_places[producer_name]; + is_input |= (tensor_place->is_input() ? true : false); + } + + if (!is_input && !visited.count(producer_name)) { + visited.insert(producer_name); + new_ops.push_back(producer_operation_place); + decoders_queue.push(producer_operation_place->get_decoder()); + } + } + } + std::reverse(new_ops.begin(), new_ops.end()); + return new_ops; +} + +InputModelTF::InputModelTFImpl::InputModelTFImpl(const GraphIterator::Ptr& graph_iterator, + const ngraph::frontend::InputModel& input_model) + : m_input_model(input_model), + m_graph_iterator(graph_iterator) { + FRONT_END_GENERAL_CHECK(m_graph_iterator, "Null pointer specified for GraphIterator"); + loadPlaces(); +} + +std::vector InputModelTF::InputModelTFImpl::getInputs() const { + return m_inputs; +} + +std::vector InputModelTF::InputModelTFImpl::getOutputs() const { + return m_outputs; +} + +ngraph::frontend::Place::Ptr InputModelTF::InputModelTFImpl::getPlaceByTensorName(const std::string& tensorName) const { + if (m_tensor_places.find(tensorName) != m_tensor_places.end()) + return m_tensor_places.at(tensorName); + + // check that operation node exists for which this place is specified + std::string operation_name; + size_t port_idx; + std::string port_type; + tf::extract_operation_name_and_port(tensorName, operation_name, port_idx, port_type); + if (m_op_places_map.find(operation_name) != m_op_places_map.end()) { + std::vector names = {tensorName}; + auto m_var_place = + std::make_shared(m_input_model, ov::PartialShape(), ov::element::undefined, names); + m_tensor_places[tensorName] = m_var_place; + return m_var_place; + } + + return nullptr; +} + +std::shared_ptr castToTensorPlace(const ngraph::frontend::Place::Ptr& place) { + if (auto var_place = std::dynamic_pointer_cast(place)) { + return var_place; + } else if (auto in_port_place = std::dynamic_pointer_cast(place)) { + return in_port_place->get_source_tensor_tf(); + } else if (auto out_port_place = std::dynamic_pointer_cast(place)) { + return out_port_place->get_target_tensor_tf(); + } + FRONT_END_GENERAL_CHECK(false, "Cannot cast this Place to TensorPlaceTF."); +} + +void InputModelTF::InputModelTFImpl::overrideAllInputs(const std::vector& inputs) { + m_graph_changed = true; + m_inputs.clear(); + for (const auto& input_place : inputs) { + m_inputs.push_back(castToTensorPlace(input_place)); + } +} + +void InputModelTF::InputModelTFImpl::overrideAllOutputs(const std::vector& outputs) { + m_graph_changed = true; + m_outputs.clear(); + for (const auto& output_place : outputs) { + m_outputs.push_back(castToTensorPlace(output_place)); + } +} + +void InputModelTF::InputModelTFImpl::extractSubgraph(const std::vector& inputs, + const std::vector& outputs) { + m_graph_changed = true; + overrideAllInputs(inputs); + overrideAllOutputs(outputs); +} + +void InputModelTF::InputModelTFImpl::setPartialShape(ngraph::frontend::Place::Ptr place, + const ov::PartialShape& p_shape) { + castToTensorPlace(place)->set_partial_shape(p_shape); +} + +ov::PartialShape InputModelTF::InputModelTFImpl::getPartialShape(ngraph::frontend::Place::Ptr place) const { + return castToTensorPlace(place)->get_partial_shape(); +} + +void InputModelTF::InputModelTFImpl::setElementType(ngraph::frontend::Place::Ptr place, const ov::element::Type& type) { + castToTensorPlace(place)->set_element_type(type); +} + +void InputModelTF::InputModelTFImpl::setTensorValue(ngraph::frontend::Place::Ptr place, const void* value) { + m_graph_changed = true; + auto tensor_place = castToTensorPlace(place); + auto p_shape = tensor_place->get_partial_shape(); + auto type = tensor_place->get_element_type(); + auto constant = opset7::Constant::create(type, p_shape.to_shape(), value); + auto name = tensor_place->get_names()[0]; + constant->set_friendly_name(name); + m_tensor_values[name] = constant; +} + +InputModelTF::InputModelTF(const GraphIterator::Ptr& graph_iterator) + : _impl{std::make_shared(graph_iterator, *this)} {} + +std::vector> InputModelTF::get_op_places() const { + return _impl->get_op_places(); +} + +std::map> InputModelTF::get_tensor_places() const { + return _impl->get_tensor_places(); +} + +std::map> InputModelTF::get_tensor_values() const { + return _impl->get_tensor_values(); +} + +std::vector InputModelTF::get_inputs() const { + return _impl->getInputs(); +} + +std::vector InputModelTF::get_outputs() const { + return _impl->getOutputs(); +} + +ngraph::frontend::Place::Ptr InputModelTF::get_place_by_tensor_name(const std::string& tensorName) const { + return _impl->getPlaceByTensorName(tensorName); +} + +void InputModelTF::override_all_outputs(const std::vector& outputs) { + _impl->overrideAllOutputs(outputs); +} + +void InputModelTF::override_all_inputs(const std::vector& inputs) { + _impl->overrideAllInputs(inputs); +} + +void InputModelTF::extract_subgraph(const std::vector& inputs, + const std::vector& outputs) { + _impl->extractSubgraph(inputs, outputs); +} + +void InputModelTF::set_partial_shape(ngraph::frontend::Place::Ptr place, const ov::PartialShape& p_shape) { + _impl->setPartialShape(place, p_shape); +} + +ov::PartialShape InputModelTF::get_partial_shape(ngraph::frontend::Place::Ptr place) const { + return _impl->getPartialShape(place); +} + +void InputModelTF::set_element_type(ngraph::frontend::Place::Ptr place, const ov::element::Type& type) { + _impl->setElementType(place, type); +} + +void InputModelTF::set_tensor_value(ngraph::frontend::Place::Ptr place, const void* value) { + _impl->setTensorValue(place, value); +} +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/ngraph_conversions.cpp b/ngraph/frontend/tensorflow/src/ngraph_conversions.cpp new file mode 100644 index 00000000000..0a3f88b20cf --- /dev/null +++ b/ngraph/frontend/tensorflow/src/ngraph_conversions.cpp @@ -0,0 +1,39 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "ngraph_conversions.hpp" + +#include "utils.hpp" + +namespace ov { +namespace frontend { +namespace tf { + +void NHWCtoNCHW(const std::string& op_name, bool need_convert, ov::Output& node) { + if (need_convert) { + auto rank = node.get_shape().size(); + if (rank == 4) { + Transpose<0, 3, 1, 2>(node); + } else if (rank == 5) { + Transpose3D<0, 4, 1, 2, 3>(node); + } + SetTracingInfo(op_name, node); + } +} + +void NCHWtoNHWC(const std::string& op_name, bool need_convert, ov::Output& node) { + if (need_convert) { + auto rank = node.get_shape().size(); + if (rank == 4) { + Transpose<0, 2, 3, 1>(node); + } else if (rank == 5) { + Transpose3D<0, 2, 3, 4, 1>(node); + } + SetTracingInfo(op_name, node); + } +} + +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/ngraph_conversions.hpp b/ngraph/frontend/tensorflow/src/ngraph_conversions.hpp new file mode 100644 index 00000000000..9a411630695 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/ngraph_conversions.hpp @@ -0,0 +1,96 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include +#include +#include + +#include "graph.pb.h" +#include "types.pb.h" + +namespace ov { +namespace frontend { +namespace tf { + +using ::tensorflow::DataType; + +void TFTensorShapeToNGraphShape(const ::tensorflow::TensorShapeProto& tf_shape, ov::PartialShape* ng_shape); + +template +void Transpose(ov::Output& node) { + static_assert(a < 4 && b < 4 && c < 4 && d < 4, "Number of dimensions cannot exceed 4"); + static_assert(a != b && a != c && a != d && b != c && b != d && c != d, "Dimensions indices cannot be equal"); + auto& s = node.get_shape(); + ov::Shape reshaped_shape{s[a], s[b], s[c], s[d]}; + ov::Shape transpose_order{a, b, c, d}; + auto input_order = + std::make_shared(ov::element::u64, ov::Shape{transpose_order.size()}, transpose_order); + node = std::make_shared(node, input_order); +} + +template +void Transpose(std::shared_ptr& node) { + Transpose(node->get_default_output()); +} + +template +void Transpose3D(ov::Output& node) { + static_assert(a < 5 && b < 5 && c < 5 && d < 5 && e < 5, "Number of dimensions cannot exceed 5"); + static_assert(a != b && a != c && a != d && a != e && b != c && b != d && b != e && c != d && c != e && d != e, + "Dimensions indices cannot be equal"); + auto& s = node.get_shape(); + ov::Shape reshaped_shape{s[a], s[b], s[c], s[d], s[e]}; + ov::Shape transpose_order{a, b, c, d, e}; + auto input_order = + std::make_shared(ov::element::u64, ov::Shape{transpose_order.size()}, transpose_order); + node = std::make_shared(node, input_order); +} + +template +void Transpose3D(std::shared_ptr& node) { + Transpose3D(node->get_default_output()); +} + +namespace detail { +template +void NHWCtoHW(const std::vector& src, std::vector& dst) { + if (dst.size() >= 2) { + dst[0] = src[1]; + dst[1] = src[2]; + } + if (dst.size() >= 3) { + dst[2] = src[3]; + } +} + +template +void NCHWtoHW(const std::vector& src, std::vector& dst) { + if (dst.size() >= 2) { + dst[0] = src[2]; + dst[1] = src[3]; + } + if (dst.size() >= 3) { + dst[2] = src[4]; + } +} +} // namespace detail + +void NHWCtoNCHW(const std::string& op_name, bool need_convert, ov::Output& ng_input); + +void NCHWtoNHWC(const std::string& op_name, bool need_convert, ov::Output& ng_node); + +template +void NHWCtoHW(bool is_nhwc, const std::vector& src, std::vector& dst) { + if (is_nhwc) { + detail::NHWCtoHW(src, dst); + } else { + detail::NCHWtoHW(src, dst); + } +} + +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/node_context.hpp b/ngraph/frontend/tensorflow/src/node_context.hpp new file mode 100644 index 00000000000..52a01560505 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/node_context.hpp @@ -0,0 +1,135 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once +#include +#include +#include +#include + +#include "tensor.pb.h" +#include "types.pb.h" + +#define NGRAPH_VARIANT_DECLARATION(TYPE, info) \ + template <> \ + class VariantWrapper : public VariantImpl { \ + public: \ + OPENVINO_RTTI(info); \ + VariantWrapper(const value_type& value) : VariantImpl(value) {} \ + } + +namespace ov { +NGRAPH_VARIANT_DECLARATION(int32_t, "Variant::int32"); +NGRAPH_VARIANT_DECLARATION(uint64_t, "Variant::uint64_t"); +NGRAPH_VARIANT_DECLARATION(std::vector, "Variant::int32_vector"); +NGRAPH_VARIANT_DECLARATION(float, "Variant::float"); +NGRAPH_VARIANT_DECLARATION(std::vector, "Variant::float_vector"); +NGRAPH_VARIANT_DECLARATION(bool, "Variant::bool"); +NGRAPH_VARIANT_DECLARATION(ov::element::Type, "Variant::ov_element_type"); +NGRAPH_VARIANT_DECLARATION(std::vector, "Variant::int64_vector"); +NGRAPH_VARIANT_DECLARATION(ov::PartialShape, "Variant::ngraph_PartialShape"); +NGRAPH_VARIANT_DECLARATION(std::vector, "Variant::string_vector"); +NGRAPH_VARIANT_DECLARATION(::tensorflow::DataType, "Variant::DataType"); +NGRAPH_VARIANT_DECLARATION(::tensorflow::TensorProto, "Variant::TensorProto"); +} // namespace ov + +namespace ov { +namespace frontend { +namespace tf { +using InPortName = size_t; +using OutPortName = size_t; +using NamedOutputs = std::map; +using NamedInputs = std::map; + +/// Keep necessary data for a single node in the original FW graph to facilitate +/// conversion process in the rules code. +class NodeContext { + const DecoderBase& m_decoder; + const NamedInputs& m_name_map; + +public: + NodeContext(const DecoderBase& decoder, const NamedInputs& name_map) : m_decoder(decoder), m_name_map(name_map) {} + + /// Returns node attribute by name. Returns 'def' value if attribute does not exist + template + T get_attribute(const std::string& name, const T& def) const { + auto res = m_decoder.get_attribute(name, VariantWrapper::get_type_info_static()); + if (res) { + auto ret = std::dynamic_pointer_cast>(res); + FRONT_END_GENERAL_CHECK(ret, "Attribute with name '", name, "' has invalid type"); + return ret->get(); + } + return def; + } + + /// Returns node attribute by name + template + T get_attribute(const std::string& name) const { + auto res = m_decoder.get_attribute(name, VariantWrapper::get_type_info_static()); + FRONT_END_GENERAL_CHECK(res, "Attribute with name '", name, "' does not exist"); + auto ret = std::dynamic_pointer_cast>(res); + FRONT_END_GENERAL_CHECK(ret, "Attribute with name '", name, "' has invalid type"); + return ret->get(); + } + + /// Check if an attribute of a given name exists + template + bool has_attribute(const std::string& name) const { + return m_decoder.get_attribute(name, VariantWrapper::get_type_info_static()) != nullptr; + } + + /// Detects if there is at least one input attached with a given name + bool has_ng_input(const size_t& port_index) const { + auto found = m_name_map.find(port_index); + if (found != m_name_map.end()) + return !found->second.empty(); + return false; + } + + /// Returns exactly one input with a given name; throws if there is no inputs or + /// there are more than one input + Output get_ng_input(const size_t& port_index) const { + FRONT_END_GENERAL_CHECK(m_name_map.at(port_index).size() == 1); + return m_name_map.at(port_index).at(0); + } + + /// Returns all inputs with a given name + OutputVector get_ng_inputs(const size_t& port_index) const { + return m_name_map.at(port_index); + } + + /// Returns all inputs in order they appear in map. This is used for FrameworkNode + /// creation + OutputVector get_all_ng_inputs() const { + OutputVector res; + for (const auto& entry : m_name_map) { + res.insert(res.end(), entry.second.begin(), entry.second.end()); + } + return res; + } + + /// Get a number of inputs + size_t get_ng_input_size() const { + return m_name_map.size(); + } + + /// Get operation type + std::string get_op_type() const { + return m_decoder.get_op_type(); + } + + /// Get a node name + std::string get_name() const { + return m_decoder.get_op_name(); + } + + /// Get a decoder + const DecoderBase* get_decoder() const { + return &m_decoder; + } +}; + +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/addN.cpp b/ngraph/frontend/tensorflow/src/op/addN.cpp new file mode 100644 index 00000000000..89d2e80e0e0 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/addN.cpp @@ -0,0 +1,33 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateAddNOp(const NodeContext& node) { + OutputVector ng_arg_vec = node.get_all_ng_inputs(); + + auto ng_addn = std::accumulate(std::next(ng_arg_vec.begin()), + ng_arg_vec.end(), + ng_arg_vec.at(0), + [&node](Output a, Output b) { + return ConstructNgNode(node.get_name(), a, b); + }); // accumulation: start with + // first element. default op is + // addition + return {ng_addn}; +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/arg_min_max.cpp b/ngraph/frontend/tensorflow/src/op/arg_min_max.cpp new file mode 100644 index 00000000000..cbd0246f859 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/arg_min_max.cpp @@ -0,0 +1,62 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateArgMinMax(const NodeContext& node, std::string mode) { + Output ng_input = node.get_ng_input(0); + + std::vector tf_dim; + GetStaticInputVector(node, 1, &tf_dim); + + Shape input_shape = ng_input.get_shape(); + size_t input_rank = input_shape.size(); + + if (tf_dim.size() != 1) { + throw errors::InvalidArgument("ArgMax Op: dimension must be scalar, operates on a single axis"); + } + + // If input dimension is negative, make it positive + if (tf_dim[0] < 0) { + NGRAPH_VLOG(3) << "Input dimension is negative, make it positive " << tf_dim[0]; + tf_dim[0] = (int64_t)input_rank + tf_dim[0]; + } + NGRAPH_VLOG(3) << "Axis along which to compute " << tf_dim[0]; + size_t k_axis = tf_dim[0]; + + auto ng_et = node.get_attribute("output_type"); + + auto ng_k = ConstructNgNode(node.get_name(), element::i64, Shape{}, std::vector({1})); + + std::string sort = "none"; + auto ng_topk = std::make_shared(ng_input, ng_k, k_axis, mode, sort, ng_et); + auto ng_indices = ng_topk->output(1); + int axis = ng_topk->get_axis(); + auto axis_to_remove = + ConstructNgNode(node.get_name(), element::i64, Shape{1}, std::vector({axis})); + auto reshaped_indices = ConstructNgNode(node.get_name(), ng_indices, axis_to_remove); + SetTracingInfo(node.get_name(), reshaped_indices); + return {reshaped_indices}; +} + +OutputVector TranslateArgMaxOp(const NodeContext& node) { + return (TranslateArgMinMax(node, "max")); +} + +OutputVector TranslateArgMinOp(const NodeContext& node) { + return (TranslateArgMinMax(node, "min")); +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/avg_pool.cpp b/ngraph/frontend/tensorflow/src/op/avg_pool.cpp new file mode 100644 index 00000000000..c4d5fd5f297 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/avg_pool.cpp @@ -0,0 +1,69 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateAvgPoolOp(const NodeContext& node) { + Output ng_input = node.get_ng_input(0); + + auto tf_strides = node.get_attribute>("strides"); + auto tf_ksize = node.get_attribute>("ksize"); + auto tf_padding_type = node.get_attribute("padding"); + auto tf_data_format = node.get_attribute("data_format"); + if (tf_data_format != "NHWC" && tf_data_format != "NCHW") { + throw errors::InvalidArgument("AvgPool data format is neither NHWC nor NCHW"); + } + + bool is_nhwc = (tf_data_format == "NHWC"); + + Strides ng_strides(2); + Shape ng_image_shape(2); + Shape ng_kernel_shape(2); + NHWCtoHW(is_nhwc, tf_strides, ng_strides); + NHWCtoHW(is_nhwc, ng_input.get_shape(), ng_image_shape); + NHWCtoHW(is_nhwc, tf_ksize, ng_kernel_shape); + NHWCtoNCHW(node.get_name(), is_nhwc, ng_input); + + CoordinateDiff padding_below; + CoordinateDiff padding_above; + Shape ng_dilations{1, 1}; + MakePadding(tf_padding_type, + ng_image_shape, + ng_kernel_shape, + ng_strides, + ng_dilations, + padding_below, + padding_above); + + // TODO: remove this once nGraph supports negative padding + // (CoordinateDiff) for AvgPool + Shape ng_padding_below(padding_below.begin(), padding_below.end()); + Shape ng_padding_above(padding_above.begin(), padding_above.end()); + + Output ng_avgpool = ConstructNgNode(node.get_name(), + ng_input, + ng_strides, + ng_padding_below, + ng_padding_above, + ng_kernel_shape, + true, + ov::op::RoundingType::FLOOR); + + NCHWtoNHWC(node.get_name(), is_nhwc, ng_avgpool); + + return {ng_avgpool}; +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/bias_add.cpp b/ngraph/frontend/tensorflow/src/op/bias_add.cpp new file mode 100644 index 00000000000..f12f1630aed --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/bias_add.cpp @@ -0,0 +1,55 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateBiasAddOp(const NodeContext& node) { + Output ng_input = node.get_ng_input(0), ng_bias = node.get_ng_input(1); + + std::string tf_data_format = node.get_attribute("data_format", "NHWC"); + + if (tf_data_format != "NHWC" && tf_data_format != "NCHW") { + throw errors::InvalidArgument("BiasAdd data format is neither NHWC nor NCHW"); + } + + auto ng_input_shape = ng_input.get_shape(); + auto ng_bias_shape = ng_bias.get_shape(); + if (ng_bias_shape.size() != 1) { + throw errors::InvalidArgument("Bias argument to BiasAdd does not have one dimension"); + } + + // We'll choose reshape over broadcast + // Reshape the bias to (1, C, 1, ...) if input is channels-first. + Output ng_bias_reshaped = ng_bias; + if (tf_data_format == "NCHW") { + auto channel_dim = ng_input_shape[1]; + std::vector target_shape(ng_input_shape.size()); + for (int64_t i = 0; i < ng_input_shape.size(); i++) { + if (i == 1) { + target_shape[i] = channel_dim; + } else { + target_shape[i] = 1; + } + } + auto target_shape_node = make_shared(element::i64, Shape{ng_input_shape.size()}, target_shape); + ng_bias_reshaped = ConstructNgNode(node.get_name(), ng_bias, target_shape_node, false); + } + + Output ng_add = ConstructNgNode(node.get_name(), ng_input, ng_bias_reshaped); + + return {ng_add}; +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/binary_op.cpp b/ngraph/frontend/tensorflow/src/op/binary_op.cpp new file mode 100644 index 00000000000..8c31910d537 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/binary_op.cpp @@ -0,0 +1,99 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +// Helper function to translate a binary op +// Parameters: +// +// TFNodeDecoder* op - TF op being translated. Must have only two +// inputs. +// const std::vector& static_input_map - the static input +// map Builder::OpMap& ng_op_map - The TF-to-nGraph op map. std::function(Output, +// Output)> +// create_binary_op - Function to construct the graph implementing +// the binary op, given the 2 ng_inputs to the +// binaryop +// Example Usage: +// +// if (op->type_string() == "SquaredDifference") { +// TF_RETURN_IF_ERROR(TranslateBinaryOp(op, ng_op_map, +// [](Output ng_input1, Output +// ng_input2) { +// auto ng_diff = Output(input1, +// input2); +// return Output(ng_diff,ng_diff); +// })); +// } +// + +namespace ov { +namespace frontend { +namespace tf { +namespace op { +OutputVector TranslateBinaryOp(const NodeContext& node, + std::function(Output&, Output&)> create_binary_op) { + Output ng_lhs = node.get_ng_input(0), ng_rhs = node.get_ng_input(1); + auto ng_node = create_binary_op(ng_lhs, ng_rhs); + + // TODO do we need it? + /* if (ng_node != ng_lhs && ng_node != ng_rhs) { + Builder::SetTracingInfo(node.get_name(), ng_node); + }*/ + return {ng_node}; +} + +OutputVector TranslateFloorDivOp(const NodeContext& node) { + auto floordiv_fn = [&node](Output x, Output y) { + return ConstructNgNode(node.get_name(), ConstructNgNode(node.get_name(), x, y)); + }; + return TranslateBinaryOp(node, floordiv_fn); +} + +// Helper function to translate a binary op in cases where there is a one-to-one +// mapping from TensorFlow ops to nGraph ops. +// +// Example usage: +// +// if (n->type_string == "Add") { +// TF_RETURN_IF_ERROR(TranslateBinaryOp(op, +// static_input_map, +// ng_op_map)); +// } +// + +template +OutputVector TranslateBinaryOp(const NodeContext& node) { + return TranslateBinaryOp(node, [&node](Output& ng_lhs, Output& ng_rhs) { + return ConstructNgNode(node.get_name(), ng_lhs, ng_rhs); + }); +} + +template OutputVector TranslateBinaryOp(const NodeContext& node); +template OutputVector TranslateBinaryOp(const NodeContext& node); +template OutputVector TranslateBinaryOp(const NodeContext& node); +template OutputVector TranslateBinaryOp(const NodeContext& node); +template OutputVector TranslateBinaryOp(const NodeContext& node); +template OutputVector TranslateBinaryOp(const NodeContext& node); +template OutputVector TranslateBinaryOp(const NodeContext& node); +template OutputVector TranslateBinaryOp(const NodeContext& node); +template OutputVector TranslateBinaryOp(const NodeContext& node); +template OutputVector TranslateBinaryOp(const NodeContext& node); +template OutputVector TranslateBinaryOp(const NodeContext& node); +template OutputVector TranslateBinaryOp(const NodeContext& node); +template OutputVector TranslateBinaryOp(const NodeContext& node); +template OutputVector TranslateBinaryOp(const NodeContext& node); +template OutputVector TranslateBinaryOp(const NodeContext& node); +template OutputVector TranslateBinaryOp(const NodeContext& node); +template OutputVector TranslateBinaryOp(const NodeContext& node); +template OutputVector TranslateBinaryOp(const NodeContext& node); + +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/cast.cpp b/ngraph/frontend/tensorflow/src/op/cast.cpp new file mode 100644 index 00000000000..e85195969e6 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/cast.cpp @@ -0,0 +1,26 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateCastOp(const NodeContext& node) { + auto ng_input = node.get_ng_input(0); + + auto ng_et = node.get_attribute("DstT"); + return {ConstructNgNode(node.get_name(), ng_input, ng_et)}; +} + +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/concat.cpp b/ngraph/frontend/tensorflow/src/op/concat.cpp new file mode 100644 index 00000000000..61355df5aea --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/concat.cpp @@ -0,0 +1,44 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov; +using namespace opset8; +using namespace ov::frontend; +using namespace frontend::tf::detail; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateConcatV2Op(const NodeContext& node) { + ValidateInputCountMin(node, 2); + + std::vector tf_concat_axis_vec; + GetStaticInputVector(node, node.get_ng_input_size() - 1, &tf_concat_axis_vec); + + int64_t concat_axis = tf_concat_axis_vec[0]; + + if (concat_axis < 0) { + auto ng_first_arg = node.get_ng_input(0); + concat_axis += int64_t(ng_first_arg.get_shape().size()); + } + + OutputVector ng_args; + + for (int i = 0; i < node.get_ng_input_size() - 1; i++) { + Output ng_arg = node.get_ng_input(i); + ng_args.push_back(ng_arg); + } + + return {ConstructNgNode(node.get_name(), ng_args, size_t(concat_axis))}; +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/const.cpp b/ngraph/frontend/tensorflow/src/op/const.cpp new file mode 100644 index 00000000000..c142ea1ad40 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/const.cpp @@ -0,0 +1,68 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +namespace { +using ConstMap = std::map&)>, + const ov::element::Type>>; + +const ConstMap& TF_NGRAPH_CONST_MAP() { + static const ConstMap the_map = { + {ov::element::f32, make_pair(MakeConstOp, ov::element::f32)}, + {ov::element::f64, make_pair(MakeConstOp, ov::element::f64)}, + {ov::element::i8, make_pair(MakeConstOp, ov::element::i8)}, + {ov::element::i16, make_pair(MakeConstOp, ov::element::i16)}, +#if 0 + {DataType::DT_QINT8, make_pair(MakeConstOp, ov::element::i8)}, + {DataType::DT_QUINT8, make_pair(MakeConstOp, ov::element::u8)}, + {DataType::DT_QUINT16, make_pair(MakeConstOp, ov::element::u16)}, +#endif + {ov::element::i32, make_pair(MakeConstOp, ov::element::i32)}, + {ov::element::i64, make_pair(MakeConstOp, ov::element::i64)}, + {ov::element::u8, make_pair(MakeConstOp, ov::element::u8)}, + {ov::element::u16, make_pair(MakeConstOp, ov::element::u16)}, + {ov::element::boolean, make_pair(MakeConstOp, ov::element::boolean)} + }; + return the_map; +} +} // namespace + +OutputVector TranslateConstOp(const NodeContext& node) { + auto dt = node.get_attribute("dtype"); + Output ng_node; + + // For some reason the following do not work (no specialization of + // tensorflow::checkpoint::SavedTypeTraits...) + // case DataType::DT_UINT32: + // TF_RETURN_IF_ERROR(MakeConstOp(op, element::u32, + // &ng_node)); + // break; + // case DataType::DT_UINT64: + // TF_RETURN_IF_ERROR(MakeConstOp(op, element::u64, + // &ng_node)); + // break; + try { + const auto& func_param = TF_NGRAPH_CONST_MAP().at(dt); + TF_RETURN_IF_ERROR(func_param.first(node, func_param.second, ng_node)); + } catch (const std::out_of_range&) { + throw errors::Unimplemented("Failed to translate Constant with target ngraph type:" + dt.get_type_name()); + } + + return {ng_node}; +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/conv_2d.cpp b/ngraph/frontend/tensorflow/src/op/conv_2d.cpp new file mode 100644 index 00000000000..f797bd5c096 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/conv_2d.cpp @@ -0,0 +1,76 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateConv2DOp(const NodeContext& node) { + auto ng_input = node.get_ng_input(0), ng_filter = node.get_ng_input(1); + + auto tf_strides = node.get_attribute>("strides"); + auto tf_dilations = node.get_attribute>("dilations"); + auto tf_padding_type = node.get_attribute("padding"); + auto tf_data_format = node.get_attribute("data_format"); + + if (tf_data_format != "NHWC" && tf_data_format != "NCHW") { + throw errors::InvalidArgument("Conv2D data format is neither NHWC nor NCHW"); + } + + bool is_nhwc = (tf_data_format == "NHWC"); + + // TF Kernel Test Checks + // Strides in the batch and depth dimension is not supported + if (tf_strides[0] != 1 || tf_strides[is_nhwc ? 3 : 1] != 1) { + throw errors::InvalidArgument("Strides in batch and depth dimensions is not supported: " + node.get_op_type()); + } + + Strides ng_strides(2); + Strides ng_dilations(2); + Shape ng_image_shape(2); + Shape ng_kernel_shape(2); + + NHWCtoHW(is_nhwc, tf_strides, ng_strides); + NHWCtoHW(is_nhwc, ng_input.get_shape(), ng_image_shape); + NHWCtoHW(is_nhwc, tf_dilations, ng_dilations); + NHWCtoNCHW(node.get_name(), is_nhwc, ng_input); + + auto& ng_filter_shape = ng_filter.get_shape(); + ng_kernel_shape[0] = ng_filter_shape[0]; + ng_kernel_shape[1] = ng_filter_shape[1]; + Transpose<3, 2, 0, 1>(ng_filter); + SetTracingInfo(node.get_name(), ng_filter); + + CoordinateDiff ng_padding_below; + CoordinateDiff ng_padding_above; + MakePadding(tf_padding_type, + ng_image_shape, + ng_kernel_shape, + ng_strides, + ng_dilations, + ng_padding_below, + ng_padding_above); + + Output ng_conv = ConstructNgNode(node.get_name(), + ng_input, + ng_filter, + ng_strides, + ng_padding_below, + ng_padding_above, + ng_dilations); + + NCHWtoNHWC(node.get_name(), is_nhwc, ng_conv); + return {ng_conv}; +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/conv_2d_backprop.cpp b/ngraph/frontend/tensorflow/src/op/conv_2d_backprop.cpp new file mode 100644 index 00000000000..43491428c8b --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/conv_2d_backprop.cpp @@ -0,0 +1,98 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateConv2DBackpropInputOp(const NodeContext& node) { + auto ng_filter = node.get_ng_input(1), ng_out_backprop = node.get_ng_input(2); + + // TODO: refactor me to be less redundant with other convolution ops + auto tf_strides = node.get_attribute>("strides"); + auto tf_dilations = node.get_attribute>("dilations"); + auto tf_padding_type = node.get_attribute("padding"); + auto tf_data_format = node.get_attribute("data_format"); + + if (tf_data_format != "NHWC" && tf_data_format != "NCHW") { + throw errors::InvalidArgument("Conv2DBackpropInput data format is neither NHWC nor NCHW: %s" + tf_data_format); + } + + std::vector tf_input_sizes; + GetStaticInputVector(node, 0, &tf_input_sizes); + + if (std::any_of(tf_input_sizes.begin(), tf_input_sizes.end(), [](int32_t size) { + return size <= 0; + })) { + throw errors::InvalidArgument("Conv2DBackpropInput input sizes must be positive integers"); + } + + bool is_nhwc = (tf_data_format == "NHWC"); + + Strides ng_strides(2); + Strides ng_dilations(2); + Shape ng_image_shape(2); + Shape ng_kernel_shape(2); + Shape ng_batch_shape(4); + + NHWCtoHW(is_nhwc, tf_strides, ng_strides); + NHWCtoHW(is_nhwc, tf_dilations, ng_dilations); + NHWCtoHW(is_nhwc, tf_input_sizes, ng_image_shape); + NHWCtoNCHW(node.get_name(), is_nhwc, ng_out_backprop); + if (is_nhwc) { + ng_batch_shape = {static_cast(tf_input_sizes[0]), + static_cast(tf_input_sizes[3]), + static_cast(tf_input_sizes[1]), + static_cast(tf_input_sizes[2])}; + } else { + ng_batch_shape = {static_cast(tf_input_sizes[0]), + static_cast(tf_input_sizes[1]), + static_cast(tf_input_sizes[2]), + static_cast(tf_input_sizes[3])}; + } + + auto& ng_filter_shape = ng_filter.get_shape(); + ng_kernel_shape[0] = ng_filter_shape[0]; + ng_kernel_shape[1] = ng_filter_shape[1]; + Transpose<3, 2, 0, 1>(ng_filter); + SetTracingInfo(node.get_name(), ng_filter); + + CoordinateDiff ng_padding_below; + CoordinateDiff ng_padding_above; + MakePadding(tf_padding_type, + ng_image_shape, + ng_kernel_shape, + ng_strides, + ng_dilations, + ng_padding_below, + ng_padding_above); + + auto ng_output_shape = ConstructNgNode(node.get_name(), + element::i64, + Shape{ng_batch_shape.size() - 2}, + vector(ng_batch_shape.begin() + 2, ng_batch_shape.end())); + + auto ng_data = ConstructNgNode(node.get_name(), + ng_out_backprop, + ng_filter, + ng_output_shape, + ng_strides, + ng_padding_below, + ng_padding_above, + ng_dilations); + + NCHWtoNHWC(node.get_name(), is_nhwc, ng_data); + return {ng_data}; +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/conv_3d.cpp b/ngraph/frontend/tensorflow/src/op/conv_3d.cpp new file mode 100644 index 00000000000..d40220edd4b --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/conv_3d.cpp @@ -0,0 +1,81 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +// Translate Conv3D Op +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateConv3DOp(const NodeContext& node) { + auto ng_input = node.get_ng_input(0), ng_filter = node.get_ng_input(1); + + auto tf_strides = node.get_attribute>("strides"); + auto tf_dilations = node.get_attribute>("dilations"); + auto tf_padding_type = node.get_attribute("padding"); + auto tf_data_format = node.get_attribute("data_format"); + + if (tf_data_format != "NDHWC" && tf_data_format != "NCDHW") { + throw errors::InvalidArgument("Conv3D data format is neither NDHWC nor NCDHW"); + } + + bool is_ndhwc = (tf_data_format == "NDHWC"); + + // TODO: in 3D + // TF Kernel Test Checks + // // Strides in the batch and depth dimension is not supported + // if (tf_strides[0] != 1 || tf_strides[is_nhwc ? 3 : 1] != 1) { + // return errors::InvalidArgument( + // "Strides in batch and depth dimensions is not supported: ", + // op->type_string()); + // } + + Strides ng_strides(3); + Strides ng_dilations(3); + Shape ng_image_shape(3); + Shape ng_kernel_shape(3); + + NHWCtoHW(is_ndhwc, tf_strides, ng_strides); + NHWCtoHW(is_ndhwc, ng_input.get_shape(), ng_image_shape); + NHWCtoHW(is_ndhwc, tf_dilations, ng_dilations); + NHWCtoNCHW(node.get_name(), is_ndhwc, ng_input); + + auto& ng_filter_shape = ng_filter.get_shape(); + ng_kernel_shape[0] = ng_filter_shape[0]; + ng_kernel_shape[1] = ng_filter_shape[1]; + ng_kernel_shape[2] = ng_filter_shape[2]; + Transpose3D<4, 3, 0, 1, 2>(ng_filter); + SetTracingInfo(node.get_name(), ng_filter); + + CoordinateDiff ng_padding_below; + CoordinateDiff ng_padding_above; + MakePadding(tf_padding_type, + ng_image_shape, + ng_kernel_shape, + ng_strides, + ng_dilations, + ng_padding_below, + ng_padding_above); + + Output ng_conv = ConstructNgNode(node.get_name(), + ng_input, + ng_filter, + ng_strides, + ng_padding_below, + ng_padding_above, + ng_dilations); + + NCHWtoNHWC(node.get_name(), is_ndhwc, ng_conv); + return {ng_conv}; +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/cumsum.cpp b/ngraph/frontend/tensorflow/src/op/cumsum.cpp new file mode 100644 index 00000000000..fef02fcbd09 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/cumsum.cpp @@ -0,0 +1,25 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateCumsumOp(const NodeContext& node) { + auto ng_x = node.get_ng_input(0), ng_axis = node.get_ng_input(1); + auto exclusive = node.get_attribute("exclusive"), reverse = node.get_attribute("reverse"); + + return {ConstructNgNode(node.get_name(), ng_x, ng_axis, exclusive, reverse)}; +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/depth_to_space.cpp b/ngraph/frontend/tensorflow/src/op/depth_to_space.cpp new file mode 100644 index 00000000000..f9703ca31af --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/depth_to_space.cpp @@ -0,0 +1,39 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +// Translate DepthToSpace op +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateDepthToSpaceOp(const NodeContext& node) { + Output ng_input = node.get_ng_input(0); + + // Get the attributes + auto block_size = node.get_attribute("block_size"); + std::string tf_data_format = node.get_attribute("data_format"); + + if (tf_data_format != "NHWC" && tf_data_format != "NCHW") { + throw errors::InvalidArgument("DepthToSpace data format is neither NHWC nor NCHW"); + } + + bool is_nhwc = (tf_data_format == "NHWC"); + + NHWCtoNCHW(node.get_name(), is_nhwc, ng_input); + auto ng_mode = DepthToSpace::DepthToSpaceMode::BLOCKS_FIRST; + Output depth_to_space = ConstructNgNode(node.get_name(), ng_input, ng_mode, block_size); + NCHWtoNHWC(node.get_name(), is_nhwc, depth_to_space); + return {depth_to_space}; +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/depthwise_conv_2d.cpp b/ngraph/frontend/tensorflow/src/op/depthwise_conv_2d.cpp new file mode 100644 index 00000000000..4e1561c8242 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/depthwise_conv_2d.cpp @@ -0,0 +1,80 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateDepthwiseConv2dNativeOp(const NodeContext& node) { + auto ng_input = node.get_ng_input(0), ng_filter = node.get_ng_input(1); + + auto tf_strides = node.get_attribute>("strides"); + auto tf_dilations = node.get_attribute>("dilations"); + auto tf_padding_type = node.get_attribute("padding"); + auto tf_data_format = node.get_attribute("data_format"); + + if (tf_data_format != "NHWC" && tf_data_format != "NCHW") { + throw errors::InvalidArgument("DepthwiseConv2D data format is neither NHWC nor NCHW"); + } + + bool is_nhwc = (tf_data_format == "NHWC"); + + Strides ng_strides(2); + Strides ng_dilations(2); + Shape ng_image_shape(2); + Shape ng_kernel_shape(2); + + NHWCtoHW(is_nhwc, ng_input.get_shape(), ng_image_shape); + NHWCtoHW(is_nhwc, tf_strides, ng_strides); + NHWCtoHW(is_nhwc, tf_dilations, ng_dilations); + NHWCtoNCHW(node.get_name(), is_nhwc, ng_input); + + auto& ng_filter_shape = ng_filter.get_shape(); + ng_kernel_shape[0] = ng_filter_shape[0]; + ng_kernel_shape[1] = ng_filter_shape[1]; + + CoordinateDiff ng_padding_below; + CoordinateDiff ng_padding_above; + MakePadding(tf_padding_type, + ng_image_shape, + ng_kernel_shape, + ng_strides, + ng_dilations, + ng_padding_below, + ng_padding_above); + + // H W I M -> H W I 1 M + auto filter_shape = ConstructNgNode( + node.get_name(), + element::u64, + Shape{5}, + ov::Shape{ng_filter_shape[0], ng_filter_shape[1], ng_filter_shape[2], 1, ng_filter_shape[3]}); + auto reshaped_filter = ConstructNgNode(node.get_name(), ng_filter, filter_shape, false); + + // H W I 1 M -> I M 1 H W + auto order = ConstructNgNode(node.get_name(), element::i64, Shape{5}, vector{2, 4, 3, 0, 1}); + auto transposed_filter = ConstructNgNode(node.get_name(), reshaped_filter, order); + + auto ng_conv = ConstructNgNode(node.get_name(), + ng_input, + transposed_filter, + ng_strides, + ng_padding_below, + ng_padding_above, + ng_dilations); + + NCHWtoNHWC(node.get_name(), is_nhwc, ng_conv); + return {ng_conv}; +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/elu.cpp b/ngraph/frontend/tensorflow/src/op/elu.cpp new file mode 100644 index 00000000000..237768ba130 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/elu.cpp @@ -0,0 +1,27 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +#include "node_context.hpp" + +using namespace std; +using namespace ov; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateEluOp(const NodeContext& node) { + auto input = node.get_ng_input(0); + auto alpha = 1.0; // node.get_attribute("alpha"); + return {ConstructNgNode(node.get_name(), input, alpha)}; +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/expand_dims.cpp b/ngraph/frontend/tensorflow/src/op/expand_dims.cpp new file mode 100644 index 00000000000..600ab95c613 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/expand_dims.cpp @@ -0,0 +1,26 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateExpandDimsOp(const NodeContext& node) { + auto ng_input = node.get_ng_input(0); + std::vector dims; + GetStaticInputVector(node, 1, &dims); + auto ng_dims = ConstructNgNode(node.get_name(), element::i64, ov::Shape{dims.size()}, dims); + return {ConstructNgNode(node.get_name(), ng_input, ng_dims)}; +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/fill.cpp b/ngraph/frontend/tensorflow/src/op/fill.cpp new file mode 100644 index 00000000000..24c4e54ce46 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/fill.cpp @@ -0,0 +1,23 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateFillOp(const NodeContext& node) { + auto ng_dims = node.get_ng_input(0), ng_value = node.get_ng_input(1); + return {ConstructNgNode(node.get_name(), ng_value, ng_dims)}; +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/fused_batch_norm.cpp b/ngraph/frontend/tensorflow/src/op/fused_batch_norm.cpp new file mode 100644 index 00000000000..92a58c429c2 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/fused_batch_norm.cpp @@ -0,0 +1,57 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateFusedBatchNormOp(const NodeContext& node) { + auto ng_input = node.get_ng_input(0), ng_scale = node.get_ng_input(1), ng_offset = node.get_ng_input(2), + ng_mean = node.get_ng_input(3), ng_variance = node.get_ng_input(4); + bool is_v3 = node.get_op_type() == "FusedBatchNormV3"; + + auto tf_data_format = node.get_attribute("data_format"); + + if (tf_data_format != "NHWC" && tf_data_format != "NCHW") { + throw errors::InvalidArgument("Conv2D data format is neither NHWC nor NCHW"); + } + + bool is_nhwc = (tf_data_format == "NHWC"); + + NGRAPH_VLOG(3) << "data_format: " << tf_data_format; + + auto tf_epsilon = node.get_attribute("epsilon", 0.0001); // TODO: where does 0.0001 come from? + + NGRAPH_VLOG(3) << "epsilon: " << tf_epsilon; + + NHWCtoNCHW(node.get_name(), is_nhwc, ng_input); + + auto ng_batch_norm = ConstructNgNode(node.get_name(), + ng_input, + ng_scale, + ng_offset, + ng_mean, + ng_variance, + tf_epsilon); + NCHWtoNHWC(node.get_name(), is_nhwc, ng_batch_norm); + + // TODO: Why are there so many? Is it correct? + OutputVector result = {ng_batch_norm, ng_mean, ng_variance, ng_mean, ng_variance}; + if (is_v3) { + // FusedBatchNormV3 has 6 outputs + result.push_back(ng_mean); // reserve_space_3 + } + return result; +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/fused_conv_2d.cpp b/ngraph/frontend/tensorflow/src/op/fused_conv_2d.cpp new file mode 100644 index 00000000000..3b8ba2d31ab --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/fused_conv_2d.cpp @@ -0,0 +1,148 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateFusedConv2DOp(const NodeContext& node) { + auto num_args = node.get_attribute("num_args"); + auto fused_ops = node.get_attribute>("fused_ops"); + + auto tf_data_format = node.get_attribute("data_format"); + bool is_nhwc = (tf_data_format == "NHWC"); + + auto CreateNgConv = [&](Output& ng_input, Output& ng_filter) { + auto tf_strides = node.get_attribute>("strides"); + auto tf_dilations = node.get_attribute>("dilations"); + auto tf_padding_type = node.get_attribute("padding"); + + if (tf_data_format != "NHWC" && tf_data_format != "NCHW") { + throw errors::InvalidArgument("Conv2D data format is neither NHWC nor NCHW"); + } + + // TF Kernel Test Checks + // Strides in the batch and depth dimension is not supported + if (tf_strides[0] != 1 || tf_strides[is_nhwc ? 3 : 1] != 1) { + throw errors::InvalidArgument("Strides in batch and depth dimensions is not supported: " + + node.get_op_type()); + } + + Strides ng_strides(2); + Strides ng_dilations(2); + Shape ng_image_shape(2); + Shape ng_kernel_shape(2); + + NHWCtoHW(is_nhwc, tf_strides, ng_strides); + NHWCtoHW(is_nhwc, ng_input.get_shape(), ng_image_shape); + NHWCtoHW(is_nhwc, tf_dilations, ng_dilations); + NHWCtoNCHW(node.get_name(), is_nhwc, ng_input); + + auto& ng_filter_shape = ng_filter.get_shape(); + ng_kernel_shape[0] = ng_filter_shape[0]; + ng_kernel_shape[1] = ng_filter_shape[1]; + Transpose<3, 2, 0, 1>(ng_filter); + SetTracingInfo(node.get_name(), ng_filter); + + CoordinateDiff ng_padding_below; + CoordinateDiff ng_padding_above; + MakePadding(tf_padding_type, + ng_image_shape, + ng_kernel_shape, + ng_strides, + ng_dilations, + ng_padding_below, + ng_padding_above); + + return ConstructNgNode(node.get_name() + "_FusedConv2D_Conv", + ng_input, + ng_filter, + ng_strides, + ng_padding_below, + ng_padding_above, + ng_dilations); + }; + + if (VecStrCmp(fused_ops, {"BiasAdd"}) || VecStrCmp(fused_ops, {"BiasAdd", "Relu"}) || + VecStrCmp(fused_ops, {"BiasAdd", "Relu6"})) { + if (num_args != 1) { + throw errors::InvalidArgument("FusedConv2DBiasAdd has incompatible num_args"); + } + + auto ng_input = node.get_ng_input(0), ng_filter = node.get_ng_input(1), ng_bias = node.get_ng_input(2), + ng_conv = CreateNgConv(ng_input, ng_filter); + + auto ng_conv_shape = ng_conv.get_shape(); + auto ng_bias_shape = ng_bias.get_shape(); + if (ng_bias_shape.size() != 1) { + throw errors::InvalidArgument("Bias argument to BiasAdd does not have one dimension"); + } + + std::vector reshape_pattern_values(ng_conv_shape.size(), 1U); + reshape_pattern_values[1] = ng_bias.get_shape().front(); + auto reshape_pattern = + make_shared(element::u64, Shape{reshape_pattern_values.size()}, reshape_pattern_values); + auto ng_bias_reshaped = ConstructNgNode(node.get_name(), ng_bias, reshape_pattern, false); + + auto ng_add = ConstructNgNode(node.get_name() + "_FusedConv2D_BiasAdd", ng_conv, ng_bias_reshaped); + + if (VecStrCmp(fused_ops, {"BiasAdd", "Relu"})) { + auto ng_relu = ConstructNgNode(node.get_name() + "_FusedConv2D_Relu", ng_add); + NCHWtoNHWC(node.get_name(), is_nhwc, ng_relu); + return {ng_relu}; + } else if (VecStrCmp(fused_ops, {"BiasAdd", "Relu6"})) { + auto ng_relu6 = ConstructNgNode(node.get_name() + "_FusedConv2D_Relu6", ng_add, 0, 6); + NCHWtoNHWC(node.get_name(), is_nhwc, ng_relu6); + return {ng_relu6}; + } else { + NCHWtoNHWC(node.get_name(), is_nhwc, ng_add); + return {ng_add}; + } + } else if (VecStrCmp(fused_ops, {"FusedBatchNorm"}) || VecStrCmp(fused_ops, {"FusedBatchNorm", "Relu"}) || + VecStrCmp(fused_ops, {"FusedBatchNorm", "Relu6"})) { + if (num_args != 4) { + throw errors::InvalidArgument("FusedConv2D with FusedBatchNorm has incompatible num_args"); + } + + auto ng_input = node.get_ng_input(0), ng_filter = node.get_ng_input(1), ng_scale = node.get_ng_input(2), + ng_offset = node.get_ng_input(3), ng_mean = node.get_ng_input(4), ng_variance = node.get_ng_input(5), + ng_conv = CreateNgConv(ng_input, ng_filter); + + auto tf_epsilon = node.get_attribute("epsilon"); + + auto ng_batch_norm = ConstructNgNode(node.get_name() + "_FusedConv2D_BatchNorm", + ng_conv, + ng_scale, + ng_offset, + ng_mean, + ng_variance, + tf_epsilon); + + if (VecStrCmp(fused_ops, {"FusedBatchNorm", "Relu"})) { + auto ng_relu = ConstructNgNode(node.get_name() + "_FusedConv2D_BatchNormRelu", ng_batch_norm); + NCHWtoNHWC(node.get_name(), is_nhwc, ng_relu); + return {ng_relu}; + } else if (VecStrCmp(fused_ops, {"FusedBatchNorm", "Relu6"})) { + auto ng_relu6 = ConstructNgNode(node.get_name() + "_FusedConv2D_BatchNormRelu", ng_batch_norm, 0, 6); + NCHWtoNHWC(node.get_name(), is_nhwc, ng_relu6); + return {ng_relu6}; + } else { + NCHWtoNHWC(node.get_name(), is_nhwc, ng_batch_norm); + return {ng_batch_norm}; + } + } else { + FRONT_END_THROW("Unsupported _FusedConv2D "); + } +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/fused_mat_mul.cpp b/ngraph/frontend/tensorflow/src/op/fused_mat_mul.cpp new file mode 100644 index 00000000000..b7f9884c1b8 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/fused_mat_mul.cpp @@ -0,0 +1,54 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateFusedMatMulOp(const NodeContext& node) { + // auto num_args = node.get_attribute("num_args"); // TODO: it is unused but why? + auto fused_ops = node.get_attribute>("fused_ops"); + + // Transpose arguments if requested. + auto transpose_a = node.get_attribute("transpose_a", false); + auto transpose_b = node.get_attribute("transpose_b", false); + + auto ng_lhs = node.get_ng_input(0), ng_rhs = node.get_ng_input(1), ng_bias = node.get_ng_input(2); + + Output ng_matmul = ConstructNgNode(node.get_name(), ng_lhs, ng_rhs, transpose_a, transpose_b); + + auto ng_matmul_shape = ng_matmul.get_shape(); + auto ng_bias_shape = ng_bias.get_shape(); + + if (ng_bias_shape.size() != 1) { + throw errors::InvalidArgument("Bias argument to BiasAdd does not have one dimension"); + } + + auto ng_add = ConstructNgNode(node.get_name(), ng_matmul, ng_bias); + if (fused_ops.size() == 1) { // Only fusing BiasAdd + return {ng_add}; + } else if (fused_ops.size() == 2) { // Also has activation + if (fused_ops[1] == "Relu") { + return {ConstructNgNode(node.get_name(), ng_add)}; + } else if (fused_ops[1] == "Relu6") { + return {ConstructNgNode(node.get_name(), ng_add, 0, 6)}; + } else { + throw errors::Internal("Expected activation to be Relu or Relu6 but got " + fused_ops[1]); + } + } else { + // Adding this here to catch future changes in _FusedMatMul + throw errors::Internal("Unsupported combination"); + } +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/gather.cpp b/ngraph/frontend/tensorflow/src/op/gather.cpp new file mode 100644 index 00000000000..64996d8028f --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/gather.cpp @@ -0,0 +1,66 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +// See .../tensorflow/include/tensorflow/cc/ops/array_ops.h +// and .../openvino/ngraph/core/include/ngraph/op/gather.hpp +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateGatherOp(const NodeContext& node) { + auto ng_input = node.get_ng_input(0), ng_input_indices = node.get_ng_input(1); + + auto ng_axis = ConstructNgNode(node.get_name(), element::i64, Shape{}, 0); + + auto gather_op = ConstructNgNode(node.get_name(), ng_input, ng_input_indices, ng_axis); + + return {gather_op}; +} + +OutputVector TranslateGatherV2Op(const NodeContext& node) { + auto ng_input = node.get_ng_input(0), ng_input_coords = node.get_ng_input(1); + + std::vector tf_axis; + GetStaticInputVector(node, 2, &tf_axis); + + if (tf_axis.size() > 1) { + std::ostringstream buf; + buf << "Found axis in GatherV2 op (" << node.get_name() << ") translation to be non scalar, of size " + << tf_axis.size(); + throw errors::Internal(buf.str()); + } + + // Negative axis is supported. Accounting for that + auto ng_input_shape = ng_input.get_shape(); + size_t ng_input_rank = ng_input_shape.size(); + int axis; + if (tf_axis[0] >= 0) { + axis = tf_axis[0]; + } else { + axis = tf_axis[0] + ng_input_rank; + } + if (axis < 0 || axis >= ng_input_rank) { + std: + ostringstream buf; + buf << "Expected axis in the range [-" << ng_input_rank << ", " << ng_input_rank << "), but got " << tf_axis[0]; + throw errors::InvalidArgument(buf.str()); + } + + auto ng_axis = ConstructNgNode(node.get_name(), element::i64, Shape{tf_axis.size()}, tf_axis); + + auto gather_op = ConstructNgNode(node.get_name(), ng_input, ng_input_coords, ng_axis); + + return {gather_op}; +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/identity.cpp b/ngraph/frontend/tensorflow/src/op/identity.cpp new file mode 100644 index 00000000000..8509a4c658e --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/identity.cpp @@ -0,0 +1,23 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateIdentityOp(const NodeContext& node) { + return {node.get_ng_input(0)}; +} + +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/log_softmax.cpp b/ngraph/frontend/tensorflow/src/op/log_softmax.cpp new file mode 100644 index 00000000000..2d725d09d1b --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/log_softmax.cpp @@ -0,0 +1,27 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateLogSoftmaxOp(const NodeContext& node) { + auto ng_inp = node.get_ng_input(0); + auto inp_shape = ng_inp.get_shape(); + size_t rank = inp_shape.size(); + int64_t axes = rank - 1; + + return {ConstructNgNode(node.get_name(), ng_inp, axes)}; +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/max_pool.cpp b/ngraph/frontend/tensorflow/src/op/max_pool.cpp new file mode 100644 index 00000000000..8ecb356767d --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/max_pool.cpp @@ -0,0 +1,71 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov; +using namespace ov::frontend::tf::detail; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateMaxPoolOp(const NodeContext& node) { + auto ng_input = node.get_ng_input(0); + + auto tf_strides = node.get_attribute>("strides"); + auto tf_ksize = node.get_attribute>("ksize"); + auto tf_padding_type = node.get_attribute("padding"); + auto tf_data_format = node.get_attribute("data_format"); + + bool is_nhwc = (tf_data_format == "NHWC") || (tf_data_format == "NDHWC"); + + int N = 2; + if (node.get_name() == "MaxPool3D") { + N = 3; + } + Strides ng_strides(N); + Shape ng_image_shape(N); + Shape ng_kernel_shape(N); + Shape ng_dilations(N, 1); + + NHWCtoHW(is_nhwc, tf_strides, ng_strides); + NHWCtoHW(is_nhwc, ng_input.get_shape(), ng_image_shape); + NHWCtoHW(is_nhwc, tf_ksize, ng_kernel_shape); + NHWCtoNCHW(node.get_name(), is_nhwc, ng_input); + + CoordinateDiff padding_below; + CoordinateDiff padding_above; + MakePadding(tf_padding_type, + ng_image_shape, + ng_kernel_shape, + ng_strides, + ng_dilations, + padding_below, + padding_above); + + // TODO: remove this once nGraph supports negative padding + // (CoordinateDiff) for MaxPool + Shape ng_padding_below(padding_below.begin(), padding_below.end()); + Shape ng_padding_above(padding_above.begin(), padding_above.end()); + + auto ng_maxpool = ConstructNgNode(node.get_name(), + ng_input, + ng_strides, + ng_padding_below, + ng_padding_above, + ng_kernel_shape, + ov::op::RoundingType::FLOOR); + + NCHWtoNHWC(node.get_name(), is_nhwc, ng_maxpool); + return {ng_maxpool}; +} + +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/no_op.cpp b/ngraph/frontend/tensorflow/src/op/no_op.cpp new file mode 100644 index 00000000000..57f09a73410 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/no_op.cpp @@ -0,0 +1,28 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector NoOp(const NodeContext& node) { + if (node.get_ng_input_size() == 0) { + return OutputVector{}; + } + if (node.get_ng_input_size() != 1) { + throw errors::InvalidArgument("NoOp has " + to_string(node.get_ng_input_size()) + " inputs, should have 1"); + } + return OutputVector{node.get_ng_input(0)}; +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/one_hot.cpp b/ngraph/frontend/tensorflow/src/op/one_hot.cpp new file mode 100644 index 00000000000..f7c4a1520a2 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/one_hot.cpp @@ -0,0 +1,30 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateOneHotOp(const NodeContext& node) { + auto ng_features = node.get_ng_input(0); + auto ng_depth = node.get_ng_input(1); + auto ng_on = node.get_ng_input(2); + auto ng_off = node.get_ng_input(3); + + auto one_hot_axis = node.get_attribute("axis"); + auto ng_onehot = make_shared(ng_features, ng_depth, ng_on, ng_off, one_hot_axis); + ng_onehot->set_friendly_name(ng_onehot->get_friendly_name()); + return ng_onehot->outputs(); +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/pad.cpp b/ngraph/frontend/tensorflow/src/op/pad.cpp new file mode 100644 index 00000000000..dedbb3a03fa --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/pad.cpp @@ -0,0 +1,73 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +// 3 different Pad Ops: Pad, PadV2, MirrorPad +// See https://www.tensorflow.org/api_docs/cc/class/tensorflow/ops/pad +// See https://www.tensorflow.org/api_docs/cc/class/tensorflow/ops/pad-v2 +// See https://www.tensorflow.org/api_docs/cc/class/tensorflow/ops/mirror-pad +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslatePadOp(const NodeContext& node) { + auto ng_input = node.get_ng_input(0), ng_paddings_op = node.get_ng_input(1); + Output pad_val_op; + + // Set inputs and pad_val_op + auto op_type = node.get_op_type(); + if (op_type == "Pad" || op_type == "MirrorPad") { + pad_val_op = + ConstructNgNode(node.get_name(), ng_input.get_element_type(), Shape(), std::vector({0})); + } else if (op_type == "PadV2") { + pad_val_op = node.get_ng_input(2); + } else { + throw errors::InvalidArgument("Incorrect TF Pad OpType: " + node.get_op_type()); + } + + // Set pad_mode + auto pad_mode = ov::op::PadMode::CONSTANT; + if (op_type == "MirrorPad") { + auto pad_mode_str = node.get_attribute("mode"); + if (pad_mode_str == "REFLECT") { + pad_mode = ov::op::PadMode::REFLECT; + } else if (pad_mode_str == "SYMMETRIC") { + pad_mode = ov::op::PadMode::SYMMETRIC; + } else { + throw errors::InvalidArgument(pad_mode_str + " is not an allowed padding mode."); + } + } + + // Set pads_begin & pads_end (from the pad_val_op) + std::vector paddings; + GetStaticInputVector(node, 1, &paddings); + if (paddings.size() % 2 != 0) { + throw errors::InvalidArgument("Constant node for paddings does not have an even number of " + "elements"); + } + std::vector pad_begin(paddings.size() / 2); + std::vector pad_end(paddings.size() / 2); + for (size_t i = 0; i < paddings.size() / 2; i++) { + pad_begin[i] = paddings[2 * i]; + pad_end[i] = paddings[2 * i + 1]; + } + auto pads_begin_node = ConstructNgNode(node.get_name(), element::i64, Shape{pad_begin.size()}, pad_begin); + auto pads_end_node = ConstructNgNode(node.get_name(), element::i64, Shape{pad_end.size()}, pad_end); + + // Create final Op + auto result_pad_op = + ConstructNgNode(node.get_name(), ng_input, pads_begin_node, pads_end_node, pad_val_op, pad_mode); + + return {result_pad_op}; +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/placeholder.cpp b/ngraph/frontend/tensorflow/src/op/placeholder.cpp new file mode 100644 index 00000000000..c8b2f7f8712 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/placeholder.cpp @@ -0,0 +1,24 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector PlaceholderOp(const NodeContext& node) { + auto ng_et = node.get_attribute("dtype"); + auto ng_shape = node.get_attribute("shape", ov::PartialShape()); + return {ConstructNgNode(node.get_name(), ng_et, ng_shape)}; +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/random_uniform.cpp b/ngraph/frontend/tensorflow/src/op/random_uniform.cpp new file mode 100644 index 00000000000..2087575e832 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/random_uniform.cpp @@ -0,0 +1,29 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { +ov::OutputVector TranslateRandomUniformOp(const NodeContext& node) { + auto data = node.get_ng_input(0); + auto seed = node.get_attribute("seed"); + auto seed2 = node.get_attribute("seed2"); + auto minval_const = make_shared(element::f32, Shape{}, 0); + auto maxval_const = make_shared(element::f32, Shape{}, 1); + auto ng_et = node.get_attribute("dtype"); + auto random_uniform = std::make_shared(data, minval_const, maxval_const, ng_et, seed, seed2); + random_uniform->set_friendly_name(node.get_name()); + return random_uniform->outputs(); +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/reduce.cpp b/ngraph/frontend/tensorflow/src/op/reduce.cpp new file mode 100644 index 00000000000..53973005574 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/reduce.cpp @@ -0,0 +1,67 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateReduceOp(const NodeContext& node, + std::function(Output, Output, const bool)> create_ng_node) { + Output ng_input = node.get_ng_input(0); + auto tf_keep_dims = node.get_attribute("keep_dims", false); + + std::vector axes; + GetStaticInputVector(node, 1, &axes); + + Shape input_shape = ng_input.get_shape(); + size_t input_rank = input_shape.size(); + + TF_RETURN_IF_ERROR(CheckAxisDimInRange(axes, input_rank)); + + std::vector ng_reduction_axes_vect(axes.size()); + std::transform(axes.begin(), axes.end(), ng_reduction_axes_vect.begin(), [input_rank](int idx) { + return idx + (idx < 0 ? (int)input_rank : 0); + }); + auto ng_reduction_axes = ConstructNgNode(node.get_name(), + element::i64, + Shape{ng_reduction_axes_vect.size()}, + ng_reduction_axes_vect); + + Output ng_node = create_ng_node(ng_input, ng_reduction_axes, tf_keep_dims); + + return {ng_node}; +} + +template +OutputVector TranslateDirectReduceOp(const NodeContext& node) { + // ensure its either an arithmetic or a logical reduction + if (!(std::is_base_of::value || + std::is_base_of::value)) { + throw errors::InvalidArgument("Expected node to be either a valid logical or arithmetic reduction " + "type"); + } + return TranslateReduceOp(node, + [&node](Output ng_input, Output ng_reduction_axes, const bool keep_dims) { + return ConstructNgNode(node.get_name(), ng_input, ng_reduction_axes, keep_dims); + }); +} + +template OutputVector TranslateDirectReduceOp(const NodeContext& node); +template OutputVector TranslateDirectReduceOp(const NodeContext& node); +template OutputVector TranslateDirectReduceOp(const NodeContext& node); +template OutputVector TranslateDirectReduceOp(const NodeContext& node); +template OutputVector TranslateDirectReduceOp(const NodeContext& node); +template OutputVector TranslateDirectReduceOp(const NodeContext& node); +template OutputVector TranslateDirectReduceOp(const NodeContext& node); +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/relu_6.cpp b/ngraph/frontend/tensorflow/src/op/relu_6.cpp new file mode 100644 index 00000000000..4f9a1627213 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/relu_6.cpp @@ -0,0 +1,24 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { +ov::OutputVector TranslateRelu6Op(const NodeContext& node) { + auto data = node.get_ng_input(0); + auto clamp = std::make_shared(data, 0.0, 6.0f); + clamp->set_friendly_name(node.get_name()); + return clamp->outputs(); +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/retval.cpp b/ngraph/frontend/tensorflow/src/op/retval.cpp new file mode 100644 index 00000000000..5adca5a0ea7 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/retval.cpp @@ -0,0 +1,30 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector RetvalOp(const NodeContext& node) { + // Make sure that this _Retval only has one input node. + if (node.get_ng_input_size() != 1) { + throw errors::InvalidArgument("_Retval has " + to_string(node.get_ng_input_size()) + " inputs, should have 1"); + } + + // auto ret_val_index = node.get_attribute("index"); + // TODO: Put ret_val_index to RT info that should be later utilized to order outpus by indices + + return {ConstructNgNode(node.get_name(), node.get_ng_input(0))}; +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/roll.cpp b/ngraph/frontend/tensorflow/src/op/roll.cpp new file mode 100644 index 00000000000..0676b205f87 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/roll.cpp @@ -0,0 +1,28 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov; +using namespace ov::opset8; +using namespace ov::frontend::tf; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { +ov::OutputVector TranslateRollOp(const NodeContext& node) { + auto data = node.get_ng_input(0); + auto shift = node.get_ng_input(1); + auto axis = node.get_ng_input(2); + auto roll = std::make_shared(data, shift, axis); + roll->set_friendly_name(node.get_name()); + return roll->outputs(); +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/rsqrt.cpp b/ngraph/frontend/tensorflow/src/op/rsqrt.cpp new file mode 100644 index 00000000000..a9554d46231 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/rsqrt.cpp @@ -0,0 +1,26 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateRsqrtOp(const NodeContext& node) { + auto input = node.get_ng_input(0); + auto ng_exponent = ConstructNgNode(node.get_name(), input.get_element_type(), Shape{1}, -0.5f); + auto power = make_shared(input, ng_exponent); + power->set_friendly_name(node.get_name()); + return power->outputs(); +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/select.cpp b/ngraph/frontend/tensorflow/src/op/select.cpp new file mode 100644 index 00000000000..51fb1bd4435 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/select.cpp @@ -0,0 +1,39 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { +OutputVector TranslateSelectOp(const NodeContext& node) { + TF_OP_VALIDATION_CHECK(node, node.get_all_ng_inputs().size() == 3, "Select op cannot be converted"); + auto in_1 = node.get_ng_input(0); + auto in_2 = node.get_ng_input(1); + auto in_3 = node.get_ng_input(2); + if (in_1.get_partial_shape().is_static() && in_2.get_partial_shape().is_static()) { + // select broadcast + if (in_1.get_shape().size() == 1 && in_2.get_shape().size() > 1) { + std::vector axes(in_2.get_shape().size() - 1); + std::iota(axes.begin(), axes.end(), 1); + auto unsqueeze_axes = make_shared(ov::element::i64, Shape{in_2.get_shape().size() - 1}, axes); + auto unsqueeze = make_shared(in_1, unsqueeze_axes); + auto ng_select = make_shared(in_1, in_2, in_3); + ng_select->set_friendly_name(node.get_name()); + return ng_select->outputs(); +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/softmax.cpp b/ngraph/frontend/tensorflow/src/op/softmax.cpp new file mode 100644 index 00000000000..31647e549af --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/softmax.cpp @@ -0,0 +1,30 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { +OutputVector TranslateSoftmaxOp(const NodeContext& node) { + auto ng_inp = node.get_ng_input(0); + auto inp_shape = ng_inp.get_shape(); + size_t rank = inp_shape.size(); + int64_t axes = rank - 1; + if (rank < 1) { + throw errors::InvalidArgument("TF Softmax logits must be >=1 dimension"); + } + + return {ConstructNgNode(node.get_name(), ng_inp, axes)}; +} + +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/sqrt.cpp b/ngraph/frontend/tensorflow/src/op/sqrt.cpp new file mode 100644 index 00000000000..ffa9dec52ad --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/sqrt.cpp @@ -0,0 +1,26 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateSqrtOp(const NodeContext& node) { + auto input = node.get_ng_input(0); + auto ng_exponent = ConstructNgNode(node.get_name(), input.get_element_type(), Shape{1}, 0.5f); + auto power = make_shared(input, ng_exponent); + power->set_friendly_name(node.get_name()); + return power->outputs(); +} +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/squeeze.cpp b/ngraph/frontend/tensorflow/src/op/squeeze.cpp new file mode 100644 index 00000000000..70cc8201e03 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/squeeze.cpp @@ -0,0 +1,35 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +OutputVector TranslateSqueezeOp(const NodeContext& node) { + Output ng_input = node.get_ng_input(0); + size_t input_dims = ng_input.get_shape().size(); + + auto tf_axis = node.get_attribute>("squeeze_dims"); + + // If input dimension is negative, make it positive + for (size_t i = 0; i < tf_axis.size(); i++) { + tf_axis[i] = tf_axis[i] < 0 ? (int32_t)(input_dims) + tf_axis[i] : tf_axis[i]; + } + + auto ng_const = ConstructNgNode(node.get_name(), element::i32, Shape{tf_axis.size()}, tf_axis); + + return {ConstructNgNode(node.get_name(), ng_input, ng_const)}; +} + +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op/unary_op.cpp b/ngraph/frontend/tensorflow/src/op/unary_op.cpp new file mode 100644 index 00000000000..028be04342f --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op/unary_op.cpp @@ -0,0 +1,94 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +using namespace std; +using namespace ov::opset8; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { + +// Helper function to translate a unary op. +// +// Parameters: +// +// TFNodeDecoder* op - TF op being translated. Must have one input. +// const std::vector& static_input_map +// - the static input map +// Builder::OpMap& ng_op_map - The TF-to-nGraph op map. +// +// std::function(Output> +// create_unary_op - Function to construct the graph implementing +// the unary op, given the input to the unop +// as an argument. +// +// Example usage: +// +// if (n->type_string == "Square") { +// TF_RETURN_IF_ERROR(TranslateUnaryOp(n, static_input_map, ng_op_map, +// [] (Output n) { +// return +// (Output(n,n)); +// }); +// } +OutputVector TranslateUnaryOp(const NodeContext& op, std::function(Output)> create_unary_op) { + Output ng_input = op.get_ng_input(0); + auto ng_node = create_unary_op(ng_input); + if (ng_node != ng_input) { + SetTracingInfo(op.get_name(), ng_node); + } + // SaveNgOp(ng_op_map, node.get_name(), ng_node); + // return Status::OK(); + return {ng_node}; +} + +// Helper function to translate a unary op in cases where there is a one-to-one +// mapping from TensorFlow ops to nGraph ops. +// +// Example usage: +// +// if (n->type_string == "Abs") { +// TF_RETURN_IF_ERROR(TranslateUnaryOp(n, static_input_map, +// ng_op_map)); +// } +// +template +OutputVector TranslateUnaryOp(const NodeContext& node) { + return TranslateUnaryOp(node, [&node](Output n) { + return ConstructNgNode(node.get_name(), n); + }); +} + +template OutputVector TranslateUnaryOp(const NodeContext& node); +template OutputVector TranslateUnaryOp(const NodeContext& node); +template OutputVector TranslateUnaryOp(const NodeContext& node); +template OutputVector TranslateUnaryOp(const NodeContext& node); +template OutputVector TranslateUnaryOp(const NodeContext& node); +template OutputVector TranslateUnaryOp(const NodeContext& node); +template OutputVector TranslateUnaryOp(const NodeContext& node); +template OutputVector TranslateUnaryOp(const NodeContext& node); +template OutputVector TranslateUnaryOp(const NodeContext& node); +template OutputVector TranslateUnaryOp(const NodeContext& node); +template OutputVector TranslateUnaryOp(const NodeContext& node); +template OutputVector TranslateUnaryOp(const NodeContext& node); +template OutputVector TranslateUnaryOp(const NodeContext& node); +template OutputVector TranslateUnaryOp(const NodeContext& node); +template OutputVector TranslateUnaryOp(const NodeContext& node); +template OutputVector TranslateUnaryOp(const NodeContext& node); +template OutputVector TranslateUnaryOp(const NodeContext& node); +template OutputVector TranslateUnaryOp(const NodeContext& node); +template OutputVector TranslateUnaryOp(const NodeContext& node); +template OutputVector TranslateUnaryOp(const NodeContext& node); +template OutputVector TranslateUnaryOp(const NodeContext& node); +template OutputVector TranslateUnaryOp(const NodeContext& node); +template OutputVector TranslateUnaryOp(const NodeContext& node); + +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/op_table.cpp b/ngraph/frontend/tensorflow/src/op_table.cpp new file mode 100644 index 00000000000..dc07e7c5731 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op_table.cpp @@ -0,0 +1,228 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "op_table.hpp" + +using namespace std; +using namespace ov; +using namespace ov::frontend::tf; + +namespace ov { +namespace frontend { +namespace tf { +namespace op { +#define OP_CONVERTER(op) ov::OutputVector op(const NodeContext& node) +#define OP_T_CONVERTER(op) \ + template \ + ov::OutputVector op(const NodeContext& node) + +OP_T_CONVERTER(TranslateUnaryOp); +OP_T_CONVERTER(TranslateBinaryOp); +OP_T_CONVERTER(TranslateDirectReduceOp); + +OP_CONVERTER(TranslateAddNOp); +OP_CONVERTER(TranslateArgMaxOp); +OP_CONVERTER(TranslateArgMinOp); +OP_CONVERTER(TranslateAvgPoolOp); +OP_CONVERTER(TranslateBiasAddOp); +OP_CONVERTER(TranslateCastOp); +OP_CONVERTER(TranslateConcatV2Op); +OP_CONVERTER(TranslateConstOp); +OP_CONVERTER(TranslateConv2DOp); +OP_CONVERTER(TranslateConv2DBackpropInputOp); +OP_CONVERTER(TranslateConv3DOp); +OP_CONVERTER(TranslateCumsumOp); +OP_CONVERTER(TranslateDepthToSpaceOp); +OP_CONVERTER(TranslateDepthwiseConv2dNativeOp); +OP_CONVERTER(TranslateEluOp); +OP_CONVERTER(TranslateExpandDimsOp); +OP_CONVERTER(TranslateFillOp); +OP_CONVERTER(TranslateFloorDivOp); +OP_CONVERTER(TranslateFusedBatchNormOp); +OP_CONVERTER(TranslateGatherOp); +OP_CONVERTER(TranslateGatherV2Op); +OP_CONVERTER(TranslateFusedConv2DOp); +OP_CONVERTER(TranslateFusedMatMulOp); +OP_CONVERTER(TranslateIdentityOp); +// OP_CONVERTER(TranslateIsFiniteOp); +// OP_CONVERTER(TranslateL2LossOp); +OP_CONVERTER(TranslateLogSoftmaxOp); +// OP_CONVERTER(TranslateLog1pOp); +// OP_CONVERTER(TranslateLRNOp); +// OP_CONVERTER(TranslateMatMulOp); +OP_CONVERTER(TranslateMaxPoolOp); +OP_CONVERTER(TranslateNonMaxSuppressionV2Op); +OP_CONVERTER(TranslatePadOp); +OP_CONVERTER(PlaceholderOp); +OP_CONVERTER(NoOp); +OP_CONVERTER(TranslateOneHotOp); +// OP_CONVERTER(TranslatePackOp); +OP_CONVERTER(TranslateRangeOp); +OP_CONVERTER(TranslateRankOp); +OP_CONVERTER(TranslateRandomUniformOp); +OP_CONVERTER(TranslateRelu6Op); +// OP_CONVERTER(TranslateReciprocalOp); +// OP_CONVERTER(TranslateReshapeOp); +OP_CONVERTER(RetvalOp); +OP_CONVERTER(TranslateRollOp); +OP_CONVERTER(TranslateRsqrtOp); +OP_CONVERTER(TranslateSelectOp); +// OP_CONVERTER(TranslateShapeOp); +// OP_CONVERTER(TranslateSizeOp); +// OP_CONVERTER(TranslateSliceOp); +// OP_CONVERTER(transpose2); +OP_CONVERTER(TranslateSoftmaxOp); +// OP_CONVERTER(TranslateSpaceToDepthOp); +// OP_CONVERTER(TranslateSplitOp); +// OP_CONVERTER(TranslateSplitOp); +OP_CONVERTER(TranslateSqueezeOp); +// OP_CONVERTER(TranslateStridedSliceOp); +OP_CONVERTER(TranslateSqrtOp); +// OP_CONVERTER(TranslateTileOp); +// OP_CONVERTER(TranslateTopKV2Op); +// OP_CONVERTER(TranslateTransposeOp); +// OP_CONVERTER(TranslateUnpackOp); +// OP_CONVERTER(TranslateWhereOp); +// OP_CONVERTER(TranslateXdivyOp); +// OP_CONVERTER(TranslateZerosLikeOp); + +const std::map get_supported_ops() { + return { + // note: UnaryOp translator declaration for each op must to be added in unary_op.cpp file + {"Abs", TranslateUnaryOp}, + {"Acos", TranslateUnaryOp}, + {"Acosh", TranslateUnaryOp}, + {"Asin", TranslateUnaryOp}, + {"Asinh", TranslateUnaryOp}, + {"Atan", TranslateUnaryOp}, + {"Atanh", TranslateUnaryOp}, + {"Ceil", TranslateUnaryOp}, + {"Cos", TranslateUnaryOp}, + {"Cosh", TranslateUnaryOp}, + {"Exp", TranslateUnaryOp}, + {"Floor", TranslateUnaryOp}, + {"Log", TranslateUnaryOp}, + {"LogicalNot", TranslateUnaryOp}, + {"Neg", TranslateUnaryOp}, + {"Relu", TranslateUnaryOp}, + {"Sigmoid", TranslateUnaryOp}, + {"Sin", TranslateUnaryOp}, + {"Sinh", TranslateUnaryOp}, + {"Sign", TranslateUnaryOp}, + {"Softplus", TranslateUnaryOp}, + {"Tan", TranslateUnaryOp}, + {"Tanh", TranslateUnaryOp}, + + // note: BinaryOp translator declaration for each op must to be added in binary_op.cpp file + {"Add", TranslateBinaryOp}, + {"AddV2", TranslateBinaryOp}, + {"Equal", TranslateBinaryOp}, + {"FloorMod", TranslateBinaryOp}, + {"Greater", TranslateBinaryOp}, + {"GreaterEqual", TranslateBinaryOp}, + {"Less", TranslateBinaryOp}, + {"LessEqual", TranslateBinaryOp}, + {"LogicalAnd", TranslateBinaryOp}, + {"LogicalOr", TranslateBinaryOp}, + {"Maximum", TranslateBinaryOp}, + {"Minimum", TranslateBinaryOp}, + {"Mul", TranslateBinaryOp}, + {"Mod", TranslateBinaryOp}, + {"NotEqual", TranslateBinaryOp}, + {"Pow", TranslateBinaryOp}, + {"RealDiv", TranslateBinaryOp}, + {"SquaredDifference", TranslateBinaryOp}, + {"Sub", TranslateBinaryOp}, + + // note: ReduceOp translator declaration for each op must to be added in reduce.cpp file + {"Any", TranslateDirectReduceOp}, + {"All", TranslateDirectReduceOp}, + {"Max", TranslateDirectReduceOp}, + {"Mean", TranslateDirectReduceOp}, + {"Min", TranslateDirectReduceOp}, + {"Prod", TranslateDirectReduceOp}, + {"Sum", TranslateDirectReduceOp}, + + // Separate translators: + {"AddN", TranslateAddNOp}, + {"ArgMax", TranslateArgMaxOp}, + {"ArgMin", TranslateArgMinOp}, + {"AvgPool", TranslateAvgPoolOp}, + {"BiasAdd", TranslateBiasAddOp}, + {"Cast", TranslateCastOp}, + {"ConcatV2", TranslateConcatV2Op}, + {"Const", TranslateConstOp}, + {"Conv2D", TranslateConv2DOp}, + {"Conv2DBackpropInput", TranslateConv2DBackpropInputOp}, + {"Conv3D", TranslateConv3DOp}, + {"Cumsum", TranslateCumsumOp}, + {"DepthToSpace", TranslateDepthToSpaceOp}, + {"DepthwiseConv2dNative", TranslateDepthwiseConv2dNativeOp}, + {"Elu", TranslateEluOp}, + {"ExpandDims", TranslateExpandDimsOp}, + {"Fill", TranslateFillOp}, + {"FloorDiv", TranslateFloorDivOp}, + {"FusedBatchNorm", TranslateFusedBatchNormOp}, + {"FusedBatchNormV2", TranslateFusedBatchNormOp}, + {"FusedBatchNormV3", TranslateFusedBatchNormOp}, + {"Gather", TranslateGatherOp}, + {"GatherV2", TranslateGatherV2Op}, + {"_FusedConv2D", TranslateFusedConv2DOp}, + {"_FusedMatMul", TranslateFusedMatMulOp}, + {"Identity", TranslateIdentityOp}, + //{"IsFinite", TranslateIsFiniteOp}, + //{"L2Loss", TranslateL2LossOp}, + {"LogSoftmax", TranslateLogSoftmaxOp}, + //{"Log1p", TranslateLog1pOp}, + //{"LRN", TranslateLRNOp}, + //{"MatMul", TranslateMatMulOp}, + {"MaxPool", TranslateMaxPoolOp}, + {"MaxPool3D", TranslateMaxPoolOp}, + //{"NonMaxSuppressionV2", TranslateNonMaxSuppressionV2Op}, + {"MirrorPad", TranslatePadOp}, + {"NoOp", NoOp}, // do nothing + {"OneHot", TranslateOneHotOp}, + //{"Pack", TranslatePackOp}, + {"Pad", TranslatePadOp}, + {"PadV2", TranslatePadOp}, + //{"_Arg", ArgOp}, // should be registered as an extension in OVTF + {"Placeholder", PlaceholderOp}, + // PreventGradient is just Identity in dataflow terms, so reuse that. + {"PreventGradient", TranslateIdentityOp}, + //{"Range", TranslateRangeOp}, + //{"Rank", TranslateRankOp}, + {"RandomUniform", TranslateRandomUniformOp}, + //{"Reciprocal", TranslateReciprocalOp}, + {"Relu6", TranslateRelu6Op}, + //{"Reshape", TranslateReshapeOp}, + {"_Retval", RetvalOp}, + {"Roll", TranslateRollOp}, + {"Rsqrt", TranslateRsqrtOp}, + {"Select", TranslateSelectOp}, + {"SelectV2", TranslateSelectOp}, + //{"Shape", TranslateShapeOp}, + //{"Size", TranslateSizeOp}, + //{"Slice", TranslateSliceOp}, + //{"Snapshot", TranslateIdentityOp}, + {"Softmax", TranslateSoftmaxOp}, + //{"SpaceToDepth", TranslateSpaceToDepthOp}, + //{"Split", TranslateSplitOp}, + //{"SplitV", TranslateSplitVOp}, + {"Sqrt", TranslateSqrtOp}, + //{"Square", TranslateSquareOp}, + {"Squeeze", TranslateSqueezeOp}, + //{"StridedSlice", TranslateStridedSliceOp}, + //{"Tile", TranslateTileOp}, + //{"TopKV2", TranslateTopKV2Op}, + //{"Transpose", TranslateTransposeOp}, + //{"Unpack", TranslateUnpackOp}, + //{"Where", TranslateWhereOp}, + //{"Xdivy", TranslateXdivyOp}, + //{"ZerosLike", TranslateZerosLikeOp}, + }; +}; +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov \ No newline at end of file diff --git a/ngraph/frontend/tensorflow/src/op_table.hpp b/ngraph/frontend/tensorflow/src/op_table.hpp new file mode 100644 index 00000000000..81bc6b3ba49 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/op_table.hpp @@ -0,0 +1,26 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include +#include +#include +#include + +#include "ngraph_conversions.hpp" +#include "node_context.hpp" +#include "utils.hpp" + +namespace ov { +namespace frontend { +namespace tf { +namespace op { +using CreatorFunction = std::function<::ov::OutputVector(const ::ov::frontend::tf::NodeContext&)>; + +const std::map get_supported_ops(); +} // namespace op +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/place.cpp b/ngraph/frontend/tensorflow/src/place.cpp new file mode 100644 index 00000000000..4d7ba1e17d6 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/place.cpp @@ -0,0 +1,308 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +#include "node_context.hpp" +#include "op_def.pb.h" +#include "tensor.pb.h" +#include "types.pb.h" + +namespace ov { +namespace frontend { + +bool PlaceTF::is_input() const { + const auto& model_ins = m_input_model.get_inputs(); + + const auto cmp = [this](const ngraph::frontend::Place::Ptr& p) { + return p.get() == this; + }; + return std::find_if(model_ins.begin(), model_ins.end(), cmp) != model_ins.end(); +} + +bool PlaceTF::is_output() const { + const auto& model_outs = m_input_model.get_outputs(); + const auto cmp = [this](const ngraph::frontend::Place::Ptr& p) { + return p.get() == this; + }; + return std::find_if(model_outs.begin(), model_outs.end(), cmp) != model_outs.end(); +} + +OpPlaceTF::OpPlaceTF(const ngraph::frontend::InputModel& input_model, std::shared_ptr op_decoder) + : PlaceTF(input_model, {op_decoder->get_op_name()}), + m_op_decoder(op_decoder) {} + +const std::vector>& OpPlaceTF::get_output_ports() const { + return m_output_ports; +} + +const std::map>>& OpPlaceTF::get_input_ports() const { + return m_input_ports; +} + +std::shared_ptr OpPlaceTF::get_input_port_tf(const std::string& inputName, int inputPortIndex) const { + FRONT_END_GENERAL_CHECK(inputPortIndex <= m_input_ports.at(inputName).size(), "inputPortIndex is out of bounds."); + return m_input_ports.at(inputName)[inputPortIndex]; +} + +std::shared_ptr OpPlaceTF::get_decoder() const { + return m_op_decoder; +} + +void OpPlaceTF::add_out_port(const std::shared_ptr& output, int idx) { + while (idx >= m_output_ports.size()) { + m_output_ports.push_back(std::shared_ptr()); + } + m_output_ports[idx] = output; +} + +void OpPlaceTF::add_in_port(const std::shared_ptr& input, const std::string& name) { + m_input_ports[name].push_back(input); +} + +ngraph::frontend::Place::Ptr OpPlaceTF::get_input_port(const std::string& name) const { + FRONT_END_GENERAL_CHECK(m_input_ports.at(name).size() == 1, "Only one input port should exist."); + return m_input_ports.at(name)[0]; +} + +ngraph::frontend::Place::Ptr OpPlaceTF::get_input_port(int outputPortIndex) const { + FRONT_END_GENERAL_CHECK(m_input_ports.size() == 1, "Only one named input port should exist."); + return m_input_ports.begin()->second[outputPortIndex]; +} + +ngraph::frontend::Place::Ptr OpPlaceTF::get_output_port(int outputPortIndex) const { + FRONT_END_GENERAL_CHECK(m_output_ports.size() > outputPortIndex, "No port with index: ", outputPortIndex); + return m_output_ports[outputPortIndex]; +} + +ngraph::frontend::Place::Ptr OpPlaceTF::get_output_port() const { + FRONT_END_GENERAL_CHECK(m_output_ports.size() == 1, "Only one output port should exist."); + return m_output_ports[0]; +} + +ngraph::frontend::Place::Ptr OpPlaceTF::get_input_port() const { + FRONT_END_GENERAL_CHECK(m_input_ports.size() == 1 && m_input_ports.begin()->second.size() == 1, + "Only one input port should exist."); + return m_input_ports.begin()->second[0]; +} + +std::vector OpPlaceTF::get_consuming_operations() const { + std::vector consuming_ops; + for (const auto& out_port : m_output_ports) { + auto consuming_ops_out = out_port->get_consuming_operations(); + consuming_ops.insert(consuming_ops.end(), consuming_ops_out.begin(), consuming_ops_out.end()); + } + return consuming_ops; +} + +std::vector OpPlaceTF::get_consuming_operations(int outputPortIndex) const { + return get_output_port(outputPortIndex)->get_consuming_operations(); +} + +std::vector OpPlaceTF::get_consuming_ports() const { + std::vector consuming_ports; + for (const auto& out_port : m_output_ports) { + auto consuming_ops_out = out_port->get_consuming_ports(); + consuming_ports.insert(consuming_ports.end(), consuming_ops_out.begin(), consuming_ops_out.end()); + } + return consuming_ports; +} + +ngraph::frontend::Place::Ptr OpPlaceTF::get_input_port(const std::string& inputName, int inputPortIndex) const { + FRONT_END_GENERAL_CHECK(inputPortIndex <= m_input_ports.at(inputName).size(), "inputPortIndex is out of bounds."); + return m_input_ports.at(inputName)[inputPortIndex]; +} + +ngraph::frontend::Place::Ptr OpPlaceTF::get_source_tensor() const { + return get_input_port()->get_source_tensor(); +} + +ngraph::frontend::Place::Ptr OpPlaceTF::get_source_tensor(const std::string& inputName) const { + return get_input_port(inputName)->get_source_tensor(); +} + +ngraph::frontend::Place::Ptr OpPlaceTF::get_source_tensor(int inputPortIndex) const { + return get_input_port(inputPortIndex)->get_source_tensor(); +} + +ngraph::frontend::Place::Ptr OpPlaceTF::get_source_tensor(const std::string& inputName, int inputPortIndex) const { + return get_input_port(inputName, inputPortIndex)->get_source_tensor(); +} + +ngraph::frontend::Place::Ptr OpPlaceTF::get_target_tensor() const { + return get_output_port()->get_target_tensor(); +} + +ngraph::frontend::Place::Ptr OpPlaceTF::get_producing_operation(const std::string& inputName) const { + return get_input_port(inputName)->get_producing_operation(); +} + +ngraph::frontend::Place::Ptr OpPlaceTF::get_producing_operation(const std::string& inputName, + int inputPortIndex) const { + return get_input_port(inputName, inputPortIndex)->get_producing_operation(); +} + +ngraph::frontend::Place::Ptr OpPlaceTF::get_producing_operation() const { + return get_input_port()->get_producing_operation(); +} + +ngraph::frontend::Place::Ptr OpPlaceTF::get_producing_operation(int inputPortIndex) const { + return get_input_port(inputPortIndex)->get_producing_operation(); +} + +ngraph::frontend::Place::Ptr OpPlaceTF::get_target_tensor(int outputPortIndex) const { + return get_output_port(outputPortIndex)->get_target_tensor(); +} + +TensorPlaceTF::TensorPlaceTF(const ngraph::frontend::InputModel& input_model, + const ov::PartialShape& pshape, + ov::element::Type type, + const std::vector& names) + : PlaceTF(input_model, names), + m_pshape(pshape), + m_type(type) {} + +std::vector TensorPlaceTF::get_consuming_ports() const { + std::vector consuming_ports; + for (const auto& consuming_port : m_consuming_ports) { + if (const auto& locked = consuming_port.lock()) { + consuming_ports.push_back(locked); + } else { + FRONT_END_THROW("Consuming Port has expired."); + } + } + return consuming_ports; +} + +ngraph::frontend::Place::Ptr TensorPlaceTF::get_producing_port() const { + FRONT_END_GENERAL_CHECK(m_producing_ports.size() == 1, "Only one producing port is supported."); + if (const auto& producing_port = m_producing_ports[0].lock()) { + return producing_port; + } + FRONT_END_THROW("Producing Port has expired."); +} + +void TensorPlaceTF::add_producing_port(const std::shared_ptr& out_port) { + m_producing_ports.push_back(out_port); +} + +void TensorPlaceTF::add_consuming_port(const std::shared_ptr& in_port) { + m_consuming_ports.push_back(in_port); +} + +std::vector TensorPlaceTF::get_consuming_operations() const { + std::vector consuming_ops; + for (const auto& consuming_port : m_consuming_ports) { + if (auto port_ptr = consuming_port.lock()) { + auto port_consuming_ops = port_ptr->get_consuming_operations(); + consuming_ops.insert(consuming_ops.end(), port_consuming_ops.begin(), port_consuming_ops.end()); + } else { + FRONT_END_THROW("Port has expired."); + } + } + return consuming_ops; +} + +bool TensorPlaceTF::is_equal_data(ngraph::frontend::Place::Ptr another) const { + auto consuming_ports = get_consuming_ports(); + bool eq_to_consuming_port = + std::any_of(consuming_ports.begin(), consuming_ports.end(), [&another](const Ptr& place) { + return place->is_equal(another); + }); + return is_equal(another) || get_producing_port()->is_equal(another) || eq_to_consuming_port; +} + +ngraph::frontend::Place::Ptr TensorPlaceTF::get_producing_operation() const { + return get_producing_port()->get_producing_operation(); +} + +std::shared_ptr InPortPlaceTF::get_source_tensor_tf() const { + if (const auto& tensor = m_source_tensor.lock()) { + return tensor; + } + FRONT_END_THROW("Source Tensor has expired."); +} + +std::shared_ptr InPortPlaceTF::get_op() { + if (const auto& op = m_op.lock()) { + return op; + } + FRONT_END_THROW("Operation has expired."); +} + +void InPortPlaceTF::set_source_tensor(const std::weak_ptr& source_tensor) { + m_source_tensor = source_tensor; +} + +std::vector InPortPlaceTF::get_consuming_operations() const { + if (const auto& consuming_op = m_op.lock()) { + return {consuming_op}; + } + FRONT_END_THROW("Operation has expired."); +} + +ngraph::frontend::Place::Ptr InPortPlaceTF::get_source_tensor() const { + if (const auto& tensor = m_source_tensor.lock()) { + return tensor; + } + FRONT_END_THROW("Source Tensor has expired."); +} + +ngraph::frontend::Place::Ptr InPortPlaceTF::get_producing_port() const { + return get_source_tensor()->get_producing_port(); +} + +bool InPortPlaceTF::is_equal_data(ngraph::frontend::Place::Ptr another) const { + return get_source_tensor()->is_equal_data(another); +} + +ngraph::frontend::Place::Ptr InPortPlaceTF::get_producing_operation() const { + return get_producing_port()->get_producing_operation(); +} + +std::shared_ptr OutPortPlaceTF::get_target_tensor_tf() const { + if (const auto& target_tensor = m_target_tensor.lock()) { + return target_tensor; + } + FRONT_END_THROW("Target Tensor has expired."); +} + +std::vector OutPortPlaceTF::get_consuming_operations() const { + if (auto tensor_ptr = m_target_tensor.lock()) { + return tensor_ptr->get_consuming_operations(); + } + FRONT_END_THROW("Tensor has expired."); +} + +void OutPortPlaceTF::set_target_tensor(const std::weak_ptr& target_tensor) { + m_target_tensor = target_tensor; +} + +std::vector OutPortPlaceTF::get_consuming_ports() const { + if (auto tensor_ptr = m_target_tensor.lock()) { + return tensor_ptr->get_consuming_ports(); + } + FRONT_END_THROW("Tensor has expired."); +} + +bool OutPortPlaceTF::is_equal_data(ngraph::frontend::Place::Ptr another) const { + return get_target_tensor()->is_equal_data(another); +} + +ngraph::frontend::Place::Ptr OutPortPlaceTF::get_target_tensor() const { + if (const auto& target_tensor = m_target_tensor.lock()) { + return target_tensor; + } + FRONT_END_THROW("Target Tensor has expired."); +} + +ngraph::frontend::Place::Ptr OutPortPlaceTF::get_producing_operation() const { + if (auto op = m_op.lock()) { + return op; + } + FRONT_END_THROW("Operation has expired."); +} +} // namespace frontend +} // namespace ov \ No newline at end of file diff --git a/ngraph/frontend/tensorflow/src/proto/allocation_description.proto b/ngraph/frontend/tensorflow/src/proto/allocation_description.proto new file mode 100644 index 00000000000..26d1844cc0e --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/allocation_description.proto @@ -0,0 +1,41 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +syntax = "proto3"; + +package tensorflow; + +option cc_enable_arenas = true; +option java_outer_classname = "AllocationDescriptionProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/allocation_description_go_proto"; + +message AllocationDescription { + // Total number of bytes requested + int64 requested_bytes = 1; + + // Total number of bytes allocated if known + int64 allocated_bytes = 2; + + // Name of the allocator used + string allocator_name = 3; + + // Identifier of the allocated buffer if known + int64 allocation_id = 4; + + // Set if this tensor only has one remaining reference + bool has_single_reference = 5; + + // Address of the allocation. + uint64 ptr = 6; +} diff --git a/ngraph/frontend/tensorflow/src/proto/api_def.proto b/ngraph/frontend/tensorflow/src/proto/api_def.proto new file mode 100644 index 00000000000..bd2e31939b0 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/api_def.proto @@ -0,0 +1,148 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +// Defines the text format for including per-op API definition and +// overrides for client language op code generators. + +syntax = "proto3"; + +package tensorflow; +option cc_enable_arenas = true; +option java_outer_classname = "ApiDefProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/api_def_go_proto"; +import "attr_value.proto"; + +// Used to specify and override the default API & behavior in the +// generated code for client languages, from what you would get from +// the OpDef alone. There will be a set of ApiDefs that are common +// to all client languages, and another set per client language. +// The per-client-language ApiDefs will inherit values from the +// common ApiDefs which it can either replace or modify. +// +// We separate the API definition from the OpDef so we can evolve the +// API while remaining backwards compatible when interpretting old +// graphs. Overrides go in an "api_def.pbtxt" file with a text-format +// ApiDefs message. +// +// WARNING: Be *very* careful changing the API for any existing op -- +// you can change the semantics of existing code. These changes may +// need to wait until a major release of TensorFlow to avoid breaking +// our compatibility promises. +message ApiDef { + // Name of the op (in the OpDef) to specify the API for. + string graph_op_name = 1; + // If this op is deprecated, set deprecation message to the message + // that should be logged when this op is used. + // The message should indicate alternative op to use, if any. + string deprecation_message = 12; + // Major version when the op will be deleted. For e.g. set this + // value to 2 if op API should be removed in TensorFlow 2.0 and + // deprecated in versions before that. + int32 deprecation_version = 13; + + enum Visibility { + // Normally this is "VISIBLE" unless you are inheriting a + // different value from another ApiDef. + DEFAULT_VISIBILITY = 0; + // Publicly visible in the API. + VISIBLE = 1; + // Do not include this op in the generated API. If visibility is + // set to 'SKIP', other fields are ignored for this op. + SKIP = 2; + // Hide this op by putting it into an internal namespace (or whatever + // is appropriate in the target language). + HIDDEN = 3; + } + Visibility visibility = 2; + + // If you specify any endpoint, this will replace all of the + // inherited endpoints. The first endpoint should be the + // "canonical" endpoint, and should not be deprecated (unless all + // endpoints are deprecated). + message Endpoint { + // Name should be either like "CamelCaseName" or + // "Package.CamelCaseName". Client-language-specific ApiDefs may + // use a snake_case convention instead of CamelCase. + string name = 1; + + // Set if this endpoint is deprecated. If set to true, a message suggesting + // to use a non-deprecated endpoint instead will be printed. If all + // endpoints are deprecated, set deprecation_message in ApiDef instead. + bool deprecated = 3; + + // Major version when an endpoint will be deleted. For e.g. set this + // value to 2 if endpoint should be removed in TensorFlow 2.0 and + // deprecated in versions before that. + int32 deprecation_version = 4; + } + repeated Endpoint endpoint = 3; + + message Arg { + string name = 1; + + // Change the name used to access this arg in the API from what + // is used in the GraphDef. Note that these names in `backticks` + // will also be replaced in the summary & description fields. + string rename_to = 2; + + // Note: this will replace any inherited arg doc. There is no + // current way of modifying arg descriptions (other than replacing + // them entirely) as can be done with op descriptions. + string description = 3; + } + repeated Arg in_arg = 4; + repeated Arg out_arg = 5; + // List of original in_arg names to specify new argument order. + // Length of arg_order should be either empty to keep current order + // or match size of in_arg. + repeated string arg_order = 11; + + // Description of the graph-construction-time configuration of this + // Op. That is to say, this describes the attr fields that will + // be specified in the NodeDef. + message Attr { + string name = 1; + + // Change the name used to access this attr in the API from what + // is used in the GraphDef. Note that these names in `backticks` + // will also be replaced in the summary & description fields. + string rename_to = 2; + + // Specify a new default value to use for this attr. This default + // will be used when creating new graphs, as opposed to the + // default in the OpDef, which will be used when interpreting old + // GraphDefs. + AttrValue default_value = 3; + + // Note: this will replace any inherited attr doc, there is no current + // way of modifying attr descriptions as can be done with op descriptions. + string description = 4; + } + repeated Attr attr = 6; + + // One-line human-readable description of what the Op does. + string summary = 7; + + // Additional, longer human-readable description of what the Op does. + string description = 8; + + // Modify an existing/inherited description by adding text to the beginning + // or end. + string description_prefix = 9; + string description_suffix = 10; +} + +message ApiDefs { + repeated ApiDef op = 1; +} diff --git a/ngraph/frontend/tensorflow/src/proto/attr_value.proto b/ngraph/frontend/tensorflow/src/proto/attr_value.proto new file mode 100644 index 00000000000..e7c2072385f --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/attr_value.proto @@ -0,0 +1,76 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +syntax = "proto3"; + +package tensorflow; + +import "tensor.proto"; +import "tensor_shape.proto"; +import "types.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "AttrValueProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/attr_value_go_proto"; + +// Protocol buffer representing the value for an attr used to configure an Op. +// Comment indicates the corresponding attr type. Only the field matching the +// attr type may be filled. +message AttrValue { + // LINT.IfChange + message ListValue { + repeated bytes s = 2; // "list(string)" + repeated int64 i = 3 [packed = true]; // "list(int)" + repeated float f = 4 [packed = true]; // "list(float)" + repeated bool b = 5 [packed = true]; // "list(bool)" + repeated DataType type = 6 [packed = true]; // "list(type)" + repeated TensorShapeProto shape = 7; // "list(shape)" + repeated TensorProto tensor = 8; // "list(tensor)" + repeated NameAttrList func = 9; // "list(attr)" + } + // LINT.ThenChange(https://www.tensorflow.org/code/tensorflow/c/c_api.cc) + + oneof value { + bytes s = 2; // "string" + int64 i = 3; // "int" + float f = 4; // "float" + bool b = 5; // "bool" + DataType type = 6; // "type" + TensorShapeProto shape = 7; // "shape" + TensorProto tensor = 8; // "tensor" + ListValue list = 1; // any "list(...)" + + // "func" represents a function. func.name is a function's name or + // a primitive op's name. func.attr.first is the name of an attr + // defined for that function. func.attr.second is the value for + // that attr in the instantiation. + NameAttrList func = 10; + + // This is a placeholder only used in nodes defined inside a + // function. It indicates the attr value will be supplied when + // the function is instantiated. For example, let us suppose a + // node "N" in function "FN". "N" has an attr "A" with value + // placeholder = "foo". When FN is instantiated with attr "foo" + // set to "bar", the instantiated node N's attr A will have been + // given the value "bar". + string placeholder = 9; + } +} + +// A list of attr names and their values. The whole list is attached +// with a string name. E.g., MatMul[T=float]. +message NameAttrList { + string name = 1; + map attr = 2; +} diff --git a/ngraph/frontend/tensorflow/src/proto/cost_graph.proto b/ngraph/frontend/tensorflow/src/proto/cost_graph.proto new file mode 100644 index 00000000000..898cbbf0ca7 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/cost_graph.proto @@ -0,0 +1,101 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +syntax = "proto3"; + +package tensorflow; + +import "tensor_shape.proto"; +import "types.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "CostGraphProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/cost_graph_go_proto"; + +message CostGraphDef { + message Node { + // The name of the node. Names are globally unique. + string name = 1; + + // The device of the node. Can be empty if the node is mapped to the + // default partition or partitioning hasn't been run yet. + string device = 2; + + // The id of the node. Node ids are only unique inside a partition. + int32 id = 3; + + // Inputs of this node. They must be executed before this node can be + // executed. An input is a particular output of another node, specified + // by the node id and the output index. + message InputInfo { + int32 preceding_node = 1; + int32 preceding_port = 2; + } + repeated InputInfo input_info = 4; + + // Outputs of this node. + message OutputInfo { + int64 size = 1; + // If >= 0, the output is an alias of an input. Note that an alias input + // may itself be an alias. The algorithm will therefore need to follow + // those pointers. + int64 alias_input_port = 2; + TensorShapeProto shape = 3; + DataType dtype = 4; + } + repeated OutputInfo output_info = 5; + + // Temporary memory used by this node. + int64 temporary_memory_size = 6; + + // Persistent memory used by this node. + int64 persistent_memory_size = 12; + + int64 host_temp_memory_size = 10 [deprecated = true]; + int64 device_temp_memory_size = 11 [deprecated = true]; + int64 device_persistent_memory_size = 16 [deprecated = true]; + + // Estimate of the computational cost of this node, in microseconds. + int64 compute_cost = 9; + + // Analytical estimate of the computational cost of this node, in + // microseconds. + int64 compute_time = 14; + + // Analytical estimate of the memory access cost of this node, in + // microseconds. + int64 memory_time = 15; + + // If true, the output is permanent: it can't be discarded, because this + // node is part of the "final output". Nodes may depend on final nodes. + bool is_final = 7; + + // Ids of the control inputs for this node. + repeated int32 control_input = 8; + + // Are the costs inaccurate? + bool inaccurate = 17; + } + repeated Node node = 1; + + // Total cost of this graph, typically used for balancing decisions. + message AggregatedCost { + // Aggregated cost value. + float cost = 1; + + // Aggregated cost dimension (e.g. 'memory', 'compute', 'network'). + string dimension = 2; + } + repeated AggregatedCost cost = 2; +} diff --git a/ngraph/frontend/tensorflow/src/proto/dataset_options.proto b/ngraph/frontend/tensorflow/src/proto/dataset_options.proto new file mode 100644 index 00000000000..acdf36b0e3e --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/dataset_options.proto @@ -0,0 +1,191 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +syntax = "proto3"; + +package tensorflow.data; + +// Represents the type of auto-sharding we enable. +enum AutoShardPolicy { + AUTO = 0; + FILE = 1; + DATA = 2; + OFF = -1; +} + +message DistributeOptions { + // The type of sharding that auto-shard should attempt. If this is set to + // FILE, then we will attempt to shard by files (each worker will get a set of + // files to process). If we cannot find a set of files to shard for at least + // one file per worker, we will error out. When this option is selected, make + // sure that you have enough files so that each worker gets at least one file. + // There will be a runtime error thrown if there are insufficient files. If + // this is set to DATA, then we will shard by elements produced by the + // dataset, and each worker will process the whole dataset and discard the + // portion that is not for itself. If this is set to OFF, then we will not + // autoshard, and each worker will receive a copy of the full dataset. This + // option is set to AUTO by default, AUTO will attempt to first shard by FILE, + // and fall back to sharding by DATA if we cannot find a set of files to + // shard. + AutoShardPolicy auto_shard_policy = 1; + // The number of devices attached to this input pipeline. + oneof optional_num_devices { + int32 num_devices = 2; + } +} + +message MapVectorization { + // Whether to vectorize map transformations. + oneof optional_enabled { + bool enabled = 1; + } + // Whether to use ChooseFastestBranchDataset with this transformation. If + // True, the pipeline picks between the vectorized and original segment at + // runtime based on their iterations speed. + oneof optional_use_choose_fastest { + bool use_choose_fastest = 2; + } +} + +message OptimizationOptions { + // Whether to apply default graph optimizations. If False, only graph + // optimizations that have been explicitly enabled will be applied. + oneof optional_apply_default_optimizations { + bool apply_default_optimizations = 1; + } + // Whether to automatically tune performance knobs. + oneof optional_autotune { + bool autotune = 2; + } + // When autotuning is enabled (through autotune), determines whether to also + // autotune buffer sizes for datasets with parallelism. + oneof optional_autotune_buffers { + bool autotune_buffers = 3; + } + // When autotuning is enabled (through autotune), determines the CPU budget to + // use. Values greater than the number of schedulable CPU cores are allowed + // but may result in CPU contention. + oneof optional_autotune_cpu_budget { + int32 autotune_cpu_budget = 4; + } + // When autotuning is enabled (through autotune), determines the RAM budget to + // use. Values greater than the available RAM in bytes may result in OOM. If + // 0, defaults to half of the available RAM in bytes. + oneof optional_autotune_ram_budget { + int32 autotune_ram_budget = 5; + } + // Whether to fuse filter transformations. + oneof optional_filter_fusion { + bool filter_fusion = 6; + } + // Whether to fuse filter dataset that predicts random_uniform < rate into a + // sampling dataset. + oneof optional_filter_with_random_uniform_fusion { + bool filter_with_random_uniform_fusion = 7; + } + // Whether to hoist tf.random_uniform() ops out of map transformations. + oneof optional_hoist_random_uniform { + bool hoist_random_uniform = 8; + } + // Whether to fuse map and batch transformations. + oneof optional_map_and_batch_fusion { + bool map_and_batch_fusion = 9; + } + // Whether to fuse map and filter transformations. + oneof optional_map_and_filter_fusion { + bool map_and_filter_fusion = 10; + } + // Whether to fuse map transformations. + oneof optional_map_fusion { + bool map_fusion = 11; + } + // Whether to parallelize stateless map transformations. + oneof optional_map_parallelization { + bool map_parallelization = 12; + } + // The map vectorization options associated with the dataset. + MapVectorization map_vectorization = 13; + // Whether to eliminate no-op transformations. + oneof optional_noop_elimination { + bool noop_elimination = 14; + } + // Whether to parallelize copying of batch elements. This optimization is + // highly experimental and can cause performance degradation (e.g. when the + // parallelization overhead exceeds the benefits of performing the data copies + // in parallel). You should only enable this optimization if a) your input + // pipeline is bottlenecked on batching and b) you have validated that this + // optimization improves performance. + oneof optional_parallel_batch { + bool parallel_batch = 15; + } + // Whether to reorder ops that will discard data to the front of unary + // cardinality preserving transformations, e.g. dataset.map(...).take(3) will + // be optimized to dataset.take(3).map(...). For now this optimization will + // move `skip`, `shard` and `take` to the front of `map` and `prefetch`. This + // optimization is only for performance; it will not affect the output of the + // dataset. + oneof optional_reorder_data_discarding_ops { + bool reorder_data_discarding_ops = 16; + } + // Whether to fuse shuffle and repeat transformations. + oneof optional_shuffle_and_repeat_fusion { + bool shuffle_and_repeat_fusion = 17; + } +} + +message ThreadingOptions { + // If set, it overrides the maximum degree of intra-op parallelism. + oneof optional_max_intra_op_parallelism { + int32 max_intra_op_parallelism = 1; + } + // If set, the dataset will use a private threadpool of the given size. + oneof optional_private_threadpool_size { + int32 private_threadpool_size = 2; + } +} + +// Represents how to handle external state during serialization. +enum ExternalStatePolicy { + WARN = 0; + IGNORE = 1; + FAIL = 2; +} + +// Message stored with Dataset objects to control how datasets are processed and +// optimized. +message Options { + // Whether the outputs need to be produced in deterministic order. + oneof optional_deterministic { + bool deterministic = 1; + } + // The distribution strategy options associated with the dataset. + DistributeOptions distribute_options = 2; + // The optimization options associated with the dataset. + OptimizationOptions optimization_options = 3; + // Whether to introduce 'slack' in the last `prefetch` of the input pipeline, + // if it exists. This may reduce CPU contention with accelerator host-side + // activity at the start of a step. The slack frequency is determined by the + // number of devices attached to this input pipeline. + oneof optional_slack { + bool slack = 4; + } + // The threading options associated with the dataset. + ThreadingOptions threading_options = 5; + // This option can be used to override the default policy for how to handle + // external state when serializing a dataset or checkpointing its iterator. + // There are three settings available - IGNORE: External state is ignored + // without a warning; WARN: External state is ignored and a warning is logged; + // FAIL: External state results in an error. + oneof optional_external_state_policy { + ExternalStatePolicy external_state_policy = 6; + } +} diff --git a/ngraph/frontend/tensorflow/src/proto/device_attributes.proto b/ngraph/frontend/tensorflow/src/proto/device_attributes.proto new file mode 100644 index 00000000000..3b713235254 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/device_attributes.proto @@ -0,0 +1,65 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +syntax = "proto3"; + +package tensorflow; + +option cc_enable_arenas = true; +option java_outer_classname = "DeviceAttributesProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/device_attributes_go_proto"; + +message InterconnectLink { + int32 device_id = 1; + string type = 2; + int32 strength = 3; +} + +message LocalLinks { + repeated InterconnectLink link = 1; +} + +message DeviceLocality { + // Optional bus locality of device. Default value of 0 means + // no specific locality. Specific localities are indexed from 1. + int32 bus_id = 1; + + // Optional NUMA locality of device. + int32 numa_node = 2; + + // Optional local interconnect links to other devices. + LocalLinks links = 3; +} + +message DeviceAttributes { + // Fully specified name of the device within a cluster. + string name = 1; + + // String representation of device_type. + string device_type = 2; + + // Memory capacity of device in bytes. + int64 memory_limit = 4; + + // Platform-specific data about device that may be useful + // for supporting efficient data transfers. + DeviceLocality locality = 5; + + // A device is assigned a global unique number each time it is + // initialized. "incarnation" should never be 0. + fixed64 incarnation = 6; + + // String representation of the physical device that this device maps to. + string physical_device_desc = 7; +} diff --git a/ngraph/frontend/tensorflow/src/proto/function.proto b/ngraph/frontend/tensorflow/src/proto/function.proto new file mode 100644 index 00000000000..e88f762780d --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/function.proto @@ -0,0 +1,138 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +syntax = "proto3"; + +package tensorflow; + +import "attr_value.proto"; +import "node_def.proto"; +import "op_def.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "FunctionProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/function_go_proto"; + +// A library is a set of named functions. +message FunctionDefLibrary { + repeated FunctionDef function = 1; + repeated GradientDef gradient = 2; +} + +// A function can be instantiated when the runtime can bind every attr +// with a value. When a GraphDef has a call to a function, it must +// have binding for every attr defined in the signature. +// +// TODO(zhifengc): +// * device spec, etc. +message FunctionDef { + // The definition of the function's name, arguments, return values, + // attrs etc. + OpDef signature = 1; + + // Attributes specific to this function definition. + map attr = 5; + + // Attributes for function arguments. These attributes are the same set of + // valid attributes as to _Arg nodes. + message ArgAttrs { + map attr = 1; + } + map arg_attr = 7; + + // Unique IDs for each resource argument, used to track aliasing resources. If + // Argument A and Argument B alias each other, then + // resource_arg_unique_ids[A.index] == resource_arg_unique_ids[B.index]. + // + // If this field is empty, none of the arguments could alias; otherwise, every + // resource argument should have an entry in this field. + // + // When instantiated, the unique IDs will be attached to the _Arg nodes' + // "_resource_arg_unique_id" attribute. + map resource_arg_unique_id = 8; + + // NOTE: field id 2 deleted on Jan 11, 2017, GraphDef version 21. + reserved 2; + + // In both of the following fields, there is the need to specify an + // output that is used as either the input to another node (in + // `node_def`) or as a return value of the function (in `ret`). + // Unlike the NodeDefs in GraphDef, we need to be able to specify a + // list in some cases (instead of just single outputs). Also, we + // need to be able to deal with lists of unknown length (so the + // output index may not be known at function definition time). So + // we use the following format instead: + // * "fun_in" where "fun_in" is the name of a function input arg in + // the `signature` field above. This represents that input, whether + // it is a single tensor or a list. + // * "fun_in:0" gives the first element of a function input arg (a + // non-list input is considered a list of length 1 for these + // purposes). + // * "node:out" where "node" is the name of a node in `node_def` and + // "out" is the name one of its op's output arguments (the name + // comes from the OpDef of the node's op). This represents that + // node's output, whether it is a single tensor or a list. + // Note: We enforce that an op's output arguments are never + // renamed in the backwards-compatibility test. + // * "node:out:0" gives the first element of a node output arg (a + // non-list output is considered a list of length 1 for these + // purposes). + // + // NOT CURRENTLY SUPPORTED (but may be in the future): + // * "node:out:-1" gives last element in a node output list + // * "node:out:1:" gives a list with all but the first element in a + // node output list + // * "node:out::-1" gives a list with all but the last element in a + // node output list + + // The body of the function. Unlike the NodeDefs in a GraphDef, attrs + // may have values of type `placeholder` and the `input` field uses + // the "output" format above. + + // By convention, "op" in node_def is resolved by consulting with a + // user-defined library first. If not resolved, "func" is assumed to + // be a builtin op. + repeated NodeDef node_def = 3; + + // A mapping from the output arg names from `signature` to the + // outputs from `node_def` that should be returned by the function. + map ret = 4; + + // A mapping from control output names from `signature` to node names in + // `node_def` which should be control outputs of this function. + map control_ret = 6; +} + +// GradientDef defines the gradient function of a function defined in +// a function library. +// +// A gradient function g (specified by gradient_func) for a function f +// (specified by function_name) must follow the following: +// +// The function 'f' must be a numerical function which takes N inputs +// and produces M outputs. Its gradient function 'g', which is a +// function taking N + M inputs and produces N outputs. +// +// I.e. if we have +// (y1, y2, ..., y_M) = f(x1, x2, ..., x_N), +// then, g is +// (dL/dx1, dL/dx2, ..., dL/dx_N) = g(x1, x2, ..., x_N, +// dL/dy1, dL/dy2, ..., dL/dy_M), +// where L is a scalar-value function of (x1, x2, ..., xN) (e.g., the +// loss function). dL/dx_i is the partial derivative of L with respect +// to x_i. +message GradientDef { + string function_name = 1; // The function name. + string gradient_func = 2; // The gradient function's name. +} diff --git a/ngraph/frontend/tensorflow/src/proto/graph.proto b/ngraph/frontend/tensorflow/src/proto/graph.proto new file mode 100644 index 00000000000..d1db146884c --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/graph.proto @@ -0,0 +1,68 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +syntax = "proto3"; + +package tensorflow; + +import "function.proto"; +import "node_def.proto"; +import "versions.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "GraphProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/graph_go_proto"; + +// Represents the graph of operations +message GraphDef { + repeated NodeDef node = 1; + + // Compatibility versions of the graph. See core/public/version.h for version + // history. The GraphDef version is distinct from the TensorFlow version, and + // each release of TensorFlow will support a range of GraphDef versions. + VersionDef versions = 4; + + // Deprecated single version field; use versions above instead. Since all + // GraphDef changes before "versions" was introduced were forward + // compatible, this field is entirely ignored. + int32 version = 3 [deprecated = true]; + + // "library" provides user-defined functions. + // + // Naming: + // * library.function.name are in a flat namespace. + // NOTE: We may need to change it to be hierarchical to support + // different orgs. E.g., + // { "/google/nn", { ... }}, + // { "/google/vision", { ... }} + // { "/org_foo/module_bar", { ... }} + // map named_lib; + // * If node[i].op is the name of one function in "library", + // node[i] is deemed as a function call. Otherwise, node[i].op + // must be a primitive operation supported by the runtime. + // + // + // Function call semantics: + // + // * The callee may start execution as soon as some of its inputs + // are ready. The caller may want to use Tuple() mechanism to + // ensure all inputs are ready in the same time. + // + // * The consumer of return values may start executing as soon as + // the return values the consumer depends on are ready. The + // consumer may want to use Tuple() mechanism to ensure the + // consumer does not start until all return values of the callee + // function are ready. + FunctionDefLibrary library = 2; +} diff --git a/ngraph/frontend/tensorflow/src/proto/graph_transfer_info.proto b/ngraph/frontend/tensorflow/src/proto/graph_transfer_info.proto new file mode 100644 index 00000000000..2a169756683 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/graph_transfer_info.proto @@ -0,0 +1,83 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +syntax = "proto3"; + +package tensorflow; + +import "types.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "GraphTransferInfoProto"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/graph_transfer_info_go_proto"; + +message GraphTransferNodeInput { + int32 node_id = 1; + int32 output_port = 2; +} +message GraphTransferNodeInfo { + string name = 1; + int32 node_id = 2; + string type_name = 3; + int32 soc_op_id = 4; + int32 padding_id = 5; + int32 input_count = 6; + int32 output_count = 7; +} +message GraphTransferConstNodeInfo { + string name = 1; + int32 node_id = 2; + repeated int64 shape = 3; + bytes data = 4; + DataType dtype = 5; +} +message GraphTransferNodeInputInfo { + int32 node_id = 1; + repeated GraphTransferNodeInput node_input = 2; +} +message GraphTransferNodeOutputInfo { + int32 node_id = 1; + repeated int32 max_byte_size = 2; +} +message GraphTransferGraphInputNodeInfo { + string name = 1; + repeated int64 shape = 2; + DataType dtype = 3; +} + +message GraphTransferGraphOutputNodeInfo { + string name = 1; + repeated int64 shape = 2; + DataType dtype = 3; +} + +// Protocol buffer representing a handle to a tensorflow resource. Handles are +// not valid across executions, but can be serialized back and forth from within +// a single run. +message GraphTransferInfo { + enum Destination { + NOP = 0; + HEXAGON = 1; + } + + repeated GraphTransferNodeInfo node_info = 1; + repeated GraphTransferConstNodeInfo const_node_info = 2; + repeated GraphTransferNodeInputInfo node_input_info = 3; + repeated GraphTransferNodeOutputInfo node_output_info = 4; + // Input Node parameters of transferred graph + repeated GraphTransferGraphInputNodeInfo graph_input_node_info = 5; + repeated GraphTransferGraphOutputNodeInfo graph_output_node_info = 6; + // Destination of graph transfer + Destination destination = 7; +} diff --git a/ngraph/frontend/tensorflow/src/proto/kernel_def.proto b/ngraph/frontend/tensorflow/src/proto/kernel_def.proto new file mode 100644 index 00000000000..9f1a3277107 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/kernel_def.proto @@ -0,0 +1,60 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +syntax = "proto3"; + +package tensorflow; + +import "attr_value.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "KernelDefProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/kernel_def_go_proto"; + +message KernelDef { + // Must match the name of an Op. + string op = 1; + + // Type of device this kernel runs on. + string device_type = 2; + + message AttrConstraint { + // Name of an attr from the Op. + string name = 1; + + // A list of values that this kernel supports for this attr. + // Like OpDef.AttrDef.allowed_values, except for kernels instead of Ops. + AttrValue allowed_values = 2; + } + repeated AttrConstraint constraint = 3; + + // Names of the Op's input_/output_args that reside in host memory + // instead of device memory. + repeated string host_memory_arg = 4; + + // This allows experimental kernels to be registered for an op that + // won't be used unless the user specifies a "_kernel" attr with + // value matching this. + string label = 5; + + // Prioritization of kernel amongst different devices. By default we assume + // priority is 0. The higher the priority the better. By default (i.e. if + // this is not set), we prefer GPU kernels over CPU. + int32 priority = 6; +} + +// A collection of KernelDefs +message KernelList { + repeated KernelDef kernel = 1; +} diff --git a/ngraph/frontend/tensorflow/src/proto/log_memory.proto b/ngraph/frontend/tensorflow/src/proto/log_memory.proto new file mode 100644 index 00000000000..81d9067f769 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/log_memory.proto @@ -0,0 +1,107 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +syntax = "proto3"; + +package tensorflow; + +import "tensor_description.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "LogMemoryProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/log_memory_go_proto"; + +message MemoryLogStep { + // Process-unique step id. + int64 step_id = 1; + + // Handle describing the feeds and fetches of the step. + string handle = 2; +} + +message MemoryLogTensorAllocation { + // Process-unique step id. + int64 step_id = 1; + + // Name of the kernel making the allocation as set in GraphDef, + // e.g., "affine2/weights/Assign". + string kernel_name = 2; + + // Allocated tensor details. + TensorDescription tensor = 3; +} + +message MemoryLogTensorDeallocation { + // Id of the tensor buffer being deallocated, used to match to a + // corresponding allocation. + int64 allocation_id = 1; + + // Name of the allocator used. + string allocator_name = 2; +} + +message MemoryLogTensorOutput { + // Process-unique step id. + int64 step_id = 1; + + // Name of the kernel producing an output as set in GraphDef, e.g., + // "affine2/weights/Assign". + string kernel_name = 2; + + // Index of the output being set. + int32 index = 3; + + // Output tensor details. + TensorDescription tensor = 4; +} + +message MemoryLogRawAllocation { + // Process-unique step id. + int64 step_id = 1; + + // Name of the operation making the allocation. + string operation = 2; + + // Number of bytes in the allocation. + int64 num_bytes = 3; + + // Address of the allocation. + uint64 ptr = 4; + + // Id of the tensor buffer being allocated, used to match to a + // corresponding deallocation. + int64 allocation_id = 5; + + // Name of the allocator used. + string allocator_name = 6; +} + +message MemoryLogRawDeallocation { + // Process-unique step id. + int64 step_id = 1; + + // Name of the operation making the deallocation. + string operation = 2; + + // Id of the tensor buffer being deallocated, used to match to a + // corresponding allocation. + int64 allocation_id = 3; + + // Name of the allocator used. + string allocator_name = 4; + + // True if the deallocation is queued and will be performed later, + // e.g. for GPU lazy freeing of buffers. + bool deferred = 5; +} diff --git a/ngraph/frontend/tensorflow/src/proto/model.proto b/ngraph/frontend/tensorflow/src/proto/model.proto new file mode 100644 index 00000000000..637c725ac12 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/model.proto @@ -0,0 +1,142 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +syntax = "proto3"; + +package tensorflow.data.model; + +option cc_enable_arenas = true; + +// Class of a node in the performance model. +enum NodeClass { + UNKNOWN = 0; + INTERLEAVE_MANY = 1; + ASYNC_INTERLEAVE_MANY = 2; + KNOWN_RATIO = 3; + ASYNC_KNOWN_RATIO = 4; + UNKNOWN_RATIO = 5; +} + +// Algorithm used for model autotuning optimization. +enum AutotuneAlgorithm { + HILL_CLIMB = 0; + GRADIENT_DESCENT = 1; +} + +// Protocol buffer representing the data used by the autotuning modeling +// framework. +message ModelProto { + // General representation of a node in the model. + message Node { + // Unique node ID. + int64 id = 1; + + // Human-readable name of the node. + string name = 2; + + // An indication whether autotuning is enabled for this node. + bool autotune = 3; + + // The number of bytes stored in this node's buffer. + int64 buffered_bytes = 4; + + // The number of elements stored in this node's buffer. + int64 buffered_elements = 5; + + // The number of bytes consumed by the node. + int64 bytes_consumed = 6; + + // The number of bytes produced by the node. + int64 bytes_produced = 7; + + // The number of elements produced by the node. + int64 num_elements = 8; + + // The aggregate processing time spent in this node. + int64 processing_time = 9; + + // An indication whether this node records metrics about produced and + // consumed elements. + bool record_metrics = 10; + + // Represents a node parameter. + message Parameter { + // Human-readable name of the parameter. + string name = 1; + + // Identifies the model value of the parameter. This can be different from + // the actual value (e.g. during optimization search). + double value = 2; + + // The actual value of the parameter. + double state_value = 3; + + // Minimum value of the parameter. + double min = 4; + + // Maximum value of the parameter. + double max = 5; + + // Identifies whether the parameter should participate in autotuning. + bool tunable = 6; + } + + // Parameters of this node. + repeated Parameter parameters = 11; + + // Statistic of inputs processing time history. + double input_processing_time_sum = 12; + int64 input_processing_time_count = 13; + + // Inputs of this node. + repeated Node inputs = 14; + + // Class of this node. + NodeClass node_class = 15; + + // Ratio of input to output elements. This is only used by KNOWN_RATIO and + // ASYNC_KNOWN_RATIO nodes. + double ratio = 16; + + // Ratio identifies how many parallelism calls are introduced by one + // buffered element. This is only used by ASYNC_KNOWN_RATIO nodes. + double memory_ratio = 17; + } + + // Output node of this model. + Node output = 1; + + // Counter for node IDs of this model. + int64 id_counter = 2; + + // Indicates whether the modeling framework should collect resource usage, + // e.g. CPU, memory. + bool collect_resource_usage = 3; + + // Contains parameters of the model autotuning optimization. + message OptimizationParams { + // Algorithm used for autotuning optimization. + AutotuneAlgorithm algorithm = 1; + + // Number of available logical threads. + int64 cpu_budget = 2; + + // Amount of available memory in bytes. + int64 ram_budget = 3; + + // Time between two consecutive `GetNext` calls to the iterator represented + // by the output node. + double model_input_time = 4; + } + + OptimizationParams optimization_params = 4; +} diff --git a/ngraph/frontend/tensorflow/src/proto/node_def.proto b/ngraph/frontend/tensorflow/src/proto/node_def.proto new file mode 100644 index 00000000000..f80bfaa8ae5 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/node_def.proto @@ -0,0 +1,100 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +syntax = "proto3"; + +package tensorflow; + +import "attr_value.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "NodeProto"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/node_def_go_proto"; + +message NodeDef { + // The name given to this operator. Used for naming inputs, + // logging, visualization, etc. Unique within a single GraphDef. + // Must match the regexp "[A-Za-z0-9.][A-Za-z0-9_>./]*". + string name = 1; + + // The operation name. There may be custom parameters in attrs. + // Op names starting with an underscore are reserved for internal use. + string op = 2; + + // Each input is "node:src_output" with "node" being a string name and + // "src_output" indicating which output tensor to use from "node". If + // "src_output" is 0 the ":0" suffix can be omitted. Regular inputs + // may optionally be followed by control inputs that have the format + // "^node". + repeated string input = 3; + + // A (possibly partial) specification for the device on which this + // node should be placed. + // The expected syntax for this string is as follows: + // + // DEVICE_SPEC ::= PARTIAL_SPEC + // + // PARTIAL_SPEC ::= ("/" CONSTRAINT) * + // CONSTRAINT ::= ("job:" JOB_NAME) + // | ("replica:" [1-9][0-9]*) + // | ("task:" [1-9][0-9]*) + // | ("device:" [A-Za-z]* ":" ([1-9][0-9]* | "*") ) + // + // Valid values for this string include: + // * "/job:worker/replica:0/task:1/device:GPU:3" (full specification) + // * "/job:worker/device:GPU:3" (partial specification) + // * "" (no specification) + // + // If the constraints do not resolve to a single device (or if this + // field is empty or not present), the runtime will attempt to + // choose a device automatically. + string device = 4; + + // Operation-specific graph-construction-time configuration. + // Note that this should include all attrs defined in the + // corresponding OpDef, including those with a value matching + // the default -- this allows the default to change and makes + // NodeDefs easier to interpret on their own. However, if + // an attr with a default is not specified in this list, the + // default will be used. + // The "names" (keys) must match the regexp "[a-z][a-z0-9_]+" (and + // one of the names from the corresponding OpDef's attr field). + // The values must have a type matching the corresponding OpDef + // attr's type field. + // TODO(josh11b): Add some examples here showing best practices. + map attr = 5; + + message ExperimentalDebugInfo { + // Opaque string inserted into error messages created by the runtime. + // + // This is intended to store the list of names of the nodes from the + // original graph that this node was derived. For example if this node, say + // C, was result of a fusion of 2 nodes A and B, then 'original_node' would + // be {A, B}. This information can be used to map errors originating at the + // current node to some top level source code. + repeated string original_node_names = 1; + + // This is intended to store the list of names of the functions from the + // original graph that this node was derived. For example if this node, say + // C, was result of a fusion of node A in function FA and node B in function + // FB, then `original_funcs` would be {FA, FB}. If the node is in the top + // level graph, the `original_func` is empty. This information, with the + // `original_node_names` can be used to map errors originating at the + // current ndoe to some top level source code. + repeated string original_func_names = 2; + } + + // This stores debug information associated with the node. + ExperimentalDebugInfo experimental_debug_info = 6; +} diff --git a/ngraph/frontend/tensorflow/src/proto/op_def.proto b/ngraph/frontend/tensorflow/src/proto/op_def.proto new file mode 100644 index 00000000000..e18cc48bfc9 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/op_def.proto @@ -0,0 +1,186 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +syntax = "proto3"; + +package tensorflow; +option cc_enable_arenas = true; +option java_outer_classname = "OpDefProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/op_def_go_proto"; +import "attr_value.proto"; +import "types.proto"; +import "resource_handle.proto"; + +// Defines an operation. A NodeDef in a GraphDef specifies an Op by +// using the "op" field which should match the name of a OpDef. +// LINT.IfChange +message OpDef { + // Op names starting with an underscore are reserved for internal use. + // Names should be CamelCase and match the regexp "[A-Z][a-zA-Z0-9>_]*". + string name = 1; + + // For describing inputs and outputs. + message ArgDef { + // Name for the input/output. Should match the regexp "[a-z][a-z0-9_]*". + string name = 1; + + // Human readable description. + string description = 2; + + // Describes the type of one or more tensors that are accepted/produced + // by this input/output arg. The only legal combinations are: + // * For a single tensor: either the "type" field is set or the + // "type_attr" field is set to the name of an attr with type "type". + // * For a sequence of tensors with the same type: the "number_attr" + // field will be set to the name of an attr with type "int", and + // either the "type" or "type_attr" field will be set as for + // single tensors. + // * For a sequence of tensors, the "type_list_attr" field will be set + // to the name of an attr with type "list(type)". + DataType type = 3; + string type_attr = 4; // if specified, attr must have type "type" + string number_attr = 5; // if specified, attr must have type "int" + // If specified, attr must have type "list(type)", and none of + // type, type_attr, and number_attr may be specified. + string type_list_attr = 6; + + // The handle data for resource inputs. + repeated ResourceHandleProto.DtypeAndShape handle_data = 7; + + // For inputs: if true, the inputs are required to be refs. + // By default, inputs can be either refs or non-refs. + // For outputs: if true, outputs are refs, otherwise they are not. + bool is_ref = 16; + }; + + // Description of the input(s). + repeated ArgDef input_arg = 2; + + // Description of the output(s). + repeated ArgDef output_arg = 3; + + // Named control outputs for this operation. Useful only for composite + // operations (i.e. functions) which want to name different control outputs. + repeated string control_output = 20; + + // Description of the graph-construction-time configuration of this + // Op. That is to say, this describes the attr fields that will + // be specified in the NodeDef. + message AttrDef { + // A descriptive name for the argument. May be used, e.g. by the + // Python client, as a keyword argument name, and so should match + // the regexp "[a-z][a-z0-9_]+". + string name = 1; + + // One of the type names from attr_value.proto ("string", "list(string)", + // "int", etc.). + string type = 2; + + // A reasonable default for this attribute if the user does not supply + // a value. If not specified, the user must supply a value. + AttrValue default_value = 3; + + // Human-readable description. + string description = 4; + + // TODO(josh11b): bool is_optional? + + // --- Constraints --- + // These constraints are only in effect if specified. Default is no + // constraints. + + // For type == "int", this is a minimum value. For "list(___)" + // types, this is the minimum length. + bool has_minimum = 5; + int64 minimum = 6; + + // The set of allowed values. Has type that is the "list" version + // of the "type" field above (uses the "list" field of AttrValue). + // If type == "type" or "list(type)" above, then the "type" field + // of "allowed_values.list" has the set of allowed DataTypes. + // If type == "string" or "list(string)", then the "s" field of + // "allowed_values.list" has the set of allowed strings. + AttrValue allowed_values = 7; + } + repeated AttrDef attr = 4; + + // Optional deprecation based on GraphDef versions. + OpDeprecation deprecation = 8; + + // One-line human-readable description of what the Op does. + string summary = 5; + + // Additional, longer human-readable description of what the Op does. + string description = 6; + + // ------------------------------------------------------------------------- + // Which optimizations this operation can participate in. + + // True if the operation is commutative ("op(a,b) == op(b,a)" for all inputs) + bool is_commutative = 18; + + // If is_aggregate is true, then this operation accepts N >= 2 + // inputs and produces 1 output all of the same type. Should be + // associative and commutative, and produce output with the same + // shape as the input. The optimizer may replace an aggregate op + // taking input from multiple devices with a tree of aggregate ops + // that aggregate locally within each device (and possibly within + // groups of nearby devices) before communicating. + // TODO(josh11b): Implement that optimization. + bool is_aggregate = 16; // for things like add + + // Other optimizations go here, like + // can_alias_input, rewrite_when_output_unused, partitioning_strategy, etc. + + // ------------------------------------------------------------------------- + // Optimization constraints. + + // Ops are marked as stateful if their behavior depends on some state beyond + // their input tensors (e.g. variable reading op) or if they have + // a side-effect (e.g. printing or asserting ops). Equivalently, stateless ops + // must always produce the same output for the same input and have + // no side-effects. + // + // By default Ops may be moved between devices. Stateful ops should + // either not be moved, or should only be moved if that state can also + // be moved (e.g. via some sort of save / restore). + // Stateful ops are guaranteed to never be optimized away by Common + // Subexpression Elimination (CSE). + bool is_stateful = 17; // for things like variables, queue + + // ------------------------------------------------------------------------- + // Non-standard options. + + // By default, all inputs to an Op must be initialized Tensors. Ops + // that may initialize tensors for the first time should set this + // field to true, to allow the Op to take an uninitialized Tensor as + // input. + bool allows_uninitialized_input = 19; // for Assign, etc. +}; +// LINT.ThenChange( +// https://www.tensorflow.org/code/tensorflow/core/framework/op_def_util.cc) + +// Information about version-dependent deprecation of an op +message OpDeprecation { + // First GraphDef version at which the op is disallowed. + int32 version = 1; + + // Explanation of why it was deprecated and what to use instead. + string explanation = 2; +}; + +// A collection of OpDefs +message OpList { + repeated OpDef op = 1; +}; diff --git a/ngraph/frontend/tensorflow/src/proto/reader_base.proto b/ngraph/frontend/tensorflow/src/proto/reader_base.proto new file mode 100644 index 00000000000..02429b8e426 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/reader_base.proto @@ -0,0 +1,30 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +syntax = "proto3"; + +package tensorflow; + +option cc_enable_arenas = true; +option java_outer_classname = "ReaderBaseProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/reader_base_go_proto"; + +// For serializing and restoring the state of ReaderBase, see +// reader_base.h for details. +message ReaderBaseState { + int64 work_started = 1; + int64 work_finished = 2; + int64 num_records_produced = 3; + bytes current_work = 4; +} diff --git a/ngraph/frontend/tensorflow/src/proto/remote_fused_graph_execute_info.proto b/ngraph/frontend/tensorflow/src/proto/remote_fused_graph_execute_info.proto new file mode 100644 index 00000000000..9857b45e4c3 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/remote_fused_graph_execute_info.proto @@ -0,0 +1,60 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +syntax = "proto3"; + +package tensorflow; + +import "graph.proto"; +import "tensor_shape.proto"; +import "types.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "RemoteFusedGraphExecuteInfoProto"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/remote_fused_graph_execute_info_go_proto"; + +// Protocol buffer representing a handle to a tensorflow resource. Handles are +// not valid across executions, but can be serialized back and forth from within +// a single run. +message RemoteFusedGraphExecuteInfo { + message TensorShapeTypeProto { + DataType dtype = 1; + TensorShapeProto shape = 2; + } + + // Definition of remote graph + GraphDef remote_graph = 1; + + // Remote fused graph input node name + repeated string graph_input_node_name = 2; + + // Remote fused graph output node name + repeated string graph_output_node_name = 3; + + // Executor's name + string executor_name = 4; + + // Optional: Parameters given to the executor + bytes serialized_executor_parameters = 5; + + // Optional: Default graph input tensor shape used to allocate memory + // before executing op + repeated TensorShapeTypeProto default_graph_input_tensor_shape = 6; + + // Optional: Default graph input tensor shape used to allocate memory + // before executing op + // TODO(satok): Remote output tensor shape once shape information is stored + // in NodeDef + repeated TensorShapeTypeProto default_graph_output_tensor_shape = 7; +} diff --git a/ngraph/frontend/tensorflow/src/proto/resource_handle.proto b/ngraph/frontend/tensorflow/src/proto/resource_handle.proto new file mode 100644 index 00000000000..a66e9205c79 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/resource_handle.proto @@ -0,0 +1,57 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +syntax = "proto3"; + +package tensorflow; + +import "tensor_shape.proto"; +import "types.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "ResourceHandle"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/resource_handle_go_proto"; + +// Protocol buffer representing a handle to a tensorflow resource. Handles are +// not valid across executions, but can be serialized back and forth from within +// a single run. +message ResourceHandleProto { + // Unique name for the device containing the resource. + string device = 1; + + // Container in which this resource is placed. + string container = 2; + + // Unique name of this resource. + string name = 3; + + // Hash code for the type of the resource. Is only valid in the same device + // and in the same execution. + uint64 hash_code = 4; + + // For debug-only, the name of the type pointed to by this handle, if + // available. + string maybe_type_name = 5; + + // Protocol buffer representing a pair of (data type, tensor shape). + message DtypeAndShape { + DataType dtype = 1; + TensorShapeProto shape = 2; + } + + // Data types and shapes for the underlying resource. + repeated DtypeAndShape dtypes_and_shapes = 6; + + reserved 7; +} diff --git a/ngraph/frontend/tensorflow/src/proto/step_stats.proto b/ngraph/frontend/tensorflow/src/proto/step_stats.proto new file mode 100644 index 00000000000..1e99958c5ea --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/step_stats.proto @@ -0,0 +1,100 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +syntax = "proto3"; + +package tensorflow; + +import "allocation_description.proto"; +import "tensor_description.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "StepStatsProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/step_stats_go_proto"; + +// An allocation/de-allocation operation performed by the allocator. +message AllocationRecord { + // The timestamp of the operation. + int64 alloc_micros = 1; + // Number of bytes allocated, or de-allocated if negative. + int64 alloc_bytes = 2; +} + +message AllocatorMemoryUsed { + string allocator_name = 1; + // These are per-node allocator memory stats. + int64 total_bytes = 2; + int64 peak_bytes = 3; + // The bytes that are not deallocated. + int64 live_bytes = 4; + // The allocation and deallocation timeline. + repeated AllocationRecord allocation_records = 6; + + // These are snapshots of the overall allocator memory stats. + // The number of live bytes currently allocated by the allocator. + int64 allocator_bytes_in_use = 5; +} + +// Output sizes recorded for a single execution of a graph node. +message NodeOutput { + int32 slot = 1; + TensorDescription tensor_description = 3; +} + +// For memory tracking. +message MemoryStats { + int64 temp_memory_size = 1; + int64 persistent_memory_size = 3; + repeated int64 persistent_tensor_alloc_ids = 5; + + int64 device_temp_memory_size = 2 [deprecated = true]; + int64 device_persistent_memory_size = 4 [deprecated = true]; + repeated int64 device_persistent_tensor_alloc_ids = 6 [deprecated = true]; +} + +// Time/size stats recorded for a single execution of a graph node. +message NodeExecStats { + // TODO(tucker): Use some more compact form of node identity than + // the full string name. Either all processes should agree on a + // global id (cost_id?) for each node, or we should use a hash of + // the name. + string node_name = 1; + int64 all_start_micros = 2; + int64 op_start_rel_micros = 3; + int64 op_end_rel_micros = 4; + int64 all_end_rel_micros = 5; + repeated AllocatorMemoryUsed memory = 6; + repeated NodeOutput output = 7; + string timeline_label = 8; + int64 scheduled_micros = 9; + uint32 thread_id = 10; + repeated AllocationDescription referenced_tensor = 11; + MemoryStats memory_stats = 12; + int64 all_start_nanos = 13; + int64 op_start_rel_nanos = 14; + int64 op_end_rel_nanos = 15; + int64 all_end_rel_nanos = 16; + int64 scheduled_nanos = 17; +} + +message DeviceStepStats { + string device = 1; + repeated NodeExecStats node_stats = 2; + // Its key is thread id. + map thread_names = 3; +} + +message StepStats { + repeated DeviceStepStats dev_stats = 1; +} diff --git a/ngraph/frontend/tensorflow/src/proto/summary.proto b/ngraph/frontend/tensorflow/src/proto/summary.proto new file mode 100644 index 00000000000..cbcda6bc1da --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/summary.proto @@ -0,0 +1,161 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +syntax = "proto3"; + +package tensorflow; + +import "tensor.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "SummaryProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/summary_go_proto"; + +// Metadata associated with a series of Summary data +message SummaryDescription { + // Hint on how plugins should process the data in this series. + // Supported values include "scalar", "histogram", "image", "audio" + string type_hint = 1; +} + +// Serialization format for histogram module in +// core/lib/histogram/histogram.h +message HistogramProto { + double min = 1; + double max = 2; + double num = 3; + double sum = 4; + double sum_squares = 5; + + // Parallel arrays encoding the bucket boundaries and the bucket values. + // bucket(i) is the count for the bucket i. The range for + // a bucket is: + // i == 0: -DBL_MAX .. bucket_limit(0) + // i != 0: bucket_limit(i-1) .. bucket_limit(i) + repeated double bucket_limit = 6 [packed = true]; + repeated double bucket = 7 [packed = true]; +} + +// A SummaryMetadata encapsulates information on which plugins are able to make +// use of a certain summary value. +message SummaryMetadata { + message PluginData { + // The name of the plugin this data pertains to. + string plugin_name = 1; + + // The content to store for the plugin. The best practice is for this to be + // a binary serialized protocol buffer. + bytes content = 2; + } + + // Data that associates a summary with a certain plugin. + PluginData plugin_data = 1; + + // Display name for viewing in TensorBoard. + string display_name = 2; + + // Longform readable description of the summary sequence. Markdown supported. + string summary_description = 3; + + // Class of data stored in this time series. Required for compatibility with + // TensorBoard's generic data facilities (`DataProvider`, et al.). This value + // imposes constraints on the dtype and shape of the corresponding tensor + // values. See `DataClass` docs for details. + DataClass data_class = 4; +} + +enum DataClass { + // Unknown data class, used (implicitly) for legacy data. Will not be + // processed by data ingestion pipelines. + DATA_CLASS_UNKNOWN = 0; + // Scalar time series. Each `Value` for the corresponding tag must have + // `tensor` set to a rank-0 tensor of type `DT_FLOAT` (float32). + DATA_CLASS_SCALAR = 1; + // Tensor time series. Each `Value` for the corresponding tag must have + // `tensor` set. The tensor value is arbitrary, but should be small to + // accommodate direct storage in database backends: an upper bound of a few + // kilobytes is a reasonable rule of thumb. + DATA_CLASS_TENSOR = 2; + // Blob sequence time series. Each `Value` for the corresponding tag must + // have `tensor` set to a rank-1 tensor of bytestring dtype. + DATA_CLASS_BLOB_SEQUENCE = 3; +} + +// A Summary is a set of named values to be displayed by the +// visualizer. +// +// Summaries are produced regularly during training, as controlled by +// the "summary_interval_secs" attribute of the training operation. +// Summaries are also produced at the end of an evaluation. +message Summary { + message Image { + // Dimensions of the image. + int32 height = 1; + int32 width = 2; + // Valid colorspace values are + // 1 - grayscale + // 2 - grayscale + alpha + // 3 - RGB + // 4 - RGBA + // 5 - DIGITAL_YUV + // 6 - BGRA + int32 colorspace = 3; + // Image data in encoded format. All image formats supported by + // image_codec::CoderUtil can be stored here. + bytes encoded_image_string = 4; + } + + message Audio { + // Sample rate of the audio in Hz. + float sample_rate = 1; + // Number of channels of audio. + int64 num_channels = 2; + // Length of the audio in frames (samples per channel). + int64 length_frames = 3; + // Encoded audio data and its associated RFC 2045 content type (e.g. + // "audio/wav"). + bytes encoded_audio_string = 4; + string content_type = 5; + } + + message Value { + // This field is deprecated and will not be set. + string node_name = 7; + + // Tag name for the data. Used by TensorBoard plugins to organize data. Tags + // are often organized by scope (which contains slashes to convey + // hierarchy). For example: foo/bar/0 + string tag = 1; + + // Contains metadata on the summary value such as which plugins may use it. + // Take note that many summary values may lack a metadata field. This is + // because the FileWriter only keeps a metadata object on the first summary + // value with a certain tag for each tag. TensorBoard then remembers which + // tags are associated with which plugins. This saves space. + SummaryMetadata metadata = 9; + + // Value associated with the tag. + oneof value { + float simple_value = 2; + bytes obsolete_old_style_histogram = 3; + Image image = 4; + HistogramProto histo = 5; + Audio audio = 6; + TensorProto tensor = 8; + } + } + + // Set of values for the summary. + repeated Value value = 1; +} diff --git a/ngraph/frontend/tensorflow/src/proto/tensor.proto b/ngraph/frontend/tensorflow/src/proto/tensor.proto new file mode 100644 index 00000000000..8280fc38ccd --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/tensor.proto @@ -0,0 +1,108 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +syntax = "proto3"; + +package tensorflow; + +import "resource_handle.proto"; +import "tensor_shape.proto"; +import "types.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "TensorProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/tensor_go_proto"; + +// Protocol buffer representing a tensor. +message TensorProto { + DataType dtype = 1; + + // Shape of the tensor. TODO(touts): sort out the 0-rank issues. + TensorShapeProto tensor_shape = 2; + + // Only one of the representations below is set, one of "tensor_contents" and + // the "xxx_val" attributes. We are not using oneof because as oneofs cannot + // contain repeated fields it would require another extra set of messages. + + // Version number. + // + // In version 0, if the "repeated xxx" representations contain only one + // element, that element is repeated to fill the shape. This makes it easy + // to represent a constant Tensor with a single value. + int32 version_number = 3; + + // Serialized raw tensor content from either Tensor::AsProtoTensorContent or + // memcpy in tensorflow::grpc::EncodeTensorToByteBuffer. This representation + // can be used for all tensor types. The purpose of this representation is to + // reduce serialization overhead during RPC call by avoiding serialization of + // many repeated small items. + bytes tensor_content = 4; + + // Type specific representations that make it easy to create tensor protos in + // all languages. Only the representation corresponding to "dtype" can + // be set. The values hold the flattened representation of the tensor in + // row major order. + + // DT_HALF, DT_BFLOAT16. Note that since protobuf has no int16 type, we'll + // have some pointless zero padding for each value here. + repeated int32 half_val = 13 [packed = true]; + + // DT_FLOAT. + repeated float float_val = 5 [packed = true]; + + // DT_DOUBLE. + repeated double double_val = 6 [packed = true]; + + // DT_INT32, DT_INT16, DT_INT8, DT_UINT8. + repeated int32 int_val = 7 [packed = true]; + + // DT_STRING + repeated bytes string_val = 8; + + // DT_COMPLEX64. scomplex_val(2*i) and scomplex_val(2*i+1) are real + // and imaginary parts of i-th single precision complex. + repeated float scomplex_val = 9 [packed = true]; + + // DT_INT64 + repeated int64 int64_val = 10 [packed = true]; + + // DT_BOOL + repeated bool bool_val = 11 [packed = true]; + + // DT_COMPLEX128. dcomplex_val(2*i) and dcomplex_val(2*i+1) are real + // and imaginary parts of i-th double precision complex. + repeated double dcomplex_val = 12 [packed = true]; + + // DT_RESOURCE + repeated ResourceHandleProto resource_handle_val = 14; + + // DT_VARIANT + repeated VariantTensorDataProto variant_val = 15; + + // DT_UINT32 + repeated uint32 uint32_val = 16 [packed = true]; + + // DT_UINT64 + repeated uint64 uint64_val = 17 [packed = true]; +} + +// Protocol buffer representing the serialization format of DT_VARIANT tensors. +message VariantTensorDataProto { + // Name of the type of objects being serialized. + string type_name = 1; + // Portions of the object that are not Tensors. + bytes metadata = 2; + // Tensors contained within objects being serialized. + repeated TensorProto tensors = 3; +} diff --git a/ngraph/frontend/tensorflow/src/proto/tensor_description.proto b/ngraph/frontend/tensorflow/src/proto/tensor_description.proto new file mode 100644 index 00000000000..a292b54c9b5 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/tensor_description.proto @@ -0,0 +1,36 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +syntax = "proto3"; + +package tensorflow; + +import "allocation_description.proto"; +import "tensor_shape.proto"; +import "types.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "TensorDescriptionProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/tensor_description_go_proto"; + +message TensorDescription { + // Data type of tensor elements + DataType dtype = 1; + + // Shape of the tensor. + TensorShapeProto shape = 2; + + // Information about the size and allocator used for the data + AllocationDescription allocation_description = 4; +} diff --git a/ngraph/frontend/tensorflow/src/proto/tensor_shape.proto b/ngraph/frontend/tensorflow/src/proto/tensor_shape.proto new file mode 100644 index 00000000000..049dedc10d3 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/tensor_shape.proto @@ -0,0 +1,58 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +// Protocol buffer representing the shape of tensors. + +syntax = "proto3"; +option cc_enable_arenas = true; +option java_outer_classname = "TensorShapeProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/tensor_shape_go_proto"; + +package tensorflow; + +// Dimensions of a tensor. +message TensorShapeProto { + // One dimension of the tensor. + message Dim { + // Size of the tensor in that dimension. + // This value must be >= -1, but values of -1 are reserved for "unknown" + // shapes (values of -1 mean "unknown" dimension). Certain wrappers + // that work with TensorShapeProto may fail at runtime when deserializing + // a TensorShapeProto containing a dim value of -1. + int64 size = 1; + + // Optional name of the tensor dimension. + string name = 2; + }; + + // Dimensions of the tensor, such as {"input", 30}, {"output", 40} + // for a 30 x 40 2D tensor. If an entry has size -1, this + // corresponds to a dimension of unknown size. The names are + // optional. + // + // The order of entries in "dim" matters: It indicates the layout of the + // values in the tensor in-memory representation. + // + // The first entry in "dim" is the outermost dimension used to layout the + // values, the last entry is the innermost dimension. This matches the + // in-memory layout of RowMajor Eigen tensors. + // + // If "dim.size()" > 0, "unknown_rank" must be false. + repeated Dim dim = 2; + + // If true, the number of dimensions in the shape is unknown. + // + // If true, "dim.size()" must be 0. + bool unknown_rank = 3; +}; diff --git a/ngraph/frontend/tensorflow/src/proto/tensor_slice.proto b/ngraph/frontend/tensorflow/src/proto/tensor_slice.proto new file mode 100644 index 00000000000..96d15344ade --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/tensor_slice.proto @@ -0,0 +1,51 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +// Protocol buffer representing slices of a tensor + +syntax = "proto3"; + +package tensorflow; + +option cc_enable_arenas = true; +option java_outer_classname = "TensorSliceProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/tensor_slice_go_proto"; + +// Can only be interpreted if you know the corresponding TensorShape. +message TensorSliceProto { + // Extent of the slice in one dimension. + message Extent { + // Either both or no attributes must be set. When no attribute is set + // means: All data in that dimension. + + // Start index of the slice, starting at 0. + int64 start = 1; + + // Length of the slice: if the length is missing or -1 we will + // interpret this as "everything in this dimension". We use + // "oneof" to preserve information about whether the length is + // present without changing the serialization format from the + // prior proto2 version of this proto. + oneof has_length { + int64 length = 2; + } + } + + // Extent of the slice in all tensor dimensions. + // + // Must have one entry for each of the dimension of the tensor that this + // slice belongs to. The order of sizes is the same as the order of + // dimensions in the TensorShape. + repeated Extent extent = 1; +} diff --git a/ngraph/frontend/tensorflow/src/proto/types.proto b/ngraph/frontend/tensorflow/src/proto/types.proto new file mode 100644 index 00000000000..82013a21e87 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/types.proto @@ -0,0 +1,101 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +syntax = "proto3"; + +package tensorflow; +option cc_enable_arenas = true; +option java_outer_classname = "TypesProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/types_go_proto"; + +// (== suppress_warning documentation-presence ==) +// LINT.IfChange +enum DataType { + // Not a legal value for DataType. Used to indicate a DataType field + // has not been set. + DT_INVALID = 0; + + // Data types that all computation devices are expected to be + // capable to support. + DT_FLOAT = 1; + DT_DOUBLE = 2; + DT_INT32 = 3; + DT_UINT8 = 4; + DT_INT16 = 5; + DT_INT8 = 6; + DT_STRING = 7; + DT_COMPLEX64 = 8; // Single-precision complex + DT_INT64 = 9; + DT_BOOL = 10; + DT_QINT8 = 11; // Quantized int8 + DT_QUINT8 = 12; // Quantized uint8 + DT_QINT32 = 13; // Quantized int32 + DT_BFLOAT16 = 14; // Float32 truncated to 16 bits. Only for cast ops. + DT_QINT16 = 15; // Quantized int16 + DT_QUINT16 = 16; // Quantized uint16 + DT_UINT16 = 17; + DT_COMPLEX128 = 18; // Double-precision complex + DT_HALF = 19; + DT_RESOURCE = 20; + DT_VARIANT = 21; // Arbitrary C++ data types + DT_UINT32 = 22; + DT_UINT64 = 23; + + // Do not use! These are only for parameters. Every enum above + // should have a corresponding value below (verified by types_test). + DT_FLOAT_REF = 101; + DT_DOUBLE_REF = 102; + DT_INT32_REF = 103; + DT_UINT8_REF = 104; + DT_INT16_REF = 105; + DT_INT8_REF = 106; + DT_STRING_REF = 107; + DT_COMPLEX64_REF = 108; + DT_INT64_REF = 109; + DT_BOOL_REF = 110; + DT_QINT8_REF = 111; + DT_QUINT8_REF = 112; + DT_QINT32_REF = 113; + DT_BFLOAT16_REF = 114; + DT_QINT16_REF = 115; + DT_QUINT16_REF = 116; + DT_UINT16_REF = 117; + DT_COMPLEX128_REF = 118; + DT_HALF_REF = 119; + DT_RESOURCE_REF = 120; + DT_VARIANT_REF = 121; + DT_UINT32_REF = 122; + DT_UINT64_REF = 123; +} +// LINT.ThenChange( +// https://www.tensorflow.org/code/tensorflow/c/tf_datatype.h, +// https://www.tensorflow.org/code/tensorflow/go/tensor.go, +// https://www.tensorflow.org/code/tensorflow/core/framework/tensor.cc, +// https://www.tensorflow.org/code/tensorflow/core/framework/types.h, +// https://www.tensorflow.org/code/tensorflow/core/framework/types.cc, +// https://www.tensorflow.org/code/tensorflow/python/framework/dtypes.py, +// https://www.tensorflow.org/code/tensorflow/python/framework/function.py) + +// For identifying the underlying type of a variant. For variants, the types +// listed here are a subset of the types in the variant type registry, +// corresponding to commonly used variants which must occasionally be +// special-cased. +enum SpecializedType { + // Invalid/unknown specialized type. + ST_INVALID = 0; + // "tensorflow::TensorList" in the variant type registry. + ST_TENSOR_LIST = 1; + // "tensorflow::data::Optional" in the variant type registry. + ST_OPTIONAL = 2; +} diff --git a/ngraph/frontend/tensorflow/src/proto/variable.proto b/ngraph/frontend/tensorflow/src/proto/variable.proto new file mode 100644 index 00000000000..b8805cc8ae9 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/variable.proto @@ -0,0 +1,96 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +syntax = "proto3"; + +package tensorflow; + +option cc_enable_arenas = true; +option java_outer_classname = "VariableProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/variable_go_proto"; + +// Indicates when a distributed variable will be synced. +enum VariableSynchronization { + // `AUTO`: Indicates that the synchronization will be determined by the + // current `DistributionStrategy` (eg. With `MirroredStrategy` this would be + // `ON_WRITE`). + VARIABLE_SYNCHRONIZATION_AUTO = 0; + // `NONE`: Indicates that there will only be one copy of the variable, so + // there is no need to sync. + VARIABLE_SYNCHRONIZATION_NONE = 1; + // `ON_WRITE`: Indicates that the variable will be updated across devices + // every time it is written. + VARIABLE_SYNCHRONIZATION_ON_WRITE = 2; + // `ON_READ`: Indicates that the variable will be aggregated across devices + // when it is read (eg. when checkpointing or when evaluating an op that uses + // the variable). + VARIABLE_SYNCHRONIZATION_ON_READ = 3; +} + +// Indicates how a distributed variable will be aggregated. +enum VariableAggregation { + // `NONE`: This is the default, giving an error if you use a + // variable-update operation with multiple replicas. + VARIABLE_AGGREGATION_NONE = 0; + // `SUM`: Add the updates across replicas. + VARIABLE_AGGREGATION_SUM = 1; + // `MEAN`: Take the arithmetic mean ("average") of the updates across + // replicas. + VARIABLE_AGGREGATION_MEAN = 2; + // `ONLY_FIRST_REPLICA`: This is for when every replica is performing the same + // update, but we only want to perform the update once. Used, e.g., for the + // global step counter. + VARIABLE_AGGREGATION_ONLY_FIRST_REPLICA = 3; +} + +// Protocol buffer representing a Variable. +message VariableDef { + // Name of the variable tensor. + string variable_name = 1; + + // Name of the tensor holding the variable's initial value. + string initial_value_name = 6; + + // Name of the initializer op. + string initializer_name = 2; + + // Name of the snapshot tensor. + string snapshot_name = 3; + + // Support for saving variables as slices of a larger variable. + SaveSliceInfoDef save_slice_info_def = 4; + + // Whether to represent this as a ResourceVariable. + bool is_resource = 5; + + // Whether this variable should be trained. + bool trainable = 7; + + // Indicates when a distributed variable will be synced. + VariableSynchronization synchronization = 8; + + // Indicates how a distributed variable will be aggregated. + VariableAggregation aggregation = 9; +} + +message SaveSliceInfoDef { + // Name of the full variable of which this is a slice. + string full_name = 1; + // Shape of the full variable. + repeated int64 full_shape = 2; + // Offset of this variable into the full variable. + repeated int64 var_offset = 3; + // Shape of this variable. + repeated int64 var_shape = 4; +} diff --git a/ngraph/frontend/tensorflow/src/proto/versions.proto b/ngraph/frontend/tensorflow/src/proto/versions.proto new file mode 100644 index 00000000000..845de2c2851 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/versions.proto @@ -0,0 +1,45 @@ +/* Copyright 2015 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.*/ +// Modification Copyright (C) 2021 Intel Corporation + +syntax = "proto3"; + +package tensorflow; + +option cc_enable_arenas = true; +option java_outer_classname = "VersionsProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/versions_go_proto"; + +// Version information for a piece of serialized data +// +// There are different types of versions for each type of data +// (GraphDef, etc.), but they all have the same common shape +// described here. +// +// Each consumer has "consumer" and "min_producer" versions (specified +// elsewhere). A consumer is allowed to consume this data if +// +// producer >= min_producer +// consumer >= min_consumer +// consumer not in bad_consumers +// +message VersionDef { + // The version of the code that produced this data. + int32 producer = 1; + + // Any consumer below this version is not allowed to consume this data. + int32 min_consumer = 2; + + // Specific consumer versions which are disallowed (e.g. due to bugs). + repeated int32 bad_consumers = 3; +} diff --git a/ngraph/frontend/tensorflow/src/tensorflow.cpp b/ngraph/frontend/tensorflow/src/tensorflow.cpp new file mode 100644 index 00000000000..f39d3b41808 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/tensorflow.cpp @@ -0,0 +1,19 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +extern "C" NGRAPH_HELPER_DLL_EXPORT ngraph::frontend::FrontEndVersion GetAPIVersion() { + return OV_FRONTEND_API_VERSION; +} + +extern "C" NGRAPH_HELPER_DLL_EXPORT void* GetFrontEndData() { + auto res = new ngraph::frontend::FrontEndPluginInfo(); + res->m_name = "tf"; + res->m_creator = []() { + return std::make_shared(); + }; + return res; +} diff --git a/ngraph/frontend/tensorflow/src/tf_framework_node.cpp b/ngraph/frontend/tensorflow/src/tf_framework_node.cpp new file mode 100644 index 00000000000..995eda18aaf --- /dev/null +++ b/ngraph/frontend/tensorflow/src/tf_framework_node.cpp @@ -0,0 +1,18 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include + +namespace ov { +namespace frontend { +namespace tf { + +void TFFrameworkNode::validate_and_infer_types() { + for (size_t i = 0; i < get_output_size(); ++i) { + set_output_type(i, ov::element::dynamic, PartialShape::dynamic()); + } +} +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/tf_framework_node.hpp b/ngraph/frontend/tensorflow/src/tf_framework_node.hpp new file mode 100644 index 00000000000..dceb1fc1378 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/tf_framework_node.hpp @@ -0,0 +1,50 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include +#include +#include + +#include "graph_iterator_proto.hpp" + +namespace ov { +namespace frontend { +namespace tf { + +class TFFrameworkNode : public ::ov::op::util::FrameworkNode { +public: + OPENVINO_OP("TFFrameworkNode", "util", ::ov::op::util::FrameworkNode); + + TFFrameworkNode(const std::shared_ptr& decoder, const OutputVector& inputs, size_t num_outputs) + : FrameworkNode(inputs, std::max(num_outputs, size_t(1))), + m_decoder(decoder) { + ov::op::util::FrameworkNodeAttrs attrs; + attrs.set_type_name(m_decoder->get_op_type()); + set_attrs(attrs); + + validate_and_infer_types(); + } + + void validate_and_infer_types() override; + + std::shared_ptr clone_with_new_inputs(const OutputVector& inputs) const override { + return std::make_shared(m_decoder, inputs, get_output_size()); + } + + std::string get_op_type() const { + return m_decoder->get_op_type(); + } + + std::shared_ptr get_decoder() const { + return m_decoder; + } + +private: + std::shared_ptr m_decoder; +}; +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/frontend/tensorflow/src/utils.cpp b/ngraph/frontend/tensorflow/src/utils.cpp new file mode 100644 index 00000000000..0e55e27618c --- /dev/null +++ b/ngraph/frontend/tensorflow/src/utils.cpp @@ -0,0 +1,20 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "utils.hpp" + +void ov::frontend::tf::SetTracingInfo(const std::string& op_name, const ov::Output& ng_node) { + auto node = ng_node.get_node_shared_ptr(); + node->set_friendly_name(op_name); + node->add_provenance_tag(op_name); +} + +void ov::frontend::tf::TFTensorShapeToNGraphShape(const tensorflow::TensorShapeProto& tf_shape, + ov::PartialShape* ng_shape) { + std::vector dims; + for (int i = 0; i < tf_shape.dim_size(); i++) { + dims.push_back(tf_shape.dim(i).size()); + } + *ng_shape = ov::PartialShape(dims); +} diff --git a/ngraph/frontend/tensorflow/src/utils.hpp b/ngraph/frontend/tensorflow/src/utils.hpp new file mode 100644 index 00000000000..1cfb0c2491e --- /dev/null +++ b/ngraph/frontend/tensorflow/src/utils.hpp @@ -0,0 +1,381 @@ +/* Copyright (C) 2018-2021 Intel Corporation + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + * + * We modified "ValuesFromConstNode" function from + * tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc file + * to integrate it with our infrastructure. The purpose and basic + * functionality remains the same. +==============================================================================*/ + +#pragma once + +#include + +#include "graph_iterator_proto.hpp" +#include "ngraph/ngraph.hpp" +#include "ngraph_conversions.hpp" +#include "node_context.hpp" + +namespace ov { +namespace frontend { +namespace tf { +namespace detail { +// TODO: avoid using directly: +using ::tensorflow::DataType; +using ::tensorflow::TensorProto; + +// TODO: separate interface from proto implementation; here is a proto implementation +class TensorWrapper { +public: + const TensorProto* tensor_def; + + TensorWrapper(const TensorProto* _tensor_def) : tensor_def(_tensor_def) {} + + // a hack to minimize amount of code + TensorWrapper& attrs() const { + return const_cast(*this); + } + + template + std::vector flat() const; + + size_t NumElements() const; + + DataType dtype() const; +}; + +} // namespace detail +} // namespace tf +} // namespace frontend +} // namespace ov + +namespace ov { +namespace frontend { +namespace tf { +using namespace ::ov::frontend::tf::detail; + +using OpMap = std::unordered_map>>; + +void extract_operation_name_and_port(const std::string& port_name, + std::string& operation_name, + size_t& port_index, + std::string& port_type); + +class Status { +public: + int status = 0; + std::string message; + + static Status OK() { + return Status(); + } + + Status(const std::string& x) : message(x), status(1) {} + Status() {} +}; + +inline bool operator!=(const Status& x, const Status& y) { + return x.status != y.status; +} + +inline std::ostream& operator<<(std::ostream& out, const Status& s) { + return out << s.message; +} + +#define TF_RETURN_IF_ERROR(S) \ + if ((S).status != 0) \ + throw S; + +class errors { +public: + static Status InvalidArgument(const std::string& x) { + return Status("InvalidArgument: " + x); + } + + static Status Internal(const std::string& x) { + return Status("Internal: " + x); + } + + static Status Unimplemented(const std::string& x) { + return Status("Unimplemented: " + x); + } +}; + +void SetTracingInfo(const std::string& op_name, const ov::Output& ng_node); + +template +static void MakePadding(const std::string& tf_padding_type, + const ov::Shape& ng_image_shape, + const ov::Shape& ng_kernel_shape, + const ov::Strides& ng_strides, + const ov::Shape& ng_dilations, + T& ng_padding_below, + T& ng_padding_above) { + if (tf_padding_type == "SAME") { + ov::Shape img_shape = {0, 0}; + img_shape.insert(img_shape.end(), ng_image_shape.begin(), ng_image_shape.end()); + ov::infer_auto_padding(img_shape, + ng_kernel_shape, + ng_strides, + ng_dilations, + ov::op::PadType::SAME_UPPER, + ng_padding_above, + ng_padding_below); + } else if (tf_padding_type == "VALID") { + ng_padding_below.assign(ng_image_shape.size(), 0); + ng_padding_above.assign(ng_image_shape.size(), 0); + } +} + +template +static void ConvertTensorDataToVector(const TensorWrapper& tensor, std::vector* vector) { + const Ttensor* data = tensor.flat().data(); + vector->resize(tensor.NumElements()); + for (int64_t i = 0; i < tensor.NumElements(); i++) { + (*vector)[i] = Tvector(data[i]); + } +} + +static bool VecStrCmp(const std::vector& a, const std::vector& b) { + return a == b; +} + +static Status ValidateInputCount(const NodeContext& op, size_t count) { + if (op.get_ng_input_size() != count) { + std::ostringstream buf; + buf << "\"" << op.get_name() << "\" requires " << count << " input(s), got " << op.get_ng_input_size() + << " instead"; + return errors::InvalidArgument(buf.str()); + } + return Status::OK(); +} + +static void ValidateInputCountMin(const ov::frontend::tf::NodeContext& node, size_t count) { + if (node.get_ng_input_size() < count) { + std::ostringstream buf; + buf << "\"" << node.get_name() << "\" requires at least " << count << " input(s), got " + << node.get_ng_input_size() << " instead"; + throw errors::InvalidArgument(buf.str()); + } +} + +// Check to make sure the axis dimension for reduction are in within range. +// Returns error if axis is out of range. Otherwise returns Status::OK(). +static Status CheckAxisDimInRange(const std::vector& axes, size_t rank) { + for (auto i : axes) { + if (i < -(int)rank || i >= (int)rank) { + std::ostringstream buf; + buf << "Axis Dimension is out of range. Got " << i << ", should be in range [-" << rank << ", " << rank + << ")"; + return errors::InvalidArgument(buf.str()); + } + } + return Status::OK(); +} + +// +// Helper for storing ops in ng_op_map. +// For most of the cases, op would have one output so +// std::vector ng_op_map[op_name] would contain one element. +// +// If storing more than one output_nodes, make sure it's in +// the same order as tensorflow would do that. +// +// Parameters: +// Builder::OpMap& ng_op_map - The TF-to-nGraph op map. +// std::string op_name - Name of the op. +// +// ov::Output output_node - ov::Node to store +// +static void SaveNgOp(OpMap& ng_op_map, const std::string& op_name, ov::Output output_node) { + // no need to try-catch, map[key] will create std::vector object + // if not exists + ng_op_map[op_name].push_back(output_node); +} + +template +ov::Output ConstructNgNode(const std::string& op_name, TArg&&... Args) { + auto ng_node = std::make_shared(std::forward(Args)...); + SetTracingInfo(op_name, ng_node); + return ng_node; +} + +static Status GetInputNode(const NodeContext op, size_t input_idx, ov::Output& result) { + // Stub + result = op.get_ng_input(input_idx); + return Status::OK(); +} + +namespace detail { +static Status GetInputNodes(const NodeContext&, size_t) { + return Status::OK(); +} + +template +static Status GetInputNodes(const NodeContext node, + size_t index, + ov::Output& result, + Arguments&... remaining) { + TF_RETURN_IF_ERROR(GetInputNode(node, index, result)); + return GetInputNodes(node, index + 1, remaining...); +} +} // namespace detail + +template +static Status GetInputNodes(const NodeContext& node, Arguments&... remaining) { + constexpr size_t args_len = sizeof...(Arguments); + TF_RETURN_IF_ERROR(ValidateInputCount(node, args_len)); + return detail::GetInputNodes(node, 0, remaining...); +} + +void TFTensorShapeToNGraphShape(const ::tensorflow::TensorShapeProto& tf_shape, ov::PartialShape* ng_shape); + +template +static void GetStaticInputVector(const ov::frontend::tf::NodeContext& node, + int64_t input_index, + std::vector* vector) { + ov::Output ng_input = node.get_ng_input(input_index); + auto constant = std::dynamic_pointer_cast(ng_input.get_node_shared_ptr()); + FRONT_END_GENERAL_CHECK(constant != nullptr, "Node ", node.get_name(), " can't be casted to Constant."); + *vector = constant->cast_vector(); +} + +// Taken from: tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc +// Extract values from a Const op to `values`. Returns true if succeeds. +// +// Modified with an extra `VecT` parameter to handle the case where the type +// in the std::vector does not match TensorFlow's notion of what the C++ type +// should be (e.g. when T is `bool`, we actually need a std::vector of `char` for +// compatibility with nGraph). +template +static Status ValuesFromConstNode(const ::ov::frontend::DecoderBase* node, + ov::Shape* const_tensor_shape, + std::vector* values) { + if (node->get_op_type() != "Const") { + return errors::InvalidArgument("TFNodeDecoder not a Const"); + } + auto dt1 = node->get_attribute("dtype", ::ov::VariantWrapper<::tensorflow::DataType>::get_type_info_static()); + FRONT_END_GENERAL_CHECK(dt1); + auto dt = std::dynamic_pointer_cast<::ov::VariantWrapper<::tensorflow::DataType>>(dt1)->get(); + + auto tensor_proto_var = + node->get_attribute("value", ::ov::VariantWrapper<::tensorflow::TensorProto>::get_type_info_static()); + FRONT_END_GENERAL_CHECK(tensor_proto_var); + auto tensor_proto = + std::dynamic_pointer_cast<::ov::VariantWrapper<::tensorflow::TensorProto>>(tensor_proto_var)->get(); + + const tensorflow::TensorShapeProto& shape = tensor_proto.tensor_shape(); + ov::PartialShape pshape; + TFTensorShapeToNGraphShape(shape, &pshape); + *const_tensor_shape = pshape.get_shape(); + FRONT_END_GENERAL_CHECK(!pshape.is_dynamic(), "Dynamic shapes are not supported in ValuesFromConstNode function"); + auto tensor_content = tensor_proto.tensor_content(); + std::vector tensor_values_plain(tensor_content.begin(), tensor_content.end()); + const T* tensor_values = reinterpret_cast(tensor_values_plain.data()); + + if (!tensor_values_plain.empty() && tensor_proto.has_tensor_shape()) { + // When tensor_shape is set, theoretically the representation of the data + // could be compressed. So, before copying values to the returned vector, + // make sure no compression happens. + // if (shape.dim_size() == 1 && shape.dim(0).size() == tensor_values_plain.size()/sizeof(T)) { + values->insert(values->end(), tensor_values, tensor_values + tensor_values_plain.size() / sizeof(T)); + return Status::OK(); + } + + const auto tensor_content_size = tensor_proto.tensor_content().size(); + if (tensor_content_size % sizeof(VecT)) { + std::cerr << "[ ERROR ] tensor_content_size (" << tensor_content_size << ") is not a multiple of " + << sizeof(VecT); + } + + // If tensor_content_size is zero, we'll have to take the values from + // int_val, float_val, etc. + if (tensor_content_size == 0) { + int64_t n_elements = 1; + for (auto i = 0; i < shape.dim_size(); i++) { + if (shape.dim(i).size() < 0) { + return errors::InvalidArgument("Const node has empty tensor and an unknown dimension size"); + } + n_elements *= shape.dim(i).size(); + } + values->resize(n_elements); + + auto val_lastsaved = (T)0; // cast + + for (auto i = 0; i < n_elements; i++) { + int64_t val_size = 0; + auto val_i = (T)0; // cast + switch (dt) { + // TODO: there are more element types to support + // here + case tensorflow::DT_INT32: + val_size = tensor_proto.int_val_size(); + if (val_size > 0) + val_i = tensor_proto.int_val()[i]; + break; + case tensorflow::DT_INT64: + val_size = tensor_proto.int64_val_size(); + if (val_size > 0) + val_i = tensor_proto.int64_val()[i]; + break; + case tensorflow::DT_FLOAT: + val_size = tensor_proto.float_val_size(); + if (val_size > 0) + val_i = tensor_proto.float_val()[i]; + break; + case tensorflow::DT_BOOL: + val_size = tensor_proto.bool_val_size(); + if (val_size > 0) + val_i = tensor_proto.bool_val()[i]; + break; + case tensorflow::DT_DOUBLE: + val_size = tensor_proto.double_val_size(); + if (val_size > 0) + val_i = tensor_proto.double_val()[i]; + break; + default: + NGRAPH_VLOG(0) << "Const node has empty tensor_proto and we don't know how to " + "handle this element type"; + return errors::Unimplemented("Encountered unknown element type " + DataType_Name(dt) + + " on an empty tensor_proto"); + } + if (val_size == 0) { + return errors::InvalidArgument("Empty values vector"); + } else if (i < val_size) { + (*values)[i] = val_i; + val_lastsaved = val_i; + } else { + (*values)[i] = val_lastsaved; + } + } + } else { + return Status::OK(); + } + + return Status::OK(); +} + +template +static Status MakeConstOp(const NodeContext& node, ov::element::Type et, ov::Output& ng_node) { + std::vector const_values; + ov::Shape ng_shape; + + TF_RETURN_IF_ERROR((ValuesFromConstNode(node.get_decoder(), &ng_shape, &const_values))); + + ng_node = ConstructNgNode(node.get_name(), et, ng_shape, const_values); + return Status::OK(); +} +} // namespace tf +} // namespace frontend +} // namespace ov diff --git a/ngraph/test/frontend/CMakeLists.txt b/ngraph/test/frontend/CMakeLists.txt index 798118442b4..f6362d551bc 100644 --- a/ngraph/test/frontend/CMakeLists.txt +++ b/ngraph/test/frontend/CMakeLists.txt @@ -11,6 +11,10 @@ if (NGRAPH_ONNX_FRONTEND_ENABLE) add_subdirectory(onnx) endif() +if (NGRAPH_TF_FRONTEND_ENABLE) + add_subdirectory(tensorflow) +endif() + set(SRC ${CMAKE_CURRENT_SOURCE_DIR}/mock_frontend.cpp) add_library(mock1_ngraph_frontend SHARED ${SRC}) diff --git a/ngraph/test/frontend/tensorflow/CMakeLists.txt b/ngraph/test/frontend/tensorflow/CMakeLists.txt new file mode 100644 index 00000000000..d4f7a8474c6 --- /dev/null +++ b/ngraph/test/frontend/tensorflow/CMakeLists.txt @@ -0,0 +1,66 @@ +# Copyright (C) 2018-2021 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +# + +set(TARGET_NAME "tensorflow_tests") + +file(GLOB_RECURSE SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) + +add_executable(${TARGET_NAME} ${SRC}) + +target_link_libraries(${TARGET_NAME} PRIVATE frontend_shared_test_classes) + +add_clang_format_target(${TARGET_NAME}_clang FOR_TARGETS ${TARGET_NAME}) + +install(TARGETS ${TARGET_NAME} + RUNTIME DESTINATION tests + COMPONENT tests + EXCLUDE_FROM_ALL) + +# Test model generating +ie_check_pip_package(tensorflow WARNING) + +set(TEST_TENSORFLOW_MODELS_DIRNAME test_model_zoo/tensorflow_test_models) +target_compile_definitions(${TARGET_NAME} PRIVATE -D TEST_TENSORFLOW_MODELS_DIRNAME=\"${TEST_TENSORFLOW_MODELS_DIRNAME}/\") + +# If 'tensorflow' is not found, code will still be compiled +# but models will not be generated and tests will fail +# This is done this way for 'code style' and check cases - cmake shall pass, but CI machine doesn't need to have +# 'tensorflow' installed to check code style +if (tensorflow_FOUND) + set(TEST_TENSORFLOW_MODELS ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TEST_TENSORFLOW_MODELS_DIRNAME}/) + + file(GLOB_RECURSE TENSORFLOW_GEN_SCRIPTS ${CMAKE_CURRENT_SOURCE_DIR}/test_models/gen_scripts/generate_*.py) + file(GLOB_RECURSE TENSORFLOW_ALL_SCRIPTS ${CMAKE_CURRENT_SOURCE_DIR}/*.py) + set(OUT_FILES "") + foreach(GEN_SCRIPT ${TENSORFLOW_GEN_SCRIPTS}) + get_filename_component(FILE_WE ${GEN_SCRIPT} NAME_WE) + set(OUT_DONE_FILE ${TEST_TENSORFLOW_MODELS}/${FILE_WE}_done.txt) + set(OUT_FILES ${OUT_DONE_FILE} ${OUT_FILES}) + add_custom_command(OUTPUT ${OUT_DONE_FILE} + COMMAND ${PYTHON_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/test_models/gen_wrapper.py + ${GEN_SCRIPT} + ${TEST_TENSORFLOW_MODELS} + ${OUT_DONE_FILE} + JOB_POOL four_jobs + DEPENDS ${TENSORFLOW_ALL_SCRIPTS} + ) + endforeach() + add_custom_target(tensorflow_test_models DEPENDS ${OUT_FILES}) + + install(DIRECTORY ${TEST_TENSORFLOW_MODELS} + DESTINATION tests/${TEST_TENSORFLOW_MODELS_DIRNAME} + COMPONENT tests + EXCLUDE_FROM_ALL) +else() + # Produce warning message at build time as well + add_custom_command(OUTPUT unable_build_tensorflow_models.txt + COMMAND ${CMAKE_COMMAND} + -E cmake_echo_color --red "Warning: Unable to generate tensorflow test models. Running '${TARGET_NAME}' will likely fail" + ) + add_custom_target(tensorflow_test_models DEPENDS unable_build_tensorflow_models.txt) +endif() + +add_dependencies(${TARGET_NAME} tensorflow_test_models) +add_dependencies(${TARGET_NAME} tensorflow_ngraph_frontend) diff --git a/ngraph/test/frontend/tensorflow/basic_api.cpp b/ngraph/test/frontend/tensorflow/basic_api.cpp new file mode 100644 index 00000000000..5aab060b8d6 --- /dev/null +++ b/ngraph/test/frontend/tensorflow/basic_api.cpp @@ -0,0 +1,23 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "basic_api.hpp" + +#include "tf_utils.hpp" + +using namespace ngraph; +using namespace ngraph::frontend; + +using TFBasicTest = FrontEndBasicTest; + +static const std::vector models{ + std::string("2in_2out/2in_2out.pb"), +}; + +INSTANTIATE_TEST_SUITE_P(TFBasicTest, + FrontEndBasicTest, + ::testing::Combine(::testing::Values(TF_FE), + ::testing::Values(std::string(TEST_TENSORFLOW_MODELS_DIRNAME)), + ::testing::ValuesIn(models)), + FrontEndBasicTest::getTestCaseName); diff --git a/ngraph/test/frontend/tensorflow/convert_model.cpp b/ngraph/test/frontend/tensorflow/convert_model.cpp new file mode 100644 index 00000000000..046063113fb --- /dev/null +++ b/ngraph/test/frontend/tensorflow/convert_model.cpp @@ -0,0 +1,23 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "convert_model.hpp" + +#include "tf_utils.hpp" + +using namespace ngraph; +using namespace ngraph::frontend; + +using TFConvertModelTest = FrontEndConvertModelTest; + +static const std::vector models{ + std::string("2in_2out/2in_2out.pb"), +}; + +INSTANTIATE_TEST_SUITE_P(TFConvertModelTest, + FrontEndConvertModelTest, + ::testing::Combine(::testing::Values(TF_FE), + ::testing::Values(std::string(TEST_TENSORFLOW_MODELS_DIRNAME)), + ::testing::ValuesIn(models)), + FrontEndConvertModelTest::getTestCaseName); diff --git a/ngraph/test/frontend/tensorflow/convert_unsupported.cpp b/ngraph/test/frontend/tensorflow/convert_unsupported.cpp new file mode 100644 index 00000000000..37ab156f428 --- /dev/null +++ b/ngraph/test/frontend/tensorflow/convert_unsupported.cpp @@ -0,0 +1,39 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +#include "common_test_utils/ngraph_test_utils.hpp" +#include "tf_utils.hpp" +#include "utils.hpp" + +using namespace ngraph; +using namespace ngraph::frontend; + +TEST(FrontEndConvertModelTest, test_unsupported_op) { + FrontEndManager fem; + FrontEnd::Ptr frontEnd; + InputModel::Ptr inputModel; + ASSERT_NO_THROW(frontEnd = fem.load_by_framework(TF_FE)); + ASSERT_NE(frontEnd, nullptr); + auto model_filename = FrontEndTestUtils::make_model_path(std::string(TEST_TENSORFLOW_MODELS_DIRNAME) + + std::string("relu_unsupported/relu_unsupported.pb")); + ASSERT_NO_THROW(inputModel = frontEnd->load(model_filename)); + ASSERT_NE(inputModel, nullptr); + std::shared_ptr function; + ASSERT_THROW(function = frontEnd->convert(inputModel), OpConversionFailure); + ASSERT_EQ(function, nullptr); + ASSERT_NO_THROW(function = frontEnd->decode(inputModel)); + ASSERT_THROW(frontEnd->convert(function), OpConversionFailure); + ASSERT_NO_THROW(function = frontEnd->convert_partially(inputModel)); + ASSERT_THROW(frontEnd->convert(function), OpConversionFailure); + + for (auto& node : function->get_ordered_ops()) { + if (node->get_friendly_name() == "relu_0") { + function->replace_node(node, std::make_shared(node->input(0).get_source_output())); + } + } + ASSERT_NO_THROW(frontEnd->convert(function)); +} diff --git a/ngraph/test/frontend/tensorflow/test_models/gen_scripts/generate_2in_2out.py b/ngraph/test/frontend/tensorflow/test_models/gen_scripts/generate_2in_2out.py new file mode 100644 index 00000000000..c1b6beedf0a --- /dev/null +++ b/ngraph/test/frontend/tensorflow/test_models/gen_scripts/generate_2in_2out.py @@ -0,0 +1,32 @@ +import numpy as np +import os +import sys +import tensorflow as tf + +tf.compat.v1.reset_default_graph() + +# Create the graph and model +with tf.compat.v1.Session() as sess: + input1 = tf.compat.v1.placeholder(tf.float32, [1, 3, 3, 1], 'inputX1') + input2 = tf.compat.v1.placeholder(tf.float32, [1, 3, 3, 1], 'inputX2') + + kernel1 = tf.constant(np.random.randn(1, 1, 1, 1), dtype=tf.float32) + kernel2 = tf.constant(np.random.randn(1, 1, 1, 1), dtype=tf.float32) + + conv2d1 = tf.nn.conv2d(input1, kernel1, strides=[1, 1], padding='VALID') + conv2d2 = tf.nn.conv2d(input2, kernel2, strides=[1, 1], padding='VALID') + + add1 = tf.add(conv2d1, conv2d2, name="add1") + + relu2a = tf.nn.relu(add1, name="relu2a") + relu2b = tf.nn.relu(add1, name="relu2b") + + add2 = tf.add(relu2a, relu2b, name="add2") + + tf.nn.relu(add2, name="relu3a") + tf.nn.relu(add2, name="relu3b") + + tf.compat.v1.global_variables_initializer() + tf_net = sess.graph_def + +tf.io.write_graph(tf_net, os.path.join(sys.argv[1], "2in_2out"), '2in_2out.pb', False) diff --git a/ngraph/test/frontend/tensorflow/test_models/gen_scripts/generate_unsupported_relu.py b/ngraph/test/frontend/tensorflow/test_models/gen_scripts/generate_unsupported_relu.py new file mode 100644 index 00000000000..2206d4e0598 --- /dev/null +++ b/ngraph/test/frontend/tensorflow/test_models/gen_scripts/generate_unsupported_relu.py @@ -0,0 +1,35 @@ +# +# unsupported relu tensorflow model generator +# + +import numpy as np +import os +import sys +import tensorflow as tf + + +def main(): + tf.compat.v1.reset_default_graph() + + # Create the graph and model + with tf.compat.v1.Session() as sess: + input = tf.compat.v1.placeholder(tf.float32, [1, 3, 3, 1], 'x') + + tf.nn.relu(input, name="relu_0") + + tf.compat.v1.global_variables_initializer() + tf_net = sess.graph_def + + tf.io.write_graph(tf_net, os.path.join(sys.argv[1], "relu_unsupported"), "relu_unsupported.pb", False) + + with open(os.path.join(sys.argv[1], "relu_unsupported", "relu_unsupported.pb"), mode='rb') as file: + modelContent = file.read() + + modelContent = modelContent.replace(b"Relu", b"Rxyz") + + with open(os.path.join(sys.argv[1], "relu_unsupported", "relu_unsupported.pb"), mode='wb') as file: + file.write(modelContent) + + +if __name__ == "__main__": + main() diff --git a/ngraph/test/frontend/tensorflow/test_models/gen_wrapper.py b/ngraph/test/frontend/tensorflow/test_models/gen_wrapper.py new file mode 100644 index 00000000000..bb982baca18 --- /dev/null +++ b/ngraph/test/frontend/tensorflow/test_models/gen_wrapper.py @@ -0,0 +1,20 @@ +import os +import subprocess + +import sys + +print(sys.argv) +if len(sys.argv) < 4: + print("Script, output folder and mark file must be specified as arguments") + exit(1) + +gen_script = sys.argv[1] +out_folder = sys.argv[2] +mark_file = sys.argv[3] + +print("Processing: {} ".format(gen_script)) +subprocess.run([sys.executable, gen_script, out_folder], env=os.environ) + +# Create mark file indicating that script was executed +with open(mark_file, "w") as fp: + pass diff --git a/ngraph/test/frontend/tensorflow/tf_utils.hpp b/ngraph/test/frontend/tensorflow/tf_utils.hpp new file mode 100644 index 00000000000..2e023d083fa --- /dev/null +++ b/ngraph/test/frontend/tensorflow/tf_utils.hpp @@ -0,0 +1,9 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include + +static const std::string TF_FE = "tf"; diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 3329fadfc5b..30e0ac1f492 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -74,7 +74,7 @@ endif() # Protobuf # -if(NGRAPH_PDPD_FRONTEND_ENABLE OR NGRAPH_ONNX_FRONTEND_ENABLE) +if(NGRAPH_PDPD_FRONTEND_ENABLE OR NGRAPH_ONNX_FRONTEND_ENABLE OR NGRAPH_TF_FRONTEND_ENABLE) if(NGRAPH_USE_SYSTEM_PROTOBUF) set(Protobuf_USE_STATIC_LIBS ON) if(VERBOSE_BUILD)