极智AI | 量化实现分享五:详解格林深瞳EQ量化算法实现

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

大家好,我是极智视界,本文剖析一下格灵深瞳 EQ (Easy Quant) 量化算法实现,以 Tengine 的实现为例。

本文是模型量化实现分享的第五篇,前面已有四篇,有兴趣的同学可以查阅:

(1) 《【模型推理】量化实现分享一:详解 min-max 对称量化算法实现

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

(3) 《【模型推理】量化实现分享三:详解 ACIQ 对称量化算法实现

(4)《【模型推理】量化实现分享四:Data-Free Quantization 香不香?详解高通 DFQ 量化算法实现

格灵深瞳 EQ 量化算法在论文《EasyQuant: Post-traning Quantization via Scale Optimization》中提出,EQ 量化算法有几个创新点:

(1) 在 KLD 获得初始值后再用贪婪方式以最大化真实值&量化值间余弦相似度为优化目标,交替搜索权值 Scale 和 激活值 Scale;

(2) 结合 Arm NEON 指令集优化,在 int7 量化时的性能表现更优。

同样,这里不止会介绍原理,也会介绍实现。

下面开始。


1、EQ 量化原理

先来看一下实验数据:

这是 ImageNet2012 上分类模型的量化精度数据,对标 Nvidia TRT,可以看到 EQ 不管是在 int8 还是 int7 量化时表现均占优。再看一组检测模型的数据:

以上是 SSD 目标检测模型在 VOC2007 上的量化精度 (mAP) 对比数据,同样可以看到 EQ 在 int8 & int7 量化效果占优。 以上两组数据还可以看出,EQ int7 接近 TRT int8 精度,所以有了下面这组性能对比数据 (总觉得少了组 EQ int8 的数据):

论文里还有其他的一些实验数据,如对比 QAT 量化感知训练的实验数据等,这里就不贴了,有兴趣的同学可以自行查阅。总结来说就是 EQ 算法在精度和性能上都是 yyds。

来讲原理。

首先量化的过程可以表示为:

其中 X 为输入 tensor,S 为缩放因子,Clip 为裁剪,Round 为取整,所以量化的整个过程分为三步:

(1) 裁剪;

(2) 缩放;

(3) 取整;

这样的说法其实比较宏观,再细一点,可以把 S 分为激活值 S 和权值 S,那么第 l 层的量化过程就可以表示为:

以上量化式是对于第 l 层来说的,再结合拓展到第 l + 1 层,会有 dequant / requant 的过程,整个逻辑就是这样的:

量化过程很清晰直观,不再赘述。然后就到了 EQ 算法的精髓。

EQ 是用余弦相似度来衡量真实值和量化值间的相似度,优化的目标是最大化余弦相似度,可以用如下式表达:

EQ 的搜索策略是这样的:初始值 Sa 和 Sw 由 KLD 获得,在这个基础上搜索优化 Sa 和 Sw。搜索空间为 [αS, βS] 线性划分 n 个,先固定 Sa 优化 Sw,再固定 Sw 优化 Sa,两者交替进行直至设计的余弦相似度收敛或超时。对于整网来说,EQ 搜索策略如下 (这也是 EQ 量化的核心逻辑):

到这里原理已讲完,多说一句,前面可以看到论文实验结果对标 TRT,看了 EQ 的算法逻辑,应该很清楚,从精度上来说,EQ 根本不太可能会比 TRT 差,因为 TRT 是基于 KLD 量化的,而 EQ 是在 KLD 量化得出的 Scale 上再进一步搜索优化,结果怎么可能会弱于初始值 KLD 呢,所以精度数据那么好看也不用吃惊,这是理所当然的结果。另外一方面,,由于 EQ 是 KLD 后的二次优化,所以 EQ 的整个量化过程开销会比 KLD 量化大得多。

到这里,主要说了精度为什么能涨点,还没说为什么能提速。这里我觉得论文是打了个擦边球,可以看到前面的性能数据对比对象是 TRT int8 vs EQ int7,这公平吗?直观来说确实不公平,不过再结合 EQ int7 精度接近 / 追平 TRT int8,再来看这个性能数据心里会平衡一些。这里也诞生了论文的一个创新点:既然前提我 int7 能做到 baseline int8 差不多的精度,那我就重心优化 int7 推理 (除非你 int7 精度比我 int7 高,不然我就用 int7 比你 int8,气死你~)。

然后说一下为什么 int7 能提速。来看下图:

