GPU加速Pinterest推薦模型,引數量增加100倍,使用者活躍度提高16%

語言: CN / TW / HK

作為圖片屆的“Twitter”,Pinterest首頁展示給使用者的圖片也離不開背後的推薦模型。近期,其工程團隊通過將機器學習服務從CPU轉移到GPU上,使得Pinterest可以上線比之前大100倍的推薦模型。上線大模型給模型質量帶來了階躍式的提升,最終將Pinterest首頁feed流的使用者活躍度提高了16%。

在本文中,他們分享瞭如何只經過微小的投入和延遲的成本實現這一優化目標,這包括優化特定運算元,整合記憶體傳輸,通過CUDA Graph技術在裝置上執行靜態圖,以及分散式系統的重新設定。他們的實踐證明,要想提高模型效果,模型引數就得變大,而使用GPU服務方案的經濟效益遠超CPU服務方案。

來源|Pinterest Engineering

翻譯|鄭澤康

Pinterest的使命是給每個人帶來創造他們所熱愛生活的靈感。為了實現這一使命,我們所有產品中都包含的一個關鍵元件就是各式各樣的推薦模型,它們負責在合適的時間給合適的人展示合適的內容。我們的推薦模型是通過高階演算法進行訓練的一系列機器學習模型,用於理解使用者在Pinterest上花費時間時的行為,這些推薦模型是通過定製的機器學習模型伺服器(Scorpion Model Server, 即SMS)來執行的。

SMS 上需要面對十分艱鉅的技術挑戰,基於3000多億個Pin的語料庫,它必須在毫秒內為4億多個使用者提供相關推薦。之前,SMS在CPU上進行模型推理,其核心經過了多年的優化以滿足我們對延遲和基礎設施成本的嚴格要求,但即使最新一代的CPU也幾乎達到了SMS服務的極限。我們必須非常審慎地確保,每次因模型變化而帶來的延遲和基礎設施成本的增加都是合情合理的。

機器學習領域裡模型引數和計算量增加的趨勢讓問題變得更加嚴峻。在推薦系統中,具有1000億引數量的模型已經很常見,並在業內常被提及。

在Pinterest,我們採用了稍微不同的方式,通過使用諸如Transformer的現代模型架構來擴大模型。在更大模型下,我們立即觀測到模型準確率發生的質變——其大幅提升了Pinner(譯註:Pinterest使用者)的參與度。但是,在CPU伺服器上執行這些現代模型架構幾乎讓成本和延遲都提升了40倍,這一代價難以承受。因此,我們轉而尋求使用GPU來加速模型推理,從而可以使用合理的成本來執行這些模型。

1

優化

當我們嘗試那些開箱即用的GPU服務時,很快意識到在經濟高效地利用GPU執行推薦模型服務之前需要對其優化。我們首先使用分析工具來分析在模型推理過程中發生了什麼,在仔細觀察分析結果時,我們注意到時間線上有大量的小CUDA Kernel在執行。

這是推薦系統模型的預期行為,其中數百個特徵在模型後期階段進行特徵拼接之前需要單獨處理。然而,對於大量的小運算元,啟動CUDA Kernel帶來的開銷比計算開銷還要昂貴。與訓練時的batchsize相比,模型服務時的batchsize相對更小,進而加劇了這一問題。

上圖分別是模型優化前和優化後的分析結果。CUDA Kernel時間線(用紅框突出顯示)表明,Kernel的啟動開銷(藍色塊之間的間隙)顯著減少,因此GPU得到了更好得利用,它將大部分時間花費在Kernel執行中。

2

減少小op的數量

