跳转到主要内容
博客

Torchserve 性能调优,动画绘图案例研究

作者: 2022 年 12 月 28 日2024 年 11 月 14 日暂无评论

在这篇文章中,我们将讨论 Torchserve 的性能调优,以便在生产环境中部署您的模型。在机器学习项目的生命周期中,最大的挑战之一就是将模型部署到生产环境。这需要一个可靠的服务解决方案以及满足 MLOps 需求的解决方案。一个健壮的服务解决方案需要支持多模型服务、模型版本控制、指标记录、监控和扩展以服务峰值流量。在这篇文章中,我们将概述 Torchserve 以及如何针对生产用例调整其性能。我们将讨论 Meta 的 Animated Drawings 应用程序,它如何将人物素描转换为动画,以及它如何通过 Torchserve 服务峰值流量。Animated Drawings 的工作流程如下。

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

许多 AI 系统和工具都旨在处理逼真的人类图像,而儿童画则增加了复杂性和不可预测性,因为它们通常以抽象、奇特的方式构建。这些形态和风格上的差异甚至会使在识别照片级图像和绘画中的物体方面表现出色的最先进的 AI 系统感到困惑。Meta AI 研究人员正在努力克服这一挑战,以便 AI 系统能够更好地识别儿童以各种各样的方式创作的人形图画。这篇精彩的博客文章提供了有关 Animated Drawings 和所采用方法的更多详细信息。

Torchserve

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

一旦您训练好模型,就需要将其集成到更大的系统中以形成一个成熟的应用程序,我们使用“模型服务”一词来指代这种集成。基本上,模型服务就是使您的训练模型能够运行推理并随后使用模型。

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

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

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

解释 API 在底层使用 Captum 提供正在服务的模型的解释,并也监听 8080 端口。

管理 API 允许注册或注销并描述模型。它还允许用户扩展或缩减服务模型的 worker 数量。

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

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

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

使用 Torchserve 部署模型的步骤

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

模型处理程序

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

Torchserve 为图像分类、分割、目标检测和文本分类等多种应用程序提供了开箱即用的处理程序。它还支持自定义处理程序,以防您的用例不在默认处理程序的范围内。

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

Torchserve **处理程序**由四个主要**函数**组成:**初始化**、**预处理**、**推理**和**后处理**,每个函数都返回一个列表。下面的代码片段显示了一个自定义处理程序的示例。**自定义处理程序继承**自 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 模型,但大多数这些因素也适用于其他框架的所有模型。

  • **模型优化**:这是将模型部署到生产环境的预备步骤。这是一个非常广泛的讨论,我们将在未来的一系列博客中深入探讨。这包括量化、剪枝以减小模型大小、使用中间表示 (IR 图)(如 PyTorch 中的 Torchscript)、融合内核等技术。目前,torchprep 提供了许多这些技术作为 CLI 工具。
  • **批量推理:** 指的是将多个输入馈送到模型中,这在训练期间是必不可少的,但在推理时管理成本也非常有用。硬件加速器针对并行性进行了优化,批量处理有助于饱和计算能力,通常会导致更高的吞吐量。推理的主要区别在于您不能等待太长时间从客户端填充批处理,我们称之为动态批处理
  • **Worker 数量:** Torchserve 使用 worker 来服务模型。Torchserve worker 是持有模型权重副本以运行推理的 Python 进程。worker 过少意味着您没有充分利用并行性,但 worker 过多会导致 worker 争用并降低端到端性能。
  • **硬件:** 根据模型、应用程序以及延迟和吞吐量预算选择合适的硬件。这可能是 Torchserve 支持的**硬件**之一:**CPU、GPU、AWS Inferentia**。某些硬件配置旨在实现一流性能,而另一些则更适合经济高效的推理。根据我们的实验,我们发现 GPU 在较大的批量大小下表现最佳,而合适的 CPU 和 AWS Inferentia 在较小的批量大小和低延迟下更具成本效益。

Torchserve 性能调优的最佳实践