在用 int8 量化时,中间数据一般用 int32 存储,直到 ARM V8.2-A 之前都没有能将两个 8bit 数据相乘的结果放到 32bit 寄存器的指令,替代的方法是用 SMLAL 将两个 8bit 数据相乘后存入 16bit 寄存器,再用 SADALP 合并两个 16bit 到 32bit 寄存器。对于一个常规卷积来说,用 int8 和 int7 的 NEON 乘法指令的次数差别如下:

  • int8 推理:一次能算 ((2^15-1)/(2^7-1)^2) = 2 次 SMLAL;
  • int7 推理:一次能算 ((2^15-1)/(2^6-1)^2) = 8 次 SMLAL;

可以看到 int7 推理相比 int8 推理具有更加高效的乘法指令优化,这也正是 int7 推理效率更高的主要原因。


2、EQ 量化实现

来看 EQ 量化的 tengine 实现。

EQ 实现的主要代码如下:

case ALGORITHM_MM_EQ:
{
    if (quant_tool.scale_file.empty()){
        quant_tool.scale_file = "table_minmax.scale";
        quant_tool.activation_quant_tool();
    }
    /* Evaluate quantitative losses */
    if (quant_tool.evaluate){
        fprintf(stderr, "[Quant Tools Info]: Step Evaluate, evaluate quantitative losses\n");
        quant_tool.assess_quant_loss(0);
    }
    /* Enable EQ search */
    fprintf(stderr, "[Quant Tools Info]: Step Search, enable EQ search\n");
    quant_tool.quant_search();
    quant_tool.model_file = "save_i8_eq.tmfile";
    save_graph_i8_perchannel(quant_tool.model_file.c_str(), quant_tool.scale_file.c_str(), quant_tool.output_file, quant_tool.inplace, true);
    break;
}

这里的主逻辑和论文里的稍微有一些出入,论文里的 EQ 搜索初始值是 KLD 得出的,而这里是 MIN-MAX 得出的。在得到 MIN-MAX 出来的 Scale 初始值后进行 EQ 的搜索,主要是这个接口:

quant_tool.quant_search();

在这个接口里主要有两个逻辑:

(1) 当层类型为 CONV 或 FC 时:先进行权值 Scale 的搜索 requant,再进行偏置 requant,再进行激活值 requant,最后计算余弦相似度;

(2) 当层类型非 CONV 及 FC 时:不进行搜索也不进行权值&偏置 requant,直接进行激活值 requant,并计算余弦相似度;

可以看到和论文里的逻辑还是不太一样,这里只进行了权值 Scale 的搜索,搜索空间为 [1.3/200 Scale, 1.3/200x201 Scale],迭代间隔 1.3/20。下面来看代码:

