Imported Upstream version 1.19.0
[platform/core/ml/nnfw.git] / compiler / circle-quantizer / src / CircleQuantizer.cpp
1 /*
2  * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *    http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <foder/FileLoader.h>
18
19 #include <luci/Importer.h>
20 #include <luci/CircleOptimizer.h>
21 #include <luci/Service/Validate.h>
22 #include <luci/CircleExporter.h>
23 #include <luci/CircleFileExpContract.h>
24 #include <luci/UserSettings.h>
25
26 #include <oops/InternalExn.h>
27 #include <arser/arser.h>
28 #include <vconone/vconone.h>
29
30 #include <functional>
31 #include <iostream>
32 #include <map>
33 #include <string>
34
35 using OptionHook = std::function<int(const char **)>;
36
37 using Algorithms = luci::CircleOptimizer::Options::Algorithm;
38 using AlgorithmParameters = luci::CircleOptimizer::Options::AlgorithmParameters;
39
40 void print_exclusive_options(void)
41 {
42   std::cout << "Use only one of the 3 options below." << std::endl;
43   std::cout << "    --quantize_dequantize_weights" << std::endl;
44   std::cout << "    --quantize_with_minmax" << std::endl;
45   std::cout << "    --requantize" << std::endl;
46   std::cout << "    --force_quantparam" << std::endl;
47 }
48
49 void print_version(void)
50 {
51   std::cout << "circle-quantizer version " << vconone::get_string() << std::endl;
52   std::cout << vconone::get_copyright() << std::endl;
53 }
54
55 int entry(int argc, char **argv)
56 {
57   // Simple argument parser (based on map)
58   std::map<std::string, OptionHook> argparse;
59   luci::CircleOptimizer optimizer;
60
61   auto options = optimizer.options();
62   auto settings = luci::UserSettings::settings();
63
64   const std::string qdqw = "--quantize_dequantize_weights";
65   const std::string qwmm = "--quantize_with_minmax";
66   const std::string rq = "--requantize";
67   const std::string fq = "--force_quantparam";
68
69   const std::string gpd = "--generate_profile_data";
70
71   arser::Arser arser("circle-quantizer provides circle model quantization");
72
73   arser.add_argument("--version")
74     .nargs(0)
75     .required(false)
76     .default_value(false)
77     .help("Show version information and exit")
78     .exit_with(print_version);
79
80   arser.add_argument("-V", "--verbose")
81     .nargs(0)
82     .required(false)
83     .default_value(false)
84     .help("output additional information to stdout or stderr");
85
86   arser.add_argument(qdqw)
87     .nargs(3)
88     .type(arser::DataType::STR_VEC)
89     .required(false)
90     .help("Quantize-dequantize weight values required action before quantization. "
91           "Three arguments required: input_model_dtype(float32) "
92           "output_model_dtype(uint8) granularity(layer, channel)");
93
94   arser.add_argument(qwmm)
95     .nargs(3)
96     .type(arser::DataType::STR_VEC)
97     .required(false)
98     .help("Quantize with min/max values. "
99           "Three arguments required: input_model_dtype(float32) "
100           "output_model_dtype(uint8) granularity(layer, channel)");
101
102   arser.add_argument(rq)
103     .nargs(2)
104     .type(arser::DataType::STR_VEC)
105     .required(false)
106     .help("Requantize a quantized model. "
107           "Two arguments required: input_model_dtype(int8) "
108           "output_model_dtype(uint8)");
109
110   arser.add_argument(fq)
111     .nargs(3)
112     .type(arser::DataType::STR_VEC)
113     .required(false)
114     .accumulated(true)
115     .help("Write quantization parameters to the specified tensor. "
116           "Three arguments required: tensor_name(string), "
117           "scale(float) zero_point(int)");
118
119   arser.add_argument("--input_type")
120     .nargs(1)
121     .type(arser::DataType::STR)
122     .required(false)
123     .help("Input type of quantized model (uint8 or int16)");
124
125   arser.add_argument("--output_type")
126     .nargs(1)
127     .type(arser::DataType::STR)
128     .required(false)
129     .help("Output type of quantized model (uint8 or int16)");
130
131   arser.add_argument("input").nargs(1).type(arser::DataType::STR).help("Input circle model");
132   arser.add_argument("output").nargs(1).type(arser::DataType::STR).help("Output circle model");
133
134   arser.add_argument(gpd).nargs(0).required(false).default_value(false).help(
135     "This will turn on profiling data generation.");
136
137   try
138   {
139     arser.parse(argc, argv);
140   }
141   catch (const std::runtime_error &err)
142   {
143     std::cerr << err.what() << std::endl;
144     std::cout << arser;
145     return 255;
146   }
147
148   {
149     // only one of qdqw, qwmm, rq, fq option can be used
150     int32_t opt_used = arser[qdqw] ? 1 : 0;
151     opt_used += arser[qwmm] ? 1 : 0;
152     opt_used += arser[rq] ? 1 : 0;
153     opt_used += arser[fq] ? 1 : 0;
154     if (opt_used != 1)
155     {
156       print_exclusive_options();
157       return 255;
158     }
159   }
160
161   if (arser.get<bool>("--verbose"))
162   {
163     // The third parameter of setenv means REPLACE.
164     // If REPLACE is zero, it does not overwrite an existing value.
165     setenv("LUCI_LOG", "100", 0);
166   }
167
168   if (arser[qdqw])
169   {
170     auto values = arser.get<std::vector<std::string>>(qdqw);
171     if (values.size() != 3)
172     {
173       std::cerr << arser;
174       return 255;
175     }
176     options->enable(Algorithms::QuantizeDequantizeWeights);
177
178     options->param(AlgorithmParameters::Quantize_input_model_dtype, values.at(0));
179     options->param(AlgorithmParameters::Quantize_output_model_dtype, values.at(1));
180     options->param(AlgorithmParameters::Quantize_granularity, values.at(2));
181   }
182
183   if (arser[qwmm])
184   {
185     auto values = arser.get<std::vector<std::string>>(qwmm);
186     if (values.size() != 3)
187     {
188       std::cerr << arser;
189       return 255;
190     }
191     options->enable(Algorithms::QuantizeWithMinMax);
192
193     options->param(AlgorithmParameters::Quantize_input_model_dtype, values.at(0));
194     options->param(AlgorithmParameters::Quantize_output_model_dtype, values.at(1));
195     options->param(AlgorithmParameters::Quantize_granularity, values.at(2));
196
197     if (arser["--input_type"])
198       options->param(AlgorithmParameters::Quantize_input_type,
199                      arser.get<std::string>("--input_type"));
200
201     if (arser["--output_type"])
202       options->param(AlgorithmParameters::Quantize_output_type,
203                      arser.get<std::string>("--output_type"));
204   }
205
206   if (arser[rq])
207   {
208     auto values = arser.get<std::vector<std::string>>(rq);
209     if (values.size() != 2)
210     {
211       std::cerr << arser;
212       return 255;
213     }
214     options->enable(Algorithms::Requantize);
215
216     options->param(AlgorithmParameters::Quantize_input_model_dtype, values.at(0));
217     options->param(AlgorithmParameters::Quantize_output_model_dtype, values.at(1));
218   }
219
220   if (arser[fq])
221   {
222     auto values = arser.get<std::vector<std::vector<std::string>>>(fq);
223
224     std::vector<std::string> tensors;
225     std::vector<std::string> scales;
226     std::vector<std::string> zero_points;
227
228     for (auto const value : values)
229     {
230       if (value.size() != 3)
231       {
232         std::cerr << arser;
233         return 255;
234       }
235
236       tensors.push_back(value[0]);
237       scales.push_back(value[1]);
238       zero_points.push_back(value[2]);
239     }
240
241     options->enable(Algorithms::ForceQuantParam);
242
243     options->params(AlgorithmParameters::Quantize_tensor_names, tensors);
244     options->params(AlgorithmParameters::Quantize_scales, scales);
245     options->params(AlgorithmParameters::Quantize_zero_points, zero_points);
246   }
247
248   std::string input_path = arser.get<std::string>("input");
249   std::string output_path = arser.get<std::string>("output");
250
251   if (arser[gpd])
252     settings->set(luci::UserSettings::Key::ProfilingDataGen, true);
253
254   // Load model from the file
255   foder::FileLoader file_loader{input_path};
256   std::vector<char> model_data = file_loader.load();
257
258   // Verify flatbuffers
259   flatbuffers::Verifier verifier{reinterpret_cast<uint8_t *>(model_data.data()), model_data.size()};
260   if (!circle::VerifyModelBuffer(verifier))
261   {
262     std::cerr << "ERROR: Invalid input file '" << input_path << "'" << std::endl;
263     return EXIT_FAILURE;
264   }
265
266   const circle::Model *circle_model = circle::GetModel(model_data.data());
267   if (circle_model == nullptr)
268   {
269     std::cerr << "ERROR: Failed to load circle '" << input_path << "'" << std::endl;
270     return EXIT_FAILURE;
271   }
272
273   // Import from input Circle file
274   luci::Importer importer;
275   auto module = importer.importModule(circle_model);
276
277   for (size_t idx = 0; idx < module->size(); ++idx)
278   {
279     auto graph = module->graph(idx);
280
281     // quantize the graph
282     optimizer.quantize(graph);
283
284     if (!luci::validate(graph))
285     {
286       std::cerr << "ERROR: Quantized graph is invalid" << std::endl;
287       return 255;
288     }
289   }
290
291   // Export to output Circle file
292   luci::CircleExporter exporter;
293
294   luci::CircleFileExpContract contract(module.get(), output_path);
295
296   if (!exporter.invoke(&contract))
297   {
298     std::cerr << "ERROR: Failed to export '" << output_path << "'" << std::endl;
299     return 255;
300   }
301
302   return 0;
303 }