极智AI | 量化实验分享四:Data-Free Quantization香不香?详解高通DFQ量化算法实现

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

大家好,我是极智视界,本文剖析一下高通 DFQ (Data-Free Quantization) 量化算法实现,以 Tengine 的实现为例。

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

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

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

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

高通 DFQ 量化算法在论文《Data-Free Quantization Through Weight Equalization and Bias Correction》中提出,论文的一开始就把量化算法划了四个层级:

Level 1: No data and no backpropagation required. Method works for any model;

Level 2: Requires data but no backpropagation. Works for any model;

Level 3: Requires data and backpropagation. Works for any model;

Level 4: Requires data and backpropagation. Only works for specific models;

认为 Level 1 级的量化是最高级的,但我并不认为 DFQ 属于 Level 1,至少它是否能 works for any model,需要打个问号。往下看你就会发现,论文以 mobilenetv2 的 conv--> bn--> relu 顺序 block 为主要论证对象,论证的网络结构还是比较局限,不过方法还是比较新颖。

下面还是会从原理和实现分别进行详细的介绍。


1、DFQ 量化原理

先来看 DFQ 的量化效果:

可以看到在 MobileNetV2、MobileNetV1 和 ResNet18 上做 fp32 --> int8 的量化,DFQ 都要优于直接 Per-layer 和 Per-channel 的量化策略,作者还拓展的在 ResNet18 上做了 fp32 --> int6 的量化试验,这个效果没有 Per-channel 好。

DFQ 算法的核心逻辑主要是(1)跨层均衡;(2)偏移吸收;(3)正常量化;(4)偏移修正:

其中(1)、(2)还未开始量化,是量化前的准备工作,(4)是量化后的修正工作。下面分别展开。

1.1 Cross-layer equalization

先说一下对于权重的 Per-layer 量化和 Per-channels 量化,假设权重 W 的 shape 是[n, c, h, w],Per-layer 量化是一下子把 W[:, :, :, :] 进行量化,最后只有一个 Scale 和 一个 Bias;而 Per-channels 则是逐通道 W[:, c, :, :],最后有 c 个 Scale 和 c 个 Bias。对于权重量化来说,Per-layer 策略有个弊端,如下 (Per output channel weight ranges of the first depthwise-separable layer in MobileNetV2):

可以看到不同通道的权重的 Range 相差很大,这个时候很难用 Per-layer 的方式只用一个 Scale + Bias 统筹,若用 Per-layer 势必对于一些 Range 小的通道权重量化后值会统一置为 0,这是不合理的。而 Per-layer 的这个缺陷恰恰用 Per-channels 量化能完美解决,因为 Per-channels 会给每个通道一个 Scale 和一个 Bias。那为啥我们不直接用 Per-channels 呢,作者认为 Per-channels 相对于 Per-layer 硬件更不友好,开销更大,所以作者在论文里使劲把 Per-channels 往 Per-layer 改造,这样就诞生了这里的跨层均衡这个 tricks,意思是缩小通道间权值 Range 的差异,使用一个新的更加均衡的 fp32 权重来替代原来的权重,最后达到Per-channels 转换为 Per-layers 的目的。具体怎么做的呢,主要运用了 RELU 的数学特性进行了如下推导:

先来看一下 RELU 函数:

对于一个正半轴的 s,有:

对于网络中的相邻两层:

结合 RELU 的数学特性,可以做如下的推导:

其中:

S = diag(s) 是一个对角矩阵,对角线上的各个值是用来调整缩放系数的因子,在 Layer 1 除完的系数需要在 Layer 2 相应乘回,示意如下图:

更进一步的,这个 S 到底怎么算,对于对称量化来说,假设第 i 个通道 Layer 1 和 Layer 2 的权重范围分别为 r1 和 r2,则 i 通道最好均衡到 r = √ (r1r2)(论文中有证明),那么 S 就可以这么计算得到:

这里介绍了 Layer 1 和 Layer 2 两层之间的通道权值范围的均衡,实际网络中肯定有更加多的相邻层,需要迭代下去看能不能达到量化误差指标,若达到即可停止跨层均衡,这样就完成了整个网络的跨层均衡。

1.2 Absorbing high biases

