关键词:
欢迎关注我的公众号 [极智视界],回复001获取Google编程规范
O_o
>_<
o_O
O_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.txt
、scale_bias.txt
和 int8 tmfile;
由于激活值量化中的 min、max 计算方式 及 权值&偏置量化过程,KL 量化和 MIN-MAX 量化逻辑相同且共用相同代码,这里就不展开介绍了,这部分有兴趣的同学可以查阅 《【模型推理】量化实现分享一:详解 min-max 对称量化算法实现》,这里主要介绍激活值量化中的 KL 量化搜索策略。
KL 量化搜索策略的入口在这:
quant_tool.activation_quant_tool();
然后会先做 min、max 的比较搜索,主要用了 std::max_element
、std::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 散度量化算法的实现,希望我的分享能对你的学习有一点帮助。
【公众号传送】
扫描下方二维码即可关注我的微信公众号【极智视界】,获取更多AI经验分享,让我们用极致+极客的心态来迎接AI !
模型推理量化实现分享一:详解min-max对称量化算法实现(代码片段)
欢迎关注我的公众号[极智视界],回复001获取Google编程规范 O_o >_< o_O O_o ~_~ o_O 大家好,我是极智视界,本文剖析一下min-max对称量化算法实现,以Tengine的实现为例。 Tengine是OpenAILab开源的优秀端... 查看详情
markdown使用mxnet实现生产级神经网络模型量化推理(代码片段)
量化感知训练实践:实现精度无损的模型压缩和推理加速(代码片段)
简介:本文以近期流行的YOLOX[8]目标检测模型为例,介绍量化感知训练的原理流程,讨论如何实现精度无损的实践经验,并展示了量化后的模型能够做到精度不低于原始浮点模型,模型压缩4X、推理加速最高2.3X... 查看详情
模型推理谈谈非线性激活函数的量化方式(代码片段)
...O_o >_< o_O O_o ~_~ o_O 本文主要聊一聊深度学习模型量化中对激活函数的处理方式。 之前已经写过几篇关于模型量化的文章:《【模型推理】谈谈几种量化策略:MinMax、KLD、ADMM、EQ》、《【模型推理】谈谈模... 查看详情
模型推理谈谈为什么量化能加速推理(代码片段)
... o_O O_o ~_~ o_O 本文主要讨论一下为什么量化能加速模型推理。 前面已经写过几篇关于模型量化相关的文章:《【模型推理】谈谈几种量化策略:MinMax、KLD、ADMM、EQ》、《【模型推理】谈谈模型量化组织方式》、... 查看详情
模型推理谈谈几种量化策略:minmaxkldadmmeq(代码片段)
...O_o >_< o_O O_o ~_~ o_O 本文主要聊一下深度学习模型量化相关策略。 模型小型化是算法部署的关键技术,模型小型化的过程通常用模型量化来描述。量化通常是高比特位到低比特位的映射过程,量化的对象既... 查看详情
模型推理谈谈模型量化组织方式(代码片段)
...O_o >_< o_O O_o ~_~ o_O 本文主要聊一下深度学习模型量化组织方式。 在我的这篇《【模型推理】谈谈推理引擎的推理组织流程》文章里对模型量化策略进行了一些介绍,有兴趣的同学可以翻看一下。今天这里主要... 查看详情
模型压缩-剪枝算法详解(代码片段)
一,前言学术界的SOTA模型在落地部署到工业界应用到过程中,通常是要面临着低延迟(Latency)、高吞吐(Throughpout)、高效率(Efficiency)挑战的。而模型压缩算法可以将一个庞大而复杂的预训练模型转化为一个精简的小模型,... 查看详情
模型推理寒武纪mluresnet50量化及离线推理流程(代码片段)
...搭建、resnet50量化、resnet50离线推理,resnet系列是标准模型,其他模型也可参考该流程执行。文章目录1、 查看详情
模型推理ncnn模型转换及量化流程(代码片段)
...规范 O_o >_< o_O O_o ~_~ o_O 本文介绍一下ncnn模型转换及量化流程,以from_darknetyolov4为例。 关于ncnn的ubuntu和windows安装方法可以参考我之前写的:《【嵌入式AI】ubuntu安装ncnn》、《【经验分享】win10qm 查看详情
利用tensorrt实现int8量化感知训练qat(代码片段)
...、分类和摘要,这些应用程序必须实时运行。大多数模型都采用浮点32位算法进行训练,以利用更宽的动态范围。但是,在推理时,与降低精度的推理相比,这些模型可能需要更长的时间来预测结果 查看详情
ppq~openppl之多平台量化部署工具来啦!
...经支持Turing系列显卡和多种DSP的INT8量化推理。面对大量模型的多平台量化部署需求,一款支持多平台量化部署的工具必不可少,PPLQuantizationTool(PPQ)应运而生。——推理库后端会对模型做大量的联合定点和图融合优化,我们写入... 查看详情
tflite 量化推理非常慢
...:2020-02-0911:30:27【问题描述】:我正在尝试将经过训练的模型从检查点文件转换为tflite。我正在使用tf.lite.LiteConverter。浮点转换以合理的推理速度进行得很好。但是INT8转换的推理速度很慢。我尝试通过输入一个非常小的网络进... 查看详情
distiller:量化算法
QuantizationAlgorithms量化算法注意:对于任何需要量化感知训练的以下方法,请参阅这里,了解如何使用Distiller的机制调用它。基于范围的线性量化(Range-BasedLinearQuantization)让我们在此分解使用的术语:线性(... 查看详情
机器学习信息熵和热力学定律中的熵有关系吗?
...化信息损失最少的目的,关于这些介绍,可以参考博文:模型量化中的KL散度扫盲_papaofdoudou的博客-CSDN博客_kl三度使用NCNN的INT8量化方式进行推理_papaofdoudou的博客-CSDN博客_n 查看详情
如何用python实现股票量化交易?(代码片段)
...超额收益的多种“大概率”事件以制定策略,用数量模型验证及固化这些规律和策略,然后严格执行已固化的策略来指导投资,以求获得可以持续的、稳定且高于平均收益的超额回报。1.2掌握技能量化交易分类1、分... 查看详情
模型推理比特大陆se5边缘盒子caffessd量化与转换部署模型(代码片段)
...本教程详细记录了在比特大陆SE5边缘盒子上对caffeSSD检测模型进行量化和转换部署模型的方法。文章目录1、准备ssd模型2、转换fp32bmodel2.1转fp32bmodel2.2、模型精度验证3、Int8量化与模型转换3.1模型转换fp32umodel3.2模型转换int8umodel3.2.1... 查看详情
模型推理tengine模型转换及量化(代码片段)
...范 O_o >_< o_O O_o ~_~ o_O 本文介绍一下Tengine模型转换及量化流程。 Tengine同ncnn一样,也是优秀的端侧推理框架,前面写过一篇《ncnn模型转换及量化流程》,有兴趣的同学可以查阅。 下面开始。文章目... 查看详情