欢迎关注我的公众号 [极智视界],回复001获取Google编程规范

O_o>_<   o_OO_o~_~o_O

  大家好,我是极智视界,本文剖析一下 KL 对称量化算法实现,以 Tengine 的实现为例。

   前面已经写过一篇《【模型推理】量化实现分享一:详解 min-max 对称量化算法实现》,有兴趣的同学可以查阅。这是上一篇的续集,也是量化实现详解的第二篇。

   量化背景就不多做介绍了,之前的文章中也说的比较多了,直接开始吧。

1、KL 量化原理

   KL 量化是用 KL 散度来衡量真实数据分布和量化数据分布之间的相似性的量化方法,是英伟达 TensorRT 中对于激活值采用的量化策略,KL 量化的主要逻辑如下:

  • KL 和 MIN-MAX 不一样,不是直接将[min, max] 映射到 [-127, 127],而是去寻找一个阈值 |T| < max(|max|, |min|),将其 [-T, T] 映射到 [-127, 127]。认为只要阈值选取得当,就能将阈值以外的值舍弃掉,也不会对精度损失造成大的影响;

  • 超出阈值 ±|T| 以外的值直接映射为阈值,如上图中的三个红色点,直接映射为 -127,这种映射关系称为是饱和的。

  KL 量化方法试图将 float32 数值分布和 int8 数值分布抽象成两个分布,用阈值 |T| 来更新这两个数值分布,并用 KL 散度来衡量这两个分布的相似性,若 KL 散度值越小,说明这两个分布越相似,也就说明这个阈值 |T| 选择的最好。对于对称量化来说,根据这个阈值就能算出 Scale,而 Zero_point 始终为零。

  下面的图是 TensorRT 中的关于 KL 散度校准的伪代码,这个图也完美诠释了 KLD 整个量化过程。(标记一下下图为图二,后面会调用)

2、KL 量化实现

   这里还是以 Tengine 中 KL 量化的实现进行说明。

  捋一下主要有以下几个流程:

   (1) 激活值量化:先求 min、max,再用 KL 策略搜索量化生成激活值校准表。fp32toint8;

  (2) 权值量化:使用 min-max 量化策略。fp32toint8;

   (3) 偏置量化:延用激活值量化 scale 进行 int32 量化。fp32toint32;

  权值和偏置的量化比激活值量化多一步,除了要计算 Scale 外,还需要对值应用 Scale 进行直接量化以生成 int8 tmfile。

   在 Tengine 中实现 KL 量化的主要代码如下:

case ALGORITHM_KL:{
   if (quant_tool.scale_file.empty()){
       quant_tool.scale_file = "table_kl.scale";
       quant_tool.activation_quant_tool();
  }
   save_graph_i8_perchannel(quant_tool.model_file.c_str(), quant_tool.scale_file.c_str(), quant_tool.output_file, quant_tool.inplace, false);
   /* Evaluate quantitative losses */
   if (quant_tool.evaluate){
       fprintf(stderr, "[Quant Tools Info]: Step Evaluate, evaluate quantitative losses\n");
       quant_tool.assess_quant_loss(0);
  }
   break;
}

   其中最主要的量化搜索策略接口是 quant_tool.activation_quant_tool()save_graph_i8_perchannel,对于 KL 量化来说这两个接口分别做了两件事:

   (1) 激活值量化,生成 table_kl.scale

   (2) 权值&偏置量化,生成 scale_weight.txtscale_bias.txt 和 int8 tmfile;

  由于激活值量化中的 min、max 计算方式 及 权值&偏置量化过程,KL 量化和 MIN-MAX 量化逻辑相同且共用相同代码,这里就不展开介绍了,这部分有兴趣的同学可以查阅 《【模型推理】量化实现分享一:详解 min-max 对称量化算法实现》,这里主要介绍激活值量化中的 KL 量化搜索策略。

   KL 量化搜索策略的入口在这:

quant_tool.activation_quant_tool();

  然后会先做 min、max 的比较搜索,主要用了 std::max_elementstd::min_element 接口,这里不多说,得到 min、max 值后开启 KL 搜索策略。

2.1 勾勒概率直方图

  做第一轮勾勒概率直方图,进行第一轮的 KL 计算,第二轮开始不用重新勾勒概率直方图,而是在第一轮构建的概率直方图上进行迭代,所以你的校准图片数量越多,这个最终得到的概率直方图会越逼近真实分布。


