欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

Extreme Intelligence AI | 量化实现分享四:无数据量化是否好闻?高通公司的 DFQ 量化算法实现 - DFQ 量化实现

最编程 2024-03-30 19:09:08
...

  我们还是以 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 并没有揉进去,这有点不讲武德。

      src=http___dingyue.ws.126.net_2020_1126_41356390g00qkdr6p01fmd200ed0083g00id00ac.gif&refer=http___dingyue.ws.126.gif

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

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


【公众号传送】 《【模型推理】量化实现分享四:Data-Free Quantization 香不香?详解高通 DFQ 量化算法实现