基於TensorFlow Serving的深度學習在線預估

  • 2019 年 11 月 21 日
  • 筆記

一、前言

隨著深度學習在影像、語言、廣告點擊率預估等各個領域不斷發展,很多團隊開始探索深度學習技術在業務層面的實踐與應用。而在廣告CTR預估方面,新模型也是層出不窮: Wide and Deep[1]、DeepCross Network[2]、DeepFM[3]、xDeepFM[4],美團很多篇深度學習部落格也做了詳細的介紹。但是,當離線模型需要上線時,就會遇見各種新的問題: 離線模型性能能否滿足線上要求、模型預估如何鑲入到原有工程系統等等。只有準確的理解深度學習框架,才能更好地將深度學習部署到線上,從而兼容原工程系統、滿足線上性能要求。

本文首先介紹下美團平台用戶增長組業務場景及離線訓練流程,然後主要介紹我們使用TensorFlow Serving部署WDL模型到線上的全過程,以及如何優化線上服務性能,希望能對大家有所啟發。

二、業務場景及離線流程

2.1 業務場景

在廣告精排的場景下,針對每個用戶,最多會有幾百個廣告召回,模型會根據用戶特徵與每一個廣告相關特徵,分別預估該用戶對每條廣告的點擊率,從而進行排序。由於廣告交易平台(AdExchange)對於DSP的超時時間限制,我們的排序模組平均響應時間必須控制在10ms以內,同時美團DSP需要根據預估點擊率參與實時競價,因此對模型預估性能要求比較高。

2.2 離線訓練

離線數據方面,我們使用Spark生成TensorFlow[5]原生態的數據格式tfrecord,加快數據讀取。

模型方面,使用經典的Wide and Deep模型,特徵包括用戶維度特徵、場景維度特徵、商品維度特徵。Wide 部分有 80多特徵輸入,Deep部分有60多特徵輸入,經過Embedding輸入層大約有600維度,之後是3層256等寬全連接,模型參數一共有35萬參數,對應導出模型文件大小約11M。

離線訓練方面,使用TensorFlow同步 + Backup Workers[6]的分散式框架,解決非同步更新延遲和同步更新性能慢的問題。

在分散式ps參數分配方面,使用GreedyLoadBalancing方式,根據預估參數大小分配參數,取代Round Robin取模分配的方法,可以使各個PS負載均衡。

計算設備方面,我們發現只使用CPU而不使用GPU,訓練速度會更快,這主要是因為儘管GPU計算上性能可能會提升,但是卻增加了CPU與GPU之間數據傳輸的開銷,當模型計算並不太複雜時,使用CPU效果會更好些。

同時我們使用了Estimator高級API,將數據讀取、分散式訓練、模型驗證、TensorFlow Serving模型導出進行封裝。

使用Estimator的主要好處在於:

  1. 單機訓練與分散式訓練可以很簡單的切換,而且在使用不同設備:CPU、GPU、TPU時,無需修改過多的程式碼。
  2. Estimator的框架十分清晰,便於開發者之間的交流。
  3. 初學者還可以直接使用一些已經構建好的Estimator模型:DNN模型、XGBoost模型、線性模型等。

三、TensorFlow Serving及性能優化

3.1 TensorFlow Serving介紹

TensorFlow Serving是一個用於機器學習模型Serving的高性能開源庫,它可以將訓練好的機器學習模型部署到線上,使用gRPC作為介面接受外部調用。TensorFlow Serving支援模型熱更新與自動模型版本管理,具有非常靈活的特點。

下圖為TensorFlow Serving整個框架圖。Client端會不斷給Manager發送請求,Manager會根據版本管理策略管理模型更新,並將最新的模型計算結果返回給Client端。

圖1. TensorFlow Serving架構,圖片來源於TensorFlow Serving官方文檔

美團內部由數據平台提供專門TensorFlow Serving通過YARN分散式地跑在集群上,其周期性地掃描HDFS路徑來檢查模型版本,並自動進行更新。當然,每一台本地機器都可以安裝TensorFlow Serving進行試驗。

在我們站外廣告精排的場景下,每來一位用戶時,線上請求端會把該用戶和召回所得100個廣告的所有資訊,轉化成模型輸入格式,然後作為一個Batch發送給TensorFlow Serving,TensorFlow Serving接受請求後,經過計算得到CTR預估值,再返回給請求端。

部署TensorFlow Serving的第一版時,QPS大約200時,打包請求需要5ms,網路開銷需要固定3ms左右,僅模型預估計算需要10ms,整個過程的TP50線大約18ms,性能完全達不到線上的要求。接下來詳細介紹下我們性能優化的過程。

