33a845c961c48a2837c63f236ff01f3c59f91e3f
[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 <luci/ImporterEx.h>
18 #include <luci/CircleQuantizer.h>
19 #include <luci/Service/Validate.h>
20 #include <luci/CircleExporter.h>
21 #include <luci/CircleFileExpContract.h>
22 #include <luci/UserSettings.h>
23
24 #include <oops/InternalExn.h>
25 #include <arser/arser.h>
26 #include <vconone/vconone.h>
27 #include <json.h>
28
29 #include <functional>
30 #include <iostream>
31 #include <map>
32 #include <string>
33
34 using OptionHook = std::function<int(const char **)>;
35
36 using LayerParam = luci::CircleQuantizer::Options::LayerParam;
37 using Algorithms = luci::CircleQuantizer::Options::Algorithm;
38 using AlgorithmParameters = luci::CircleQuantizer::Options::AlgorithmParameters;
39
40 std::vector<std::shared_ptr<LayerParam>> read_layer_params(std::string &filename)
41 {
42   Json::Value root;
43   std::ifstream ifs(filename);
44
45   // Failed to open cfg file
46   if (not ifs.is_open())
47     throw std::runtime_error("Cannot open config file. " + filename);
48
49   Json::CharReaderBuilder builder;
50   JSONCPP_STRING errs;
51
52   // Failed to parse
53   if (not parseFromStream(builder, ifs, &root, &errs))
54     throw std::runtime_error("Cannot parse config file (json format). " + errs);
55
56   auto layers = root["layers"];
57   std::vector<std::shared_ptr<LayerParam>> p;
58   for (auto layer : layers)
59   {
60     if (layer.isMember("name"))
61     {
62       auto l = std::make_shared<LayerParam>();
63       {
64         l->name = layer["name"].asString();
65         l->dtype = layer["dtype"].asString();
66         l->granularity = layer["granularity"].asString();
67       }
68       p.emplace_back(l);
69     }
70
71     // Multiple names with the same dtype & granularity
72     if (layer.isMember("names"))
73     {
74       for (auto name : layer["names"])
75       {
76         auto l = std::make_shared<LayerParam>();
77         {
78           l->name = name.asString();
79           l->dtype = layer["dtype"].asString();
80           l->granularity = layer["granularity"].asString();
81         }
82         p.emplace_back(l);
83       }
84     }
85   }
86
87   return p;
88 }
89
90 void print_exclusive_options(void)
91 {
92   std::cout << "Use only one of the 3 options below." << std::endl;
93   std::cout << "    --quantize_dequantize_weights" << std::endl;
94   std::cout << "    --quantize_with_minmax" << std::endl;
95   std::cout << "    --requantize" << std::endl;
96   std::cout << "    --force_quantparam" << std::endl;
97 }
98
99 void print_version(void)
100 {
101   std::cout << "circle-quantizer version " << vconone::get_string() << std::endl;
102   std::cout << vconone::get_copyright() << std::endl;
103 }
104
105 int entry(int argc, char **argv)
106 {
107   luci::CircleQuantizer quantizer;
108
109   auto options = quantizer.options();
110   auto settings = luci::UserSettings::settings();
111
112   const std::string qdqw = "--quantize_dequantize_weights";
113   const std::string qwmm = "--quantize_with_minmax";
114   const std::string rq = "--requantize";
115   const std::string fq = "--force_quantparam";
116   const std::string cq = "--copy_quantparam";
117   const std::string fake_quant = "--fake_quantize";
118   const std::string cfg = "--config";
119
120   const std::string tf_maxpool = "--TF-style_maxpool";
121
122   const std::string gpd = "--generate_profile_data";
123
124   arser::Arser arser("circle-quantizer provides circle model quantization");
125
126   arser::Helper::add_version(arser, print_version);
127   arser::Helper::add_verbose(arser);
128
129   arser.add_argument(qdqw)
130     .nargs(3)
131     .type(arser::DataType::STR_VEC)
132     .help("Quantize-dequantize weight values required action before quantization. "
133           "Three arguments required: input_model_dtype(float32) "
134           "output_model_dtype(uint8) granularity(layer, channel)");
135
136   arser.add_argument(qwmm)
137     .nargs(3)
138     .type(arser::DataType::STR_VEC)
139     .help("Quantize with min/max values. "
140           "Three arguments required: input_model_dtype(float32) "
141           "output_model_dtype(uint8) granularity(layer, channel)");
142
143   arser.add_argument(tf_maxpool)
144     .nargs(0)
145     .default_value(false)
146     .help("Force MaxPool Op to have the same input/output quantparams. NOTE: This feature can "
147           "degrade accuracy of some models");
148
149   arser.add_argument(fake_quant)
150     .nargs(0)
151     .help("Convert a quantized model to a fake-quantized model. NOTE: This feature will "
152           "generate an fp32 model.");
153
154   arser.add_argument(rq)
155     .nargs(2)
156     .type(arser::DataType::STR_VEC)
157     .help("Requantize a quantized model. "
158           "Two arguments required: input_model_dtype(int8) "
159           "output_model_dtype(uint8)");
160
161   arser.add_argument(fq)
162     .nargs(3)
163     .type(arser::DataType::STR_VEC)
164     .accumulated(true)
165     .help("Write quantization parameters to the specified tensor. "
166           "Three arguments required: tensor_name(string), "
167           "scale(float) zero_point(int)");
168
169   arser.add_argument(cq)
170     .nargs(2)
171     .type(arser::DataType::STR_VEC)
172     .accumulated(true)
173     .help("Copy quantization parameter from a tensor to another tensor."
174           "Two arguments required: source_tensor_name(string), "
175           "destination_tensor_name(string)");
176
177   arser.add_argument("--input_type")
178     .help("Input type of quantized model (uint8, int16, int32, int64, float32, or bool). For "
179           "multiple inputs, "
180           "use comma-separated values. e.g., uint8,int16");
181
182   arser.add_argument("--output_type")
183     .help("Output type of quantized model (uint8, int16, int32, int64, float32, or bool). For "
184           "multiple outputs, "
185           "use comma-separated values. e.g., uint8,int16");
186
187   arser.add_argument(cfg).help("Path to the quantization configuration file");
188
189   arser.add_argument("input").help("Input circle model");
190   arser.add_argument("output").help("Output circle model");
191
192   arser.add_argument(gpd).nargs(0).required(false).default_value(false).help(
193     "This will turn on profiling data generation.");
194
195   try
196   {
197     arser.parse(argc, argv);
198   }
199   catch (const std::runtime_error &err)
200   {
201     std::cerr << err.what() << std::endl;
202     std::cout << arser;
203     return 255;
204   }
205
206   {
207     // only one of qdqw, qwmm, rq, fq, cq, fake_quant option can be used
208     int32_t opt_used = arser[qdqw] ? 1 : 0;
209     opt_used += arser[qwmm] ? 1 : 0;
210     opt_used += arser[rq] ? 1 : 0;
211     opt_used += arser[fq] ? 1 : 0;
212     opt_used += arser[cq] ? 1 : 0;
213     opt_used += arser[fake_quant] ? 1 : 0;
214     if (opt_used != 1)
215     {
216       print_exclusive_options();
217       return 255;
218     }
219   }
220
221   if (arser.get<bool>("--verbose"))
222   {
223     // The third parameter of setenv means REPLACE.
224     // If REPLACE is zero, it does not overwrite an existing value.
225     setenv("LUCI_LOG", "100", 0);
226   }
227
228   if (arser[qdqw])
229   {
230     auto values = arser.get<std::vector<std::string>>(qdqw);
231     if (values.size() != 3)
232     {
233       std::cerr << arser;
234       return 255;
235     }
236     options->enable(Algorithms::QuantizeDequantizeWeights);
237
238     options->param(AlgorithmParameters::Quantize_input_model_dtype, values.at(0));
239     options->param(AlgorithmParameters::Quantize_output_model_dtype, values.at(1));
240     options->param(AlgorithmParameters::Quantize_granularity, values.at(2));
241
242     if (arser[cfg])
243     {
244       auto filename = arser.get<std::string>(cfg);
245       try
246       {
247         auto layer_params = read_layer_params(filename);
248
249         options->layer_params(AlgorithmParameters::Quantize_layer_params, layer_params);
250       }
251       catch (const std::runtime_error &e)
252       {
253         std::cerr << e.what() << '\n';
254         return 255;
255       }
256     }
257   }
258
259   if (arser[qwmm])
260   {
261     auto values = arser.get<std::vector<std::string>>(qwmm);
262     if (values.size() != 3)
263     {
264       std::cerr << arser;
265       return 255;
266     }
267     options->enable(Algorithms::QuantizeWithMinMax);
268
269     options->param(AlgorithmParameters::Quantize_input_model_dtype, values.at(0));
270     options->param(AlgorithmParameters::Quantize_output_model_dtype, values.at(1));
271     options->param(AlgorithmParameters::Quantize_granularity, values.at(2));
272
273     if (arser["--input_type"])
274       options->param(AlgorithmParameters::Quantize_input_type,
275                      arser.get<std::string>("--input_type"));
276
277     if (arser["--output_type"])
278       options->param(AlgorithmParameters::Quantize_output_type,
279                      arser.get<std::string>("--output_type"));
280
281     if (arser[tf_maxpool] and arser.get<bool>(tf_maxpool))
282       options->param(AlgorithmParameters::Quantize_TF_style_maxpool, "True");
283
284     if (arser[cfg])
285     {
286       auto filename = arser.get<std::string>(cfg);
287       try
288       {
289         auto layer_params = read_layer_params(filename);
290
291         options->layer_params(AlgorithmParameters::Quantize_layer_params, layer_params);
292       }
293       catch (const std::runtime_error &e)
294       {
295         std::cerr << e.what() << '\n';
296         return 255;
297       }
298     }
299   }
300
301   if (arser[rq])
302   {
303     auto values = arser.get<std::vector<std::string>>(rq);
304     if (values.size() != 2)
305     {
306       std::cerr << arser;
307       return 255;
308     }
309     options->enable(Algorithms::Requantize);
310
311     options->param(AlgorithmParameters::Quantize_input_model_dtype, values.at(0));
312     options->param(AlgorithmParameters::Quantize_output_model_dtype, values.at(1));
313   }
314
315   if (arser[fq])
316   {
317     auto values = arser.get<std::vector<std::vector<std::string>>>(fq);
318
319     std::vector<std::string> tensors;
320     std::vector<std::string> scales;
321     std::vector<std::string> zero_points;
322
323     for (auto const value : values)
324     {
325       if (value.size() != 3)
326       {
327         std::cerr << arser;
328         return 255;
329       }
330
331       tensors.push_back(value[0]);
332       scales.push_back(value[1]);
333       zero_points.push_back(value[2]);
334     }
335
336     options->enable(Algorithms::ForceQuantParam);
337
338     options->params(AlgorithmParameters::Quantize_tensor_names, tensors);
339     options->params(AlgorithmParameters::Quantize_scales, scales);
340     options->params(AlgorithmParameters::Quantize_zero_points, zero_points);
341   }
342
343   if (arser[cq])
344   {
345     auto values = arser.get<std::vector<std::vector<std::string>>>(cq);
346
347     std::vector<std::string> src;
348     std::vector<std::string> dst;
349
350     for (auto const value : values)
351     {
352       if (value.size() != 2)
353       {
354         std::cerr << arser;
355         return 255;
356       }
357
358       src.push_back(value[0]);
359       dst.push_back(value[1]);
360     }
361
362     options->enable(Algorithms::CopyQuantParam);
363
364     options->params(AlgorithmParameters::Quantize_src_tensor_names, src);
365     options->params(AlgorithmParameters::Quantize_dst_tensor_names, dst);
366   }
367
368   if (arser[fake_quant])
369     options->enable(Algorithms::ConvertToFakeQuantizedModel);
370
371   std::string input_path = arser.get<std::string>("input");
372   std::string output_path = arser.get<std::string>("output");
373
374   if (arser[gpd])
375     settings->set(luci::UserSettings::Key::ProfilingDataGen, true);
376
377   // Load model from the file
378   luci::ImporterEx importerex;
379   auto module = importerex.importVerifyModule(input_path);
380   if (module.get() == nullptr)
381     return EXIT_FAILURE;
382
383   for (size_t idx = 0; idx < module->size(); ++idx)
384   {
385     auto graph = module->graph(idx);
386
387     // quantize the graph
388     quantizer.quantize(graph);
389
390     if (!luci::validate(graph))
391     {
392       std::cerr << "ERROR: Quantized graph is invalid" << std::endl;
393       return 255;
394     }
395   }
396
397   // Export to output Circle file
398   luci::CircleExporter exporter;
399
400   luci::CircleFileExpContract contract(module.get(), output_path);
401
402   if (!exporter.invoke(&contract))
403   {
404     std::cerr << "ERROR: Failed to export '" << output_path << "'" << std::endl;
405     return 255;
406   }
407
408   return 0;
409 }