极智AI | 量化实现分享二:详解KL对称量化算法实现

本文涉及的产品
视觉智能开放平台,视频资源包5000点
视觉智能开放平台,分割抠图1万点
视觉智能开放平台,图像资源包5000点
简介: 大家好,我是极智视界,本文剖析一下 KL 对称量化算法实现,以 Tengine 的实现为例。

大家好,我是极智视界,本文剖析一下 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 散度量化算法的实现,希望我的分享能对你的学习有一点帮助。


logo_show.gif


相关文章
|
3天前
|
传感器 人工智能 监控
智慧电厂AI算法方案
智慧电厂AI算法方案通过深度学习和机器学习技术,实现设备故障预测、发电运行优化、安全监控和环保管理。方案涵盖平台层、展现层、应用层和基础层,具备精准诊断、智能优化、全方位监控等优势,助力电厂提升效率、降低成本、保障安全和环保合规。
智慧电厂AI算法方案
|
3天前
|
机器学习/深度学习 人工智能 监控
智慧交通AI算法解决方案
智慧交通AI算法方案针对交通拥堵、违法取证难等问题,通过AI技术实现交通管理的智能化。平台层整合多种AI能力,提供实时监控、违法识别等功能;展现层与应用层则通过一张图、路口态势研判等工具,提升交通管理效率。方案优势包括先进的算法、系统集成性和数据融合性,应用场景涵盖车辆检测、道路环境检测和道路行人检测等。
|
3天前
|
传感器 人工智能 监控
智慧化工厂AI算法方案
智慧化工厂AI算法方案针对化工行业生产过程中的安全风险、效率瓶颈、环保压力和数据管理不足等问题,通过深度学习、大数据分析等技术,实现生产过程的实时监控与优化、设备故障预测与维护、安全预警与应急响应、环保监测与治理优化,全面提升工厂的智能化水平和管理效能。
智慧化工厂AI算法方案
|
28天前
|
机器学习/深度学习 人工智能 算法
"拥抱AI规模化浪潮:从数据到算法,解锁未来无限可能,你准备好迎接这场技术革命了吗?"
【10月更文挑战第14天】本文探讨了AI规模化的重要性和挑战,涵盖数据、算法、算力和应用场景等方面。通过使用Python和TensorFlow的示例代码,展示了如何训练并应用一个基本的AI模型进行图像分类,强调了AI规模化在各行业的广泛应用前景。
29 5
|
1月前
|
机器学习/深度学习 人工智能 开发框架
【AI系统】AI 学习方法与算法现状
在人工智能的历史长河中,我们见证了从规则驱动系统到现代机器学习模型的转变。AI的学习方法基于深度神经网络,通过前向传播、反向传播和梯度更新不断优化权重,实现从训练到推理的过程。当前,AI算法如CNN、RNN、GNN和GAN等在各自领域取得突破,推动技术进步的同时也带来了更大的挑战,要求算法工程师与系统设计师紧密合作,共同拓展AI技术的边界。
76 1
|
30天前
|
人工智能 算法 前端开发
无界批发零售定义及无界AI算法,打破传统壁垒,累积数据流量
“无界批发与零售”是一种结合了批发与零售的商业模式,通过后端逻辑、数据库设计和前端用户界面实现。该模式支持用户注册、登录、商品管理、订单处理、批发与零售功能,并根据用户行为计算信用等级,确保交易安全与高效。
|
30天前
|
人工智能 算法 JavaScript
无界SaaS与AI算力算法,链接裂变万企万商万物互联
本文介绍了一种基于无界SaaS与AI算力算法的商业模式的技术实现方案,涵盖前端、后端、数据库及AI算法等关键部分。通过React.js构建用户界面,Node.js与Express搭建后端服务,MongoDB存储数据,TensorFlow实现AI功能。提供了项目结构、代码示例及部署建议,强调了安全性、可扩展性和性能优化的重要性。
|
3月前
|
机器学习/深度学习 人工智能 算法
AI入门必读:Java实现常见AI算法及实际应用,有两下子!
本文全面介绍了人工智能(AI)的基础知识、操作教程、算法实现及其在实际项目中的应用。首先,从AI的概念出发,解释了AI如何使机器具备学习、思考、决策和交流的能力,并列举了日常生活中的常见应用场景,如手机助手、推荐系统、自动驾驶等。接着,详细介绍了AI在提高效率、增强用户体验、促进技术创新和解决复杂问题等方面的显著作用,同时展望了AI的未来发展趋势,包括自我学习能力的提升、人机协作的增强、伦理法规的完善以及行业垂直化应用的拓展等...
180 3
AI入门必读:Java实现常见AI算法及实际应用,有两下子!
|
2月前
|
机器学习/深度学习 人工智能 开发框架
智能ai量化高频策略交易软件、现货合约跟单模式开发技术规则
该项目涵盖智能AI量化高频策略交易软件及现货合约跟单模式开发,融合人工智能、量化交易与软件工程。软件开发包括需求分析、技术选型、系统构建、测试部署及运维;跟单模式则涉及功能定义、策略开发、交易执行、终端设计与市场推广,确保系统高效稳定运行。
|
3月前
|
算法 JavaScript 前端开发
国标非对称加密:RSA算法、非对称特征、js还原、jsencrypt和rsa模块解析
国标非对称加密:RSA算法、非对称特征、js还原、jsencrypt和rsa模块解析
270 1

热门文章

最新文章