愛奇藝 CTR 場景下的 GPU 推理性能優化

01

   背景介紹

GPU 目前大量應用在了愛奇藝深度學習平臺上。GPU 擁有成百上千個處理核心,能夠並行的執行大量指令,非常適合用來做深度學習相關的計算。在 CV(計算機視覺),NLP(自然語言處理)的模型上,已經廣泛的使用了 GPU,相比 CPU 通常能夠更快、更經濟的完成模型的訓練和推理。
CTR (Click Trough Rate) 模型廣泛使用在推薦、廣告、搜索等場景中,用來估算用戶點擊某個廣告、視頻的概率。在 CTR 模型的訓練場景中已經大量使用了 GPU,在提升訓練速度的同時和降低了所需的服務器成本。
但在推理場景下,當我們直接把訓練好的模型通過 Tensorflow-serving 部署在 GPU 之後,發現推理效果並不理想。表現在:
  1. 推理延遲高,CTR 類模型通常是面向終端用戶的,對於推理延遲非常敏感。
  2. GPU 利用率低,計算能力未能全部發揮出來。


02

   原因分析

分析工具

  1. Tensorflow Board,tensorflow 官方提供的工具,能夠可視化的查看計算流圖中各個階段的耗時,並彙總算子的總耗時。
  2. Nsight 是 NVIDIA 面向 CUDA 開發者提供的開發工具套件,能夠對 CUDA 程序進行相對底層的跟蹤、調試和性能分析。

分析結論

典型的 CTR 模型輸入,包含大量的稀疏類特徵(如設備 ID、最近瀏覽視頻 ID 等)。Tensorflow 的 FeatureColumn 會對這些特徵進行處理,首先進行 identity/hash 操作,得到 embedding table 的 index。再經 embedding lookup 和求均值等操作後,得到對應的 embedding tensor。多個特徵對應的 embedding tensor 拼接後得到一個新的 tensor,再進入後續的 DNN/Transformer 等結構。
因此每個稀疏特徵在模型的輸入層,都會啓動若干個算子。每個算子會對應着一次或者幾次 GPU 計算,即 cuda kernel。每個 cuda kernel 包括兩個階段,launch cuda kernel(啓動 kernel 所必需的 overhead) 和 kernel 執行(在 cuda 核心上真正執行矩陣計算)。稀疏特徵 identity/hash/embedding lookup 對應的算子計算量較小,launch kernel 的耗時往往超過 kernel 執行的時間。一般來說 CTR 模型包含了幾十到幾百個稀疏特徵,理論上就會有數百次 launch kernel,是當前主要的性能瓶頸。
在使用 GPU 訓練 CTR 模型時,沒有遇到這個問題。因爲訓練本身是一個離線任務,不關注延遲,所以訓練時候的 batch size 都可以很大。雖然仍會進行多次 launch kernel,只要執行 kernel 時候計算的樣本數量足夠多,lauch kernel 的開銷平均到每個樣本上的時間就很小了。而對於在線推理的場景,如果要求 Tensorflow Serving 收到足夠的推理請求併合並批次後再進行計算,那麼推理延遲就會很高。


03

   優化方案

我們的目標是在基本不改變訓練代碼,不改變服務框架的前提下,進行性能優化。我們很自然的想到兩個方法,減少啓動的 kernel 數量,提高 kernel 啓動的速度。

算子融合

基本操作就是將多個連續的操作或算子合併成一個單一的算子,一方面可以減少 cuda kernel 啓動的次數,另一方面可以把計算過程中一些中間結果存在寄存器或者共享內存,只在算子的最後把計算結果寫入全局的 cuda 內存。
主要有兩種方法
  1. 基於深度學習編譯器的自動融合
  2. 針對業務的手動算子融合

自動融合

我們嘗試了多種深度學習編譯器,如 TVM/TensorRT/XLA,實測可以實現 DNN 部分少量算子的融合,如連續的 MatrixMat/ADD/Relu。由於 TVM/TensorRT 需要導出 onnx 等中間格式,需要修改原有模型的上線流程。所以我們通過 tf.ConfigProto() 開啓 tensorflow 內置的 XLA 來進行融合。
但自動融合對稀疏特徵相關的算子並沒有很好的融合效果。

手動算子融合