跨层均衡主要考虑对权重的处理,在整个量化处理中激活值的量化也会对整体量化效果产生比较大的影响,特别是我们在对权重进行均衡缩放处理后,相应的激活值的 Range 也会变化,如当某层某通道的 S > 1 时,则该通道的激活值 Range 范围也会变大,为了避免不同通道的激活值差异过大,需要吸收高偏差到下一层。这里同样运用到了 RELU 的数学性质,再来一波推导。

默认这种 CONV + BN + RELU 顺序结构:

对于 c < (Wx + b) || c = 0 (经 BN 输出的激活值服从高斯分布,由 3σ 原则知有 99.865% 的概率满足 c <= (Wx + b)),则有:

对于 BN --> RELU 这两层来说,进行推导:

其中:

这里就比较巧妙,直接在 Layer 1 输出激活值 h() 上减去 c,使得 Range 范围变小而减小量化误差,而减掉的部分则由下一层 Layer 2 的偏置完全吸收,这样就达到了一个平衡。这样就完成了 bias absorption。

2.3 Quantization

不多说,就是正常量化,实质文章中也没多说。掠过~~~

2.4 Quantization bias correction

先举个量化的例子,比如原始 fp32 Range 范围为 [-1.0, 1.0],咱们进行 fp32 --> int8 量化,则 Sacle = 255 / 2.0 = 127.5,量化后的值域 Range 为 [-127.5, 127.5],然后一般会做个 round() 取整操作,得到最后的量化 Range 为 [-127, 128],如果只考虑对称量化,就没有 + Zero_Point 的偏移,这样就完成了 fp32 --> int8 的过程。然后我们尝试一下复原,即 int8 --> fp32,你会发现最后得到的 fp32 Range 范围为 [-127/127.5, 128/127.5],这与最开始的 [-1.0, 1.0] 存在一些偏差,这个偏差很容易想到是由于 round() 导致的,这也说明了量化的过程是不可逆的。

然后再回到这里,这个 Quantization bias correction 就是为了校正上面提到的不可逆量化误差而导致的量化偏差。且文中还提到比较关键的一点是认为量化误差是有偏误差,有偏误差的意思从分布上来说量化误差的均值并不为0,即会影响输出分布,从而导致输出有偏差。接下来的解法就是类似前面的(1)Cross-layer equalization 和 (2)Absorbing high biases 中用到的 先乘后除/先除后乘 和 先减后加/先加后减 的补偿操作,这里也采用类似的思想,把误差给补回来。下面又开始推导。

假设某层的权重为 W,量化后的权重为 W',则:

其中:

继而可以推导出偏移:

这个时候重心转移到求 E[x],考虑到网络结构为 BN --> RELU 的顺序结构,RELU 函数的特点是负半轴抑制,所以只会保留正半轴的 BN 输出,可以用以下两种方法来计算 RELU() 后的 E[x]:

(1) 有校准集时,可直接通过统计数据分布来得到 E[x] (但这有违 Data-Free 的思想);

(2) 无校准集时 (Data-Free),经 BN 输出的激活值服从高斯分布,然后再进 RELU,相当于把高斯分布截断只保留正半轴,则问题转换为求正半轴高斯分布的均值,可以这么计算:

好了 DFQ 的原理就是这些,还是有些东西的。接下来是实现。


2、DFQ 量化实现

我们还是以 tengine 中 DFQ 的实现为例进行介绍。

首先先提个小 bug 单(捂脸~)

然后开始。

DFQ 量化的主要实现在这里:

