1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
|
#include "caffe2/core/logging.h"
#include "caffe2/utils/cpuid.h"
#include "l2_minimization.h"
#include <cassert>
#include <cmath>
#include <limits>
#include <immintrin.h>
#include <c10/util/irange.h>
using namespace std;
namespace dnnlowp {
#undef NDEBUG
// Use fp16_min as the small scale cutoff because we don't want to use scales in fp16 subnormal range.
// This is to be consistent with Glow and FakeLowP implementation for NNPI.
constexpr float SMALL_SCALE_THRESHOLD = 6.1e-5f;
static float
GetNorm(float begin, float end, float density, NormMinimization::Kind kind) {
float norm = 0;
// assume values are uniformly distributed within each histogram bin
if (NormMinimization::L2 == kind) {
// err = density * (integral_{begin, end} x^2)
// = density * (end^3 - begin^3) / 3
norm = (end * end * end - begin * begin * begin) / 3;
// for begin = d/2 and end = -d/2, this leads to d^3/12
} else {
// err = density * (integral_{begin, end} |x|)
// = density * (end^2 - begin^2) / 2
float left_begin = std::min(0.0f, begin);
float left_end = std::min(0.0f, end);
assert(left_begin * left_begin >= left_end * left_end);
norm += (left_begin * left_begin - left_end * left_end) / 2;
float right_begin = std::max(0.0f, begin);
float right_end = std::max(0.0f, end);
assert(right_end * right_end >= right_begin * right_begin);
norm += (right_end * right_end - right_begin * right_begin) / 2;
}
return density * norm;
}
// Filter out outliers in input distributions
// Exploit the input distributions for the quick search
TensorQuantizationParams NormMinimization::NonlinearQuantizationParamsSearch(
const Histogram& hist,
bool preserve_sparsity,
int precision) {
if (preserve_sparsity) {
VLOG(2) << "l2_approx with symmetric quantization falls back to L2";
return ChooseQuantizationParams(hist, preserve_sparsity, precision);
}
VLOG(2) << "Using the nonlinear quantile search";
// NOLINTNEXTLINE(cppcoreguidelines-init-variables)
float min, max;
vector<float> bins_f(dnnlowp::adjust_hist_to_include_zero(hist, &min, &max));
int nbins = bins_f.size();
// NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions,bugprone-narrowing-conversions)
float bin_width = (max - min) / nbins;
float scale = (max - min) / float((1 << precision) - 1);
if (bin_width == 0 || scale < SMALL_SCALE_THRESHOLD) {
QuantizationFactory* qfactory = QuantizationFactory::GetDefaultInstance();
return qfactory->ChooseQuantizationParams(
min, max, precision, preserve_sparsity);
}
int dst_nbins = 1 << precision;
float org_max = max;
float org_min = min;
// calculate the CDF
uint64_t total = 0;
for (uint64_t x : bins_f) {
total += x;
}
vector<uint64_t> CDF;
uint64_t sum = 0;
for (uint64_t x : bins_f) {
sum += x;
CDF.push_back(sum);
}
double stepsize = 0.00001; // experiment on the granularity
double alpha = 0.0f, beta = 1.0f; // lowerbound and upperbound
int start_bin = 0;
int end_bin = nbins - 1;
double norm_min = numeric_limits<double>::max();
while (alpha < beta) {
// find the next step
double next_alpha = alpha + stepsize;
double next_beta = beta - stepsize;
// find the left and right bins between the quantile bounds
int i = start_bin, j = end_bin;
while (i < end_bin && CDF[i] < next_alpha * total)
i++;
while (j > start_bin && CDF[j] > next_beta * total)
j--;
// decide the next move
// cout << i << ", " << j << endl;
int next_start_bin = start_bin, next_end_bin = end_bin;
if ((i - start_bin) > (end_bin - j)) {
// move the start_bin
next_start_bin = i;
alpha = next_alpha;
} else {
// move the end_bin
next_end_bin = j;
beta = next_beta;
}
if (next_start_bin == start_bin && next_end_bin == end_bin)
continue;
// calculate the norm
double norm = 0;
double dst_bin_width =
// NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions,bugprone-narrowing-conversions)
bin_width * (next_end_bin - next_start_bin + 1) / dst_nbins;
// go over each histogram bin and accumulate errors
for (int src_bin = 0; src_bin < nbins; ++src_bin) {
// distances from the beginning of first dst_bin to the beginning and
// end of src_bin
// NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions,bugprone-narrowing-conversions)
double src_bin_begin = (src_bin - next_start_bin) * bin_width;
double src_bin_end = src_bin_begin + bin_width;
// which dst_bins the beginning and end of src_bin belong to?
int dst_bin_of_begin = std::min(
(1 << precision) - 1.,
std::max(0., floor(src_bin_begin / dst_bin_width)));
int dst_bin_of_end = std::min(
(1 << precision) - 1.,
std::max(0., floor(src_bin_end / dst_bin_width)));
double dst_bin_of_begin_center =
dst_bin_of_begin * dst_bin_width + dst_bin_width / 2;
double density = bins_f[src_bin] / bin_width;
if (dst_bin_of_begin == dst_bin_of_end) {
// if src_bin is entirely within 1 dst_bin
double delta_begin = src_bin_begin - dst_bin_of_begin_center;
double delta_end = src_bin_end - dst_bin_of_begin_center;
// NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions,bugprone-narrowing-conversions)
norm += GetNorm(delta_begin, delta_end, density, kind_);
} else {
double delta_begin = src_bin_begin - dst_bin_of_begin_center;
double delta_end = dst_bin_width / 2;
// NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions,bugprone-narrowing-conversions)
norm += GetNorm(delta_begin, delta_end, density, kind_);
// NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions,bugprone-narrowing-conversions)
norm += (dst_bin_of_end - dst_bin_of_begin - 1) *
// NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions,bugprone-narrowing-conversions)
GetNorm(-dst_bin_width / 2, dst_bin_width / 2, density, kind_);
double dst_bin_of_end_center =
dst_bin_of_end * dst_bin_width + dst_bin_width / 2;
delta_begin = -dst_bin_width / 2;
delta_end = src_bin_end - dst_bin_of_end_center;
// NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions,bugprone-narrowing-conversions)
norm += GetNorm(delta_begin, delta_end, density, kind_);
}
}
if (norm > norm_min)
break;
norm_min = norm;
start_bin = next_start_bin;
end_bin = next_end_bin;
}
VLOG(2) << "best quantization range " << start_bin << "," << end_bin + 1
<< "," << norm_min;
double selected_sum = 0;
for (int i = start_bin; i < end_bin + 1; ++i) {
selected_sum += bins_f[i];
}
VLOG(2) << "best quantization range covers "
<< (double)selected_sum / total * 100 << " %%";
// NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions,bugprone-narrowing-conversions)
max = min + bin_width * (end_bin + 1);
// NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions,bugprone-narrowing-conversions)
min = min + bin_width * start_bin;
VLOG(2) << "Org min " << org_min << " org max " << org_max << " found min "
<< min << " max " << max << " with minimal norm " << norm_min;
QuantizationFactory* qfactory = QuantizationFactory::GetDefaultInstance();
return qfactory->ChooseQuantizationParams(
min, max, precision, preserve_sparsity);
}
TensorQuantizationParams NormMinimization::ChooseQuantizationParams(
const Histogram& hist,
bool preserve_sparsity,
int precision) {
VLOG(2) << "Using the brute force search";
// NOLINTNEXTLINE(cppcoreguidelines-init-variables)
float min, max;
vector<float> bins_f(dnnlowp::adjust_hist_to_include_zero(hist, &min, &max));
int nbins = bins_f.size();
// NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions,bugprone-narrowing-conversions)
float bin_width = (max - min) / nbins;
float scale = (max - min) / float((1 << precision) - 1);
if (bin_width == 0 || scale < SMALL_SCALE_THRESHOLD) {
QuantizationFactory* qfactory = QuantizationFactory::GetDefaultInstance();
return qfactory->ChooseQuantizationParams(
min, max, precision, preserve_sparsity);
}
int dst_nbins = 1 << precision;
// NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions,bugprone-narrowing-conversions)
int zero_bin = round(-min / bin_width);
vector<pair<int, float>> best_start_bins(nbins + 1);
// Look at mapping [start_bin, start_bin + nbins_selected) to
// [0, 1 << precision) for every (start_bin, nbins_selected) combination and
// pick the one with smallest L2 quantization error
#ifdef _OPENMP
#pragma omp parallel for schedule(dynamic)
#endif
for (int nbins_selected = 1; nbins_selected <= nbins; ++nbins_selected) {
float norm_min = numeric_limits<float>::max();
int best_start_bin = 0;
int start_bin_begin = 0, start_bin_end = nbins - nbins_selected + 1;
if (preserve_sparsity) {
// when preserving sparsity we only check the range
// starting from 0 (when min is 0) or symmetric around 0.
if (min == 0) {
start_bin_begin = 0;
start_bin_end = 1;
} else {
start_bin_begin = zero_bin - nbins_selected / 2;
start_bin_end = start_bin_begin + 1;
}
}
// NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions,bugprone-narrowing-conversions)
float dst_bin_width = bin_width * nbins_selected / dst_nbins;
// NOLINTNEXTLINE(cppcoreguidelines-init-variables)
int start_bin;
for (start_bin = start_bin_begin; start_bin < start_bin_end; ++start_bin) {
float norm = 0;
// go over each histogram bin and accumulate errors
caffe2::CpuId cpuid = caffe2::GetCpuId();
if (kind_ == NormMinimization::L2 && cpuid.avx2() && cpuid.fma()) {
norm = internal::L2MinimizationKernelAVX2(
precision,
bins_f.data(),
nbins,
bin_width,
dst_bin_width,
start_bin);
} else {
for (int src_bin = 0; src_bin < nbins; ++src_bin) {
// distances from the beginning of first dst_bin to the beginning and
// end of src_bin
// NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions,bugprone-narrowing-conversions)
float src_bin_begin = (src_bin - start_bin) * bin_width;
float src_bin_end = src_bin_begin + bin_width;
// which dst_bins the beginning and end of src_bin belong to?
int dst_bin_of_begin = std::min(
// NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions,bugprone-narrowing-conversions)
(1 << precision) - 1.0f,
std::max(0.0f, floorf(src_bin_begin / dst_bin_width)));
int dst_bin_of_end = std::min(
// NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions,bugprone-narrowing-conversions)
(1 << precision) - 1.0f,
std::max(0.0f, floorf(src_bin_end / dst_bin_width)));
float dst_bin_of_begin_center =
// NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions,bugprone-narrowing-conversions)
dst_bin_of_begin * dst_bin_width + dst_bin_width / 2;
float density = bins_f[src_bin] / bin_width;
float delta_begin = src_bin_begin - dst_bin_of_begin_center;
if (dst_bin_of_begin == dst_bin_of_end) {
// if src_bin is entirely within 1 dst_bin
float delta_end = src_bin_end - dst_bin_of_begin_center;
norm += GetNorm(delta_begin, delta_end, density, kind_);
} else {
float delta_end = dst_bin_width / 2;
norm += GetNorm(delta_begin, delta_end, density, kind_);
// NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions,bugprone-narrowing-conversions)
norm += (dst_bin_of_end - dst_bin_of_begin - 1) *
GetNorm(-dst_bin_width / 2, dst_bin_width / 2, density, kind_);
float dst_bin_of_end_center =
// NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions,bugprone-narrowing-conversions)
dst_bin_of_end * dst_bin_width + dst_bin_width / 2;
delta_begin = -dst_bin_width / 2;
delta_end = src_bin_end - dst_bin_of_end_center;
norm += GetNorm(delta_begin, delta_end, density, kind_);
}
}
}
if (norm < norm_min) {
norm_min = norm;
best_start_bin = start_bin;
}
} // for each start_bin
best_start_bins[nbins_selected] = {best_start_bin, norm_min};
} // for each nbins_selected
float norm_min = numeric_limits<float>::max();
int best_nbins_selected = 1, best_start_bin = 0;
for (int nbins_selected = 1; nbins_selected <= nbins; ++nbins_selected) {
float norm = best_start_bins[nbins_selected].second;
if (norm < norm_min) {
norm_min = norm;
best_start_bin = best_start_bins[nbins_selected].first;
best_nbins_selected = nbins_selected;
}
}
float total_sum = 0;
for (const auto i : c10::irange(bins_f.size())) {
total_sum += bins_f[i];
}
float selected_sum = 0;
int i_begin = std::max(0, best_start_bin);
int i_end = std::min(nbins, best_start_bin + best_nbins_selected);
for (int i = i_begin; i < i_end; ++i) {
selected_sum += bins_f[i];
}
VLOG(2) << "best quantization range covers " << selected_sum / total_sum * 100
<< " %%";
// NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions,bugprone-narrowing-conversions)
max = min + bin_width * (best_start_bin + best_nbins_selected);
// NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions,bugprone-narrowing-conversions)
min = min + bin_width * (best_start_bin);
QuantizationFactory* qfactory = QuantizationFactory::GetDefaultInstance();
return qfactory->ChooseQuantizationParams(
min, max, precision, preserve_sparsity);
} // ChooseQuantizationParams
} // namespace dnnlowp
|