1. 程式人生 > >利用docker部署深度學習模型的一個最佳實踐

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

前言

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

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

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

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

部署

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

  1. docker-py進行container的啟動和關閉。
  2. grpc和進行模型的外部通訊。
  3. python的with語句表達模型的載入和資源的釋放。
  4. 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)

整個流程是這麼個步驟:

  1. _init_方法獲得docker client。
  2. get_container方法例項化一個container。
  3. with語句進入介面的_enter_方法,負責獲取container例項和例項內模型啟動結束的flag。
  4. with語句清理介面的_exit_方法,負責例項的關閉。
  5. run方法通過grpc呼叫docker內模型和返回結果。

docker-py是一個docker的python介面,docker除了cmdline的操作方式,還提供了REST的呼叫介面,docker-py就是其中一個很人性化的封裝,具體使用可見官方文件

container的例項化中有這幾個地方需要注意:

  1. runtime需要用nvidia,與使用nvidia-docker效果一樣。
  2. detach是後臺模式,與-d效果一樣。
  3. auto_remove是自動刪除,與--rm效果類似。
  4. environment 來設定CUDA_VISIBLE_DEVICES。
  5. 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跑滿,用過的模型中darkfloweast都有這樣的問題。

還有在寫inference是,還常常遇到需要修改輸入輸出tensor的情況,在輸入端加placeholder,稍微對tensorflow不熟,同時還需要修改一些在預測階段有所改變的tensor。確實是很不人道。

最後想提一點,這種部署方式除了部署時靈活方便,另外一個額外的好處就是使用jupyter時也方便,在jupyter使用時最常見的問題有兩個,一個是需要經常使用set_env去設定CUDA_VISIBLE_DEVICES,另一個是用完了得把notebook關掉,不然jupyter程序會一直佔用GPU。