构建模型服务方法示例

释放双眼,带上耳机,听听看~!
本文介绍了使用模型服务方法构建示例预测器和TorchServe的方法构建示例服务,以及浏览流行的开源模型服务库和系统,解释生产模型发布过程,讨论模型发布后的监控。

本章内容包括:

  • 使用模型服务方法构建一个示例预测器
  • 使用TorchServe和模型服务器方法构建一个示例服务
  • 浏览流行的开源模型服务库和系统
  • 解释生产模型发布过程
  • 讨论模型发布后的监控

在前一章中,我们讨论了模型服务的概念,以及用户场景和设计模式。在本章中,我们将重点介绍这些概念在生产环境中的实际实现。

正如我们所说,目前实施模型服务的挑战之一是有太多的可能方式。除了多个黑盒解决方案外,还有许多选项可以自定义和从头开始构建全部或部分内容。我们认为通过具体的示例来教授您如何选择合适的方法是最好的方式。

在本章中,我们实施了两个示例服务,演示了两种最常用的模型服务方法:一个使用自建模型服务容器,展示了模型服务方法(第7.1节),另一个使用TorchServe(用于PyTorch模型的模型服务器),展示了模型服务器方法(第7.2节)。这两种方法都用于提供在第3章中训练的意图分类模型的服务。一旦您完成了这些示例,我们将在第7.3节中介绍最流行的开源模型服务工具,帮助您了解它们的功能、最佳用法以及对您决策重要的其他因素。在本章的其余部分,我们将重点关注模型服务的操作和监控,包括将模型发布到生产环境并监控模型性能。

通过阅读本章,您不仅可以具体了解不同的模型服务设计,还可以具备选择适合您自己情况的正确方法的能力。更重要的是,本章将呈现模型服务领域的整体视角,不仅涵盖构建模型服务,还包括在构建模型服务系统之后的运营和监控。

注意: 在本章中,模型服务、模型推断和模型预测这些术语是可以互换使用的。它们都指的是使用给定的数据点执行模型。

一个模型服务示例

在本节中,我们将展示第一个样本预测服务。该服务采用了模型服务的方法(第6.2.2节),可用于单模型应用(第6.3.1节)和多租户应用(第6.3.2节)。

这个样本服务遵循单模型应用设计(第6.3.1节),它包括前端API组件和后端预测器。我们还对预测器进行了一些改进,使其能够支持多个意图分类模型。我们将按照以下步骤介绍这个样本服务:

  1. 在本地运行样本预测服务
  2. 讨论系统设计
  3. 查看其子组件的实现细节:前端服务和后端预测器

与服务互动

请参考以下代码运行在您的本地机器上的样例预测服务。以下脚本首先运行后端预测器,然后运行前端服务。

注意, 设置预测服务有点繁琐;我们需要运行元数据和存储服务,并准备好模型。为了清晰地演示这个概念,代码清单 7.1 强调了主要的设置步骤。要使模型服务在您的本地机器上运行,请完成附录 A 中的实验(A.2 节),然后使用命令 ./scripts/lab-004-model-serving.sh {run_id} {document} 发送模型预测请求。

# step 1: start backend predictor service
docker build -t orca3/intent-classification-predictor:latest  
  -f predictor/Dockerfile predictor
  
docker run --name intent-classification-predictor 
   --network orca3 --rm -d -p "${ICP_PORT}":51001 
   -v "${MODEL_CACHE_DIR}":/models 
   orca3/intent-classification-predictor:latest
   
# step 2: start the prediction service (the web api)
docker build -t orca3/services:latest -f 
  services.dockerfile .
  
docker run --name prediction-service --network orca3 
  --rm -d -p "${PS_PORT}":51001 -v "${MODEL_CACHE_DIR}":/tmp/modelCache  
  orca3/services:latest prediction-service.jar

一旦服务启动,您可以向其发送预测请求;服务将加载在第3章中训练的意图分类模型,对给定的文本进行模型预测,并返回预测结果。在下面的示例中,将一个文本字符串“merry christmas”发送到服务,并预测为“joy”类别:

#./scripts/lab-004-model-serving.sh 1 "merry christmas"
grpcurl -plaintext
         -d "{
            "runId": "1",
            "document": "merry christmas"
     }"
localhost:"${PS_PORT}"
prediction.PredictionService/Predict

model_id is 1
document is hello world
{
   "response": "{"result": "joy"}"
}

服务设计

该示例服务由前端接口组件和后端预测器组件组成。前端组件完成以下三个任务:托管公共的预测API、从元数据存储下载模型文件到共享磁盘卷,以及将预测请求转发给后端预测器。后端预测器是一个自建的预测器容器,负责加载意图分类模型并执行这些模型以响应预测请求。

该预测服务有两个外部依赖项:元数据存储服务和共享磁盘卷。元数据存储服务保存有关模型的所有信息,例如模型算法名称、模型版本和指向真实模型文件云存储的模型URL。共享磁盘卷实现了前端服务和后端预测器之间的模型文件共享。您可以在图7.1中看到模型服务过程的端到端概览。

构建模型服务方法示例

在图7.1中展示的示例模型服务的系统设计中,完成一个预测请求需要经过六个步骤。让我们逐步了解图中标注的每个步骤:

  1. 用户向预测服务(前端组件)发送一个预测请求,请求中包含指定的模型ID和文本字符串(即文档)。模型ID是训练服务生成的用于标识每个模型的唯一标识符。

  2. 前端服务根据模型ID从元数据存储中获取模型的元数据。对于每个成功训练的模型,训练服务会将模型文件保存到云存储,并将模型元数据(模型ID、模型版本、名称和URL)保存到元数据存储中;这就是为什么我们可以在元数据存储中找到模型信息。

  3. 如果模型文件尚未下载,前端组件会将其下载到共享磁盘卷。

  4. 前端组件将推理请求转发给后端预测器。

  5. 后端预测器通过从共享磁盘卷中读取模型文件将意图分类模型加载到内存中。

  6. 后端预测器执行模型对给定的文本字符串进行预测,并将预测结果返回给前端组件。

前端服务

现在,让我们专注于前端服务。前端服务有三个主要组件:Web界面、预测器管理和预测器后端客户端(CustomGrpcPredictorBackend)。这些组件响应托管的公共gRPC模型服务API,并管理后端预测器的连接和通信。图7.2展示了前端服务的内部结构以及在接收到预测请求时的内部工作流程。

构建模型服务方法示例

让我们考虑一下图7.2中描述的模型服务工作流中的意图预测场景,并应用我们刚刚回顾的六个步骤:

  1. 用户通过Web界面向模型ID为A的接口发送意图预测请求。
  2. Web界面调用预测器连接管理器来处理此请求。
  3. 预测器连接管理器通过查询元数据存储库,通过搜索等于A的模型ID来获取模型元数据;返回的模型元数据包含模型算法类型和模型文件URL。
  4. 基于模型算法类型,预测器管理器选择合适的预测器后端客户端来处理请求。在这种情况下,它选择CustomGrpcPredictorBackend,因为我们正在演示用于意图分类的自建模型服务容器。
  5. CustomGrpcPredictorBackend客户端首先在共享模型文件磁盘中检查模型A的文件是否存在。如果模型以前没有被下载过,则使用模型元数据中的模型URL从云存储中下载模型文件到共享文件磁盘中。
  6. CustomGrpcPredictorBackend客户端接着调用在服务配置文件中与该后端客户端预先注册的模型预测器。在本例中,CustomGrpcPredictorBackend将调用我们自建的预测器,即意图预测器,将在7.1.4节中进行讨论。

现在我们已经回顾了系统设计和工作流程,让我们考虑主要组件的实际代码实现,包括Web界面(预测API)、预测连接管理器、预测后端客户端和意图预测器。

前端服务模型服务代码解析

下面的代码清单重点介绍了图7.2中提到的预测工作流的核心实现。您还可以在src/main/java/org/orca3/miniAutoML/prediction/PredictionService.java中找到完整的实现。

public void predict(PredictRequest request, .. .. ..) {
    .. .. ..
    String runId = request.getRunId();
    
    if (predictorManager.containsArtifact(runId)) { 
        artifactInfo = predictorManager.getArtifact(runId);
    } else { 
      try {
         artifactInfo = msClient.getArtifact(
              GetArtifactRequest.newBuilder()
             .setRunId(runId).build());
      } catch (Exception ex) {
               .. .. .. 
      }
    }
    
    # Step 4, pick predictor backend client by model algorithm type
    PredictorBackend predictor;
    if (predictorManager.containsPredictor(artifactInfo.getAlgorithm())) {
          predictor = predictorManager.getPredictor(artifactInfo.getAlgorithm());
    } else {
        .. .. ..
    }
    
    # Step 5, use the selected predictor client to download the model files
    predictor.downloadModel(runId, artifactInfo);
    
    # Step 6, use the selected predictor client to call
    # its backend predictor for model serving
    String r = predictor.predict(
      artifactInfo, request.getDocument());
    .. .. ..
}

预测 API

前端服务只提供一个 API——Predict,用于发出预测请求。请求有两个参数:runId 和 document。runId 不仅用于引用训练服务中的模型训练运行(第3章),还可以用作引用模型的模型 ID。document 是客户想要进行预测的文本。

通过使用 Predict API,用户可以指定一个意图模型(使用 runId)来预测给定文本字符串(document)的意图。以下清单显示了 Predict API 的 gRPC 契约(grpc-contract/src/main/proto/prediction_service.proto)。

service PredictionService {
rpc Predict(PredictRequest) returns (PredictResponse);
}

message PredictRequest {
 string runId = 3;
 string document = 4;
}

message PredictResponse {
 string response = 1;
}

预测器连接管理器

前端服务的一个重要角色是路由预测请求。给定一个预测请求,前端服务需要根据请求中所需的模型算法类型找到正确的后端预测器。这个路由是在 PredictorConnectionManager 中完成的。在我们的设计中,模型算法和预测器的映射是预先定义在环境属性中的。当服务启动时,PredictorConnectionManager 将读取这个映射,因此服务知道哪个后端预测器用于哪种模型算法类型。

