onnxruntime評測模型精度不一致問題排查

gpu精度不一致問題追查
在做模型轉換相關工作,但是最近發現轉換後的模型精度評測的時候會出現兩次評測精度不一致, 模型轉換是從caffe轉換成量化後的onnx模型,中間會有幾個臨時模型,分別爲original_onnx, 這個是直接轉換的模型,一個optimized_onnx,是對原onnx模型進行結構優化/整合後的模型,另一個爲quantized_onnx模型,既量化後的模型,我們發現對量化後對模型進行精度評測的時候,多次評測的結果不同,而且是同樣的轉換代碼的情況下,同樣的評測圖片集,最終評測結果不一致,在以百分比現實的精度結果的小數點後一兩位就開始有差別,即誤差會達到千分之一以上。

首先說明下這個問題排查的重要性,千分之一,看似不多,實則在深度學習領域,不算少了,能通過對模型對一番更改,某些場景下提升千分之幾的精度,已經可以發論文了,不過這不是重點,重點是我們想通過定期精度評測來評估轉換代碼對精度對影響,既然是評估影響,那麼就要排除干擾,顯然對統一模型同樣數據集進行評測後對精度不一致是一種干擾項,我們必須找到這種不一致的原因,以排除其影響。

1.精度問題首先在一個評測腳本里被發現,而這個腳本會同時執行多個模型的評測任務,自然想到執行單個的模型任務排除干擾,也降低復現開銷,由於工程裏已經有另外一套單個模型的評測腳本,自然就直接用了那個腳本進行評測,發現用那個單個腳本多次評測並未出現精度不一致的問題。

2. 考慮到評測是對一系列批次的圖片進行推理,然後對每批次對圖片計算結果的吻合度,想到是不是評測集(我們用對imagenet,5萬張)對加載順序不一致導致對,經過排查,我們的評測集圖片始終是相同的,而且評測的順序也是保證一致的。 

3. 基於2,如果評測結果有異常,那麼對同一張圖片推理的結果也應該有異常,於是又實驗了對同一張圖,推理若干次,幾萬次吧,因爲最初觀察到對每次評測都會復現精度不一致,而每次評測圖片數量5萬,那麼推理5萬是能達到這個量級的。結果沒有復現。

4. 那會不會是某些圖片造成的呢。我們隨機選取了一些圖片重複3的實驗,沒有復現。

5. 綜上,目前有問題的是多模型評測的時候出現問題,而單模型評測沒復現,考慮到這兩者的差異,因爲多模型評測腳本是順序執行,不存在模型之間的干擾,而二者有一個區別是,單模型評測是基於已經轉換後的模型進行評測,而多模型評測腳本每次評測的時候會從caffe模型轉換開始,轉換然後評測,那麼往前回溯一下,會不會是轉換過程引入的不一致。經過對比生成的幾個模型的md5值,發現確實轉換出的模型就有差異,浮點模型沒有差異,但是定點模型有差異,那麼精度不一致的問題很可能出現在模型定點化的過程中,而這個過程中最有可能是校準過程。

6. 要分析校準過程中的問題,先要弄清校準過程。由於模型在量化過程中數值分佈會發生變化,浮點模型中對分佈範圍很隨意,從前一個節點到後一個節點對傳遞過程中都基本能保持仍在合法對浮點範圍內,不會溢出或者截斷,但是量化模型,比如INT8的量化模型,所有數值範圍都是-128~127,很容易在某些節點的運算後發生數值溢出和截斷,而這種行爲是對模型執行精度有很大影響,因而我們希望通過校準過程,在那些能改變輸入數值分佈的節點前(比如conv) 插入校準節點以對該節點的輸入數據分佈進行統計,根據統計結果對原節點的+bias和*scale參數做修改,或者另外添加參數(可能需要修改節點實現),如此在量化模型執行過程中,通過該節點的計算將對數據分佈進行改變,以期不顯著降低精度。校準過程中會通過校準圖片(最好和後續推理和評測同分布的圖片)進行推理,在推理的過程中,校準圖片會更新自身參數,比如最簡單的是max校準方法,統計出流經該節點的輸入數值的最大值。
7. 基於6,後面的過程就比較清晰了,把所有節點的輸出dump出來,然後比對,看是從哪個節點開始計算結果不一致。在比對前,我們懷疑過,某個節點的實現造成(比較有可能的是校準和卷積,求和節點)(這種計算問題浮點數並行計算然後求和,由於浮點值的表示範圍有限,當求和順序不一致會導致結果不一致(因爲中間結果存在溢出和精度截斷可能)),c++到python的浮點數傳遞/轉換可能有問題(不清楚具體傳遞和轉換機制,只是懷疑正好遇到了某臨界值,或者c++的值傳遞過程中遭遇溢出將隨機值填入尾部等等),爲了排除後者,我們直接比對c++部分的節點輸出值的二進制表示,發現是某一個conv的節點起計算輸出的值會有差異,而之後的節點會將差異傳遞最終導致結果的不一致。

8. 再分析該conv節點,將該conv節點的input以及其他參數屬性都dump出來觀察,發現出問題的conv節點的輸入和屬性都是一樣的,但是輸出不一樣,並且輸出的值的差異會按一定概率分佈在幾個值上,差異並不是完全隨機的。

9. 仔細查看該conv節點的計算實現,是onnxruntime的實現,onnxruntime/onnxruntime/core/providers/cuda/nn/conv.cc,將其中影響計算的一些參數dump並比對,最終發現是每次執行的時候gpu卡的卷積算法選擇不同導致的。每當節點的輸入shape或者weight的shape改變,conv都會重新執行

cudnnFindConvolutionForwardAlgorithmEx搜尋高效的卷積算法,具體說明參見https://docs.nvidia.com/deeplearning/sdk/cudnn-api/index.html#cudnnFindConvolutionForwardAlgorithmEx,可搜索的卷積計算算法類型有https://docs.nvidia.com/deeplearning/sdk/cudnn-api/index.html#cudnnConvolutionFwdAlgo_t其中幾種會導致計算結果的不一致,具體原因和gpu架構設計有關,工作內容的原因,暫沒有繼續深究了。所以要解決精度不一致的問題,有兩種辦法,一種是將算法選擇限定在保證一致性的算法範圍內,另一種直接屏蔽算法選擇,用默認的

CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_GEMM算法,顯然這兩種都會導致推理評測性能的損失,不過經過測試,性能損失在5%以內,可以接受。若要追求高性能,可以在精度評測需要保證一致性的時候將算法選擇限制住,在推理的時候將算法選擇打開,不過我們的應用場景內沒有這種需求,所以就粗暴的限制了算法的選擇。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章