為瞭解決模型初次請求耗時較長的問題,EAS提供了模型預熱功能,使模型服務在上線之前得到預熱,從而實現模型服務上線後即可進入正常服務狀態。本文介紹如何使用模型預熱功能。
背景資訊
通常向模型發送初次請求時,不同的Runtime會進行不同的初始化操作,導致初次請求耗時相對較高。因此,模型服務在擴容或服務更新時,可能存在請求逾時。例如,Java Processor在初次啟動時,JVM的冷啟動會導致初始的部分請求耗時較長。對於部分TensorFlow模型,初次調用時需要將模型相關檔案或參數載入到記憶體中,該過程可能要耗費較長時間,從而導致前幾次請求模型服務的RT較長,甚至出現408逾時或450等情況。因此,EAS提供了模型預熱功能,即模型服務上線之前對模型服務進行調用,使模型得到預熱,從而實現模型服務上線後即可進入正常服務狀態。
模型預熱的實現原理是在模型服務上線之前,由EAS服務引擎向自身發送您上傳的預熱請求。系統設定每10秒(keep_alive*2)發送一次請求或在上一次請求正確結束後立即發送請求,且每個請求檔案連續發送5次。如果模型初次請求小於10秒,則根據經驗除了初次預熱耗時較長,後面的幾次請求都可以在很短的時間內完成,總預熱時間約在10秒以內。如果初次請求大於10秒,則預熱耗時會稍微長一些,可能達到幾十秒。
使用EAS提供的模型預熱功能,首先需要產生一份預熱請求檔案,然後在模型服務部署的JSON檔案中指定該請求檔案,最後在部署模型服務或更新模型服務的過程中,EAS服務引擎內部會發送預熱請求,發送成功後,該模型服務才啟動完成。
使用模型預熱
產生預熱請求檔案就是按照模型服務上線後的真實請求構造一份請求檔案,供預熱時讀取調用。您可以直接通過EAS提供的服務調用SDK構造請求,SDK的介面詳情請參見SDK使用說明。本文以TensorFlow模型為例,介紹如何使用模型預熱功能。
產生服務預熱請求檔案。
本文以Python SDK和Java SDK為例,構造TensorFlow模型服務的請求。對於其它有預熱需求的模型服務,您可以使用對應的SDK,參見產生TensorFlow預熱檔案的方法構造服務要求。對於將字串作為輸入的模型服務,您可以將請求按照STRING存入TXT檔案中(同一個TXT檔案可以包括多行請求,每個請求為一行),EAS會自動區分檔案類型,並按照不同的形式發送預熱請求。
重要TensorFlow模型預熱所需的請求必須與上線後真實請求的輸入輸出簽名完全一致。
使用SDK構造TensorFlow模型服務的請求樣本如下:
使用Python SDK構造
#!/usr/bin/env python from eas_prediction import PredictClient from eas_prediction import StringRequest from eas_prediction import TFRequest if __name__ == '__main__': # 請求樣本,請根據具體情況構造。請特別注意:預熱所需的請求必須與上線後真實請求的輸入簽名完全一致。 req = TFRequest('serving_default') req.add_feed('sentence1', [200, 15], TFRequest.DT_INT32, [1] * 200 * 15) req.add_feed('sentence2', [200, 15], TFRequest.DT_INT32, [1] * 200 * 15) req.add_feed('y', [200, 2], TFRequest.DT_INT32, [2] * 200 * 2) req.add_feed('keep_rate', [], TFRequest.DT_FLOAT, [0.2]) req.add_feed('images', [1, 784], TFRequest.DT_FLOAT, [1] * 784) req.add_fetch('sorted_labels') req.add_fetch('sorted_probs') # print(req.request_data) # 列印檢查參數。 with open("warm_up.bin", "wb") as fw : fw.write(req.to_string()); # 儲存得到的 warm_up.bin 即為預熱請求檔案。
使用Java SDK構造
在Maven工程中使用EASJava SDK,必須在pom.xml檔案的<dependencies>中添加eas-sdk的依賴,樣本如下,最新版本以Maven倉庫中顯示的為準。
<dependency> <groupId>com.aliyun.openservices.eas</groupId> <artifactId>eas-sdk</artifactId> <version>2.0.13</version> </dependency>
Java SDK程式碼範例如下:
import java.io.File; import com.aliyun.openservices.eas.predict.request.TFDataType; import com.aliyun.openservices.eas.predict.request.TFRequest; import org.apache.commons.io.FileUtils; public class TestTf { public static void main(String[] args) throws Exception{ // 請求樣本,請按照實際需要的預熱請求進行構造。 TFRequest request = new TFRequest(); request.setSignatureName("predict_images"); float[] content = new float[784]; for (int i = 0; i < content.length; i++){ content[i] = (float)0.0; } request.addFeed("images", TFDataType.DT_FLOAT, new long[]{1, 784}, content); request.addFetch("scores"); try { // 構造檔案。如果沒有該檔案,則建立一個新的檔案。 File writename = new File("/path/to/warm_up1.bin"); FileUtils.writeByteArrayToFile(writename, request.getRequest().toByteArray()); } catch (Exception ex) { } } }
驗證請求正確性。
您可以通過以下任何一種方式進行驗證:
方式一:發送服務要求進行驗證
通過以下命令向模型服務發送請求。如果返回內容太大無法直接在終端列印,可通過添加
--output <filePath>
,將結果儲存在檔案中。curl --data-binary @"</path/to/warmup.bin>" -H 'Authorization: <yourToken>' <serviceAddress>
您需要將以下參數替換為實際值:
</path/to/warmup.bin>:上一步產生的請求檔案路徑。
<yourToken>:模型服務的訪問Token。
<serviceAddress>:模型服務的訪問地址。
方式二:通過解析的方式驗證
Python解析
from eas_prediction import TFRequest req = TFRequest() with open('/path/to/warm_up1.bin', 'rb') as wm: req.request_data.ParseFromString(wm.read()) print(req.request_data)
Java解析
import com.aliyun.openservices.eas.predict.proto.PredictProtos; import org.apache.commons.io.FileUtils; import java.io.File; public class Test { public static void main(String[] args) throws Exception { File refile = new File("/path/to/warm_up1.bin"); byte[] data = FileUtils.readFileToByteArray(refile); PredictProtos.PredictRequest pb = PredictProtos.PredictRequest.parseFrom(data); System.out.println(pb); } }
配置模型服務。
將模型預熱的請求檔案上傳至OSS。
配置模型服務參數。
在模型描述JSON檔案中,配置模型服務參數。
{ "name":"warm_up_demo", "model_path":"oss://path/to/model", "warm_up_data_path":"oss://path/to/warm_up_test.bin", // 模型預熱的請求檔案路徑。 "processor":"tensorflow_cpu_1.15", "metadata":{ "cpu":2, "instance":1, "rpc": { "warm_up_count": 5, // 每個預熱請求發送的次數。如果沒有配置,則預設為5。 } } }
與模型預熱相關的參數如下,其他參數解釋請參見建立服務:
warm_up_data_path:預熱的請求檔案路徑,系統會自動尋找該檔案並在模型服務上線前進行預熱。
warm_up_count:每個預熱請求發送的次數。如果沒有配置,則預設為5。
部署或更新模型服務的過程中,EAS引擎內部會發送預熱請求,發送成功後,該模型服務才啟動完成。
TensorFlow模型預熱的常見問題
問題現象
在實際業務情境中,可能出現TensorFlow模型更新後導致服務不穩定的情況。在Processor中添加預熱功能(指在Processor的實現代碼中添加調用模型推理函數進行預熱)後,仍無法解決該問題。經多方測試發現,對於TensorFlow模型而言,每一次不同的輸入輸出簽名都會導致模型重新載入所需檔案進行預熱,即使模型已經將全部輸入輸出簽名進行了預熱載入,發送部分輸入輸出請求時仍需耗費較長時間重新載入。
原因分析
出現該現象的原因是TensorFlow的
session->Run(inputs, output_tensor_names, {}, &outputs)
會對inputs和output_tensor_names進行雜湊校正,如果輸入輸出發生變化,就重新載入,而不判斷實際是否需要重新載入。例如,模型的輸入如下。
Inputs: threshold: []; DT_FLOAT model_id: []; DT_STRING input_holder: [-1]; DT_STRING
模型的輸出如下。
Outputs: model_version_id: []; DT_STRING sorted_labels: [-1, 3]; DT_STRING sorted_probs: [-1, 3]; DT_FLOAT
發送如下預熱請求。
request.addFeed("input_holder",TFDataType.DT_STRING, new long[]{1}, input); request.addFeed("threshold", TFDataType.DT_FLOAT, new long[] {}, th); request.addFeed("model_id", TFDataType.DT_STRING, new long[]{}, model_name); request.addFetch("sorted_labels"); request.addFetch("sorted_probs");
預熱成功後,再次發送如下請求(與預熱請求相比,增加一個輸出),模型仍然需要重新載入所需檔案。
request.addFeed("input_holder",TFDataType.DT_STRING, new long[]{1}, input); request.addFeed("threshold", TFDataType.DT_FLOAT, new long[] {}, th); request.addFeed("model_id", TFDataType.DT_STRING, new long[]{}, model_name); request.addFetch("sorted_labels"); request.addFetch("sorted_probs"); request.addFetch("model_version_id"); // 與預熱請求相比,增加一個輸出。
解決方案
對於TensorFlow而言,每個服務都需要使用真實業務請求進行預熱,並且該預熱僅適用於請求的輸入輸出。因此,EAS提供的模型預熱功能需要您上傳真實的請求資料。
對於TensorFlow模型進行預熱,只需要讓
session->Run
按照真實請求成功執行一次即可。因此,您可以只上傳一個預熱檔案,並嚴格按照實際調用的輸入輸出進行預熱。