10b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle/* Copyright 2017 The TensorFlow Authors. All Rights Reserved. 20b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle 30b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew SelleLicensed under the Apache License, Version 2.0 (the "License"); 40b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selleyou may not use this file except in compliance with the License. 50b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew SelleYou may obtain a copy of the License at 60b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle 70b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle http://www.apache.org/licenses/LICENSE-2.0 80b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle 90b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew SelleUnless required by applicable law or agreed to in writing, software 100b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selledistributed under the License is distributed on an "AS IS" BASIS, 110b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew SelleWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 120b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew SelleSee the License for the specific language governing permissions and 130b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Sellelimitations under the License. 140b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle==============================================================================*/ 150b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include <string.h> 160b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include <algorithm> 170b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include <cmath> 180b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include <memory> 190b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include <string> 200b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include <unordered_map> 210b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include <vector> 220b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle 230b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include "tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h" 240b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include "tensorflow/contrib/lite/toco/model.h" 250b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include "tensorflow/contrib/lite/toco/runtime/types.h" 260b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include "tensorflow/contrib/lite/toco/tooling_util.h" 270b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include "tensorflow/core/platform/logging.h" 280b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle 290b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Sellenamespace toco { 300b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle 310b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Sellebool ResolveConstantUnaryOperator::Run(Model* model, std::size_t op_index) { 320b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle const auto unary_it = model->operators.begin() + op_index; 330b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle const auto* unary_op = unary_it->get(); 340b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle // Test for unary ops of types that we know how to resolve 3538b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower if (unary_op->type != OperatorType::kCast && 36c02147704b1244010a93557de33ad4c64a20a5a5A. Unique TensorFlower unary_op->type != OperatorType::kNeg && 3738b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower unary_op->type != OperatorType::kTensorFlowRsqrt && 380b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle unary_op->type != OperatorType::kTensorFlowSqrt && 390b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle unary_op->type != OperatorType::kTensorFlowSquare && 400b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle unary_op->type != OperatorType::kTensorFlowSum && 410b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle unary_op->type != OperatorType::kTensorFlowMin && 420b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle unary_op->type != OperatorType::kTensorFlowMax && 430b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle unary_op->type != OperatorType::kTensorFlowReshape) { 440b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle return false; 450b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle } 460b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle // Check if the input is a constant parameter. 470b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle if (!IsConstantParameterArray(*model, unary_op->inputs[0])) { 480b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle return false; 490b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle } 500b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle 510b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle // if the unary op involves a tensor required by a rnn state, ignore it 520b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle for (const auto& rnn_state : model->flags.rnn_states()) { 530b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle if (unary_op->inputs[0] == rnn_state.back_edge_source_array()) { 540b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle return false; 550b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle } 560b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle if (unary_op->inputs[0] == rnn_state.state_array()) { 570b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle return false; 580b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle } 590b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle } 600b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle 6138b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower auto& output_array = model->GetArray(unary_op->outputs[0]); 6238b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower if (!output_array.has_shape()) { 6338b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower // Yield until the output array dims have been resolved. 6438b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower return false; 6538b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower } 6638b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower 670b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle // At the moment we don't want to care about fused activation functions. 680b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle // The idea is that we should do the present constants-propagation before 690b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle // activation functions get fused. 700b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle if (unary_op->fused_activation_function != 710b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle FusedActivationFunctionType::kNone) { 720b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle AddMessageF( 730b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle "Not resolving constant %s " 740b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle " because it has a fused activation function", 750b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle LogName(*unary_op)); 760b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle return false; 770b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle } 7838b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower 790b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle const auto& input_array = model->GetArray(unary_op->inputs[0]); 800b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle // We have already tested above for existence of buffers (synonymous to being 810b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle // a constant param). 820b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle CHECK(input_array.buffer); 8338b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower std::vector<DataType<ArrayDataType::kFloat>> const* input_float_data; 8438b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower if (unary_op->type == OperatorType::kCast) { 8538b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower CastOperator const* cast_op = static_cast<CastOperator const*>(unary_op); 8638b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower if (cast_op->dst_data_type != ArrayDataType::kFloat) { 8738b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower AddMessageF( 8838b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower "Not resolving constant %s because we currently only support casting " 8938b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower "to float", 9038b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower LogName(*unary_op)); 9138b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower return false; 9238b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower } 9338b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower if (cast_op->src_data_type != input_array.buffer->type) { 9438b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower AddMessageF( 9538b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower "Not resolving constant %s because cast op source type does not " 9638b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower "match input type", 9738b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower LogName(*unary_op)); 9838b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower } 9938b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower } else { 10038b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower if (input_array.buffer->type != ArrayDataType::kFloat) { 10138b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower return false; 10238b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower } 10338b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower input_float_data = &(input_array.GetBuffer<ArrayDataType::kFloat>().data); 1040b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle } 1050b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle 10638b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower // Create a float buffer on the output array, which are always constant. 1070b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle const Shape& output_shape = output_array.shape(); 10838b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower const int output_dims_count = output_shape.dimensions_count(); 10938b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower const int output_buffer_size = RequiredBufferSizeForShape(output_shape); 1100b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle auto& output_float_data = 1110b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle output_array.GetMutableBuffer<ArrayDataType::kFloat>().data; 1120b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle output_float_data.resize(output_buffer_size); 1130b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle 11438b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower const Shape& input_shape = input_array.shape(); 11538b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower const int input_buffer_size = RequiredBufferSizeForShape(input_shape); 11638b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower if (unary_op->type == OperatorType::kCast) { 11738b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower for (int i = 0; i < output_buffer_size; i++) { 11838b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower float outval = 0.0f; 11938b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower if (input_array.buffer->type == ArrayDataType::kFloat) { 12038b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower outval = static_cast<float>( 12138b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower input_array.GetBuffer<ArrayDataType::kFloat>().data[i]); 12238b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower } else if (input_array.buffer->type == ArrayDataType::kUint8) { 12338b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower outval = static_cast<float>( 12438b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower input_array.GetBuffer<ArrayDataType::kUint8>().data[i]); 12538b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower } else if (input_array.buffer->type == ArrayDataType::kInt32) { 12638b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower outval = static_cast<float>( 12738b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower input_array.GetBuffer<ArrayDataType::kInt32>().data[i]); 12838b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower } else if (input_array.buffer->type == ArrayDataType::kInt64) { 12938b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower outval = static_cast<float>( 13038b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower input_array.GetBuffer<ArrayDataType::kInt64>().data[i]); 13138b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower } else { 13238b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower LOG(FATAL) << "Unsupported cast op input type"; 13338b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower } 13438b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower output_float_data[i] = outval; 13538b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower } 13638b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower } else if (unary_op->type == OperatorType::kTensorFlowReshape) { 1370b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle CHECK(input_buffer_size == output_buffer_size); 13838b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower memcpy(output_float_data.data(), (*input_float_data).data(), 13938b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower output_buffer_size * sizeof(output_float_data[0])); 1400b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle } else if (unary_op->type == OperatorType::kTensorFlowSum) { 1410b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle // At the moment only full reduction across all dimensions is supported. 1420b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle float sum = 0.f; 14338b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower for (int i = 0; i < input_buffer_size; i++) { 14438b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower sum += (*input_float_data)[i]; 1450b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle } 146c7e1de032658f7256e53b8a5a6066b140ecb9615A. Unique TensorFlower for (int i = 0; i < output_buffer_size; ++i) { 147c7e1de032658f7256e53b8a5a6066b140ecb9615A. Unique TensorFlower output_float_data[i] = sum; 148c7e1de032658f7256e53b8a5a6066b140ecb9615A. Unique TensorFlower } 1490b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle } else if (unary_op->type == OperatorType::kTensorFlowMin) { 1500b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle // At the moment only full reduction across all dimensions is supported. 1510b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle // TODO(starka): Output should not be padded. 1520b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle for (int i = 0; i < output_dims_count; i++) { 1530b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle CHECK_EQ(output_shape.dims(i), 1); 1540b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle } 15538b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower float min = (*input_float_data)[0]; 15638b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower for (int i = 0; i < input_buffer_size; i++) { 15738b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower min = std::min(min, (*input_float_data)[i]); 1580b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle } 1590b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle output_float_data[0] = min; 1600b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle } else if (unary_op->type == OperatorType::kTensorFlowMax) { 1610b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle // At the moment only full reduction across all dimensions is supported. 1620b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle // TODO(starka): Output should not be padded. 1630b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle for (int i = 0; i < output_dims_count; i++) { 1640b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle CHECK_EQ(output_shape.dims(i), 1); 1650b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle } 16638b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower float max = (*input_float_data)[0]; 16738b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower for (int i = 0; i < input_buffer_size; i++) { 16838b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower max = std::max(max, (*input_float_data)[i]); 1690b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle } 1700b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle output_float_data[0] = max; 171c02147704b1244010a93557de33ad4c64a20a5a5A. Unique TensorFlower } else if (unary_op->type == OperatorType::kNeg || 172c02147704b1244010a93557de33ad4c64a20a5a5A. Unique TensorFlower unary_op->type == OperatorType::kTensorFlowRsqrt || 1730b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle unary_op->type == OperatorType::kTensorFlowSqrt || 1740b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle unary_op->type == OperatorType::kTensorFlowSquare) { 1750b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle // Element-wise ops. Should have perfectly matching sizes here. 1760b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle for (int i = 0; i < output_dims_count; i++) { 1770b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle CHECK_EQ(output_shape.dims(i), input_shape.dims(i)); 1780b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle } 1790b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle 18038b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower for (int i = 0; i < output_buffer_size; i++) { 18138b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower const float val = (*input_float_data)[i]; 1820b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle float outval = 0.f; 183c02147704b1244010a93557de33ad4c64a20a5a5A. Unique TensorFlower if (unary_op->type == OperatorType::kNeg) { 184c02147704b1244010a93557de33ad4c64a20a5a5A. Unique TensorFlower outval = -val; 185c02147704b1244010a93557de33ad4c64a20a5a5A. Unique TensorFlower } else if (unary_op->type == OperatorType::kTensorFlowRsqrt) { 1860b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle outval = 1.0f / std::sqrt(val); 1870b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle } else if (unary_op->type == OperatorType::kTensorFlowSqrt) { 1880b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle outval = std::sqrt(val); 1890b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle } else if (unary_op->type == OperatorType::kTensorFlowSquare) { 1900b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle outval = val * val; 1910b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle } else { 1920b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle LOG(FATAL) << "should not get here."; 1930b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle } 1940b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle output_float_data[i] = outval; 1950b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle } 1960b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle } else { 1970b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle LOG(FATAL) << "should not get here."; 1980b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle } 1990b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle for (const auto& input : unary_op->inputs) { 2000b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle if (CountOpsWithInput(*model, input) == 1) { 201ba4aec48268d02f111cd7e2c2666f4e7b077e68aA. Unique TensorFlower model->EraseArray(input); 2020b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle } 2030b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle } 2040b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle AddMessageF("Resolved constant %s to the equivalent constant array", 2050b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle LogName(*unary_op)); 2060b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle model->operators.erase(unary_it); 2070b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle return true; 2080b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle} 2090b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle 2100b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle} // namespace toco 211