/* calculate hist */
uint32_t inum = 0;
for (int i = 0; i < ir_graph->tensor_num; i++){
   struct tensor* ir_tensor = ir_graph->tensor_list[i];
   if (ir_tensor->tensor_type == TENSOR_TYPE_VAR || ir_tensor->tensor_type == TENSOR_TYPE_INPUT){
       float step_max = std::abs(max_activation[i]);
       if (std::abs(min_activation[i]) > step_max)
           step_max = std::abs(min_activation[i]);
       float step_bin = step_max / 2048.0f;

       std::vector<float> every_edge;
       if (nums == imgs_list.size() - 1){
           for (int j = 0; j < 2048; j++){
               float edge_float = (step_bin * (j + 0.5f));
               every_edge.push_back(edge_float);
          }
           hist_edge.push_back(every_edge);
           hist_gram.push_back(histCount((float*)ir_tensor->data, ir_tensor->elem_num, step_max));
      }
       else{
           std::vector<uint32_t> hist_tmp;
           hist_tmp = histCount((float*)ir_tensor->data, ir_tensor->elem_num, step_max);
           for (int j = 0; j < 2048; j++){
               hist_gram[inum][j] += hist_tmp[j];}
      }
       tensor_hist[i] = inum;
       hist_tensor[inum] = i;
       inum++;}
}

   来看以下 histCount 接口:

std::vector<uint32_t> histCount(float* data, uint32_t elem_num, float abs_max){
   float bin_scale = abs_max / 2047.f;
   int bin_zp = 0;
   std::vector<uint32_t> hist(2048);
   for (int i = 0; i < elem_num; i++){
       if (data[i] != 0){
           uint32_t hist_idx = round(std::abs(data[i]) / bin_scale);
           hist[hist_idx]++;}
  }
   return hist;
}

  最后对得到的概率直方图做一个归一化处理:

distribution = normalize_histogram(distribution_in);

   直方图归一化的实现接口也很简单:

std::vector<float> normalize_histogram(std::vector<uint32_t>& histogram){
   std::vector<float> histogram_out(histogram.size());
   const size_t length = histogram.size();
   float sum = 0;
   for (size_t i = 1; i < length; i++)
       sum += histogram[i];

   for (size_t i = 1; i < length; i++)
       histogram_out[i] = float(histogram[i] / sum);

   return histogram_out;
}

2.2 计算 P

  接下来的逻辑需要回头看一下图二,先计算 P 再计算 Q 最后计算 KL 散度。

   先是计算模拟量化分布 P,从 target_bin = 128 --> 2048 递增检索,溢出部分映射到边缘处理,可以把 P 认为是量化前 fp32 数据分布,即真实分布:

// get P
fill(quantize_distribution.begin(), quantize_distribution.end(), 0.0f);
const float num_per_bin = static_cast<float>(threshold) / static_cast<float>(target_bin);

for (int i = 0; i < target_bin; i++){
   const float start = static_cast<float>(i) * num_per_bin;
   const float end = start + num_per_bin;

   const int left_upper = static_cast<int>(ceil(start));
   if (static_cast<float>(left_upper) > start){
       const float left_scale = static_cast<float>(left_upper) - start;
       quantize_distribution[i] += left_scale * distribution[left_upper - 1];
  }

   const int right_lower = static_cast<int>(floor(end));

   if (static_cast<float>(right_lower) < end){
       const float right_scale = end - static_cast<float>(right_lower);
       quantize_distribution[i] += right_scale * distribution[right_lower];
  }

   for (int j = left_upper; j < right_lower; j++){
       quantize_distribution[i] += distribution[j];}
}

2.2 计算 Q

   然后是计算真实量化分布 Q,伴随 P 从 target_bin = 128 --> 2048 递增检索,可以把 Q 认为是量化后 int8 数据分布,即量化分布:

