背景
深度學習場景使用函數計算典型案例
阿裡雲
函數計算客戶
碼隆科技是一家專注于深度學習與計算機視覺技術創新的公司。當碼隆的客戶上傳大量圖像資料後,需要盡快把圖像按照客戶指定的方式處理,包括商品識别,紡織面料等柔性材質識别分析,内容審查,以圖搜圖等等。圖像處理基于碼隆預先訓練好的深度學習模型,要求在短時間内準備大量的計算資源進行大規模并行處理。客戶将深度學習推理邏輯實作為函數,在函數中加載模型後對圖像資料進行處理。通過函數計算提供的大規模計算能力,客戶能夠短時間處理大量圖像,平穩應對峰值壓力。更多詳細案例請見
函數計算客戶案例。
深度學習場景的客戶在使用函數計算服務中更希望平台做哪些改進?
深度學習場景下加載模型是主要的應用層冷啟動開銷,模型的規格多為 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平台手動繪制生成。

經過應用遷移處理後得到了一個可以運作在函數計算服務上的函數,很明顯可以看到函數入口
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
- 執行
會顯示如下資訊,部署成功後可到對應 region 下檢視部署是否生效。fun deploy
使用函數 initializer 接口優化深度學習場景下模型加載的冷啟動延時
登陸函數計算
控制台對應 region 下找到所建立的函數,連續執行兩次檢視執行結果如下。
- 首次執行
使用函數 initializer 接口優化深度學習場景下模型加載的冷啟動延時 - 第二次執行
使用函數 initializer 接口優化深度學習場景下模型加載的冷啟動延時
從以上圖檔可以看到首次函數執行時間為 3793ms,第二次函數執行時間為 810ms,執行結果都為
the predict is 2
,從執行結果可以确認函數執行正确,但性能真的提高了嗎?下面會有簡單的性能測試做對比。
這裡對改造前的函數做同樣的測試:
-
使用函數 initializer 接口優化深度學習場景下模型加載的冷啟動延時 - 首次執行日志
使用函數 initializer 接口優化深度學習場景下模型加載的冷啟動延時 -
使用函數 initializer 接口優化深度學習場景下模型加載的冷啟動延時
從以上圖檔可以看到首次函數執行時間為 17506ms,第二次函數執行時間為 815ms,通過日志可以發現首次觸發函數執行大約 13s 花費在加載模型和依賴庫上,函數的執行時間會随着模型和依賴庫規格的增大而增大。由此可見,initializer 函數的引入會使得函數執行個體在首次啟動時規避冷啟動開銷,降低函數執行時間,提高函數性能,并且不會對後續的請求産生任何影響。
總結
通過将深度學習場景下規格較大的模型、依賴庫的加載等初始化邏輯進行提取放到 initializer 函數中可以極大的提升函數性能,規避使用者系統/函數更新帶來的冷啟動開銷,幫助使用者實作業務系統的熱更新。
最後歡迎大家通過掃碼加入我們使用者群中,使用過程中有問題或者有其他問題可以在群裡提出來。函數計算官網客戶群(11721331)。
參考文章:
1 :
Tensorflow MINIST資料模型的訓練,儲存,恢複和手寫字型識别2: