【華為昇騰】DB_ResNet精度調優 Siammask性能調優 模型眾籌項目復盤


本系列博客內容均進行了脫敏處理,對於典型問題的相關處理經驗可供其他開發者參考

項目背景介紹

        為了滿足當今飛速發展的深度神經網絡對芯片算力的需求,華為公司於2018年推出了昇騰系列AI處理器,這一系列芯片的算力擔當是使用達芬奇架構的AI Core,它能夠對INT8 INT4 FP16進行高性能的計算。

        下圖就具體刻畫了昇騰處理器中AI Core的結構和主要組成部分,其中主要包括計算單元、存儲系統和控制單元的三部分。因硬件結構不是本文的重點,故不展開介紹,詳情可參見昇騰官方文檔

 

       而為使昇騰AI處理器發揮出極佳的性能,華為公司還設計了一套完善的軟件解決方案——CANN(異構計算架構),包含了計算資源、性能調優的運行框架以及功能多樣的配套工具,下圖就具體展示了昇騰AI處理器的軟件架構(該圖適用於我使用的CANN 3.x版本)。據我了解,在后續的版本中,CANN被重新架構為昇騰計算語言接口、昇騰計算服務層、昇騰計算編譯層、昇騰計算執行層、昇騰計算基礎層;如有時間,我將會額外寫博文進行介紹。

   依托這樣的昇騰AI處理器和昇騰AI異構計算架構CANN,華為構建了昇騰生態並豐富了上層應用。而我們就對接從軟件使能層到上層應用層的中間環節,針對復雜場景文本檢測模型DB_ResNet和視頻目標跟蹤模型Siammask,完成代碼的遷移、在基於昇騰AI處理器的Atlas800 服務器上進行訓練、調優,並在Atlas300上進行推理和測試,最終產出兩個適配於昇騰平台的模型。在這一過程中,遇到了一些具體的問題,在此進行介紹。

DB_ResNet的精度坑

        DB_ResNet在項目的早期就完成了開源代碼的調試和tensorflow版本代碼的開發,在GPU上訓練出了與預期相符的模型。但遷移到NPU平台后,出現了訓練不收斂,loss明顯波動的現象;在去除隨機性並對齊雙方參數配置后,仍出現如下圖的問題。如圖1展示的是使用tensorboard繪制的NPU上訓練過程,圖2展示的是在相同配置下,GPU上的訓練過程,相關討論詳見如下鏈接

 

       我們可以明顯的看到遷移后的模型表現與預期不符,未能很好的收斂,我們在后續的項目進程中持續的就此問題進行探索。

探索一:排除動態算子topk的影響

       在我們開發該版本代碼時,平台暫不支持動態shape。而我們的模型使用BCELoss解決樣本不均衡問題,在區分正負樣本時使用了tf.math.topk這一算子。從代碼流程的角度上看,假設每一批次中的正樣本點有k個,則模型需要根據前向的輸出篩選出得分最高的3k個負樣本參與后續計算。而這就代表着,topk算子的輸入k需要根據每一批次數據的情況進行動態的調整。這碰到了算子不支持的問題。

       我們在早期的遷移中,為了減少開發周期,使用了一個較為簡單的方案規避。即預先統計所有樣本中正樣本點數量的平均值,在模型計算中,都篩選3*avg_count數量的負樣本點進行計算。在早期的討論中,就懷疑過是由於這樣粗略的規避方案,引發了模型的性能問題。因此我們實現了更優的規避方案,即在數據預處理階段通過計算正樣本的比率,並引入mask變量表明正樣本數量;再把topk這個函數換為tf.sort,通過mask*sort的返回值實現topk的功能。

        該方案有效地規避了動態算子的問題,並貢獻在issue中供其他開發人員參考,但並未能解決收斂性問題。除了懷疑topk算子代碼遷移的問題,我們還要考慮的似乎有更多。

探索二:使用data dump尋找出現下溢的算子

        其實在早期的討論中,我們就與華為方達成了一些共識,我們都認為AI Core算子以fp16的精度計算,模型中存在的類型轉換可能引發了下溢的問題,造成了模型的不收斂。

        在我們團隊內部,也對模型計算出的梯度進行了可視化的展現,如圖4所示,從整體來看,npu計算出的梯度分布相似於gpu上的分布,但仍有一定數量的較小值和0值。囿於時間限制,我們沒有進行更深入的探究,而是做了一些其它嘗試,最終得到了loss_scale + 截斷的訓練策略,使得模型得到了一定程度上的收斂,但最終的F值只有0.75,與GPU訓練出的0.82有一定的差距。

 

          鑒於華為內部團隊的開發經驗,類似的精度問題或收斂問題往往是由於某一算子的底層實現漏洞或錯誤使用造成的。因此我們在接下來大量的時間內,都在使用tfdbg配合data dump工具進行分析,試圖找出出現下溢的算子。我們使用了修改融合規則、替換算子文件等手段,進行了如issue[3][4]的討論,先后懷疑了batchnorm、adam、relu等的問題,但最后都無功而返,我們的懷疑往往因為考慮不夠全面而被打回。與此同時,我們分階段的測試前向和后向的過程,發現前向傳播的全流程輸出基本符合預期,而關於loss計算部分,我們借助data dump卻未找到明顯的漏洞。那個產生下溢的算子,似乎已經很難找到了。

探索三:放棄經驗主義 探究loss scale

         基於經驗難以找到溢出算子,我們不妨回歸loss scale的方案,為什么使用這樣的方法就能夠使得不收斂的模型產生一定的收斂效果呢?

         我們使用了最朴素的做法,將關鍵步驟的中間結果全部打印出來。我們發現,由於整網中流動的數字都較小,尤其在經過log(1+x) 函數之后,模型中流淌較多的超過fp16表征范圍的數字,這就引起了fp32向fp16轉換時的下溢。這一現象在data dump過程中也有一定的表現,但當時認為參數的方差差距較小,對模型的影響較小,從而忽視了相關問題,這也是需要在SOP中改進的。

         使用loss scale是解決這一問題的有效辦法。我們之前根據調優經驗,設定全局loss scale為212,但這個值其實是可以在訓練的各個階段進行調整的。經過華為老師們的不斷努力,我們通過手動調優的方式,最終得到了最優模型F值為0.76,照初始精度有了一定的提升。在平台提供的工具中,也有自動化動態調整loss scale的接口,但在項目開發周期內,由於該功能在處理MaxPoolGrad 算子時存在溢出而無法使用。因此可以后續跟進研發進程,進行更多的測試。

小結

  總的來說,在訓練過程中產生 精度劣化的原因主要有算子融合、算子精度不足、常量折疊、fp16精度不足、網絡中存在“放大器結構”等等。使用tfdbg進行data dump,配合華為提供的比對工具可以較為有效的找到出現問題的算子。但造成精度問題的原因不全是算子問題,當前比對的維度包括余弦相似度、最大絕對誤差、累積相對誤差、歐氏相對距離、KLD散度、標准差幾個維度。僅通過這幾個維度能否很好的描繪出各種場景的精度損失原因,這是值得思考和討論的問題。華為也在不斷地更新精度分析工具,甚至推出了 一鍵比對工具 點個贊!

Siammask的速度坑

  該模型作為針對視頻場景下的復雜網絡,訓練所需的成本極高;其需要的總數據集達數百G,在GPU的訓練總時長也達5天之久;而我們在遷移到NPU平台上訓練后,遇到了較為明顯的速度問題。具體來看,在訓練過程中每50個step就會發生阻塞,停下來報tf_adapter/kernels/geop_npu.cc,訓練50個step的時間是2s左右,但是停下來報信息的時間可能超過20分鍾,整網單步的平均訓練時間達50s之久!GPU和NPU二者之間有60余倍的速度差異!相關討論詳見issue

       而我們也就針對上述的現象進行了長久的嘗試,最終速度達到了與V100上平齊的水平,在下文中,我們將對各階段的嘗試進行更具體的介紹。而在該模型復現的過程中,我們也發現開源代碼與論文刊登不匹配的問題,我們也將把一些佐證材料在本節中進行具體的展示。