// get Q
std::vector<float> expand_distribution(threshold, 0);
for (int i = 0; i < target_bin; i++){
   const float start = static_cast<float>(i) * num_per_bin;
   const float end = start + num_per_bin;
   float count = 0;

   const int left_upper = static_cast<int>(ceil(start));
   float left_scale = 0;
   if (static_cast<float>(left_upper) > start){
       left_scale = static_cast<float>(left_upper) - start;
       if (distribution[left_upper - 1] != 0){
           count += left_scale;}
  }

   const int right_lower = static_cast<int>(floor(end));
   float right_scale = 0;
   if (static_cast<float>(right_lower) < end){
       right_scale = end - static_cast<float>(right_lower);
       if (distribution[right_lower] != 0){
           count += right_scale;}
  }

   for (int j = left_upper; j < right_lower; j++){
       if (distribution[j] != 0){
           count++;}
  }

   const float expand_value = quantize_distribution[i] / count;

   if (static_cast<float>(left_upper) > start){
       if (distribution[left_upper - 1] != 0){
           expand_distribution[left_upper - 1] += expand_value * left_scale;}
  }
   if (static_cast<float>(right_lower) < end){
       if (distribution[right_lower] != 0){
           expand_distribution[right_lower] += expand_value * right_scale;}
  }
   for (int j = left_upper; j < right_lower; j++){
       if (distribution[j] != 0){
           expand_distribution[j] += expand_value;}}
}

2.3 计算 KL 散度

  接下来是计算真实分布 P 和量化分布 Q 的 KL 散度:

const float kl_divergence = compute_kl_divergence(t_distribution, expand_distribution);

   实现 KL 散度计算的接口也很简单:

float compute_kl_divergence(std::vector<float>& dist_a, std::vector<float>& dist_b){
   const size_t length = dist_a.size();
   float result = 0;

   for (size_t i = 0; i < length; i++){
       if (dist_a[i] != 0){
           if (dist_b[i] == 0){
               result += 1;
          }
           else{
               result += dist_a[i] * log(dist_a[i] / dist_b[i]);}}
  }
   return result;
}

  最终我们是想找到一个使 KL 散度最小的 target_bin,由于是在 128 --> 2048 的循环中检索的,所以这个实现可以这么写:

// the best num of bin
if (kl_divergence < min_kl_divergence)
{
  min_kl_divergence = kl_divergence;
  target_threshold = threshold;
}

   这样就得到了我们梦寐以求的那个 target_bin,也就是这里的 target_threshold。

2.4 计算 Scale

  在计算得到 target_threshold 后,再去计算 Scale 就很简单了,直接这样就好了。

float act_scale = hist_edge[i][threshold_bin] / fake_quant_set;    // fake_quant_set = 127
int act_zero_point = 0;

  重申,由于是对称量化,所以只需计算 Scale,Zero_point 始终为零。

  然后就可以保存我们的激活值量化校准表 table_kl.scale 了,再次重申,后面的权值&偏置量化方法和 MIN-MAX 的一致,而 MIN-MAX 的量化方法我在前面的文章中已经介绍过,这里就不多赘述。

  以上就完成了实用的 KL 散度量化算法的实现,希望我的分享能对你的学习有一点帮助。

【公众号传送】

【模型推理】量化实现分享二:详解 KL 对称量化算法实现

【模型推理】量化实现分享二:详解 KL 对称量化算法实现的更多相关文章

  1. 【模型推理】量化实现分享三:详解 ACIQ 对称量化算法实现

      欢迎关注我的公众号 [极智视界],回复001获取Google编程规范   O_o   >_<   o_O   O_o   ~_~   o_O   大家好,我是极智视界,本文剖析一下AC ...

  2. 【模型推理】量化实现分享一:详解 min-max 对称量化算法实现

      欢迎关注我的公众号 [极智视界],回复001获取Google编程规范   O_o   >_<   o_O   O_o   ~_~   o_O   大家好,我是极智视界,本文剖析一下 m ...

  3. 数据挖掘模型中的IV和WOE详解

    IV: 某个特征中 某个小分组的 响应比例与未响应比例之差 乘以 响应比例与未响应比例的比值取对数 数据挖掘模型中的IV和WOE详解 http://blog.csdn.net/kevin7658/ar ...

  4. [转]Vue项目全局配置微信分享思路详解

    这篇文章给大家介绍了vue项目全局配置微信分享思路讲解,使用vue作为框架,使用vux作为ui组件库,具体内容详情大家跟随脚本之家小编一起学习吧 这个项目为移动端项目,主要用于接入公众号服务.项目采用 ...

  5. iOS开发之窥探UICollectionViewController(二) --详解CollectionView各种回调

    UICollectionView的布局是可以自己定义的,在这篇博客中先在上篇博客的基础上进行扩充,我们先使用UICollectionViewFlowLayout,然后好好的介绍一下UICollecti ...

  6. 转载:数据挖掘模型中的IV和WOE详解

    1.IV的用途 IV的全称是Information Value,中文意思是信息价值,或者信息量. 我们在用逻辑回归.决策树等模型方法构建分类模型时,经常需要对自变量进行筛选.比如我们有200个候选自变 ...

  7. 评分卡模型中的IV和WOE详解

    1.IV的用途   IV的全称是Information Value,中文意思是信息价值,或者信息量. 我们在用逻辑回归.决策树等模型方法构建分类模型时,经常需要对自变量进行筛选.比如我们有200个候选 ...

  8. 微信JSSDK分享功能详解

    本文以微信分享到朋友圈,分享给微信好友为例为参考,进行调用测试,想添加其他的功能,自行查看开发人员文档即可 工欲善其事,必先利其器,好好利用下边的帮助工具,都是腾讯给开发人员的工具 1.微信开发者说明 ...

  9. IO模型之AIO代码及其实践详解

    一.AIO简介 AIO是java中IO模型的一种,作为NIO的改进和增强随JDK1.7版本更新被集成在JDK的nio包中,因此AIO也被称作是NIO2.0.区别于传统的BIO(Blocking IO, ...

随机推荐

  1. JVM核心——JVM运行和类加载全过程

    1.类加载全过程 (1)类加载机制 JVM把class文件加载到内存,并对数据进行校验.解析和初始化,最终形成JVM可以直接使用的Java类型的过程. 加载 将class文件字节码内容加载到内存中,并 ...

  2. [loj3256]火灾

    将问题差分,即求$\sum_{i=1}^{r}S_{i}(t)-\sum_{i=1}^{l-1}S_{i}(t)$,由于两者类似,不妨考虑前者 构造矩阵$A_{i,j}=S_{j}(i)-S_{j}( ...

  3. [loj3343]超现实树

    定义1:两棵树中的$x$和$y$对应当且仅当$x$到根的链与$y$到根的链同构 定义2:$x$和$y$的儿子状态相同当且仅当$x$与儿子所构成的树与$y$与儿子所构成的树同构 根据题中所给的定义,有以 ...

  4. Aggregated APIServer 构建云原生应用最佳实践

    作者 张鹏,腾讯云容器产品工程师,拥有多年云原生项目开发落地经验.目前主要负责腾讯云 TKE 云原生 AI 产品的开发工作. 谢远东,腾讯高级工程师,Kubeflow Member.Fluid(CNC ...

  5. 数值分析:幂迭代和PageRank算法(Numpy实现)

    1. 幂迭代算法(简称幂法) (1) 占优特征值和占优特征向量 已知方阵\(\bm{A} \in \R^{n \times n}\), \(\bm{A}\)的占优特征值是比\(\bm{A}\)的其他特 ...

  6. 洛谷 P7155 [USACO20DEC] Spaceship P(dp)

    Portal Yet another 1e9+7 Yet another 计数 dp Yet another 我做不出来的题 考虑合法的按键方式长啥样.假设我们依次按下了 \(p_1,p_2,\dot ...

  7. P7708「Wdsr-2.7」八云蓝自动机 Ⅰ

    *X. P7708「Wdsr-2.7」八云蓝自动机 Ⅰ. 摘自 分治与根号数据结构学习笔记 第三部分 莫队 例题 X.. 一道莫队好题.私以为本题最有价值的地方在于对单点修改的转化以及对交换两个数的处 ...

  8. DirectX12 3D 游戏开发与实战第九章内容(下)

    仅供个人学习使用,请勿转载.谢谢! 9.纹理贴图 学习目标 学习如何将局部纹理映射到网格三角形中 探究如何创建和启用纹理 学会如何通过纹理过滤来创建更加平滑的图像 探索如何使用寻址模式来进行多次贴图 ...

  9. Perl去重fasta序列

    常规方法 #! usr/bin/perl -w use strict; my $input=shift; my %hash; open IN,"<$input"; $/=&q ...

  10. 谈谈AI

    由AI大作业想到的 近几年<人工智能导论>的大作业是编写一个博弈程序,这类程序的典型框架就是α-β剪枝算法,像著名的打败了国际象棋大师卡斯帕罗的深蓝,就是这么干的,一些中国象棋程序也是这样 ...