吞吐提升30倍:CV流水線走向全棧並行化

陶然陶然發表於2023-02-13

   引言

  史丹佛教授、Tcl 語言發明者 John Ousterhout 曾寫過一本書《軟體設計的哲學》,系統討論了軟體設計的通用原則和方法論,整書的核心觀點是:軟體設計的核心在於降低複雜性。

  實際上,這個觀點也適用於涉及底層硬體適配的軟體設計。

  以視覺模型開發為例,以往視覺模型開發過程中,人們一般會更加關注模型本身的最佳化來提升速度與效果。而對於影像前處理(預處理)和後處理階段,人們則很少關注。

  當模型計算即模型訓練和推理主階段的效率越來越高時,影像的前後處理階段愈發成為影像處理任務的效能瓶頸。  

  具體而言,在傳統的影像處理流程中,前後處理部分通常都是用 CPU 進行操作的,這會導致整個流程中 50% 到 90% 以上的工作負荷都和前後處理相關,從而它們會成為整個演算法流程的效能瓶頸。

   01 主流CV庫的侷限性

  上述問題是目前市面上的主流 CV 庫在應用場景上的主要侷限性,也就是說,對底層硬體依賴的不一致性導致了複雜性和效能瓶頸。正如 John Ousterhout 總結複雜性原因時所道:複雜性源於依賴性。  

  主流的影像處理庫 OpenCV,其應用場景非常廣泛,但在實際使用的時候也會面臨一些問題。

  比如用 OpenCV 的 CPU 版本先做訓練再做推理的時候,在推理階段可能需要一個效能比較高的版本。

  因為在訓練場景裡,前後處理與模型推理可以在時間上進行覆蓋,從而覆蓋前處理的時間。但推理流水線中,模型只包含前向推理,且經過 Tensor RT 加速後耗時急劇減小,這時前處理的耗時佔比會非常高,難以被模型推理所覆蓋。

  要想減少推理場景的耗時,提高推理場景的效能,一般會用 OpenCV 的 GPU 版本進行加速。

  但是 OpenCV 的 CPU 版本和 GPU 版本之間可能會出現結果不一致的情況。典型的例子是 resize 運算元,其在 CPU 版本和 GPU 版本上對於差值的計算方式是不一致的。

  OpenCV 在訓練和推理的時候會使用不同版本的運算元,在訓練的時候一般用 CPU,因為其 CPU 運算元覆蓋度比較高,在推理的時候一般用 GPU,因為效能比較好。因此,這也會導致結果對齊的問題。也就是說,當用 CPU 做模型訓練,並用 GPU 做模型推理的時候,會導致最終的輸出結果無法對齊。

  其次,部分 GPU 運算元的效能會有所退化。在 OpenCV 中,部分 GPU 運算元本身的耗時比較大,從而導致整個運算元的效能回退,甚至差於 CPU 版本。

  第三,OpenCV 的 GPU 運算元覆蓋度是有限的,部分運算元只有 CPU 版本。還有一些 GPU 運算元在引數、資料型別等方面的覆蓋度也沒有 CPU 版本高,從而帶來使用上的限制。

  最後,如果在使用中將 CPU 運算元和 GPU 運算元互動使用,就會帶來大量的 CPU 和 GPU 之間的資料複製和同步操作,進而導致整體的加速效能不夠高。

  另外一個常用的影像處理庫是 TorchVision。

  TorchVision 在做模型推理的時候,有些運算元缺乏 C++ 介面,從而在呼叫的時候缺乏靈活性。如果要生成 C++ 版本,必須透過 TorchScript 生成。這會導致使用上的許多不便,因為在流程中間插入其它庫的運算元來互動使用會帶來額外開銷和工作量。TorchVision 還有一個缺點是運算元的覆蓋度不高。

  以上就是目前的主流 CV 庫的侷限性。

   02 統一 CV 流水線

  既然前後處理的效能瓶頸主要在於使用 CPU 計算,而模型計算階段使用 GPU 的技術已經越來越成熟。

  那麼,一個很自然的解決方案是,用 GPU 對前後處理進行加速,對整個演算法流水線將會有非常大的效能提升。

  為此,NVIDIA英偉達攜手位元組跳動開源了影像預處理運算元庫 CV-CUDA。CV-CUDA 能高效地在 GPU 上執行,運算元速度能達到 OpenCV 的百倍左右。

  2023 年 1 月 15 日,9:30-11:30,由 NVIDIA英偉達 主辦的『CV-CUDA 首次公開課』,邀請了來自 NVIDIA英偉達、位元組跳動、新浪微博的 3 位技術專家(張毅、盛一耀、龐鋒),就相關主題進行深度分享,本文彙總了三位專家的演講精華。  

  採用 GPU 替換 CPU 有很多好處。首先,前後處理的運算元遷移到 GPU 上以後,可以提高運算元的計算效率。

  其次,由於所有的流程都是在 GPU 上進行的,可以減少 CPU 和 GPU 之間的資料複製。

  最後,把 CPU 的負載遷移到 GPU 上後,可以降低 CPU 的負載,將 CPU 用於處理其它需要很複雜邏輯的任務。  

  將整個流程遷移到 GPU 上後,對於整個流水線可以帶來近 30 倍的提升,從而節省計算開銷,降低運營成本。

  透過圖中資料對比可以看到,在相同的伺服器和引數配置下,對於 30fps 1080p 影片流,OpenCV 最多可以開 2-3 個並行流,PyTorch(CPU)最多可以開 1.5 個並行流,而 CV-CUDA 最多可以開 60 個並行流。可以看出整體效能提升程度非常大,涉及到的前處理運算元有 resize、padding、normalize 等,後處理運算元有 crop、resize、compose 等。

   03 非同步化

  為什麼 GPU 可以適配前後處理的加速需求?得益於模型計算與前後處理之間的非同步化,並與 GPU 的平行計算能力相適應。  

  我們以模型訓練和模型推理的預處理非同步化分別進行說明。

  1. 模型訓練的預處理非同步化  

  模型訓練可以分為兩部分,第一個是資料準備,第二個是模型計算。

  目前主流的機器學習框架,比如 PyTorch、TensorFlow,它們在資料準備和模型計算之間是非同步的。以 PyTorch 為例,其會開啟多個子程式進行資料的準備。

  如圖中所示,其包含兩個狀態,即模型計算和資料準備,兩者存在時間先後關係,比如當 D0 完成之後,就可以進行 B0,以此類推。

  從效能角度看,我們期望資料準備的速度能夠跟得上模型計算的速度。但實際情況中,一些資料讀取和資料預處理過程的耗時很長,導致相應的模型計算在進行前有一定的空窗期,從而導致 GPU 利用率下降。

  資料準備可以分成資料讀取和資料預處理,這兩個階段可以序列執行,也可以並行執行,比如在 PyTorch 的框架下是序列執行的。

  影響資料讀取的效能因素有很多,比如資料儲存介質、儲存格式、並行度、執行程式數等。

  相比之下,資料預處理的效能影響因素比較簡單,就是並行度。並行度越高,資料預處理的效能越好。也就是說,讓資料預處理與模型計算非同步化,並提高資料預處理的並行度,可以提高資料預處理的效能。

  2. 模型推理的預處理非同步化  

  在模型推理階段,其效能有兩個指標,第一個是吞吐,第二個是延時。一定程度上,這兩個指標是彼此互斥的。

  對於單個 query 而言,當 server 接收到資料之後,會進行資料的預處理,再進行模型推理。所以對於單個 query 而言,一定程度上它是一個序列的過程。

  但這樣做在效率上是很低的,會浪費很多計算資源。為了提高吞吐量,很多推理引擎會採用和訓練階段一樣的策略,將資料準備和模型計算非同步化。在資料準備階段,會積累一定量的 query,組合成一個 batch,再進行後續的計算,以提高整體的吞吐量。

  從吞吐而言,模型推理和模型訓練是比較類似的。把資料預處理階段從 CPU 搬到 GPU 上,可以得到吞吐上的收益。

  同時,從延時的角度上看,對於每條 query 語句,如果能夠減少預處理過程所花費的時間,對於每條 query 而言,其延時也會得到相應的縮短。

  模型推理還有一個特點是,其模型計算量比較小,因為只涉及前向計算,不涉及後向計算。這意味著模型推理對資料預處理的需求更高。

  3. 核心問題:CPU 資源競爭

  假設有足夠的 CPU 資源用於計算,理論上預處理不會成為效能瓶頸。因為一旦發現效能跟不上,只需要增加程式做預處理操作即可。

  因此,只有當 CPU 出現資源競爭的時候,資料預處理才可能成為效能瓶頸。

  在實際業務中,CPU 資源競爭的情況是很常見的,這會導致後續訓練和推理階段中 GPU 利用率降低,進而訓練速度降低。

  隨著 GPU 算力不斷增加,可以預見,對資料準備階段的速度要求會越來越高。

  為此,將預處理部分搬上 GPU,來緩解 CPU 資源競爭問題,提高 GPU 利用率,就成了很自然的選擇。

  總體而言,這種設計降低了系統的複雜性,將模型流水線的主體與 GPU 直接適配,對於提高 GPU 和 CPU 的利用率都能帶來很大的助益。同時,它也避免了不同版本之間的結果對齊問題,減少了依賴性,符合 John Ousterhout 提出的軟體設計原則。

   04 CV-CUDA

  把預處理以及後處理過程搬上 GPU 需要滿足多個條件。

  第一是其效能至少要好於 CPU。這主要基於 GPU 的高併發計算能力。

  第二是對預處理的加速,不能造成對其它流程比如模型推理的負面影響。對於第二個需求,CV-CUDA 的每個運算元都留有 stream 和 CUDA 視訊記憶體的介面,從而可以更合理地配置GPU的資源,使得在 GPU 上執行這些預處理運算元的時候,不會過於影響到模型計算本身。

  第三,網際網路企業中有非常多樣的業務需求,涉及的模型種類很多,相應的預處理邏輯也是種類繁多,因此預處理運算元需要開發成定製化的,從而有更大的靈活性來實現複雜的邏輯。  

  總體而言,CV-CUDA 從硬體、軟體、演算法、語言等方面對模型流水線中的前後處理階段進行了加速,以及整個流水線的統一。

  1. 硬體

  硬體方面,CV-CUDA 基於 GPU 的平行計算能力,能夠大幅提高前後處理的速度和吞吐,減少模型計算的等待時間,提高 GPU 的利用率。

  CV-CUDA 支援 Batch 和 Variable Shape 模式。Batch模式支援批處理,可以充分發揮 GPU 的並行特性,而 OpenCV 不管是 CPU 還是 GPU 版本都只能對單張圖片進行呼叫。

  Variable Shape 模式是指在一個batch當中,每張圖片的長和寬可以不一樣。網路上的圖片一般長寬都是不一致的,主流框架的做法是把長和寬分別 resize 到同一個大小,再對同一長寬的圖片打包為一個 batch,再對 batch 進行處理。CV-CUDA 可以直接把不同長和寬的影像直接放在一個 batch 中進行處理,不僅能提升效率,使用上也很方便。

  Variable Shape 的另外一層含義是在對影像進行處理的時候,可以指定每張圖片的某些引數,比如 rotate,對一個batch的影像可以指定每張圖片的旋轉角度。

  2. 軟體

  軟體方面,CV-CUDA 開發了大量的軟體最佳化方法來做進一步的最佳化,包括效能最佳化(比如訪存最佳化)和資源利用最佳化(比如視訊記憶體預分配),從而可以高效地執行在雲端的訓練和推理場景中。

  首先是視訊記憶體預分配設定。OpenCV在 呼叫 GPU 版本的時候,部分運算元會在內部執行 cudaMalloc,這會導致耗時大量增加。在 CV-CUDA 中,所有的視訊記憶體預分配都是在初始化階段執行,而在訓練和推理階段,不會進行任何視訊記憶體分配操作,從而提高效率。

  其次,所有的運算元都是非同步操作的。CV-CUDA 對大量kernel進行了融合,從而減少 kernel 的數量,進而減少 kernel 的啟動時間以及資料複製擦做,提高整體執行的效率。

  第三,CV-CUDA 還對訪存進行了最佳化,比如合併訪存、向量化讀寫等,提高頻寬的利用率,還利用 shared memory 來提高訪存讀寫效率。

  最後,CV-CUDA 在計算上也做了很多最佳化,比如 fast math、warp reduce/block reduce 等。

  3. 演算法

  演算法方面,CV-CUDA 的運算元都是獨立設計的、定製化的,從而可以支援非常複雜的邏輯實現,並且方便進行使用和除錯。

  如何理解獨立設計?影像處理庫的運算元呼叫有兩種形式,一種是整體性的 pipeline 形式,只能獲取 pipeline 的結果,比如 DALI,另一種是模組化的獨立運算元的形式,可以獲取每一個運算元的單獨結果,比如 OpenCV。CV-CUDA 採用了和 OpenCV 相同的呼叫形式,在使用和除錯上會比較方便。

  4. 語言

  語言方面,CV-CUDA 支援豐富的 API,可以無縫將前後處理銜接訓練和推理場景。

  這些API包括常用的 C、C++、Python 的介面等,這使得我們可以同時支援訓練和推理場景,它還支援 PyTorch、TensorRT 的介面,在未來,CV-CUDA 還將支援 Triton、TensorFlow、JAX 等介面。

  推理階段,可以直接用 Python 或 C++ 的介面進行推理,只要保證推理的時候將前後處理、模型、GPU 放在一個 stream 上即可。

   05 應用案例

  透過展示 CV-CUDA 在 NVIDIA 英偉達、位元組跳動、新浪微博的應用案例,我們可以體會到 CV-CUDA 帶來的效能提升有多顯著。  

  首先是 NVIDIA英偉達展示的圖片分類案例。

  在圖片分類的流水線中,首先是 JPEG decode,其對圖片進行解碼;綠色部分是前處理步驟,包含 resize、convert data type、normalize 和 reformat;藍色部分是使用 PyTorch 的前向推理過程,最後對分類的結果進行打分和排序。  

  將 CV-CUDA 與 OpenCV 的 CPU 版本與 GPU 版本進行效能對比可以發現,OpenCV 的 GPU 版本相比於 CPU 版本能得到較大的效能提升,而透過應用 CV-CUDA,又能將效能翻倍。比如 OpenCV 的 CPU 運算元每毫秒處理的圖片數是 22 張,GPU 運算元每毫秒處理的圖片數是 200 多張,CV-CUDA 則每毫秒可以處理 500 多張圖片,其吞吐量是 OpenCV 的 CPU 版本的 20 多倍,是 GPU 版本的兩倍,效能提升很明顯。

  其次是位元組跳動展示的 OCR1、OCR2、影片多模態三個案例。  

  在模型訓練上,可以看到在 OCR1、OCR2、影片多模態三個任務上,使用了 CV-CUDA 後獲得了 50% 到 100% 的效能收益。

  為什麼有這麼大的效能收益?實際上這三個任務比較大的一個共同點是,它們的圖片預處理邏輯非常複雜,比如 decode、resize、crop 等,而且這些還是大類,實際上每個運算元類中還可能有很多小類或子類預處理。對於這三個任務而言,其涉及到預處理鏈路上的資料增強種類可能就有十幾種,所以其對於 CPU 的計算壓力非常大,如果能把這部分計算搬到 GPU上,CPU 的資源競爭就會明顯下降,整體吞吐也能提高很多。

  最後是新浪微博展示的影片處理案例。  

  對於影片處理流程,傳統的做法是把影片幀先在 CPU 環境中解碼,把原始的位元組流解碼成圖片資料,再做一些常規操作,比如 resize、crop 等,再把資料上傳到 GPU 上做具體的模型計算。  

  而 CV-CUDA 的處理方式是將 CPU 解碼之後放在記憶體中的位元組流上傳到 GPU 上,並且預處理也位於 GPU 上,從而跟模型計算進行無縫銜接,不需要從視訊記憶體和記憶體之間的複製操作。  

  圖中給出了採用 OpenCV(奇數)和 CV-CUDA(偶數)各自的處理時間,藍色指的是模型的消耗時間,橙色指的是解碼的消耗時間,綠色指的是預處理的消耗時間。

  OpenCV 可以分為 CPU 解碼和 GPU 解碼兩種模式,CV-CUDA 只採用 GPU 解碼模式。

  可以看到,對於 CPU 解碼的 OpenCV,OpenCV 的解碼和預處理都比 CV-CUDA 的耗時高得多。  

  再看 OpenCV 採用 GPU 解碼的情況,可以看到,OpenCV 和 CV-CUDA 在模型和解碼部分的耗時是接近的,而預處理方面仍然差距很大。  

  在 pipeline 整體對比上,CV-CUDA 也有很明顯的優勢,一方面 CV-CUDA 更節省 CPU 資源,也就是將 GPU 利用率打滿的情況下,CV-CUDA 只需要 OpenCV 的 10%CPU 配置;同時,CV-CUDA 也更節省 GPU 資源,在整體 pipeline 上,CV-CUDA 效率提升70%。

   06 未來展望

  CV-CUDA 在模型訓練和推理階段都能有效地解決 CPU 資源競爭的問題,從而能夠提高模型訓練和推理的效率。

  但如何正確理解 CV-CUDA 的優勢?需要理解其發揮作用的根本前提,並且其優勢相對於 CPU、OpenCV 並不是絕對的。

  首先, CV-CUDA 實際上也不是萬靈藥。比如在模型訓練階段,如果瓶頸不是在預處理上,而是在資料讀取、模型推理上。這時候,如果用 CV-CUDA 來替換原來的預處理方案,實際上也是沒有任何用處的。

  此外,在使用 CV-CUDA 的過程中,如果對預處理邏輯合理分配 CPU 和 GPU 的工作量,實際上有時候能夠達到更好的效能效果。

  比如,CPU 仍然可以進行圖片解碼和 resize,resize 之後再放到 GPU 上進行處理。

  為什麼把解碼和 resize 放到 CPU 上做?首先,對於圖片解碼而言,其實 GPU 的硬解碼單元是有限的。其次,對於 resize 而言,通常情況下,resize 都會把一張較大的圖片,轉換成一張較小的圖片。

  如果在 resize 之前,把資料複製到 GPU 上,可能會佔用很多的視訊記憶體資料搬運的頻寬。

  當然,CPU 和 GPU 之間的工作量具體怎麼分配,還是需要結合實際情況來判斷的。

  而最重要的原則是,不要將 CPU 和 GPU 之間的計算交替穿插進行,因為跨 device 傳輸資料都是有開銷的。如果交替過於頻繁,反而可能將計算本身帶來的收益抹平,進而導致效能不增反降。

  2022 年 12 月,CV-CUDA 釋出了 alpha 版本,其中包含 20 多個運算元,比如常用的 Flip、Rotate、Perspective、Resize 等。

  目前 OpenCV 的運算元更多,有數千個運算元,CV-CUDA 目前只對比較常用的運算元進行加速,後續會不斷增加新的運算元。

  今年 3 月 CV-CUDA 還會發布 beta 版本,會增加 20 多的運算元,達到 50 多個運算元。beta 版本將包含一些非常用的運算元,比如 ConvexHull、FindContours 等。  

   07 尾聲

  回過頭來看 CV-CUDA 的設計方案,可以發現,其背後並沒有太複雜的原理,甚至可以說一目瞭然。

  從複雜性的角度,這可以說是 CV-CUDA 的優點。《軟體設計的哲學》提過一個判斷軟體複雜性的原則——如果一個軟體系統難以理解和修改,那就很複雜;如果很容易理解和修改,那就很簡單。

  可以將 CV-CUDA 的有效性理解為,模型計算階段與 GPU 的適配性,帶動了前後處理階段與 GPU 的適配性。而這個趨勢,其實才剛剛開始。

來自 “ DataFunTalk ”, 原文作者:DataFun;原文連結:http://server.it168.com/a2023/0213/6789/000006789143.shtml,如有侵權,請聯絡管理員刪除。

相關文章