嘉賓 | 黃彬
整理 | 涂承燁
日前,在51CTO主辦的AISummit 全球人工智能技術大會上,網易云音樂算法平臺研發專家黃彬帶來了主題演講《網易云音樂在線預估系統的實踐與思考》,從技術研發的視角分享了如何建設一套高性能、易用,且功能豐富的一個預估系統的相關實踐與思考。
現將演講內容整理如下,希望對諸君有所啟發。
系統整體架構
首先,我們來看一下整個預估系統的一個架構,如下圖所示:
系統整體架構
中間的Predict Server,是預估系統的核心組件,包括查詢組件、特征處理組件,和模型計算組件。左側的監控系統用于線網服務的監控,確保系統網絡的暢通。右側的PushServer用于模型推送,把最新的模型推送進線上預估系統進行預測。
目標是建設一套高性能、易用,且功能豐富的一個預估系統。
高性能計算
如何提升計算性能?我們常見的計算性能問題有哪些,我從三個方面進行闡述。
- 特征處理
在通用方案里,我們的特征計算和模型計算是分進程部署的,這樣就會導致有大量的特征存在跨服務、跨語言的傳遞,會帶來多次編解碼和內存拷貝,導致會存在比較大的性能開銷。
- 模型更新
我們知道在模型更新時,會有大塊類型的申請和釋放。然而在一些通用方案里,它不會自帶模型預熱的方案,這樣就會導致模型更新的過程中有比較高的耗時抖動,無法支持模型的實時化更新。
- 計算調度
一般的框架使用的是同步機制,并發度不夠,CPU利用率比較低,無法滿足高并發的計算需求。
那么,我們在預估系統里面是如何解決這些性能瓶頸呢?
1、無縫集成機器學習庫
我們為什么要做這樣一個事情呢?因為在傳統方案里,我們都知道特征處理和模型計算分進程部署,這樣就會帶來比較多的特定的跨網絡傳輸,序列化、反序化,還有頻繁的內存申請和釋放。尤其是在推薦場景的特征量特別大時,這樣就會帶來比較明顯的性能開銷。下圖中,靠上方的流程圖就展示了通用方案里具體的情況。
無縫集成計器學習庫
為了解決上述問題,我們就在預估系統里面將高性能計算學習框架集成到預估系統內部,這樣的好處就是我們能夠確保特征處理和模型計算能夠同進程部署,能夠以指針的形式去實現對特征的操作,避免序列化、反序列化以及網絡傳輸的開銷,從而在特征計算以及特征處理這一塊帶來比較好的計算性能提升,這就是無縫集成機器學習帶來的好處。
2、架構設計考量
首先,整個系統采用全異步的架構設計。異步架構帶來的好處就是外部調用是無堵塞等待的,所以異步機制可以確保在CPU高負載的情況下,例如在60%到70%的情況下,依舊保持線網服務的耗時穩定性。
其次,訪存優化。訪存優化主要是基于服務器的NUMA架構,我們采用了綁核運行的方式。通過這種方式能夠去解決之前NUMA架構存在的遠端內存訪問的問題,從而提升了我們服務的計算性能。
第三,并行計算。我們對計算任務進行分片的處理,采用多線程并發的方式做計算,這樣就能夠較大程度降低服務的時耗,提升資源的利用率。
架構設計考量
以上就是我們在預估系統的系統架構考量上的實踐。
3、多級緩存
多級緩存,主要應用在特征查詢階段和初級階段。我們封裝的緩存機制,一方面能夠降低查詢的外部調用,另一方面也能夠減少特征抽取導致反復無效的計算。
通過緩存的方式,可以極大程度的提升查詢和抽取的效率。尤其在查詢階段,我們根據特征的重要程度以及根據特征的量級,我們封裝了多種組件,例如同步查詢、異步查詢以及特征批量導入等組件。
第一種是同步查詢,主要適用于一些比較重要的特征,當然同步查詢的性能沒有那么高效。
第二種是異步查詢,主要針對一些“艾特維度”的特征,這些特征有可能重要程度不是那么高,那么就可以采用這種異步查詢的方式。
第三種是特征批量導入,主要適用于特征規模不是特別大的特征數據。我們將這些特征批量導入到進程內部,就可以實現特征的本地化查詢,性能是非常高效的。
多級緩存
4、模型計算優化
介紹完緩存機制之后,我們來看一下模型計算的行為優化。模型計算,我們主要從模型輸入優化、模型加載優化以及內核優化,這三個方面進行優化。
- 模型輸入優化
在模型輸入這一塊,大家都知道TF Servering采用的是Example的輸入。Example輸入會存在Example的構造、Example的序列化反序化以及模型內部調用Parse Example的情況,這樣就會存在比較明顯的耗時。
在下圖中,我們看【優化前】的截圖展示了模型計算優化前的數據統計情況。我們可以看到,有一個比較長的Parse Example解析耗時,并且在Parse Example解析完之前,其他op是沒有辦法執行并行調度的。為了解決模型樹的性能問題,我們在預估系統里封裝了高性能的模型輸入方案。通過新的方案,我們能夠實現特征輸入零拷貝,從而減少這種Example的構造耗時以及解析耗時。
在下圖中,我們看【優化后】的截圖展示了模型計算優化之后的數據統計情況,我們可以看到,已經沒有了Parse Example解析耗時,就只剩下Example的解析耗時。
模型計算優化
- 模型加載優化
介紹完模型輸入優化,我們來看一下模型加載的優化。Tensorflow的模型加載是懶加載的模式,模型加載到內部之后,它并不會進行模型預熱,而是要等到線網正式請求來了之后才會進行模型預熱,這樣就會導致模型加載之后會有比較嚴重的耗時抖動。
為了解決這個問題,我們在預估系統內部實現了自動的模型預熱功能,并且實現了新舊模型的熱切換,還實現了舊模型的異步卸載和內存釋放。這樣通過這些模型加載的一些優化手段,實現模型的分鐘級更新能力。
- 內核優化
接下來,我們看一下模型內核的優化。目前我們主要是對Tensorflow內核做了一些內核同步優化,以及我們會根據模型去調整op間和op內的一些線程池等等。
以上就是我們在模型計算方面的一些性能優化的嘗試。
通過介紹上面的性能優化方案之后,我們來看一下最終的性能優化成果。
性能優化成果
這里我們使用預估系統和通用方案的系統做了一個對比。我們可以看到預估系統在CPU使用達到80%的情況下,整個服務的計算耗時以及超時率都非常穩定,非常低。通過對比,我們可以得出新方案(預估系統)在計算處理這一塊,性能是有提升兩倍,對CPU的榨取能力更強,服務耗時更低。
得益于我們對系統的優化,我們可以為業務算法提供更多模型的復雜度計算以及更多候選集的計算。
上圖中舉了一個例子,候選集從之前的300個候選集擴充到1000個候選集,同時我們增加了模型計算復雜度以及使用了一些比較復雜的特征,分別在多個業務里帶來了比較好的效果提升。
以上就是預估系統在性能優化上以及性能優化成果的介紹。
如何提升開發效率
1、系統分層設計
系統采用分層的架構設計。我們將整個預估系統分成三層,分為底層架構層、中間模板層和上層結構層。
底層架構層主要提供異步機制、任務隊列、并發調度、網絡通信等。
中間模板層主要提供模型計算相關的組件,包括查詢管理、緩存管理、模型加載管理以及模型計算管理。
上層接口層主要提供Highlevel的接口,業務僅需實現此層接口,大量減少代碼開發。
通過系統的分層架構設計,不同業務之間完全可以復用底層和中間層的代碼,開發只需要關注最上層的少量代碼開發就可以。同時,我們也在進一步的思考,有沒有什么辦法能夠進一步減少上層接口層的代碼開發?下面我們來詳細介紹一下。
2、通用查詢封裝
通過基于動態pb技術實現特征查詢和特征解析形成的通用化方案的封裝,能夠實現僅通過XML配置表名、查詢KEY、緩存時間、查詢依賴等就能實現特征查詢、解析、緩存全流程。
如下圖所示,我們通過少量的幾行配置,就能夠實現復雜的一個查詢邏輯。同時,通過查詢封裝提升了查詢的效率。
3、特征計算封裝
特征計算可以說是整個預估系統里面,代碼開發復雜度最高的一個模塊,那么什么是特征計算呢?
特征計算包括離線過程和在線過程。離線過程其實就是離線樣本,通過處理得到離線訓練平臺需要的一些格式,例如TF Recocd的格式。在線過程,主要是對在線的請求做一些特征計算,通過處理得到在線預測平臺需要的一些格式。離線過程和在線過程,其實對特征處理的計算邏輯是完全一樣的。但是因為離線過程和在線過程的計算平臺不一樣,使用的語言不一樣,就需要開發多套代碼來實現特征計算,所以存在以下三個問題。
- 一致性難以保障
一致性難以保證的根本原因是離線訓練和在線預測對特征處理邏輯難以統一。一方面會影響到算法的效果,另外一方面會導致在開發過程中帶來比較高昂的一次性校驗成本。
- 效率低
如果要新加一個特征,就需要涉及到離線過程和在線過程的多套代碼的開發,導致開發效率非常的低。
- 復用難
復用難,主要原因是框架缺乏對復用能力的支持,導致不同業務之間想要做到特征計算的復用,變得非常難。
以上就是特征計算框架存在的一些問題。
為了解決這些問題,我們將按照如下四點思路來逐步解決。
首先,我們提出算子的概念,將特征計算抽象成算子的封裝。其次,算子封裝之后,我們建立一個算子庫,通過算子庫能夠提供業務之間算子的復用能力。然后,我們基于算子,定義特征計算描述語言DSL。通過這種描述語言,我們能夠完成特征計算的配置化表達。最后,就是前面介紹的,因為在線過程和離線過程存在多套邏輯,會導致邏輯不一致的問題,我們就需要解決特征一次性的問題。
以上四點,就是我們如何對特征計算框架進行封裝的思路。
- 算子抽象
為了實現算子抽象,首先必須實現數據協議的統一。我們利用動態pb的技術,根據特征的原數據信息,將任意的一個特征按照統一的數據進行處理,這樣就為我們的算子封裝提供了數據基礎。接下來,我們對特征處理的過程進行抽樣封裝,將特征計算過程抽象成解析、計算、組裝、異常處理幾個過程,并且統一計算過程API,從而實現了算子抽象。
- 建立算子庫
有了算子的抽象之后,我們就可以建立算子庫。算子庫分為平臺通用算子庫和業務自定義算子庫。平臺通用算子庫主要是實現公司級的復用。業務自定義算子庫主要是針對業務的一些自定義場景及特征,實現組內的復用。我們通過算子的封裝以及算子庫的建設,實現特征計算的多場景復用,提升開發效率。
- 計算描述語言DSL
特征計算的配置化表達,是指定義特征計算表達的配置化語言叫DSL。通過配置化語言,我們能夠實現算子的多層嵌套表達,能夠實現四則運算等等。下圖中的第一幅截圖展示了配置化語言的具體語法情況。
我們通過特征計算的配置化語言能夠帶來什么好處呢?
第一,我們能夠通過配置化完成整個特征計算,從而達到開發效率的提升。
第二,我們能夠通過發布特征計算配置化表達,實現特征計算的熱更新。
第三,訓練和預測使用同一份特征計算的配置,從而實現線上線下一致性。
這就是特征計算表達帶來的好處。
- 特征一致性
前面說到,特征計算分為離線過程和在線過程。因為離線和在線的多平臺原因,導致邏輯計算的不一致。為了解決這個問題,我們在特征計算框架里,實現了特征計算框架的跨平臺運行能力。核心邏輯采用C++開發,對外暴露的是C++接口以及Java接口。在打包構建的過程中,能夠一鍵實現C++的so庫以及jar包,從而確保特征計算能夠運行在線計算的C++平臺以及離線的Spark平臺或者說Flink平臺,并且用特征計算表達,可以確保特征計算實現線上線下邏輯的一致性。
上面介紹的是特征計算的具體情況。下面我們來看一下特征計算目前已經取得的一些成果。
我們現在已經沉淀了120家的算子,通過特征計算的DSL語言能夠實現配置化,完成整個特征計算。通過我們提供的跨平臺運行能力,實現了線上線下邏輯不一致的問題。
下圖中的截圖展示了通過少量的配置,就可以實現整個特征計算的過程,較大程度提高了特征計算的開發性效率。
上述介紹了我們在開發效率提升的一個探索??偟脕碚f,我們通過系統的分層設計,提高代碼的復用度,以及通過對查詢、對抽取、對模型計算的封裝,能夠實現配置化的開發流程。
4、模型計算封裝
模型計算同樣也是采用封裝的形式。通過配置化表達的形式,實現模型的加載、模型的輸入構造、模型的計算等等,使用幾行配置,實現整個模型計算的表達過程。
模型實時化落地
下面我們來看一下模型實時化的落地案例。
1、實時化項目背景
我們為什么要做這樣的模型實時化項目?
主要原因是傳統的推薦系統是天級別更新用戶推薦結果的系統,它的實時性非常差,無法滿足這種實時性要求比較高的場景,例如我們的直播場景,或者說其他一些實時性要求比較高的場景。
還有一個原因是傳統的樣本生產方式,是存在特征穿越的問題。什么是特征穿越呢?下圖中展示了特征穿越產生的根本原因,是因為我們在做樣本拼接的過程中,我們采用的是“T-1”時刻的模型預估結構,和“T”時刻的特征進行拼接,這樣就會出現特征穿越的問題。特征穿越會非常大程度的影響線網推薦的效果。為了解決實時性的問題,以及為了解決樣本穿越的問題,我們就在預估系統里去落地這樣一個模型實時化的方案。
2、實時化方案介紹
模型實時化方案從三個維度進行闡述。
- 樣本實時生成
- 模型增量訓練
- 預估系統實時化
樣本實時生成。我們基于在線預估系統,將預估系統的特征實時落地到Kafka,通過RACE ID的形式關聯,這樣我們就能夠確保樣本實現秒級落盤,并且能夠解決特征穿越的問題。
模型增量訓練。有了樣本的秒級落盤之后,我們就可以修改訓練模塊,實現模型的增量訓練,就能實現模型的分鐘級更新。
預估系統實時化。有了模型的分鐘級導出之后,我們通過模型推送服務Push Server,將最新的模型推送到線上預估系統,能夠使得現場預估系統使用最新的模型進行預測。
模型實時化方案總得來說,就是要實現樣本的秒級落盤,實現模型的分鐘級訓練和分鐘級線上更新。
我們現在的模型實時化方案,已經在多個場景進行了落地。通過模型實時化方案,在業務上有比較好的效果提升。
上圖中,主要展示了模型實時化方案的具體實驗數據。我們可以看到增量訓練,它的訓練周期越短越好。通過具體的數據,我們可知周期為15分鐘的效果遠遠大于2小時、10小時、一天的?,F在的模型實時化方案已經有一套規范化的接入流程,能夠批量為業務帶來比較好的效果提升。
上述介紹了預估系統如何提升計算性能,如何提升開發效率,以及如何通過工程的手段帶來項目算法提升三個方面的探索和嘗試。
整個預估系統的平臺價值,或者說整個預估系統的平臺目的,可以概括成三個字,就是“快、好、省”。
“快”就是前面介紹的應用性建設。我們希望通過持續的應用性建設,使得業務的迭代能夠更加高效。
“好”就是希望通過工程手段,例如通過模型實時化方案,以及通過特征計算的線上線下邏輯一致性方案,能夠為業務帶來比較好的效果提升。
“省”就是使用預估系統的更高性能,能夠更加節省計算資源,以及節省計算成本。
未來,我們會朝這三個目標持續努力。
以上就是我對云音樂預估系統的介紹,我的分享到此為止就結束了,謝謝各位!