我們很自然的想到,如果有多個特徵在輸入層被相同類型的 FeatureColumn 組合所處理,那麼我們可以實現一個算子,把多個特徵的輸入拼接成數組作爲算子的輸入。算子的輸出是一個張量,這個張量的 shape 和原本多個特徵分別計算後再拼接得到的張量 shape 一致。
以原有的 IdentityCategoricalColumn + EmbeddingColumn 組合爲例,我們實現了 BatchIdentiyEmbeddingLookup 算子,達到相同的計算邏輯。
爲了方便算法同學使用,我們封裝了一個新的 FusedFeatureLayer,來代替原生的 FeatureLayer;除了包含融合算子,還實現了以下邏輯:
  1. 融合的邏輯在推理時候生效,訓練時候走原來的邏輯。
  2. 需要對特徵進行排序,保證相同類型的特徵可以排在一起。
  3. 由於每個特徵的輸入均爲變長,在這裏我們額外生成了一個索引數組,來標記輸入數組的每個元素屬於哪個特徵。
對於業務來說,只需要替換原來的 FeatureLayer 即可達到融合的效果。
實測原本數百次的 launch kernel,經過手動融合後縮減到了 10 次以內。大大減少了啓動 kernel 的開銷。


MultiStream 提高 launch 效率

TensorFlow 本身是一個單流模型,只包含一個 Cuda Stream Group(由  Compute Stream、H2D Stream,D2H Stream 和 D2D Stream 組成)多個 kernel 只能在同一個 Compute Stream 上串行執行效率較低。即使通過多個 tensorflow 的 session 來 launch cuda kernel,在 GPU 側仍然需要排隊。

爲此 NVIDIA 的技術團隊維護了一個自己的 Tensorflow 分支,支持多個 Stream Group 同時執行。以此來提高 launch cuda kernel 的效率。我們將此特性移植到了我們的 Tensorflow Serving 裏。


在 Tensorflow Serving 運行時候,需要開啓 Nvidia MPS,減少多個 CUDA Context 間的相互干擾。

小數據拷貝優化

在前邊優化基礎上,我們針對小數據拷貝進一步做了優化。當 Tensorflow Serving 從請求中反序列化出中各個特徵的值後,會多次調用 cudamemcpy,將數據從 host 拷貝到 device。調用次數取決於特徵數量。
大部分 CTR 類業務,實測當 batchsize 較小時和,先將數據在 host 側拼接,再一次性的調用 cudamemcpy 效率會更高一些。


合併批次

GPU 場景下需要開啓批次合併。默認情況下 Tensorflow Serving 是不對請求進行合併的。爲了更好的利用 GPU 的並行計算能力,讓一次前向計算時候可以包含更多的樣本。我們在運行時候打開了 Tensorflow Serving 的 enable_batching 選項,來對多個請求進行批次合併。同時需要提供一個 batch config 文件,重點配置以下參數,以下是我們總結的一些經驗。
  1. max_batch_size:一個批次允許的最大請求數量,可以稍微大一點。
  2. batch_timeout_micros:合併一個批次等待的最長時間,即使該批次的數量未達到max_batch_size,也會立即進行計算(單位是微秒),理論上延遲要求越高,這兒設置的越小,最好設置在 5 毫秒以下。
  3. num_batch_threads:最大推理併發線程,在開啓了 MPS 之後,設置成 1 到 4 都可以,再多延遲會高。
在這裏需要注意的是,CTR 類模型大部分輸入的稀疏特徵都爲變長特徵。如果客戶端沒有專門做約定,可能出現多個請求中在某個特徵上的長度不一致。Tensorflow Serving 有一個默認的 padding 邏輯,給較短的請求在對應的特徵上補 0。而對於變長特徵使用 -1 來表示空,默認的補 0 會事實上改變原有的請求的含義。
比如用戶 A 最近的觀看視頻 id 爲 [3,5],用戶 B 最近的觀看視頻 id 爲 [7,9,10]。如果默認補齊,請求變成 [[3,5,0], [7,9,10]],在後續的處理中,模型會認爲 A 最近觀看了 id 爲 3,5,0 的 3 個視頻。
因此我們修改了 Tensorflow Serving 響應的補齊邏輯,遇到這種情況會補齊爲 [[3,5,-1], [7,9,10]]。第一行的含義仍然是觀看了視頻 3,5。

04

   最終效果

經過各種上述各種優化,在延遲和吞吐量滿足了我們的需求,並落地在推薦個性化 Push、瀑布流業務上。業務效果如下:
  1. 吞吐量相比原生Tensorflow GPU 容器提升 6 倍以上
  2. 延遲和 CPU 基本一致,滿足業務需求
  3. 支持相同的 QPS 時候,成本降低 40% 以上

也許你還想看
愛奇藝數據湖實戰 - Hive數倉平滑入湖
稀疏大模型在愛奇藝廣告排序場景中的實踐
愛奇藝圖片格式演進


本文分享自微信公衆號 - 愛奇藝技術產品團隊(iQIYI-TP)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

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