3.2 性能優化

3.2.1 請求端優化

線上請求端優化主要是對一百個廣告進行並行處理,我們使用OpenMP多執行緒並行處理數據,將請求時間性能從5ms降低到2ms左右。

#pragma omp parallel for  for (int i = 0; i < request->ad_feat_size(); ++i) {      tensorflow::Example example;      data_processing();  }

3.2.2 構建模型OPS優化

在沒有進行優化之前,模型的輸入是未進行處理的原格式數據,例如,渠道特徵取值可能為:'渠道1'、'渠道2' 這樣的string格式,然後在模型裡面做One Hot處理。

最初模型使用了大量的高階tf.feature_column對數據進行處理, 轉為One Hot和embedding格式。 使用tf.feature_column的好處是,輸入時不需要對原數據做任何處理,可以通過feature_column API在模型內部對特徵做很多常用的處理,例如:tf.feature_column.bucketized_column可以做分桶,tf.feature_column.crossed_column可以對類別特徵做特徵交叉。但特徵處理的壓力就放在了模型里。

為了進一步分析使用feature_column的耗時,我們使用tf.profiler工具,對整個離線訓練流程耗時做了分析。在Estimator框架下使用tf.profiler是非常方便的,只需加一行程式碼即可。

with tf.contrib.tfprof.ProfileContext(job_dir + 『/tmp/train_dir』) as pctx:     estimator = tf.estimator.Estimator(model_fn=get_model_fn(job_dir),                                        config=run_config,                                        params=hparams)    

下圖為使用tf.profiler,網路在向前傳播的耗時分布圖,可以看出使用feature_column API的特徵處理耗費了很大時間。

圖2. 優化前profiler記錄, 前向傳播的耗時佔總訓練時間55.78%,主要耗費在feature_column OPS對原始數據的預處理

為了解決特徵在模型內做處理耗時大的問題,我們在處理離線數據時,把所有string格式的原生數據,提前做好One Hot的映射,並且把映射關係落到本地feature_index文件,進而供線上線下使用。這樣就相當於把原本需要在模型端計算One Hot的過程省略掉,替代為使用詞典做O(1)的查找。同時在構建模型時候,使用更多性能有保證的低階API替代feature_column這樣的高階API。下圖為性能優化後,前向傳播耗時在整個訓練流程的佔比。可以看出,前向傳播的耗時佔比降低了很多。

圖3. 優化後profiler記錄,前向傳播耗時佔總訓練時間39.53%

3.2.3 XLA,JIT編譯優化

TensorFlow採用有向數據流圖來表達整個計算過程,其中Node代表著操作(OPS),數據通過Tensor的方式來表達,不同Node間有向的邊表示數據流動方向,整個圖就是有向的數據流圖。

XLA(Accelerated Linear Algebra)是一種專門對TensorFlow中線性代數運算進行優化的編譯器,當打開JIT(Just In Time)編譯模式時,便會使用XLA編譯器。整個編譯流程如下圖所示:

圖4. TensorFlow計算流程

首先TensorFlow整個計算圖會經過優化,圖中冗餘的計算會被剪掉。HLO(High Level Optimizer)會將優化後的計算圖生成HLO的原始操作,XLA編譯器會對HLO的原始操作進行一些優化,最後交給LLVM IR,進而根據不同的後端設備,生成不同的機器程式碼。

JIT的使用,有助於LLVM IR根據 HLO原始操作生成更高效的機器碼;同時,對於多個可融合的HLO原始操作,會融合成一個更加高效的計算操作。但是JIT的編譯是在程式碼運行時進行編譯,這也意味著運行程式碼時會有一部分額外的編譯開銷。

圖5. 網路結構、Batch Size對JIT性能影響[7]

上圖顯示為不同網路結構,不同Batch Size下使用JIT編譯後與不使用JIT編譯的耗時之比。可以看出,較大的Batch Size性能優化比較明顯,層數與神經元個數變化對JIT編譯優化影響不大。

在實際的應用中,具體效果會因網路結構、模型參數、硬體設備等原因而異。

3.2.4 最終性能

經過上述一系列的性能優化,模型預估時間從開始的10ms降低到1.1ms,請求時間從5ms降到2ms。整個流程從打包發送請求到收到結果,耗時大約6ms。