虽然在这个示例中我们只演示了我们自己构建的意图分类预测器,但 PredictorConnectionManager 可以支持任何其他类型的后端预测器。让我们来看一下下面的清单(config/config-docker-docker.properties),了解如何配置模型算法和预测器的映射。

# the algorithm and predictor mapping can be defined in
# either app config or docker properties

# enable algorithm types
ps.enabledPredictors=intent-classification

# define algorithm and predictors mapping
# predictor.<algorithm_type>.XXX = predictor[host, port, type]

predictors.intent-classification.host= 
  Intent-classification-predictor predictors.intent-
classification.port=51001 predictors.intent-
classification.techStack=customGrpc

现在,让我们回顾一下代码清单 7.5,看看预测器管理器是如何读取算法和预测器的映射,并使用这些信息来初始化预测器后端客户端以发送预测请求的。完整的实现位于 prediction-service/src/main/java/org/orca3/miniAutoML/prediction/PredictorConnectionManager.java 中。

public class PredictorConnectionManager {
  private final Map<String, List<ManagedChannel>> channels = new HashMap<>();
  
  private final Map<String, PredictorBackend> clients = new HashMap<>();
    
  private final Map<String, GetArtifactResponse> artifactCache;
  
  // create predictor backend objects for
  // the registered algorithm and predictor
  public void registerPredictor(String algorithm,Properties properties) {
     String host = properties.getProperty(String.format(“predictors.%s.host”, algorithm));
     int port = Integer.parseInt(properties.getProperty(String.format(“predictors.%s.port”, algorithm)));
     String predictorType = properties.getProperty( String.format(“predictors.%s.techStack”, algorithm));

     ManagedChannel channel = ManagedChannelBuilder
       .forAddress(host, port)
       .usePlaintext().build();
       
     switch (predictorType) {
       .. ..
       case “customGrpc”:
     default:
       channels.put(algorithm, List.of(channel));
       clients.put(algorithm, new CustomGrpcPredictorBackend(channel, modelCachePath, minioClient));
     break;
     } 
   }
   .. .. .. 
}

在代码清单 7.5 中,我们可以看到 PredictorConnectionManager 类提供了 registerPredictor 函数来注册预测器。它首先从属性中读取算法和预测器的映射信息,然后创建实际的预测器后端客户端 CustomGrpcPredictorBackend,用于与后端意图预测容器进行通信。

您还可能注意到 PredictorConnectionManager 类有几个缓存,例如模型元数据缓存(artifactCache)和模型后端预测器客户端(clients)。这些缓存可以极大地提高模型服务的效率。例如,模型元数据缓存(artifactCache)可以通过避免为已经下载的模型调用元数据存储服务来减少服务请求的响应时间。

预测器后端客户端

预测器客户端是前端服务用于与不同预测器后端进行通信的对象。按设计,每种类型的预测器后端支持自己的模型类型,并且它有自己的用于通信的客户端,该客户端被创建并存储在 PredictorConnectionManager 中。每个预测器后端客户端都继承自 PredictorBackend 接口,如下面的代码清单所示。

public interface PredictorBackend {
   void downloadModel(String runId,
           GetArtifactResponse artifactResponse);

   String predict(GetArtifactResponse artifact, String document);
   void registerModel(GetArtifactResponse artifact); 
}

这三个方法 downloadModel、predict 和 registerModel 都很容易理解。每个客户端都实现了这些方法,用于下载模型并向其注册的后端服务发送预测请求。参数 GetArtifactResponse 是从元数据存储中获取的模型元数据对象。

在这个(意图预测器)示例中,预测器后端客户端是 CustomGrpcPredictorBackend。您可以在 prediction-service/src/main/java/org/orca3/miniAutoML/prediction/CustomGrpcPredictorBackend.java 中找到详细的实现。以下代码片段展示了该客户端如何使用 gRPC 协议向自建的意图预测器容器发送预测请求:

// calling backend predictor for model serving
public String predict(GetArtifactResponse artifact, String document) { 
  return stub.predictorPredict(PredictorPredictRequest
         .newBuilder().setDocument(document)
         .setRunId(artifact.getRunId())
         .build()).getResponse();
}

意图分类预测器

我们已经看到了前端服务及其内部的路由逻辑,现在让我们来看看这个示例预测服务的最后一部分:后端预测器。为了向您展示一个完整的深度学习用例,我们实现了一个预测器容器,用于执行第3章中训练的意图分类模型。

我们可以将这个自建的意图分类预测器看作一个独立的微服务,可以同时为多个意图模型提供服务。它具有gRPC Web接口和模型管理器。模型管理器是预测器的核心部分;它执行多个任务,包括加载模型文件、初始化模型、将模型缓存到内存中以及使用用户输入执行模型。图7.3展示了预测器的设计图和预测器内部的预测工作流程。

构建模型服务方法示例

让我们以模型A的意图预测请求来考虑图7.3中的工作流程。它按照以下步骤运行:

  1. 前端服务中的预测器客户端调用预测器的Web gRPC接口,使用模型A进行意图预测。
  2. 请求触发了模型管理器的调用。
  3. 模型管理器从共享磁盘卷中加载模型A的模型文件,初始化模型,并将其放入模型缓存中。模型文件应该已经由前端服务放置在共享磁盘卷中。
  4. 模型管理器借助Transformer的帮助执行模型A,对输入和输出数据进行预处理和后处理。
  5. 返回预测结果。

接下来,让我们来看一下工作流中提到的组件的实际实现。

预测API

意图预测器具有一个API – PredictorPredict(请参见代码清单7.7)。它接受两个参数,runId和document。runId是模型的ID,document是一个文本字符串。您可以在grpc-contract/src/main/proto/prediction_service.proto中找到完整的gRPC协议。

service Predictor {
rpc PredictorPredict(PredictorPredictRequest) returns (PredictorPredictResponse);
}

message PredictorPredictRequest {
 string runId = 1;
 string document = 2;
}

message PredictorPredictResponse {
 string response = 1;
}

您可能注意到预测器的API与前端API(代码清单7.2)相同,这是为了简化起见。但在实际应用中,它们通常是不同的,主要是因为它们是为不同的目的而设计的。预测器的predict API更适合模型执行,而前端的predict API更适合客户和业务的需求。

模型文件

我们在模型训练服务(第3章)中生成的每个意图分类模型都有三个文件。manifest.json文件包含模型元数据和数据集标签;预测器需要这些信息来将模型预测结果从整数转换为有意义的文本字符串。model.pth是模型的学习参数;预测器将读取这些网络参数来设置模型的神经网络,用于模型服务。vocab.pth是用于模型训练的词汇文件,也是服务所必需的,因为我们需要它将用户输入(字符串)转换为模型输入(十进制数)。让我们回顾一下样本意图模型:

├── manifest.json 
├── model.pth
└── vocab.pth

