天天看點

使用函數 initializer 接口優化深度學習場景下模型加載的冷啟動延時

背景

深度學習場景使用函數計算典型案例

阿裡雲

函數計算

客戶

碼隆科技

是一家專注于深度學習與計算機視覺技術創新的公司。當碼隆的客戶上傳大量圖像資料後,需要盡快把圖像按照客戶指定的方式處理,包括商品識别,紡織面料等柔性材質識别分析,内容審查,以圖搜圖等等。圖像處理基于碼隆預先訓練好的深度學習模型,要求在短時間内準備大量的計算資源進行大規模并行處理。客戶将深度學習推理邏輯實作為函數,在函數中加載模型後對圖像資料進行處理。通過函數計算提供的大規模計算能力,客戶能夠短時間處理大量圖像,平穩應對峰值壓力。更多詳細案例請見

函數計算客戶案例

深度學習場景的客戶在使用函數計算服務中更希望平台做哪些改進?

深度學習場景下加載模型是主要的應用層冷啟動開銷,模型的規格多為 500MB+,應用層冷啟動開銷往往會導緻毛刺的産生,為歸避這類問題,函數計算引入了 initializer 接口來解決應用層冷啟動開銷帶來的毛刺問題。

功能簡介

Initializer 程式設計模式為使用者提供了

initializer 入口定義

,便于使用者将業務邏輯分為initializer函數和請求處理函數兩部分。函數計算使用容器執行使用者函數代碼,這樣的執行環境我們稱之為函數執行個體。函數執行個體會在啟動的時候能夠自動執行 initializer 函數,進行業務層冷啟動,成功之後,該執行個體收到使用者的 Invoke 請求,就能夠執行使用者的請求處理函數了。

引入 initializer 接口的優勢:

  • 分離初始化邏輯和請求處理邏輯,程式邏輯更清晰,讓使用者更易寫出結構良好,性能更優的代碼;
  • 使用者函數代碼更新時,系統能夠保證使用者函數的平滑更新,規避應用層初始化冷啟動帶來的性能損耗。新的函數執行個體啟動後能夠自動執行使用者的初始化邏輯,在初始化完成後再處理請求;
  • 在應用負載上升,需要增加更多函數執行個體時,系統能夠識别函數應用層初始化的開銷,更精準的計算資源伸縮的時機和所需的資源量,讓請求延時更加平穩;
  • 即使在使用者有持續的請求且不更新函數的情況下,FC系統仍然有可能将已有容器回收或更新,這時沒有平台方(FC)的冷啟動,但是會有業務方冷啟動,Initializer 的引入可以最大限度減少這種情況;

案例實踐

本實踐以

函數計算部署機器學習遇到的問題和解法

這篇文章為基礎,做了進一步改造和優化。下文将按照以下幾個步驟講解如何利用函數計算以高性能、低延時玩轉深度學習場景下的識别手寫數字案例:

首先需要訓練預期的模型,模型的訓練可參考

這篇文章

。按照文章中的步驟下載下傳 MINIST 資料庫和相關代碼并開始訓練模型,訓練時長持續半小時左右,訓練成功後的結構目錄如下,其中 model_data 目錄下的檔案便是通過訓練得到的模型。

project root
├── main.py
├── grf.pb
└── model_data
    ├── checkpoint
    ├── model.data-00000-of-00001
    ├── model.index
    └── model.meta           

本案例需要安裝的應用依賴有

tensorflow

opencv-python

,模型的訓練和函數的處理邏輯都強依賴這兩個庫,訓練模型可在本地直接操作,通過 pip 在本地安裝兩個依賴庫即可,版本不限。由于函數運作在函數計算(FC)系統同樣依賴這兩個庫,需要提前下載下傳好依賴并打包上傳到 OSS。推薦使用 fcli 工具的 sbox 指令,下面以 runtime 為 python2.7 進行操作:

目前 Pypi 上 tensorflow 最新版本為 1.11.0,為避免因版本問題影響您的實踐,建議安裝 1.8.0 版本。
cd <'此項目的根目錄中'>
mkdir applib      // 建立存儲所有應用依賴的目錄
fcli shell        // fcli version >= 0.24
sbox -d applib -t python2.7
pip install -t $(pwd) tensorflow==1.8.0
pip install -t $(pwd) opencv-python           