为了在使用 Torchserve 服务模型时获得最佳性能,我们在此分享一些最佳实践。Torchserve 提供了一个基准测试套件,可以提供有用的见解,以便对以下详细说明的不同选择做出明智的决定。

  • 作为第一步,**优化您的模型**,PyTorch 模型优化教程。**模型优化**的选择也与所选**硬件**密切**相关**。我们将在另一篇博客文章中更详细地讨论它。
  • **决定**模型部署的**硬件**可能与延迟和吞吐量预算以及每次推理的成本密切相关。根据模型和应用程序的大小,它可能会有所不同,对于某些模型,如计算机视觉模型,历史上在 CPU 上进行生产运行是负担不起的。然而,通过像最近添加到 Torchserve 的 IPEX 这样的优化,这已经变得更加经济实惠且具有成本效益,您可以在这个研究性案例研究中了解更多信息
  • Torchserve 中的**worker** 是提供并行性的 Python 进程,应谨慎设置 worker 数量。默认情况下,Torchserve 启动等于 VCPU 或主机上可用 GPU 数量的 worker,这会大大增加 Torchserve 的启动时间。Torchserve 公开了一个配置属性来设置 worker 数量。为了通过**多个 worker** 提供**高效的并行性**并避免它们争夺资源,我们**建议**在 CPU 和 GPU 上进行以下设置作为基准:**CPU**:在处理程序中,`torch.set_num_threads(1)`,然后将 worker 数量设置为`物理核心数 / 2`。但最佳线程配置可以通过利用 Intel CPU 启动脚本来实现。**GPU**:可用 GPU 数量可以通过 config.properties 中的number_gpus 设置。Torchserve 使用轮询方式将 worker 分配给 GPU。我们建议按如下方式设置 worker 数量:`Worker 数量 = (可用 GPU 数量) / (独特模型数量)`。请注意,Ampere 之前的 GPU 不提供任何 Multi Instance GPUs 的资源隔离。
  • **批量大小**直接影响延迟和吞吐量。为了更好地利用计算资源,需要增加批量大小。然而,延迟和吞吐量之间存在权衡。**较大的批量大小**可以**增加吞吐量,但也会导致更高的延迟**。批量大小可以在 Torchserve 中通过两种方式设置:通过 config.properties 中的模型配置,或者在使用管理 API 注册模型时设置。

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

动画绘图性能调优

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

如何运行基准测试套件

Torchserve 中的自动化基准测试套件允许您使用不同的设置(包括批量大小和 worker 数量)对多个模型进行基准测试,并最终为您生成报告。入门步骤如下:

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 延迟**、**吞吐量**。我们特别关注这些数字与**批量大小**、所用**设备、worker 数量**以及是否进行了任何**模型优化**的**组合**。

该模型的**延迟 SLA**已设置为**100 毫秒**,这是一个实时应用程序,如前所述,延迟是更受关注的问题,而**吞吐量**理想情况下应尽可能高,同时**不违反延迟 SLA**。

通过在不同批量大小(1-32)、worker 数量(1-16)和设备(CPU、GPU)上进行搜索,我们进行了一系列实验,下表总结了其中最好的结果。

设备并发# 请求#worker批次大小Payload/图像优化吞吐量P99 延迟
CPU10100011不适用3.45305.3 毫秒
CPU1100011不适用3.45291.8 毫秒
GPU10100011不适用41.0525.48 毫秒
GPU1100011不适用42.2123.6 毫秒
GPU10100014不适用54.7873.62 毫秒
GPU10100014model.half()78.6250.69 毫秒
GPU10100018model.half()85.2994.4 毫秒

该模型在 CPU 上在所有尝试的批量大小、并发性和 worker 数量设置下都未能达到 SLA,实际上高出约 13 倍。

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

我们能对模型进行的最**简单**的**优化**之一是将其精度降低到 **fp16**,这只需一行代码 (**model.half()**),并且可以将**模型 P99 延迟**减少 **32%**,同时将吞吐量增加几乎相同的量。

还可以通过 Torchscripting 模型并使用optimize_for_inference 或其他技巧(包括 onnx 或 tensorrt 运行时优化,它们利用激进的融合)进行其他优化,这些超出了本文的范围。我们将在单独的帖子中讨论模型优化。

我们发现在 CPU 和 GPU 上,**worker 数量设置为 1** 在本例中表现最佳。

  • 将模型转移到 GPU,使用**worker 数量 = 1**,并且**批量大小 = 1**,与 **CPU 相比,吞吐量增加了约 12 倍**,**延迟**也**增加了约 13 倍**。
  • 将模型移至 GPU,使用 **model.half()**,**worker 数量 = 1**,以及**批量大小 = 8**,在**吞吐量**和可容忍延迟方面获得了**最佳**结果。与 **CPU 相比,吞吐量**增加了**约 25 倍**,同时**延迟**仍然**满足 SLA(94.4 毫秒)**。

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

结论

在这篇文章中,我们讨论了 Torchserve 为调整生产环境中的性能而公开的考虑因素和旋钮。我们讨论了 Torchserve 基准测试套件作为调整性能和获取有关模型优化、硬件选择和一般成本的可能选择的见解的手段。我们使用 Animated Drawings 应用程序(它使用 Detectron2 的 Mask-RCNN 模型)作为案例研究,展示了使用基准测试套件进行性能调优。

有关 Torchserve 性能调优的更多详细信息,请参阅我们的文档此处。如有任何其他问题和反馈,请随时在Torchserve 仓库中提出问题。

致谢

我们衷心感谢 Somya Jain (Meta)、Christopher Gustave (Meta) 在本博客的许多步骤中提供的巨大支持和指导,并提供了对 Sketch Animator 工作流程的见解。此外,特别感谢 AWS 的李宁为通过自动化基准测试套件使 Torchserve 上的性能调优变得更容易所做的巨大努力。