diff --git a/tools/convert_tool/CMakeLists.txt b/tools/convert_tool/CMakeLists.txt index 2537bbac5..a7d6d03da 100644 --- a/tools/convert_tool/CMakeLists.txt +++ b/tools/convert_tool/CMakeLists.txt @@ -61,6 +61,47 @@ list(APPEND CAFFE_SERIALIZER_SRCS ${CAFFE_PROTO_SRC}) # NCNN file(GLOB_RECURSE NCNN_SERIALIZER_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/ncnn/*.cpp") +# TENSORFLOW +file(GLOB_RECURSE TF_SERIALIZER_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/tensorflow/*.cpp") + +list(APPEND TENGINE_LIB_SRCS ${serializer_src}) + +# the generated pb.cc +set(TF_PROTO_SRC ${CMAKE_CURRENT_BINARY_DIR}/tensorflow/graph.pb.cc + ${CMAKE_CURRENT_BINARY_DIR}/tensorflow/function.pb.cc + ${CMAKE_CURRENT_BINARY_DIR}/tensorflow/node_def.pb.cc + ${CMAKE_CURRENT_BINARY_DIR}/tensorflow/op_def.pb.cc + ${CMAKE_CURRENT_BINARY_DIR}/tensorflow/attr_value.pb.cc + ${CMAKE_CURRENT_BINARY_DIR}/tensorflow/tensor.pb.cc + ${CMAKE_CURRENT_BINARY_DIR}/tensorflow/tensor_shape.pb.cc + ${CMAKE_CURRENT_BINARY_DIR}/tensorflow/types.pb.cc + ${CMAKE_CURRENT_BINARY_DIR}/tensorflow/versions.pb.cc + ${CMAKE_CURRENT_BINARY_DIR}/tensorflow/resource_handle.pb.cc) + +set(TF_PROTO_PATH ${CMAKE_CURRENT_SOURCE_DIR}/tensorflow) +set(TF_PROTO_OUT_PATH ${CMAKE_CURRENT_BINARY_DIR}/tensorflow) + +ADD_CUSTOM_COMMAND(OUTPUT ${TF_PROTO_SRC} + COMMAND mkdir -p ${TF_PROTO_OUT_PATH} + COMMAND ${Protobuf_PROTOC_EXECUTABLE} --cpp_out=${TF_PROTO_OUT_PATH} --proto_path=${TF_PROTO_PATH} ${TF_PROTO_PATH}/graph.proto + COMMAND ${Protobuf_PROTOC_EXECUTABLE} --cpp_out=${TF_PROTO_OUT_PATH} --proto_path=${TF_PROTO_PATH} ${TF_PROTO_PATH}/function.proto + COMMAND ${Protobuf_PROTOC_EXECUTABLE} --cpp_out=${TF_PROTO_OUT_PATH} --proto_path=${TF_PROTO_PATH} ${TF_PROTO_PATH}/node_def.proto + COMMAND ${Protobuf_PROTOC_EXECUTABLE} --cpp_out=${TF_PROTO_OUT_PATH} --proto_path=${TF_PROTO_PATH} ${TF_PROTO_PATH}/op_def.proto + COMMAND ${Protobuf_PROTOC_EXECUTABLE} --cpp_out=${TF_PROTO_OUT_PATH} --proto_path=${TF_PROTO_PATH} ${TF_PROTO_PATH}/attr_value.proto + COMMAND ${Protobuf_PROTOC_EXECUTABLE} --cpp_out=${TF_PROTO_OUT_PATH} --proto_path=${TF_PROTO_PATH} ${TF_PROTO_PATH}/tensor.proto + COMMAND ${Protobuf_PROTOC_EXECUTABLE} --cpp_out=${TF_PROTO_OUT_PATH} --proto_path=${TF_PROTO_PATH} ${TF_PROTO_PATH}/tensor_shape.proto + COMMAND ${Protobuf_PROTOC_EXECUTABLE} --cpp_out=${TF_PROTO_OUT_PATH} --proto_path=${TF_PROTO_PATH} ${TF_PROTO_PATH}/types.proto + COMMAND ${Protobuf_PROTOC_EXECUTABLE} --cpp_out=${TF_PROTO_OUT_PATH} --proto_path=${TF_PROTO_PATH} ${TF_PROTO_PATH}/versions.proto + COMMAND ${Protobuf_PROTOC_EXECUTABLE} --cpp_out=${TF_PROTO_OUT_PATH} --proto_path=${TF_PROTO_PATH} ${TF_PROTO_PATH}/resource_handle.proto + #COMMAND mv ${TF_PROTO_OUT_PATH}/*.pb.h ${TF_PROTO_PATH}/../include/ +) + +ADD_CUSTOM_TARGET(TF_SERIALIZER_TARGET DEPENDS ${TF_PROTO_OUT_PATH}) + +include_directories(${TF_PROTO_OUT_PATH}) + +list(APPEND TF_SERIALIZER_SRCS ${TF_PROTO_SRC}) + # SAVE GRAPH FILE(GLOB_RECURSE SAVE_GRAPH_SRCS "${CMAKE_SOURCE_DIR}/tools/save_graph/*.cpp" "${CMAKE_SOURCE_DIR}/tools/save_graph/*.c") @@ -73,6 +114,7 @@ FILE(GLOB_RECURSE CONVERT_TOOL_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/convert_tool.cp list(APPEND CONVERT_TOOL_SRCS ${ONNX_SERIALIZER_SRCS}) list(APPEND CONVERT_TOOL_SRCS ${CAFFE_SERIALIZER_SRCS}) list(APPEND CONVERT_TOOL_SRCS ${NCNN_SERIALIZER_SRCS}) +list(APPEND CONVERT_TOOL_SRCS ${TF_SERIALIZER_SRCS}) list(APPEND CONVERT_TOOL_SRCS ${SAVE_GRAPH_SRCS}) list(APPEND CONVERT_TOOL_SRCS ${GRAPH_OPT_SRCS}) diff --git a/tools/convert_tool/convert_tool.cpp b/tools/convert_tool/convert_tool.cpp index 491f39e7d..9221bb44f 100644 --- a/tools/convert_tool/convert_tool.cpp +++ b/tools/convert_tool/convert_tool.cpp @@ -31,6 +31,7 @@ #include "onnx/onnx2tengine.hpp" #include "caffe/caffe2tengine.hpp" #include "ncnn/ncnn2tengine.hpp" +#include "tensorflow/tf2tengine.hpp" #include "utils/graph_optimizer/graph_opt.hpp" const char* help_params = "[Convert Tools Info]: optional arguments:\n" @@ -178,6 +179,11 @@ int main(int argc, char* argv[]) ncnn_serializer n2t; graph = n2t.ncnn2tengine(model_file, proto_file); } + else if (file_format == "tensorflow") + { + tensorflow_serializer tf2t; + graph = tf2t.tensorflow2tengine(model_file); + } else { fprintf(stderr, "Convert model failed: support onnx only...\n"); diff --git a/tools/convert_tool/tensorflow/attr_value.proto b/tools/convert_tool/tensorflow/attr_value.proto new file mode 100644 index 000000000..74e2a3b0a --- /dev/null +++ b/tools/convert_tool/tensorflow/attr_value.proto @@ -0,0 +1,62 @@ +syntax = "proto3"; + +package tensorflow; +option cc_enable_arenas = true; +option java_outer_classname = "AttrValueProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; + +import "tensor.proto"; +import "tensor_shape.proto"; +import "types.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/tools/convert_tool/tensorflow/function.proto b/tools/convert_tool/tensorflow/function.proto new file mode 100644 index 000000000..ccb12cb4f --- /dev/null +++ b/tools/convert_tool/tensorflow/function.proto @@ -0,0 +1,101 @@ +syntax = "proto3"; + +package tensorflow; +option cc_enable_arenas = true; +option java_outer_classname = "FunctionProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; + +import "attr_value.proto"; +import "node_def.proto"; +import "op_def.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; + + // NOTE: field id 2 deleted on Jan 11, 2016, GraphDef version 21. + + // 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; +} + +// 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/tools/convert_tool/tensorflow/graph.proto b/tools/convert_tool/tensorflow/graph.proto new file mode 100644 index 000000000..65119189f --- /dev/null +++ b/tools/convert_tool/tensorflow/graph.proto @@ -0,0 +1,56 @@ +syntax = "proto3"; + +package tensorflow; +option cc_enable_arenas = true; +option java_outer_classname = "GraphProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; + +import "node_def.proto"; +import "function.proto"; +import "versions.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]; + + // EXPERIMENTAL. DO NOT USE OR DEPEND ON THIS YET. + // + // "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/tools/convert_tool/tensorflow/node_def.proto b/tools/convert_tool/tensorflow/node_def.proto new file mode 100644 index 000000000..e6d545ad5 --- /dev/null +++ b/tools/convert_tool/tensorflow/node_def.proto @@ -0,0 +1,63 @@ +syntax = "proto3"; + +package tensorflow; +option cc_enable_arenas = true; +option java_outer_classname = "NodeProto"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; + +import "attr_value.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]*) + // | ( ("gpu" | "cpu") ":" ([1-9][0-9]* | "*") ) + // + // Valid values for this string include: + // * "/job:worker/replica:0/task:1/gpu:3" (full specification) + // * "/job:worker/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; +}; diff --git a/tools/convert_tool/tensorflow/op_def.proto b/tools/convert_tool/tensorflow/op_def.proto new file mode 100644 index 000000000..baf68eaad --- /dev/null +++ b/tools/convert_tool/tensorflow/op_def.proto @@ -0,0 +1,157 @@ +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"; + +import "attr_value.proto"; +import "types.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. +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; + + // 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; + + // 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. + + // 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. +}; + +// 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/tools/convert_tool/tensorflow/resource_handle.proto b/tools/convert_tool/tensorflow/resource_handle.proto new file mode 100644 index 000000000..b1921337f --- /dev/null +++ b/tools/convert_tool/tensorflow/resource_handle.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; + +package tensorflow; +option cc_enable_arenas = true; +option java_outer_classname = "ResourceHandle"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; + +// 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; +}; diff --git a/tools/convert_tool/tensorflow/tensor.proto b/tools/convert_tool/tensorflow/tensor.proto new file mode 100644 index 000000000..c79293023 --- /dev/null +++ b/tools/convert_tool/tensorflow/tensor.proto @@ -0,0 +1,88 @@ +syntax = "proto3"; + +package tensorflow; +option cc_enable_arenas = true; +option java_outer_classname = "TensorProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; + +import "resource_handle.proto"; +import "tensor_shape.proto"; +import "types.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. 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; +}; + +// 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/tools/convert_tool/tensorflow/tensor_shape.proto b/tools/convert_tool/tensorflow/tensor_shape.proto new file mode 100644 index 000000000..1ec3c5323 --- /dev/null +++ b/tools/convert_tool/tensorflow/tensor_shape.proto @@ -0,0 +1,45 @@ +// 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"; + +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/tools/convert_tool/tensorflow/tf2tengine.cpp b/tools/convert_tool/tensorflow/tf2tengine.cpp new file mode 100644 index 000000000..db593fc10 --- /dev/null +++ b/tools/convert_tool/tensorflow/tf2tengine.cpp @@ -0,0 +1,2408 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/* + * Copyright (c) 2021, OPEN AI LAB + * Author: bzhang@openailab.com + */ + +#include "tf2tengine.hpp" + +const int OP_VERSION = 1; + +bool tensorflow_serializer::find_op_load_method(const std::string& op_name) +{ + if (op_load_map.count(op_name)) + return true; + + return false; +} + +ir_tensor_t* tensorflow_serializer::find_tensor(ir_graph_t* graph, const std::string& tensor_name) +{ + for (uint16_t i = 0; i < graph->tensor_num; i++) + { + ir_tensor_t* tensor = get_ir_graph_tensor(graph, i); + if (tensor->name == tensor_name) + { + return tensor; + } + } + + return nullptr; +} + +static bool GetAttrValue(const tensorflow::NodeDef* node, const char* key, tensorflow::AttrValue& value) +{ + const google::protobuf::Map& attr = node->attr(); + + const google::protobuf::Map::const_iterator it = attr.find(key); + if (it != attr.end()) + { + value = it->second; + return true; + } + + return false; +} + +int GetTensorContentAndDim(const tensorflow::TensorProto& tf_tensor, int** dims, void** mem_ptr, + std::string& layout) +{ + const tensorflow::TensorShapeProto& shape = tf_tensor.tensor_shape(); + + int elem_num = 1; + int dim_num = shape.dim_size(); + + int* dim_tmp = (int*)malloc(sizeof(int) * dim_num); + for (int i = 0; i < dim_num; i++) + { + elem_num *= shape.dim(i).size(); + dim_tmp[i] = shape.dim(i).size(); + } + + void* mem_buf = nullptr; + + if (tf_tensor.tensor_content().size()) + { + int content_size = tf_tensor.tensor_content().size(); + mem_buf = malloc(content_size + 128); + void* src = (void*)tf_tensor.tensor_content().c_str(); + memcpy(mem_buf, src, content_size); + } + else if (tf_tensor.dtype() == tensorflow::DataType::DT_FLOAT) + { + // in packed format + int data_num = tf_tensor.float_val_size(); + mem_buf = malloc(elem_num * sizeof(float)); + float* mem = (float*)mem_buf; + if (data_num >= elem_num) + { + for (int i = 0; i < elem_num; i++) + { + mem[i] = tf_tensor.float_val(i); + } + } + else + { + // data_num < elem_num + for (int i = 0; i < data_num; i++) + { + mem[i] = tf_tensor.float_val(i); + } + + for (int i = data_num; i < elem_num; i++) + { + mem[i] = mem[data_num - 1]; + } + } + } + else if (tf_tensor.dtype() == tensorflow::DataType::DT_INT32) + { + int data_num = tf_tensor.int_val_size(); + + mem_buf = malloc(elem_num * sizeof(int)); + + int* mem = (int*)mem_buf; + + if (data_num >= elem_num) + { + for (int i = 0; i < elem_num; i++) + { + mem[i] = tf_tensor.int_val(i); + } + } + else + { + // data_num < elem_num + for (int i = 0; i < data_num; i++) + { + mem[i] = tf_tensor.int_val(i); + } + + for (int i = data_num; i < elem_num; i++) + { + mem[i] = mem[data_num - 1]; + } + } + } + + *mem_ptr = mem_buf; + *dims = dim_tmp; + switch (dim_num) + { + case 0: + layout = "W"; + break; + case 1: + layout = "W"; + break; + case 2: + layout = "HW"; + break; + case 4: + layout = "NCHW"; + break; + default: + break; + } + return dim_num; +} +int tensorflow_serializer::load_binary_file(std::string model_file) +{ + std::ifstream is(model_file.c_str(), std::ios::in | std::ios::binary); + + if (!is.is_open()) + { + TLOG_ERR("cannot open file: %s \n", model_file.c_str()); + return false; + } + + google::protobuf::io::IstreamInputStream input_stream(&is); + google::protobuf::io::CodedInputStream coded_input(&input_stream); + +#if GOOGLE_PROTOBUF_VERSION >= 3011000 + coded_input.SetTotalBytesLimit(INT_MAX); +#else + coded_input.SetTotalBytesLimit(INT_MAX, INT_MAX / 2); +#endif + bool ret = tf_net.ParseFromCodedStream(&coded_input); + + is.close(); + + if (!ret) + { + TLOG_ERR("parse file: %s failed\n", model_file.c_str()); + return -1; + } + return ret; +} +int load_const_tensor(TFNode* tf_node, ir_graph_t* graph) +{ + ir_node_t* node = create_ir_node(graph, tf_node->name.c_str(), OP_CONST, OP_VERSION); + ir_tensor_t* tensor = create_ir_tensor(graph, tf_node->name.c_str(), TENGINE_DT_FP32); + tensorflow::AttrValue value; + const tensorflow::NodeDef* node_def = tf_node->pb_defs[0]; + if (GetAttrValue(node_def, "value", value)) + { + const tensorflow::TensorProto& tf_tensor = value.tensor(); + void* mem_ptr; + int* dims; + std::string layout; + int dim_num = GetTensorContentAndDim(tf_tensor, &dims, &mem_ptr, layout); + set_ir_tensor_shape(tensor, dims, dim_num); + + int mem_size = 1; + for (int i = 0; i < dim_num; i++) + mem_size *= dims[i]; + tensor->data = (float*)malloc(sizeof(float) * mem_size); + tensor->tensor_type = TENSOR_TYPE_CONST; + + float* tmp = (float*)mem_ptr; + float* tr_tmp = (float*)tensor->data; + for (int i = 0; i < mem_size; i++) + { + tr_tmp[i] = tmp[i]; + } + } + set_ir_node_output_tensor(node, 0, tensor); + tf_node->ir_node = node; + tf_node->ir_tensor = tensor; +} + +int tensorflow_serializer::set_graph_input(ir_graph_t* graph) +{ + int node_num = tf_graph.seq_nodes.size(); + std::vector input_nodes; + for (int i = 0; i < node_num; i++) + { + TFNode* tf_node = tf_graph.seq_nodes[i]; + if (tf_node->op == "Placeholder") + { + ir_tensor_t* ir_tensor = create_ir_tensor(graph, tf_node->name.c_str(), TENGINE_DT_FP32); + tensorflow::AttrValue shape; + + int pb_defs_cnt = tf_node->pb_defs.size(); + int* dims; + if (pb_defs_cnt == 1) + { + if (GetAttrValue(tf_node->pb_defs[0], "shape", shape)) + { + int dim_size = shape.shape().dim_size(); + + dims = (int*)sys_malloc(dim_size); + memset(dims, 0, sizeof(int) * dim_size); + for (int i = 0; i < dim_size; ++i) + { + dims[i] = shape.shape().dim(i).size(); + } + if (dim_size == 4) + { + dims[0] = shape.shape().dim(0).size() == -1 ? 1 : shape.shape().dim(0).size(); + dims[1] = shape.shape().dim(3).size(); + dims[2] = shape.shape().dim(1).size(); + dims[3] = shape.shape().dim(2).size(); + } + set_ir_tensor_shape(ir_tensor, dims, dim_size); + } + } + else + { + tensorflow::AttrValue value; + const tensorflow::NodeDef* node_def = tf_node->pb_defs[pb_defs_cnt - 1]; + if (GetAttrValue(node_def, "value", value)) + { + const tensorflow::TensorProto& tf_tensor = value.tensor(); + + void* mem_ptr; + std::vector tf_dims; + std::string layout; + int dim_num = GetTensorContentAndDim(tf_tensor, &dims, &mem_ptr, layout); + + int mem_size = 1; + for (int i = 0; i < dim_num; i++) + mem_size *= dims[i]; + ir_tensor->data = (float*)malloc(sizeof(float) * mem_size); + ir_tensor->tensor_type = TENSOR_TYPE_CONST; + // tensor->data = mem_ptr; + float* tmp = (float*)mem_ptr; + float* tr_tmp = (float*)ir_tensor->data; + for (int i = 0; i < mem_size; i++) + tr_tmp[i] = tmp[i]; + + int* reshape_dim = (int*)mem_ptr; + for (int i = 0; i < tf_dims[0]; i++) + { + dims[i] = reshape_dim[i]; + } + + for (unsigned int i = 0; i < tf_dims[0]; i++) + { + if (dims[i] == -1) + dims[i] = 1; + } + free(mem_ptr); + set_ir_tensor_shape(ir_tensor, dims, tf_dims[0]); + } + } + ir_node_t* node = create_ir_node(graph, tf_node->name.c_str(), OP_INPUT, OP_VERSION); + + int tensor_id = get_ir_tensor_index_from_name(graph, tf_node->name.c_str()); + + set_ir_node_output_tensor(node, 0, ir_tensor); + input_nodes.push_back(node->index); + } + } + int16_t* node_idx = (int16_t*)sys_malloc(sizeof(int16_t) * input_nodes.size()); + for (int i = 0; i < input_nodes.size(); i++) + { + node_idx[i] = input_nodes[i]; + } + set_ir_graph_input_node(graph, node_idx, input_nodes.size()); + return 0; +} +int tensorflow_serializer::construct_graph() +{ + int node_number = tf_net.node_size(); + std::unordered_map node_map; + + /* first scan, setup all nodes */ + for (int i = 0; i < node_number; i++) + { + const tensorflow::NodeDef& node_param = tf_net.node(i); + + TFNode* tf_node = new TFNode(); + + tf_node->idx = i; + tf_node->name = node_param.name(); + tf_node->op = node_param.op(); + tf_node->pb_defs.push_back(&tf_net.node(i)); + + tf_graph.seq_nodes.push_back(tf_node); + + node_map[tf_node->name] = tf_node; + } + + /* the second scan, setup connections */ + for (int i = 0; i < node_number; i++) + { + const tensorflow::NodeDef& node_param = tf_net.node(i); + const std::string& name = node_param.name(); + + TFNode* cur_node = node_map[name]; + + for (int j = 0; j < node_param.input_size(); j++) + { + const std::string& input_name = node_param.input(j); + std::string::size_type pos = input_name.find(":"); + std::string cleanup_name; + + if (pos == std::string::npos) + pos = input_name.size(); + + if (input_name[0] == '^') + cleanup_name = input_name.substr(1, pos); + else + cleanup_name = input_name.substr(0, pos); + + TFNode* input_node = node_map[cleanup_name]; + + if (input_node == nullptr) + { + TLOG_ERR("cannot find input: %s for node: %s \n", input_name.c_str(), name.c_str()); + return false; + } + + cur_node->inputs.push_back(input_node); + input_node->outputs.push_back(cur_node); + } + } + return 0; +} +int DisconnectNode(TFNode* cur_node) +{ + TFNode* input_node; + + for (unsigned int i = 0; i < cur_node->inputs.size(); i++) + { + input_node = cur_node->inputs[i]; + + auto ir = input_node->outputs.begin(); + + while (ir != input_node->outputs.end()) + { + if (*ir != cur_node) + ir++; + else + break; + } + + if (ir == input_node->outputs.end()) + { + TLOG_ERR("ERROR on node connection!!\n"); + } + + input_node->outputs.erase(ir); + } + + cur_node->inputs.clear(); + + TFNode* output_node; + + for (unsigned int i = 0; i < cur_node->outputs.size(); i++) + { + output_node = cur_node->outputs[i]; + + auto ir = output_node->inputs.begin(); + + while (ir != output_node->inputs.end()) + { + if (*ir != cur_node) + ir++; + else + break; + } + + if (ir == output_node->inputs.end()) + { + TLOG_ERR("ERROR on node connection!!\n"); + } + + output_node->inputs.erase(ir); + } + + cur_node->outputs.clear(); +} + +int tensorflow_serializer::MergeParentNode(TFNode* base_node, TFNode* parent_node) +{ + /* remove the input for parent node */ + + auto input_ir = base_node->inputs.begin(); + + while (input_ir != base_node->inputs.end()) + { + if (*input_ir == parent_node) + break; + + input_ir++; + } + + if (parent_node->inputs.size() == 1) + { + *input_ir = parent_node->inputs[0]; + } + else + { + base_node->inputs.erase(input_ir); + /* connect parent's input node and base node */ + + base_node->inputs.insert(base_node->inputs.end(), parent_node->inputs.begin(), parent_node->inputs.end()); + } + + /* setup the outputs of parent node's parent */ + + for (auto node : parent_node->inputs) + { + for (unsigned int i = 0; i < node->outputs.size(); i++) + { + if (node->outputs[i] == parent_node) + { + node->outputs[i] = base_node; + break; + } + } + } + + /* bridge parent's output, for those edges do not connect with base node */ + + auto output_ir = parent_node->outputs.begin(); + + while (output_ir != parent_node->outputs.end()) + { + TFNode* node = *output_ir; + + if (node != base_node) + { + base_node->outputs.push_back(node); + + for (unsigned int i = 0; i < node->inputs.size(); i++) + { + if (node->inputs[i] == parent_node) + { + node->inputs[i] = base_node; + break; + } + } + } + + output_ir++; + } + + /* handle TF definitions */ + + base_node->pb_defs.insert(base_node->pb_defs.end(), parent_node->pb_defs.begin(), parent_node->pb_defs.end()); + + // std::cout<<"base node: "<name<<" merge parent: "<name<<"\n"; + + parent_node->inputs.clear(); + parent_node->outputs.clear(); + + return 0; +} + +bool CheckComposedBNAdd(TFNode* cur_node) +{ + if (cur_node->op != "Add") + return false; + + TFNode* input0 = cur_node->inputs[0]; + TFNode* input1 = cur_node->inputs[1]; + + if (input0->op != "Mul" || input1->op != "Sub") + return false; + + /* further check: /add_1 int name */ + if (cur_node->name.find("/add_1") != std::string::npos) + { + if (input0->name.find("/mul_1") != std::string::npos || input1->name.find("/mul_1") != std::string::npos) + cur_node->BNAddType = 1; + else + cur_node->BNAddType = 0; + + return true; + } + + return false; +} + +int tensorflow_serializer::BNRecursiveInputMerge(TFNode* node) +{ + bool mul_1_node = false; + bool mul_node = false; + if (node->name.find("/mul") != std::string::npos) + { + if (node->BNAddType == 1) + { + if (node->name.find("/mul_1") != std::string::npos) + { + mul_1_node = true; + } + else if (node->name.find("/mul_2") == std::string::npos) + { + // disconnect the connection between mul and mul2 + auto ir = node->outputs.begin(); + + if ((*ir)->name.find("/mul2") == std::string::npos) + ir++; + + TFNode* mul2_node = *ir; + + node->outputs.erase(ir); + + ir = mul2_node->inputs.begin(); + + if ((*ir)->name.find("/mul") == std::string::npos) + ir++; + + mul2_node->inputs.erase(ir); + } + } + else + { + if (node->name.find("/mul_1") != std::string::npos) + { + // disconnect the connection between add_1 mul_1 + auto ir = node->inputs.begin(); + + if ((*ir)->name.find("/add_1") == std::string::npos) + ir++; + + if ((*ir)->name.find("/add_1") != std::string::npos) + { + TFNode* Rsqrt_node = *ir; + + node->inputs.erase(ir); + + ir = Rsqrt_node->outputs.begin(); + + if ((*ir)->name.find("/mul_1") == std::string::npos) + ir++; + + Rsqrt_node->outputs.erase(ir); + } + } + else + { + mul_node = true; + } + } + } + + int orig_input_size = node->inputs.size(); + std::vector input_cpy = node->inputs; + + for (int i = 0; i < orig_input_size; i++) + { + if (mul_node && i == 0) + continue; + if (mul_1_node && i == 0) + continue; + + TFNode* input_node = input_cpy[i]; + input_node->BNAddType = node->BNAddType; + if (input_node->op == "Const") + continue; + + BNRecursiveInputMerge(input_node); + MergeParentNode(node, input_node); + } +} +int tensorflow_serializer::FuseComposedBN(TFNode* cur_node) +{ + BNRecursiveInputMerge(cur_node); + cur_node->op = "ComposedBN"; + + /* set new name */ + auto pos = cur_node->name.find("/add_1"); + cur_node->name.replace(pos, strlen("/add_1"), "bn.fused"); + + /* skip to create static node for add/y */ + + for (unsigned int i = 0; i < cur_node->inputs.size(); i++) + { + TFNode* node = cur_node->inputs[i]; + + if (node->name.find("/add/y") != std::string::npos) + node->no_static_node = true; + } +} +int tensorflow_serializer::MergeChildNode(TFNode* base_node, TFNode* child_node) +{ + auto output_ir = base_node->outputs.begin(); + + while (output_ir != base_node->outputs.end()) + { + if (*output_ir == child_node) + break; + output_ir++; + } + + if (child_node->outputs.size() == 1) + { + *output_ir = child_node->outputs[0]; + } + else + { + base_node->outputs.erase(output_ir); + base_node->outputs.insert(base_node->outputs.end(), child_node->outputs.begin(), child_node->outputs.end()); + } + + for (auto node : child_node->outputs) + { + for (unsigned int i = 0; i < node->inputs.size(); i++) + { + if (node->inputs[i] == child_node) + { + node->inputs[i] = base_node; + break; + } + } + } + + auto ir = child_node->inputs.begin(); + + while (ir != child_node->inputs.end()) + { + TFNode* node = *ir; + + if (node != base_node) + { + base_node->inputs.push_back(node); + + for (unsigned int i = 0; i < node->outputs.size(); i++) + { + if (node->outputs[i] == child_node) + { + node->outputs[i] = base_node; + break; + } + } + } + + ir++; + } + + base_node->pb_defs.insert(base_node->pb_defs.end(), child_node->pb_defs.begin(), child_node->pb_defs.end()); + + // std::cout<<"base node: "<name<<" merge child: "<name<<"\n"; + + child_node->inputs.clear(); + child_node->outputs.clear(); + + return 0; +} + +void tensorflow_serializer::CleanupResizeNearestNeighbor() +{ + auto ir = tf_graph.seq_nodes.begin(); + + while (ir != tf_graph.seq_nodes.end()) + { + TFNode* cur_node = *ir; + + if (cur_node->op == "ResizeNearestNeighbor") + { + TFNode* data_node = cur_node->inputs[0]; + TFNode* data_shape_node = nullptr; + + for (unsigned int i = 0; i < data_node->outputs.size(); i++) + { + data_shape_node = data_node->outputs[i]; + + if (data_shape_node->op == "Shape") + break; + } + + DisconnectNode(data_shape_node); + + TFNode* mul_node = cur_node->inputs[1]; + TFNode* stride_slice = mul_node->inputs[0]; + DisconnectNode(stride_slice); + DisconnectNode(mul_node); + } + + ir++; + } +} + +void tensorflow_serializer::MergeReluMinimum() +{ + for (auto ir = tf_graph.seq_nodes.begin(); ir != tf_graph.seq_nodes.end(); ir++) + { + TFNode* cur_node = *ir; + + if (cur_node->inputs.size() == 0) + continue; + + TFNode* input0 = cur_node->inputs[0]; + + if (cur_node->op == "Minimum" && input0->op == "Relu") + { + TFNode* const_node = cur_node->inputs[1]; + + DisconnectNode(const_node); + + MergeChildNode(input0, cur_node); + + input0->op = "Relu6"; + } + } +} + +int tensorflow_serializer::optimize_graph() +{ + /* first clean up the predictions module of TF */ + auto ir = tf_graph.seq_nodes.begin(); + + while (ir != tf_graph.seq_nodes.end()) + { + TFNode* cur_node = *ir; + + if (cur_node->op == "Reshape") + { + /* Reshape should have two inputs */ + + TFNode* input_node0 = cur_node->inputs[0]; + TFNode* input_node1 = cur_node->inputs[1]; + + if (input_node0->op == "Softmax" || input_node1->op == "Softmax") + { + DisconnectNode(cur_node); + ir = tf_graph.seq_nodes.erase(ir); + delete cur_node; + continue; + } + + TFNode* output_node = cur_node->outputs[0]; + if (NULL == output_node) + continue; + + if (output_node->op == "Softmax" || output_node->op == "MatMul") + { + TFNode* input_node0 = cur_node->inputs[0]; + TFNode* input_node1 = cur_node->inputs[1]; + TFNode* input_node; + + if (input_node0->op == "Const") + { + DisconnectNode(input_node0); + input_node = input_node1; + } + else + { + DisconnectNode(input_node1); + input_node = input_node0; + } + + MergeChildNode(input_node, cur_node); + + ir = tf_graph.seq_nodes.erase(ir); + delete cur_node; + continue; + } + } + + ir++; + } + + /* remove the squeeze node and identity */ + ir = tf_graph.seq_nodes.begin(); + + while (ir != tf_graph.seq_nodes.end()) + { + TFNode* cur_node = *ir; + + if (cur_node->op == "Squeeze") + { + TFNode* softmax_node = nullptr; + TFNode* shape_node = nullptr; + + for (unsigned int j = 0; j < cur_node->outputs.size(); j++) + { + if (cur_node->outputs[j]->op == "Softmax") + softmax_node = cur_node->outputs[j]; + else if (cur_node->outputs[j]->op == "Shape") + shape_node = cur_node->outputs[j]; + } + + if (softmax_node) + { + if (shape_node) + DisconnectNode(shape_node); + + TFNode* input_node = cur_node->inputs[0]; + MergeChildNode(input_node, cur_node); + ir = tf_graph.seq_nodes.erase(ir); + delete cur_node; + continue; + } + + if (cur_node->outputs.size() == 1 && softmax_node == nullptr) + { + TFNode* child_node = cur_node->outputs[0]; + + MergeParentNode(child_node, cur_node); + ir = tf_graph.seq_nodes.erase(ir); + delete cur_node; + continue; + } + } + + if (cur_node->op == "Identity") + { + TFNode* input_node = cur_node->inputs[0]; + MergeChildNode(input_node, cur_node); + + ir = tf_graph.seq_nodes.erase(ir); + delete cur_node; + continue; + } + + if (cur_node->op == "ConcatV2") + { + TFNode* axis_node = nullptr; + + for (unsigned int i = 0; i < cur_node->inputs.size(); i++) + { + TFNode* check_node = cur_node->inputs[i]; + + if (check_node->op == "Const") + { + axis_node = check_node; + break; + } + } + + if (axis_node) + { + cur_node->pb_defs.push_back(axis_node->pb_defs[0]); + DisconnectNode(axis_node); + } + } + + ir++; + } + + /* merge FIFOQueueV2 DequeueManyV2 */ + + ir = tf_graph.seq_nodes.begin(); + + while (ir != tf_graph.seq_nodes.end()) + { + TFNode* cur_node = *ir; + + if (cur_node->op == "FIFOQueueV2") + { + TFNode* queue_node = cur_node->outputs[0]; + + if (queue_node->op == "QueueDequeueManyV2") + { + MergeParentNode(queue_node, queue_node->inputs[1]); + } + + MergeChildNode(cur_node, queue_node); + + break; + } + + ir++; + } + + /* remove ExpandDims */ + ir = tf_graph.seq_nodes.begin(); + + while (ir != tf_graph.seq_nodes.end()) + { + TFNode* cur_node = *ir; + + if (cur_node->op == "ExpandDims") + { + TFNode* input0 = cur_node->inputs[0]; + TFNode* input1 = cur_node->inputs[1]; + + if (input0->op == "Constant" && input1->op == "Const") + { + TFNode* input1 = cur_node->inputs[1]; + TFNode* child_node = cur_node->outputs[0]; + + DisconnectNode(input1); + DisconnectNode(cur_node); + + child_node->inputs.push_back(input1); + input1->outputs.push_back(child_node); + } + else + { + if (input1->op == "Const") + DisconnectNode(input1); + else + DisconnectNode(input0); + + TFNode* child_node = cur_node->outputs[0]; + + MergeParentNode(child_node, cur_node); + } + + ir = tf_graph.seq_nodes.erase(ir); + delete cur_node; + + continue; + } + + ir++; + } + + /* merge biasadd and conv */ + ir = tf_graph.seq_nodes.begin(); + + while (ir != tf_graph.seq_nodes.end()) + { + TFNode* cur_node = *ir; + + if (cur_node->op == "Conv2D" || cur_node->op == "DepthwiseConv2dNative" || cur_node->op == "MatMul") + { + TFNode* output_node = cur_node->outputs[0]; + + if (output_node->op == "BiasAdd" || output_node->op == "Add") + { + MergeChildNode(cur_node, output_node); + } + } + + ir++; + } + + /* merge composed BatchNormal */ + + ir = tf_graph.seq_nodes.begin(); + + while (ir != tf_graph.seq_nodes.end()) + { + TFNode* cur_node = *ir; + + if (CheckComposedBNAdd(cur_node)) + FuseComposedBN(cur_node); + ir++; + } + + /* cleanup ResizeNearestNeighbor */ + CleanupResizeNearestNeighbor(); + + /* merge Minimum and Relu */ + + MergeReluMinimum(); + /* merge input node and reshape */ + ir = tf_graph.seq_nodes.begin(); + while (ir != tf_graph.seq_nodes.end()) + { + TFNode* cur_node = *ir; + if (cur_node->op == "Reshape") + { + /* Reshape should have two inputs */ + TFNode* input_node0 = cur_node->inputs[0]; + TFNode* input_node1 = cur_node->inputs[1]; + + if (input_node0->op == "Placeholder" || input_node1->op == "Placeholder") + { + TFNode* input_node; + TFNode* const_node; + + if (input_node0->op == "Const") + { + const_node = input_node0; + input_node = input_node1; + } + else + { + const_node = input_node1; + input_node = input_node0; + } + + DisconnectNode(const_node); + MergeChildNode(input_node, cur_node); + input_node->pb_defs.insert(input_node->pb_defs.end(), const_node->pb_defs[0]); + + ir = tf_graph.seq_nodes.erase(ir); + delete cur_node; + break; + } + } + ir++; + } + + /* remove the shape and StrideSlice */ + + ir = tf_graph.seq_nodes.begin(); + + while (ir != tf_graph.seq_nodes.end()) + { + TFNode* cur_node = *ir; + if (cur_node->op == "StridedSlice") + { + /* check if input0 is "shape" */ + TFNode* input_node = cur_node->inputs[0]; + + if (input_node->op == "Shape") + { + /* here we go */ + DisconnectNode(cur_node); + DisconnectNode(input_node); + break; + } + } + + ir++; + } + + /* merge pad and conv */ + + ir = tf_graph.seq_nodes.begin(); + + while (ir != tf_graph.seq_nodes.end()) + { + TFNode* cur_node = *ir; + + if (cur_node->op == "Conv2D" || cur_node->op == "DepthwiseConv2dNative") + { + /* check if input is pad or not */ + TFNode* input_node = cur_node->inputs[0]; + + if (input_node->op == "Pad") + { + TFNode* padding_args = input_node->inputs[1]; + + input_node->pb_defs.push_back(padding_args->pb_defs[0]); + + DisconnectNode(padding_args); + MergeParentNode(cur_node, input_node); + } + } + ir++; + } + + /*remove ArgMax node */ + + ir = tf_graph.seq_nodes.begin(); + while (ir != tf_graph.seq_nodes.end()) + { + TFNode* cur_node = *ir; + if (cur_node->op == "ArgMax") + { + DisconnectNode(cur_node); + tf_graph.seq_nodes.erase(ir); + + break; + } + + ir++; + } + + /* remove last squeeze */ + + ir = tf_graph.seq_nodes.begin(); + + while (ir != tf_graph.seq_nodes.end()) + { + TFNode* cur_node = *ir; + + if (cur_node->op == "Squeeze" && cur_node->outputs.empty()) + { + DisconnectNode(cur_node); + break; + } + ir++; + } + + /* remove no input and output nodes */ + + ir = tf_graph.seq_nodes.begin(); + + while (ir != tf_graph.seq_nodes.end()) + { + TFNode* cur_node = *ir; + + if (cur_node->inputs.size() == 0 && cur_node->outputs.size() == 0) + { + ir = tf_graph.seq_nodes.erase(ir); + delete cur_node; + } + else + ir++; + } + + /* remove no input but not placeholder/const nodes */ + ir = tf_graph.seq_nodes.begin(); + + while (ir != tf_graph.seq_nodes.end()) + { + TFNode* cur_node = *ir; + + if (cur_node->inputs.size() == 0 && cur_node->op != "Const" && cur_node->op != "Placeholder" && cur_node->op != "FIFOQueueV2") + { + DisconnectNode(cur_node); + tf_graph.seq_nodes.erase(ir); + delete cur_node; + + ir = tf_graph.seq_nodes.begin(); // restart + } + else + ir++; + } + + return 0; +} + +int tensorflow_serializer::FindRNNScope(std::string& rnn_scope) +{ + std::string rnn_node; + + std::string::size_type while_pos; + + int rnn_type = -1; + + for (unsigned int i = 0; i < tf_graph.seq_nodes.size(); i++) + { + TFNode* node = tf_graph.seq_nodes.at(i); + std::string& name = node->name; + + while_pos = name.find("while"); + + if (while_pos == std::string::npos) + continue; + + std::string::size_type cell_pos = name.find("lstm_cell", while_pos); + + if (cell_pos != std::string::npos) + { + rnn_node = node->name; + rnn_type = TF_RNN_LSTM; + break; + } + + cell_pos = name.find("gru_cell", while_pos); + + if (cell_pos != std::string::npos) + { + rnn_node = node->name; + rnn_type = TF_RNN_GRU; + break; + } + + cell_pos = name.find("basic_lstm_cell", while_pos); + + if (cell_pos != std::string::npos) + { + rnn_node = node->name; + rnn_type = TF_RNN_BASIC_LSTM; + break; + } + + cell_pos = name.find("basic_rnn_cell", while_pos); + + if (cell_pos != std::string::npos) + { + rnn_node = node->name; + rnn_type = TF_RNN_BASIC_RNN; + break; + } + } + + if (rnn_node.empty()) + return -1; + + std::string rnn_layer = rnn_node.substr(0, while_pos - 1); + std::string::size_type up_pos = rnn_layer.rfind("/"); + + rnn_scope = rnn_layer.substr(0, up_pos + 1); + + return rnn_type; +} + +void tensorflow_serializer::ParseLSTMGraph(LSTMNode* lstm_node, std::set& rnn_graph) +{ + /* parse input node */ + + for (unsigned int i = 0; i < lstm_node->inputs.size(); i++) + { + TFNode* node = lstm_node->inputs[i]; + + if (node->op != "Const") + continue; + + // node->no_static_node=true; //do not automatically create Static Node + + if (node->name.find("lstm_cell/kernel") != std::string::npos) + { + lstm_node->kernel = node; + } + else if (node->name.find("lstm_cell/bias") != std::string::npos) + { + lstm_node->bias = node; + } + else if (node->name.find("lstm_cell/w_f_diag") != std::string::npos) + { + lstm_node->w_f_diag = node; + } + else if (node->name.find("lstm_cell/w_o_diag") != std::string::npos) + { + lstm_node->w_o_diag = node; + } + else if (node->name.find("lstm_cell/w_i_diag") != std::string::npos) + { + lstm_node->w_i_diag = node; + } + else if (node->name.find("lstm_cell/projection/kernel") != std::string::npos) + { + lstm_node->projection = node; + } + } + + auto rnn_ir = rnn_graph.begin(); + auto rnn_ir_end = rnn_graph.end(); + + while (rnn_ir != rnn_ir_end) + { + TFNode* node = *rnn_ir; + int name_len = node->name.size(); + std::string zero_name = "LSTMCellZeroState/zeros"; + std::string zero1_name = "LSTMCellZeroState/zeros_1"; + std::string forget_name = "lstm_cell/add/y"; + + if (node->name.find(zero_name, name_len - zero_name.size()) != std::string::npos) + lstm_node->init_c = node; + else if (node->name.find(zero1_name, name_len - zero1_name.size()) != std::string::npos) + lstm_node->init_h = node; + else if (node->name.find(forget_name, name_len - forget_name.size()) != std::string::npos) + lstm_node->forget_bias = node; + + rnn_ir++; + } +} +void ParseGRUGraph(TFGraph& tf_graph, GRUNode* gru_node, std::set& rnn_graph) +{ + /* parse input node */ + + for (unsigned int i = 0; i < gru_node->inputs.size(); i++) + { + TFNode* node = gru_node->inputs[i]; + + if (node->op != "Const") + continue; + + // node->no_static_node=true; //do not automatically create Static Node + + if (node->name.find("gru_cell/gates/kernel") != std::string::npos) + { + gru_node->gate_kernel = node; + } + else if (node->name.find("gru_cell/gates/bias") != std::string::npos) + { + gru_node->gate_bias = node; + } + else if (node->name.find("gru_cell/candidate/kernel") != std::string::npos) + { + gru_node->candidate_kernel = node; + } + else if (node->name.find("gru_cell/candidate/bias") != std::string::npos) + { + gru_node->candidate_bias = node; + } + } + + auto rnn_ir = rnn_graph.begin(); + auto rnn_ir_end = rnn_graph.end(); + + while (rnn_ir != rnn_ir_end) + { + TFNode* node = *rnn_ir; + int name_len = node->name.size(); + std::string zero_name = "GRUCellZeroState/zeros"; + + if (node->name.find(zero_name, name_len - zero_name.size()) != std::string::npos) + gru_node->init_h = node; + + rnn_ir++; + } +} +void ParseRNNGraph(TFGraph& tf_graph, RNNNode* rnn_node, std::set& rnn_graph) +{ + /* parse input node */ + + for (unsigned int i = 0; i < rnn_node->inputs.size(); i++) + { + TFNode* node = rnn_node->inputs[i]; + + if (node->op != "Const") + continue; + + // node->no_static_node=true; //do not automatically create Static Node + + if (node->name.find("basic_rnn_cell/kernel") != std::string::npos) + { + rnn_node->kernel = node; + } + else if (node->name.find("basic_rnn_cell/bias") != std::string::npos) + { + rnn_node->bias = node; + } + } + + auto rnn_ir = rnn_graph.begin(); + auto rnn_ir_end = rnn_graph.end(); + + while (rnn_ir != rnn_ir_end) + { + TFNode* node = *rnn_ir; + int name_len = node->name.size(); + std::string zero_name = "BasicRNNCellZeroState/zeros"; + + if (node->name.find(zero_name, name_len - zero_name.size()) != std::string::npos) + rnn_node->init_h = node; + + rnn_ir++; + } +} +void tensorflow_serializer::StripRNNScope(std::string& rnn_scope, int rnn_type) +{ + // collect attributes according to rnn_type + + if (rnn_type == TF_RNN_LSTM) + { + LSTMNode* lstm_node = new LSTMNode(); + + lstm_node->name = rnn_scope + "lstm"; + lstm_node->op = "LSTM"; + + std::set& rnn_graph = lstm_node->rnn_graph; + + std::set rnn_inputs; + std::set rnn_outputs; + + auto ir = tf_graph.seq_nodes.begin(); + std::string::size_type prefix_len = rnn_scope.size(); + + while (ir != tf_graph.seq_nodes.end()) + { + TFNode* node = *ir; + + if (node->name.find(rnn_scope.c_str(), 0, prefix_len) == std::string::npos) + { + ir++; + continue; + } + + /* this is a node, inside rnn scope, remove it from graph first */ + ir = tf_graph.seq_nodes.erase(ir); + + rnn_graph.insert(node); + } + + auto rnn_ir = rnn_graph.begin(); + auto rnn_end = rnn_graph.end(); + + while (rnn_ir != rnn_end) + { + TFNode* node = *rnn_ir; + + for (unsigned int i = 0; i < node->inputs.size(); i++) + { + TFNode* input = node->inputs[i]; + + if (!rnn_graph.count(input)) + rnn_inputs.insert(input); + } + + for (unsigned int i = 0; i < node->outputs.size(); i++) + { + TFNode* output = node->outputs[i]; + + if (!rnn_graph.count(output)) + rnn_outputs.insert(output); + } + + rnn_ir++; + } + + // insert lstm node + auto seq_ir = tf_graph.seq_nodes.begin(); + + while (seq_ir != tf_graph.seq_nodes.end()) + { + TFNode* node = *seq_ir; + + if (rnn_inputs.count(node)) + { + tf_graph.seq_nodes.insert(seq_ir, lstm_node); + break; + } + + seq_ir++; + } + + // connect inputs and outputs + auto set_ir = rnn_inputs.begin(); + auto set_ir_end = rnn_inputs.end(); + + while (set_ir != set_ir_end) + { + TFNode* input_node = *set_ir; + + for (unsigned int j = 0; j < input_node->outputs.size(); j++) + { + TFNode* child_node = input_node->outputs[j]; + + if (rnn_graph.count(child_node)) + input_node->outputs[j] = lstm_node; + } + + lstm_node->inputs.push_back(input_node); + + if (input_node->op == "Identity") + { + TFNode* parent_node = input_node->inputs[0]; + + MergeChildNode(parent_node, input_node); + } + + set_ir++; + } + + set_ir = rnn_outputs.begin(); + set_ir_end = rnn_outputs.end(); + + while (set_ir != set_ir_end) + { + TFNode* output_node = *set_ir; + + for (unsigned int j = 0; j < output_node->inputs.size(); j++) + { + TFNode* parent_node = output_node->inputs[j]; + + if (rnn_graph.count(parent_node)) + output_node->inputs[j] = lstm_node; + } + + lstm_node->outputs.push_back(output_node); + set_ir++; + } + + /* sort input node and output node according to index */ + std::sort(lstm_node->inputs.begin(), lstm_node->inputs.end(), + [](const TFNode* a, const TFNode* b) { return a->idx < b->idx; }); + + std::sort(lstm_node->outputs.begin(), lstm_node->outputs.end(), + [](const TFNode* a, const TFNode* b) { return a->idx < b->idx; }); + + ParseLSTMGraph(lstm_node, rnn_graph); + } + + if (rnn_type == TF_RNN_BASIC_RNN) + { + RNNNode* rnn_node = new RNNNode(); + + rnn_node->name = rnn_scope + "rnn"; + // std::cout<op = "RNN"; + + std::set& rnn_graph = rnn_node->rnn_graph; + + std::set rnn_inputs; + std::set rnn_outputs; + + auto ir = tf_graph.seq_nodes.begin(); + std::string::size_type prefix_len = rnn_scope.size(); + + while (ir != tf_graph.seq_nodes.end()) + { + TFNode* node = *ir; + + if (node->name.find(rnn_scope.c_str(), 0, prefix_len) == std::string::npos) + { + ir++; + continue; + } + + /* this is a node, inside rnn scope, remove it from graph first */ + ir = tf_graph.seq_nodes.erase(ir); + + rnn_graph.insert(node); + } + + auto rnn_ir = rnn_graph.begin(); + auto rnn_end = rnn_graph.end(); + + while (rnn_ir != rnn_end) + { + TFNode* node = *rnn_ir; + + for (unsigned int i = 0; i < node->inputs.size(); i++) + { + TFNode* input = node->inputs[i]; + + if (!rnn_graph.count(input)) + rnn_inputs.insert(input); + } + + for (unsigned int i = 0; i < node->outputs.size(); i++) + { + TFNode* output = node->outputs[i]; + + if (!rnn_graph.count(output)) + rnn_outputs.insert(output); + } + + rnn_ir++; + } + + // insert rnn node + auto seq_ir = tf_graph.seq_nodes.begin(); + + while (seq_ir != tf_graph.seq_nodes.end()) + { + TFNode* node = *seq_ir; + + if (rnn_inputs.count(node)) + { + tf_graph.seq_nodes.insert(seq_ir, rnn_node); + break; + } + + seq_ir++; + } + + // connect inputs and outputs + auto set_ir = rnn_inputs.begin(); + auto set_ir_end = rnn_inputs.end(); + + while (set_ir != set_ir_end) + { + TFNode* input_node = *set_ir; + + for (unsigned int j = 0; j < input_node->outputs.size(); j++) + { + TFNode* child_node = input_node->outputs[j]; + + if (rnn_graph.count(child_node)) + input_node->outputs[j] = rnn_node; + } + + rnn_node->inputs.push_back(input_node); + + if (input_node->op == "Identity") + { + TFNode* parent_node = input_node->inputs[0]; + + MergeChildNode(parent_node, input_node); + } + + set_ir++; + } + + set_ir = rnn_outputs.begin(); + set_ir_end = rnn_outputs.end(); + + while (set_ir != set_ir_end) + { + TFNode* output_node = *set_ir; + + for (unsigned int j = 0; j < output_node->inputs.size(); j++) + { + TFNode* parent_node = output_node->inputs[j]; + + if (rnn_graph.count(parent_node)) + output_node->inputs[j] = rnn_node; + } + + rnn_node->outputs.push_back(output_node); + set_ir++; + } + + /* sort input node and output node according to index */ + std::sort(rnn_node->inputs.begin(), rnn_node->inputs.end(), + [](const TFNode* a, const TFNode* b) { return a->idx < b->idx; }); + + std::sort(rnn_node->outputs.begin(), rnn_node->outputs.end(), + [](const TFNode* a, const TFNode* b) { return a->idx < b->idx; }); + + ParseRNNGraph(tf_graph, rnn_node, rnn_graph); + } + if (rnn_type == TF_RNN_GRU) + { + GRUNode* gru_node = new GRUNode(); + + gru_node->name = rnn_scope + "gru"; + // std::cout<op = "GRU"; + + std::set& rnn_graph = gru_node->rnn_graph; + + std::set rnn_inputs; + std::set rnn_outputs; + + auto ir = tf_graph.seq_nodes.begin(); + std::string::size_type prefix_len = rnn_scope.size(); + + while (ir != tf_graph.seq_nodes.end()) + { + TFNode* node = *ir; + + if (node->name.find(rnn_scope.c_str(), 0, prefix_len) == std::string::npos) + { + ir++; + continue; + } + + /* this is a node, inside rnn scope, remove it from graph first */ + ir = tf_graph.seq_nodes.erase(ir); + + rnn_graph.insert(node); + } + + auto rnn_ir = rnn_graph.begin(); + auto rnn_end = rnn_graph.end(); + + while (rnn_ir != rnn_end) + { + TFNode* node = *rnn_ir; + + for (unsigned int i = 0; i < node->inputs.size(); i++) + { + TFNode* input = node->inputs[i]; + + if (!rnn_graph.count(input)) + rnn_inputs.insert(input); + } + + for (unsigned int i = 0; i < node->outputs.size(); i++) + { + TFNode* output = node->outputs[i]; + + if (!rnn_graph.count(output)) + rnn_outputs.insert(output); + } + + rnn_ir++; + } + + // insert rnn node + auto seq_ir = tf_graph.seq_nodes.begin(); + + while (seq_ir != tf_graph.seq_nodes.end()) + { + TFNode* node = *seq_ir; + + if (rnn_inputs.count(node)) + { + tf_graph.seq_nodes.insert(seq_ir, gru_node); + break; + } + + seq_ir++; + } + + // connect inputs and outputs + auto set_ir = rnn_inputs.begin(); + auto set_ir_end = rnn_inputs.end(); + + while (set_ir != set_ir_end) + { + TFNode* input_node = *set_ir; + + for (unsigned int j = 0; j < input_node->outputs.size(); j++) + { + TFNode* child_node = input_node->outputs[j]; + + if (rnn_graph.count(child_node)) + input_node->outputs[j] = gru_node; + } + + gru_node->inputs.push_back(input_node); + + if (input_node->op == "Identity") + { + TFNode* parent_node = input_node->inputs[0]; + + MergeChildNode(parent_node, input_node); + } + + set_ir++; + } + + set_ir = rnn_outputs.begin(); + set_ir_end = rnn_outputs.end(); + + while (set_ir != set_ir_end) + { + TFNode* output_node = *set_ir; + + for (unsigned int j = 0; j < output_node->inputs.size(); j++) + { + TFNode* parent_node = output_node->inputs[j]; + + if (rnn_graph.count(parent_node)) + output_node->inputs[j] = gru_node; + } + + gru_node->outputs.push_back(output_node); + set_ir++; + } + + /* sort input node and output node according to index */ + std::sort(gru_node->inputs.begin(), gru_node->inputs.end(), + [](const TFNode* a, const TFNode* b) { return a->idx < b->idx; }); + + std::sort(gru_node->outputs.begin(), gru_node->outputs.end(), + [](const TFNode* a, const TFNode* b) { return a->idx < b->idx; }); + + ParseGRUGraph(tf_graph, gru_node, rnn_graph); + } + + // cleanup zero in/zero out node + auto seq_ir = tf_graph.seq_nodes.begin(); + + while (seq_ir != tf_graph.seq_nodes.end()) + { + TFNode* node = *seq_ir; + + if (node->inputs.size() == 0 && node->outputs.size() == 0) + { + delete node; + seq_ir = tf_graph.seq_nodes.erase(seq_ir); + } + else + { + seq_ir++; + } + } +} +int tensorflow_serializer::optimize_rnn() +{ + while (1) + { + std::string rnn_scope; + + int rnn_type = FindRNNScope(rnn_scope); + + if (rnn_scope.empty()) + break; + + StripRNNScope(rnn_scope, rnn_type); + } + + return true; +} + +int tensorflow_serializer::generate_graph(ir_graph_t* graph) +{ + int node_number = tf_graph.seq_nodes.size(); + int i; + + bool debug_graph = false; + const char* debug_env = std::getenv("DEBUG_TF"); + if ((debug_env) && (debug_env[0] == '1')) + { + debug_graph = true; + } + + // first: create all tensor node + for (i = 0; i < node_number; i++) + { + TFNode* tf_node = tf_graph.seq_nodes[i]; + + if (debug_graph) + { + std::cout << i << "\t" << tf_node->op << "\t" << tf_node->name << "\n"; + } + + if (tf_node->no_static_node) + continue; + + if (tf_node->op == "Placeholder") + continue; + + if (tf_node->op == "Const") + { + load_const_tensor(tf_node, graph); + continue; + } + } + + for (int i = 0; i < (int)tf_graph.seq_nodes.size(); i++) + { + TFNode* tf_node = tf_graph.seq_nodes[i]; + + if (tf_node->op == "Placeholder" || tf_node->op == "Const") + continue; + + ir_node_t* ir_node = nullptr; + int node_idx = get_ir_node_index_from_name(graph, tf_node->name.c_str()); + if (node_idx < 0) + { + ir_node = create_ir_node(graph, tf_node->name.c_str(), op_load_map[tf_node->op].first, OP_VERSION); + } + else + { + ir_node = get_ir_graph_node(graph, node_idx); + } + for (int in = 0; in < tf_node->inputs.size(); in++) + { + TFNode* node = tf_node->inputs[in]; + int tensor_idx = get_ir_tensor_index_from_name(graph, node->name.c_str()); + ir_tensor_t* tensor = nullptr; + if (node->name == "Placeholder") + { + continue; + } + if (tensor_idx < 0) + tensor = create_ir_tensor(graph, tf_node->name.c_str(), TENGINE_DT_FP32); + else + tensor = get_ir_graph_tensor(graph, tensor_idx); + set_ir_node_input_tensor(ir_node, in, tensor); + input_tensors.push_back(node->name.c_str()); + } + for (int out = 0; out < tf_node->outputs.size(); out++) + { + TFNode* node = tf_node->outputs[out]; + + int tensor_idx = get_ir_tensor_index_from_name(graph, node->name.c_str()); + ir_tensor_t* tensor = nullptr; + if (tensor_idx < 0) + tensor = create_ir_tensor(graph, tf_node->name.c_str(), TENGINE_DT_FP32); + else + tensor = get_ir_graph_tensor(graph, tensor_idx); + set_ir_node_output_tensor(ir_node, out, tensor); + output_tensors.push_back(node->name.c_str()); + } + // printf("node op : %s \n", tf_node->op.c_str()); + op_load_t loader = op_load_map[tf_node->op].second; + if (loader(tf_node, tf_graph, graph, ir_node) < 0) + { + fprintf(stderr, "load op %s func failed in node %s .\n", tf_node->op.c_str(), tf_node->name.c_str()); + return -1; + } + } + + if (i < node_number) + return -1; + + return 0; +} + +int tensorflow_serializer::set_graph_output(ir_graph_t* graph) +{ + int layer_number = tf_graph.seq_nodes.size(); + std::vector output_nodes; + + std::vector graph_outputs; + for (int i = 0; i < output_tensors.size(); i++) + { + int check_flag = true; + + auto it = find(input_tensors.begin(), input_tensors.end(), output_tensors[i]); + if (it == input_tensors.end()) + { + graph_outputs.push_back(output_tensors[i]); + } + } + + for (int i = 0; i < graph_outputs.size(); i++) + { + int tensor_id = get_ir_tensor_index_from_name(graph, graph_outputs[i].c_str()); + ir_tensor_t* tensor = nullptr; + if (tensor_id < 0) + tensor = create_ir_tensor(graph, graph_outputs[i].c_str(), TENGINE_DT_FP32); + else + tensor = get_ir_graph_tensor(graph, tensor_id); + int node_idx = get_ir_node_index_from_name(graph, graph_outputs[i].c_str()); + ir_node_t* node = get_ir_graph_node(graph, node_idx); + set_ir_node_output_tensor(node, 0, tensor); + output_nodes.push_back(node->index); + } + + std::vector node_idx; + for (int i = 0; i < output_nodes.size(); i++) + { + node_idx.push_back(output_nodes[i]); + } + set_ir_graph_output_node(graph, node_idx.data(), output_nodes.size()); + return 0; + return 0; +} + +int tensorflow_serializer::load_graph(ir_graph_t* graph) +{ + if (construct_graph() < 0) + return -1; + if (optimize_rnn() < 0) + return false; + if (optimize_graph() < 0) + return -1; + if (set_graph_input(graph) < 0) + return -1; + fprintf(stderr, "Process 2: Finish set graph input \n"); + if (generate_graph(graph) < 0) + return -1; + fprintf(stderr, "Process 3: Finish load graph node \n"); + + if (set_graph_output(graph) < 0) + return -1; + fprintf(stderr, "Process 4: Finish set graph output \n"); + + return 0; +} + +int tensorflow_serializer::load_model(ir_graph_t* graph, std::string model_file) +{ + register_op_load(); + if (load_binary_file(model_file) < 0) + return -1; + fprintf(stderr, "Process 1: Finish load protobuf file \n"); + load_graph(graph); + + return 0; +} + +graph_t tensorflow_serializer::tensorflow2tengine(std::string model_file) +{ + fprintf(stderr, "----------tensorflow2tengine begin----------\n"); + + context_t context = create_context(NULL, 1); + ir_graph_t* ir_graph = create_ir_graph((struct context*)context); + if (ir_graph == NULL) + { + destroy_context(context); + return NULL; + } + ir_graph->attribute->private_context = 1; // new context + + int ret = load_model(ir_graph, model_file); + if (0 != ret) + { + destroy_graph(ir_graph); + return NULL; + } + ir_graph->device = find_default_device(); + + fprintf(stderr, "----------tensorflow2tengine done.----------\n"); + return ir_graph; +} + +int load_pool(TFNode* tf_node, TFGraph& tf_graph, ir_graph_t* graph, ir_node_t* node) +{ + TFNode* input = tf_node->inputs[0]; + struct pool_param* param = (struct pool_param*)node->op.param_mem; + const tensorflow::NodeDef* node_def = tf_node->pb_defs[0]; + tensorflow::AttrValue value; + + if (GetAttrValue(node_def, "ksize", value)) + { + param->kernel_h = value.list().i(1); + param->kernel_w = value.list().i(2); + } + + if (GetAttrValue(node_def, "strides", value)) + { + param->stride_h = value.list().i(1); + param->stride_w = value.list().i(2); + } + + if (GetAttrValue(node_def, "padding", value)) + { + if (value.s() == "VALID") + { + param->pad_h0 = 0; + param->pad_h1 = 0; + param->pad_w0 = 0; + param->pad_w1 = 0; + } + else if (value.s() == "SAME") + { + param->pad_h0 = -1; + param->pad_h1 = -1; + param->pad_w0 = -1; + param->pad_w1 = -1; + } + } + + if (tf_node->op == "AvgPool") + { + param->pool_method = 1; + } + else if (tf_node->op == "MaxPool") + { + param->pool_method = 0; + } + return 0; +} +int load_conv(TFNode* tf_node, TFGraph& tf_graph, ir_graph_t* graph, ir_node_t* node) +{ + TFNode* input0 = tf_node->inputs[0]; /* input */ + TFNode* input1 = tf_node->inputs[1]; /* weight */ + TFNode* input2 = nullptr; + if (tf_node->inputs.size() > 2) + { + input2 = tf_node->inputs[2]; + } + const tensorflow::NodeDef* node_def = tf_node->pb_defs[0]; + struct conv_param* param = (struct conv_param*)node->op.param_mem; + tensorflow::AttrValue value; + + if (GetAttrValue(node_def, "dilations", value)) + { + param->dilation_h = value.list().i(1); + param->dilation_w = value.list().i(2); + } + if (GetAttrValue(node_def, "padding", value)) + { + if (value.s() == "VALID") + { + param->pad_h0 = 0; + param->pad_h1 = 0; + param->pad_w0 = 0; + param->pad_w1 = 0; + } + else if (value.s() == "SAME") + { + param->pad_h0 = -1; + param->pad_h1 = -1; + param->pad_w0 = -1; + param->pad_w1 = -1; + } + } + + if (GetAttrValue(node_def, "strides", value)) + { + param->stride_h = value.list().i(1); + param->stride_w = value.list().i(2); + } + + int in_channel = 1, out_channel = 1, kernel_h = 0, kernel_w = 0; + int group = 1; + // Tensorflow has to get those information from weights + + const tensorflow::NodeDef* weight_def = input1->pb_defs[0]; + + if (GetAttrValue(weight_def, "value", value)) + { + const tensorflow::TensorShapeProto& shape = value.tensor().tensor_shape(); + + if (shape.dim_size() == 4) + { + kernel_h = shape.dim(0).size(); + kernel_w = shape.dim(1).size(); + in_channel = shape.dim(2).size(); + out_channel = shape.dim(3).size(); + } + else if (shape.dim_size() == 3) + { + kernel_h = 1; + kernel_w = shape.dim(0).size(); + in_channel = shape.dim(1).size(); + out_channel = shape.dim(2).size(); + } + } + ir_tensor_t* weight_tensor = input1->ir_tensor; + + int elem_size = out_channel * in_channel * kernel_h * kernel_w; + float* new_weight = (float*)malloc(sizeof(float) * elem_size); + float* src = (float*)weight_tensor->data; + weight_tensor->data = sys_malloc(elem_size * sizeof(float)); + float* ptr = (float*)weight_tensor->data; + + for (int o = 0; o < out_channel; o++) + for (int h = 0; h < kernel_h; h++) + for (int w = 0; w < kernel_w; w++) + for (int i = 0; i < in_channel; i++) + { + ptr[o * in_channel * kernel_h * kernel_w + i * kernel_h * kernel_w + h * kernel_w + w] + = src[h * (kernel_w * in_channel * out_channel) + w * (in_channel * out_channel) + i * out_channel + o]; + } + + free(src); + + weight_tensor->tensor_type = TENSOR_TYPE_CONST; + if (tf_node->op == "DepthwiseConv2dNative") + { + group = in_channel; + out_channel = in_channel * out_channel; + in_channel = 1; + } + + int* dims = (int*)malloc(sizeof(int) * 4); + dims[0] = out_channel; + dims[1] = in_channel; + dims[2] = kernel_h; + dims[3] = kernel_w; + + // SetTensorDim(weight_tensor, dims); + set_ir_tensor_shape(weight_tensor, dims, 4); + param->kernel_h = kernel_h; + param->kernel_w = kernel_w; + param->output_channel = out_channel; + param->group = group; + + auto saved_param = param; + + if (tf_node->op == "DepthwiseConv2dNative") + { + in_channel = group; + out_channel = out_channel / in_channel; + } + + int pb_def_num = tf_node->pb_defs.size(); + + if (pb_def_num > 1) + { + // the last one, + const tensorflow::NodeDef* node_def = tf_node->pb_defs[pb_def_num - 1]; + + /* possible pad */ + if (node_def->op() == "Const") + { + tensorflow::AttrValue value; + + if (GetAttrValue(node_def, "value", value) && value.has_tensor()) + { + const tensorflow::TensorProto& tf_tensor = value.tensor(); + + int dim_size = tf_tensor.tensor_shape().dim_size(); + + if (dim_size == 2 && tf_tensor.tensor_shape().dim(0).size() == 4 && tf_tensor.tensor_shape().dim(1).size() == 2) + { + std::vector shape_data(8); + + if (tf_tensor.tensor_content().size()) + { + int* ptr = shape_data.data(); + memcpy(ptr, tf_tensor.tensor_content().c_str(), tf_tensor.tensor_content().size()); + } + else + { + int data_num = tf_tensor.int_val_size(); + + for (int i = 0; i < data_num; i++) + { + shape_data[i] = tf_tensor.int_val(i); + } + } + + /* h pad */ + saved_param->pad_h0 = shape_data[2]; + saved_param->pad_h1 = shape_data[3]; + /* w pad */ + saved_param->pad_w0 = shape_data[4]; + saved_param->pad_w1 = shape_data[5]; + // printf("%d %d %d %d \n",) + } + } + } + } + return 0; +} +int load_batchnorm(TFNode* tf_node, TFGraph& tf_graph, ir_graph_t* graph, ir_node_t* node) +{ + struct batchnorm_param* param = (struct batchnorm_param*)node->op.param_mem; + const tensorflow::NodeDef* node_def = tf_node->pb_defs[0]; + tensorflow::AttrValue value; + + if (GetAttrValue(node_def, "epsilon", value)) + { + param->eps = value.f(); + } + + return 0; +} +int load_relu6(TFNode* tf_node, TFGraph& tf_graph, ir_graph_t* graph, ir_node_t* node) +{ + return 0; +} +int load_softmax(TFNode* tf_node, TFGraph& tf_graph, ir_graph_t* graph, ir_node_t* node) +{ + struct softmax_param* param = (struct softmax_param*)node->op.param_mem; + const tensorflow::NodeDef* node_def = tf_node->pb_defs[0]; + tensorflow::AttrValue value; + if (GetAttrValue(node_def, "value", value)) + { + const tensorflow::TensorProto& tf_tensor = value.tensor(); + int axis = tf_tensor.int_val(0); + param->axis = axis; + } + else + param->axis = 1; + + return 0; +} +int load_relu(TFNode* tf_node, TFGraph& tf_graph, ir_graph_t* graph, ir_node_t* node) +{ + TFNode* input = tf_node->inputs[0]; + struct relu_param* param = (struct relu_param*)node->op.param_mem; + param->negative_slope = 0.f; + return 0; +} +static EltType MapEltwise(TFNode* tf_node, const std::string& elt_op) +{ + if (elt_op == "Add" || elt_op == "AddN") + return ELT_SUM; + else if (elt_op == "Mul") + return ELT_PROD; + else if (elt_op == "Sub") + return ELT_SUB; + else if (elt_op == "Rsqrt") + return ELT_RSQRT; + else if (elt_op == "Minimum") + return ELT_MIN_SCALAR; + else if (elt_op == "Exp") + return ELT_EXP; + else if (elt_op == "Log") + return ELT_LOG; + else if (elt_op == "Pow") + return ELT_POW; + else if (elt_op == "RealDiv") + return ELT_DIV; + else if (elt_op == "Sqrt") + return ELT_SQRT; + else if (elt_op == "Floor") + return ELT_FLOOR; + else + return ELT_LAST; +} +int load_eltwise(TFNode* tf_node, TFGraph& tf_graph, ir_graph_t* graph, ir_node_t* node) +{ + if (tf_node->op == "Add" || tf_node->op == "Mul" || tf_node->op == "Sub" || tf_node->op == "Minimum" || tf_node->op == "AddN" || tf_node->op == "Pow" || tf_node->op == "RealDiv") + { + if (tf_node->inputs.size() != 2) + return false; + } + else if (tf_node->op == "Rsqrt" || tf_node->op == "Exp" || tf_node->op == "Log" || tf_node->op == "Sqrt" || tf_node->op == "Floor") + { + if (tf_node->inputs.size() != 1) + return false; + } + else + { + TLOG_ERR("Unsupported op: %s \n", tf_node->op.c_str()); + return false; + } + struct eltwise_param* param = (struct eltwise_param*)node->op.param_mem; + param->type = MapEltwise(tf_node, tf_node->op); + param->caffe_flavor = 1; + return 0; +} +static void* LoadConstParam(TFNode* tf_node) +{ + tensorflow::AttrValue value; + + const tensorflow::NodeDef* node_def = tf_node->pb_defs[0]; + + if (GetAttrValue(node_def, "value", value)) + { + const tensorflow::TensorProto& tf_tensor = value.tensor(); + void* mem_ptr = nullptr; + int* dims; + std::string layout; + int dim_num = GetTensorContentAndDim(tf_tensor, &dims, &mem_ptr, layout); + return mem_ptr; + } + + return nullptr; +} + +int load_reduction(TFNode* tf_node, TFGraph& tf_graph, ir_graph_t* graph, ir_node_t* node) +{ + TFNode* input1 = tf_node->inputs[1]; + struct reduction_param* param = (struct reduction_param*)node->op.param_mem; + const tensorflow::NodeDef* node_def = tf_node->pb_defs[0]; + tensorflow::AttrValue value; + GetAttrValue(node_def, "keep_dims", value); + param->keepdim = value.b(); + if (tf_node->op == "Sum") + { + param->type = 0; + } + else if (tf_node->op == "Mean") + { + param->type = 1; + } + else if (tf_node->op == "Asum") + { + param->type = 2; + } + else if (tf_node->op == "Sqsum") + { + param->type = 3; + } + else if (tf_node->op == "Max") + { + param->type = 4; + } + else if (tf_node->op == "Min") + { + param->type = 5; + } + else if (tf_node->op == "Prod") + { + param->type = 6; + } + else if (tf_node->op == "L2") + { + param->type = 7; + } + else if (tf_node->op == "Logsum") + { + param->type = 8; + } + else if (tf_node->op == "Logsumexp") + { + param->type = 9; + } + int* data = (int*)LoadConstParam(input1); + return 0; +} +int load_pad(TFNode* tf_node, TFGraph& tf_graph, ir_graph_t* graph, ir_node_t* node) +{ + struct pad_param* param = (struct pad_param*)node->op.param_mem; + + TFNode* input = tf_node->inputs[1]; + int* paddings = (int*)LoadConstParam(input); + param->mode = 0; + param->pad_0_h = paddings[0]; + param->pad_0_w = paddings[1]; + param->pad_1_h = paddings[2]; + param->pad_1_w = paddings[3]; + param->pad_2_h = paddings[4]; + param->pad_2_w = paddings[5]; + param->pad_3_h = paddings[6]; + param->pad_3_w = paddings[7]; + return 0; +} +void tensorflow_serializer::register_op_load() +{ + op_load_map["AvgPool"] = std::pair(OP_POOL, load_pool); + op_load_map["MaxPool"] = std::pair(OP_POOL, load_pool); + op_load_map["Conv2D"] = std::pair(OP_CONV, load_conv); + op_load_map["DepthwiseConv2dNative"] = std::pair(OP_CONV, load_conv); + op_load_map["FusedBatchNorm"] = std::pair(OP_BATCHNORM, load_batchnorm); + op_load_map["Relu6"] = std::pair(OP_RELU6, load_relu6); + op_load_map["Relu"] = std::pair(OP_RELU, load_relu); + op_load_map["Softmax"] = std::pair(OP_SOFTMAX, load_softmax); + op_load_map["Add"] = std::pair(OP_ELTWISE, load_eltwise); + op_load_map["Sub"] = std::pair(OP_ELTWISE, load_eltwise); + op_load_map["Mul"] = std::pair(OP_ELTWISE, load_eltwise); + op_load_map["Minimum"] = std::pair(OP_ELTWISE, load_eltwise); + op_load_map["Rsqrt"] = std::pair(OP_ELTWISE, load_eltwise); + op_load_map["Exp"] = std::pair(OP_ELTWISE, load_eltwise); + op_load_map["Log"] = std::pair(OP_ELTWISE, load_eltwise); + op_load_map["Pow"] = std::pair(OP_ELTWISE, load_eltwise); + op_load_map["RealDiv"] = std::pair(OP_ELTWISE, load_eltwise); + op_load_map["Sqrt"] = std::pair(OP_ELTWISE, load_eltwise); + op_load_map["AddN"] = std::pair(OP_ELTWISE, load_eltwise); + op_load_map["Mean"] = std::pair(OP_REDUCTION, load_reduction); + op_load_map["Pad"] = std::pair(OP_PAD, load_pad); +} \ No newline at end of file diff --git a/tools/convert_tool/tensorflow/tf2tengine.hpp b/tools/convert_tool/tensorflow/tf2tengine.hpp new file mode 100644 index 000000000..00199738e --- /dev/null +++ b/tools/convert_tool/tensorflow/tf2tengine.hpp @@ -0,0 +1,254 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/* + * Copyright (c) 2021, OPEN AI LAB + * Author: bzhang@openailab.com + */ + +#ifndef __TENSORFLOW2TENGINE_HPP__ +#define __TENSORFLOW2TENGINE_HPP__ + +#include +#include +#include +#include +#include +#include +#include + +#include "graph.pb.h" + +#include +#include +#include +#include +#include + +extern "C" { +#include "tengine/c_api.h" +#include "graph/graph.h" +#include "graph/subgraph.h" +#include "graph/node.h" +#include "graph/tensor.h" +#include "executer/executer.h" +#include "module/module.h" +#include "utility/log.h" +#include "utility/sys_port.h" +#include "utility/vector.h" +#include "save_graph/op_include.h" +} + +#define TF_RNN_LSTM 0 +#define TF_RNN_GRU 1 +#define TF_RNN_BASIC_LSTM 2 +#define TF_RNN_BASIC_RNN 3 + +struct TFNode +{ + int idx; + std::string name; + std::string op; + std::vector inputs; + std::vector outputs; + std::vector pb_defs; + ir_node_t* ir_node; + ir_tensor_t* ir_tensor; + bool no_static_node; + int BNAddType; + + TFNode() + { + no_static_node = false; + } + + virtual ~TFNode() + { + } +}; + +struct LSTMNode : public TFNode +{ + float clip; + + std::string direction; + + /* optional inputs */ + TFNode* kernel; + TFNode* bias; + TFNode* w_f_diag; + TFNode* w_i_diag; + TFNode* w_o_diag; + TFNode* projection; + TFNode* init_h; + TFNode* init_c; + TFNode* forget_bias; + + std::set rnn_graph; + + LSTMNode() + { + kernel = nullptr; + bias = nullptr; + w_f_diag = nullptr; + w_i_diag = nullptr; + w_o_diag = nullptr; + projection = nullptr; + init_h = nullptr; + init_c = nullptr; + forget_bias = nullptr; + } + + ~LSTMNode() + { + auto rnn_ir = rnn_graph.begin(); + auto rnn_end = rnn_graph.end(); + + while (rnn_ir != rnn_end) + { + delete (*rnn_ir); + rnn_ir++; + } + } +}; + +struct RNNNode : public TFNode +{ + float clip; + + std::string direction; + + /* optional inputs */ + TFNode* kernel; + TFNode* bias; + TFNode* init_h; + + std::set rnn_graph; + + RNNNode() + { + kernel = nullptr; + bias = nullptr; + init_h = nullptr; + } + + ~RNNNode() + { + auto rnn_ir = rnn_graph.begin(); + auto rnn_end = rnn_graph.end(); + + while (rnn_ir != rnn_end) + { + delete (*rnn_ir); + rnn_ir++; + } + } +}; + +struct GRUNode : public TFNode +{ + float clip; + + std::string direction; + + /* optional inputs */ + TFNode* kernel; + TFNode* bias; + TFNode* init_h; + // gru kernel & bias + TFNode* gate_kernel; + TFNode* gate_bias; + TFNode* candidate_kernel; + TFNode* candidate_bias; + + std::set rnn_graph; + + GRUNode() + { + kernel = nullptr; + bias = nullptr; + init_h = nullptr; + gate_kernel = nullptr; + gate_bias = nullptr; + candidate_kernel = nullptr; + candidate_bias = nullptr; + } + + ~GRUNode() + { + auto rnn_ir = rnn_graph.begin(); + auto rnn_end = rnn_graph.end(); + + while (rnn_ir != rnn_end) + { + delete (*rnn_ir); + rnn_ir++; + } + } +}; + +struct TFGraph +{ + std::vector seq_nodes; + + ~TFGraph() + { + for (auto node : seq_nodes) + delete node; + } +}; + +class tensorflow_serializer +{ +public: + graph_t tensorflow2tengine(std::string model_file); + typedef int (*op_load_t)(TFNode* tf_node, TFGraph& tf_graph, ir_graph_t* graph, ir_node_t* node); + +private: + std::unordered_map > op_load_map; + int load_graph(ir_graph_t* graph); + int load_model(ir_graph_t* graph, std::string model_file); + int load_binary_file(std::string model_file); + int load_graph_node(tensorflow::GraphDef& tf_net, ir_graph_t* graph); + int load_tensor_data(TFNode* tf_node, ir_graph_t* graph); + int optimize_graph(); + int set_graph_input(ir_graph_t* graph); + int set_graph_output(ir_graph_t* graph); + bool find_op_load_method(const std::string& op_name); + int generate_graph(ir_graph_t* graph); + int construct_graph(); + ir_tensor_t* find_tensor(ir_graph_t* graph, const std::string& tensor_name); + void register_op_load(); + int FindRNNScope(std::string& rnn_scope); + void ParseLSTMGraph(LSTMNode* lstm_node, std::set& rnn_graph); + void StripRNNScope(std::string& rnn_scope, int rnn_type); + void MergeReluMinimum(); + int MergeChildNode(TFNode* base_node, TFNode* child_node); + int MergeParentNode(TFNode* base_node, TFNode* child_node); + int BNRecursiveInputMerge(TFNode* node); + int FuseComposedBN(TFNode* cur_node); + int optimize_rnn(); + void CleanupResizeNearestNeighbor(); + tensorflow::GraphDef tf_net; + TFGraph tf_graph; + std::vector input_tensors; + std::vector output_tensors; +}; + +#endif \ No newline at end of file diff --git a/tools/convert_tool/tensorflow/types.proto b/tools/convert_tool/tensorflow/types.proto new file mode 100644 index 000000000..1beb2a1aa --- /dev/null +++ b/tools/convert_tool/tensorflow/types.proto @@ -0,0 +1,66 @@ +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"; + +// 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 + + // TODO(josh11b): DT_GENERIC_PROTO = ??; + // TODO(jeff,josh11b): DT_UINT64? DT_UINT32? + + // 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; +} +// LINT.ThenChange(https://www.tensorflow.org/code/tensorflow/c/c_api.h,https://www.tensorflow.org/code/tensorflow/go/tensor.go) diff --git a/tools/convert_tool/tensorflow/versions.proto b/tools/convert_tool/tensorflow/versions.proto new file mode 100644 index 000000000..7d5e58ae7 --- /dev/null +++ b/tools/convert_tool/tensorflow/versions.proto @@ -0,0 +1,31 @@ +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"; + +// 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; +};