作者:Hamid Shojanazeri、Geeta Chauhan、Mark Saroufim、Jesse Smith

在本文中,我们将讨论 Torchserve 的性能调优,以便在生产环境中为您的模型提供服务。机器学习项目生命周期中最大的挑战之一是在生产环境中部署模型。这需要可靠的服务解决方案以及解决 MLOps 需求的解决方案。一个强大的服务解决方案需要提供对多模型服务、模型版本控制、指标日志记录、监控和扩展以服务于峰值流量的支持。在本文中,我们将概述 Torchserve 以及如何调整其性能以用于生产用例。我们将讨论 Meta 的动画绘画应用程序,该应用程序可以将您的人物素描变成动画,以及它如何使用 Torchserve 服务于峰值流量。动画绘画的工作流程如下。

https://ai.facebook.com/blog/using-ai-to-bring-childrens-drawings-to-life/

许多人工智能系统和工具旨在处理逼真的人类图像,而儿童的绘画则增加了一定程度的复杂性和不可预测性,因为它们通常以抽象的、异想天开的方式构建。这些类型的形态和风格变化甚至会让最先进的 AI 系统感到困惑,这些系统擅长在逼真的图像和绘画中识别物体。Meta AI 研究人员正在努力克服这一挑战,以便 AI 系统能够更好地识别儿童以各种方式创作的人物绘画。这篇精彩的博客文章提供了有关动画绘画和所采取方法的更多详细信息。

Torchserve

图 1. Torchserve 性能调优的总体流程

一旦您训练好模型,就需要将其集成到更大的系统中才能拥有一个成熟的应用程序,我们使用术语“模型服务”来指代这种集成。基本上,模型服务就是使您训练好的模型可用于运行推理以及后续的模型使用。

Torchserve 是 PyTorch 首选的生产模型服务解决方案。它是一个高性能且可扩展的工具,可将您的模型包装在 HTTP 或 HTTPS API 中。它有一个用 Java 实现的前端,负责处理从为模型服务分配工作进程到处理客户端和服务器之间连接的多个任务。Torchserve 有一个 Python 后端,负责处理推理服务。

Torchserve 支持多模型服务和版本控制以进行 AB 测试、动态批处理、日志记录和指标。它公开了四个 API,分别用于推理解释管理指标

推理 API 默认在端口 8080 上监听,并且可以通过 localhost 访问,这可以在Torchserve 配置中进行配置,并启用从模型获取预测。

解释 API 在后台使用 Captum 来提供正在服务的模型的解释,并且也在端口 8080 上监听。

管理 API 允许注册或取消注册并描述模型。它还使用户能够向上或向下扩展服务于模型的 worker 数量。

指标 API 默认在端口 8082 上监听,使我们能够监控正在服务的模型。

Torchserve 通过支持批量推理和多个服务于您模型的工作进程,使您可以扩展模型服务并处理峰值流量。可以通过管理 API 进行扩展,并通过配置文件进行设置。此外,指标 API 可帮助您通过默认指标和可自定义指标来监控模型服务。

其他高级设置(例如接收请求的队列长度、输入批处理的最大等待时间以及许多其他属性)可以通过配置文件进行配置,该文件可以在 Torchserve 启动时传递给它。

使用 Torchserve 服务模型的步骤

  1. 安装 Torchserve、模型归档器及其要求。
  2. 选择适合您任务的默认处理程序(例如,图像分类等)或编写自定义处理程序
  3. 打包您的模型工件(训练好的模型检查点和所有其他加载和运行模型所需的文件)和处理程序,使用Torcharchive将其打包成“.mar”文件,并将其放置在模型存储中。
  4. 开始服务您的模型.
  5. 运行推理。我们将在此处更详细地讨论模型处理程序和指标。

模型处理程序

Torchserve 在后端使用处理程序来加载模型、预处理接收到的数据、运行推理和后处理响应。Torchserve 中的处理程序是一个 python 脚本,所有模型初始化、预处理、推理和后处理逻辑都进入其中。

Torchserve 为许多应用程序(如图像分类、分割、对象检测和文本分类)提供了开箱即用的处理程序。如果您的用例在默认处理程序中不受支持,它也支持自定义处理程序。

它在自定义处理程序中提供了极大的灵活性,这有可能使 Torchserve 成为 多框架服务工具。自定义处理程序使您可以定义自定义逻辑来初始化模型,该模型也可以用于加载来自其他框架(如 ONNX)的模型。