完成之後 exit 退出沙盒環境,并執行 exit 退出fcli。

依賴和模型下載下傳成功後需要進行壓縮并上傳到 OSS 中,以便後面函數可以直接從 OSS 下載下傳即可,在項目的根目錄執行下面兩條指令可以得到

applib.zip

model_data.zip

兩個 zip 壓縮包。

cd applib && zip -r applib.zip * && mv applib.zip ../ ; cd ..
cd model_data && zip -r model_data.zip * && mv model_data.zip ../ ; cd ..           

下面提供了一個簡單的上傳 zip 包到 OSS 的模版,上傳成功後删除本地的依賴和模型目錄即可。

# -*- coding: utf-8 -*-
import oss2

auth = oss2.Auth(<'Your access_key_id'>, <'Your access_key_secret'>)
bucket = oss2.Bucket(auth, <'Your endpoint'>, <'Your bucket'>)
bucket.put_object_from_file('applib.zip', <'Your applib.zip path'>)
bucket.put_object_from_file('model_data.zip', <'Your model_data.zip path'>)
           

如何将本地機器學習應用進行改造并遷移到函數計算的流程在

中有詳細的步驟,這篇文章中的改造并沒有 initializer 的概念,您隻需要關注 index.py 和 loader.py 是如何産生的,

詳細代碼連結

,改造後的目錄結構如下,其中 index.py 存放了機器學習相關邏輯的代碼,loader.py 存放了函數入口和加載依賴邏輯的代碼。

project root
└─── code
    ├── loader.py
    └── index.py
    └── pic
        └── e2.jpg           

e2.jpg 隻是文章中提供的一個簡單的數字 2 圖檔,如做驗證性測試需要更多的素材可以通過

keras.js

平台手動繪制生成。

使用函數 initializer 接口優化深度學習場景下模型加載的冷啟動延時

經過應用遷移處理後得到了一個可以運作在函數計算服務上的函數,很明顯可以看到函數入口

loader.handler

中首先需要從 OSS 加載應用依賴(tensorflow、opencv)和資源依賴(模型),加載的過程都屬于應用層冷啟動,冷啟動所耗費的時間在一定程度上和所需依賴的大小規格成正比。為避免後續處理邏輯受到應用層冷啟動延時的影響,這裡将加載依賴邏輯放入 initializer 函數中。

其中 index.py 檔案保持不變,loader.py 檔案需要進行如下改造:

  • 添加 initializer 函數,initializer 入口便為 loader.initializer。
  • 将對 download_and_unzip_if_not_exist 的調用從 handler 中更換到 initializer 函數中。

loader.py 經過改造後的代碼如下:

# -*- coding:utf-8 -*-
import sys
import zipfile
import os
import oss2
import imp
import time

app_lib_object = os.environ['AppLibObject']
app_lib_dir = os.environ['AppLibDir']
model_object = os.environ['ModelObject']
model_dir = os.environ['ModelDir']

local = bool(os.getenv('local', ""))
print 'local running: ' + str(local)

def download_and_unzip_if_not_exist(objectKey, path, context):
    creds = context.credentials
    if (local):
        print 'thank you for running function in local!!!!!'
        auth = oss2.Auth(creds.access_key_id,
                         creds.access_key_secret)
    else:
        auth = oss2.StsAuth(creds.access_key_id,
                            creds.access_key_secret,
                            creds.security_token)

    endpoint = os.environ['Endpoint']
    bucket = os.environ['Bucket']

    print 'objectKey: ' + objectKey
    print 'path: ' + path
    print 'endpoint: ' + endpoint
    print 'bucket: ' + bucket

    bucket = oss2.Bucket(auth, endpoint, bucket)

    zipName = '/tmp/tmp.zip'

    print 'before downloading ' + objectKey + ' ...'
    start_download_time = time.time()
    bucket.get_object_to_file(objectKey, zipName)
    print 'after downloading, used %s seconds...' % (time.time() - start_download_time)

    if not os.path.exists(path):
        os.mkdir(path)

    print 'before unzipping ' + objectKey + ' ...'
    start_unzip_time = time.time()
    with zipfile.ZipFile(zipName, "r") as z:
        z.extractall(path)
    print 'unzipping done, used %s seconds...' % (time.time() - start_unzip_time)

