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 <memory>
160b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include <string>
170b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include <unordered_map>
180b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include <vector>
190b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
200b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include "tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h"
210b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include "tensorflow/contrib/lite/toco/graph_transformations/remove_trivial_passthrough.h"
220b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include "tensorflow/contrib/lite/toco/model.h"
230b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include "tensorflow/contrib/lite/toco/tooling_util.h"
240b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include "tensorflow/core/platform/logging.h"
250b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
260b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Sellenamespace toco {
270b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
280b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Sellenamespace {
290b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
300b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selletemplate <ArrayDataType A>
310b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Sellevoid DequantizeBuffer(Array* array) {
320b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  const auto old_data = array->GetBuffer<A>().data;
330b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  array->buffer = nullptr;
340b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  array->data_type = ArrayDataType::kFloat;
350b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  auto& new_data = array->GetMutableBuffer<ArrayDataType::kFloat>().data;
360b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  new_data.resize(old_data.size());
370b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  const auto& qparams = array->GetQuantizationParams();
380b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  for (int i = 0; i < old_data.size(); i++) {
390b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    new_data[i] = qparams.scale * (old_data[i] - qparams.zero_point);
400b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  }
410b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle}
420b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
430b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Sellestd::vector<std::unique_ptr<Operator>>::iterator FindFirstOpWithInput(
440b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    Model* model, const string& array_name) {
450b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  for (auto it = model->operators.begin(); it != model->operators.end(); ++it) {
460b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    for (const auto& input : it->get()->inputs) {
470b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      if (input == array_name) {
480b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle        return it;
490b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      }
500b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    }
510b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  }
520b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  return model->operators.end();
530b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle}
540b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
550b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Sellevoid ClearArrayQuantizationParams(const string& array_name, Model* model) {
56ba4aec48268d02f111cd7e2c2666f4e7b077e68aA. Unique TensorFlower  auto* array = &model->GetArray(array_name);
570b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  CHECK(array->quantization_params);
580b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  for (auto& input_array : *model->flags.mutable_input_arrays()) {
590b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    if (input_array.name() == array_name) {
600b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      auto& qparams = *array->quantization_params;
610b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      const double new_std_value = 1. / qparams.scale;
620b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      const double new_mean_value = qparams.zero_point;
630b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      if (input_array.has_std_value()) {
640b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle        CHECK_LE(std::abs(new_std_value - input_array.std_value()), 0.001);
650b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      } else {
660b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle        input_array.set_std_value(new_std_value);
670b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      }
680b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      if (input_array.has_mean_value()) {
690b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle        CHECK_LE(std::abs(new_mean_value - input_array.mean_value()), 0.001);
700b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      } else {
710b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle        input_array.set_mean_value(new_mean_value);
720b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      }
730b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    }
740b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  }
750b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  array->quantization_params = nullptr;
760b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle}
770b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
780b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Sellebool DequantizeArray(const string& array_name,
790b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle                     GraphTransformation* transformation, Model* model) {
80ba4aec48268d02f111cd7e2c2666f4e7b077e68aA. Unique TensorFlower  auto* array = &model->GetArray(array_name);
810b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  if (!array->quantization_params) {
820b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    return false;
830b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  }
840b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  transformation->AddMessageF("Dequantizing array: %s", array_name);
850b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
860b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  // Dequantize any buffer
870b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  if (array->buffer) {
880b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    if (array->data_type == ArrayDataType::kUint8) {
890b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      DequantizeBuffer<ArrayDataType::kUint8>(array);
900b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    } else if (array->data_type == ArrayDataType::kInt32) {
910b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      DequantizeBuffer<ArrayDataType::kInt32>(array);
920b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    } else {
930b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      LOG(FATAL) << "Unhandled data type";
940b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    }
950b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    CHECK(array->data_type == ArrayDataType::kFloat);
960b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    CHECK(array->buffer->type == ArrayDataType::kFloat);
970b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
980b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    // Clear quantization params, officially makes this a non-quantized array.
990b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    ClearArrayQuantizationParams(array_name, model);
1000b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    return true;
1010b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  } else {
1020b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    array->data_type = ArrayDataType::kFloat;
1030b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  }
1040b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
1050b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  // Clear quantization params, officially makes this a non-quantized array.
1060b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  ClearArrayQuantizationParams(array_name, model);
1070b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
1080b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  if (array->buffer) {
1090b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    return true;
1100b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  }
1110b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
1120b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  auto* op_outputting_array = GetOpWithOutput(*model, array_name);
1130b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  if (op_outputting_array) {
1140b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    if (op_outputting_array->type == OperatorType::kTensorFlowReshape) {
1150b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      return true;
1160b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    }
1170b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  }
1180b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
1190b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  // If there was no minmax info, we can return now. Indeed,
1200b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  // the below only serves to create a FakeQuant node, but some arrays are
1210b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  // quantized without MinMax (see the CHECK above) and that corresponds to
1220b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  // places where a FakeQuant node is actually not wanted, because the
1230b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  // quantization params are meant to be inferred in another way (e.g. bias
1240b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  // vector for a Conv op, see their special-casing in quantize.cc).
1250b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  if (!array->minmax) {
1260b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    return true;
1270b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  }
1280b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
1290b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  // Determine whether to insert a FakeQuant before or after
1300b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  // this array.
1310b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  bool must_insert_fakequant_before = false;
1320b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  bool must_insert_fakequant_after = false;
1330b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  if (IsInputArray(*model, array_name)) {
1340b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    must_insert_fakequant_after = true;
1350b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  }
1360b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  for (const string& output_array : model->flags.output_arrays()) {
1370b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    if (array_name == output_array) {
1380b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      must_insert_fakequant_before = true;
1390b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    }
1400b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  }
1410b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  for (const auto& rnn_state : model->flags.rnn_states()) {
1420b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    if (array_name == rnn_state.state_array()) {
1430b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      must_insert_fakequant_after = true;
1440b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    }
1450b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    if (array_name == rnn_state.back_edge_source_array()) {
1460b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      must_insert_fakequant_before = true;
1470b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    }
1480b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  }
1490b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  CHECK(!(must_insert_fakequant_before && must_insert_fakequant_after));
1500b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
1510b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  // Create and insert the FakeQuant node
1520b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  auto* fakequant_op = new FakeQuantOperator;
1530b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  model->operators.emplace(FindFirstOpWithInput(model, array_name),
1540b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle                           fakequant_op);
1550b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  const string& new_array_name = AvailableArrayName(*model, array_name);
1560b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  auto& new_array = model->GetOrCreateArray(new_array_name);
1570b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  new_array.data_type = ArrayDataType::kFloat;
1580b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  new_array.copy_shape(array->shape());
1590b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  new_array.GetOrCreateMinMax() = array->GetMinMax();
1600b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  fakequant_op->minmax.reset(new MinMax);
1610b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  *fakequant_op->minmax = array->GetMinMax();
1620b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  if (must_insert_fakequant_before) {
1630b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    for (const auto& op : model->operators) {
1640b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      for (string& output : op->outputs) {
1650b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle        if (output == array_name) {
1660b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle          output = new_array_name;
1670b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle        }
1680b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      }
1690b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    }
1700b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    fakequant_op->inputs = {new_array_name};
1710b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    fakequant_op->outputs = {array_name};
1720b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  } else {
1730b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    for (const auto& op : model->operators) {
1740b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      for (string& input : op->inputs) {
1750b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle        if (input == array_name) {
1760b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle          input = new_array_name;
1770b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle        }
1780b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      }
1790b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    }
1800b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    fakequant_op->inputs = {array_name};
1810b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    fakequant_op->outputs = {new_array_name};
1820b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  }
1830b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  return true;
1840b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle}
1850b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
1860b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle}  // namespace
1870b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
1880b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Sellebool Dequantize::Run(Model* model, std::size_t op_index) {
1890b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  const auto op_it = model->operators.begin() + op_index;
1900b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  auto* op = op_it->get();
1910b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
1920b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  if (op->type == OperatorType::kDequantize) {
1930b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    auto& input_array = model->GetArray(op->inputs[0]);
1940b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    if (input_array.data_type == ArrayDataType::kFloat) {
1950b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      return false;
1960b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    }
1970b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    if (input_array.final_data_type != ArrayDataType::kFloat) {
1980b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      return false;
1990b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    }
2000b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    input_array.data_type = ArrayDataType::kFloat;
2010b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    input_array.quantization_params = nullptr;
2020b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    auto& output_array = model->GetArray(op->outputs[0]);
2030b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    output_array.data_type = ArrayDataType::kFloat;
2040b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    output_array.quantization_params = nullptr;
2050b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    return RemoveTrivialPassthroughOp(this, model, op_index);
2060b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  }
2070b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
2080b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  std::vector<string> arrays;
2090b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  for (const string& input : op->inputs) {
2100b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    arrays.push_back(input);
2110b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  }
2120b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  for (const string& output : op->outputs) {
2130b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    arrays.push_back(output);
2140b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  }
2150b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  bool changed = false;
2160b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  for (const string& array : arrays) {
2170c4c353690b0cec2e26e10abd59f66f7e61cc974Zhixian Yan    if (!model->IsOptionalArray(array)) {
2180c4c353690b0cec2e26e10abd59f66f7e61cc974Zhixian Yan      changed |= DequantizeArray(array, this, model);
2190c4c353690b0cec2e26e10abd59f66f7e61cc974Zhixian Yan    }
2200b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  }
2210b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
2220b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  return changed;
2230b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle}
2240b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
2250b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle}  // namespace toco
226