开源项目 | 五分钟搭建BERT服务,实现1000+QPS
作者丨劉欣
單位丨香儂科技算法架構負責人
研究方向丨NLP工程化、算法平臺架構
深度學習模型在訓練和測試時,通常使用小批量(mini-batch)的方式將樣本組裝在一起,這樣能充分利用 GPU 的并行計算特性,加快運算速度。?
但在將使用了深度學習模型的服務部署上線時,由于用戶請求通常是離散和單次的,若采取傳統的循環服務器或多線程服務器,在短時間內有大量請求時,會造成 GPU 計算資源閑置,用戶等待時間線性變長。?
基于此,我們開發了 service-streamer,它是一個中間件,將服務請求排隊組成一個完整的 batch,再送進 GPU 運算。這樣可以犧牲最小的時延(默認最大 0.1s),提升整體性能,極大優化 GPU 利用率。
Github開源鏈接:
https://github.com/ShannonAI/service-streamer
功能特色
安裝步驟
可通過 pip 安裝,要求 Python>=3.5:
五分鐘搭建BERT服務
1. 首先我們定義一個完型填空模型(bert_model.py),其 predict 方法接受批量的句子,并給出每個句子中 [MASK] 位置的預測結果。
2. 然后使用 Flask 將模型封裝成 web 服務 flask_example.py。這時候你的 web 服務每秒鐘只能完成 12 句請求。
model=TextInfillingModel()@app.route("/naive",?methods=["POST"])def?naive_predict(?):??????inputs?=?request.form.getlist("s")??????outputs?=?model.predict(inputs)??????return?jsonify(outputs)app.run(port=5005)
def?naive_predict(?):
??????inputs?=?request.form.getlist("s")
??????outputs?=?model.predict(inputs)
??????return?jsonify(outputs)
app.run(port=5005)
3. 下面我們通過 service_streamer 封裝你的模型函數,三行代碼使 BERT 服務的預測速度達到每秒 200+ 句(16 倍 QPS)。
from?service_streamer?import?ThreadStreamer?streamer=?ThreadedStreamer?(model.predict,batch_size=64,?max_latency=0.1)@app.route("/stream",?methods=["POST"])def?stream_predict(?):?????inputs?=?request.form.getlist("s")????outputs?=?streamer.predict(inputs)????return?isonify(outputs)app.run(port=5005,?debug=False)import?ThreadStreamer?streamer=?ThreadedStreamer?(model.predict,batch_size=64,?max_latency=0.1)
def?stream_predict(?):
?????inputs?=?request.form.getlist("s")
????outputs?=?streamer.predict(inputs)
????return?isonify(outputs)
app.run(port=5005,?debug=False)
import?multiprocessingfrom?service_streamer?import?ManagedModel,?Streamermultiprocessing.set_start_method("spawn",?force=True)class?ManagedBertModel(ManagedModel):?????def?init_model(self):???????????self.model?=?TextInfillingModel(?)??????def?predict(self,?batch):????????????return?self.model.predict(batch)streamer?=Streamer(ManagedBertModel,?batch_size=64,?max_latency=0.1,?worker_num?=?8,?cuda_devices=(0,1,2,3))app.run(port=5005,?debug=False)
from?service_streamer?import?ManagedModel,?Streamer
multiprocessing.set_start_method("spawn",?force=True)
class?ManagedBertModel(ManagedModel):
?????def?init_model(self):
???????????self.model?=?TextInfillingModel(?)
??????def?predict(self,?batch):
????????????return?self.model.predict(batch)
streamer?=Streamer(ManagedBertModel,?batch_size=64,?max_latency=0.1,?
worker_num?=?8,?cuda_devices=(0,1,2,3))
app.run(port=5005,?debug=False)
更多指南
除了上面的 5 分鐘教程,service-streamer 還提供了:?
API介紹
快速入門
通常深度學習的 inference 按 batch 輸入會比較快。
outputs?=?model.predict(batch_inputs)
from?service_streamer?import?ThreadedStreamer#?用Streamer封裝batch_predict函數streamer?=?ThreadedStreamer(model.predict,?batch_size=64,?max_latency=0.1)#?用Streamer異步調用predict函數outputs?=?streamer.predict(batch_inouts)import?ThreadedStreamer
#?用Streamer封裝batch_predict函數
streamer?=?ThreadedStreamer(model.predict,?batch_size=64,?max_latency=0.1)
#?用Streamer異步調用predict函數
outputs?=?streamer.predict(batch_inouts)
然后你的 web server 需要開啟多線程(或協程)即可。?
短短幾行代碼,通常可以實現數十(batch_size/batch_per_request)倍的加速。
分布式GPU worker?
上面的例子是在 web server 進程中,開啟子線程作為 GPU worker 進行 batch predict,用線程間隊列進行通信和排隊。?
實際項目中 web server 的性能(QPS)遠高于 GPU 模型的性能,所以我們支持一個 web server 搭配多個 GPU worker 進程。
import?multiprocessing;?multiprocessing.set_start_method("spawn",?force=True)from?service_streamer?import?Streamer#?spawn出4個gpu?worker進程streamer?=?Streamer(model.predict,?64,?0.1,?worker_num=4)outputs?=?streamer.redict(batch)
multiprocessing.set_start_method("spawn",?force=True)
from?service_streamer?import?Streamer
#?spawn出4個gpu?worker進程
streamer?=?Streamer(model.predict,?64,?0.1,?worker_num=4)
outputs?=?streamer.redict(batch)
Streamer 默認采用 spawn 子進程運行 gpu worker,利用進程間隊列進行通信和排隊,將大量的請求分配到多個 worker 中處理,再將模型 batch predict 的結果傳回到對應的 web server,并且返回到對應的 http response。
上面這種方式定義簡單,但是主進程初始化模型,多占了一份顯存,并且模型只能運行在同一塊 GPU 上,所以我們提供了 ManageModel 類,方便模型 lazy 初始化和遷移,以支持多 GPU。
from?service_streamer?import?ManagedModelclass?ManagedBertModel(ManagedModel):???def?init_model(self):???????self.model?=?Model(?)??def?predict(self,?batch):??????return?self.model.predict(batch)?#?spawn出4個gpu?worker進程,平均分數在0/1/2/3號GPU上streamer?=?Streamer(ManagedBertModel,?64,?0.1,?worker_num=4,cuda_devices=(0,1,2,3))outputs?=?streamer.predict(batch)import?ManagedModel
class?ManagedBertModel(ManagedModel):
???def?init_model(self):
???????self.model?=?Model(?)
??def?predict(self,?batch):
??????return?self.model.predict(batch)?
#?spawn出4個gpu?worker進程,平均分數在0/1/2/3號GPU上
streamer?=?Streamer(ManagedBertModel,?64,?0.1,?worker_num=4,cuda_devices=(0,1,2,3))
outputs?=?streamer.predict(batch)
有時候,你的 web server 中需要進行一些 CPU 密集型計算,比如圖像、文本預處理,再分配到 GPU worker 進入模型。CPU 資源往往會成為性能瓶頸,于是我們也提供了多 web server 搭配(單個或多個)GPU worker 的模式。
使用 RedisStreamer 指定所有 web server 和 GPU worker 共用的 redis broker 地址。
#?默認參數可以省略,使用localhost:6379streamer?=?RedisStreamer(redis_broker="172.22.22.22:6379")
streamer?=?RedisStreamer
(redis_broker="172.22.22.22:6379")
cd?examplegunicorn?-c?redis_streamer_gunicorn.py?flask_example:app
這樣每個請求會負載均衡到每個 web server 中進行 CPU 預處理,然后均勻的分布到 GPU worke 中進行模型 predict。
Future API
from?service_streamer?import?ThreadedStreamerstreamer?=?ThreadedStreamer(model.predict,?64,?0.1)xs?={}for?i?in?range(200):????future?=?streamer.submit(["Happy?birthday?to?[MASK]",????????????????????????????????????????????"Today?is?my?lucky?[MASK]"])?????xs.append(future)#?先拿到所有future對象,再等待異步返回for?future?in?xs:?????outputs?=?future.result()?????print(outputs)import?ThreadedStreamer
streamer?=?ThreadedStreamer(model.predict,?64,?0.1)
xs?={}
for?i?in?range(200):
????future?=?streamer.submit(["Happy?birthday?to?[MASK]",?
???????????????????????????????????????????"Today?is?my?lucky?[MASK]"])
?????xs.append(future)
#?先拿到所有future對象,再等待異步返回
for?future?in?xs:
?????outputs?=?future.result()
?????print(outputs)
基準測試
我們使用 wrk 來使做基準測試。?
環境
單個GPU進程
#?start?flask?threaded?serverpython?example/flask_example.py#?benchmark?naive?api?without?service_streamer./wrk?-t?4?-c?128?-d?20s?--timeout=10s?-s?scripts/streamer.lua?http://127.0.0.1:5005/naive#?benchmark?stream?api?with?service_streamer./wrk?-t?4?-c?128?-d?20s?--timeout=10s?-s?scripts/streamer.lua?http://127.0.0.1:5005/naive
python?example/flask_example.py
#?benchmark?naive?api?without?service_streamer
./wrk?-t?4?-c?128?-d?20s?--timeout=10s?-s?scripts/streamer.lua?http://127.0.0.1:5005/naive
#?benchmark?stream?api?with?service_streamer
./wrk?-t?4?-c?128?-d?20s?--timeout=10s?-s?scripts/streamer.lua?http://127.0.0.1:5005/naive
多個GPU進程?
這里對比單 web server 進程的情況下,多 GPU worker 的性能,驗證通過和負載均衡機制的性能損耗。Flask 多線程 server 已經成為性能瓶頸,故采用 gevent server。
利用Future API使用多個GPU?
為了規避 web server 的性能瓶頸,我們使用底層 Future API 本地測試多 GPU worker 的 benchmark。
可以看出 service_streamer 的性能跟 GPUworker 數量及乎成線性關系,其中進程間通信的效率略高于 redis 通信。
點擊以下標題查看更多往期內容:?
KDD Cup 2019 AutoML Track冠軍團隊技術分享
神經網絡架構搜索(NAS)綜述 | 附資料推薦
小米拍照黑科技:基于NAS的圖像超分辨率算法
深度解讀:小米AI實驗室最新成果FairNAS
自動機器學習(AutoML)最新綜述
NAS-FPN:基于自動架構搜索的特征金字塔網絡
#投 稿 通 道#
?讓你的論文被更多人看到?
如何才能讓更多的優質內容以更短路徑到達讀者群體,縮短讀者尋找優質內容的成本呢?答案就是:你不認識的人。
總有一些你不認識的人,知道你想知道的東西。PaperWeekly 或許可以成為一座橋梁,促使不同背景、不同方向的學者和學術靈感相互碰撞,迸發出更多的可能性。
PaperWeekly 鼓勵高校實驗室或個人,在我們的平臺上分享各類優質內容,可以是最新論文解讀,也可以是學習心得或技術干貨。我們的目的只有一個,讓知識真正流動起來。
??來稿標準:
? 稿件確系個人原創作品,來稿需注明作者個人信息(姓名+學校/工作單位+學歷/職位+研究方向)?
? 如果文章并非首發,請在投稿時提醒并附上所有已發布鏈接?
? PaperWeekly 默認每篇文章都是首發,均會添加“原創”標志
? 投稿郵箱:
? 投稿郵箱:hr@paperweekly.site?
? 所有文章配圖,請單獨在附件中發送?
? 請留下即時聯系方式(微信或手機),以便我們在編輯發布時和作者溝通
?
現在,在「知乎」也能找到我們了
進入知乎首頁搜索「PaperWeekly」
點擊「關注」訂閱我們的專欄吧
關于PaperWeekly
PaperWeekly 是一個推薦、解讀、討論、報道人工智能前沿論文成果的學術平臺。如果你研究或從事 AI 領域,歡迎在公眾號后臺點擊「交流群」,小助手將把你帶入 PaperWeekly 的交流群里。
▽ 點擊 |?閱讀原文?| 訪問項目主頁
總結
以上是生活随笔為你收集整理的开源项目 | 五分钟搭建BERT服务,实现1000+QPS的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: BERT or XLNet,围观NLP巅
- 下一篇: 如何用最简单的方式理解傅立叶变换?