深度学习系统设计(二)(4)

本文涉及的产品
简介: 深度学习系统设计(二)

深度学习系统设计(二)(3)https://developer.aliyun.com/article/1517013

7.1.3 前端服务

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


图 7.2 前端服务设计和模型提供工作流程

接下来我们考虑在图 7.2 中描述的模型提供工作流程中的意图预测场景,并应用刚刚复习过的六个步骤:

  1. 用户向 Web 接口发送包含模型 ID A 的意图预测请求。
  2. Web 接口调用预测器连接管理器来提供此请求。
  3. 预测器连接管理器通过查询元数据存储获取模型元数据,查询的条件为模型 ID 等于 A;返回的模型元数据包含模型算法类型和模型文件 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 找到完整的实现。

7.2 前端服务预测工作流

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());                  ❺
  .. .. ..
}

❶ 获取所需的模型 ID

❷ 从元数据存储中获取模型元数据

❸ 根据模型算法类型选择后端预测器

❹ 下载模型文件

❺ 调用后端预测器运行模型推理

预测 API

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

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

7.3 预测服务 gRPC 接口

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)来看看模型算法和预测器映射是如何配置的。

7.4 模型算法和预测器映射配置

# 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

7.5 预测器管理器加载算法和预测器映射

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;
     }
  }
  .. .. ..
}

❶ 预测器后端映射的算法

❷ 模型元数据缓存; 键字符串为模型 ID。

❸ 从配置中读取算法和预测器映射

❹ 创建预测器后端客户端并将其保存在内存中

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

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

预测器后端客户端

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

列表 7.6 预测器后端接口

public interface PredictorBackend {
   void downloadModel(String runId, 
           GetArtifactResponse artifactResponse);
   String predict(GetArtifactResponse artifact, String document);
   void registerModel(GetArtifactResponse artifact);
}

downloadModepredictregisterModel三个方法都是不言自明的。每个客户端实现这些方法来下载模型并向其注册的后端服务发送预测请求。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();
}

❶ 模型的文本输入

❷ 模型 ID

7.1.4 意图分类预测器

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

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

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

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


图 7.3 后端意图预测器设计和预测工作流程

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

预测 API

意图预测器有一个 API — PredictorPredict(见代码列表 7.7)。它接受两个参数,runIddocumentrunId 是模型 ID,document 是一个文本字符串。你可以在 grpc-contract/src/main/proto/ prediction_service.proto 中找到完整的 gRPC 合同。

列表 7.7 意图预测器 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 设计有利于客户和业务的需求。

模型文件

我们在模型训练服务(第三章)中生成的每个意图分类模型都有三个文件。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 模型时,有两种选择:序列化整个模型或仅序列化学习参数。第一种选项序列化整个模型对象,包括其类和目录结构,而第二种选项仅保存模型网络的可学习参数。

根据马修·英卡威奇的文章“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(仅参数)的模型架构。服务中的模型执行代码源自模型训练代码。如果你将以下清单中的模型定义与我们训练代码中的TextClassificationModel类(training-code/text-classification/train.py)进行比较,你会发现它们是相同的。这是因为模型服务本质上是模型训练运行。

清单 7.8 模型的神经网络(架构)

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 节和第八章中,我们将深入讨论模型版本的元数据管理。我们在这里只是描述了大致的想法。

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

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

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

模型管理器

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

清单 7.9 意图预测器模型管理器

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 中找到完整的实现。

清单 7.10 意图预测器预测工作流程

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}))

❶ 启动 gRPC 服务器

❷ 将模型服务逻辑注册到公共 API

❸ 进行预测

7.1.5 模型逐出

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

为了减少每个模型预测请求的延迟,我们的设计在模型管理器组件(内存中)中缓存模型图,以避免模型加载已使用的模型。但想象一下,我们可以继续训练新的意图分类模型并对其进行预测。这些新产生的模型将继续加载到模型管理器的模型缓存中。最终,预测器将耗尽内存。

为了解决这些问题,模型管理器需要升级以包含模型淘汰功能。例如,我们可以引入 LRU(最近最少使用)算法来重建模型管理器的模型缓存。借助 LRU 的帮助,我们可以仅保留最近访问的模型在模型缓存中,并在当前加载的模型超过内存阈值时淘汰最少访问的模型。

7.2 TorchServe 模型服务器示例

在本节中,我们将向您展示使用模型服务器方法构建预测服务的示例。更具体地说,我们使用了 TorchServe 后端(一个为 PyTorch 模型构建的模型服务工具)来替换上一节(7.1.4)中讨论的自建预测器。

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