探索一:排除不支持算子extract_image_patches的影響

      在我們開發的版本中,tf.image. extract_image_patches在算子編譯過程中報錯,該算子可以視作卷積的一部分,用滑動窗口取patch,但沒有filter,也沒有將patch和filter作卷積的操作。在我們的開發版本中,由於泛化性的問題無法使用,而我們也針對這樣的情況進行了替代性開發。

       我們提出了三種實現方式,一是使用卷積的方式進行替代;二是使用py_func的方式進行實現,三是使用內循環的方式實現功能。我們也將這三個方案的代碼分享到了issue中,供其他團隊參考使用。

      經過測試之后,我們決定使用自實現的方案二作為替代手段訓練,我們在GPU上測試未發現明顯的性能損失,遷移到NPU后也成功打通了整網。但我們通過profiling工具分析后發現,僅這一步操作在NPU上就有22s的耗時,而這大概率和CPU與NPU之間頻繁的信息交互相關,針對不支持算子的替代性方案引入了很大的速度損耗。此外通過profiling,我們也發現resize_bilinear算子的支持度不夠,耗時較長。

       在明確了問題源之后,我們的項目主管老師推動工程師團隊對該算子進行了泛化能力的開發,使其能夠處理我們模型中相應規格的數據。產出新版本算子后,我們替換算子文件重新訓練,每步的平均執行時間從50s降至7.639s,有了很大的提升。

探索二:設置大小循環,進一步加快模型速度

      在等待工程師團隊開發出新版本的同時,我們也針對耗時較長的resize_bilinear單算子在AICore上嘗試使用大小循環的方式進行訓練,通過減少Host和Device之間的通信,更加充分的發揮NPU的計算性能;如issue中討論的內容所示,我們設置每1000輪進行一次CPU與NPU的通信,且不適用TDT的方式運行。我們發現在單算子的測試場景下,每步的平均耗時從設置前的34s下降至設置后的21.9s;雖仍與GPU上的運行速度有較大的差異,但證明大小循環的方式對於性能提升是有效的。

       而當新版本算子文件替換之后,我們也將上述的經驗應用於實踐中,使得端到端的平均單步訓練時間從7.639s下降至6.393s,模型性能進一步提高。

探索三:優化TranData、Five2Four算子,模型速度符合預期

      項目主管老師后續繼續跟進模型性能問題,在CANN 3.2.0版本上替換或增加了five_2_four.py, trans_data_negative_target_tc.py, trans_data_common_func.py等三個算子文件后,SiamMask網絡在NPU上訓練了5000step,每step耗時下降到約1.2~1.6秒之間,與競品耗時相當;而整網的耗時問題也得到了閉環解決,如圖所示,通過一步步的調優,模型速度最終符合預期。

小結

   總的來看,CANN軟件棧還是很大程度上的激發了昇騰AI處理器的計算能力,最后的性能比V100還要好。但為了適配AI Core的計算需求,CANN會在網絡中插入TransData、trans_Cast等等算子,部分插入的算子會引入很大的性能問題。且在實際運行中還偶有AICore和AICPU之間的頻繁交互,這都可能造成性能劣化。雖如此,但還是要給華為的技術支持老師們點個贊!能夠對我們提出的問題很快的響應,也能針對我們的需求進行適配開發。相信隨着CANN在越來越多的模型上得到應用,潛在的問題會被一個個解決,能夠適配應用到越來越多的場景中。

回顧與反思

1. 作為PM不能太執拗於過往經驗,要更多的結合實際場景進行分析;

2. 混合精度計算本就是一個復雜問題,尤其在復雜網絡中。如無必要,勿增實體;在之后的項目中,要提前審視技術實現難度(但混合精度的確速度有提升,在業務代碼未很好優化的前提下,還比V100的速度快);

3. 盡量不使用官方已經不維護的內容,無論是tf.slim還是tfdbg;

4. 要學習華為技術支持的響應速度,遇到算子精度和性能相關的問題, 在modelzoo上提issue,響應速度很快!官方修復后會更新在社區版本中,然后就可以進行驗證了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM