1 /* Copyright 2016 The TensorFlow Authors. All Rights Reserved. 2 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 ==============================================================================*/ 15 #ifndef TENSORFLOW_CORE_COMMON_RUNTIME_SHAPE_REFINER_H_ 16 #define TENSORFLOW_CORE_COMMON_RUNTIME_SHAPE_REFINER_H_ 17 18 #include <vector> 19 20 #include "absl/container/flat_hash_map.h" 21 #include "tensorflow/core/common_runtime/graph_runner.h" 22 #include "tensorflow/core/framework/function.pb.h" 23 #include "tensorflow/core/framework/shape_inference.h" 24 #include "tensorflow/core/graph/graph.h" 25 #include "tensorflow/core/lib/core/status.h" 26 #include "tensorflow/core/platform/macros.h" 27 28 namespace tensorflow { 29 namespace grappler { 30 class GraphProperties; 31 } 32 33 // This class stores extra inference information in addition to 34 // InferenceContext, such as node input and output types. 35 class ExtendedInferenceContext { 36 public: ExtendedInferenceContext(std::unique_ptr<shape_inference::InferenceContext> ic,const Node * node)37 ExtendedInferenceContext( 38 std::unique_ptr<shape_inference::InferenceContext> ic, const Node* node) 39 : inference_context_(std::move(ic)), op_(node->name()) { 40 input_types_.reserve(node->num_inputs()); 41 for (int i = 0; i < node->num_inputs(); i++) { 42 input_types_.push_back(node->input_type(i)); 43 } 44 output_types_.reserve(node->num_outputs()); 45 for (int i = 0; i < node->num_outputs(); i++) { 46 output_types_.push_back(node->output_type(i)); 47 } 48 } 49 input_type(int64 idx)50 DataType input_type(int64 idx) const { return input_types_[idx]; } output_type(int64 idx)51 DataType output_type(int64 idx) const { return output_types_[idx]; } 52 get_context()53 shape_inference::InferenceContext* get_context() { 54 return inference_context_.get(); 55 } 56 op()57 std::string op() const { return op_; } 58 59 private: 60 std::unique_ptr<shape_inference::InferenceContext> inference_context_; 61 std::string op_; 62 std::vector<DataType> input_types_; 63 std::vector<DataType> output_types_; 64 65 TF_DISALLOW_COPY_AND_ASSIGN(ExtendedInferenceContext); 66 }; 67 68 // ShapeRefiner performs shape inference for TensorFlow Graphs. It is 69 // responsible for instantiating InferenceContext objects for each 70 // Node in the Graph, and providing/storing the 'input_tensor' Tensors 71 // used by Shape Inference functions, when available at graph 72 // construction time. 73 class ShapeRefiner { 74 public: 75 ShapeRefiner(int graph_def_version, const OpRegistryInterface* ops); 76 77 // Same as ShapeRefiner(versions.producer(), ops) 78 ShapeRefiner(const VersionDef& versions, const OpRegistryInterface* ops); 79 80 ~ShapeRefiner(); 81 82 // Performs validation of 'node' and runs 'node's shape function, 83 // storing its shape outputs. 84 // 85 // All inputs of 'node' must be added to ShapeRefiner prior to 86 // adding 'node'. 87 // 88 // Returns an error if: 89 // - the shape function for 'node' was not registered. 90 // - 'node' was added before its inputs. 91 // - The shape inference function returns an error. 92 Status AddNode(const Node* node); 93 94 // Sets 'node's 'output_port' output to have shape 'shape'. 95 // 96 // Returns an error if 'node' was not previously added to this 97 // object, if 'output_port' is invalid, or if 'shape' is 98 // not compatible with the existing shape of the output. 99 Status SetShape(const Node* node, int output_port, 100 shape_inference::ShapeHandle shape); 101 102 // Update the input shapes of node in case the shapes of the fan-ins of 'node' 103 // have themselves been modified (For example, in case of incremental shape 104 // refinement). If 'relax' is true, a new shape with the broadest set of 105 // information will be set as the new input (see InferenceContext::RelaxInput 106 // for full details and examples). Sets refined to true if any shapes have 107 // changed (in their string representations). Note that shapes may have been 108 // updated to newer versions (but with identical string representations) even 109 // if <*refined> is set to false. 110 Status UpdateNode(const Node* node, bool relax, bool* refined); 111 112 // Returns the InferenceContext for 'node', if present. GetContext(const Node * node)113 shape_inference::InferenceContext* GetContext(const Node* node) const { 114 auto it = node_to_context_.find(node); 115 if (it == node_to_context_.end()) { 116 return nullptr; 117 } 118 return it->second->get_context(); 119 } 120 121 // Returns the ExtendedInferenceContext for 'node', if present. GetExtendedContext(const Node * node)122 ExtendedInferenceContext* GetExtendedContext(const Node* node) const { 123 auto it = node_to_context_.find(node); 124 if (it == node_to_context_.end()) { 125 return nullptr; 126 } 127 return it->second.get(); 128 } 129 130 // Getters and setters for graph_def_version_. graph_def_version()131 int32 graph_def_version() const { return graph_def_version_; } set_graph_def_version(int32 version)132 void set_graph_def_version(int32 version) { graph_def_version_ = version; } 133 set_require_shape_inference_fns(bool require_shape_inference_fns)134 void set_require_shape_inference_fns(bool require_shape_inference_fns) { 135 require_shape_inference_fns_ = require_shape_inference_fns; 136 } set_disable_constant_propagation(bool disable)137 void set_disable_constant_propagation(bool disable) { 138 disable_constant_propagation_ = disable; 139 } 140 141 // Set function library to enable function shape inference. 142 // Without function library, function inference always yields unknown shapes. 143 // With this enabled, shape inference can take more time since it descends 144 // into all function calls. It doesn't do inference once for each function 145 // definition, but once for each function call. 146 // The function library must outlive the shape refiner. set_function_library_for_shape_inference(const tensorflow::FunctionLibraryDefinition * lib)147 void set_function_library_for_shape_inference( 148 const tensorflow::FunctionLibraryDefinition* lib) { 149 function_library_ = lib; 150 } 151 function_shape_inference_supported()152 bool function_shape_inference_supported() const { 153 return function_library_ != nullptr; 154 } 155 156 private: 157 friend class ShapeRefinerTest; 158 friend class ::tensorflow::grappler::GraphProperties; 159 160 // Returns true if the ranks and all dimensions of <s0> and <s1> are either 161 // equal in value or both unknown. 162 static bool SameDefinedShape(shape_inference::InferenceContext* c, 163 shape_inference::ShapeHandle s0, 164 shape_inference::ShapeHandle s1); 165 166 // Returns true if the shapes and types stored in <*existing> are identical in 167 // value to the shapes and types in <*updated>. 168 static bool IsUpdatedShapesOrTypes( 169 shape_inference::InferenceContext* c, 170 const std::vector<shape_inference::ShapeAndType>& existing, 171 const std::vector<shape_inference::ShapeAndType>& updated); 172 173 // Performs shape inference for the given function_def within the 174 // given outer_context. Internally it instantiates the function as a graph 175 // and runs shape inference recursively on it with the input shapes provided 176 // by the outer_context. 177 // 178 // Returns an error if: 179 // - number of inputs/outputs on outer_context doesn't match the function_def 180 // 181 // On success: 182 // - outer_context will contain output shapes inferred from input shapes 183 Status InferShapesForFunction(const FunctionDef* function_def, 184 AttrSlice attributes, 185 ExtendedInferenceContext* outer_context); 186 187 // Performs shape inference for a node inside a function. 188 // 189 // 'outer_context' is the 'InferenceContext' for the function's call op. 190 Status InferShapesForFunctionSubNode( 191 const Node* node, shape_inference::InferenceContext* outer_context); 192 193 // Performs validation of 'node' and runs 'node's shape function, 194 // storing its shape outputs. 195 // 196 // All inputs of 'node' must be added to ShapeRefiner prior to 197 // adding 'node'. 198 // 199 // Optionally, if 'node' is in a nested function, the 'InferenceContext' for 200 // the call op of the function can be passed as 'outer_context' (pass nullptr 201 // otherwise). This gets used to perform constant propagation across Arg nodes 202 // by requesting the constant of value of the incoming tensor from the 203 // 'outer_context'. 204 // 205 // Returns an error if: 206 // - the shape function for 'node' was not registered. 207 // - 'node' was added before its inputs. 208 // - The shape inference function returns an error. 209 Status AddNodeInternal(const Node* node, 210 shape_inference::InferenceContext* outer_context); 211 212 // Attempts to evaluate the 'dst_idx'-th input to 'node'. If the input edge 213 // value can be evaluated, 'evaluated' is set to true and the value returned 214 // in 'result'. Otherwise 'evaluated' is set to false. 215 // 216 // Optionally, if 'node' is in a nested function, the 'InferenceContext' for 217 // the call op of the function can be passed as 'outer_context' (pass nullptr 218 // otherwise). This gets used to perform constant propagation across Arg nodes 219 // by requesting the constant of value of the incoming tensor from the 220 // 'outer_context'. 221 Status EvaluateConstantTensorForEdge( 222 const Node* node, int dst_idx, bool* evaluated, Tensor* result, 223 shape_inference::InferenceContext* outer_context); 224 225 // Wrapper around EvaluateConstantTensorForEdge for scalar int32/int64 input 226 // tensors. The caller is responsible for checking that the specified edge is 227 // scalar and int32 or int64. 228 // 229 // Optionally, if 'node' is in a nested function, the 'InferenceContext' for 230 // the call op of the function can be passed as 'outer_context' (pass nullptr 231 // otherwise). This gets used to perform constant propagation across Arg nodes 232 // by requesting the constant of value of the incoming tensor from the 233 // 'outer_context'. 234 Status EvaluateConstantIntScalarEdge( 235 const Node* node, int dst_idx, bool* evaluated, int64* result, 236 shape_inference::InferenceContext* outer_context); 237 238 // This function tries to materialize as much information about the 'node''s 239 // dst_idx input as a statically computable shape, and the result may be 240 // partially known, depending on what is statically inferable. 241 // 242 // This is called when node.input[dst_idx] is a tensor that is used to define 243 // the shape of some other tensor (e.g., the second argument to Reshape is a 244 // <shape> tensor, where each element of the shape tensor is a dimension of 245 // the target tensor). It returns in <result> a shape for that input. 246 // 247 // Unlike simply resolving node.input[dst_idx] to a constant and then 248 // converting that to a shape, this function can return a partial shape. This 249 // is useful for cases where the shape tensor is only partially defined, such 250 // as with calls for: reshape(x, shape(y)) where shape(y) is partially 251 // defined. 252 // 253 // The implementation has op implementations for ops commonly called on shape 254 // tensors, and the implementations are specialized to shape tensors (namely, 255 // the output is a vector). 256 // 257 // <target_context> is used when creating new DimensionHandle and ShapeHandle 258 // objects. 259 // 260 // Optionally, if 'node' is in a nested function, the 'InferenceContext' for 261 // the call op of the function can be passed as 'outer_context' (pass nullptr 262 // otherwise). This gets used to perform constant propagation across Arg nodes 263 // by requesting the constant of value of the incoming tensor from the 264 // 'outer_context'. 265 Status ConstantPartialShape(shape_inference::InferenceContext* target_context, 266 const Node* node, int dst_idx, 267 shape_inference::ShapeHandle* result, 268 shape_inference::InferenceContext* outer_context); 269 270 // Implementation of ConstantPartialShape for StridedSlice nodes. 271 // 272 // Optionally, if 'node' is in a nested function, the 'InferenceContext' for 273 // the call op of the function can be passed as 'outer_context' (pass nullptr 274 // otherwise). This gets used to perform constant propagation across Arg nodes 275 // by requesting the constant of value of the incoming tensor from the 276 // 'outer_context'. 277 Status PartialStridedSliceShape( 278 Node* slice_node, shape_inference::InferenceContext* ctx, 279 shape_inference::ShapeHandle* result, 280 shape_inference::InferenceContext* outer_context); 281 282 // Runs the shape function registered for the node's op type. 283 // 284 // Optionally, if 'node' is in a nested function, the 'InferenceContext' for 285 // the call op of the function can be passed as 'outer_context' (pass nullptr 286 // otherwise). This gets used to perform constant propagation across Arg nodes 287 // by requesting the constant of value of the incoming tensor from the 288 // 'outer_context'. 289 Status RunShapeFn(const Node* node, const OpRegistrationData* op_reg_data, 290 ExtendedInferenceContext* ec, 291 shape_inference::InferenceContext* outer_context = nullptr); 292 293 int32 graph_def_version_; 294 const OpRegistryInterface* const ops_registry_; 295 296 // The lifetime of the tensors are bound to the runner, so it should be the 297 // deleted after the tensors. 298 GraphRunner graph_runner_; 299 300 // Stores a map from a node to its ExtendedInferenceContext. 301 absl::flat_hash_map<const Node*, std::unique_ptr<ExtendedInferenceContext>, 302 hash<const Node*>> 303 node_to_context_; 304 305 // Holds a cache from 'tensor name' to the tensor that is 306 // evaluatable as a constant expression. This reduces repeated 307 // execution of the entire constant subgraph as a graph is being 308 // built up. This could be changed to some kind of size-based LRU 309 // cache to avoid consuming too much memory, if that eventually 310 // becomes a concern. 311 // 312 // Only tensors less than 1KiB are currently stored in the cache. 313 static constexpr int64 kMaxTensorSize = 1024; 314 std::unordered_map<string, Tensor> const_tensor_map_; 315 316 bool require_shape_inference_fns_ = true; 317 bool disable_constant_propagation_ = false; 318 319 // Function library is optional, but has to be set to enable function 320 // shape inference. 321 const tensorflow::FunctionLibraryDefinition* function_library_ = nullptr; 322 323 // Cache the graph corresponding to each function definition for which shapes 324 // are refined. 325 absl::flat_hash_map<const FunctionDef*, std::unique_ptr<const Graph>, 326 hash<const FunctionDef*>> 327 functions_; 328 329 TF_DISALLOW_COPY_AND_ASSIGN(ShapeRefiner); 330 }; 331 332 } // namespace tensorflow 333 334 #endif // TENSORFLOW_CORE_COMMON_RUNTIME_SHAPE_REFINER_H_ 335