// A sample manifest.json file
{
  "Algorithm": "intent-classification",
  "Framework": "Pytorch",
  "FrameworkVersion": "1.9.0",
  "ModelName": "intent",
  "CodeVersion": "80bf0da",
  "ModelVersion": "1.0",
  "classes": {
    "0": "cancel",
    "1": "ingredients_list",
    "2": "nutrition_info",
    "3": "greeting",
    .. .. ..
}

在保存PyTorch模型时,有两种选择:序列化整个模型或仅序列化学习的参数。第一种选择将序列化整个模型对象,包括其类和目录结构,而第二种选择仅保存模型网络的可学习参数。

根据Matthew Inkawhich的文章“PyTorch: Saving and Loading Models”(mng.bz/zm9B),PyTorch团队建议仅保存模型的学习参数(即模型的state_dict)。如果保存整个模型,则序列化数据将与保存模型时使用的特定类和精确目录结构绑定在一起。模型类本身不会被保存,而是保存包含该类的文件。因此,在加载时,序列化的模型代码在其他项目中使用或重构后可能会以各种方式出现错误。
出于这个原因,我们只保存模型的state_dict(学习参数)作为模型文件,例如在这个例子中是model.pth文件。我们使用以下代码进行保存:torch.save(model.state_dict(), model_local_path)。因此,预测器需要知道模型的神经网络架构(请参见代码清单7.8)来加载模型文件,因为模型文件只是state_dict – 模型网络的参数。

代码清单7.8(predictor/predict.py)展示了我们在预测器中用于加载模型文件(仅包含参数的model.pth)的模型架构。服务中的模型执行代码源自模型训练代码。如果将以下清单中的模型定义与我们训练代码(training-code/text-classification/train.py)中的TextClassificationModel类进行比较,您会发现它们是相同的。这是因为模型服务本质上是一个模型训练运行。

class TextClassificationModel(nn.Module):
   def __init__(self, vocab_size, embed_dim,fc_size, num_class): 
       super(TextClassificationModel, self).__init__()
       self.embedding = nn.EmbeddingBag(vocab_size, embed_dim,sparse=True) 
       self.fc1 = nn.Linear(embed_dim, fc_size)
       self.fc2 = nn.Linear(fc_size, num_class)
       self.init_weights()

   def forward(self, text, offsets):
       embedded = self.embedding(text, offsets)
       return self.fc2(self.fc1(embedded))

也许您会想知道训练代码和模型服务代码是否现在合并在一起。当训练代码发生变化时,似乎预测器中的模型服务代码也需要进行调整。这只在某种程度上是正确的;模型训练算法的变化对模型服务的影响往往取决于上下文。以下是这种关系的一些细微之处。

首先,训练代码和服务代码只需要在神经网络架构和输入/输出数据模式上进行同步。其他模型训练的变化,例如训练策略、超参数调整、数据集拆分和增强,不会影响模型服务,因为它们仅导致模型的权重和偏置文件发生变化。其次,在训练中改变神经网络架构时,应引入模型版本控制。在实践中,每次模型训练或重新训练都会为输出模型分配一个新的模型版本。因此,需要解决的问题是如何为模型提供不同版本的服务。

这个示例服务不处理模型版本管理。然而,在第7.5节和第8章中,我们将深入讨论模型版本的元数据管理。这里我们只是简单描述了大致的思路。

如果您正在使用类似的模型服务方法并具有自定义的预测器后端,您需要准备多个预测器后端的版本,以匹配使用不同神经网络架构训练的模型。发布模型时,训练代码、服务代码和模型文件的版本需要作为模型元数据的一部分相关联,并保存在元数据存储中。因此,在服务时,预测服务(前端服务)可以搜索元数据存储,以确定应将请求路由到哪个预测器版本以进行给定模型的预测。

如果您使用模型服务器方法,在不同版本的模型上进行服务变得更加容易,因为这种方法打破了服务代码(模型执行代码)与训练代码之间的依赖关系。您可以在第7.2节中看到一个具体的例子。

注意: 正如我们在第6章(第6.1.3节)中提到的,模型训练和模型服务都使用相同的机器学习算法,但以不同的执行模式进行:学习和评估。然而,我们希望再次澄清这个概念。理解训练代码、服务代码和模型文件之间的关系是构建服务系统设计的基础。

模型管理

模型管理器是这个意图预测器的关键组件。它托管内存模型缓存,加载模型文件并执行模型。下面的代码清单(predictor/predict.py)显示了模型管理器的核心代码。

class ModelManager:
  def __init__(self, config, tokenizer, device):
    self.model_dir = config.MODEL_DIR
    self.models = {}
    
  # load model file and initialize model
  def load_model(self, model_id):
    if model_id in self.models:
      return
      
    # load model files, including vocabulary, prediction class mapping. 
    vacab_path = os.path.join(self.model_dir, model_id, "vocab.pth") 
    manifest_path = os.path.join(self.model_dir, model_id, "manifest.json") 
    model_path = os.path.join(self.model_dir, model_id, "model.pth")

    vocab = torch.load(vacab_path)
    with open(manifest_path, 'r') as f:
    manifest = json.loads(f.read())
    classes = manifest['classes']

    # initialize model graph and load model weights
    num_class, vocab_size, emsize = len(classes), len(vocab), 64 
    model = TextClassificationModel(vocab_size, emsize,
       self.config.FC_SIZE, num_class).to(self.device)
    model.load_state_dict(torch.load(model_path))
    model.eval()
    
    self.models[self.model_key(model_id)] = model
self.models[self.model_vocab_key(model_id)]
➥ = vocab self.models[self.model_classes(model_id)]
➥ = classes

  # run model to make prediction
  def predict(self, model_id, document):
    # fetch model graph, dependency and
    # classes from cache by model id
    
    model = self.models[self.model_key(model_id)]
    vocab = self.models[self.model_vocab_key(model_id)] 
    classes = self.models[self.model_classes(model_id)]

    def text_pipeline(x):
      return vocab(self.tokenizer(x))

    # transform user input data (text string)
    # to model graph’s input
    processed_text = torch.tensor(text_pipeline(document), dtype=torch.int64) 
    offsets = [0, processed_text.size(0)]
    offsets = torch.tensor(offsets[:-1]).cumsum(dim=0)

    val = model(processed_text, offsets)

    # convert prediction result from an integer to
    # a text string (class)
    res_index = val.argmax(1).item()
    res = classes[str(res_index)]
    print("label is {}, {}".format(res_index, res))
    return res

意图预测器的预测请求工作流程

您已经了解了意图预测器的主要组件,现在让我们看看这个预测器内部的端到端工作流程。首先,我们通过将PredictorServicer注册到gRPC服务器来公开预测API,这样前端服务就可以远程与预测器通信。其次,当前端服务调用PredictorPredict API时,模型管理器将加载模型到内存中,运行模型,并返回预测结果。代码清单7.10突出了上述工作流程的代码实现。您可以在predictor/predict.py中找到完整的实现。

def serve():
  .. .. ..
  model_manager = ModelManager(config,tokenizer=get_tokenizer('basic_english'), device="cpu")
  server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
  prediction_service_pb2_grpc.add_PredictorServicer_to_server(PredictorServicer(model_manager), server)
.. .. ..

class PredictorServicer(prediction_service_pb2_grpc.PredictorServicer): 
   def __init__(self, model_manager):
     self.model_manager = model_manager

   # Serving logic
   def PredictorPredict(self, request, context: grpc.ServicerContext):
      # load model
      self.model_manager.load_model(model_id=request.runId)
      
class_name = self.model_manager.
   predict(request.runId, request.document)
return PredictorPredictResponse(response=json.dumps({'res': class_name}))

模型驱逐

样例代码没有涵盖模型驱逐——即从预测服务的内存空间中驱逐不经常使用的模型文件。在设计中,对于每个预测请求,预测服务将从元数据存储中查询和下载请求的模型,然后从本地磁盘读取并将模型初始化到内存中。对于某些模型来说,这些操作是耗时的。

为了减少每个模型预测请求的延迟,我们的设计在模型管理器组件中(在内存中)缓存了模型图,以避免加载已使用的模型。但想象一下,我们可能会继续训练新的意图分类模型并对其进行预测。这些新生成的模型将继续加载到模型管理器的模型缓存中。最终,预测器将耗尽内存。 为了解决这类问题,模型管理器需要升级,包括模型驱逐功能。例如,我们可以引入LRU(最近最少使用)算法来重建模型管理器的模型缓存。借助LRU的帮助,我们可以仅保留最近访问的模型在模型缓存中,并在当前加载的模型超过内存阈值时驱逐最少访问的模型。

TorchServe模型服务器示例

在本节中,我们将向您展示使用模型服务器方法构建预测服务的示例。具体来说,我们使用TorchServe后端(一种专为PyTorch模型构建的模型服务工具)来取代前一节(7.1.4)中讨论的自建预测器。

为了与第7.1节中的模型服务方法进行公平比较,我们通过重用上一节中显示的前端服务来开发这个模型服务器方法示例。更具体地说,我们只添加了另一个预测器后端,仍然使用前端服务、gRPC API和意图分类模型来演示相同的端到端预测工作流程。

在第7.1.4节中的意图预测器与TorchServe预测器(模型服务器方法)之间有一个重要区别。同一个预测器可以为任何PyTorch模型提供服务,无论其预测算法如何。

玩转这个服务

由于这个模型服务器示例是在之前的示例服务的基础上开发的,我们以相同的方式与预测服务进行交互。唯一的区别是我们启动了一个TorchServe后端(容器),而不是启动一个自建的意图预测器容器。代码清单7.11只展示了启动服务和发送意图预测请求的关键步骤。要在本地运行该实验,请完成附录A(A.2节)中的实验,并参考scripts/lab-006-model-serving-torchserve.sh文件(mng.bz/0yEN

# step 1: start torchserve backend
docker run --name intent-classification-torch-predictor 
  --network orca3 --rm -d -p "${ICP_TORCH_PORT}":7070  
  -p "${ICP_TORCH_MGMT_PORT}":7071 
  -v "${MODEL_CACHE_DIR}":/models 
  -v "$(pwd)/config/torch_server_config.properties":  
  /home/model-server/config.properties 
  pytorch/torchserve:0.5.2-cpu torchserve 
  --start --model-store /models
  
# step 2: start the prediction service (the web frontend)
docker build -t orca3/services:latest -f services.dockerfile . 
docker run --name prediction-service --network orca3 
   --rm -d -p "${PS_PORT}":51001 
   -v "${MODEL_CACHE_DIR}":/tmp/modelCache 
   orca3/services:latest  
   prediction-service.jar
   
# step 3: make a prediction request, ask intent for “merry christmas”
grpcurl -plaintext
  -d "{
    "runId": "${MODEL_ID}",
    "document": "merry christmas"
 }"
localhost:"${PS_PORT}" prediction.PredictionService/Predict

服务设计

这个示例服务遵循图7.1中的相同系统设计,唯一的区别是预测器后端变成了TorchServe服务器。请参考图7.4中的更新的系统设计。

构建模型服务方法示例

从图7.4中,我们可以看到模型服务的工作流程与图7.1中的模型服务示例保持一致。用户调用预测服务的前端API发送模型服务请求;前端服务然后下载模型文件并将预测请求转发到TorchServe后端。

前端服务

在7.1.3节中,我们确认前端服务可以通过在预测器连接管理器中注册预测器来支持不同的预测器后端。当收到预测请求时,预测器连接管理器会通过检查请求的模型算法类型将请求路由到适当的预测器后端。

按照之前的设计,为了支持新的TorchServe后端,我们在前端服务中添加了一个新的预测器客户端(TorchGrpcPredictorBackend)来表示TorchServe后端;请参考图7.5的更新系统设计。

构建模型服务方法示例

在图7.5中,我们添加了两个灰色的框,它们分别是TorchServe的gRPC预测器后端客户端(TorchGrpcPredictorBackend)和后端的TorchServe服务器。TorchGrpcPredictorBackend通过下载模型文件并将预测请求发送到TorchServe容器来进行响应。在这个例子中,预测器连接管理器会选择TorchServe作为后端,因为请求的模型的元数据(在元数据存储中)定义了TorchServe作为其预测器。

TorchServe后端

TorchServe是由PyTorch团队开发的用于提供PyTorch模型服务的工具。TorchServe作为一个黑盒子运行,并为模型预测和内部资源管理提供了HTTP和gRPC接口。图7.6展示了我们在这个示例中如何使用TorchServe的工作流程。

构建模型服务方法示例

在我们的示例代码中,我们将TorchServe作为一个由PyTorch团队提供的Docker容器运行,并将一个本地文件目录挂载到容器中。该文件目录用作TorchServe进程的模型存储。在图7.6中,我们通过三个步骤来运行模型预测。首先,我们将PyTorch模型文件复制到模型存储目录中。其次,我们调用TorchServe管理API将模型注册到TorchServe进程中。最后,我们调用TorchServe API来运行模型预测,针对的是我们的意图分类模型。

与7.1.4节中的自建意图预测器相比,TorchServe要简单得多。我们可以在不编写任何代码的情况下实现模型服务;我们只需要设置一个带有磁盘共享的Docker容器。此外,与仅适用于意图分类算法的意图预测器不同,TorchServe不与任何特定的训练算法绑定;只要模型是使用PyTorch框架训练的,TorchServe就可以提供服务。

TorchServe提供的灵活性和便利性带来了一些要求。TorchServe要求操作者使用他们自己的一套API来发送模型服务请求,同时要求模型文件以TorchServe格式打包。让我们在接下来的两个小节中看看这些要求。

TorchServe API

TorchServe提供了许多类型的API,包括健康检查、模型解释、模型服务、工作器管理和模型注册等。每个API都有HTTP和gRPC两种实现方式。由于TorchServe在其官方网站(pytorch.org/serve/)和GitHub存储库(github.com/pytorch/ser…)上对API的合同和使用方法进行了非常详细的说明,您可以在那里找到详细信息。在本节中,我们将重点介绍我们在示例服务中使用的模型注册和模型推断API。

模型注册API

由于TorchServe采用黑盒方法进行模型服务,因此在使用模型之前需要先将模型注册。具体来说,在将模型文件放置在TorchServe的模型存储中(一个本地文件目录)之后,TorchServe不会自动加载模型。我们需要向TorchServe注册模型文件和模型的执行方法,这样TorchServe才知道如何处理这个模型。

在我们的代码示例中,我们使用TorchServe的gRPC模型注册API从预测服务中注册我们的意图模型,示例如下:

public void registerModel(GetArtifactResponse artifact) {
    String modelUrl = String.format(MODEL_FILE_NAME_TEMPLATE, artifact.getRunId());
    String torchModelName = String.format(TORCH_MODEL_NAME_TEMPLATE, artifact.getName(), artifact.getVersion());

    ManagementResponse r = managementStub.registerModel(
           RegisterModelRequest.newBuilder()
             .setUrl(modelUrl)
             .setModelName(torchModelName)
             .build());
    # Assign resource (TorchServe worker) for this model 
    managementStub.scaleWorker(ScaleWorkerRequest.newBuilder()
             .setModelName(torchModelName)
             .setMinWorker(1)
             .build());
 }

TorchServe模型文件已经包含了模型的元数据,包括模型版本、模型运行时和模型服务入口。因此,在注册模型时,通常只需在registerModel API中设置模型文件名即可。除了模型注册,我们还可以使用scaleWorker API来控制为该模型分配多少计算资源。

模型推断API

TorchServe为各种模型提供了统一的模型服务API,这使得TorchServe的使用非常简单。要运行默认版本模型的预测,可以向POST /predictions/{model_name}发出REST调用。要运行已加载模型的特定版本的预测,可以向POST /predictions/{model_name}/{version}发出REST调用。预测请求中要预测的内容以二进制格式输入。例如,

# prediction with single input on model resnet-18
curl http://localhost:8080/predictions/resnet-18  
  -F "data=@kitten_small.jpg"
  
# prediction with multiple inputs on model squeezenet1_1
curl http://localhost:8080/predictions/squeezenet1_1  
  -F 'data=@docs/images/dogs-before.jpg' 
  -F 'data=@docs/images/kitten_small.jpg'

在我们的示例服务中,我们使用gRPC接口将预测请求发送给TorchServe。代码清单7.12展示了TorchGrpcPredictorBackend客户端将预测请求从前端API调用转换为TorchServe后端的gRPC调用。您可以在prediction-service/src/main/java/org/orca3/miniAutoML/prediction/TorchGrpcPredictorBackend.java中找到TorchGrpcPredictorBackend的完整源代码。

// call TorchServe gRPC prediction api
public String predict(GetArtifactResponse artifact, String document) {
  return stub.predictions(PredictionsRequest.newBuilder()        
             .setModelName(String.format(TORCH_MODEL_NAME_TEMPLATE,artifact.getName(), artifact.getVersion())) 
             .putAllInput(ImmutableMap.of("data",ByteString.copyFrom(document, StandardCharsets.UTF_8))) 
             .build()).getPrediction()
             
}

TorchServe模型文件

到目前为止,您已经了解了TorchServe模型服务的工作流程和API。您可能会想知道在TorchServe中如何进行模型服务,而它对所服务的模型一无所知。在第6章中,我们了解到为了提供模型服务,预测服务需要了解模型算法和模型的输入/输出模式。令人费解的是,TorchServe在不知道模型算法和模型的输入/输出数据格式的情况下运行模型服务。其中的技巧就在于TorchServe的模型文件。

TorchServe要求模型被打包成一个特殊的.mar文件。我们可以使用torch-model-archiver命令行工具或model_archiver Python库将PyTorch模型文件打包成.mar文件。 要归档TorchServe的.mar文件,我们需要提供模型名称、模型文件(.pt或.pth)和一个处理器文件。处理器文件是关键部分,它是一个定义自定义TorchServe推理逻辑的Python代码文件。由于TorchServe的模型包(.mar文件)包含模型算法、模型数据和模型执行代码,并且模型执行代码遵循TorchServe的预测接口(协议),TorchServe可以使用其通用的预测API执行任何模型(.mar文件),而无需知道模型算法。

当TorchServe接收到预测请求时,它首先会找到托管模型的内部工作进程,然后触发模型的处理器文件来处理请求。处理器文件包含四个逻辑部分:模型网络初始化、输入数据预处理、模型推断和预测结果后处理。为了使前面的解释更具体,让我们以我们的意图模型文件为例。

如果我们打开样例服务中意图模型的.mar文件,与我们在7.1.4节中看到的模型文件相比,我们会发现多了两个额外的文件:MANIFEST.json和torchserve_handler.py。下面是一个意图.mar文件的文件夹结构示例:

# intent.mar content

├── MAR-INF
│ └── MANIFEST.json
├── manifest.json
├── model.pth
├── torchserve_handler.py*
└── vocab.pth

# MANIFEST.json, TorchServe .mar metadata
{
 "createdOn": "09/11/2021 10:26:59",
 "runtime": "python",
 "model": {
    "modelName": "intent_80bf0da", 
    "serializedFile": "model.pth",     
    "handler": "torchserve_handler.py", 
    "modelVersion": "1.0"
},
 "archiverVersion": "0.4.2"
}

MANIFEST.json定义了模型的元数据,包括模型版本、模型权重、模型名称和处理程序文件。通过有一个MANIFEST.json文件,TorchServe知道如何加载和运行任意模型,而不需要了解其实现细节。

TORCHSERVE处理程序文件

一旦模型在TorchServe中注册,TorchServe将使用模型处理程序文件中的handle(self, data, context)函数作为模型预测的入口点。处理程序文件管理模型服务的整个过程,包括模型初始化、输入请求的预处理、模型执行和预测结果的后处理。 代码清单7.13突出了用于本示例服务中使用的意图分类.mar文件的处理程序文件的关键部分。您可以在我们的Git存储库中的training-code/text-classification/torchserve_handler.py中找到此文件。

class ModelHandler(BaseHandler):
   """
   A custom model handler implementation for serving
   intent classification prediction in torch serving server.
   """
   
   # Model architecture
   class TextClassificationModel(nn.Module):
     def __init__(self, vocab_size, embed_dim, fc_size, num_class):
         super(ModelHandler.TextClassificationModel, self)

➥ .__init__()
         self.embedding = nn.EmbeddingBag(vocab_size,

➥ embed_dim, sparse=True)
         self.fc1 = nn.Linear(embed_dim, fc_size)
         self.fc2 = nn.Linear(fc_size, num_class)
         self.init_weights()

     def init_weights(self):
         .. .. ..

     def forward(self, text, offsets):
         embedded = self.embedding(text, offsets)
         return self.fc2(self.fc1(embedded))

   # Load dependent files and initialize model
   def initialize(self, ctx):
       model_dir = properties.get("model_dir")
       model_path = os.path.join(model_dir, "model.pth") 
       vacab_path = os.path.join(model_dir, "vocab.pth") 
       manifest_path = os.path.join(model_dir, "manifest.json")

       # load vocabulary
       self.vocab = torch.load(vacab_path)

       # load model manifest, including label index map.
       with open(manifest_path, 'r') as f:
           self.manifest = json.loads(f.read())
       classes = self.manifest['classes']

       # intialize model
       self.model = self.TextClassificationModel(vocab_size, emsize, self.fcsize, num_class).to("cpu")  
       self.model.load_state_dict(torch.load(model_path)) 
       self.model.eval()
       self.initialized = True

# Transform raw input into model input data.
   def preprocess(self, data):

       preprocessed_data = data[0].get("data")
       if preprocessed_data is None:
          preprocessed_data = data[0].get("body") 
          
       text_pipeline = lambda x: self.vocab(self.tokenizer(x))

       user_input = " ".join(str(preprocessed_data)) 
       processed_text = torch.tensor(text_pipeline(user_input),dtype=torch.int64)

       offsets = [0, processed_text.size(0)]
       offsets = torch.tensor(offsets[:-1]).cumsum(dim=0)
       
     return (processed_text, offsets)

# Run model inference by executing the model with model input
   def inference(self, model_input):
       model_output = self.model.forward(model_input[0], model_input[1]) 
       return model_output

# Take output from network and post-process to desired format
   def postprocess(self, inference_output):
       res_index = inference_output.argmax(1).item()
       classes = self.manifest['classes']
       postprocess_output = classes[str(res_index)]
       return [{"predict_res":postprocess_output}]

# Entry point of model serving, invoke by TorchServe 
# for prediction request

def handle(self, data, context):
       model_input = self.preprocess(data)
       model_output = self.inference(model_input)
       return self.postprocess(model_output)

通过从代码清单7.13中的handle函数开始,您将清楚地了解处理程序文件如何执行模型服务。initialize函数加载所有模型文件(权重、标签和词汇表)并初始化模型。handle函数是模型服务的入口点;它对二进制模型输入进行预处理,运行模型推断,对模型输出进行后处理,并返回结果。

在训练过程中打包.mar文件

当我们决定使用TorchServe进行模型服务时,最好在培训时生成.mar文件。此外,由于TorchServe处理程序文件包含了模型架构和模型执行逻辑,通常它是模型训练代码的一部分。 有两种方法可以打包.mar文件。首先,在模型训练完成后,我们可以运行torch-model-archiver CLI工具将模型权重打包为序列化文件,并将依赖文件作为额外文件进行打包。其次,我们可以使用model_archiver Python库在模型训练代码的最后一步生成.mar文件。以下代码片段是我们用于打包意图分类模型的示例:

## Method one: package model by command line cli tool.
torch-model-archiver --model-name intent_classification --version 1.0  
  --model-file torchserve_model.py --serialized-file 
    workspace/MiniAutoML/{model_id}/model.pth 
  --handler torchserve_handler.py --extra-files 
workspace/MiniAutoML/{model_id}/vocab.pth,
➥ workspace/MiniAutoML/{model_id}/manifest.json

## Method two: package model in training code.
model_archiver.archive(model_name=archive_model_name,   
  handler_file=handler, model_state_file=model_local_path,  
  extra_files=extra_files, model_version=config.MODEL_SERVING_VERSION,dest_path=config.JOB_ID)

在Kubernetes中进行扩展

在我们的示例服务中,为了演示目的,我们运行了一个单独的TorchServe容器作为预测后端,但在生产环境中情况并非如此。扩展TorchServe面临的挑战如下:

  • 负载均衡器使得TorchServe模型注册困难。在TorchServe中,模型文件需要先注册到TorchServe服务器才能使用。但在生产环境中,TorchServe实例被放置在网络负载均衡器后面,所以我们只能将预测请求发送到负载均衡器,让它将请求路由到一个随机的TorchServe实例。在这种情况下,由于我们无法指定哪个TorchServe实例为哪个模型提供服务,因此注册模型变得困难。负载均衡器将TorchServe实例对我们隐藏起来。
  • 每个TorchServe实例需要有一个模型存储目录来加载模型,在注册之前需要将模型文件放在模型存储目录中。如果有多个TorchServe实例,管理模型文件的复制变得困难,因为我们需要知道每个TorchServe实例的IP地址或DNS来复制模型文件。
  • 我们需要在TorchServe实例之间平衡模型。让每个TorchServe实例加载每个模型文件是一个不好的做法,这会造成计算资源的极大浪费。我们应该将负载均匀地分散在不同的TorchServe实例之间。

为了解决这些挑战并扩展TorchServe后端,我们可以在Kubernetes中引入“sidecar”模式。图7.7显示了整体概念。

构建模型服务方法示例

在图7.7中的建议是在每个TorchServe Pod中添加一个代理容器(作为sidecar)。我们不直接调用TorchServe API,而是将预测请求发送到代理容器。代理容器中的代理API将隐藏TorchServe模型管理的细节,包括模型下载和模型注册。它将准备TorchServe容器以便为任意模型提供服务。

在添加了代理容器后,模型服务的工作流程(图7.7)如下:首先,预测请求到达代理容器。其次,代理容器下载模型文件并将其输入到共享磁盘(模型存储)。第三,代理容器将模型注册到TorchServe容器,并将推理请求转换为TorchServe格式。第四,TorchServe容器执行模型服务并将结果返回给代理容器。最后,代理容器将预测响应返回给用户。

通过使用代理容器,我们不需要担心将预测请求发送到未注册该模型的TorchServe实例上。代理容器(sidecar)将通过将模型文件复制到模型存储并注册模型,使TorchServe容器为任何预测请求做好准备。它还简化了资源管理的工作,因为现在我们只需依靠负载均衡器将预测工作负载(模型)分布在TorchServe Pod之间。此外,通过在所有TorchServe Pod之间共享一个磁盘,我们可以共享所有TorchServe实例的模型存储,从而减少模型下载时间并节省网络带宽。

模型服务器 VS 模型服务

在设计模型服务应用程序时,选择模型服务器方法和模型服务方法是我们需要做出的第一个决策。选择不当的话,我们的服务应用程序可能难以使用和维护,或者构建时间过长。

我们在第6章(第6.2节和6.3节)已经讨论了这两种方法的区别,但这是一个非常关键的选择,值得再次进行审视。通过在第7.1节和第7.2节中使用这两种方法的具体示例,很明显模型服务器方法避免了为特定模型类型构建专门的后端预测器的工作。相反,它可以直接使用,并且可以为任意模型提供服务,而不管模型实现的是哪种算法。因此,似乎模型服务器方法应该始终是最佳选择。但这并不正确;模型服务器或模型服务的选择应该取决于使用案例和业务需求。

对于单应用场景,实践中模型服务方法在构建和维护方面更简单。模型服务后端预测器相当简单,因为模型服务代码是训练代码的简化版本。这意味着我们可以轻松地将模型训练容器转换为模型服务容器。

构建完成后,模型服务方法更容易维护,因为我们拥有端到端的代码,并且工作流程简单。对于模型服务器方法,无论我们选择开源的预构建模型服务器还是构建自己的服务器,设置系统的过程都是复杂的。需要花费很多精力来充分了解系统,以便操作和维护。

对于模型服务平台场景,系统需要支持许多不同类型的模型,模型服务器方法无疑是最佳选择。当您为500种不同类型的模型构建模型服务系统时,如果选择模型服务器方法,您只需要拥有一个单一类型的预测器后端来处理所有模型。相反,使用模型服务方法,您将需要拥有500个不同的模型预测器!对于所有这些预测器的计算资源管理和维护工作非常困难。

我们的建议是在初学阶段使用模型服务方法,因为它更简单、更容易。当您需要在服务系统中支持超过5到10种类型的模型或应用程序时,可以转向模型服务器方法。

开源模型服务工具

有许多开源的模型服务工具可供选择。有多种选择是很好的,但同时也可能让人感到困惑。为了帮助您更轻松地做出选择,我们将介绍一些流行的模型服务工具,包括TensorFlow Serving、TorchServe、Triton和KServe。所有这些工具都可以直接使用,并适用于生产环境的用例。

因为这里所描述的每个工具都有详尽的文档,我们将保持在一般层面上进行讨论,只关注它们的整体设计、主要特点和适用的用例。这些信息应该足够作为您进一步探索的起点。

TensorFlow Serving

TensorFlow Serving(www.tensorflow.org/tfx/guide/s…)是一个可定制的独立网络系统,用于在生产环境中为TensorFlow模型提供服务。TensorFlow Serving采用模型服务器的方法,可以使用相同的服务器架构和API为所有类型的TensorFlow模型提供服务。

TensorFlow Serving具有以下功能:

  • 可以为多个模型或同一模型的多个版本提供服务。
  • 与TensorFlow模型的集成直接支持。
  • 自动发现新的模型版本并支持不同的模型文件源。
  • 提供统一的gRPC和HTTP端点用于模型推断。
  • 支持批量预测请求和性能调优。
  • 具有可扩展的设计,可根据版本策略和模型加载进行定制。

在TensorFlow Serving中,一个模型由一个或多个servable组成。Servable是执行计算的底层对象(例如查找或推断),它是TensorFlow Serving中的核心抽象。源是插件模块,用于查找和提供servable。加载器标准是加载和卸载servable的API。管理器处理servable的完整生命周期,包括加载、卸载和提供servable。

构建模型服务方法示例

图7.8说明了向客户展示一个servable的工作流程。首先,源插件为特定的servable创建一个加载器;加载器包含加载servable所需的元数据。其次,源在文件系统(模型仓库)中找到一个servable;它通知DynamicManager有关servable的版本和加载器的信息。第三,基于预定义的版本策略,DynamicManager决定是否加载模型。最后,客户端发送一个servable的预测请求,DynamicManager返回一个句柄,以便客户端执行模型。

TensorFlow Serving要求模型以SavedModel(mng.bz/9197)格式保存。我们可以使用tf.saved_model.save(model, save_path) API来实现这一目的。SavedModel是一个包含序列化签名和运行所需状态(包括变量值和词汇表)的目录。例如,一个SavedModel目录包含两个子目录assets和variables,以及一个文件saved_model.pb。

assets文件夹包含TensorFlow图形使用的文件,例如用于初始化词汇表的文本文件。variables文件夹包含训练检查点。saved_model.pb文件存储实际的TensorFlow程序或模型,以及一组命名的签名,每个签名都标识一个接受张量输入并生成张量输出的函数。

由于TensorFlow的SavedModel文件可以直接加载到TensorFlow Serving进程中,因此运行模型服务非常简单。一旦服务进程启动,我们可以将模型文件复制到TensorFlow Serving的模型目录中,然后立即发送gRPC或REST的预测请求。让我们来回顾一下以下的预测示例:

# 1. Save model in training code
MODEL_DIR='tf_model'
version = "1"
export_path = os.path.join(MODEL_DIR, str(version))
model.save(export_path, save_format="tf")

# 2. Start tensorflow serving service locally as a docker container
docker run -p 8501:8501
--mount type=bind,source=/workspace/tf_model,target=/models/model_a/ -e MODEL_NAME=model_a -t tensorflow/serving --model_config_file_poll_wait_seconds=60 --model_config_file=/models/model_a/models.config

# 3. Send predict request to local tensorflow serving docker container
# The request url pattern to call a specific version of a model is
   /v1/models/<model name>/versions/<version number>
json_response = requests.post('http://localhost:8501/ 
➥ v1/models/model_a/versions/1:predict',
  data=data, headers=headers)

要将多个模型和同一模型的多个版本加载到serving服务器中,我们可以在模型配置中配置模型的版本,如下所示:

model_config_list {
  config{
    name: 'model_a'
    base_path: '/models/model_a/'
    model_platform: 'tensorflow'
    model_version_policy{
      specific{
        versions:2
        versions:3
      } 
   }
}

config{
    name: 'model_b'
    base_path: '/models/model_b/'
    model_platform: 'tensorflow'
    } 
}

在此配置中,我们定义了两个模型,model_a和model_b。因为model_a具有model_version_policy,所以两个版本(v2和v3)都会被加载并可以提供服务。默认情况下,将提供最新版本的模型,因此当检测到model_b的新版本时,旧版本将被替换。

回顾一下,TensorFlow Serving是用于TensorFlow模型的生产级模型服务解决方案。它支持REST、gRPC、GPU加速、小批量处理和边缘设备上的模型服务。虽然TensorFlow Serving在高级指标、灵活的模型管理和部署策略方面存在不足,但如果你只有TensorFlow模型,它仍然是一个不错的选择。

TensorFlow Serving的主要缺点是它是一种供应商锁定解决方案,仅支持TensorFlow模型。如果您正在寻找一种与训练框架无关的方法,那么TensorFlow Serving不会是您的选择。

TorchServe

TorchServe(pytorch.org/serve/)是一个高性能、灵活且易于使用的工具,用于提供PyTorch eager mode和torchscripted(一种在高性能环境中运行的PyTorch模型的中间表示)模型的服务。与TensorFlow Serving类似,TorchServe采用了模型服务器的方法,可以使用统一的API来提供各种PyTorch模型的服务。不同之处在于,TorchServe提供了一组管理API,使模型管理非常方便和灵活。例如,我们可以通过编程方式注册和注销模型或不同版本的模型。我们还可以为模型和不同版本的模型扩展或缩减服务工作节点。

TorchServe服务器由三个组件组成:前端、后端和模型存储。前端处理TorchServe的请求和响应,并管理模型的生命周期。后端是一组模型工作节点,负责对模型进行实际的推理运算。模型存储是一个目录,其中包含所有可加载的模型;它可以是云存储文件夹或本地主机文件夹。图7.9显示了TorchServe实例的高级架构。

构建模型服务方法示例

图7.9绘制了两个工作流程:模型推理和模型管理。对于模型推理,首先,用户将预测请求发送到模型的推理端点,例如/predictions/{model_name}/{version}。接下来,推理请求将被路由到已加载模型的一个工作进程上。然后,工作进程将从模型存储中读取模型文件,并让模型处理程序加载模型、预处理输入数据并运行模型以获得预测结果。

对于模型管理,用户在访问模型之前需要注册模型。这可以通过使用管理API来完成。我们还可以为模型的工作进程数量进行扩缩容。在接下来的示例使用部分中,我们将看到一个示例。

特点

TorchServe提供以下功能:

  • 可以同时服务多个模型或同一模型的多个版本
  • 提供统一的gRPC和HTTP端点进行模型推理
  • 支持批处理预测请求和性能调优
  • 支持使用序列和并行流水线组合PyTorch模型和Python函数的工作流程
  • 提供管理API以注册/注销模型和扩缩容工作进程
  • 处理模型版本控制,用于A/B测试和实验

TorchServe模型文件

纯PyTorch模型不能直接加载到TorchServe服务器。TorchServe要求所有模型都打包成一个.mar文件。详细的示例可以参考第7.2.6节。

模型服务

以下代码段列出了使用TorchServe进行模型推理的五个一般步骤。具体的示例可以查看我们示例意图分类预测器的README文档(mng.bz/WA8a):

# 1. Create model store directory for torch serving
# and copy model files (mar files) to it
mkdir -p /tmp/model_store/torchserving
cp sample_models/intent.mar /tmp/model_store/torchserving

# 2. Run the torch serving container
docker pull pytorch/torchserve:0.4.2-cpu
docker --rm --shm-size=1g 
       --ulimit memlock=-1 
       --ulimit stack=67108864  
       -p8080:8080 
       -p8081:8081  
       -p8082:8082 
       -p7070:7070 
       -p7071:7071 
       --mount type=bind,source=/tmp/model_store/torchserving,target=/tmp/models

pytorch/torchserve:0.4.2-cpu torchserve --model-store=/tmp/models

# 3. Register intent model through torchserving management api
curl -X POST "http://localhost:8081/models?url=
➥ intent_1.mar&initial_workers=1&model_name=intent"

# 4. Query intent model in torch serving with default version.
curl --location --request GET 'http://localhost:8080/predictions/intent'  
--header 'Content-Type: text/plain' 
--data-raw 'make a 10 minute timer'

# 5. Query intent model in torch serving with specified version - 1.0
curl --location --request GET 'http://localhost:8080/predictions/intent /1.0' 
--header 'Content-Type: text/plain' 
--data-raw 'make a 10 minute timer'

除了使用管理API注册模型之外,我们还可以使用scale worker API来动态调整任何模型版本的工作进程数量,以更好地适应不同的推理请求负载,示例如下:

# 1. Scale up the worker number for the intent model. Default number is 1.
# Set minimum worker count to 3 and maximum worker count to 6
# for version 1.0 of the intent model
curl -v -X PUT "http:/ /localhost:8081/models/intent/1.0?min_worker=3&max_worker=6"

# 2. Use the describe model API to get detail runtime status of # default version of the intent model.
curl http:/ /localhost:8081/models/intent

# 3. Use the describe model API to get detail runtime status of # specific version (1.0) of the intent model.
curl http:/ /localhost:8081/models/intent/1.0

TorchServe是针对PyTorch模型的生产级模型服务解决方案,它专为高性能推理和生产环境使用场景设计。TorchServe的管理API为自定义模型部署策略提供了很大的灵活性,并且允许我们在每个模型的级别管理计算资源。

与TensorFlow Serving类似,TorchServe的主要缺点是它是一种供应商锁定的解决方案,它仅支持PyTorch模型。因此,如果你正在寻找一种与训练框架无关的方法,TorchServe可能不是你的选择。

Triton Inference Server

Triton Inference Server(developer.nvidia.com/nvidia-trit…)是由NVIDIA开发的开源推理服务器。它提供了针对CPU和GPU进行优化的云端和边缘推理解决方案。Triton支持HTTP/REST和gRPC协议,允许远程客户端请求由服务器管理的任何模型的推理。对于边缘部署,Triton作为一个带有C API的共享库可用,它允许将Triton的全部功能直接包含在应用程序中。

与其他模型服务工具相比,Triton的主要优势之一是训练框架的兼容性。与仅支持TensorFlow模型的TensorFlow Serving和仅支持PyTorch模型的TorchServe不同,Triton服务器可以为几乎任何框架训练的模型提供服务,包括TensorFlow、TensorRT、PyTorch、ONNX和XGBoost。Triton服务器可以从本地存储、Google Cloud Platform或Amazon Simple Storage Service(Amazon S3)加载模型文件,并在基于GPU或CPU的基础设施(云端、数据中心或边缘)上运行。

推理性能也是Triton的优势之一。Triton在GPU上并行运行模型以最大化吞吐量和利用率;支持基于x86和ARM的CPU推理;提供动态批处理、模型分析器、模型集成和音频流传输等功能。这些功能使得模型服务在内存使用效率和稳健性方面更加高效。

高层架构方面,图7.10展示了Triton推理服务器的高层架构。所有的推理请求都以REST或gRPC请求发送,然后在内部转换为C API调用。模型从模型存储库加载,模型存储库是一个基于文件系统的存储库,可以看作是文件夹/目录结构。

构建模型服务方法示例

对于每个模型,Triton准备了一个调度器。调度和批处理算法可以根据每个模型进行配置。每个模型的调度器可以选择对推理请求进行批处理,然后将请求传递给与模型类型相对应的后端,例如PyTorch模型的PyTorch后端。Triton后端是执行模型的实现。它可以是深度学习框架(如PyTorch、TensorFlow、TensorRT或ONNX Runtime)的封装。一旦后端使用批处理请求中提供的输入进行推理,并生成所请求的输出,就会返回输出。

值得注意的是,Triton支持后端C API,允许通过自定义预处理和后处理操作或甚至新的深度学习框架来扩展Triton。这就是如何扩展Triton服务器的方式。您可以查看triton-inference-server/backend GitHub仓库(github.com/triton-infe…)以找到所有Triton后端实现。作为额外的好处,通过专用的模型管理API,可以使用HTTP/REST、gRPC协议或C API查询和控制Triton提供的模型。

特点

Triton提供以下功能:

  • 支持所有主要的深度学习和机器学习框架后端。
  • 在单个GPU或CPU上同时运行来自同一或不同框架的多个模型。在多GPU服务器上,Triton会自动在每个GPU上创建每个模型的实例,以增加利用率。
  • 优化实时推理、批处理推理以最大化GPU/CPU利用率以及带有内置音频流输入支持的流式推理。Triton还支持模型集成,用于需要多个模型执行端到端推理的用例,例如对话式人工智能。
  • 在严格的延迟约束下,处理输入请求的动态批处理,以实现高吞吐量和利用率。
  • 在生产环境中实时更新模型,无需重新启动推理服务器或中断应用程序。
  • 使用模型分析器自动找到最佳模型配置并最大化性能。
  • 支持多GPU、多节点的大型模型推理。

TRITON模型文件

每个Triton模型都必须包含一个模型配置,该配置提供有关模型的必需和可选信息。通常,它是一个指定为ModelConfig protobuf的config.pbtxt文件(mng.bz/81Kz)。以下是一个用于PyTorch模型的简单模型配置(config.pbtxt)的示例:

platform: “pytorch_libtorch”
pytorch_libtorch
  max_batch_size: 8
  input [
    {
      name: “input0”
      data_type: TYPE_FP32
      dims: [ 16 ]
    },
    {
      name: “input1”
      data_type: TYPE_FP32
      dims: [ 16 ]
    }
  ]
  output [
    {
      name: “output0”
      data_type: TYPE_FP32
      dims: [ 16 ]
    }
  ]

通常,训练应用程序在训练服务完成时会创建此config.pbtxt文件,并将此配置作为模型文件的一部分上传到模型存储库。有关Triton模型配置的更多详细信息,请查看Triton模型配置文档(mng.bz/Y6mA)。

除了统一的配置文件外,每个训练框架的Triton模型文件格式是不同的。例如,TensorFlow模型采用SavedModel格式(mng.bz/El4d)可以直接加载到Triton中。但是,PyTorch模型需要使用TorchScript程序保存。

TorchScript是一种从PyTorch代码创建可序列化和可优化模型的方法。Triton要求将PyTorch模型序列化为TorchScript的原因是TorchScript可以用作PyTorch模型的中间表示。它可以独立于Python运行,例如在独立的C++程序中运行。请参阅以下代码片段,了解如何从PyTorch模型创建TorchScript模型:

#### Pytorch training code
# 1. Define an instance of your model.
Model = ...TorchModel()

# 2. Switch the model to eval model
model.eval()

# 3. Build an example input of the model’s forward() method.
Example = torch.rand(1, 3, 224, 224)

# 4. Use torch.jit.trace to generate a torch.jit.ScriptModule via tracing. Traced_script_module = torch.jit.trace(model, example)

# 5. Save the TorchScript model
traced_script_module.save(“traced_torch_model.pt”)

有关其他训练框架的模型格式要求,请查看triton-inference-server/backend GitHub存储库(mng.bz/NmOn)。

Triton中的模型服务涉及以下三个步骤:首先,将模型文件复制到模型存储库;其次,调用管理API(POST v2/repository/models/MODELNAME/load)注册模型;最后,发送推理请求(POSTv2/models/{MODEL_NAME}/load)注册模型;最后,发送推理请求(POST v2/models/{MODEL_NAME}/versions/${MODEL_VERSION})。有关Triton管理API的更多信息,请查看Triton的HTTP/REST和gRPC协议文档(mng.bz/DZvR)。有关推理API的信息,请查看KServe社区标准推理协议文档(kserve.github.io/website/0.1…)。

回顾一下,我们认为Triton是最佳的模型服务方法有以下三个原因。首先,Triton与训练框架无关;它提供了一个设计良好且可扩展的后端框架,使其能够执行几乎任何训练框架构建的模型。其次,Triton具有更好的模型服务性能,例如服务吞吐量。Triton拥有多种机制来提高其服务性能,如动态批处理、GPU优化和模型分析工具。第三,Triton支持高级的模型服务用例,如模型集成和音频流式传输。

KServe和其他工具

开源的模型服务工具众多,包括KServe(www.kubeflow.org/docs/extern…)、Seldon Core(www.seldon.io/solutions/o…)和BentoML(github.com/bentoml/Ben…)。每个工具都有一些独特的优势。例如,BentoML运行轻量且易于使用,而Seldon Core和KServe则使在Kubernetes上进行模型部署变得简单快速。尽管模型服务工具各不相同,但它们有很多共同点:它们都需要以特定格式打包模型,定义模型包装器和配置文件以执行模型,将模型上传到存储库,并通过gRPC或HTTP/REST端点发送预测请求。通过阅读本章中的TorchServe、TensorFlow和Triton示例,您应该能够自行探索其他工具。

在结束模型服务工具讨论之前,我们特别想提到KServe。KServe是由Seldon、Google、Bloomberg、NVIDIA、Microsoft和IBM等多家知名高科技公司共同合作开展的模型服务项目。这个开源项目值得您关注,因为它旨在为常见的机器学习服务问题提供一个标准化的解决方案。

KServe旨在为Kubernetes上提供无服务器的推理解决方案。它提供了一个抽象的模型服务接口,适用于诸如TensorFlow、XGBoost、scikit-learn、PyTorch和ONNX等常见的机器学习框架。

在我们看来,KServe的主要贡献在于它创建了一个标准的服务接口,适用于所有主要的模型服务工具。例如,我们之前提到的所有模型服务工具现在都支持KServe的模型推理协议。这意味着我们可以使用一个推理API集合(KServe API)来查询由不同的模型服务工具托管的任何模型,比如Triton、TorchServe和TensorFlow。

KServe的另一个优势是它专为在Kubernetes上提供原生的无服务器解决方案而设计。KServe使用Knative来处理网络路由、模型工作进程的自动缩放(甚至可以缩减到零)和模型版本跟踪。通过一个简单的配置(参见下面的示例),您可以将模型部署到您的Kubernetes集群中,然后使用标准化的API进行查询:

apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
 name: “torchserve”
spec:
 predictor:
   pytorch:
     storageUri: gs://kfserving-examples/models 
          /torchserve/image_classifier

在幕后,KServe使用不同的模型服务工具来运行推理,例如TensorFlow Serving和Triton。KServe通过简单的Kubernetes CRD配置隐藏了所有细节的好处。在前面的示例中,InferenceService CRD配置隐藏了一系列工作,包括预测服务器的设置、模型复制、模型版本跟踪和预测请求路由。

在编写本书时,KServe的新版本(v2)仍处于测试阶段。尽管它还不够成熟,但其在平台支持和无服务器模型部署方面的标准化推理协议的独特优势使其在其他方法中脱颖而出。如果您想在Kubernetes上建立一个适用于所有主要训练框架的大型模型服务平台,那么KServe值得您关注。

将一个模型服务工具集成到现有的模型服务系统中

在现有的模型服务系统中集成一个模型服务工具可能是一项复杂的任务,但通过仔细的规划和执行是可行的。以下是集成过程的一般步骤:

  1. 评估兼容性:在集成模型服务工具之前,评估其与现有模型服务系统的兼容性。考虑支持的模型格式、推理协议(gRPC、HTTP/REST)以及任何特定的依赖关系或要求等因素。
  2. 定义集成目标:明确定义集成目标和目标。确定您通过将模型服务工具纳入系统中所希望实现的具体功能或改进。这将有助于指导集成过程,并确保与整体目标保持一致。
  3. 确定集成点:确定在现有系统中集成模型服务工具的适当集成点。这可能涉及更改或扩展现有的模型存储、模型注册和推理请求接口,以便与模型服务工具进行交互。
  4. 开发适配器或代理:根据模型服务工具的要求,开发适配器或代理组件,将现有系统的请求转换为模型服务工具所需的格式和协议。这可能涉及数据格式转换、模型加载和注册等操作。
  5. 部署和测试:在现有系统中部署集成的模型服务工具,并进行测试和验证,确保它与现有系统无缝协作,并满足预期的功能和性能要求。

需要注意的是,集成模型服务工具时可能会涉及到一些系统的调整和改变,具体取决于现有系统的架构和要求。因此,在集成过程中,需要进行适当的规划和测试,以确保集成的顺利进行,并满足业务需求。

同时,也要牢记模型服务工具可能对系统性能、资源利用和网络通信等方面产生的影响。在集成之前,建议进行性能测试和评估,以确保集成的模型服务工具能够满足系统的性能要求,并提供良好的用户体验。

构建模型服务方法示例

发布模型

发布模型是将新训练好的模型部署到预测服务并向用户提供访问的行为。在构建生产环境中的模型服务系统时,自动化模型部署和支持模型评估是我们需要解决的两个主要问题。

首先,当训练服务完成模型构建时,该模型应自动发布到生产环境中的预测服务中。其次,新发布的模型及其之前的版本都应在预测服务中可访问,以便我们可以在相同的环境中进行评估并进行公平比较。在本节中,我们提出了一个三步模型发布过程来解决这些挑战。

首先,数据科学家(Alex)或训练服务将最近生成的模型(包括模型文件和元数据)注册到元数据存储中,这是一个云元数据和资源存储系统,将在下一章中讨论。其次,Alex对新注册的模型进行模型评估。他可以通过向预测服务发送针对特定模型版本的预测请求来测试这些模型的性能。预测服务内置了从元数据存储加载任意特定版本模型的机制。

第三步,Alex在元数据存储中将表现最佳的模型版本设置为发布模型版本。一旦设置完成,所选版本的模型将对外公开!客户端应用程序将在不知不觉中开始使用来自预测服务的新发布版本的模型。图7.12说明了这个三步过程。

构建模型服务方法示例

在接下来的三个部分中,我们将逐步深入探讨图7.12所示的三个模型发布步骤。在此过程中,我们还将探讨元数据存储的细节,以及它与存储和预测服务之间的交互。让我们开始吧!

注册模型

在大多数深度学习系统中,都有一个存储服务来存储模型。在我们的示例中,这个服务被称为元数据存储;它用于管理深度学习系统生成的艺术品(如模型)的元数据。下一章将详细讨论元数据和艺术品存储服务。

要将模型注册到元数据存储中,通常需要提供模型文件和模型元数据。模型文件可以是模型的权重、嵌入和其他执行模型所需的文件。模型元数据可以是描述模型的事实的任何数据,如模型名称、模型ID、模型版本、训练算法、数据集信息和训练执行指标。图7.13说明了元数据存储内部如何存储模型元数据和模型文件。

构建模型服务方法示例

在图7.13中,我们可以看到元数据存储有两个部分:模型查找表和模型元数据列表。模型元数据列表只是纯粹的元数据存储,所有的模型元数据对象都存储在这个列表中。模型查找表用作快速搜索的索引表。查找表中的每个记录指向元数据列表中的一个实际元数据对象。

训练服务可以在训练完成后自动将模型注册到元数据存储中。数据科学家也可以手动注册模型,这通常发生在数据科学家希望部署他们本地构建的模型(而不使用深度学习系统)时。

当元数据存储接收到模型注册请求时,首先会为该模型创建一个元数据对象。其次,它通过添加一个新的搜索记录来更新模型查找表;该记录使我们能够通过模型名称和版本找到该模型元数据对象。除了通过模型名称和版本在查找表中进行搜索外,元数据存储还允许使用模型ID进行模型元数据搜索。

实际的模型文件存储在艺术品存储中,即云对象存储,如Amazon S3。模型在艺术品存储中的存储位置保存在模型的元数据对象中,作为一个指针。

图7.13显示了模型A在模型查找表中的两个搜索记录:版本1.0.0和1.1.0。每个搜索记录映射到一个不同的模型元数据对象(分别为ID = 12345和ID = 12346)。通过这种存储结构,我们可以通过使用模型名称和模型版本找到任何模型元数据;例如,我们可以通过搜索“model A”和版本“1.1.0”找到模型元数据对象ID = 12346。

使用模型的规范名称和版本来查找实际的元数据和模型文件是预测服务能够同时提供不同模型版本的基础。让我们在下一节中看看元数据存储在预测服务中的使用方式。

使用预测服务实时加载任意版本的模型

为了在生产环境中决定使用哪个模型版本,我们希望公平地(在相同环境下)和方便地(使用相同的API)评估每个模型版本的性能。为了实现这一目标,我们可以调用预测服务以不同的模型版本运行预测请求。

在我们的提案中,当预测服务接收到预测请求时,它会实时从元数据存储中加载模型。数据科学家可以通过在预测请求中定义模型名称和版本,允许预测服务使用任何模型版本进行预测。图7.14说明了这个过程。

构建模型服务方法示例

图7.14显示了预测服务实时加载预测请求中指定的模型。在接收到预测请求时,路由层首先在元数据存储中查找所请求的模型,下载模型文件,然后将请求传递给后端预测器。以下是运行时模型加载和服务过程的七个步骤的详细说明:

  1. 用户向预测服务发送预测请求。在请求中,他们可以通过提供模型名称和版本(/predict/{model_name}/{version})或模型ID(/predict/{model_id})来指定要使用的模型。
  2. 预测服务内部的路由层搜索元数据存储并找到模型元数据对象。
  3. 路由层将模型文件下载到所有预测器都可以访问的共享磁盘上。
  4. 通过检查模型元数据(例如算法类型),路由层将预测请求路由到正确的后端预测器。
  5. 预测器从共享磁盘中加载模型。
  6. 预测器处理数据预处理、执行模型、进行后处理,并将结果返回给路由层。
  7. 路由层将预测结果返回给调用者。

更新默认模型版本发布模型

在模型评估之后,模型发布的最后一步是让客户在预测服务中使用经过验证的新模型版本。我们希望模型发布过程在用户不知情的情况下进行,以便客户不会意识到底层模型版本的更改。

在前一节(7.5.2)的第1步中,用户可以使用”/predict/{model_name}/{version}”的API请求任何指定的模型版本进行模型服务。这个能力对于评估同一个模型的多个版本至关重要,这样我们就可以防止模型性能退化。

但在生产环境中,我们不希望客户跟踪模型版本和模型ID。作为替代,我们可以定义几个静态版本字符串作为变量,代表新发布的模型,并让客户在预测请求中使用它们,而不是使用真实的模型版本。

例如,我们可以定义两个特殊的静态模型版本或标签,比如STG和PROD,分别代表预生产和生产环境。如果与模型A关联的PROD标签的模型版本是1.0.0,用户可以调用”/predict/model_A/PROD”,预测服务将加载模型A和版本1.0.0进行模型服务。当我们将新发布的模型版本升级到1.2.0时,通过将PROD标签与版本1.2.0关联,”/predict/model_A/PROD”请求将落在模型版本1.2.0上。

通过特殊的静态版本/标签字符串,预测用户无需记住模型ID或版本,他们只需使用”/predict/{model_name}/PROD”发送预测请求来使用新发布的模型。在幕后,我们(数据科学家或工程师)维护这些特殊字符串与元数据存储的查找表中实际版本之间的映射,这样预测服务就知道为/STG或/PROD请求下载哪个模型版本。

在我们的提案中,我们将将特定的模型版本映射到静态模型版本的操作称为模型发布操作。图7.15说明了模型发布过程。

构建模型服务方法示例

在图7.15中,数据科学家首先将模型A版本1.0.0注册为模型A版本PROD在元数据存储中。然后在模型查找表中,(Model A, PROD)记录的指向更改为指向实际模型对象记录(ModelA,version:1.0.0)。因此,当用户调用预测服务中的/predict/ModelA/PROD时,实际上是在调用/predict/ModelA/1.0.0。

接下来,当预测服务收到具有等于STG或PROD的模型版本的预测请求时,服务将在元数据存储中搜索查找表,并使用注册到PROD的实际模型版本来下载模型文件。在图7.15中,预测服务将为/ModelA/PROD请求加载模型ModelA,version: 1.0.0,并为/ModelA/STG请求加载模型ModelA,version: 1.1.0。

对于未来的模型发布,数据科学家只需要更新元数据存储的查找表中的模型记录,将最新的模型版本映射到STG和PROD。预测服务将自动为新的预测请求加载新的模型版本。所有这些操作都是自动进行的,用户无感知。

模型的后期监控

与监控机器学习系统中的其他服务(如数据管理)相比,模型投入生产后的工作仍然没有完成。我们不仅需要监控和维护预测服务本身,还需要关注服务所提供的模型的性能。模型漂移是指知识领域分布的变化,不再与训练数据集匹配,导致模型性能的下降。这可能发生在预测服务完全正常运行的情况下,因为模型推断是独立于预测服务运行的。

为了对抗模型漂移,数据科学家需要使用新数据重新训练模型,或者使用改进的训练算法重新构建模型。这听起来像是一个数据科学项目,但它需要大量的底层工程工作,比如从预测服务中收集和分析模型指标以检测模型漂移。在本节中,我们从工程角度讨论模型监控,并探讨工程师在监控过程中的作用。

指标收集和质量门

工程师在模型指标收集和模型质量门设置这两个方面可以发挥重要作用。让我们来解释一下。

为了进行模型漂移检测的分析,数据科学家需要数据进行分析,而工程师可以找到途径提供必要的数据(指标)。尽管在大多数情况下,工程师可能需要创建一个单独的数据管道来收集模型性能指标,但这通常是过度的。通常,模型性能指标可以通过现有的遥测系统(如Datadog)和日志系统(如Sumo和Splunk)进行收集和可视化。因此,请尽量充分利用您已有的日志和指标系统,而不要费力地构建一个新的指标系统。

工程师还可以协助构建模型质量门。工程师可以与数据科学家合作,自动化故障排除步骤,例如检查数据质量和生成模型推断分析报告。通过设定阈值,这些检查最终将形成一个模型质量门。

需要收集的指标

从理论上讲,我们需要至少收集五种指标来支持模型性能的衡量。它们分别是预测追踪、预测日期、模型版本、观察结果和观察率以及日期。让我们逐一看一下:

  1. 预测追踪:通常,我们通过为每个预测请求分配一个唯一的请求ID来跟踪每个预测请求,但这还不够。对于一些复杂的场景,比如PDF扫描,我们需要将不同类型的模型预测组合在一起以生成最终结果。例如,我们首先将PDF文档发送到一个光学字符识别(OCR)模型中提取文本信息,然后将文本发送到一个自然语言处理(NLP)模型中识别目标实体。在这种情况下,除了为父预测请求分配一个唯一的请求ID之外,我们还可以为每个子预测请求分配一个groupRequestID,这样我们就可以在故障排除时将所有相关的预测请求进行分组。

  2. 预测日期:通常情况下,预测请求在一秒内完成。为了跟踪预测的日期,我们可以使用预测开始时间或完成时间,因为它们之间没有太大的区别。但对于诸如欺诈检测之类的情况,预测的完成时间戳可能与预测开始时间戳相差很大,因为它可以将多天的用户活动作为输入。

  3. 模型版本 – 为了将模型性能数据映射到确切的模型文件,我们需要了解模型版本。此外,当我们将多个模型组合为一个预测请求时,需要在日志中跟踪每个模型的版本。

  4. 观察结果 – 预测结果需要与预测输入一起记录,以供将来进行比较。此外,我们可以为客户提供反馈或调查的API,让他们报告模型性能方面的问题。通过使用反馈API,客户可以报告模型ID、预期的预测结果和当前的预测结果。

  5. 观察日期和率 – 许多时候,观察结果是手动收集的,需要记录观察的频率。数据科学家需要观察日期和率来决定数据是否能够在统计上代表模型的整体性能。

非常好,您已经读到这里了!模型服务是机器学习系统的重要组成部分,因为外部业务应用程序依赖于它。随着模型类型、预测请求数量和推理类型(在线/离线)的增加,许多模型服务框架/系统被发明出来,它们变得越来越复杂。如果您遵循第6章和第7章介绍的模型服务思维模型,从模型的加载和执行开始,无论代码库的规模如何,组件的数量如何,您都可以轻松地浏览这些服务系统。

总结

  • 本章中的模型服务示例由前端API组件和后端模型预测容器组成。由于预测器是基于第3章的意图模型训练代码构建的,因此它只能用于意图分类模型的服务。

  • 模型服务器示例由与第3章相同的前端API和不同的后端TorchServe预测器组成。TorchServe后端不仅限于意图分类模型,还可以提供任意的PyTorch模型。这是模型服务器方法相对于模型服务方法的一个重要优势。

  • 对于实施模型服务器方法,我们建议使用现有的工具,例如Triton服务器,而不是自己构建。

  • 模型服务方法适用于单个应用程序场景;它可以快速实施,并且您完全控制端到端工作流的代码实现。

  • 模型服务器方法适用于平台场景;当服务系统需要支持五种或更多不同类型的模型时,它可以大大减少开发和维护工作量。

  • TorchServe、TensorFlow Serving和Triton都是可靠的开源模型服务工具,它们都采用了模型服务器方法。如果适用,我们建议使用Triton,因为它与大多数模型训练框架兼容,并且在GPU加速方面具有性能优势。

  • KServe提供了适用于所有主要服务工具的标准服务接口,包括TensorFlow Serving、TorchServe和Triton。KServe可以极大地提高我们的服务系统的兼容性,因为我们可以使用一套API在不同的后端上运行模型服务。

  • 在生产中发布新模型或模型服务系统的新版本不应该是一个事后的考虑;我们需要在设计阶段就考虑到这一点。

  • 对于模型性能监控,模型指标收集和模型质量门是工程师需要重点关注的两个领域。

本网站的内容主要来自互联网上的各种资源,仅供参考和信息分享之用,不代表本网站拥有相关版权或知识产权。如您认为内容侵犯您的权益,请联系我们,我们将尽快采取行动,包括删除或更正。
AI教程

基于深度学习的高精度深海鱼检测识别系统

2023-12-17 18:55:14

AI教程

百度AI产品“文心一言”发布会观后感

2023-12-17 19:04:14

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索