1. 程式人生 > >使用函式 initializer 介面優化深度學習場景下模型載入的冷啟動延時

使用函式 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

應用依賴的安裝

本案例需要安裝的應用依賴有 tensorflowopencv-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.zipmodel_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 平臺手動繪製生成。

30321e3e7b260c558ddae712ca13e293.png

引入 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 下檢視部署是否生效。
    image.png

測試

功能測試

登陸函式計算 控制檯 對應 region 下找到所建立的函式,連續執行兩次檢視執行結果如下。

  • 首次執行
    image.png
  • 第二次執行
    image.png

從以上圖片可以看到首次函式執行時間為 3793ms,第二次函式執行時間為 810ms,執行結果都為 the predict is 2 ,從執行結果可以確認函式執行正確,但效能真的提高了嗎?下面會有簡單的效能測試做對比。

效能測試

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

  • 首次執行
    image.png
  • 首次執行日誌
    image.png
  • 第二次執行
    image.png

從以上圖片可以看到首次函式執行時間為 17506ms,第二次函式執行時間為 815ms,通過日誌可以發現首次觸發函式執行大約 13s 花費在載入模型和依賴庫上,函式的執行時間會隨著模型和依賴庫規格的增大而增大。由此可見,initializer 函式的引入會使得函式例項在首次啟動時規避冷啟動開銷,降低函式執行時間,提高函式效能,並且不會對後續的請求產生任何影響。

總結

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

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

參考文章:

1 :Tensorflow MINIST資料模型的訓練,儲存,恢復和手寫字型識別
2:函式計算部署機器學習遇到的問題和解法