第 7.1.4 节的意图预测器和 TorchServe 预测器(模型服务器方法)之间有一个很大的区别。相同的预测器可以为任何 PyTorch 模型提供服务,而不管其预测算法如何。

7.2.1 玩转服务

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

列表 7.11 启动预测服务并进行预测调用

# 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

❶ 将本地目录挂载到 TorchServe 容器

❷ 启动 TorchServe

❸ 设置 TorchServe 从 /models 目录加载模型

❹ 设置预测服务的本地模型目录以下载模型

7.2.2 服务设计

此示例服务遵循图 7.1 中的相同系统设计;唯一的区别是预测器后端变成了 TorchServe 服务器。请参阅图 7.4 以获取更新后的系统设计。


图 7.4 系统概述和模型服务端到端工作流程

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

7.2.3 前端服务

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

遵循之前的设计,为了支持我们的新 TorchServe 后端,我们在前端服务中添加了一个新的预测器客户端(TorchGrpcPredictorBackend)来代表 TorchServe 后端;请参阅图 7.5 以获取更新后的系统设计。


图 7.5 前端服务设计和模型服务工作流程

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

7.2.4 TorchServe 后端

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


图 7.6 TorchServe 后端的模型服务工作流程:TorchServe 应用程序作为一个黑盒运行。

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

跟第 7.1.4 节中的自构建意图预测器相比,TorchServe 要简单得多。我们甚至不需要编写任何代码就可以使模型服务正常运行,只需使用共享磁盘设置 Docker 容器即可。此外,TorchServe 不仅适用于意图分类算法,它不受任何特定训练算法的限制,只要模型是使用 PyTorch 框架训练的,TorchServe 就可以为其提供服务。

TorchServe 提供了极大的灵活性和便利性,但也有相关要求。TorchServe 要求操作员使用其独有的 API 发送模型服务请求,并要求模型文件以 TorchServe 格式打包。下面的两小节会详细介绍这些要求。

7.2.5 TorchServe API

TorchServe 提供了众多类型的 API,例如健康检查、模型解释、模型服务、工作线程管理和模型注册等。每个 API 都有 HTTP 和 gRPC 两种实现方式。由于 TorchServe 在其官网(pytorch.org/serve/)和 GitHub 仓库(github.com/pytorch/serve)上都对 API 的协议和使用方式进行了详细的解释,你可以在那里找到具体的信息。在本小节中,我们将着重介绍我们在示例服务中使用的模型注册 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 注册模型

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

模型推理 API

TorchServe 为各种模型提供了统一的模型服务 API,使其使用起来非常简单。如果想要为模型的默认版本运行预测,只需向/predictions/{model_name}发送一个 REST 请求;如果想要为加载的特定版本的模型运行预测,则向/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 的完整源代码。

清单 7.12 从前端服务调用 TorchServe 预测 API

// 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()
           .toString(StandardCharsets.UTF_8);
}

❶ 将文本输入转换为二进制格式以调用 TorchServe

深度学习系统设计(二)(5)https://developer.aliyun.com/article/1517017

相关实践学习
基于函数计算一键部署掌上游戏机
本场景介绍如何使用阿里云计算服务命令快速搭建一个掌上游戏机。
建立 Serverless 思维
本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
相关文章
|
23天前
|
机器学习/深度学习 存储 人工智能
深度学习系统设计(三)(4)
深度学习系统设计(三)
35 0
|
23天前
|
Kubernetes 算法 PyTorch
深度学习系统设计(三)(5)
深度学习系统设计(三)
24 0
|
23天前
|
机器学习/深度学习 存储 算法
深度学习系统设计(三)(3)
深度学习系统设计(三)
27 0
|
23天前
|
机器学习/深度学习 Kubernetes 调度
深度学习系统设计(三)(2)
深度学习系统设计(三)
27 0
|
23天前
|
存储 机器学习/深度学习 数据可视化
深度学习系统设计(三)(1)
深度学习系统设计(三)
24 0
|
23天前
|
存储 TensorFlow API
深度学习系统设计(二)(5)
深度学习系统设计(二)
25 1
|
23天前
|
机器学习/深度学习 缓存 算法
深度学习系统设计(二)(3)
深度学习系统设计(二)
13 0
|
23天前
|
机器学习/深度学习 算法 PyTorch
深度学习系统设计(二)(2)
深度学习系统设计(二)
17 1
|
23天前
|
Kubernetes PyTorch 算法框架/工具
深度学习系统设计(二)(1)
深度学习系统设计(二)
25 0
|
1月前
|
机器学习/深度学习 运维 算法