def initializer(context):
    if not local:
        download_and_unzip_if_not_exist(app_lib_object, app_lib_dir, context)
        download_and_unzip_if_not_exist(model_object, model_dir, context)
    sys.path.insert(1, app_lib_dir)

def handler(event, context):
    desc = None
    fn, modulePath, desc = imp.find_module('index')
    mod = imp.load_module('index', fn, modulePath, desc)
    request_handler = getattr(mod, 'handler')
    return request_handler(event, context)           

本地開發已經完成,下面借助阿裡雲函數計算的工具

fun

可以進行一鍵部署,Fun 是一個用于支援 Serverless 應用部署的工具,它通過一個資源配置檔案(template.yml),協助您進行開發、建構、部署操作,步驟如下:

  • release 頁面對應平台的 binary 版本,解壓就可以使用。或者使用 npm install @alicloud/fun * -g 也可以直接使用。
  • 使用 fun config 配置 ak、region 等資訊。
  • 編寫 template.yml
  • fun deploy 部署

template.yml 檔案如下:

ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
  tensorflow: # 服務名
    Type: 'Aliyun::Serverless::Service'
    Properties:
      Description: 'tensorflow demo'
      Policies:
        - AliyunOSSReadOnlyAccess
    initializer: # 函數名
      Type: 'Aliyun::Serverless::Function'
      Properties:
        Handler: loader.handler  # 處理函數入口
        Initializer: loader.initializer  # initializer 入口
        CodeUri: ./code/
        Description: 'tensorflow application!'
        Runtime: python2.7
        MemorySize: 1024
        Timeout: 300
        InitializationTimeout: 60
        EnvironmentVariables:
          Bucket: test-bucket # 替換為自己的 oss bucket
          Endpoint: 'https://oss-cn-hangzhou.aliyuncs.com' # 替換掉 OSS Endpoint
          AppLibObject: applib.zip
          AppLibDir: /tmp/applib
          ModelObject: model_data.zip
          ModelDir: /tmp/model           
  • 執行

    fun deploy

    會顯示如下資訊,部署成功後可到對應 region 下檢視部署是否生效。
    使用函數 initializer 接口優化深度學習場景下模型加載的冷啟動延時

登陸函數計算

控制台

對應 region 下找到所建立的函數,連續執行兩次檢視執行結果如下。

  • 首次執行
    使用函數 initializer 接口優化深度學習場景下模型加載的冷啟動延時
  • 第二次執行
    使用函數 initializer 接口優化深度學習場景下模型加載的冷啟動延時

從以上圖檔可以看到首次函數執行時間為 3793ms,第二次函數執行時間為 810ms,執行結果都為

the predict is 2

,從執行結果可以确認函數執行正确,但性能真的提高了嗎?下面會有簡單的性能測試做對比。

這裡對改造前的函數做同樣的測試:

  • 使用函數 initializer 接口優化深度學習場景下模型加載的冷啟動延時
  • 首次執行日志
    使用函數 initializer 接口優化深度學習場景下模型加載的冷啟動延時
  • 使用函數 initializer 接口優化深度學習場景下模型加載的冷啟動延時

從以上圖檔可以看到首次函數執行時間為 17506ms,第二次函數執行時間為 815ms,通過日志可以發現首次觸發函數執行大約 13s 花費在加載模型和依賴庫上,函數的執行時間會随着模型和依賴庫規格的增大而增大。由此可見,initializer 函數的引入會使得函數執行個體在首次啟動時規避冷啟動開銷,降低函數執行時間,提高函數性能,并且不會對後續的請求産生任何影響。

總結

通過将深度學習場景下規格較大的模型、依賴庫的加載等初始化邏輯進行提取放到 initializer 函數中可以極大的提升函數性能,規避使用者系統/函數更新帶來的冷啟動開銷,幫助使用者實作業務系統的熱更新。

最後歡迎大家通過掃碼加入我們使用者群中,使用過程中有問題或者有其他問題可以在群裡提出來。函數計算官網客戶群(11721331)。

使用函數 initializer 接口優化深度學習場景下模型加載的冷啟動延時

參考文章:

1 :

Tensorflow MINIST資料模型的訓練,儲存,恢複和手寫字型識别

2:

繼續閱讀