Torchserve 处理程序由四个主要函数组成:initializepreprocessinferencepostprocess,每个函数都返回一个列表。下面的代码片段显示了一个自定义处理程序的示例。自定义处理程序继承自 Torchserve 中的 BaseHandler,并且可以 覆盖 任何主要函数。以下是用于加载Detectron2 模型进行人物检测的处理程序的示例,该模型已导出到 Torchscript 并使用 model.half() 以 FP16 运行推理,详细信息在本文的另一个部分中进行了解释。


class MyModelHandler(BaseHandler):
    def initialize(self, context):
        self.manifest = ctx.manifest
        properties = ctx.system_properties
        model_dir = properties.get("model_dir")
        serialized_file = self.manifest["model"]["serializedFile"]
        model_pt_path = os.path.join(model_dir, serialized_file)

        self.device = torch.device(
        "cuda:" + str(properties.get("gpu_id"))
        if torch.cuda.is_available() and properties.get("gpu_id") is not None
        else "cpu"
        )
        self.model = torch.jit.load(model_pt_path, map_location=self.device)

        self.model = self.model.half()

    def preprocess(self, data):

        inputs = []
        for request in batch:

            request_body = request.get("body")

            input_ = io.BytesIO(request_body)
            image = cv2.imdecode(np.fromstring(input_.read(), np.uint8), 1)
            input = torch.Tensor(image).permute(2, 0, 1)
            input = input.to(self.device)
            input = input.half()
            inputs.append({"image": input})

        return inputs

    def inference(self,inputs):
        predictions = self.model(**inputs)
        return predictions

    def postprocess(self, output):
        responses = []
        for inference_output in inference_outputs:
            responses_json = {
            'classes': inference_output['pred_classes'].tolist(),
            'scores': inference_output['scores'].tolist(),
            "boxes": inference_output['pred_boxes'].tolist()
            }
            responses.append(json.dumps(responses_json))

        return responses

指标

在生产环境中服务模型的一个重要组成部分是监控它们的能力。Torchserve 定期收集 系统级指标,并且允许添加自定义指标

系统级指标包括主机上的 CPU 利用率、可用和已用磁盘空间以及内存,以及具有不同响应代码(例如 200-300、400-500 和 500 以上)的请求数。自定义指标可以添加到指标中,如此处所述。TorchServe 将这两组指标记录到不同的日志文件中。默认情况下,指标在以下位置收集:

  • 系统指标 - log_directory/ts_metrics.log
  • 自定义指标 - log directory/model_metrics.log

如前所述,Torchserve 还公开了指标 API,该 API 默认在端口 8082 上监听,使用户能够查询和监控收集的指标。默认指标端点返回 Prometheus 格式的指标。您可以使用 curl 请求查询指标,或者将Prometheus 服务器指向该端点,并使用Grafana 创建仪表板。

在服务模型时,您可以使用 curl 请求查询指标,如下所示:

curl http://127.0.0.1:8082/metrics

如果您正在研究导出记录的指标,请参阅此示例,该示例使用 mtail 将指标导出到 Prometheus。在仪表板中跟踪这些指标使您可以监控可能在离线基准测试运行期间零星出现或难以发现的性能回归。

在生产环境中调整模型性能时需要考虑的事项

图 1 中建议的工作流程是在生产环境中通过 Torchserve 部署模型的一般方法。

在许多情况下,生产模型服务是基于吞吐量延迟服务水平协议 (SLA) 进行优化的。通常,实时应用程序更关心延迟,而离线应用程序可能更关心更高的吞吐量

有许多主要因素会影响生产中服务模型的性能。特别是,我们在此处重点介绍使用 Torchserve 服务 PyTorch 模型,但是,这些因素中的大多数也适用于来自其他框架的所有模型。

  • 模型优化:这是将模型部署到生产环境的预先步骤。这是一个非常广泛的讨论,我们将在未来的博客系列中深入探讨。这包括量化、剪枝等技术,以减小模型的大小,在 PyTorch 中使用中间表示 (IR 图),例如 Torchscript,融合内核以及许多其他技术。目前,torchprep 以 CLI 工具的形式提供了许多这些技术。
  • 批量推理:它指的是将多个输入馈送到模型中,虽然这在训练期间至关重要,但它对于在推理时管理成本也很有帮助。硬件加速器针对并行性进行了优化,而批处理有助于饱和计算能力,并且通常会导致更高的吞吐量。推理的主要区别在于您不能等待太久才能从客户端获取批处理,我们称之为动态批处理。
  • 工作进程数:Torchserve 使用工作进程来服务模型。Torchserve 工作进程是 Python 进程,其中保存模型权重的副本以运行推理。工作进程太少意味着您没有从足够的并行性中受益,但工作进程太多可能会导致工作进程争用并降低端到端性能。

  • 硬件:根据模型、应用程序以及延迟、吞吐量预算选择合适的硬件。这可能是 Torchserve 中支持的硬件之一:CPU、GPU、AWS Inferentia。某些硬件配置旨在实现一流的性能,而另一些硬件配置更适合具有成本效益的推理。从我们的实验中,我们发现 GPU 在较大的批处理大小下表现最佳,而合适的 CPU 和 AWS Inferentia 在较低的批处理大小和低延迟方面可能更具成本效益。

Torchserve 性能调优的最佳实践

为了在使用 Torchserve 服务模型时获得最佳性能,我们在此处分享一些最佳实践。Torchserve 提供了一个基准测试套件,该套件提供了有用的见解,可以根据以下详细信息做出明智的决策。

  • 优化您的模型作为第一步,PyTorch 模型优化教程模型优化选择也与所选的硬件密切相关。我们将在另一篇博客文章中更详细地讨论它。
  • 决定模型部署的硬件可能与延迟和吞吐量预算以及每次推理的成本密切相关。根据模型和应用程序的大小,它可能会有所不同,对于某些模型(如计算机视觉模型),从历史上看,在 CPU 上生产环境中运行是负担不起的。但是,通过优化(例如IPEX,最近已添加到 Torchserve 中),这变得更加经济实惠且具有成本效益,您可以在此调查性案例研究中了解更多信息。
  • Torchserve 中的工作进程是提供并行性的 Python 进程,应仔细设置工作进程的数量。默认情况下,Torchserve 启动的工作进程数等于主机上的 VCPU 或可用 GPU,这可能会大大增加 Torchserve 的启动时间。

    Torchserve 公开了一个配置属性来设置工作进程的数量。为了通过多个工作进程提供高效的并行性并避免它们争夺资源,作为基线,我们建议在 CPU 和 GPU 上遵循以下设置:

    CPU:在处理程序中,torch.set_num_threads(1) 然后将工作进程数设置为物理核心数 / 2。但是,可以通过利用 Intel CPU 启动器脚本来实现最佳线程配置。

    GPU:可通过 config.properties 中的number_gpus 设置可用 GPU 的数量。Torchserve 使用循环分配将工作进程分配给 GPU。我们建议按如下方式设置工作进程数。工作进程数 = (可用 GPU 数) / (唯一模型数)。请注意,Ampere 之前的 GPU 不提供任何具有多实例 GPU 的资源隔离。

  • 批处理大小可以直接影响延迟和吞吐量。为了更好地利用计算资源,需要增加批处理大小。但是,延迟和吞吐量之间存在权衡。较大的批处理大小可以增加吞吐量,但也会导致更高的延迟。可以在 Torchserve 中通过两种方式设置批处理大小:通过 config.properties 中的模型配置或在使用管理 API 注册模型时设置。

在下一节中,我们将使用 Torchserve 基准测试套件来确定模型优化、硬件、工作进程和批处理大小的最佳组合。

动画绘画性能调优

要使用 Torchserve 基准测试套件,首先我们需要有一个归档文件,即如上所述的“.mar”文件,其中包含模型、处理程序和所有其他工件,以便加载和运行推理。动画绘画使用 Detectron2 的 Mask-RCNN 实现作为对象检测模型。

如何运行基准测试套件

Torchserve 中的自动化基准测试套件使您可以基准测试具有不同设置(包括批处理大小和工作进程数)的多个模型,并最终为您生成报告。要开始使用,请执行以下操作:

git clone https://github.com/pytorch/serve.git

cd serve/benchmarks

pip install -r requirements-ab.txt

apt-get install apache2-utils

模型级别设置可以在类似于以下的 yaml 文件中配置:


Model_name:
    eager_mode:
        benchmark_engine: "ab"
        url: "Path to .mar file"
        workers:
            - 1
            - 4
        batch_delay: 100
        batch_size:
            - 1
            - 2
            - 4
            - 8
        requests: 10000
        concurrency: 10
        input: "Path to model input"
        backend_profiling: False
        exec_env: "local"
        processors:
            - "cpu"
            - "gpus": "all"

