【模型推理】量化實現分享三:詳解 ACIQ 對稱量化演算法實現

極智視界發表於2021-12-29

歡迎關注我的公眾號 [極智視界],回覆001獲取Google程式設計規範

O_o>_<   o_OO_o~_~o_O

  大家好,我是極智視界,本文剖析一下ACIQ 對稱量化演算法實現,以 Tengine 的實現為例。

  這是量化實現的第三篇,前面還有一、二,有興趣的同學可以查閱

  (1) 《【模型推理】量化實現分享一:詳解 min-max 對稱量化演算法實現》;

   (2)《【模型推理】量化實現分享二:詳解 KL 對稱量化演算法實現》;

   ACIQ 和前面的量化策略類似,也是會擷取一個閾值 T,然後將 [-T, T] 對映到量化值域,不同的是尋找 T 的過程,本文不止講原理,也結合 tengine 講講量化策略的實現。下面開始。

1、ACIQ 量化策略原理

   ACIQ 量化策略在論文《Post training 4-bit quantization of convolutional networks for rapid-deployment》中被提出,先貼一下效果:

  上圖比對統一採用 8-bit 權值量化、4-bit 啟用值量化,在量化效率上 ACIQ 比 KL 量化過程快 4000 倍(unbelievable~),在量化精度上,可以看到除了 resnet-101,其他測試的網路量化效果均好於 KL 量化,可以說是效率和效果一個也不落。

   在文章的一開始,作者就寫道 Unlike traditional approaches that focus on the quantization at the network level, in this work we propose to minimize the quantization effect at the tensor level. 可以看出 ACIQ 是從 Tensor 級別出發的量化策略,整個推導邏輯主要是:

  (1) first, derive a generic expression for any given distribution for the expected MSE as a function of clipping value;

  (2) then use this expression to develop a specifific expression for each distribution;

  (3) finally, establish the optimal clipping values by solving the equations for which the derivative with respect to the clipping value are set to zero;

  通常在量化的時候需要做裁剪,以應對原始資料的長尾問題,假設 α 為截斷值,截斷可以表示為:

       

  ACIQ 需要一個較強先驗假設:Tensor (feature map) 服從拉普拉斯分佈或高斯分佈,然後採用最優化思想求解量化過程截斷值對應的最小量化損失,整個量化過程是將服從原始分佈的值對映到 2^M量化離散值域,M 為量化位元數,意思是將上面的 [-α, α] 的值域等分給 2^M,如下圖:

       

  假設原始分佈的概率密度函式為 f(x),截斷值 α 以及量化函式 Q(x),則量化前後的 L2 Loss 可以這麼計算:

       

  以上算式很明顯可以分為三個部分:

   (1) [負無窮, -α];

   (2) [-α, α];

  (3) [α, 正無窮];

  對於高斯分佈N(0, σ^2) 或者 拉普拉斯分佈 Laplace(0, b)) 這種 0 軸對稱分佈來說,(1) 和 (3) 是等價的,含義是 |x| 到 |α| 之間的均方誤差 (mean-square-error)。在做 [-α, α] 等分對映到 2^M 後,每個量化值會取每段中間的值 q1、q2、q3 ... q2^M,第 (2) 項就是中間截斷的累計誤差。現在整個量化過程轉化為求一個使 E[(X - Q(X))^2] 最小的截斷值 α (深度學習到最後都是數學問題啊~~),然後再結合先驗分佈,做一些公式的等價變換~變換~之後,得到最終的整體量化損失優化目標函式:

       

   數學上,要求目標函式的最小值 ==> 求偏導,令其為 0。

   對於拉普拉斯分佈來說,求偏導後的表示式為:

       

   對於高斯分佈來說,求偏導後的表示式為:

       

  最後不管對於拉普拉斯分佈還是高斯分佈來說,M 是你想量化的位元位,還有像 β (拉普拉斯分佈引數)、σ (高斯分佈引數) 這些都是已知值,自然可以求出我們想要的截斷值 α 了,對於對稱量化來說有了截斷值就 ok 了。

2、ACIQ 量化策略實現

   下面來看 ACIQ 在 tengine 中的實現。

   量化實現主要程式碼:

case ALGORITHM_ACIQ:{
    if (quant_tool.scale_file.empty()){
        quant_tool.scale_file = "table_aciq.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;
}

2.1 啟用值量化

   啟用值量化入口:

quant_tool.activation_quant_tool();

   首先就是求 min、max 值,這個過程和前面寫過的量化策略是一樣的邏輯,就不多說了,接著進 ACIQ 策略:

for (int i = 0; i < ir_graph->tensor_num; i++){
    struct tensor* t = ir_graph->tensor_list[i];
    if (t->tensor_type == TENSOR_TYPE_VAR || t->tensor_type == TENSOR_TYPE_INPUT){
        float absmax = 0.f;
        float act_scale = 1.f;
        int act_zero_point = 0;
        int emlement_num = t->elem_num;

        absmax = std::max(std::abs(max_activation[i]), std::abs(min_activation[i]));
        float threshold = compute_aciq_gaussian_clip(absmax, emlement_num, 8);
        act_scale = threshold / 127.f;

        /* the scale of softmax is always scale = 1 / 127.f */
        for (int j = 0; j < ir_graph->node_num; j++){
            struct node* noden = ir_graph->node_list[j];
            struct tensor* tensor_tmp = get_ir_graph_tensor(ir_graph, noden->output_tensors[0]);

            if (!(tensor_tmp->tensor_type == TENSOR_TYPE_INPUT || tensor_tmp->tensor_type == TENSOR_TYPE_VAR))
                continue;

            std::string tmp_op_name = get_op_name_from_type(noden->op.type);
            std::string cur_name = t->name;
            std::string tmp_name = tensor_tmp->name;

            if ((cur_name == tmp_name) && tmp_op_name == "Softmax"){
                act_scale = 1 / 127.f;
                break;}
        }
        fprintf(fp_aciq, "%s %f %d\n", ir_graph->tensor_list[i]->name, act_scale, act_zero_point);}
}

   關鍵是這個函式,tengine 裡預設先驗服從高斯分佈, int8 量化:

float threshold = compute_aciq_gaussian_clip(absmax, emlement_num, 8);

   來看一下它的實現:

static float compute_aciq_gaussian_clip(float absmax, int N, int num_bits)
{
    const float alpha_gaussian[8] = {0, 1.71063519, 2.15159277, 2.55913646, 2.93620062, 3.28691474, 3.6151146, 3.92403714};   // 當8-bit量化時,α=3.92403714

    const double gaussian_const = (0.5 * 0.35) * (1 + sqrt(3.14159265358979323846 * log(4))); 

    double std = (absmax * 2 * gaussian_const) / sqrt(2 * log(N));  

    return (float)(alpha_gaussian[num_bits - 1] * std);
}

   這樣就得到了截斷值,然後就可以求 scale 了:

act_scale = threshold / 127.f;

   這樣就完成了啟用值的量化。

2.2 權值&偏置量化

   權值&偏置的量化過程和前面介紹過的 MIN-MAX 和 KL 量化的邏輯一樣,這裡不再贅述。

   最後實踐一下,可以發現 ACIQ 的量化過程十分的快,比 KL 量化快 4000 倍不是瞎說的,主要是源於先驗的高斯分佈 alpha_gaussian、gaussian_const、std 這些值不需要進行搜尋。

  以上分享了 ACIQ 的量化原理和實現,希望我的分享能對你的學習有一點幫助。


【公眾號傳送】
【模型推理】量化實現分享三:詳解 ACIQ 對稱量化演算法實現

相關文章