在本文中,我们将讨论如何对 Torchserve 进行性能调优,以便在生产环境中部署您的模型。机器学习项目生命周期中最大的挑战之一是在生产环境中部署模型。这需要一个可靠的服务解决方案,以及满足 MLOps 需求的解决方案。一个强大的服务解决方案需要支持多模型服务、模型版本控制、指标日志记录、监控以及扩展以应对高峰流量。在本文中,我们将概述 Torchserve 以及如何针对生产用例调整其性能。我们将讨论 Meta 的 Animated Drawings 应用,它可以将您的人形草图转化为动画,以及它如何利用 Torchserve 处理高峰流量。Animated Drawing 的工作流程如下。
https://ai.facebook.com/blog/using-ai-to-bring-childrens-drawings-to-life/
许多 AI 系统和工具被设计用于处理逼真的人类图像,而儿童绘画则增加了复杂性和不可预测性,因为它们通常以抽象、奇特的方式构建。这类形态和风格的变化甚至会使那些在逼真图像和绘画中识别物体方面表现卓越的顶尖 AI 系统感到困惑。Meta AI 研究人员正致力于克服这一挑战,以便 AI 系统能更好地识别儿童以各种不同方式创作的人形绘画。这篇精彩的博文提供了更多关于 Animated Drawings 及其所采用方法的信息。
Torchserve
训练好模型后,需要将其集成到更大的系统中以形成一个完整的应用,我们将这种集成称为“模型服务”(model serving)。基本上,模型服务就是让您训练好的模型能够进行推理并后续使用。
Torchserve 是 PyTorch 在生产环境中部署模型的首选解决方案。它是一个高性能且可扩展的工具,将您的模型封装在 HTTP 或 HTTPS API 中。它有一个用 Java 实现的前端,负责处理从分配服务模型的工作进程到处理客户端和服务器之间连接的多个任务。Torchserve 有一个 Python 后端,负责处理推理服务。
Torchserve 支持多模型服务和 AB 测试版本控制、动态批处理、日志记录和指标。它公开了四个 API:推理、解释、管理和指标。
推理 API 默认监听 8080 端口,可通过 localhost 访问,这可以在 Torchserve 配置中进行配置,并用于从模型获取预测结果。
解释 API 在内部使用 Captum 为正在服务的模型提供解释,并且也监听 8080 端口。
管理 API 允许注册或注销以及描述模型。它还允许用户扩展或缩减服务模型的 worker 数量。
指标 API 默认监听 8082 端口,使我们能够监控正在服务的模型。
Torchserve 通过支持批量推理和多个服务您模型的 worker 来帮助您扩展模型服务并处理高峰流量。可以通过管理 API 和通过配置文件进行设置来实现扩展。此外,指标 API 还可以帮助您通过默认和可定制的指标来监控您的模型服务。
其他高级设置,如接收请求的队列长度、批输入的最大等待时间以及许多其他属性,都可以通过启动 Torchserve 时传递的配置文件进行配置。
使用 Torchserve 部署模型的步骤
- 安装 Torchserve、model archiver 及其依赖。
- 选择适合您任务的默认 handler(例如图像分类等),或者编写自定义 handler。
- 使用Torcharchive 将您的模型 artifacts(训练好的模型 checkpoint 和加载、运行模型所需的所有其他文件)和 handler 打包成“.mar”文件,并将其放入模型库。
- 开始部署您的模型.
- 运行推理。我们将在此处更详细地讨论 model handlers 和指标。
模型 handler
Torchserve 在后端使用 handler 来加载模型、预处理接收到的数据、运行推理并后处理响应。Torchserve 中的 handler 是一个包含所有模型初始化、预处理、推理和后处理逻辑的 Python 脚本。
Torchserve 为图像分类、分割、目标检测和文本分类等多种应用提供了现成的 handler。如果您的用例不受默认 handler 支持,它也支持自定义 handler。
自定义 handler 提供了极大的灵活性,这使得 Torchserve 有可能成为一个 多框架 服务工具。自定义 handler 允许您定义自定义逻辑来初始化模型,也可以用于加载 ONNX 等其他框架的模型。
Torchserve 的 handler 由四个主要 函数 组成:initialize(初始化)、preprocess(预处理)、inference(推理)和 postprocess(后处理),每个函数都返回一个列表。下面的代码片段展示了一个自定义 handler 的示例。自定义 handler 继承 Torchserve 中的 BaseHandler,并且可以 覆盖 任何 主要 函数。这里是一个用于加载 Detectron2 模型进行人物检测的 handler 示例,该模型已导出为 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,默认监听 8082 端口,用户可以查询和监控收集到的指标。默认的指标端点返回 Prometheus 格式的指标。您可以使用 curl 请求查询指标,或将 Prometheus Server 指向该端点,并使用 Grafana 构建仪表盘。
部署模型时,您可以使用 curl 请求查询指标,如下所示
curl http://127.0.0.1:8082/metrics
如果您希望导出记录的指标,请参考此示例,它使用 mtail 将指标导出到 Prometheus。在仪表盘中跟踪这些指标可以帮助您监控在离线基准测试运行中可能零星出现或难以发现的性能退化。
在生产环境中调优模型性能需要考虑的事项
图 1 中所示的工作流程是使用 Torchserve 在生产环境中部署模型的总体思路。
在许多情况下,生产环境中的模型服务是根据吞吐量或延迟服务水平协议(SLA)进行优化的。通常,实时应用更关心延迟,而离线应用可能更关心更高的吞吐量。
影响生产环境中模型服务性能的主要因素有很多。特别地,我们在这里重点讨论使用 Torchserve 部署 PyTorch 模型,但这些因素中的大多数也适用于其他框架的模型。
- 模型优化:这是将模型部署到生产环境的前置步骤。这是一个非常广泛的讨论,我们将在未来一系列博文中深入探讨。这包括量化、剪枝(以减小模型大小)、使用 PyTorch 中的 Torchscript 等中间表示(IR 图)、内核融合等多种技术。目前,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 启动的 worker 数量等于主机的 VCPU 或可用 GPU 数量,这会显著增加 Torchserve 的启动时间。
Torchserve 公开了一个配置属性来设置 worker 数量。为了通过 多个 worker 提供 高效的并行性 并避免它们争夺资源,我们推荐在 CPU 和 GPU 上采用以下基准设置
CPU:在 handler 中,设置
torch.set_num_threads(1)
,然后将 worker 数量设置为num physical cores / 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”文件,其中包含模型、handler 和加载及运行推理所需的所有其他 artifacts。Animated Drawings 使用 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 延迟、吞吐量、并发数、请求数、handler 时间以及其他一些指标。这里我们重点关注一些用于性能调优的重要指标,它们是:并发数、模型 P99 延迟、吞吐量。我们特别结合批量大小、使用的设备、worker 数量以及是否进行了任何模型优化来查看这些数值。
该模型的延迟 SLA 设置为 100 毫秒,这是一个实时应用,正如我们之前讨论的,延迟更受关注,并且在不违反延迟 SLA 的前提下,吞吐量理想情况下应尽可能高。
通过在不同的批量大小(1-32)、worker 数量(1-16)和设备(CPU、GPU)空间中进行探索,我们进行了一系列实验,下表总结了其中的最佳结果。
设备 | 并发数 | 请求数 | worker 数 | 批量大小 | Payload/图像 | 优化 | 吞吐量 | 延迟 P99 |
CPU | 10 | 1000 | 1 | 1 | 小 | N/A | 3.45 | 305.3 毫秒 |
CPU | 1 | 1000 | 1 | 1 | 小 | N/A | 3.45 | 291.8 毫秒 |
GPU | 10 | 1000 | 1 | 1 | 小 | N/A | 41.05 | 25.48 毫秒 |
GPU | 1 | 1000 | 1 | 1 | 小 | N/A | 42.21 | 23.6 毫秒 |
GPU | 10 | 1000 | 1 | 4 | 小 | N/A | 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 上使用所有尝试过的批量大小、并发数和 worker 数量设置,其延迟均未达到 SLA 要求,实际上高出约 13 倍。
将模型服务迁移到 GPU,可以立即将延迟提高约 **13 倍**,从 305 毫秒降至 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 仓库中提交 issue。
致谢
我们感谢 Somya Jain (Meta)、Christopher Gustave (Meta) 在本博文撰写过程中的大力支持和指导,并为 Sketch Animator 工作流程提供了见解。此外,特别感谢来自 AWS 的李宁 (Li Ning),他在通过自动化基准测试套件使 Torchserve 性能调优更容易方面做出了巨大努力。