为了解决模型初次请求耗时较长的问题,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
按照真实请求成功执行一次即可。因此,您可以只上传一个预热文件,并严格按照实际调用的输入输出进行预热。