forked from huawei/mindspore2022
1006 lines
33 KiB
C++
1006 lines
33 KiB
C++
/**
|
|
* Copyright 2020-2021 Huawei Technologies Co., Ltd
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "mindspore/lite/tools/converter/quantizer/quantize_util.h"
|
|
#include <cmath>
|
|
#include <string>
|
|
#include <map>
|
|
#include <fstream>
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#include <vector>
|
|
#include <set>
|
|
#include "include/version.h"
|
|
#include "ops/concat.h"
|
|
#include "ops/crop.h"
|
|
#include "ops/eltwise.h"
|
|
#include "ops/fusion/activation.h"
|
|
#include "ops/fusion/add_fusion.h"
|
|
#include "ops/fusion/avg_pool_fusion.h"
|
|
#include "ops/fusion/conv2d_fusion.h"
|
|
#include "ops/fusion/conv2d_transpose_fusion.h"
|
|
#include "ops/fusion/full_connection.h"
|
|
#include "ops/fusion/layer_norm_fusion.h"
|
|
#include "ops/fusion/max_pool_fusion.h"
|
|
#include "ops/fusion/mul_fusion.h"
|
|
#include "ops/gather.h"
|
|
#include "ops/mat_mul.h"
|
|
#include "ops/reshape.h"
|
|
#include "ops/split.h"
|
|
#include "ops/transpose.h"
|
|
#include "tools/converter/ops/ops_def.h"
|
|
#include "tools/anf_exporter/anf_exporter.h"
|
|
#include "tools/converter/quantizer/bitpacking.h"
|
|
#include "src/common/utils.h"
|
|
#include "tools/common/tensor_util.h"
|
|
#include "abstract/abstract_value.h"
|
|
#include "securec/include/securec.h"
|
|
|
|
using std::string;
|
|
using std::vector;
|
|
|
|
namespace mindspore::lite::quant {
|
|
const std::vector<std::string> QuantStrategy::conv_types_ = {ops::kNameConv2DFusion, ops::kNameConv2dTransposeFusion};
|
|
const std::vector<std::string> QuantStrategy::mul_types_ = {ops::kNameMatMul, ops::kNameFullConnection};
|
|
QuantStrategy::QuantStrategy(size_t weight_size, size_t conv_weight_quant_channel_threshold)
|
|
: m_weight_size_(weight_size), m_conv_weight_quant_channel_threshold_(conv_weight_quant_channel_threshold) {}
|
|
|
|
bool QuantStrategy::CanConvOpQuantized(const CNodePtr &node) const {
|
|
MS_ASSERT(node != nullptr);
|
|
auto primitive_c = GetValueNode<std::shared_ptr<ops::PrimitiveC>>(node->input(0));
|
|
if (primitive_c == nullptr) {
|
|
MS_LOG(ERROR) << "primitive_c is nullptr";
|
|
return false;
|
|
}
|
|
if (!IsContain(conv_types_, primitive_c->name())) {
|
|
return false;
|
|
}
|
|
if (node->size() < 3) {
|
|
return false;
|
|
}
|
|
auto inputNode = node->input(2);
|
|
if (!inputNode->isa<Parameter>()) {
|
|
return false;
|
|
}
|
|
auto paramNode = inputNode->cast<ParameterPtr>();
|
|
auto abstract_base = paramNode->abstract();
|
|
if (abstract_base == nullptr) {
|
|
return false;
|
|
}
|
|
if (!utils::isa<abstract::ShapePtr>(abstract_base->GetShapeTrack())) {
|
|
MS_LOG(INFO) << "Shape of Abstract of parameter should be ShapePtr " << paramNode->name();
|
|
return false;
|
|
}
|
|
auto weight_shape = utils::cast<abstract::ShapePtr>(abstract_base->GetShapeTrack())->shape();
|
|
size_t shapeSize = 1;
|
|
for (auto dim : weight_shape) {
|
|
shapeSize = shapeSize * dim;
|
|
}
|
|
if (shapeSize < m_weight_size_) {
|
|
MS_LOG(INFO) << "shapeSize Invalid!" << shapeSize;
|
|
return false;
|
|
}
|
|
if (weight_shape[0] <= static_cast<int>(m_conv_weight_quant_channel_threshold_)) {
|
|
MS_LOG(INFO) << "channel less m_conv_weight_quant_channel_threshold_!" << weight_shape[0];
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool QuantStrategy::CanOpPostQuantized(const AnfNodePtr &node) {
|
|
MS_ASSERT(node != nullptr);
|
|
if (!node->isa<mindspore::CNode>()) {
|
|
return false;
|
|
}
|
|
const auto cnode = std::dynamic_pointer_cast<mindspore::CNode>(node);
|
|
auto type = NodePrimitiveType(cnode);
|
|
static const std::vector<std::string> int8OpList = {
|
|
ops::kNameAddFusion, ops::kNameActivation, ops::kNameAvgPoolFusion,
|
|
ops::kNameConcat, ops::kNameConv2DFusion, ops::kNameConv2dTransposeFusion,
|
|
ops::kNameCrop, ops::kNameEltwise, ops::kNameFullConnection,
|
|
ops::kNameGather, ops::kNameLayerNormFusion, ops::kNameMatMul,
|
|
ops::kNameMaxPoolFusion, ops::kNameMulFusion, ops::kNameReshape,
|
|
ops::kNameSplit, ops::kNameTranspose, lite::kNameTupleGetItem,
|
|
};
|
|
bool contain = IsContain(int8OpList, type);
|
|
if (!contain) {
|
|
MS_LOG(INFO) << "not quant, " << cnode->fullname_with_scope() << " of type: " << type;
|
|
}
|
|
return contain;
|
|
}
|
|
|
|
bool QuantStrategy::CanMulOpQuantized(const CNodePtr &node) const {
|
|
MS_ASSERT(node != nullptr);
|
|
auto primitive_c = GetValueNode<std::shared_ptr<ops::PrimitiveC>>(node->input(0));
|
|
if (primitive_c == nullptr) {
|
|
MS_LOG(ERROR) << "primitive_c is nullptr";
|
|
return false;
|
|
}
|
|
|
|
if (!IsContain(mul_types_, primitive_c->name())) {
|
|
return false;
|
|
}
|
|
|
|
if (node->size() < 3) {
|
|
MS_LOG(INFO) << node->fullname_with_scope() << " input size less!";
|
|
return false;
|
|
}
|
|
|
|
auto inputNode1 = node->input(1);
|
|
auto inputNode2 = node->input(2);
|
|
if (inputNode1 == nullptr || inputNode2 == nullptr) {
|
|
MS_LOG(INFO) << node->fullname_with_scope() << " mul input is nullptr!";
|
|
return false;
|
|
}
|
|
|
|
ParameterPtr paramNode = nullptr;
|
|
if (inputNode1->isa<Parameter>()) {
|
|
paramNode = inputNode1->cast<ParameterPtr>();
|
|
} else if (inputNode2->isa<Parameter>()) {
|
|
paramNode = inputNode2->cast<ParameterPtr>();
|
|
}
|
|
|
|
if (paramNode == nullptr) {
|
|
MS_LOG(INFO) << node->fullname_with_scope() << " invalid paramNode!";
|
|
return false;
|
|
}
|
|
|
|
auto abstract_base = paramNode->abstract();
|
|
if (abstract_base == nullptr) {
|
|
MS_LOG(INFO) << "abstract is nullptr";
|
|
return false;
|
|
}
|
|
|
|
if (!utils::isa<abstract::ShapePtr>(abstract_base->GetShapeTrack())) {
|
|
MS_LOG(INFO) << "Shape of Abstract of parameter should be ShapePtr " << paramNode->name();
|
|
return false;
|
|
}
|
|
auto weight_shape = utils::cast<abstract::ShapePtr>(abstract_base->GetShapeTrack())->shape();
|
|
size_t shapeSize = 1;
|
|
for (auto dim : weight_shape) {
|
|
shapeSize = shapeSize * dim;
|
|
}
|
|
if (shapeSize < m_weight_size_) {
|
|
MS_LOG(INFO) << "shapeSize Invalid!" << shapeSize;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QuantStrategy::CanTensorQuantized(const AnfNodePtr &inputNode) const {
|
|
if (inputNode == nullptr) {
|
|
MS_LOG(INFO) << "CanTensorQuantized input is nullptr!";
|
|
return false;
|
|
}
|
|
ParameterPtr paramNode = nullptr;
|
|
|
|
if (inputNode->isa<Parameter>()) {
|
|
paramNode = inputNode->cast<ParameterPtr>();
|
|
}
|
|
|
|
if (paramNode == nullptr) {
|
|
MS_LOG(INFO) << "CanTensorQuantized invalid paramNode!";
|
|
return false;
|
|
}
|
|
|
|
auto abstract_base = paramNode->abstract();
|
|
if (abstract_base == nullptr) {
|
|
MS_LOG(INFO) << "abstract is nullptr";
|
|
return false;
|
|
}
|
|
|
|
if (!utils::isa<abstract::ShapePtr>(abstract_base->GetShapeTrack())) {
|
|
MS_LOG(INFO) << "Shape of Abstract of parameter should be ShapePtr " << paramNode->name();
|
|
return false;
|
|
}
|
|
|
|
auto weight_shape = utils::cast<abstract::ShapePtr>(abstract_base->GetShapeTrack())->shape();
|
|
if (weight_shape.size() < 2) { // do not quant single dim tensors
|
|
return false;
|
|
}
|
|
|
|
size_t shapeSize = 1;
|
|
for (auto dim : weight_shape) {
|
|
shapeSize = shapeSize * dim;
|
|
}
|
|
if (shapeSize < m_weight_size_) {
|
|
MS_LOG(INFO) << "shapeSize Invalid!" << shapeSize;
|
|
return false;
|
|
}
|
|
|
|
if (weight_shape.size() == 4) { // assume Convolution
|
|
if (weight_shape[0] <= static_cast<int>(m_conv_weight_quant_channel_threshold_)) {
|
|
MS_LOG(INFO) << "channel less m_conv_weight_quant_channel_threshold_!" << weight_shape[0];
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
QuantParamHolderPtr GetCNodeQuantHolder(const PrimitivePtr &primitive) {
|
|
MS_ASSERT(primitive != nullptr);
|
|
QuantParamHolderPtr quant_params_holder = nullptr;
|
|
auto quant_params_valueptr = primitive->GetAttr("quant_params");
|
|
if (quant_params_valueptr == nullptr) {
|
|
quant_params_holder = std::make_shared<QuantParamHolder>(0, 0);
|
|
primitive->AddAttr("quant_params", quant_params_holder);
|
|
} else {
|
|
quant_params_holder = quant_params_valueptr->cast<QuantParamHolderPtr>();
|
|
if (quant_params_holder == nullptr) {
|
|
quant_params_holder = std::make_shared<QuantParamHolder>(0, 0);
|
|
primitive->AddAttr("quant_params", quant_params_holder);
|
|
}
|
|
}
|
|
return quant_params_holder;
|
|
}
|
|
|
|
bool TensorQuantParamsInited(const schema::TensorT &tensor) {
|
|
if (tensor.quantParams.empty()) {
|
|
return false;
|
|
}
|
|
|
|
for (auto &quant_param : tensor.quantParams) {
|
|
if (!quant_param->inited) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
STATUS CalQuantizationParams(schema::QuantParamT *quantParam, double mMin, double mMax, bool narrowRange, int numBits) {
|
|
MS_ASSERT(quantParam != nullptr);
|
|
if (mMin > 0.0f) {
|
|
MS_LOG(DEBUG) << "min " << mMin << " is bigger then 0, set to 0, this may course low precision";
|
|
mMin = 0.0f;
|
|
}
|
|
if (mMax < 0.0f) {
|
|
MS_LOG(DEBUG) << "mMax " << mMax << " is smaller than 0, set to 0, this may course low precision";
|
|
mMax = 0.0f;
|
|
}
|
|
if (mMin > mMax) {
|
|
MS_LOG(ERROR) << "cal error while min" << mMin << ">" << mMax;
|
|
return RET_PARAM_INVALID;
|
|
}
|
|
if (mMin == mMax) {
|
|
if (mMin != 0.0f) {
|
|
MS_LOG(ERROR) << "min and max should both be zero if they are equal to each other";
|
|
return RET_ERROR;
|
|
}
|
|
quantParam->inited = true;
|
|
quantParam->min = mMin;
|
|
quantParam->max = mMax;
|
|
quantParam->scale = 0.0f;
|
|
quantParam->zeroPoint = 0;
|
|
quantParam->narrowRange = narrowRange;
|
|
quantParam->numBits = numBits;
|
|
return RET_OK;
|
|
}
|
|
|
|
const int8_t quantMax = (1 << (unsigned int)(numBits - 1)) - 1;
|
|
const int8_t quantMin = -1 * (1 << (unsigned int)(numBits - 1)) + (narrowRange ? 1 : 0);
|
|
auto quantMinFloat = static_cast<double>(quantMin);
|
|
auto quantMaxFloat = static_cast<double>(quantMax);
|
|
if (fabs(quantMaxFloat - quantMinFloat) <= 0.0f) {
|
|
MS_LOG(ERROR) << "divisor cannot be 0";
|
|
return RET_ERROR;
|
|
}
|
|
double scale = (mMax - mMin) / (quantMaxFloat - quantMinFloat);
|
|
if (fabs(scale) <= 0.0f) {
|
|
MS_LOG(ERROR) << "divisor 'scale' cannot be 0";
|
|
return RET_ERROR;
|
|
}
|
|
const double zeroPointFromMin = quantMinFloat - mMin / scale;
|
|
const double zeroPointFromMax = quantMaxFloat - mMax / scale;
|
|
const double zpFromMinError = std::abs(quantMinFloat) + std::abs(mMin / scale);
|
|
const double zpFromMaxError = std::abs(quantMaxFloat) + std::abs(mMax / scale);
|
|
const double zpDouble = zpFromMinError < zpFromMaxError ? zeroPointFromMin : zeroPointFromMax;
|
|
int zeroPoint;
|
|
if (zpDouble < quantMinFloat) {
|
|
zeroPoint = quantMin;
|
|
} else if (zpDouble > quantMaxFloat) {
|
|
zeroPoint = quantMax;
|
|
} else {
|
|
zeroPoint = static_cast<int32_t>(std::round(zpDouble));
|
|
}
|
|
if (std::abs(mMin) == std::abs(mMax)) {
|
|
zeroPoint = 0;
|
|
}
|
|
// The zero point should always be in the range of quantized value,
|
|
// [qmin, qmax].
|
|
MS_ASSERT(zeroPoint >= quantMin);
|
|
MS_ASSERT(zeroPoint <= quantMax);
|
|
quantParam->inited = true;
|
|
quantParam->min = mMin;
|
|
quantParam->max = mMax;
|
|
quantParam->scale = scale;
|
|
quantParam->zeroPoint = zeroPoint;
|
|
quantParam->narrowRange = narrowRange;
|
|
quantParam->numBits = numBits;
|
|
|
|
return RET_OK;
|
|
}
|
|
|
|
static bool SearchLowerBound(const std::vector<float> &data, const size_t &index, const float &max_tmp, float *min_tmp,
|
|
size_t *min_idx) {
|
|
size_t length = data.size();
|
|
if (max_tmp - data.at(index) < delta) {
|
|
return false;
|
|
}
|
|
if (fabs(max_tmp - *min_tmp) <= 0.0f || fabs(length - *min_idx) <= 0.0f) {
|
|
MS_LOG(ERROR) << "divisor cannot be 0";
|
|
return false;
|
|
}
|
|
float range_ratio = (data.at(index) - *min_tmp) / (max_tmp - *min_tmp);
|
|
float index_ratio = static_cast<float>(index - *min_idx) / (length - *min_idx);
|
|
if (fabs(index_ratio) <= 0.0f) {
|
|
MS_LOG(ERROR) << "divisor cannot be 0";
|
|
return false;
|
|
}
|
|
if (index_ratio > 0 && range_ratio / index_ratio > ratio) {
|
|
*min_idx = index;
|
|
*min_tmp = data.at(index);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool SearchUpperBound(const std::vector<float> &data, const size_t &index, float *max_tmp, const float &min_tmp,
|
|
size_t *max_idx) {
|
|
size_t length = data.size();
|
|
if (data.at(index) - min_tmp < delta) {
|
|
return false;
|
|
}
|
|
if (fabs(*max_tmp - min_tmp) <= 0.0f || fabs(length - *max_idx) <= 0.0f) {
|
|
MS_LOG(ERROR) << "divisor cannot be 0";
|
|
return false;
|
|
}
|
|
float range_ratio = (*max_tmp - data.at(index)) / (*max_tmp - min_tmp);
|
|
float index_ratio = static_cast<float>(index - *max_idx) / (length - *max_idx);
|
|
if (fabs(index_ratio) <= 0.0f) {
|
|
MS_LOG(ERROR) << "divisor cannot be 0";
|
|
return false;
|
|
}
|
|
if (index_ratio > 0 && range_ratio / index_ratio > ratio) {
|
|
*max_idx = index;
|
|
*max_tmp = data.at(index);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static float CalPercentile(const std::vector<float> &data, const int &outlier_percent) {
|
|
const int size = data.size();
|
|
float val = outlier_percent / kPercentBase * size;
|
|
int index = std::ceil(val);
|
|
float result;
|
|
if (index - val > 0) {
|
|
result = data.at(index - 1);
|
|
} else {
|
|
result = (data.at(index - 1) + data.at(index)) / 2;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::pair<float, float> OutlierMethod(std::vector<float> min_datas, std::vector<float> max_datas) {
|
|
std::sort(max_datas.begin(), max_datas.end());
|
|
std::sort(min_datas.begin(), min_datas.end());
|
|
float min_val = CalPercentile(min_datas, percent);
|
|
float max_val = CalPercentile(max_datas, kPercentBase - percent);
|
|
std::reverse(max_datas.begin(), max_datas.end());
|
|
MS_ASSERT(min_val < max_val);
|
|
MS_ASSERT(min_datas.size() == max_datas.size());
|
|
float min_tmp = min_val;
|
|
float max_tmp = max_val;
|
|
size_t min_idx = 0;
|
|
size_t max_idx = 0;
|
|
size_t length = min_datas.size();
|
|
for (size_t i = 0; i < length; i++) {
|
|
if (!SearchLowerBound(min_datas, i, max_tmp, &min_tmp, &min_idx)) {
|
|
break;
|
|
}
|
|
if (!SearchUpperBound(min_datas, i, &max_tmp, min_tmp, &max_idx)) {
|
|
break;
|
|
}
|
|
}
|
|
std::pair<float, float> result{min_tmp, max_tmp};
|
|
return result;
|
|
}
|
|
|
|
static std::vector<float> InitClusters(float *data, size_t elem_count, size_t k) {
|
|
std::set<float> set_unique{};
|
|
for (size_t i = 0; i < elem_count; i++) {
|
|
set_unique.emplace(data[i]);
|
|
}
|
|
std::vector<float> data_unique;
|
|
data_unique.assign(set_unique.begin(), set_unique.end());
|
|
std::vector<float> clusters{};
|
|
if (set_unique.size() < k) {
|
|
return clusters;
|
|
}
|
|
// init cluster
|
|
MS_ASSERT(k != 1);
|
|
float ratio = static_cast<float>(data_unique.size()) / (k - 1);
|
|
std::sort(data_unique.begin(), data_unique.end());
|
|
for (size_t i = 0; i < k; i++) {
|
|
size_t index = std::floor(i * ratio);
|
|
if (i * ratio - index > 0) {
|
|
clusters.emplace_back((data_unique[index] + data_unique[index + 1]) / 2);
|
|
} else {
|
|
clusters.emplace_back(data_unique[index]);
|
|
}
|
|
}
|
|
return clusters;
|
|
}
|
|
|
|
std::vector<int8_t> KMeans(float *data, size_t elem_count, size_t k, size_t epochs, schema::QuantParamT *quantParam) {
|
|
MS_ASSERT(data != nullptr);
|
|
MS_ASSERT(quantParam != nullptr);
|
|
std::vector<float> clusters = InitClusters(data, elem_count, k);
|
|
std::vector<int8_t> clusters_index{};
|
|
double error{0};
|
|
if (clusters.size() < k) {
|
|
MS_LOG(WARNING) << "K is less than the size of data so KMeans function is not executed.";
|
|
return clusters_index;
|
|
}
|
|
for (size_t epoch = 0; epoch < epochs; epoch++) {
|
|
double error_cur{0};
|
|
clusters_index.clear();
|
|
std::vector<std::vector<float>> clusters_data(clusters.size());
|
|
for (size_t i = 0; i < elem_count; i++) {
|
|
size_t index = 0;
|
|
float min_distance = pow(data[i] - clusters[0], 2);
|
|
for (size_t j = 1; j < clusters.size(); j++) {
|
|
if (pow(data[i] - clusters[j], 2) < min_distance) {
|
|
min_distance = pow(data[i] - clusters[j], 2);
|
|
index = j;
|
|
}
|
|
}
|
|
clusters_index.emplace_back(index + INT8_MIN);
|
|
clusters_data[index].emplace_back(data[i]);
|
|
}
|
|
for (size_t j = 0; j < clusters.size(); j++) {
|
|
if (!clusters_data[j].empty()) {
|
|
clusters[j] = std::accumulate(clusters_data[j].begin(), clusters_data[j].end(), 0.0) / clusters_data[j].size();
|
|
}
|
|
}
|
|
// compare error
|
|
for (size_t j = 0; j < elem_count; j++) {
|
|
error_cur += pow(data[j] - clusters[clusters_index[j]], 2);
|
|
}
|
|
error_cur = pow(error_cur / elem_count, 0.5);
|
|
if (std::abs((error_cur - error) / error_cur) <= 0.0f) {
|
|
break;
|
|
}
|
|
error = error_cur;
|
|
}
|
|
// update data
|
|
return clusters_index;
|
|
}
|
|
|
|
std::string NodePrimitiveType(const CNodePtr &cnode) {
|
|
if (cnode == nullptr) {
|
|
MS_LOG(ERROR) << "cnode is null";
|
|
return "";
|
|
}
|
|
auto primitive_c = GetValueNode<std::shared_ptr<ops::PrimitiveC>>(cnode->input(0));
|
|
if (primitive_c == nullptr) {
|
|
MS_LOG(ERROR) << "primitive_c is null";
|
|
return "";
|
|
}
|
|
return primitive_c->name();
|
|
}
|
|
|
|
std::vector<int> DataToVector(const string &str) {
|
|
std::vector<int> result;
|
|
auto raw_datas = str;
|
|
auto ind = raw_datas.find(',');
|
|
while (ind != std::string::npos) {
|
|
auto data = raw_datas.substr(0, ind);
|
|
Trim(&data);
|
|
result.push_back(std::stoul(data));
|
|
raw_datas = raw_datas.substr(ind + 1);
|
|
Trim(&raw_datas);
|
|
ind = raw_datas.find(',');
|
|
}
|
|
if (!raw_datas.empty()) {
|
|
result.push_back(std::stoul(raw_datas));
|
|
}
|
|
if (result.empty()) {
|
|
MS_LOG(ERROR) << "result is empty";
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::vector<std::vector<int>> DataToVectors(const string &str) {
|
|
std::vector<std::vector<int>> result;
|
|
auto raw_datas = str;
|
|
auto ind = raw_datas.find(';');
|
|
while (ind != std::string::npos) {
|
|
auto data = raw_datas.substr(0, ind);
|
|
Trim(&data);
|
|
result.push_back(DataToVector(data));
|
|
raw_datas = raw_datas.substr(ind + 1);
|
|
Trim(&raw_datas);
|
|
ind = raw_datas.find(';');
|
|
}
|
|
if (!raw_datas.empty()) {
|
|
result.push_back(DataToVector(raw_datas));
|
|
}
|
|
if (result.empty()) {
|
|
MS_LOG(ERROR) << "result is empty";
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void ParseInputShape(PostQuantConfig *post_quant_config, std::string raw_shape) {
|
|
MS_ASSERT(post_quant_config != nullptr);
|
|
auto ind = raw_shape.find('/');
|
|
while (ind != std::string::npos) {
|
|
auto shape = raw_shape.substr(0, ind);
|
|
Trim(&shape);
|
|
post_quant_config->input_shapes.push_back(DataToVectors(shape));
|
|
raw_shape = raw_shape.substr(ind + 1);
|
|
Trim(&raw_shape);
|
|
ind = raw_shape.find('/');
|
|
}
|
|
if (!raw_shape.empty()) {
|
|
post_quant_config->input_shapes.push_back(DataToVectors(raw_shape));
|
|
}
|
|
}
|
|
|
|
void ParseImagePath(PostQuantConfig *post_quant_config, std::string raw_image_paths) {
|
|
MS_ASSERT(post_quant_config != nullptr);
|
|
auto ind = raw_image_paths.find(',');
|
|
while (ind != std::string::npos) {
|
|
auto image_path = raw_image_paths.substr(0, ind);
|
|
Trim(&image_path);
|
|
post_quant_config->image_paths.push_back(image_path);
|
|
raw_image_paths = raw_image_paths.substr(ind + 1);
|
|
Trim(&raw_image_paths);
|
|
ind = raw_image_paths.find(',');
|
|
}
|
|
post_quant_config->image_paths.push_back(raw_image_paths);
|
|
}
|
|
|
|
void ParseBatchCount(PostQuantConfig *post_quant_config, const std::string &value) {
|
|
MS_ASSERT(post_quant_config != nullptr);
|
|
post_quant_config->batch_count = std::stoul(value);
|
|
}
|
|
|
|
void ParseThreadNum(PostQuantConfig *post_quant_config, const std::string &value) {
|
|
MS_ASSERT(post_quant_config != nullptr);
|
|
post_quant_config->thread_num = std::stoul(value);
|
|
}
|
|
|
|
void ParseMethodX(PostQuantConfig *post_quant_config, const std::string &value) {
|
|
MS_ASSERT(post_quant_config != nullptr);
|
|
if (value != kMethodKL && value != kMethodMaxMin && value != kMethodOutlier) {
|
|
MS_LOG(WARNING) << "unsupported method_x: " << value << ". Use default value.";
|
|
} else {
|
|
post_quant_config->method_x = value;
|
|
}
|
|
}
|
|
|
|
void ParseMixed(PostQuantConfig *post_quant_config, std::string value) {
|
|
MS_ASSERT(post_quant_config != nullptr);
|
|
std::for_each(value.begin(), value.end(), ::tolower);
|
|
if (value == "true") {
|
|
post_quant_config->mixed = true;
|
|
}
|
|
}
|
|
|
|
void ParseMeanErrorThreshold(PostQuantConfig *post_quant_config, const std::string &value) {
|
|
MS_ASSERT(post_quant_config != nullptr);
|
|
post_quant_config->mean_error_threshold = std::stof(value);
|
|
}
|
|
|
|
void ParseBiasCorrection(PostQuantConfig *post_quant_config, std::string value) {
|
|
MS_ASSERT(post_quant_config != nullptr);
|
|
std::for_each(value.begin(), value.end(), ::tolower);
|
|
if (value == "true") {
|
|
post_quant_config->bias_correction = true;
|
|
}
|
|
}
|
|
|
|
STATUS ParseConfigFile(std::string config_file, PostQuantConfig *post_quant_config) {
|
|
MS_ASSERT(post_quant_config != nullptr);
|
|
|
|
if (config_file.empty() || config_file.length() >= PATH_MAX) {
|
|
MS_LOG(ERROR) << "invalid config path!";
|
|
return RET_PARAM_INVALID;
|
|
}
|
|
// check whether config file path is valid
|
|
auto resolved_path = std::make_unique<char[]>(PATH_MAX);
|
|
if (resolved_path == nullptr) {
|
|
MS_LOG(ERROR) << "New an object failed.";
|
|
return RET_ERROR;
|
|
}
|
|
#ifdef _WIN32
|
|
if (_fullpath(resolved_path.get(), config_file.c_str(), kMaxNum1024) != nullptr) {
|
|
config_file = string(resolved_path.get());
|
|
}
|
|
#else
|
|
if (realpath(config_file.c_str(), resolved_path.get()) != nullptr) {
|
|
config_file = string(resolved_path.get());
|
|
}
|
|
#endif
|
|
std::ifstream fs(config_file.c_str(), std::ifstream::in);
|
|
if (!fs.is_open()) {
|
|
MS_LOG(ERROR) << "config file open failed: " << config_file;
|
|
return RET_PARAM_INVALID;
|
|
}
|
|
|
|
std::string INPUT_SHAPES = "input_shapes";
|
|
std::string IMAGE_PATH = "image_path";
|
|
std::string BATCH_COUNT = "batch_count";
|
|
std::string THREAD_NUM = "thread_num";
|
|
std::string METHOD_X = "method_x";
|
|
std::string MIXED = "mixed";
|
|
std::string MEAN_ERROR_THRESHOLD = "mean_error_threshold";
|
|
std::string BIAS_CORRECTION = "bias_correction";
|
|
|
|
std::map<std::string, std::function<void(PostQuantConfig *, std::string)>> value_parser;
|
|
value_parser[INPUT_SHAPES] = ParseInputShape;
|
|
value_parser[IMAGE_PATH] = ParseImagePath;
|
|
value_parser[BATCH_COUNT] = ParseBatchCount;
|
|
value_parser[THREAD_NUM] = ParseThreadNum;
|
|
value_parser[METHOD_X] = ParseMethodX;
|
|
value_parser[MIXED] = ParseMixed;
|
|
value_parser[MEAN_ERROR_THRESHOLD] = ParseMeanErrorThreshold;
|
|
value_parser[BIAS_CORRECTION] = ParseBiasCorrection;
|
|
|
|
std::string line;
|
|
while (std::getline(fs, line)) {
|
|
Trim(&line);
|
|
if (line.empty()) {
|
|
continue;
|
|
}
|
|
auto index = line.find('=');
|
|
if (index == std::string::npos) {
|
|
MS_LOG(ERROR) << "the config file is invalid, can not find '=', please check";
|
|
return RET_PARAM_INVALID;
|
|
}
|
|
auto key = line.substr(0, index);
|
|
auto value = line.substr(index + 1);
|
|
Trim(&key);
|
|
Trim(&value);
|
|
auto it = value_parser.find(key);
|
|
if (it != value_parser.end()) {
|
|
it->second(post_quant_config, value);
|
|
} else {
|
|
MS_LOG(WARNING) << "unsupported parameter: " << key;
|
|
}
|
|
}
|
|
|
|
for (const auto &path : post_quant_config->image_paths) {
|
|
MS_LOG(DEBUG) << "calibration data_path: " << path;
|
|
}
|
|
MS_LOG(DEBUG) << "batch_count: " << post_quant_config->batch_count << "\n"
|
|
<< "method_x: " << post_quant_config->method_x << "\n"
|
|
<< "thread_num: " << post_quant_config->thread_num << "\n"
|
|
<< "bias_correction: " << post_quant_config->bias_correction << "\n"
|
|
<< "mixed: " << post_quant_config->mixed << "\n"
|
|
<< "mean_error_threshold: " << post_quant_config->mean_error_threshold;
|
|
post_quant_config->inited = true;
|
|
fs.close();
|
|
return RET_OK;
|
|
}
|
|
|
|
SessionModel CreateSessionByFuncGraph(const FuncGraphPtr &func_graph, const converter::Flags &flags, int thread_num) {
|
|
SessionModel sm;
|
|
auto meta_graph = Export(func_graph, true, true);
|
|
if (meta_graph == nullptr) {
|
|
MS_LOG(ERROR) << "Export to meta_graph failed";
|
|
return sm;
|
|
}
|
|
|
|
// transform
|
|
GraphDefTransform fb_transform;
|
|
fb_transform.SetGraphDef(meta_graph);
|
|
auto status = fb_transform.Transform(flags);
|
|
if (status != RET_OK) {
|
|
MS_LOG(ERROR) << "FBTransform model failed";
|
|
return sm;
|
|
}
|
|
meta_graph->version = Version();
|
|
|
|
flatbuffers::FlatBufferBuilder builder(kMaxNum1024);
|
|
auto offset = schema::MetaGraph::Pack(builder, meta_graph);
|
|
builder.Finish(offset);
|
|
schema::FinishMetaGraphBuffer(builder, offset);
|
|
auto size = builder.GetSize();
|
|
auto *content = reinterpret_cast<const char *>(builder.GetBufferPointer());
|
|
if (content == nullptr) {
|
|
MS_LOG(ERROR) << "GetBufferPointer return null";
|
|
return sm;
|
|
}
|
|
auto model = lite::Model::Import(content, size);
|
|
if (model == nullptr) {
|
|
MS_LOG(ERROR) << "Import model failed";
|
|
return sm;
|
|
}
|
|
|
|
Context ctx;
|
|
ctx.thread_num_ = thread_num;
|
|
|
|
auto session = session::LiteSession::CreateSession(&ctx);
|
|
if (session == nullptr) {
|
|
MS_LOG(ERROR) << "create session failed.";
|
|
return sm;
|
|
}
|
|
|
|
status = session->CompileGraph(model);
|
|
if (status != RET_OK) {
|
|
MS_LOG(ERROR) << "CompileGraph error";
|
|
return sm;
|
|
}
|
|
model->Free();
|
|
delete meta_graph;
|
|
sm.session = session;
|
|
sm.model = model;
|
|
return sm;
|
|
}
|
|
|
|
STATUS CollectCalibInputs(const std::vector<std::string> &input_dirs, size_t count_limited,
|
|
std::vector<std::vector<std::string>> *inputs) {
|
|
if (inputs == nullptr) {
|
|
MS_LOG(ERROR) << "inputs is null";
|
|
return RET_ERROR;
|
|
}
|
|
auto AddImage = [&inputs](const std::string &file, size_t index) {
|
|
if (index >= inputs->size()) {
|
|
MS_LOG(ERROR) << "images_ size: " << inputs->size() << " but input index: " << index;
|
|
return;
|
|
}
|
|
struct stat buf {};
|
|
if (stat(file.c_str(), &buf) == 0) {
|
|
inputs->at(index).push_back(file);
|
|
} else {
|
|
MS_LOG(WARNING) << "invalid image file path: " << file;
|
|
}
|
|
};
|
|
|
|
inputs->resize(input_dirs.size());
|
|
auto input_i = 0;
|
|
bool multi_input = input_dirs.size() > 1;
|
|
for (const auto &image_path : input_dirs) {
|
|
DIR *root = opendir(image_path.c_str());
|
|
if (root == nullptr) {
|
|
MS_LOG(ERROR) << "invalid image path: " << image_path;
|
|
return RET_PARAM_INVALID;
|
|
}
|
|
struct dirent *image_dir = readdir(root);
|
|
size_t count = 0;
|
|
while (image_dir != nullptr) {
|
|
string file_name(image_dir->d_name);
|
|
if (file_name != "." && file_name != "..") {
|
|
const std::string file_path = image_path + "/" + file_name;
|
|
if (multi_input || count == 0 || count < count_limited) {
|
|
AddImage(file_path, input_i);
|
|
count++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
image_dir = readdir(root);
|
|
}
|
|
std::sort(inputs->at(input_i).begin(), inputs->at(input_i).end());
|
|
if (count_limited != 0 && count_limited < inputs->at(input_i).size()) {
|
|
inputs->at(input_i).resize(count_limited);
|
|
}
|
|
closedir(root);
|
|
input_i++;
|
|
}
|
|
return RET_OK;
|
|
}
|
|
|
|
STATUS CopyInputDataToTensor(size_t input_index, size_t image_index,
|
|
const std::vector<std::vector<std::string>> &images, mindspore::tensor::MSTensor *tensor) {
|
|
MS_ASSERT(tensor != nullptr);
|
|
if (input_index >= images.size()) {
|
|
MS_LOG(ERROR) << "images_ size: " << images.size() << " but input_index: " << input_index;
|
|
return RET_ERROR;
|
|
}
|
|
if (image_index >= images[input_index].size()) {
|
|
MS_LOG(ERROR) << "images_[input_index] size: " << images[input_index].size() << " but image_index: " << image_index;
|
|
return RET_ERROR;
|
|
}
|
|
string path = images[input_index][image_index];
|
|
MS_LOG(INFO) << "read image: " << path;
|
|
size_t size;
|
|
char *bin_buf = ReadFile(path.c_str(), &size);
|
|
if (bin_buf == nullptr) {
|
|
MS_LOG(ERROR) << "ReadFile return nullptr";
|
|
return RET_NULL_PTR;
|
|
}
|
|
auto data = tensor->MutableData();
|
|
if (data == nullptr) {
|
|
MS_LOG(ERROR) << "Get tensor MutableData return nullptr";
|
|
return RET_NULL_PTR;
|
|
}
|
|
if (size != tensor->Size()) {
|
|
MS_LOG(ERROR) << "the input data is not consistent with model input, file_size: " << size
|
|
<< " input tensor size: " << tensor->Size();
|
|
return RET_ERROR;
|
|
}
|
|
if (memcpy_s(data, tensor->Size(), bin_buf, size) != EOK) {
|
|
MS_LOG(ERROR) << "memcpy data failed.";
|
|
delete[] bin_buf;
|
|
return RET_ERROR;
|
|
}
|
|
delete[] bin_buf;
|
|
return RET_OK;
|
|
}
|
|
|
|
FuncGraphPtr CopyFuncGraph(const FuncGraphPtr &func_graph) {
|
|
Cloner cloner({func_graph}, true, true, true, std::make_shared<TraceCopy>(), nullptr);
|
|
auto new_func_graph = cloner[func_graph];
|
|
|
|
std::map<std::string, CNodePtr> old_cnode_map;
|
|
for (const auto &cnode : func_graph->GetOrderedCnodes()) {
|
|
old_cnode_map[cnode->fullname_with_scope()] = cnode;
|
|
}
|
|
|
|
for (auto &cnode : new_func_graph->GetOrderedCnodes()) {
|
|
auto cnode_name = cnode->fullname_with_scope();
|
|
auto old_cnode_iter = old_cnode_map.find(cnode_name);
|
|
if (old_cnode_iter == old_cnode_map.end()) {
|
|
MS_LOG(ERROR) << "can not find node: " << cnode_name;
|
|
return nullptr;
|
|
}
|
|
auto old_cnode = old_cnode_iter->second;
|
|
auto inputs = cnode->inputs();
|
|
for (const auto &input_node : inputs) {
|
|
if (input_node->isa<Parameter>()) {
|
|
auto param_node = input_node->cast<ParameterPtr>();
|
|
if (!param_node->has_default()) {
|
|
MS_LOG(ERROR) << "Param node has no default parameter: " << cnode_name;
|
|
return nullptr;
|
|
}
|
|
auto old_tensor_info = std::static_pointer_cast<tensor::Tensor>(param_node->default_param());
|
|
if (old_tensor_info == nullptr) {
|
|
MS_LOG(ERROR) << "Default param of param node is not a tensor info:" << cnode_name;
|
|
return nullptr;
|
|
}
|
|
auto new_tensor_info = lite::CreateTensorInfo(old_tensor_info->data().data(), old_tensor_info->data().nbytes(),
|
|
old_tensor_info->shape(), old_tensor_info->data_type());
|
|
if (new_tensor_info == nullptr) {
|
|
MS_LOG(ERROR) << "Create tensor info failed";
|
|
return nullptr;
|
|
}
|
|
auto status = lite::InitParameterFromTensorInfo(param_node, new_tensor_info);
|
|
if (status != RET_OK) {
|
|
MS_LOG(ERROR) << "init parameter from tensor info failed";
|
|
return nullptr;
|
|
}
|
|
}
|
|
} // end inputs loop
|
|
} // end cnodes loop
|
|
return new_func_graph;
|
|
}
|
|
|
|
void GetLiteParameter(const AnfNodePtr &node, ParameterPtr *param_node, tensor::TensorPtr *tensor_info) {
|
|
MS_ASSERT(node != nullptr);
|
|
MS_ASSERT(param_node != nullptr);
|
|
MS_ASSERT(tensor_info != nullptr);
|
|
|
|
auto op_name = node->fullname_with_scope();
|
|
|
|
*param_node = node->cast<ParameterPtr>();
|
|
if (*param_node == nullptr) {
|
|
MS_LOG(INFO) << op_name << " can not cast to ParameterPtr";
|
|
return;
|
|
}
|
|
if (!(*param_node)->has_default()) {
|
|
MS_LOG(INFO) << op_name << " not has_default";
|
|
return;
|
|
}
|
|
|
|
*tensor_info = std::static_pointer_cast<tensor::Tensor>((*param_node)->default_param());
|
|
if (*tensor_info == nullptr) {
|
|
MS_LOG(INFO) << "default_param can not cast to tensor::Tensor";
|
|
return;
|
|
}
|
|
}
|
|
|
|
STATUS UpdateTensorDataAndSize(const tensor::TensorPtr &weight, void *quant_datas, int new_size, TypeId new_data_type) {
|
|
MS_ASSERT(weight != nullptr);
|
|
MS_ASSERT(new_size > 0);
|
|
weight->set_data_type(new_data_type);
|
|
if (new_size != weight->data().nbytes()) {
|
|
MS_LOG(ERROR) << "Data size of tensor info is error.";
|
|
return RET_ERROR;
|
|
}
|
|
if (memcpy_s(weight->data_c(), new_size, quant_datas, new_size) != EOK) {
|
|
MS_LOG(ERROR) << "memcpy data failed.";
|
|
return RET_ERROR;
|
|
}
|
|
return RET_OK;
|
|
}
|
|
|
|
int CalChannels(const ShapeVector &dims, int channel_cnt, bool *channel_at_first) {
|
|
auto channels = dims[0];
|
|
if (!(*channel_at_first)) {
|
|
if (dims.size() != 2) {
|
|
MS_LOG(WARNING) << "unexpected dims size: " << dims.size();
|
|
*channel_at_first = true;
|
|
} else {
|
|
channels = dims[1];
|
|
}
|
|
} else {
|
|
channels = channel_cnt == -1 ? channels : channel_cnt;
|
|
}
|
|
return channels;
|
|
}
|
|
|
|
void CalQuantAssitInfo(const PrimitivePtr &primitive, const ShapeVector &shapes, int index, bool *channel_at_first,
|
|
int *channel_cnt) {
|
|
if (primitive->name() == ops::kNameMatMul && static_cast<int>(shapes.size()) == 2) {
|
|
auto matmul_prim = primitive->cast<std::shared_ptr<ops::MatMul>>();
|
|
MS_ASSERT(matmul_prim != nullptr);
|
|
*channel_at_first =
|
|
index != 1 || (matmul_prim->GetAttr(ops::kTransposeB) != nullptr && matmul_prim->get_transpose_b());
|
|
} else if (primitive->name() == ops::kNameLSTM) {
|
|
if (index == 1 || index == 2) {
|
|
if (shapes.size() != 3) {
|
|
MS_LOG(WARNING) << "unexpected lstm shape size: " << shapes.size();
|
|
} else {
|
|
*channel_cnt = shapes[0] * shapes[1];
|
|
}
|
|
} else if (index == 3) {
|
|
if (shapes.size() != 2) {
|
|
MS_LOG(WARNING) << "unexpected lstm shape size: " << shapes.size();
|
|
} else {
|
|
auto tensor_elem_cnt = shapes[0] * shapes[1];
|
|
if (tensor_elem_cnt / 4 * 4 == tensor_elem_cnt) {
|
|
*channel_cnt = 4;
|
|
}
|
|
}
|
|
} else {
|
|
MS_LOG(WARNING) << "unexpected index of lstm: " << index;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CalQuantAssitInfo(const schema::PrimitiveT &primitive, const std::vector<int> &shapes, int index,
|
|
bool *channel_at_first, int *channel_cnt) {
|
|
if (primitive.value.type == schema::PrimitiveType_MatMul && static_cast<int>(shapes.size()) == 2) {
|
|
auto matmul_prim = primitive.value.AsMatMul();
|
|
MS_ASSERT(matmul_prim != nullptr);
|
|
*channel_at_first = index != 1 || matmul_prim->transpose_b;
|
|
} else if (primitive.value.type == schema::PrimitiveType_LSTM) {
|
|
if (index == 1 || index == 2) {
|
|
if (shapes.size() != 3) {
|
|
MS_LOG(WARNING) << "unexpected lstm shape size: " << shapes.size();
|
|
} else {
|
|
*channel_cnt = shapes[0] * shapes[1];
|
|
}
|
|
} else if (index == 3) {
|
|
if (shapes.size() != 2) {
|
|
MS_LOG(WARNING) << "unexpected lstm shape size: " << shapes.size();
|
|
} else {
|
|
auto tensor_elem_cnt = shapes[0] * shapes[1];
|
|
if (tensor_elem_cnt / 4 * 4 == tensor_elem_cnt) {
|
|
*channel_cnt = 4;
|
|
}
|
|
}
|
|
} else {
|
|
MS_LOG(WARNING) << "unexpected index of lstm: " << index;
|
|
}
|
|
}
|
|
}
|
|
} // namespace mindspore::lite::quant
|