2 * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved
3 * Copyright 2019 The TensorFlow Authors. All Rights Reserved.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 #include "luci/Pass/QuantizeWithMinMaxPass.h"
18 #include "QuantizationUtils.h"
20 #include <luci/IR/CircleNodes.h>
21 #include <luci/IR/CircleNodeVisitor.h>
24 #include <oops/UserExn.h>
35 // Check if the node is the bias of Conv2D, DepthwiseConv2D, or FullyConnected layer
36 // If true, return <input, weight> pair of the successor node (used to quantize bias)
37 // If flase, return <nullptr, nullptr>
38 std::pair<loco::Node *, loco::Node *> get_input_weight_of_bias(CircleNode *node)
40 auto circle_const = dynamic_cast<CircleConst *>(node);
41 if (circle_const == nullptr)
42 return std::make_pair(nullptr, nullptr);
44 auto succs = loco::succs(node);
45 if (succs.size() != 1) // assume bias is used by only one node
46 return std::make_pair(nullptr, nullptr);
48 for (auto out : succs)
50 auto conv = dynamic_cast<CircleConv2D *>(out);
51 if (conv != nullptr && conv->bias() == circle_const)
53 assert(conv->input() != nullptr);
54 assert(conv->filter() != nullptr);
55 return std::make_pair(conv->input(), conv->filter());
57 auto dw_conv = dynamic_cast<CircleDepthwiseConv2D *>(out);
58 if (dw_conv != nullptr && dw_conv->bias() == circle_const)
60 assert(dw_conv->input() != nullptr);
61 assert(dw_conv->filter() != nullptr);
62 return std::make_pair(dw_conv->input(), dw_conv->filter());
64 auto fc = dynamic_cast<CircleFullyConnected *>(out);
65 if (fc != nullptr && fc->bias() == circle_const)
67 assert(fc->input() != nullptr);
68 assert(fc->weights() != nullptr);
69 return std::make_pair(fc->input(), fc->weights());
72 return std::make_pair(nullptr, nullptr);
75 void asym_quant_bias_per_layer(CircleConst *node, float input_scale, float weight_scale,
76 float *scaling_factor, int64_t *zp)
78 float scale = input_scale * weight_scale;
79 const float scaling_factor_inv = (scale == 0) ? 0 : 1.0 / scale;
81 uint32_t size = node->size<loco::DataType::FLOAT32>();
82 std::vector<int32_t> quantized_values(size);
83 for (uint32_t i = 0; i < size; ++i)
86 static_cast<int32_t>(std::round(node->at<loco::DataType::FLOAT32>(i) * scaling_factor_inv));
89 node->dtype(loco::DataType::S32); // change the type of tensor
90 node->size<loco::DataType::S32>(size); // resize tensor
91 const int32_t kMinScale = std::numeric_limits<int32_t>::lowest();
92 const int32_t kMaxScale = std::numeric_limits<int32_t>::max();
93 for (uint32_t i = 0; i < size; ++i)
95 node->at<loco::DataType::S32>(i) =
96 std::min(kMaxScale, std::max(kMinScale, quantized_values[i]));
98 *scaling_factor = scale;
102 void quant_bias_per_channel(CircleConst *node, float input_scale, std::vector<float> &weight_scale,
103 std::vector<float> &scaling_factor, std::vector<int64_t> &zp)
105 float scaling_factor_inv{0};
107 uint32_t size = node->size<loco::DataType::FLOAT32>();
108 std::vector<int32_t> quantized_values(size);
110 for (uint32_t i = 0; i < size; ++i)
112 scaling_factor[i] = input_scale * weight_scale[i];
113 scaling_factor_inv = (scaling_factor[i] == 0) ? 0 : 1.0 / scaling_factor[i];
114 quantized_values[i] =
115 static_cast<int32_t>(std::round(node->at<loco::DataType::FLOAT32>(i) * scaling_factor_inv));
119 node->dtype(loco::DataType::S32); // change the type of tensor
120 node->size<loco::DataType::S32>(size); // resize tensor
121 const int32_t kMinScale = std::numeric_limits<int32_t>::lowest();
122 const int32_t kMaxScale = std::numeric_limits<int32_t>::max();
123 for (uint32_t i = 0; i < size; ++i)
125 node->at<loco::DataType::S32>(i) =
126 std::min(kMaxScale, std::max(kMinScale, quantized_values[i]));
130 bool has_min_max(const CircleNode *node)
132 return node->quantparam() && !node->quantparam()->min.empty() && !node->quantparam()->max.empty();
135 bool is_quantized(const CircleNode *node)
137 return node->dtype() == loco::DataType::U8 || // activation, weight
138 node->dtype() == loco::DataType::S32; // bias
141 void sym_wquant_per_channel(CircleConst *node, std::vector<float> &scaling_factor)
143 assert(node->dtype() == loco::DataType::FLOAT32);
145 const int32_t kMaxScale = std::numeric_limits<int16_t>::max();
146 const int32_t kMinScale = -kMaxScale;
148 uint32_t size = node->size<loco::DataType::FLOAT32>();
149 std::vector<int32_t> quantized_values(size);
151 loco::TensorShape dimension;
153 uint32_t indices[4] = {
156 int channel_dim_index{0};
158 if (!get_channel_dim_index(node, dimension, channel_dim_index))
164 for (indices[0] = 0; indices[0] < dimension.dim(0).value(); indices[0]++)
166 for (indices[1] = 0; indices[1] < dimension.dim(1).value(); indices[1]++)
168 for (indices[2] = 0; indices[2] < dimension.dim(2).value(); indices[2]++)
170 for (indices[3] = 0; indices[3] < dimension.dim(3).value(); indices[3]++)
172 int channel_idx = indices[channel_dim_index];
173 const float scaling_factor_inv = 1.0 / scaling_factor[channel_idx];
174 auto data = node->at<loco::DataType::FLOAT32>(cal_offset(dimension, indices));
175 quantized_values[cal_offset(dimension, indices)] =
176 static_cast<int32_t>(std::round(data * scaling_factor_inv));
182 node->dtype(loco::DataType::S16); // change the type of tensor
183 node->size<loco::DataType::S16>(size); // resize tensor
184 for (uint32_t i = 0; i < size; ++i)
186 node->at<loco::DataType::S16>(i) =
187 std::min(kMaxScale, std::max(kMinScale, quantized_values[i]));
191 void asym_wquant_per_channel(CircleConst *node, std::vector<float> &min,
192 std::vector<float> &scaling_factor)
194 assert(node->dtype() == loco::DataType::FLOAT32);
196 const int32_t kMinScale = 0;
197 const int32_t kMaxScale = 255;
199 uint32_t size = node->size<loco::DataType::FLOAT32>();
200 std::vector<int32_t> quantized_values(size);
202 loco::TensorShape dimension;
204 uint32_t indices[4] = {
207 int channel_dim_index{0};
209 if (!get_channel_dim_index(node, dimension, channel_dim_index))
215 for (indices[0] = 0; indices[0] < dimension.dim(0).value(); indices[0]++)
217 for (indices[1] = 0; indices[1] < dimension.dim(1).value(); indices[1]++)
219 for (indices[2] = 0; indices[2] < dimension.dim(2).value(); indices[2]++)
221 for (indices[3] = 0; indices[3] < dimension.dim(3).value(); indices[3]++)
223 int channel_idx = indices[channel_dim_index];
224 const float scaling_factor_inv = 1.0 / scaling_factor[channel_idx];
225 auto data = node->at<loco::DataType::FLOAT32>(cal_offset(dimension, indices));
226 quantized_values[cal_offset(dimension, indices)] =
227 static_cast<int32_t>(std::round((data - min[channel_idx]) * scaling_factor_inv));
233 node->dtype(loco::DataType::U8); // change the type of tensor
234 node->size<loco::DataType::U8>(size); // resize tensor
235 for (uint32_t i = 0; i < size; ++i)
237 node->at<loco::DataType::U8>(i) = std::min(kMaxScale, std::max(kMinScale, quantized_values[i]));
241 void asym_wquant_per_layer(CircleConst *node, float min, float scaling_factor)
243 const int32_t kMinScale = 0;
244 const int32_t kMaxScale = 255;
246 uint32_t size = node->size<loco::DataType::FLOAT32>();
248 const float scaling_factor_inv = 1.0 / scaling_factor;
249 std::vector<int32_t> quantized_values(size);
250 for (uint32_t i = 0; i < size; ++i)
252 auto data = node->at<loco::DataType::FLOAT32>(i);
253 quantized_values[i] = static_cast<int32_t>(std::round((data - min) * scaling_factor_inv));
256 node->dtype(loco::DataType::U8); // change the type of tensor
257 node->size<loco::DataType::U8>(size); // resize tensor
258 for (uint32_t i = 0; i < size; ++i)
260 node->at<loco::DataType::U8>(i) = std::min(kMaxScale, std::max(kMinScale, quantized_values[i]));
264 // Check if node is weights of conv2d, depthwise_conv2d, or fully_connected layer
265 bool is_weights(CircleNode *node)
267 auto circle_const = dynamic_cast<CircleConst *>(node);
268 if (circle_const == nullptr)
271 auto succs = loco::succs(node);
272 if (succs.size() != 1) // assume weights is used by only one node
275 for (auto out : succs)
277 auto conv = dynamic_cast<CircleConv2D *>(out);
278 if (conv != nullptr && conv->filter() == circle_const)
281 auto dw_conv = dynamic_cast<CircleDepthwiseConv2D *>(out);
282 if (dw_conv != nullptr && dw_conv->filter() == circle_const)
285 auto fc = dynamic_cast<CircleFullyConnected *>(out);
286 if (fc != nullptr && fc->weights() == circle_const)
293 * @brief QuantizeActivation quantizes tensors for activations
294 * @details Quantize using recorded min/max values
296 struct QuantizeActivation final : public luci::CircleNodeMutableVisitor<bool>
298 QuantizeActivation(loco::DataType input, loco::DataType output)
299 : input_type(input), output_type(output)
303 loco::DataType input_type;
304 loco::DataType output_type;
306 // Quantize input tensors of each node
307 bool visit(luci::CircleNode *node)
310 INFO(l) << "QuantizeActivation visit node: " << node->name() << std::endl;
311 auto arity = node->arity();
312 for (uint32_t i = 0; i < arity; i++)
314 auto input_node = node->arg(i);
315 auto circle_node = loco::must_cast<luci::CircleNode *>(input_node);
317 // Check if this is already quantized
318 if (is_quantized(circle_node))
321 // Check if this is bias (bias is quantized later)
322 auto iw = get_input_weight_of_bias(circle_node);
323 if (iw.first != nullptr && iw.second != nullptr)
326 // Check if this is activation
327 // We assume min/max are recorded only for activations
328 if (has_min_max(circle_node) && !is_weights(circle_node))
330 // Quantize using recorded min/max
331 auto quantparam = circle_node->quantparam();
332 assert(quantparam->min.size() == 1); // only support layer-wise quant
333 assert(quantparam->max.size() == 1); // only support layer-wise quant
334 auto min = quantparam->min[0];
335 auto max = quantparam->max[0];
337 float scaling_factor{0};
342 if (output_type == loco::DataType::U8)
344 compute_asym_scale_zp(min, max, scaling_factor, zp, nudged_min, nudged_max);
345 circle_node->dtype(loco::DataType::U8);
349 compute_sym_scale_zp(min, max, scaling_factor, zp, nudged_min, nudged_max);
350 circle_node->dtype(loco::DataType::S16);
353 circle_node->quantparam()->max[0] = nudged_max;
354 circle_node->quantparam()->min[0] = nudged_min;
355 circle_node->quantparam()->scale.push_back(scaling_factor);
356 circle_node->quantparam()->zerop.push_back(zp);
363 struct QuantizeBias final : public luci::CircleNodeMutableVisitor<bool>
365 QuantizeBias(loco::DataType input, loco::DataType output, QuantizationGranularity gr)
366 : input_type(input), output_type(output), granularity(gr)
370 loco::DataType input_type;
371 loco::DataType output_type;
372 QuantizationGranularity granularity;
374 // Quantize bias node
375 bool visit(luci::CircleNode *node)
377 // Check if this is already quantized
378 if (is_quantized(node))
381 // Check if this is bias
382 auto iw = get_input_weight_of_bias(node);
383 if (iw.first == nullptr || iw.second == nullptr)
386 auto input = loco::must_cast<luci::CircleNode *>(iw.first);
387 auto weight = loco::must_cast<luci::CircleNode *>(iw.second);
389 if (granularity == QuantizationGranularity::ChannelWise)
391 assert(input->quantparam()->scale.size() == 1); // input scale's layer-wise
392 auto input_scale = input->quantparam()->scale[0];
394 assert(weight->quantparam() != nullptr); // weight scale's channel-wise
395 auto weight_scale = weight->quantparam()->scale;
397 auto circle_const = loco::must_cast<luci::CircleConst *>(node);
399 uint32_t size = circle_const->size<loco::DataType::FLOAT32>();
400 assert(size == weight_scale.size());
401 std::vector<float> scaling_factor(size);
402 std::vector<int64_t> zp(size);
404 quant_bias_per_channel(circle_const, input_scale, weight_scale, scaling_factor, zp);
406 auto quantparam = std::make_unique<CircleQuantParam>();
407 quantparam->scale = scaling_factor;
408 quantparam->zerop = zp;
409 assert(circle_const->quantparam() == nullptr); // bias should not be quantized before
410 circle_const->quantparam(std::move(quantparam));
414 assert(input->quantparam()->scale.size() == 1); // Only support per-layer quant
415 auto input_scale = input->quantparam()->scale[0];
417 assert(weight->quantparam()->scale.size() == 1); // Only support per-layer quant
418 auto weight_scale = weight->quantparam()->scale[0];
420 auto circle_const = loco::must_cast<luci::CircleConst *>(node);
421 float scaling_factor{0};
423 asym_quant_bias_per_layer(circle_const, input_scale, weight_scale, &scaling_factor, &zp);
424 auto quantparam = std::make_unique<CircleQuantParam>();
425 quantparam->scale.push_back(scaling_factor);
426 quantparam->zerop.push_back(zp);
427 assert(circle_const->quantparam() == nullptr); // bias should not be quantized before
428 circle_const->quantparam(std::move(quantparam));
435 * @brief QuantizeWeights quantizes tensors for weights
436 * @details Find min/max values on the fly and then quantize
438 struct QuantizeWeights final : public luci::CircleNodeMutableVisitor<bool>
440 QuantizeWeights(loco::DataType input, loco::DataType output, QuantizationGranularity gr)
441 : input_type(input), output_type(output), granularity(gr)
445 loco::DataType input_type;
446 loco::DataType output_type;
447 QuantizationGranularity granularity;
449 // Quantize input tensors of each node
450 bool visit(luci::CircleNode *node)
453 INFO(l) << "QuantizeWeights visit node: " << node->name() << std::endl;
454 auto arity = node->arity();
455 for (uint32_t i = 0; i < arity; i++)
457 auto input_node = node->arg(i);
458 auto circle_node = loco::must_cast<luci::CircleNode *>(input_node);
460 // Check if this is already quantized
461 if (is_quantized(circle_node))
464 if (is_weights(circle_node))
466 auto circle_const = loco::must_cast<luci::CircleConst *>(circle_node);
468 // Find min/max per channel-wise
469 if (granularity == QuantizationGranularity::ChannelWise)
471 auto quantparam = circle_node->quantparam();
472 assert(quantparam != nullptr);
473 auto min = quantparam->min;
474 auto scaling_factor = quantparam->scale;
476 if (output_type == loco::DataType::U8)
478 asym_wquant_per_channel(circle_const, min, scaling_factor);
482 sym_wquant_per_channel(circle_const, scaling_factor);
485 // Find min/max per layer-wise
488 // Quantize using recorded quantparam
489 auto quantparam = circle_node->quantparam();
490 assert(quantparam != nullptr);
491 assert(quantparam->min.size() == 1); // only support layer-wise quant
492 assert(quantparam->scale.size() == 1); // only support layer-wise quant
493 auto min = quantparam->min[0];
494 auto scaling_factor = quantparam->scale[0];
495 asym_wquant_per_layer(circle_const, min, scaling_factor);
505 bool QuantizeWithMinMaxPass::run(loco::Graph *g)
508 INFO(l) << "QuantizeWithMinMaxPass Start" << std::endl;
510 // Quantize activation
511 for (auto node : loco::active_nodes(loco::output_nodes(g)))
513 QuantizeActivation qa(_input_dtype, _output_dtype);
514 auto circle_node = loco::must_cast<luci::CircleNode *>(node);
515 circle_node->accept(&qa);
519 for (auto node : loco::active_nodes(loco::output_nodes(g)))
521 QuantizeWeights qw(_input_dtype, _output_dtype, _granularity);
522 auto circle_node = loco::must_cast<luci::CircleNode *>(node);
523 circle_node->accept(&qw);
527 for (auto node : loco::active_nodes(loco::output_nodes(g)))
529 QuantizeBias qb(_input_dtype, _output_dtype, _granularity);
530 auto circle_node = loco::must_cast<luci::CircleNode *>(node);
531 circle_node->accept(&qb);
534 // Update output dtype
535 auto graph_outputs = g->outputs();
536 for (auto node : loco::output_nodes(g))
538 auto circle_node = loco::must_cast<luci::CircleOutput *>(node);
539 if (static_cast<luci::CircleNode *>(circle_node->from())->dtype() == _output_dtype)
541 circle_node->dtype(_output_dtype);
542 auto graph_output = graph_outputs->at(circle_node->index());
543 graph_output->dtype(_output_dtype);
547 INFO(l) << "QuantizeWithMinMaxPass End" << std::endl;
548 return false; // one time run