1 /* Copyright 2017 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 16 #ifndef TENSORFLOW_CORE_GRAPPLER_COSTS_GRAPH_PROPERTIES_H_ 17 #define TENSORFLOW_CORE_GRAPPLER_COSTS_GRAPH_PROPERTIES_H_ 18 19 #include <unordered_map> 20 #include <unordered_set> 21 #include <vector> 22 23 #include "tensorflow/core/framework/shape_inference.h" 24 #include "tensorflow/core/grappler/clusters/cluster.h" 25 #include "tensorflow/core/grappler/costs/op_performance_data.pb.h" 26 #include "tensorflow/core/grappler/grappler_item.h" 27 28 namespace tensorflow { 29 30 namespace grappler { 31 32 // Optional attributes that tell about node output information. 33 // We use these side information, if provided, for static shape inference 34 // and VirtualScheduler scheduling. 35 36 // Switch op attribute as a vector of int that tells which branch the 37 // Switch output is taken on every round of execution. 38 // Used for scheduling ops after Switch correctly (e.g., While loop). 39 ABSL_CONST_INIT const char kOutputSlots[] = "_output_slot_vector"; 40 41 // Example: 42 // Assume a node has two outputs and iterated for three times. Then it has: 43 // _execution_count = 3 44 // _output_sizes_vector = [2, 2, 2] 45 // _output_dtype_vector.size = 6 46 // _output_shape_vector.size = 6 47 48 // If all the iterations have same output shapes, then 49 // _execution_count = 3 50 // _same_output_for_iterations = true 51 // _output_sizes_vector = [2] 52 // _output_dtype_vector.size = 2 53 // _output_shape_vector.size = 2 54 55 // How many times this node has been executed. 56 ABSL_CONST_INIT const char kExecutionCount[] = "_execution_count"; 57 58 // Records the output sizes for each round of execution. 59 ABSL_CONST_INIT const char kOutputSizes[] = "_output_sizes_vector"; 60 61 // The node has been scheduled multiple times with outputs that have the same 62 // shape. 63 ABSL_CONST_INIT const char kOutputSame[] = "_same_output_for_iterations"; 64 65 // Outputs DataType vector. 66 ABSL_CONST_INIT const char kOutputTypes[] = "_output_dtype_vector"; 67 68 // Outputs TensorShapeProto vector. 69 ABSL_CONST_INIT const char kOutputShapes[] = "_output_shape_vector"; 70 71 class SymbolicShapeRefiner; 72 class TopoQueue; 73 74 // Infer OpInfo::TensorProperties for graph nodes inputs/outputs. 75 // 76 // Typical use case, is to infer tensor properties from a graph, before doing 77 // optimization pass. Nodes modified during optimization pass have to be 78 // invalidated, to prevent further incorrect optimizations based on wrong shape 79 // and data type properties. 80 class GraphProperties { 81 public: 82 // The item must outlive the properties GraphProperties(const GrapplerItem & item)83 explicit GraphProperties(const GrapplerItem& item) : item_(item) {} 84 85 // Infer the shapes through abstract interpretation. Feed information can be 86 // incorrect so it should be discarded to ensure correctness of the analysis. 87 // However, it can help infer shapes in the fanout of fed nodes (even though 88 // the correctness of these shapes can't be guaranteed), so in some cases 89 // (such as simulation or scheduling) it makes sense of keep these shapes. 90 // aggressive_shape_inference option executes nodes on the host to identify 91 // output values when possible and does other aggressive strategies. 92 // Similar to assuming_valid_feeds, this may cause incorrectness in graph 93 // analyses, but is useful for simulation or scheduling. 94 // If include_input_tensor_values is true, the values of constant tensors 95 // will included in the input properties. 96 // If include_output_tensor_values is true, the values of constant tensors 97 // will be included in the output properties. 98 Status InferStatically(bool assume_valid_feeds, 99 bool aggressive_shape_inference, 100 bool include_input_tensor_values, 101 bool include_output_tensor_values); InferStatically(bool assume_valid_feeds,bool aggressive_shape_inference,bool include_tensor_values)102 Status InferStatically(bool assume_valid_feeds, 103 bool aggressive_shape_inference, 104 bool include_tensor_values) { 105 return InferStatically( 106 assume_valid_feeds, 107 /*aggressive_shape_inference=*/aggressive_shape_inference, 108 /*include_input_tensor_values=*/include_tensor_values, 109 /*include_output_tensor_values=*/include_tensor_values); 110 } InferStatically(bool assume_valid_feeds)111 Status InferStatically(bool assume_valid_feeds) { 112 return InferStatically(assume_valid_feeds, 113 /*aggressive_shape_inference=*/false, 114 /*include_tensor_values=*/true); 115 } 116 // Infer the shape by running the graph on the specified cluster and recording 117 // the shapes of the processed tensors. 118 Status InferDynamically(Cluster* cluster); 119 // Extract the properties from a cost graph. For testing only since there is 120 // no way to ensure that the cost graph match the item. 121 Status InferFromCostGraph(const CostGraphDef& cost_graph); 122 123 // Stores `item_.graph` with the inferred output shapes to `output_graph_def`. 124 Status AnnotateOutputShapes(GraphDef* output_graph_def, 125 bool allow_symbolic_shapes) const; 126 AnnotateOutputShapes(GraphDef * output_graph_def)127 Status AnnotateOutputShapes(GraphDef* output_graph_def) const { 128 return AnnotateOutputShapes(output_graph_def, false); 129 } 130 131 // Return the properties of node inputs/outputs, including data types and 132 // shapes. Note that the dimensions in the shapes can be negative. We use the 133 // -1 value to denote that we don't know anything about a dimension. We use 134 // values strictly less than -1 to encode symbolic dimensions: although we 135 // don't know the actual value of the symbolic dimension, we know that all the 136 // dimensions denoted by the same negative value are the equal. 137 bool HasInputProperties(const string& node_name) const; 138 bool HasOutputProperties(const string& node_name) const; 139 const std::vector<OpInfo::TensorProperties>& GetInputProperties( 140 const string& node_name) const; 141 const std::vector<OpInfo::TensorProperties>& GetOutputProperties( 142 const string& node_name) const; 143 144 // Invalidate input/output properties for nodes modified during graph 145 // optimization pass, to prevent potential optimizations, based on incorrect 146 // shape information. 147 void ClearInputProperties(const string& node_name); 148 void ClearOutputProperties(const string& node_name); 149 // Returns true if we have *any* properties. has_properties()150 bool has_properties() const { 151 return !input_properties_.empty() || !output_properties_.empty(); 152 } 153 CheckShapeIncompatible(const string & node_name)154 bool CheckShapeIncompatible(const string& node_name) const { 155 return incompatible_shape_nodes_.find(node_name) != 156 incompatible_shape_nodes_.end(); 157 } 158 159 private: 160 // Relaxes shapes <shapes_and_types>, determined from an EnqueueV2 node, into 161 // <*queue_shapes_and_types>. 162 static Status RelaxEnqueueShapesAndMergeTypes( 163 SymbolicShapeRefiner* shape_refiner, const NodeDef* qnode, 164 const std::vector<shape_inference::ShapeAndType>& shapes_and_types, 165 std::vector<shape_inference::ShapeAndType>* queue_shapes_and_types); 166 167 // Update the shapes of the enqueue node, port them over to the corresponding 168 // queue, and schedule the reprocessing of the queue if needed. 169 static Status UpdateEnqueue( 170 const NodeDef* enqueue_node, 171 const std::unordered_map<const NodeDef*, const NodeDef*>& 172 resource_handles, 173 SymbolicShapeRefiner* shape_refiner, bool* new_shapes); 174 175 // Update the shapes and types of the Queue node, if not set by Enqueue node. 176 static Status UpdateQueue(const NodeDef* queue_node, 177 SymbolicShapeRefiner* shape_refiner, 178 bool* new_shapes); 179 180 // Update the output shapes of a Merge node, and enqueue its fanout in 181 // new_shapes if needed. 182 Status UpdateMerge(SymbolicShapeRefiner* shape_refiner, const NodeDef* node, 183 bool* new_shapes) const; 184 // Process the Enter node, and enqueue its fanout in new_shapes if needed. 185 static Status UpdateEnter(SymbolicShapeRefiner* shape_refiner, 186 const NodeDef* node, bool* new_shapes); 187 // Update the shapes for node 'n'. If output shapes for n have changed, 188 // enqueue its fanout in 'new_shapes'. 189 Status UpdateShapes(SymbolicShapeRefiner* shape_refiner, 190 const std::unordered_map<const NodeDef*, const NodeDef*>& 191 resource_handles, 192 const NodeDef* n, bool* new_shapes) const; 193 // Propagate the shapes for the nodes enqueued in new_shapes and their 194 // transitive fanout until a fixed point is reached. 195 Status PropagateShapes( 196 SymbolicShapeRefiner* shape_refiner, TopoQueue* new_shapes, 197 const std::unordered_map<const NodeDef*, const NodeDef*>& 198 resource_handles, 199 int num_loops) const; 200 201 // Data members 202 const GrapplerItem& item_; 203 std::unordered_map<string, std::vector<OpInfo::TensorProperties>> 204 input_properties_; 205 std::unordered_map<string, std::vector<OpInfo::TensorProperties>> 206 output_properties_; 207 const std::vector<OpInfo::TensorProperties> missing_properties_; 208 209 // Nodes with output shape incompatible between shape inference and 210 // annotation. 211 std::unordered_set<string> incompatible_shape_nodes_; 212 }; 213 214 } // end namespace grappler 215 } // end namespace tensorflow 216 217 #endif // TENSORFLOW_CORE_GRAPPLER_COSTS_GRAPH_PROPERTIES_H_ 218