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