使用函式 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 下檢視部署是否生效。
測試
功能測試
登陸函式計算 控制檯 對應 region 下找到所建立的函式,連續執行兩次檢視執行結果如下。
- 首次執行
- 第二次執行
從以上圖片可以看到首次函式執行時間為 3793ms,第二次函式執行時間為 810ms,執行結果都為 the predict is 2
,從執行結果可以確認函式執行正確,但效能真的提高了嗎?下面會有簡單的效能測試做對比。
效能測試
這裡對改造前的函式做同樣的測試:
- 首次執行
- 首次執行日誌
- 第二次執行
從以上圖片可以看到首次函式執行時間為 17506ms,第二次函式執行時間為 815ms,通過日誌可以發現首次觸發函式執行大約 13s 花費在載入模型和依賴庫上,函式的執行時間會隨著模型和依賴庫規格的增大而增大。由此可見,initializer 函式的引入會使得函式例項在首次啟動時規避冷啟動開銷,降低函式執行時間,提高函式效能,並且不會對後續的請求產生任何影響。
總結
通過將深度學習場景下規格較大的模型、依賴庫的載入等初始化邏輯進行提取放到 initializer 函式中可以極大的提升函式效能,規避使用者系統/函式升級帶來的冷啟動開銷,幫助使用者實現業務系統的熱升級。
最後歡迎大家通過掃碼加入我們使用者群中,使用過程中有問題或者有其他問題可以在群裡提出來。函式計算官網客戶群(11721331)。
參考文章:
1 :Tensorflow MINIST資料模型的訓練,儲存,恢復和手寫字型識別
2:函式計算部署機器學習遇到的問題和解法