利用docker部署深度學習模型的一個最佳實踐

技術小能手發表於2018-09-06

最近團隊的模型部署上線終於全面開始用上docker了,這感覺,真香!

講道理,docker是天然的微服務,確實是能敏捷高效的解決深度學習這一塊的幾個痛點。

部分神經網路框架比如caffe依賴過重,安裝困難。
各種網路模型未做工程化優化,部署困難。
tensorflow等框架對GPU等硬體的佔用難以靈活控制。

對於做應用來說,這些問題諸如對GPU的硬體的管理,對複雜依賴的部署,而這些正好就是docker的強項。而python本身表達能力強,可以以很短的程式碼量達成我們的目的。

部署

具體的部署步驟涉及這幾個工具鏈:

Dockerfile進行模型的映象部署。
docker-py進行container的啟動和關閉。
grpc和進行模型的外部通訊。
python的with語句表達模型的載入和資源的釋放。
gitlab進行內網的程式碼分發和版本控制。

整個介面的呼叫精簡成物件導向的呼叫方式,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。

原文釋出時間為:2018-09-5

本文作者:丁果

本文來自雲棲社群合作伙伴“Python中文社群”,瞭解相關資訊可以關注“Python中文社群”。


相關文章