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