mirror of
https://github.com/saymrwulf/pytorch.git
synced 2026-05-14 20:57:59 +00:00
This reverts commit 716b3b893d.
Reverted https://github.com/pytorch/pytorch/pull/103725 on behalf of https://github.com/osalpekar due to Broke caffe2 builds due. More info at [D46920675](https://www.internalfb.com/diff/D46920675) ([comment](https://github.com/pytorch/pytorch/pull/103725#issuecomment-1603129273))
898 lines
28 KiB
C++
898 lines
28 KiB
C++
#include "caffe2/quantization/server/activation_distribution_observer.h"
|
|
#include "caffe2/quantization/server/caffe2_dnnlowp_utils.h"
|
|
#include "caffe2/quantization/server/dnnlowp.h"
|
|
|
|
namespace caffe2 {
|
|
|
|
using namespace std;
|
|
using namespace dnnlowp;
|
|
|
|
OutputMinMaxObserver::OutputMinMaxObserver(OperatorBase* op)
|
|
: ObserverBase<OperatorBase>(op), info_(make_shared<OperatorInfo>()) {
|
|
for (int i = 0; i < op->OutputSize(); ++i) {
|
|
info_->tensor_infos.emplace_back(op->debug_def().output(i));
|
|
info_->type = op->debug_def().type();
|
|
}
|
|
}
|
|
|
|
// A global table that collects min/max for each tensor name.
|
|
// Useful in case there are multiple copies of the same network.
|
|
static map<string, pair<float, float>> min_max_map_;
|
|
|
|
// NOLINTNEXTLINE(modernize-use-equals-default)
|
|
OutputMinMaxObserver::~OutputMinMaxObserver() {
|
|
/*#pragma omp critical
|
|
{
|
|
for (int i = 0; i < info_->tensor_infos.size(); ++i) {
|
|
LOG(INFO) <<
|
|
this << " " << info_->type << " " << i << " " <<
|
|
info_->tensor_infos[i].name << " " <<
|
|
info_->tensor_infos[i].min << " " <<
|
|
info_->tensor_infos[i].max << " " <<
|
|
min_max_map_[info_->tensor_infos[i].name].first << " " <<
|
|
min_max_map_[info_->tensor_infos[i].name].second << " ";
|
|
}
|
|
}*/
|
|
}
|
|
|
|
template <typename T>
|
|
void FindMinMax(const T* data, float* min, float* max, int len) {
|
|
vector<float> temp(len);
|
|
for (int i = 0; i < len; ++i) {
|
|
temp[i] = data[i];
|
|
}
|
|
fbgemm::FindMinMax(temp.data(), min, max, len);
|
|
}
|
|
|
|
float* GetFloatTensorData(TensorCPU* tensor) {
|
|
float* data = nullptr;
|
|
vector<float> data_temp;
|
|
if (tensor->IsType<float>()) {
|
|
if (!tensor->data<float>()) {
|
|
return nullptr;
|
|
}
|
|
data = tensor->template data<float>();
|
|
} else if (tensor->IsType<int>()) {
|
|
if (!tensor->data<int>()) {
|
|
return nullptr;
|
|
}
|
|
const int* data_orig = tensor->data<int>();
|
|
data_temp.resize(tensor->numel());
|
|
for (int j = 0; j < tensor->numel(); ++j) {
|
|
data_temp[j] = data_orig[j];
|
|
}
|
|
data = data_temp.data();
|
|
} else if (tensor->IsType<long>()) {
|
|
if (!tensor->data<long>()) {
|
|
return nullptr;
|
|
}
|
|
const long* data_orig = tensor->data<long>();
|
|
data_temp.resize(tensor->numel());
|
|
for (int j = 0; j < tensor->numel(); ++j) {
|
|
data_temp[j] = data_orig[j];
|
|
}
|
|
data = data_temp.data();
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
template <>
|
|
void FindMinMax<float>(const float* data, float* min, float* max, int len) {
|
|
fbgemm::FindMinMax(data, min, max, len);
|
|
}
|
|
|
|
void OutputMinMaxObserver::Stop() {
|
|
for (int i = 0; i < subject_->OutputSize(); ++i) {
|
|
if (!subject_->OutputIsTensorType(i, CPU)) {
|
|
continue;
|
|
}
|
|
Tensor* tensor = subject_->template Output<Tensor>(i, CPU);
|
|
if (!tensor || tensor->numel() == 0 || tensor->numel() == -1)
|
|
continue;
|
|
string out_name(subject_->debug_def().output(i));
|
|
|
|
float min = numeric_limits<float>::lowest(),
|
|
max = numeric_limits<float>::max();
|
|
|
|
if (tensor->IsType<float>()) {
|
|
if (!tensor->data<float>()) {
|
|
continue;
|
|
}
|
|
FindMinMax(tensor->data<float>(), &min, &max, tensor->numel());
|
|
} else if (tensor->IsType<int>()) {
|
|
if (!tensor->data<int>()) {
|
|
continue;
|
|
}
|
|
FindMinMax(tensor->data<int>(), &min, &max, tensor->numel());
|
|
} else if (tensor->IsType<long>()) {
|
|
if (!tensor->data<long>()) {
|
|
continue;
|
|
}
|
|
FindMinMax(tensor->data<long>(), &min, &max, tensor->numel());
|
|
} else {
|
|
if (!warning_printed_) {
|
|
LOG(INFO) << "Tensor " << out_name << " has unsupported type "
|
|
<< tensor->meta().name() << " with size " << tensor->numel();
|
|
warning_printed_ = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
#ifdef _OPENMP
|
|
#pragma omp critical
|
|
#endif
|
|
{
|
|
if (min_max_map_.find(out_name) == min_max_map_.end()) {
|
|
min_max_map_[out_name] = make_pair(
|
|
numeric_limits<float>::max(), numeric_limits<float>::lowest());
|
|
}
|
|
|
|
info_->tensor_infos[i].Update(min, max);
|
|
|
|
min_max_map_[out_name].first =
|
|
std::min(min_max_map_[out_name].first, min);
|
|
min_max_map_[out_name].second =
|
|
std::max(min_max_map_[out_name].second, max);
|
|
assert(min_max_map_[out_name].second >= min_max_map_[out_name].first);
|
|
assert(min_max_map_[out_name].first < 1e38);
|
|
|
|
VLOG(2) << this << " " << info_->type << " " << i << " " << out_name
|
|
<< " " << info_->tensor_infos[i].min << " "
|
|
<< info_->tensor_infos[i].max << " "
|
|
<< min_max_map_[out_name].first << " "
|
|
<< min_max_map_[out_name].second;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
OutputMinMaxNetObserver::OutputMinMaxNetObserver(
|
|
NetBase* subject,
|
|
const string& out_file_name,
|
|
int dump_freq,
|
|
// NOLINTNEXTLINE(modernize-pass-by-value)
|
|
string delimiter)
|
|
: NetObserver(subject),
|
|
dump_freq_(dump_freq),
|
|
cnt_(0),
|
|
out_file_name_(out_file_name),
|
|
delimiter_(delimiter) {
|
|
VLOG(2) << out_file_name;
|
|
min_max_infos_.resize(subject->GetOperators().size());
|
|
int i = 0;
|
|
for (auto* op : subject->GetOperators()) {
|
|
OutputMinMaxObserver* observer = new OutputMinMaxObserver(op);
|
|
op->AttachObserver(std::unique_ptr<OutputMinMaxObserver>(observer));
|
|
min_max_infos_[i] = observer->GetInfo();
|
|
++i;
|
|
}
|
|
}
|
|
|
|
void OutputMinMaxNetObserver::DumpAndReset_(
|
|
const std::string& out_file_name,
|
|
bool print_total_min_max) {
|
|
ofstream f(out_file_name);
|
|
if (!f) {
|
|
LOG(WARNING) << this << ": can't open " << out_file_name;
|
|
}
|
|
|
|
// NOLINTNEXTLINE(clang-diagnostic-sign-compare)
|
|
for (int op_index = 0; op_index < min_max_infos_.size(); ++op_index) {
|
|
OutputMinMaxObserver::OperatorInfo* op_info =
|
|
min_max_infos_[op_index].get();
|
|
if (op_info) {
|
|
// NOLINTNEXTLINE(clang-diagnostic-sign-compare)
|
|
for (int i = 0; i < op_info->tensor_infos.size(); ++i) {
|
|
const OutputMinMaxObserver::TensorInfo& tensor_info =
|
|
op_info->tensor_infos[i];
|
|
|
|
ostringstream ost;
|
|
ost << op_index << delimiter_ << op_info->type << delimiter_ << i
|
|
<< delimiter_ << tensor_info.name << delimiter_;
|
|
if (print_total_min_max) {
|
|
ost << tensor_info.total_min << delimiter_ << tensor_info.total_max;
|
|
} else {
|
|
ost << tensor_info.min << delimiter_ << tensor_info.max;
|
|
}
|
|
|
|
LOG(INFO) << this << delimiter_ << ost.str();
|
|
f << ost.str() << endl;
|
|
|
|
op_info->tensor_infos[i].min = numeric_limits<float>::max();
|
|
op_info->tensor_infos[i].max = numeric_limits<float>::lowest();
|
|
}
|
|
}
|
|
}
|
|
f.close();
|
|
}
|
|
|
|
// NOLINTNEXTLINE(bugprone-exception-escape)
|
|
OutputMinMaxNetObserver::~OutputMinMaxNetObserver() {
|
|
DumpAndReset_(out_file_name_, true);
|
|
|
|
#ifdef _OPENMP
|
|
#pragma omp critical
|
|
#endif
|
|
{
|
|
ofstream f;
|
|
// NOLINTNEXTLINE(cppcoreguidelines-init-variables)
|
|
time_t rawtime;
|
|
time(&rawtime);
|
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
|
|
struct tm timeinfo;
|
|
localtime_r(&rawtime, &timeinfo);
|
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,cppcoreguidelines-avoid-magic-numbers,modernize-avoid-c-arrays)
|
|
char buffer[128] = {};
|
|
strftime(buffer, sizeof(buffer), "%Y-%m-%d-%H-%M-%S", &timeinfo);
|
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,cppcoreguidelines-avoid-magic-numbers,modernize-avoid-c-arrays)
|
|
char buffer2[256] = {};
|
|
snprintf(buffer2, sizeof(buffer2), "global_%s.minmax", buffer);
|
|
|
|
f.open(buffer2);
|
|
int op_index = 0;
|
|
// NOLINTNEXTLINE(performance-for-range-copy)
|
|
for (auto key_value : min_max_map_) {
|
|
ostringstream ost;
|
|
assert(key_value.second.first <= key_value.second.second);
|
|
assert(key_value.second.first < 1e38);
|
|
ost << op_index << " 0 " << key_value.first << " "
|
|
<< key_value.second.first << " " << key_value.second.second;
|
|
f << ost.str() << endl;
|
|
|
|
++op_index;
|
|
}
|
|
f.close();
|
|
}
|
|
}
|
|
|
|
void OutputMinMaxNetObserver::Stop() {
|
|
++cnt_;
|
|
if (dump_freq_ == -1 || (cnt_ % dump_freq_) != 0) {
|
|
return;
|
|
}
|
|
|
|
ostringstream ost;
|
|
size_t last_dot = out_file_name_.rfind('.');
|
|
size_t last_slash = out_file_name_.rfind('/');
|
|
if (last_dot != string::npos &&
|
|
(last_slash == string::npos || last_slash < last_dot)) {
|
|
ost << out_file_name_.substr(0, last_dot) << "_" << cnt_ / dump_freq_
|
|
<< out_file_name_.substr(last_dot);
|
|
} else {
|
|
ost << out_file_name_ << "_" << cnt_ / dump_freq_;
|
|
}
|
|
|
|
DumpAndReset_(ost.str());
|
|
return;
|
|
}
|
|
|
|
// NOLINTNEXTLINE(modernize-pass-by-value)
|
|
HistogramObserver::HistogramObserver(OperatorBase* op, shared_ptr<Info> info)
|
|
: ObserverBase<OperatorBase>(op), info_(info) {}
|
|
|
|
void HistogramObserver::Stop() {
|
|
for (int i = 0; i < subject_->OutputSize(); ++i) {
|
|
if (!subject_->OutputIsTensorType(i, CPU)) {
|
|
continue;
|
|
}
|
|
Tensor* tensor = subject_->template Output<Tensor>(i, CPU);
|
|
if (!tensor || tensor->numel() == 0 || tensor->numel() == -1) {
|
|
continue;
|
|
}
|
|
|
|
string out_name(subject_->debug_def().output(i));
|
|
|
|
const float* data = nullptr;
|
|
vector<float> data_temp;
|
|
|
|
if (tensor->IsType<float>()) {
|
|
if (!tensor->data<float>()) {
|
|
continue;
|
|
}
|
|
data = tensor->template data<float>();
|
|
} else if (tensor->IsType<int>()) {
|
|
if (!tensor->data<int>()) {
|
|
continue;
|
|
}
|
|
const int* data_orig = tensor->data<int>();
|
|
data_temp.resize(tensor->numel());
|
|
for (int j = 0; j < tensor->numel(); ++j) {
|
|
data_temp[j] = data_orig[j];
|
|
}
|
|
data = data_temp.data();
|
|
} else if (tensor->IsType<long>()) {
|
|
if (!tensor->data<long>()) {
|
|
continue;
|
|
}
|
|
const long* data_orig = tensor->data<long>();
|
|
data_temp.resize(tensor->numel());
|
|
for (int j = 0; j < tensor->numel(); ++j) {
|
|
data_temp[j] = data_orig[j];
|
|
}
|
|
data = data_temp.data();
|
|
} else {
|
|
if (!warning_printed_) {
|
|
LOG(INFO) << "Tensor " << out_name << " has unsupported type "
|
|
<< tensor->meta().name() << " with size " << tensor->numel();
|
|
warning_printed_ = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
info_->histograms[i].Add(data, tensor->numel());
|
|
info_->total_histograms[i].Add(data, tensor->numel());
|
|
}
|
|
return;
|
|
}
|
|
|
|
OutputColumnMaxHistogramObserver::OutputColumnMaxHistogramObserver(
|
|
OperatorBase* op,
|
|
const std::string& col_max_blob_name,
|
|
int nbins,
|
|
// NOLINTNEXTLINE(modernize-pass-by-value)
|
|
std::shared_ptr<HistogramObserver::Info> info)
|
|
: ObserverBase<OperatorBase>(op),
|
|
col_max_blob_name_(col_max_blob_name),
|
|
nbins_(nbins),
|
|
info_(info) {
|
|
const auto& output_names = op->debug_def().output();
|
|
auto it =
|
|
std::find(output_names.begin(), output_names.end(), col_max_blob_name);
|
|
CAFFE_ENFORCE(
|
|
it != output_names.end(), "Cannot find blob in operator output.");
|
|
col_max_blob_idx_ = std::distance(output_names.begin(), it);
|
|
};
|
|
|
|
void OutputColumnMaxHistogramObserver::Stop() {
|
|
if (!subject_->OutputIsTensorType(col_max_blob_idx_, CPU)) {
|
|
return;
|
|
}
|
|
Tensor* tensor = subject_->template Output<Tensor>(col_max_blob_idx_, CPU);
|
|
if (!tensor || tensor->numel() == 0 || tensor->numel() == -1) {
|
|
return;
|
|
}
|
|
|
|
float* data = GetFloatTensorData(tensor);
|
|
if (data == nullptr && !warning_printed_) {
|
|
LOG(INFO) << "Tensor " << col_max_blob_name_
|
|
<< " has mismatching type, or unsupported type "
|
|
<< tensor->meta().name() << " with size " << tensor->numel();
|
|
warning_printed_ = true;
|
|
return;
|
|
}
|
|
|
|
// determine number of columns
|
|
CAFFE_ENFORCE(
|
|
tensor->dim() == 2,
|
|
"Tensor " + col_max_blob_name_ +
|
|
" is not two-dimensional. Tensor.dim() = " +
|
|
caffe2::to_string(tensor->dim()));
|
|
int num_columns = tensor->size_from_dim(1);
|
|
if (num_columns_ == -1) {
|
|
num_columns_ = num_columns;
|
|
}
|
|
CAFFE_ENFORCE(
|
|
num_columns_ == num_columns, "Observed inconsistent number of columns.");
|
|
int num_rows = tensor->size_to_dim(1);
|
|
for (int col = 0; col < num_columns; col++) {
|
|
// find col max of the ith column
|
|
auto col_max = std::abs(data[col]);
|
|
for (int r = 0; r < num_rows; r++) {
|
|
int idx = r * num_columns + col;
|
|
col_max = max(col_max, std::abs(data[idx]));
|
|
}
|
|
// NOLINTNEXTLINE(clang-diagnostic-sign-compare)
|
|
if (info_->histograms.size() <= col) {
|
|
info_->histograms.emplace_back(nbins_);
|
|
info_->total_histograms.emplace_back(nbins_);
|
|
info_->min_max_info.tensor_infos.emplace_back(col_max_blob_name_);
|
|
}
|
|
info_->histograms[col].Add(col_max);
|
|
info_->total_histograms[col].Add(col_max);
|
|
}
|
|
}
|
|
|
|
HistogramNetObserver::HistogramNetObserver(
|
|
NetBase* subject,
|
|
// NOLINTNEXTLINE(modernize-pass-by-value)
|
|
const string& out_file_name,
|
|
int nbins,
|
|
int dump_freq,
|
|
bool mul_nets,
|
|
string op_filter,
|
|
// NOLINTNEXTLINE(modernize-pass-by-value)
|
|
string delimiter)
|
|
: NetObserver(subject),
|
|
dump_freq_(dump_freq),
|
|
cnt_(0),
|
|
mul_nets_(mul_nets),
|
|
op_filter_(op_filter),
|
|
delimiter_(delimiter),
|
|
out_file_name_(out_file_name) {
|
|
net_name_ = subject->Name();
|
|
if (op_filter != "") {
|
|
bool has_op = false;
|
|
for (auto* op : subject->GetOperators()) {
|
|
if (op->debug_def().type() == op_filter) {
|
|
has_op = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!has_op) {
|
|
LOG(INFO) << "Net " << net_name_ << " doesn't include operator "
|
|
<< op_filter;
|
|
return;
|
|
}
|
|
}
|
|
|
|
hist_infos_.resize(subject->GetOperators().size());
|
|
|
|
int i = 0;
|
|
for (auto* op : subject->GetOperators()) {
|
|
shared_ptr<HistogramObserver::Info> info(new HistogramObserver::Info);
|
|
info->min_max_info.type = op->debug_def().type();
|
|
|
|
for (int j = 0; j < op->OutputSize(); ++j) {
|
|
info->histograms.emplace_back(nbins);
|
|
info->total_histograms.emplace_back(nbins);
|
|
info->min_max_info.tensor_infos.emplace_back(op->debug_def().output(j));
|
|
}
|
|
|
|
HistogramObserver* observer = new HistogramObserver(op, info);
|
|
op->AttachObserver(unique_ptr<HistogramObserver>(observer));
|
|
hist_infos_[i] = info;
|
|
++i;
|
|
}
|
|
}
|
|
|
|
void HistogramNetObserver::DumpAndReset_(
|
|
const string& out_file_name,
|
|
bool print_total_min_max) {
|
|
if (hist_infos_.size() == 0) {
|
|
return;
|
|
}
|
|
stringstream file_name;
|
|
file_name << out_file_name;
|
|
LOG(INFO) << "Dumping histograms of net " << net_name_ << " in " << this;
|
|
if (mul_nets_) {
|
|
file_name << ".";
|
|
file_name << this;
|
|
}
|
|
ofstream f(file_name.str());
|
|
if (!f) {
|
|
LOG(WARNING) << this << ": can't open " << file_name.str();
|
|
}
|
|
|
|
// NOLINTNEXTLINE(clang-diagnostic-sign-compare)
|
|
for (int op_index = 0; op_index < hist_infos_.size(); ++op_index) {
|
|
HistogramObserver::Info* info = hist_infos_[op_index].get();
|
|
if (!info) {
|
|
continue;
|
|
}
|
|
|
|
// NOLINTNEXTLINE(clang-diagnostic-sign-compare)
|
|
for (int i = 0; i < info->histograms.size(); ++i) {
|
|
const Histogram* hist =
|
|
(print_total_min_max ? info->total_histograms : info->histograms)[i]
|
|
.Finalize();
|
|
if (hist->Min() >= hist->Max()) {
|
|
LOG(WARNING) << "Histogram of "
|
|
<< info->min_max_info.tensor_infos[i].name
|
|
<< " has an empty range: min " << hist->Min()
|
|
<< " and max " << hist->Max();
|
|
}
|
|
if (hist->GetHistogram()->empty()) {
|
|
LOG(WARNING) << "Histogram of "
|
|
<< info->min_max_info.tensor_infos[i].name << " is empty";
|
|
}
|
|
|
|
ostringstream ost;
|
|
ost << op_index << delimiter_ << info->min_max_info.type << delimiter_
|
|
<< i << delimiter_ << info->min_max_info.tensor_infos[i].name
|
|
<< delimiter_ << hist->Min() << delimiter_ << hist->Max()
|
|
<< delimiter_ << hist->GetHistogram()->size();
|
|
|
|
for (uint64_t c : *hist->GetHistogram()) {
|
|
ost << delimiter_ << c;
|
|
}
|
|
|
|
if (print_total_min_max) {
|
|
LOG(INFO) << this << delimiter_ << ost.str();
|
|
}
|
|
|
|
f << ost.str() << endl;
|
|
|
|
if (!print_total_min_max) {
|
|
info->histograms[i] = DynamicHistogram(hist->GetHistogram()->size());
|
|
}
|
|
}
|
|
}
|
|
f.flush();
|
|
f.close();
|
|
}
|
|
|
|
// NOLINTNEXTLINE(bugprone-exception-escape)
|
|
HistogramNetObserver::~HistogramNetObserver() {
|
|
DumpAndReset_(out_file_name_, false);
|
|
}
|
|
|
|
void HistogramNetObserver::Stop() {
|
|
++cnt_;
|
|
if (dump_freq_ == -1 || (cnt_ % dump_freq_) != 0) {
|
|
return;
|
|
}
|
|
|
|
ostringstream ost;
|
|
size_t last_dot = out_file_name_.rfind('.');
|
|
size_t last_slash = out_file_name_.rfind('/');
|
|
if (last_dot != string::npos &&
|
|
(last_slash == string::npos || last_slash < last_dot)) {
|
|
ost << out_file_name_.substr(0, last_dot) << "_" << cnt_ / dump_freq_
|
|
<< out_file_name_.substr(last_dot);
|
|
} else {
|
|
ost << out_file_name_ << "_" << cnt_ / dump_freq_;
|
|
}
|
|
|
|
DumpAndReset_(ost.str());
|
|
return;
|
|
}
|
|
|
|
static bool HasDNNLowPEngine_(const OperatorDef& op_def) {
|
|
const string ENGINE_PREFIX = "DNNLOWP";
|
|
return strncmp(
|
|
op_def.engine().c_str(),
|
|
ENGINE_PREFIX.c_str(),
|
|
ENGINE_PREFIX.size()) == 0;
|
|
}
|
|
|
|
static bool HasDNNLowPEngine_(const OperatorBase& op) {
|
|
return HasDNNLowPEngine_(op.debug_def());
|
|
}
|
|
|
|
OutputColumnMaxHistogramNetObserver::OutputColumnMaxHistogramNetObserver(
|
|
NetBase* subject,
|
|
// NOLINTNEXTLINE(modernize-pass-by-value)
|
|
const std::string& out_file_name,
|
|
const std::vector<std::string>& observe_column_max_for_blobs,
|
|
int nbins,
|
|
int dump_freq,
|
|
bool mul_nets,
|
|
// NOLINTNEXTLINE(modernize-pass-by-value)
|
|
string delimiter)
|
|
: NetObserver(subject),
|
|
dump_freq_(dump_freq),
|
|
cnt_(0),
|
|
mul_nets_(mul_nets),
|
|
out_file_name_(out_file_name),
|
|
delimiter_(delimiter) {
|
|
if (observe_column_max_for_blobs.size() == 0) {
|
|
return;
|
|
}
|
|
col_max_blob_names_.insert(
|
|
observe_column_max_for_blobs.begin(), observe_column_max_for_blobs.end());
|
|
int op_idx = 0;
|
|
for (auto* op : subject->GetOperators()) {
|
|
const auto& op_output_names = op->debug_def().output();
|
|
int output_idx = 0;
|
|
std::unordered_map<int, std::shared_ptr<HistogramObserver::Info>>
|
|
output_col_hists_map;
|
|
for (const auto& output_blob : op_output_names) {
|
|
if (col_max_blob_names_.find(output_blob) == col_max_blob_names_.end()) {
|
|
++output_idx;
|
|
continue;
|
|
}
|
|
/// create col max hist observer for blob
|
|
auto info = std::make_shared<HistogramObserver::Info>();
|
|
info->min_max_info.type = op->debug_def().type();
|
|
// number of histograms in info will be determined at runtime by the
|
|
// number of columns in the tensor.
|
|
OutputColumnMaxHistogramObserver* observer =
|
|
new OutputColumnMaxHistogramObserver(op, output_blob, nbins, info);
|
|
op->AttachObserver(
|
|
unique_ptr<OutputColumnMaxHistogramObserver>(observer));
|
|
output_col_hists_map[output_idx] = info;
|
|
++output_idx;
|
|
}
|
|
if (output_col_hists_map.size() > 0) {
|
|
hist_infos_[op_idx] = output_col_hists_map;
|
|
}
|
|
++op_idx;
|
|
}
|
|
}
|
|
|
|
void OutputColumnMaxHistogramNetObserver::DumpAndReset_(
|
|
const std::string& out_file_name,
|
|
bool print_total_min_max) {
|
|
stringstream file_name;
|
|
file_name << out_file_name;
|
|
if (mul_nets_) {
|
|
file_name << ".";
|
|
file_name << this;
|
|
}
|
|
ofstream f(file_name.str());
|
|
if (!f) {
|
|
LOG(WARNING) << this << ": can't open " << file_name.str();
|
|
}
|
|
for (const auto& it : hist_infos_) {
|
|
auto output_idx_hists_map = it.second;
|
|
for (const auto& output_idx_hist : output_idx_hists_map) {
|
|
int output_idx = output_idx_hist.first;
|
|
HistogramObserver::Info* info = output_idx_hist.second.get();
|
|
if (!info) {
|
|
continue;
|
|
}
|
|
// NOLINTNEXTLINE(clang-diagnostic-sign-compare)
|
|
for (int i = 0; i < info->histograms.size(); ++i) {
|
|
const Histogram* hist =
|
|
(print_total_min_max ? info->total_histograms : info->histograms)[i]
|
|
.Finalize();
|
|
if (hist->Min() >= hist->Max()) {
|
|
LOG(WARNING) << "Histogram of "
|
|
<< info->min_max_info.tensor_infos[i].name
|
|
<< " has an empty range: min " << hist->Min()
|
|
<< " and max " << hist->Max();
|
|
}
|
|
if (hist->GetHistogram()->empty()) {
|
|
LOG(WARNING) << "Histogram of "
|
|
<< info->min_max_info.tensor_infos[i].name
|
|
<< " is empty";
|
|
}
|
|
ostringstream ost;
|
|
// op_idx, output_idx, blob_name, col, min, max, nbins
|
|
ost << it.first << delimiter_ << output_idx << delimiter_
|
|
<< info->min_max_info.tensor_infos[i].name << delimiter_ << i
|
|
<< delimiter_ << hist->Min() << delimiter_ << hist->Max()
|
|
<< delimiter_ << hist->GetHistogram()->size();
|
|
|
|
// bins
|
|
for (uint64_t c : *hist->GetHistogram()) {
|
|
ost << delimiter_ << c;
|
|
}
|
|
if (print_total_min_max) {
|
|
LOG(INFO) << this << delimiter_ << ost.str();
|
|
}
|
|
f << ost.str() << endl;
|
|
if (!print_total_min_max) {
|
|
info->histograms[i] = DynamicHistogram(hist->GetHistogram()->size());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
f.close();
|
|
}
|
|
|
|
void OutputColumnMaxHistogramNetObserver::Stop() {
|
|
++cnt_;
|
|
if (dump_freq_ == -1 || (cnt_ % dump_freq_) != 0) {
|
|
return;
|
|
}
|
|
ostringstream ost;
|
|
size_t last_dot = out_file_name_.rfind('.');
|
|
size_t last_slash = out_file_name_.rfind('/');
|
|
if (last_dot != string::npos &&
|
|
(last_slash == string::npos || last_slash < last_dot)) {
|
|
ost << out_file_name_.substr(0, last_dot) << "_" << cnt_ / dump_freq_
|
|
<< out_file_name_.substr(last_dot);
|
|
} else {
|
|
ost << out_file_name_ << "_" << cnt_ / dump_freq_;
|
|
}
|
|
DumpAndReset_(ost.str());
|
|
return;
|
|
}
|
|
|
|
// NOLINTNEXTLINE(bugprone-exception-escape)
|
|
OutputColumnMaxHistogramNetObserver::~OutputColumnMaxHistogramNetObserver() {
|
|
DumpAndReset_(out_file_name_, true);
|
|
}
|
|
|
|
RegisterQuantizationParamsNetObserver::RegisterQuantizationParamsNetObserver(
|
|
NetBase* subject,
|
|
const string& min_max_file_name,
|
|
bool is_weight,
|
|
const string& qparams_output_file_name)
|
|
: NetObserver(subject) {
|
|
ifstream f(min_max_file_name);
|
|
|
|
// check the format by looking at the first line
|
|
string first_line, word;
|
|
getline(f, first_line);
|
|
f.seekg(0, f.beg);
|
|
istringstream ist(first_line);
|
|
int nwords_first_line = 0;
|
|
while (ist >> word) {
|
|
++nwords_first_line;
|
|
}
|
|
|
|
bool new_format = nwords_first_line == 6;
|
|
if (!new_format && nwords_first_line != 5) {
|
|
LOG(WARNING) << "min_max file " << min_max_file_name
|
|
<< " has an invalid format";
|
|
}
|
|
|
|
// Optionally dump quantization params to file
|
|
ofstream fout;
|
|
if (!qparams_output_file_name.empty()) {
|
|
fout.open(qparams_output_file_name);
|
|
if (!fout) {
|
|
LOG(WARNING) << this << ": can't open " << qparams_output_file_name;
|
|
}
|
|
}
|
|
|
|
// parse the input file
|
|
int op_index = 0;
|
|
for (auto* op : subject->GetOperators()) {
|
|
for (int i = 0; i < op->OutputSize(); ++i) {
|
|
// NOLINTNEXTLINE(cppcoreguidelines-init-variables)
|
|
int op_index2, i2;
|
|
string op_type, tensor_name;
|
|
// NOLINTNEXTLINE(cppcoreguidelines-init-variables)
|
|
float min, max;
|
|
|
|
if (new_format) {
|
|
f >> op_index2 >> op_type >> i2 >> tensor_name >> min >> max;
|
|
} else {
|
|
f >> op_index2 >> i2 >> tensor_name >> min >> max;
|
|
}
|
|
assert(op_index2 == op_index);
|
|
assert(i2 == i);
|
|
assert(tensor_name == op->debug_def().output(i));
|
|
|
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
|
|
TensorQuantizationParams qparams;
|
|
if (max > min) {
|
|
unique_ptr<QuantizationFactory> qfactory(GetQuantizationFactoryOf(op));
|
|
qparams = qfactory->ChooseQuantizationParams(min, max, is_weight);
|
|
} else {
|
|
qparams.scale = 0.1f;
|
|
qparams.zero_point = -min / qparams.scale;
|
|
qparams.precision = 8;
|
|
}
|
|
|
|
if (HasDNNLowPEngine_(*op)) {
|
|
SetStaticQuantizationParams(op, i, qparams);
|
|
}
|
|
|
|
if (fout.is_open()) {
|
|
fout << op_index << " " << op_type << " " << i << " " << tensor_name
|
|
<< " " << qparams.Min() << " " << qparams.Max() << " "
|
|
<< qparams.scale << " " << qparams.zero_point << " "
|
|
<< qparams.precision << endl;
|
|
}
|
|
}
|
|
++op_index;
|
|
}
|
|
|
|
if (fout.is_open()) {
|
|
fout.close();
|
|
}
|
|
}
|
|
|
|
RegisterQuantizationParamsWithHistogramNetObserver::
|
|
RegisterQuantizationParamsWithHistogramNetObserver(
|
|
NetBase* subject,
|
|
const string& histogram_file_name,
|
|
bool is_weight,
|
|
const string& qparams_output_file_name)
|
|
: NetObserver(subject) {
|
|
ifstream f(histogram_file_name);
|
|
|
|
// check the format by looking at the first line
|
|
string first_line, word;
|
|
getline(f, first_line);
|
|
f.seekg(0, f.beg);
|
|
istringstream ist(first_line);
|
|
int nwords_first_line = 0;
|
|
while (ist >> word) {
|
|
++nwords_first_line;
|
|
}
|
|
|
|
ist.str(first_line);
|
|
ist.clear();
|
|
|
|
bool new_format = true;
|
|
// NOLINTNEXTLINE(cppcoreguidelines-init-variables)
|
|
int op_index, i, nbins;
|
|
string op_type, tensor_name;
|
|
// NOLINTNEXTLINE(cppcoreguidelines-init-variables)
|
|
float min, max;
|
|
ist >> op_index >> op_type >> i >> tensor_name >> min >> max >> nbins;
|
|
if (nwords_first_line != nbins + 7) {
|
|
ist.str(first_line);
|
|
ist.clear();
|
|
ist >> op_index >> i >> tensor_name >> min >> max >> nbins;
|
|
if (nwords_first_line == nbins + 6) {
|
|
new_format = false;
|
|
} else {
|
|
LOG(WARNING) << "histogram file " << histogram_file_name
|
|
<< " has an invalid format";
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Optionally dump quantization params to file
|
|
ofstream fout;
|
|
if (!qparams_output_file_name.empty()) {
|
|
fout.open(qparams_output_file_name);
|
|
if (!fout) {
|
|
LOG(WARNING) << this << ": can't open " << qparams_output_file_name;
|
|
}
|
|
}
|
|
|
|
// parse the input file
|
|
op_index = 0;
|
|
for (auto* op : subject->GetOperators()) {
|
|
for (i = 0; i < op->OutputSize(); ++i) {
|
|
// NOLINTNEXTLINE(cppcoreguidelines-init-variables)
|
|
int op_index2, i2;
|
|
|
|
if (new_format) {
|
|
f >> op_index2 >> op_type >> i2 >> tensor_name >> min >> max >> nbins;
|
|
} else {
|
|
f >> op_index2 >> i2 >> tensor_name >> min >> max >> nbins;
|
|
}
|
|
LOG_IF(WARNING, op_index2 != op_index)
|
|
<< "op index " << op_index2 << " doesn't match with " << op_index;
|
|
LOG_IF(WARNING, tensor_name != op->debug_def().output(i))
|
|
<< tensor_name << " in histogram file line " << op_index
|
|
<< " doesn't match with operation def " << op->debug_def().output(i);
|
|
LOG_IF(WARNING, i2 != i)
|
|
<< "output tensor index " << i2 << " doesn't match with " << i;
|
|
if (new_format) {
|
|
LOG_IF(WARNING, op_type != op->debug_def().type())
|
|
<< "operator type " << op_type << " in histogram file line "
|
|
<< op_index << " doesn't match with operation def "
|
|
<< op->debug_def().type();
|
|
}
|
|
|
|
vector<uint64_t> bins;
|
|
for (int j = 0; j < nbins; ++j) {
|
|
// NOLINTNEXTLINE(cppcoreguidelines-init-variables)
|
|
uint64_t cnt;
|
|
f >> cnt;
|
|
bins.push_back(cnt);
|
|
}
|
|
|
|
Histogram hist = Histogram(min, max, bins);
|
|
|
|
LOG(INFO) << "Choosing qparams for " << tensor_name;
|
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
|
|
TensorQuantizationParams qparams;
|
|
if (max > min) {
|
|
unique_ptr<QuantizationFactory> qfactory(GetQuantizationFactoryOf(op));
|
|
qparams = qfactory->ChooseQuantizationParams(hist, is_weight);
|
|
} else {
|
|
qparams.scale = 0.1f;
|
|
qparams.precision = 8;
|
|
qparams.zero_point =
|
|
(isinf(min / qparams.scale) || isnan(min / qparams.scale))
|
|
? 0
|
|
: std::max(
|
|
0,
|
|
std::min(
|
|
int((-min) / qparams.scale),
|
|
(1 << qparams.precision) - 1));
|
|
}
|
|
|
|
if (HasDNNLowPEngine_(*op)) {
|
|
SetStaticQuantizationParams(op, i, qparams);
|
|
}
|
|
|
|
if (fout.is_open()) {
|
|
fout << op_index << " " << op_type << " " << i << " " << tensor_name
|
|
<< " " << qparams.Min() << " " << qparams.Max() << " "
|
|
<< qparams.scale << " " << qparams.zero_point << " "
|
|
<< qparams.precision << endl;
|
|
}
|
|
}
|
|
++op_index;
|
|
}
|
|
|
|
if (fout.is_open()) {
|
|
fout.close();
|
|
}
|
|
}
|
|
|
|
} // namespace caffe2
|