• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #ifndef TENSORFLOW_LITE_TOCO_TOOLING_UTIL_H_
16 #define TENSORFLOW_LITE_TOCO_TOOLING_UTIL_H_
17 
18 #include <algorithm>
19 #include <cmath>
20 #include <iostream>
21 #include <limits>
22 #include <memory>
23 #include <string>
24 #include <vector>
25 
26 #include "absl/strings/string_view.h"
27 #include "tensorflow/core/platform/logging.h"
28 #include "tensorflow/lite/kernels/internal/types.h"
29 #include "tensorflow/lite/toco/model.h"
30 #include "tensorflow/lite/toco/model_flags.pb.h"
31 #include "tensorflow/lite/toco/runtime/types.h"
32 #include "tensorflow/lite/toco/toco_flags.pb.h"
33 #include "tensorflow/lite/toco/types.pb.h"
34 #include "tensorflow/core/lib/core/errors.h"
35 #include "tensorflow/core/lib/core/status.h"
36 
37 // TODO(aselle): Replace with using a container specific hash override instead.
38 namespace std {
39 template <>
40 struct hash<toco::OperatorType> {
41   size_t operator()(const toco::OperatorType& op) const {
42     return std::hash<size_t>()(static_cast<size_t>(op));
43   }
44 };
45 }  // namespace std
46 
47 namespace toco {
48 
49 constexpr int kLogLevelModelChanged = 1;
50 constexpr int kLogLevelModelUnchanged = 2;
51 
52 absl::string_view FindLongestCommonPrefix(absl::string_view a,
53                                           absl::string_view b);
54 std::string LogName(const Operator& op);
55 
56 std::string ArrayDataTypeName(ArrayDataType data_type);
57 
58 // Returns true if the given array is specified as a model input array.
59 bool IsInputArray(const Model& model, const std::string& array_name);
60 // Returns true if the given array is specified as a model output array.
61 bool IsOutputArray(const Model& model, const std::string& array_name);
62 
63 bool IsArrayConsumed(const Model& model, const std::string& name);
64 int CountTrueOutputs(const Model& model, const Operator& op);
65 
66 int CountOpsWithInput(const Model& model, const std::string& array_name);
67 bool DeleteArrayIfUnused(const std::string& array_name, Model* model);
68 
69 // Deletes the op and any of its input and output arrays if they are unused
70 // after the op has been deleted.
71 void DeleteOpAndArrays(Model* model, const Operator* op);
72 
73 std::vector<std::unique_ptr<Operator>>::const_iterator FindOpWithOutput(
74     const Model& model, const std::string& array_name);
75 Operator* GetOpWithOutput(const Model& model, const std::string& array_name);
76 
77 std::vector<std::unique_ptr<Operator>>::iterator FindOpWithOutput(
78     Model& model, const std::string& array_name);
79 
80 std::vector<std::unique_ptr<Operator>>::const_iterator FindOpWithInput(
81     const Model& model, const std::string& array_name);
82 
83 std::vector<std::unique_ptr<Operator>>::iterator FindOpWithInput(
84     Model& model, const std::string& array_name);
85 
86 Operator* GetOpWithInput(const Model& model, const std::string& array_name);
87 Operator* GetFirstOpWithInput(const Model& model,
88                               const std::string& array_name);
89 
90 // Replaces all uses of the |old_array_name| with the |new_array_name|.
91 void ReplaceArrayUsage(Model* model, const std::string& old_array_name,
92                        const std::string& new_array_name);
93 
94 std::vector<std::unique_ptr<Operator>>::const_iterator FindOp(
95     const Model& model, const Operator* op);
96 std::vector<std::unique_ptr<Operator>>::iterator FindOp(Model& model,
97                                                         const Operator* op);
98 
99 const char* OperatorTypeName(OperatorType type);
100 std::string HelpfulOperatorTypeName(const Operator& op);
101 
102 // Whether the operator can be fused with an activation function. Note that this
103 // will return false by default for new operators; fusing support is opt-in.
104 bool OperatorSupportsFusedActivation(OperatorType type);
105 
106 void DumpGraphvizVideoFrame(const Model& model);
107 void LogDump(int log_level, const std::string& message, const Model& model);
108 void LogSummary(int log_level, const std::string& message, const Model& model);
109 
110 // TODO(b/36075966): Clean up when dims superseded by array shape.
111 void ExtendShape(Shape* shape, int new_shape_size);
112 
113 // TODO(b/36075966): Clean up when dims superseded by array shape.
114 void UnextendShape(Shape* shape, int new_shape_size);
115 
116 // Checks that all dimensions of 'shape' are at least 1. Note that scalars,
117 // lacking dimensions, satisfy this condition and are considered non-empty.
118 bool IsNonEmpty(const Shape& shape);
119 
120 // Given two shapes with potentially different dimensionality and dimension
121 // arrays d0 and d1. Without loss of generality, assume that shape0 may have
122 // higher dimensionality (length(d0) >= length(d1)). Then shape0 and shape1
123 // "agree up to broadcasting" if:
124 // - When walking the d0 and d1 from back to front with indices i0, i1,
125 //   d0[i0] == d1[i1] or d0[i0] == 1 or d1[i1] == 1, for each dimension until
126 //   i1 == 0 (inclusive).
127 bool ShapesAgreeUpToBroadcasting(const Shape& shape0, const Shape& shape1);
128 
129 // A stricter constraint than ShapesAgreeUpToBroadcasting().
130 //
131 // Given two shapes with potentially different dimensionality and dimension
132 // arrays d0 and d1. Without loss of generality, assume that shape0 may have
133 // higher dimensionality (length(d0) >= length(d1)). Then shape0 and shape1
134 // "agree up to extending" if:
135 // - When walking the d0 and d1 from back to front with indices i0, i1,
136 //   d0[i0] == d1[i1] for each dimension until i1 == 0 (inclusive).
137 // - For the remaining indices [0..i0), d0[i0] == 1.
138 bool ShapesAgreeUpToExtending(const Shape& shape0, const Shape& shape1);
139 
140 inline ::tflite::RuntimeShape ToRuntimeShape(const Shape& shape) {
141   return ::tflite::RuntimeShape(shape.dimensions_count(), shape.dims().data());
142 }
143 
144 bool IsArrayFullyConnectedWeights(const Model& model, const std::string& name);
145 
146 // If there is a wildcard dimension (-1), this may return a negative value.
147 int RequiredBufferSizeForShape(const Shape& shape);
148 
149 bool IsConstantParameterArray(const Model& model, const std::string& name);
150 
151 // Compares two constant parameter arrays for exact equality.
152 bool CompareConstantArrays(const Array& lhs_array, const Array& rhs_array);
153 
154 void CheckNoMissingArray(const Model& model);
155 void CheckInvariants(const Model& model);
156 
157 void CheckModelCounts(const Model& model);
158 
159 void FixOperatorOrdering(Model* model);
160 void FixNoMissingArray(Model* model);
161 void FixNoOrphanedArray(Model* model);
162 
163 // Fixes input/output arrays that may have issues during export or inference.
164 void FixEdgeArrays(Model* model);
165 
166 // Finds and deduplicates large constant arrays in the model.
167 // After constant propagation runs it's possible to end up with several of the
168 // same large array (whether they be zeros or otherwise).
169 //
170 // |min_size| is used to adjust the minimum size in bytes of an array before
171 // it's considered for deduping. As deduping can make the graphs more difficult
172 // to read this helps prevent small arrays from spidering out.
173 void DedupeConstantArrays(Model* model, size_t min_size);
174 
175 // Copies the contents of an array into another.
176 // Expects that the shape and data type match.
177 template <ArrayDataType A>
178 void CopyArrayBuffer(const Array& source_array, Array* target_array) {
179   int source_buffer_size = RequiredBufferSizeForShape(source_array.shape());
180   int target_buffer_size = RequiredBufferSizeForShape(target_array->shape());
181   CHECK_EQ(source_buffer_size, target_buffer_size)
182       << "Buffer sizes must match in element count";
183   CHECK(source_array.data_type == target_array->data_type)
184       << "Data types must match";
185   if (source_array.buffer) {
186     const auto& source_buffer = source_array.GetBuffer<A>();
187     auto& target_buffer = target_array->GetMutableBuffer<A>();
188     target_buffer.data = source_buffer.data;
189   }
190 }
191 
192 // Inserts a no-op reshape operator between the source array and the target
193 // array. This effectively just copies the data.
194 void InsertCopyOperator(Model* model, const std::string& source_array_name,
195                         const std::string& target_array_name);
196 
197 // Clones an array with all data and parameters.
198 void CloneArray(Model* model, const std::string& source_array_name,
199                 const std::string& target_array_name);
200 
201 void ResolveModelFlags(const ModelFlags& model_flags, Model* model);
202 
203 template <typename T>
204 T ConvertOperator(Operator* o, OperatorType type) {
205   if (o != nullptr && o->type == type) {
206     return static_cast<T>(o);
207   }
208 
209   return nullptr;
210 }
211 
212 void CheckIsReadyForQuantization(const Model& model);
213 
214 bool ReshapeIsEquivalentToTranspose(const Model& model,
215                                     const TensorFlowReshapeOperator* op,
216                                     bool allow_extra_unary_dims);
217 
218 inline int Offset(const Shape& shape, const std::vector<int>& indices) {
219   DCHECK_EQ(shape.dimensions_count(), indices.size());
220   const int dims_count = shape.dimensions_count();
221   int offset = 0;
222   for (int i = 0; i < dims_count; i++) {
223     const int index = indices[i];
224     DCHECK(index >= 0 && index < shape.dims(i));
225     offset *= shape.dims(i);
226     offset += index;
227   }
228   return offset;
229 }
230 
231 inline std::vector<int> ReverseOffset(const Shape& shape, int index) {
232   DCHECK_GE(index, 0);
233   DCHECK_LT(index, RequiredBufferSizeForShape(shape));
234   const int dims_count = shape.dimensions_count();
235   std::vector<int> indices(dims_count);
236   int residual = index;
237   for (int i = dims_count - 1; i >= 0; i--) {
238     indices[i] = residual % shape.dims(i);
239     residual /= shape.dims(i);
240   }
241   return indices;
242 }
243 
244 int ElementSize(ArrayDataType data_type);
245 
246 void DropMinMax(Model* model, const std::string& array_name);
247 
248 bool IsAllocatableTransientArray(const Model& model,
249                                  const std::string& array_name);
250 
251 void CreateOrCheckRnnStateArray(const std::string& name, int size,
252                                 int state_num_dims, Model* model);
253 
254 std::string AvailableArrayName(const Model& model, const std::string& name);
255 
256 // Formats a shape as a string: [ dims(0), dims(1), ..., dims(num_dims-1) ].
257 std::string ShapeToString(const Shape& shape);
258 
259 void PrintArrayShape(Model* model, const std::string& name);
260 
261 void MakeArrayDims(int num_dims, int batch, int height, int width, int depth,
262                    std::vector<int>* out_dims);
263 
264 // Defines a constant int32 array with the provided values formatted for use
265 // as op parameters.
266 std::string CreateInt32Array(Model* model, const std::string& param_name,
267                              const std::vector<int>& value);
268 
269 bool EstimateArithmeticOpsCount(const Model& model, const Operator& op,
270                                 int64* result);
271 bool EstimateArithmeticOpsCount(const Model& model, int64* result);
272 std::string FormattedNumber(int64 x);
273 
274 int AxesCount(AxesOrder axes_order);
275 
276 // Returns the permutation of the dimensions based on the input axes order and
277 // output axes order.
278 void GetShuffleShape(AxesOrder input_axes_order, AxesOrder output_axes_order,
279                      std::vector<int>* shuffle);
280 
281 // Extend shuffle is designed to match ExtendShape, which pads the shape with
282 // unit dimensions at the beginning.
283 void ExtendShuffle(const std::vector<int>& input_shuffle, int newdim,
284                    std::vector<int>* extended_shuffle);
285 
286 void ShuffleDims(const Shape& input_shape, AxesOrder input_axes_order,
287                  AxesOrder output_axes_order, Shape* output_shape);
288 void ShuffleArray(const Shape& input_shape, AxesOrder input_axes_order,
289                   AxesOrder output_axes_order, const Shape& output_shape,
290                   const float* input_data, float* output_data);
291 void ShuffleArray(const Shape& input_shape, AxesOrder input_axes_order,
292                   AxesOrder output_axes_order, const Shape& output_shape,
293                   const uint8* input_data, uint8* output_data);
294 
295 // Returns true if it may be OK for any graph transformation to ever discard
296 // that array. The idea is that we can't ever discard arrays that are either
297 // an input or an output of the whole graph, or that appear in RNN back-edges,
298 // as that would undercut explicit flags that the user might pass.
299 bool IsDiscardableArray(const Model& model, const std::string& array_name);
300 
301 void CheckFinalDataTypesSatisfied(const Model& model);
302 
303 ArrayDataType ConvertIODataTypeToArrayDataType(IODataType type);
304 
305 // The process of building models varies according to the import format.
306 //
307 // (a) In some cases, such as model-proto format, the model should be fully
308 // specified. In these cases, no extra action should be taken by this function.
309 // (b) In other cases, such as TF graphdef format, the desired types of RNN
310 // arrays are not specified directly in the model, neither can they be inferred.
311 // However, we can set the types of RNN destination arrays to float. This breaks
312 // any cycles such as when resolution of the type of an RNN source array depends
313 // on the type of its destination array.
314 //
315 // This function is applied after the main import, after resolution of flags and
316 // after application of ArraysExtraInfo. It only defaults destination RNN arrays
317 // to float. If the model is subsequently quantized, it is assumed that the
318 // model contains sufficient information for that to be completed. If it is
319 // already quantized, then case (a) should hold.
320 void FinishBuildingRNNStates(Model* model);
321 
322 void UseArraysExtraInfo(Model* model, bool quantize_output);
323 
324 // Calculates the number of elements in tensor given a shape. Shape elements
325 // are assumed to be of type T, while the result total is of type U. If U
326 // doesn't have enough range to represent the sum of elements, an error is
327 // returned.
328 template <typename T, typename U>
329 tensorflow::Status NumElements(const std::vector<T>& shape, U* num_elements) {
330   static_assert(
331       std::numeric_limits<T>::max() <= std::numeric_limits<uint64_t>::max(),
332       "vector type exceed capabilities of NumElements");
333 
334   *num_elements = 1;
335   for (const T& dim : shape) {
336     if (dim < 0) {
337       // TensorFlow's shapes sometimes include -1 to represent an "unknown"
338       // size but TOCO isn't able to create arrays of unknown sizes and will
339       // crash in RequiredBufferSizeForShape().
340       return tensorflow::errors::InvalidArgument(
341           "Tensor shape should not include negative values");
342     }
343     if (*num_elements != 0 &&
344         static_cast<uint64_t>(dim) >
345             std::numeric_limits<U>::max() / *num_elements) {
346       *num_elements = 0;
347       return tensorflow::errors::InvalidArgument("Tensor shape is too large");
348     }
349     *num_elements *= dim;
350   }
351   return tensorflow::Status::OK();
352 }
353 
354 // A model file may have shuffled FC weights.
355 // When that happens, we want to de-shuffle them immediately on import,
356 // so that the rest of toco doesn't need to know about shuffled weights.
357 void UndoWeightsShuffling(Model* model);
358 
359 // Copies minmax, quantization_params, and narrow_range.
360 void CopyMinMaxAndQuantizationRelatedFields(const Array& src, Array* dst);
361 
362 // Delete Array if it's discardable and not referenced as input or output array
363 // by any other op than the specified op.
364 bool DeleteArrayIfUnusedOutsideOfOp(const std::string& array_name,
365                                     const Operator* op, Model* model);
366 
367 }  // namespace toco
368 
369 #endif  // TENSORFLOW_LITE_TOCO_TOOLING_UTIL_H_
370