此 yaml 文件将在benchmark_config_template.yaml 文件中引用,该文件包含用于生成报告的其他设置,也可以选择与 AWS CloudWatch 配合使用以获取日志。

python benchmarks/auto_benchmark.py --input benchmark_config_template.yaml

运行基准测试,结果将写入“csv”文件,该文件可以在“_ /tmp/benchmark/ab_report.csv_”中找到,完整报告可以在“/tmp/ts_benchmark/report.md”中找到。它将包括 Torchserve 平均延迟、模型 P99 延迟、吞吐量、并发数、请求数、处理程序时间和一些其他指标等项目。在这里,我们重点关注一些重要的指标,我们跟踪这些指标以调整性能,这些指标是:并发模型 P99 延迟、吞吐量。我们专门结合批处理大小、使用的设备、工作进程数以及是否已完成任何模型优化来查看这些数字。

此模型的延迟 SLA 已设置为 100 毫秒,这是一个实时应用程序,正如我们之前讨论的那样,延迟更受关注,而吞吐量理想情况下应尽可能高,同时不违反延迟 SLA。

通过搜索空间,在不同的批处理大小 (1-32)、工作进程数 (1-16) 和设备 (CPU、GPU) 上,我们运行了一组实验,这些实验在下表中总结了最佳实验。

设备 并发 # 请求数 # 工作进程数 批处理大小 有效负载/图像 优化 吞吐量 延迟 P99
CPU 10 1000 1 1 不适用 3.45 305.3 毫秒
CPU 1 1000 1 1 不适用 3.45 291.8 毫秒
GPU 10 1000 1 1 不适用 41.05 25.48 毫秒
GPU 1 1000 1 1 不适用 42.21 23.6 毫秒
GPU 10 1000 1 4 不适用 54.78 73.62 毫秒
GPU 10 1000 1 4 model.half() 78.62 50.69 毫秒
GPU 10 1000 1 8 model.half() 85.29 94.4 毫秒

在 CPU 上,使用所有尝试的批处理大小、并发和工作进程数设置,此模型的延迟均未达到 SLA,实际上高出约 13 倍。

模型服务迁移到 GPU,可以立即提高延迟13 倍,从 305 毫秒降至 23.6 毫秒。

我们可以对模型执行的最简单优化之一是将精度降低到 fp16,这是一行代码 (model.half()),可以将模型 P99 延迟降低 32%,并将吞吐量提高几乎相同的量。

通过 Torchscript 化模型并使用optimize_for_inference 或其他技巧(包括利用激进融合的 onnx 或 tensorrt 运行时优化)可以进行其他优化,但这超出了本文的范围。我们将在另一篇文章中讨论模型优化。

我们发现在 CPU 和 GPU 上,将工作进程数 = 1 都效果最佳。

  • CPU 相比,将模型迁移到 GPU,使用工作进程数 = 1批处理大小 = 1吞吐量提高了约 12 倍,延迟提高了约 13 倍。
  • 将模型迁移到 GPU,使用 model.half()工作进程数 = 1批处理大小 = 8,在吞吐量和可容忍的延迟方面产生了最佳结果。与 CPU 相比吞吐量提高了约 25 倍,延迟仍然满足 SLA(94.4 毫秒)。

注意:如果您正在运行基准测试套件,请确保您设置了正确的 batch_delay,并将请求的并发数设置为与您的批处理大小成比例的数字。并发数在此处是指发送到服务器的并发请求数。

结论

在本文中,我们讨论了 Torchserve 公开的用于调整生产环境性能的注意事项和旋钮。我们讨论了 Torchserve 基准测试套件,作为调整性能的一种手段,并获得了有关模型优化、硬件选择和总体成本的可能选择的见解。我们使用了动画绘画应用程序,该应用程序使用 Detectron2 的 Mask-RCNN 模型作为案例研究,以展示使用基准测试套件进行的性能调优。

有关 Torchserve 性能调优的更多详细信息,请参阅我们的文档此处。如有任何其他问题和反馈,请随时在Torchserve 存储库上打开工单。

致谢

我们要感谢 Somya Jain (Meta)、Christopher Gustave (Meta) 在本博客的许多步骤中给予的大力支持和指导,并提供了对 Sketch Animator 工作流程的见解。还要特别感谢来自 AWS 的Li Ning 为简化 Torchserve 上的性能调优(通过自动化基准测试套件)所做的巨大努力。