圖6. 模型計算時間相關參數:QPS:1308,50line:1.1ms,999line:3.0ms。下面四個圖分別為:耗時分布圖顯示大部分耗時控制在1ms內;請求次數顯示每分鐘請求大約8萬次,摺合QPS為1308;平均耗時時間為1.1ms;成功率為100%

3.3 模型切換毛刺問題

通過監控發現,當模型進行更新時,會有大量的請求超時。如下圖所示,每次更新都會導致有大量請求超時,對系統的影響較大。通過TensorFlow Serving日誌和程式碼分析發現,超時問題主要源於兩個方面,一方面,更新、載入模型和處理TensorFlow Serving請求的執行緒共用一個執行緒池,導致切換模型時無法處理請求;另一方面,模型載入後,計算圖採用Lazy Initialization方式,導致第一次請求需要等待計算圖初始化。

圖7. 模型切換導致請求超時

問題一主要是因為載入和卸載模型執行緒池配置問題,在源程式碼中:

uint32 num_load_threads = 0; uint32 num_unload_threads = 0;

這兩個參數默認為 0,表示不使用獨立執行緒池,和Serving Manager在同一個執行緒中運行。修改成1便可以有效解決此問題。

模型載入的核心操作為RestoreOp,包括從存儲讀取模型文件、分配記憶體、查找對應的Variable等操作,其通過調用Session的run方法來執行。而默認情況下,一個進程內所有Session的運算均使用同一個執行緒池。所以導致模型載入過程中載入操作和處理Serving請求的運算使用同一執行緒池,導致Serving請求延遲。解決方法是通過配置文件設置,可構造多個執行緒池,模型載入時指定使用獨立的執行緒池執行載入操作。

對於問題二,模型首次運行耗時較長的問題,採用在模型載入完成後提前進行一次Warm Up運算的方法,可以避免在請求時運算影響請求性能。這裡使用Warm Up的方法是,根據導出模型時設置的Signature,拿出輸入數據的類型,然後構造出假的輸入數據來初始化模型。

通過上述兩方面的優化,模型切換後請求延遲問題得到很好的解決。如下圖所示,切換模型時毛刺由原來的84ms降低為4ms左右。

圖8. 優化後模型切換後,毛刺降低

四、總結與展望

本文主要介紹了用戶增長組基於Tensorflow Serving在深度學習線上預估的探索,對性能問題的定位、分析、解決;最終實現了高性能、穩定性強、支援各種深度學習模型的在線服務。

在具備完整的離線訓練與在線預估框架基礎之後,我們將會加快策略的快速迭代。在模型方面,我們可以快速嘗試新的模型,嘗試將強化學習與競價結合;在性能方面,結合工程要求,我們會對TensorFlow的圖優化、底層操作運算元、操作融合等方面做進一步的探索;除此之外,TensorFlow Serving的預估功能可以用於模型分析,Google也基於此推出What-If-Tools來幫助模型開發者對模型深入分析。最後,我們也會結合模型分析,對數據、特徵再做重新的審視。

參考文獻

[1] Cheng, H. T., Koc, L., Harmsen, J., Shaked, T., Chandra, T., Aradhye, H., … & Anil, R. (2016, September). Wide & deep learning for recommender systems. In Proceedings of the 1st Workshop on Deep Learning for Recommender Systems (pp. 7-10). ACM.

[2] Wang, R., Fu, B., Fu, G., & Wang, M. (2017, August). Deep & cross network for ad click predictions. In Proceedings of the ADKDD'17 (p. 12). ACM.

[3] Guo, H., Tang, R., Ye, Y., Li, Z., & He, X. (2017). Deepfm: a factorization-machine based neural network for ctr prediction. arXiv preprint arXiv:1703.04247.

[4] Lian, J., Zhou, X., Zhang, F., Chen, Z., Xie, X., & Sun, G. (2018). xDeepFM: Combining Explicit and Implicit Feature Interactions for Recommender Systems. arXiv preprint arXiv:1803.05170.

[5] Abadi, M., Barham, P., Chen, J., Chen, Z., Davis, A., Dean, J., … & Kudlur, M. (2016, November). TensorFlow: a system for large-scale machine learning. In OSDI (Vol. 16, pp. 265-283).

[6] Goyal, P., Dollár, P., Girshick, R., Noordhuis, P., Wesolowski, L., Kyrola, A., … & He, K. (2017). Accurate, large minibatch SGD: training imagenet in 1 hour. arXiv preprint arXiv:1706.02677.

[7] Neill, R., Drebes, A., Pop, A. (2018). Performance Analysis of Just-in-Time Compilation for Training TensorFlow Multi-Layer Perceptrons.