• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Copyright 2018 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 #include "tensorflow/core/grappler/utils/symbolic_shapes.h"
17 
18 #include <unordered_map>
19 
20 #include "tensorflow/core/util/bcast.h"
21 
22 namespace tensorflow {
23 namespace grappler {
24 namespace {
25 
ShapeDims(const TensorShapeProto & shape)26 BCast::Vec ShapeDims(const TensorShapeProto& shape) {
27   BCast::Vec dims;
28   dims.reserve(shape.dim_size());
29   for (int i = 0; i < shape.dim_size(); ++i)
30     dims.push_back(shape.dim(i).size());
31   return dims;
32 }
33 
34 }  // namespace
35 
IsKnown(const TensorShapeProto::Dim & dim)36 bool IsKnown(const TensorShapeProto::Dim& dim) { return dim.size() >= 0; }
37 
IsKnownSymbolically(const TensorShapeProto::Dim & dim)38 bool IsKnownSymbolically(const TensorShapeProto::Dim& dim) {
39   return dim.size() <= -2;
40 }
41 
IsUnknown(const TensorShapeProto::Dim & dim)42 bool IsUnknown(const TensorShapeProto::Dim& dim) { return dim.size() == -1; }
43 
ShapeIsSymbolicallyDefined(const TensorShapeProto & shape)44 bool ShapeIsSymbolicallyDefined(const TensorShapeProto& shape) {
45   return !shape.unknown_rank() &&
46          std::all_of(
47              shape.dim().begin(), shape.dim().end(),
48              [](const TensorShapeProto::Dim& dim) { return !IsUnknown(dim); });
49 }
50 
ShapeIsSymbolicallyDefined(const OpInfo::TensorProperties & properties)51 bool ShapeIsSymbolicallyDefined(const OpInfo::TensorProperties& properties) {
52   return ShapeIsSymbolicallyDefined(properties.shape());
53 }
54 
Rank(const TensorShapeProto & shape)55 int Rank(const TensorShapeProto& shape) {
56   if (shape.unknown_rank()) {
57     return -1;
58   }
59   return shape.dim_size();
60 }
61 
NumCoefficients(const TensorShapeProto & shape)62 int64 NumCoefficients(const TensorShapeProto& shape) {
63   if (shape.unknown_rank()) {
64     return -1;
65   }
66   int64 num_coefficients = 1;
67   for (const auto& dim : shape.dim()) {
68     if (dim.size() < 0) {
69       return -1;
70     }
71     num_coefficients *= dim.size();
72   }
73   return num_coefficients;
74 }
75 
ShapesSymbolicallyEqual(const TensorShapeProto & left,const TensorShapeProto & right)76 bool ShapesSymbolicallyEqual(const TensorShapeProto& left,
77                              const TensorShapeProto& right) {
78   if (left.unknown_rank() || right.unknown_rank() ||
79       left.dim_size() != right.dim_size()) {
80     return false;
81   }
82   for (int i = 0; i < left.dim_size(); ++i) {
83     const auto& ldim = left.dim(i);
84     const auto& rdim = right.dim(i);
85     if (IsUnknown(ldim) || IsUnknown(rdim) || ldim.size() != rdim.size()) {
86       return false;
87     }
88   }
89   return true;
90 }
91 
ShapesSymbolicallyEqual(const OpInfo::TensorProperties & left,const OpInfo::TensorProperties & right)92 bool ShapesSymbolicallyEqual(const OpInfo::TensorProperties& left,
93                              const OpInfo::TensorProperties& right) {
94   return ShapesSymbolicallyEqual(left.shape(), right.shape());
95 }
96 
ShapesBroadcastable(const TensorShapeProto & left,const TensorShapeProto & right)97 bool ShapesBroadcastable(const TensorShapeProto& left,
98                          const TensorShapeProto& right) {
99   if (!ShapeIsSymbolicallyDefined(left) || !ShapeIsSymbolicallyDefined(right)) {
100     return false;
101   }
102   BCast bcast(ShapeDims(left), ShapeDims(right),
103               /*fewer_dims_optimization*/ false);
104   return bcast.IsValid();
105 }
106 
ShapesBroadcastable(const OpInfo::TensorProperties & left,const OpInfo::TensorProperties & right)107 bool ShapesBroadcastable(const OpInfo::TensorProperties& left,
108                          const OpInfo::TensorProperties& right) {
109   return ShapesBroadcastable(left.shape(), right.shape());
110 }
111 
ShapeAfterBroadcast(const TensorShapeProto & left,const TensorShapeProto & right,TensorShapeProto * output_shape)112 bool ShapeAfterBroadcast(const TensorShapeProto& left,
113                          const TensorShapeProto& right,
114                          TensorShapeProto* output_shape) {
115   if (!ShapeIsSymbolicallyDefined(left) || !ShapeIsSymbolicallyDefined(right)) {
116     return false;
117   }
118   BCast bcast(ShapeDims(left), ShapeDims(right),
119               /*fewer_dims_optimization*/ false);
120   if (!bcast.IsValid()) {
121     return false;
122   }
123   output_shape->set_unknown_rank(false);
124   output_shape->clear_dim();
125   for (const auto& dim : bcast.output_shape()) {
126     output_shape->add_dim()->set_size(dim);
127   }
128   return true;
129 }
130 
CompareSymbolicallyShapedTensorSizes(const TensorShapeProto & left,const TensorShapeProto & right)131 bool CompareSymbolicallyShapedTensorSizes(const TensorShapeProto& left,
132                                           const TensorShapeProto& right) {
133   // if one of the ranks is unknown, it's impossible to compare tensor sizes
134   if (left.unknown_rank() || right.unknown_rank()) {
135     return false;
136   }
137 
138   // Tensor size, computed as a product of defined dimensions
139   int64 left_defined_size = 1;
140   int64 right_defined_size = 1;
141 
142   // Keep how many times each unknown dimension appeared on the left and right
143   std::unordered_map<int64, int64> left_unknown_dims;
144   std::unordered_map<int64, int64> right_unknown_dims;
145 
146   // Assign unique id to every unknown dimension (-1). We are going to
147   // assign positive ids, because negative values are already used by
148   // symbolic dimensions.
149   int64 unknown_dim_id = 1;
150 
151   // For each shape dimension update "defined tensor size", if shape is defined,
152   // or increment a counter for unknown dim.
153   auto process_dimensions =
154       [&unknown_dim_id](const TensorShapeProto& shape, int64* defined_size,
155                         std::unordered_map<int64, int64>* unknown_dims) {
156         for (int i = 0; i < shape.dim_size(); ++i) {
157           const auto& dim = shape.dim(i);
158           int64 dim_size = dim.size();
159           if (dim_size > 0) {
160             *defined_size *= dim_size;
161           } else if (IsUnknown(dim)) {
162             ++(*unknown_dims)[unknown_dim_id++];
163           } else if (IsKnownSymbolically(dim)) {
164             ++(*unknown_dims)[dim_size];
165           }
166         }
167       };
168 
169   process_dimensions(left, &left_defined_size, &left_unknown_dims);
170   process_dimensions(right, &right_defined_size, &right_unknown_dims);
171 
172   // Compute a union of unknown dimension ids appeared in both shapes
173   std::set<int64> unknown_dims;
174   for (const auto& el : left_unknown_dims) unknown_dims.insert(el.first);
175   for (const auto& el : right_unknown_dims) unknown_dims.insert(el.first);
176 
177   // Cancel unknown dimensions that appeared in both shapes
178   for (int64 unknown_dim : unknown_dims) {
179     int64 co_occurrence = std::min(left_unknown_dims[unknown_dim],
180                                    right_unknown_dims[unknown_dim]);
181     left_unknown_dims[unknown_dim] -= co_occurrence;
182     right_unknown_dims[unknown_dim] -= co_occurrence;
183   }
184 
185   // Count unbalanced unknown dimensions
186   int64 left_unbalanced_unknown_dims = 0;
187   int64 right_unbalanced_unknown_dims = 0;
188   for (const auto& el : left_unknown_dims)
189     left_unbalanced_unknown_dims += el.second;
190   for (const auto& el : right_unknown_dims)
191     right_unbalanced_unknown_dims += el.second;
192 
193   if (left_unbalanced_unknown_dims == 0 && right_unbalanced_unknown_dims == 0) {
194     // If unknown dimensions cancelled each other, compare tensor sizes
195     // represented by defined dimensions
196     return left_defined_size < right_defined_size;
197   }
198 
199   if (left_defined_size <= right_defined_size &&
200       left_unbalanced_unknown_dims == 0 && right_unbalanced_unknown_dims > 0) {
201     // If size of a 'left" tensor computed from defined dimensions less or
202     // equal, and shape on the right has unbalanced unknown dimensions, we can
203     // guarantee that shape on the left is strictly smaller (assuming that
204     // unknown dimension size is larger than 1)
205     return true;
206   }
207 
208   // In every other case, assuming that unknown dimensions can be arbitrary
209   // large in size, we can't guarantee any ordering
210   return false;
211 }
212 
CompareSymbolicallyShapedTensorSizes(const OpInfo::TensorProperties & left,const OpInfo::TensorProperties & right)213 bool CompareSymbolicallyShapedTensorSizes(
214     const OpInfo::TensorProperties& left,
215     const OpInfo::TensorProperties& right) {
216   return CompareSymbolicallyShapedTensorSizes(left.shape(), right.shape());
217 }
218 
ComputeSizeRatio(const TensorShapeProto & numerator,const TensorShapeProto & denominator)219 int64 ComputeSizeRatio(const TensorShapeProto& numerator,
220                        const TensorShapeProto& denominator) {
221   if (numerator.unknown_rank() || denominator.unknown_rank()) {
222     return -1;
223   }
224   std::multiset<int> symbolic_dims;
225   int64 num = 1;
226   for (const auto& dim : numerator.dim()) {
227     if (dim.size() == -1) {
228       return -1;
229     } else if (dim.size() < -1) {
230       symbolic_dims.insert(dim.size());
231     } else {
232       num *= dim.size();
233     }
234   }
235   int64 denom = 1;
236   for (const auto& dim : denominator.dim()) {
237     if (dim.size() == -1) {
238       return -1;
239     } else if (dim.size() < -1) {
240       auto it = symbolic_dims.find(dim.size());
241       if (it == symbolic_dims.end()) {
242         return -1;
243       }
244       symbolic_dims.erase(it);
245     } else {
246       denom *= dim.size();
247     }
248   }
249   if (denom == 0) {
250     return -1;
251   }
252   if (!symbolic_dims.empty()) {
253     return -1;
254   }
255   return num / denom;
256 }
257 
258 }  // end namespace grappler
259 }  // end namespace tensorflow
260