case ALGORITHM_DFQ:
{
    quant_tool.data_free_quant();
    quant_tool.model_file = "test_dfq_fp32.tmfile";
    if (quant_tool.scale_file.empty()){
        quant_tool.scale_file = "table_minmax.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;
}

可以看到和其他量化算法相比较,DFQ 只是多了一句 quant_tool.data_free_quant(),如下:

结合上面理论的讲解,应该比较容易猜测 quant_tool.data_free_quant() 应该主要在做量化前处理工作:跨层均衡和高偏移吸收,进入到 data_free_quant() 接口看代码,各种判断+内嵌循环让代码的可读性并不友好。下面慢慢道来。

刚开始主要做一些初始化的工作,就不多说了。

tengine 的实现 主要是对 DW Conv 和 Direct Conv 两种类型的算子进行 DFQ 的前处理均衡,这里你可能会有一些疑问,原理部分一直再说 BN、RELU 的一些数学特性,到这里怎么就变成 CONV 了呢,这主要是由于算子融合,tengine 或者 ncnn 在做模型转换成 tmfile(tengine) 或 bin/params(ncnn) 的时候都会做一些图优化的工作,CONV+BN+RELU 的结构是最基础需要融合成大算子的,所以到了 tengine 的 DFQ 实现里你就看不到针对于 BN、RELU 的一些处理计算了,但是用到的跨层均衡化思想和 DFQ 是一致的。

下面来看对于 Direct Conv 的处理:

/// Direct Conv
auto op_name0 = graphn->node_list[node_input_id]->op.type;      
// 识别到 OP_CONV
if (node_proto[node_input_id].output_node_list.size() == 1 && op_name0 == OP_CONV){
    struct conv_param* conv_param0 = (struct conv_param*)graphn->node_list[node_input_id]->op.param_mem;
    if (conv_param0->group != conv_param0->output_channel || conv_param0->group == 1){
        node_proto[i].pass = 1;               // layer1                                // 待均衡的相邻两层
        node_proto[node_input_id].pass = 1;   // layer0
        // layer0 min/max range    
        struct node* nodeP = graphn->node_list[node_input_id];
        struct tensor* input_tensor = get_ir_graph_tensor(graphn, nodeP->input_tensors[1]);
        uint16_t dims0 = input_tensor->dims[0];
        uint16_t dims123 = input_tensor->dims[1] * input_tensor->dims[2] * input_tensor->dims[3];
        std::vector<float> layer0_max(dims0, 0.0f);
        std::vector<float> layer0_min(dims0, 0.0f);
        std::vector<float> layer0_range(dims0, 0.0f);
        float* data_layer0 = (float*)input_tensor->data;
        for (int d0 = 0; d0 < dims0; d0++){
            for (int d1 = 0; d1 < dims123; d1++){
                if (data_layer0[dims123 * d0 + d1] >= layer0_max[d0])
                    layer0_max[d0] = data_layer0[dims123 * d0 + d1];
                if (data_layer0[dims123 * d0 + d1] < layer0_max[d0])
                    layer0_min[d0] = data_layer0[dims123 * d0 + d1];}
        }
        for (int d0 = 0; d0 < dims0; d0++){
            layer0_range[d0] = layer0_max[d0] - layer0_min[d0];
        }
        // layer1 min/max range
        nodeP = graphn->node_list[i];
        input_tensor = get_ir_graph_tensor(graphn, nodeP->input_tensors[1]);
        dims0 = input_tensor->dims[0];
        uint16_t dims1 = input_tensor->dims[1];
        uint16_t dims23 = input_tensor->dims[2] * input_tensor->dims[3];
        std::vector<float> layer1_max(dims1, 0.0f);
        std::vector<float> layer1_min(dims1, 0.0f);
        std::vector<float> layer1_range(dims1, 0.0f);
        float* data_layer1 = (float*)input_tensor->data;
        for (int d0 = 0; d0 < dims0; d0++){
            for (int d1 = 0; d1 < dims1; d1++){
                for (int d2 = 0; d2 < dims23; d2++){
                    if (data_layer1[dims1 * dims23 * d0 + dims23 * d1 + d2] >= layer1_max[d1]){
                        layer1_max[d1] = data_layer1[dims1 * dims23 * d0 + dims23 * d1 + d2];
                    }
                    if (data_layer1[dims1 * dims23 * d0 + dims23 * d1 + d2] < layer1_min[d1]){
                        layer1_min[d1] = data_layer1[dims1 * dims23 * d0 + dims23 * d1 + d2];}}}
        }
        for (int d0 = 0; d0 < dims1; d0++){
            layer1_range[d0] = layer1_max[d0] - layer1_min[d0];
        }
        //////////////////////////////////////////////////////////////////////////////////
        // layer ops sqrt
        float* ops_range = new float[dims1];
        for (int ops = 0; ops < dims1; ops++){
            ops_range[ops] = sqrt(layer0_range[ops] * layer1_range[ops]);    // 计算通道最合适的缩放Range    r = √ (r1r2)
        }
        // 计算缩放Scale
        float* S01 = new float[dims1];
        float* S01_F = new float[dims1];  
        for (int ops = 0; ops < dims1; ops++){
            if (ops_range[ops] == 0){
                S01[ops] = 0.0;
            }
            else{
                S01[ops] = layer0_range[ops] / ops_range[ops];
            }
            if (layer0_range[ops] == 0)
                S01_F[ops] = 0.0;
            else
                S01_F[ops] = ops_range[ops] / layer0_range[ops];
        }
        //////////////////////////////////////////////////////////////////////////////////
        // layer0 output 缩放均衡
        nodeP = graphn->node_list[node_input_id];
        input_tensor = get_ir_graph_tensor(graphn, nodeP->input_tensors[1]);
        dims0 = input_tensor->dims[0];
        dims123 = input_tensor->dims[1] * input_tensor->dims[2] * input_tensor->dims[3];
        for (int d0 = 0; d0 < dims0; d0++){
            for (int d1 = 0; d1 < dims123; d1++){
                data_layer0[dims123 * d0 + d1] = data_layer0[dims123 * d0 + d1] * S01_F[d0];}
        }
        input_tensor = get_ir_graph_tensor(graphn, nodeP->input_tensors[2]);
        dims0 = input_tensor->dims[0];
        float* data_layer0_bias = (float*)sys_malloc(sizeof(float) * dims0);
        data_layer0_bias = (float*)input_tensor->data;
        for (int d0 = 0; d0 < dims0; d0++){
            data_layer0_bias[d0] = data_layer0_bias[d0] * S01_F[d0];
        }
        // layer1 output 缩放均衡
        nodeP = graphn->node_list[i];
        input_tensor = get_ir_graph_tensor(graphn, nodeP->input_tensors[1]);
        dims0 = input_tensor->dims[0];
        dims1 = input_tensor->dims[1];
        dims23 = input_tensor->dims[2] * input_tensor->dims[3];
        for (int d0 = 0; d0 < dims0; d0++){
            for (int d1 = 0; d1 < dims1; d1++){
                for (int d2 = 0; d2 < dims23; d2++){
                    data_layer1[dims1 * dims23 * d0 + dims23 * d1 + d2] = data_layer1[dims1 * dims23 * d0 + dims23 * d1 + d2] * S01[d1];}}
        }
        delete[] S01;    // free the memory
        S01 = NULL;
        delete[] S01_F;
        S01_F = NULL;
        delete[] ops_range;
        ops_range = NULL;
    }
}

然后.....循环循环直至均衡整网生成 dfq_fp32_tmfile,然后......还没开始就结束了,如果我没看错的话这就是目前 tengine DFQ 相对于 MIN-MAX 量化实现的不同之处 (意思是后续量化逻辑和 MIN-MAX 一致,tengine DFQ 不同的地方就是输入的 fp32 tmfile 权重数据是经过跨层均衡的),但这也只是实现了 DFQ 论文里第(1)个 tricks,其他几个 tricks 并没有揉进去,这有点不讲武德。

当然这应该也是有待完善的地方,到这里原理和实现暂时就介绍完了。


以上详细分享了高通 DFQ 量化的原理和实现,希望我的分享能对你的学习有一点帮助。


logo_show.gif

相关文章
|
10天前
|
传感器 人工智能 监控
智慧电厂AI算法方案
智慧电厂AI算法方案通过深度学习和机器学习技术,实现设备故障预测、发电运行优化、安全监控和环保管理。方案涵盖平台层、展现层、应用层和基础层,具备精准诊断、智能优化、全方位监控等优势,助力电厂提升效率、降低成本、保障安全和环保合规。
智慧电厂AI算法方案
|
11天前
|
机器学习/深度学习 人工智能 监控
智慧交通AI算法解决方案
智慧交通AI算法方案针对交通拥堵、违法取证难等问题,通过AI技术实现交通管理的智能化。平台层整合多种AI能力,提供实时监控、违法识别等功能;展现层与应用层则通过一张图、路口态势研判等工具,提升交通管理效率。方案优势包括先进的算法、系统集成性和数据融合性,应用场景涵盖车辆检测、道路环境检测和道路行人检测等。
|
11天前
|
传感器 人工智能 监控
智慧化工厂AI算法方案
智慧化工厂AI算法方案针对化工行业生产过程中的安全风险、效率瓶颈、环保压力和数据管理不足等问题,通过深度学习、大数据分析等技术,实现生产过程的实时监控与优化、设备故障预测与维护、安全预警与应急响应、环保监测与治理优化,全面提升工厂的智能化水平和管理效能。
智慧化工厂AI算法方案
|
1月前
|
人工智能 安全 决策智能
OpenAI推出实验性“Swarm”框架,引发关于AI驱动自动化的争论
OpenAI推出实验性“Swarm”框架,引发关于AI驱动自动化的争论
|
1月前
|
机器学习/深度学习 人工智能 算法
"拥抱AI规模化浪潮:从数据到算法,解锁未来无限可能,你准备好迎接这场技术革命了吗?"
【10月更文挑战第14天】本文探讨了AI规模化的重要性和挑战,涵盖数据、算法、算力和应用场景等方面。通过使用Python和TensorFlow的示例代码,展示了如何训练并应用一个基本的AI模型进行图像分类,强调了AI规模化在各行业的广泛应用前景。
31 5
|
1月前
|
算法 搜索推荐 Java
java 后端 使用 Graphics2D 制作海报,画echarts图,带工具类,各种细节:如头像切割成圆形,文字换行算法(完美实验success),解决画上文字、图片后不清晰问题
这篇文章介绍了如何使用Java后端技术,结合Graphics2D和Echarts等工具,生成包含个性化信息和图表的海报,并提供了详细的代码实现和GitHub项目链接。
109 0
java 后端 使用 Graphics2D 制作海报,画echarts图,带工具类,各种细节:如头像切割成圆形,文字换行算法(完美实验success),解决画上文字、图片后不清晰问题
|
1月前
|
机器学习/深度学习 人工智能 开发框架
【AI系统】AI 学习方法与算法现状
在人工智能的历史长河中,我们见证了从规则驱动系统到现代机器学习模型的转变。AI的学习方法基于深度神经网络,通过前向传播、反向传播和梯度更新不断优化权重,实现从训练到推理的过程。当前,AI算法如CNN、RNN、GNN和GAN等在各自领域取得突破,推动技术进步的同时也带来了更大的挑战,要求算法工程师与系统设计师紧密合作,共同拓展AI技术的边界。
84 1
|
1月前
|
人工智能
用AI人模拟社会学实验,居然成功了?斯坦福、NYU用GPT-4模仿人类,准确度惊人!
斯坦福大学和纽约大学的研究团队利用GPT-4模型成功模拟了人类在社交互动中的行为模式,实验结果显示AI能以惊人准确度模仿人类对话,甚至在在线论坛和社交媒体上与真人难以区分。这一突破不仅展示了AI在社会学研究中的巨大潜力,还引发了对AI伦理和透明度的深入探讨。尽管存在一些局限性和挑战,这项研究为未来社会学实验提供了新工具和方法。[论文地址:https://docsend.com/view/qeeccuggec56k9hd]
61 2
|
1月前
|
人工智能 算法 前端开发
无界批发零售定义及无界AI算法,打破传统壁垒,累积数据流量
“无界批发与零售”是一种结合了批发与零售的商业模式,通过后端逻辑、数据库设计和前端用户界面实现。该模式支持用户注册、登录、商品管理、订单处理、批发与零售功能,并根据用户行为计算信用等级,确保交易安全与高效。
|
1月前
|
人工智能 算法 JavaScript
无界SaaS与AI算力算法,链接裂变万企万商万物互联
本文介绍了一种基于无界SaaS与AI算力算法的商业模式的技术实现方案,涵盖前端、后端、数据库及AI算法等关键部分。通过React.js构建用户界面,Node.js与Express搭建后端服务,MongoDB存储数据,TensorFlow实现AI功能。提供了项目结构、代码示例及部署建议,强调了安全性、可扩展性和性能优化的重要性。
下一篇
无影云桌面