Extreme Intelligence AI | 量化实现分享四:无数据量化是否好闻?高通公司的 DFQ 量化算法实现 - 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 量化的原理和实现,希望我的分享能对你的学习有一点帮助。
【公众号传送】 《【模型推理】量化实现分享四:Data-Free Quantization 香不香?详解高通 DFQ 量化算法实现》
推荐阅读
-
CVPR 2021 | pixelNeRF:基于 NeRF 的多视角 3D 重建网络
-
清华大学提出了一种新的三维重建方法:O²-Recon,用二维扩散模型补充残缺的三维物体
-
基于图像的三维物体重建:深度学习时代的最新技术和训练趋势概览
-
基于图像的 3D 物体深度学习时代的性能比较和未来研究方向:最新技术与趋势概览
-
基于深度学习的 3D 重建算法综述
-
三维重建算法概述 | 传统 + 深度学习
-
探索基于 NeRF 的 3D 现实重建技术
-
什么是 3D 重构?三维重建有什么用?-以下为部分截图,点击文末名片关注我的公众号AI科技星球发码321即可获得(必发码321)
-
A100 实现了无需三维卷积的三维重建方法,每帧重建时间仅为 70 毫秒
-
GPS 的全称是什么?