利用docker部署深度學習模型的一個最佳實踐
前言
最近團隊的模型部署上線終於全面開始用上docker了,這感覺,真香!
講道理,docker是天然的微服務,確實是能敏捷高效的解決深度學習這一塊的幾個痛點。
- 部分神經網路框架比如caffe依賴過重,安裝困難。
- 各種網路模型未做工程化優化,部署困難。
- tensorflow等框架對GPU等硬體的佔用難以靈活控制。
對於做應用來說,這些問題諸如對GPU的硬體的管理,對複雜依賴的部署,而這些正好就是docker的強項。而python本身表達能力強,可以以很短的程式碼量達成我們的目的。
部署
具體的部署步驟涉及這幾個工具鏈:
整個介面的呼叫精簡成面向物件的呼叫方式,with語句進入時啟動模型,佔用GPU,開啟rpc呼叫埠,之後在呼叫結束後退出模型,釋放資源,整個呼叫過程就簡化成如下樣子:
with Model_Docker() as sess:
img = cv2.imread('demo.jpg')
r = sess.run(img)
print('result:',r,'type',type(r))
其中Model_Docker是這樣的:
class CTPN_Docker(object): def __init__(self): self.client = docker.from_env() def get_container(self,client): container = client.containers.run(image = DEMO_IMAGE_NAME:TAG, command = "python server.py", runtime='nvidia', environment = ["CUDA_VISIBLE_DEVICES=0"], ports = {'8888/tcp':'8888'}, detach=True, auto_remove = True) return container def __enter__(self): self.container = self.get_container(self.client) for line in self.container.logs(stream=True): if line.strip().find(b'grpc_server_start') >= 0: break return self def __exit__(self, exc_type, exc_val, exc_tb): self.container.stop() print('container has stopped.') def run(self,img): assert isinstance(img,np.ndarray), 'img must be a numpy array.' imgstr = img.tobytes() shape = json.dumps(img.shape) stub = ctpn_pb2_grpc.ModelStub(grpc.insecure_channel('localhost:50051')) response = stub.predict(ctpn_pb2.rect_request(img=imgstr, shape=shape)) return json.loads(response.message)
整個流程是這麼個步驟:
- _init_方法獲得docker client。
- get_container方法例項化一個container。
- with語句進入介面的_enter_方法,負責獲取container例項和例項內模型啟動結束的flag。
- with語句清理介面的_exit_方法,負責例項的關閉。
- run方法通過grpc呼叫docker內模型和返回結果。
docker-py是一個docker的python介面,docker除了cmdline的操作方式,還提供了REST的呼叫介面,docker-py就是其中一個很人性化的封裝,具體使用可見官方文件。
container的例項化中有這幾個地方需要注意:
- runtime需要用nvidia,與使用nvidia-docker效果一樣。
- detach是後臺模式,與-d效果一樣。
- auto_remove是自動刪除,與--rm效果類似。
- environment 來設定CUDA_VISIBLE_DEVICES。
- ports 來指定匯出埠對映。
除了docker-py呼叫中的這些技巧,還有如下幾個指令在構建過程中值得注意。
1、grpc的編譯,這裡沒啥好說的,和grpc的官方說明文件裡一樣。
RUN python -m grpc_tools.protoc --python_out=. --grpc_python_out=. -I. mode.proto
2、docker image的構建,有時候構建需要新增--no-cache,避免遠端資源更新了,docker構建卻沒重新。
docker build --no-cache -t name .
3、pip安裝的時候需要新增幾個引數,-r指定.txt安裝,-i指定清華映象為安裝源,--no-cache-dir壓縮docker映象。
RUN pip install -r requirements_docker.txt -i https://pypi.tuna.tsinghua.edu.cn/simple --no-cache-dir
後記
這一輪AI浪潮撲騰到今天,也積累了大量可落地的框架和應用。不過在github歡快的clone程式碼的時候,一直注意到一些事。和web等領域不同的是,幾乎所有模型幾乎都是以原始碼的形式分發的,很少有工程化的封裝,更別說封裝成庫來部署了。就拿現在我在做的目標檢測和文字識別的幾個模型來說,yolo、fasterrcnn、ctpn和crnn等都是這樣。
當然這也好理解,這些開源作品基本都是大佬在水文章之餘寫的,而且一個完整的模型包括訓練、測試和預測,模型在公開資料集上的訓練效果才是關鍵,工程化的問題並不是最重要的事情,不過我還是想吐槽一下。
比如fasterrcnn中訓練資料是寫死的,準備好訓練集後得通過一個軟連線將訓練集和訓練資料替換掉。這還不是最毒瘤的,較新的ctpn是繼承自fasterrcnn,也是採用這種方法.
又比如在匯入資料階段也是各用各的法子,這些做法有往往採用多執行緒和多程序,結果管理不好,一大堆死執行緒不說,還經常把cpu跑滿,用過的模型中darkflow和east都有這樣的問題。
還有在寫inference是,還常常遇到需要修改輸入輸出tensor的情況,在輸入端加placeholder,稍微對tensorflow不熟,同時還需要修改一些在預測階段有所改變的tensor。確實是很不人道。
最後想提一點,這種部署方式除了部署時靈活方便,另外一個額外的好處就是使用jupyter時也方便,在jupyter使用時最常見的問題有兩個,一個是需要經常使用set_env去設定CUDA_VISIBLE_DEVICES,另一個是用完了得把notebook關掉,不然jupyter程序會一直佔用GPU。