模型服务预热

为了解决模型初次请求耗时较长的问题,EAS提供了模型预热功能,使模型服务在上线之前得到预热,从而实现模型服务上线后即可进入正常服务状态。本文介绍如何使用模型预热功能。

背景信息

初次向模型发送请求时,不同的Runtime会进行不同的初始化操作,导致初次请求耗时较高,可能存在请求超时。如Java Processor在初次启动时,JVM的冷启动会导致初始的部分请求耗时较长。对于部分TensorFlow模型,初次调用时需要将模型相关文件或参数加载到内存中,该过程可能耗费较长时间,导致前几次请求模型服务的RT较长,甚至出现408超时或450等情况。因此,EAS提供了模型预热功能,即模型服务上线前调用模型服务进行预热,使其上线后即可正常服务。

模型预热的原理是EAS服务引擎在模型服务上线前向自身发送预热请求。除初次请求耗时较长,后面几次请求可在很短时间内完成。

使用EAS模型预热功能,首先需要生成预热请求文件,然后在模型服务部署的JSON文件中指定该请求文件,最后在部署或更新模型服务时,EAS服务引擎会发送预热请求,发送成功后,模型服务才启动完成。

使用模型预热

生成预热请求文件就是按照模型服务上线后的真实请求构造一份请求文件,供预热时读取调用。您可以直接通过EAS提供的服务调用SDK构造请求,SDK的接口详情请参见SDK使用说明。本文以TensorFlow模型为例,介绍如何使用模型预热功能。

  1. 生成服务预热请求文件。

    本文以Python SDKJava 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) {
              }
          }
      }
  2. 验证请求正确性。

    您可以通过以下任何一种方式进行验证:

    • 方式一:发送服务请求进行验证

      通过以下命令向模型服务发送请求。如果返回内容太大无法直接在终端打印,可通过添加--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);
            }
        }
  3. 配置模型服务。

    1. 将模型预热的请求文件上传至OSS。

    2. 配置模型服务参数。

      在模型描述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。
              }
          }
      }

      与模型预热相关的参数如下,其他参数解释请参见JSON部署参数说明

      • warm_up_data_path:预热的请求文件路径,系统会自动寻找该文件并在模型服务上线前进行预热。

      • warm_up_count:每个预热请求发送的次数。如果没有配置,则默认为5。

  4. 部署或更新模型服务,详情请参见创建服务修改服务配置

    部署或更新模型服务的过程中,EAS引擎内部会发送预热请求,发送成功后,该模型服务才启动完成。

TensorFlow模型预热的常见问题

  • 问题现象

    在实际业务场景中,TensorFlow模型更新后可能导致服务不稳定。即使在Processor中添加预热功能,仍无法解决该问题。测试发现,每次不同的输入输出签名都会导致模型重新加载文件进行预热,即使模型已经预热加载了全部签名,发送部分请求时仍需耗费较长时间重新加载。

  • 原因分析

    这是因为TensorFlowsession->Run(inputs, output_tensor_names, {}, &outputs) 会对inputsoutput_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"); // 与预热请求相比,增加一个输出。
  • 解决方法

    每个服务都需要使用真实业务请求进行预热,并且该预热仅适用于请求的输入输出。因此,EAS提供的模型预热功能需要您上传真实的请求数据。只需让session->Run按照真实请求成功执行一次即可,您可以只上传一个预热文件,并严格按照实际调用的输入输出进行预热。