if (this->op_name == OP_CONV || this->op_name == OP_FC)    /// 当层类型为CONV或FC时
{
    this->gen_weight_scale(this->weight_tensor_fake_quant, this->weight_data_fake_quant, this->weight_tensor_fake_quant->elem_num, 8, 1, weight_tensor_fake_quant->dims[0]);
    this->gen_weight_scale(this->weight_tensor_fp32, this->weight_data_fp32, this->weight_tensor_fp32->elem_num, 8, 1, weight_tensor_fp32->dims[0]);
    std::vector<double> cosin_save(weight_tensor_fake_quant->dims[0], -1);
    std::vector<float> zoom_save(weight_tensor_fake_quant->dims[0], -1);
    for (int snum = 0; snum < 201; snum = snum + 20){     /// 搜索空间
        float zoom = 1.3 / 200 * (snum + 1);
        /* step 0 weight requant */
        if (snum < 200)
            this->weight_requant_search(weight_tensor_fake_quant, weight_data_fake_quant, weight_tensor_fake_quant->elem_num, 8, 1, weight_tensor_fake_quant->dims[0], zoom);
        else{
            this->weight_requant_search(weight_tensor_fake_quant, weight_data_fake_quant, weight_tensor_fake_quant->elem_num, 8, 1, weight_tensor_fake_quant->dims[0], zoom_save.data());
            float* buf = (float*)sys_malloc(weight_tensor_fake_quant->dims[0] * 4);
            memcpy(buf, zoom_save.data(), weight_tensor_fake_quant->dims[0] * 4);
            for (int bi = 0; bi < weight_tensor_fake_quant->dims[0]; bi++){
                buf[bi] *= weight_tensor_fp32->scale_list[bi];
            }
            weight_tensor_fp32->scale_list = buf;
            weight_tensor_fp32->quant_param_num = weight_tensor_fp32->dims[0];
        }
        if (interleave_size_fake != 0){
            int M = weight_tensor_fake_quant->dims[0];
            int K = weight_tensor_fake_quant->elem_num / weight_tensor_fake_quant->dims[0];
            this->conv_hcl_interleave_pack4_fp32(M, K, weight_data_fake_quant, interleave_buffer_fake_quant);
        }
        /* step 1 bias requant */
        if (node_fake_quant->ir_node->input_num > 2){
            struct tensor* input_tensor_fake_quant = graphn_fake_quant->tensor_list[node_fake_quant->ir_node->input_tensors[0]];
            struct tensor* bias_tensor_fake_quant = graphn_fake_quant->tensor_list[node_fake_quant->ir_node->input_tensors[2]];
            struct tensor* bias_tensor_fp32 = graphn_fp32->tensor_list[node_fp32->ir_node->input_tensors[2]];
            bias_size = bias_tensor_fp32->elem_num * bias_tensor_fp32->elem_size;
            bias_data_fp32 = (float*)bias_tensor_fp32->data;
            bias_data_fake_quant = (float*)bias_tensor_fake_quant->data;
            this->bias_requant(input_tensor_fake_quant, weight_tensor_fake_quant, bias_tensor_fake_quant, bias_data_fake_quant, bias_tensor_fake_quant->elem_num, bias_tensor_fake_quant->dims[0]);
        }
        /* step 2 activation requant */
        for (int imgi = 0; imgi < this->max_search_img_num; imgi++){
            this->set_node_input_output_tensor(i, imgi, snum);
            /* FP32 op run */
            if (snum == 0){
                node_ops_fp32->run(node_ops_fp32, node_fp32, exec_graph_fp32);
                this->execidx_elemnum[i] = output_tensor_fp32->elem_num;   //exec idx --> output elem num
                this->execidx_elemsize[i] = output_tensor_fp32->elem_size; //exec idx --> output elem size
                this->execidx_nodename[i] = output_tensor_fp32->name;
            }
            /* fake quant op run */
            node_ops_fake_quant->run(node_ops_fake_quant, node_fake_quant, exec_graph_fake_quant);
            this->activation_requant(out_imgs_fake_quant[imgi].data(), output_tensor_fake_quant->elem_num, 8, 1, output_tensor_fake_quant->scale, output_tensor_fake_quant->zero_point);
        } 
        output_channel = output_tensor_fp32->dims[1];
        /* step 3 cal cosin_similarity */
        if (this->op_name == OP_CONV || (this->op_name == OP_FC && this->max_search_img_num > 1))
            this->cosin_similarity(this->cosin, this->out_imgs_fp32, this->out_imgs_fake_quant, this->max_search_img_num, this->execidx_elemnum[i], output_channel);
        else
            this->cosin_similarity(this->cosin, this->out_imgs_fp32, this->out_imgs_fake_quant, this->max_search_img_num, this->execidx_elemnum[i], 1);
        for (int cosi = 0; cosi < output_channel; cosi++){
            if (cosin[cosi] > cosin_save[cosi]){
                cosin_save[cosi] = cosin[cosi];
                zoom_save[cosi] = zoom;}
        }
        if (snum == 200){
            if (this->op_name == OP_CONV || (this->op_name == OP_FC && this->max_search_img_num > 1))
                this->print_cosin(this->cosin.data(), i, output_channel);
            else
                this->print_cosin(this->cosin.data(), i, 1);
        }
        if (op_name == OP_CONV || op_name == OP_FC){
            memcpy(weight_data_fake_quant, weight_data_fp32, weight_size);
            memcpy(interleave_buffer_fake_quant, interleave_buffer_fp32, interleave_size_fake);
            if (node_fake_quant->ir_node->input_num > 2){
                memcpy(bias_data_fake_quant, bias_data_fp32, bias_size);}}}
}
else{
    /* per image run */
    for (int imgi = 0; imgi < this->max_search_img_num; imgi++){
        this->set_node_input_output_tensor(i, imgi, 0);
        node_ops_fp32->run(node_ops_fp32, node_fp32, exec_graph_fp32);
        /* step 0 activation requant */
        node_ops_fake_quant->run(node_ops_fake_quant, node_fake_quant, exec_graph_fake_quant);
        this->activation_requant(out_imgs_fake_quant[imgi].data(), output_tensor_fake_quant->elem_num, 8, 1, output_tensor_fake_quant->scale, output_tensor_fake_quant->zero_point);
        this->execidx_elemnum[i] = output_tensor_fp32->elem_num;   //exec idx --> output elem num
        this->execidx_elemsize[i] = output_tensor_fp32->elem_size; //exec idx --> output elem size
        this->execidx_nodename[i] = output_tensor_fp32->name;
    }
    /* step 1 cal cosin_similarity */
    this->cosin_similarity(this->cosin, out_imgs_fp32, out_imgs_fake_quant, this->max_search_img_num, this->execidx_elemnum[i], 1);
    this->print_cosin(this->cosin.data(), i, 1);
    this->execidx_loss[i] = cosin;
}

这样就完成了 EQ 算法的搜索过程。


以上详细分享了格灵深瞳 EQ 算法的原理和实现,希望我的分享能对你的学习有一点帮助。


logo_show.gif

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