我們採取的第一個方法是找出減少小op數量的機會。我們尋找常被使用的模型元件,並儘可能對其優化。其中一個例子是Embedding的查表模組,它包含兩個計算步驟:原始ID到索引的查詢,索引到Embedding的查詢。由於我們有大量特徵,這兩步操作會被重複數百次。通過使用cuCollections (http://github.com/NVIDIA/cuCollections) 以支援GPU上原始ID的雜湊表,並實現了自定義的Embedding查詢模組以合併多個查詢操作,我們顯著地減少了op的數量。在執行一些優化後,馬上看到了更優的效能表現。

3

合併記憶體拷貝

同樣,當我們在主機和GPU視訊記憶體之間搬運Tensor時,也存在整合資料傳輸的機會。通用推薦模型裡的一個候選樣例通常會使用數百個特徵作為輸入,對於每一次推理,各個特徵作為一個單獨的tensor被拷貝到GPU視訊記憶體中。雖然在主機和GPU視訊記憶體之間搬運資料非常快,但是為每個請求排程數百個cudaMemcpy()函式呼叫的開銷很快成為瓶頸。

從主機單獨拷貝Tensor到 GPU vs 一次拷貝整個記憶體緩衝區

為了解決這個問題,我們應用了一個簡單的優化將cudaMemcpy()呼叫次數從幾百次減少到一次:不再使用Torch框架將Tensor單獨移動到GPU上,而是先將所有Tensor的資料放置到一塊預先分配好的連續記憶體緩衝區中,並一次性將緩衝區內容拷貝到GPU裡,最終通過指向GPU視訊記憶體緩衝區的不同偏移量來重構得到GPU上的所有張量。

該優化帶來的代價是要顯式管理預先分配的記憶體緩衝區的生命週期,以及需要對不同資料型別手動處理GPU視訊記憶體對齊。但作為結果,P50資料拷貝延遲從10ms 降低到1ms以下,這證明了該優化帶來的複雜性是可以接受的。

4

利用CUDA Graph

為了進一步優化模型推理過程,我們使用CUDA Graph (http://pytorch.org/blog/accelerating-pytorch-with-cuda-graphs/)  來完全消除剩餘小op的開銷。CUDA Graph允許我們將模型推理過程捕捉為靜態圖,而不是單獨排程。它可以讓整個計算作為一個單元進行執行,而不產生任何Kernel啟動開銷。我們支援將CUDA Graph作為模型服務的一個新後端。一開始載入模型時,模型服務執行一次模型推理以構建圖例項,該例項可以針對實時流量重複執行。

CUDA Graph在一個批處理內(下圖)執行Kernel,而不是在一個序列中逐個執行(上圖),這減少了Kernel之間CPU啟動帶來的開銷。圖表來自:http://pytorch.org/blog/accelerating-pytorch-with-cuda-graphs/

CUDA Graph自身的一些限制給我們的模型服務帶來了額外的複雜性。其中最大的限制是CUDA Graph要求所有Tensor都具有靜態形狀和佈局,這對動態大小批次和不規則的變長Tensor帶來了挑戰。然而,我們相信為了獲得更好效能進行的權衡是值得的,並且我們可以將輸入Tensor補齊到精心挑選的靜態形狀。

5

使用更大的batchsize

最後,我們重新研究了SMS為模型推理執行的批處理策略。SMS支援動態批處理,可以讓多個請求合併成更大的批次。它通常能帶來更好的吞吐量,但代價是需要較短的時間以等待請求序列收集足夠多的項(item)。對於CPU上的ML推斷,我們通常想要將請求分成小批量來提高並行度以減小延時。然而對於GPU,其延時對batchsize並不敏感,使用更大的batchsize對GPU提升工作負載效率更重要。

這種batchsize的需求使我們重新思考了SMS裡的分散式系統設定。對於CPU上的ML推斷,我們使用scatter-gather結構將原始請求拆分,並在多個葉子結點上並行執行,以獲得更小的延時。

此外,該架構允許我們為每個葉子結點分配一個固定的資料切片,以優化特徵提取期間的快取命中率。然而,由於GPU傾向使用大batchsize,因此刪除root layer直接在原始請求中使用大batchsize更有意義。最終我們使用了CacheLib的混合快取,它用DRAM和SSD來補償與scatter-gather 架構設定相比的快取容量損失。

6

結果

我們首先測量了模型單次推理的延時。我們使用c5.18x AWS例項提供CPU服務,g5.4 AWS例項提供GPU服務。

CPU延時隨著batchsize線性增長,在較小的batchsize下,GPU延時幾乎相同,此時Kernel啟動開銷佔延時主導因素。然而隨著batchsize增加,實際計算時間主導了延時,GPU延時以亞線性形式增長。在實踐中,GPU效率提升的亮點在於SMS可以使用更大的batch工作。結合了所有優化之後,我們獲得了驚人的結果,相比 CPU 服務,GPU服務在較大batchsize下的每批次的延遲提升了100倍以上。

我們的服務指標也展示了令人印象深刻的結果。通過優化模型操作,重新設定分散式系統以及優化資料傳輸並使用CUDA Graph,我們以30%的更低延時上線了77倍大的模型,並以適當成本增加了20%的吞吐量。

最後,兩個數量級效率提升開啟了Pinterest最先進的推薦模型架構。可以看到,模型質量顯著地提升直接轉化為更高的使用者參與度。在過去的一年,我們以適當的基礎設施成本將一個主要產品的使用者參與度提升了16%。很快,我們將推出比我們的CPU模型大100倍的最大模型。

7

結論

將基於CPU模型服務轉換成基於GPU服務的過程很複雜,但這是我們在Pinterest使用最先進推薦模型的一個必要步驟。我們能夠以適當的成本提供大100倍的推薦模型,這為我們的機器學習工程師給Pinner解鎖更相關和響應更迅速的推薦內容提供了基礎。

(本文經授權後編譯釋出,原文:http://medium.com/pinterest-engineering/gpu-accelerated-ml-inference-at-pinterest-ad1b6a03a16d )

歡迎下載體驗 OneFlow v0.8.0 最新版本: http://github.com/Oneflow-Inc/oneflow/