2022 年 7 月 28 日 — 发布者:Chansung Park 和 Sayak Paul(ML-GDE) 在这篇文章中,我们将分享在对跨多个部署配置的图像分类模型进行负载测试时学到的经验教训和发现。这些配置涉及使用 TensorFlow Serving 进行基于 REST 的部署。通过这种方式,我们旨在为读者提供对不同配置之间差异的全面了解。…
发布者:Chansung Park 和 Sayak Paul(ML-GDE)
在这篇文章中,我们将分享在对跨多个部署配置的图像分类模型进行负载测试时学到的经验教训和发现。这些配置涉及使用 TensorFlow Serving 进行基于 REST 的部署。通过这种方式,我们旨在为读者提供对不同配置之间差异的全面了解。
这篇文章更关注架构决策而不是代码。我们将首先概述我们的设置,包括技术规格。我们还将分享我们对所做设计选择及其影响的评论。
TensorFlow Serving 功能丰富,并在其设计中嵌入了目标规范(稍后会详细介绍)。对于 在线预测场景,模型通常会以某种服务形式公开。
为了进行测试,我们使用了一个 预训练的 ResNet50 模型,它可以将各种图像分类到不同的类别中。然后,我们将该模型按以下方式提供服务
- Docker 用于容器化环境。
- Kubernetes 用于编排容器节点集群以实现可扩展性。我们使用 Kubernetes Engine(GKE)来管理它。
- GitHub Actions 用于在 GKE 上自动推出部署。
我们的部署平台(Kubernetes 集群中的节点)是基于 CPU 的。我们在任何阶段都没有使用 GPU。为此,我们可以构建一个针对 CPU 优化的 TensorFlow Serving 镜像,并利用其他一些选项来减少延迟并提高系统的整体吞吐量。我们将在本文后面讨论这些内容。
您可以在 此仓库 中找到所有代码,并了解部署是如何执行的。在这里,您将找到示例笔记本和详细的设置说明,以便您可以试用该代码。因此,我们不会逐行讨论代码,而是在必要时阐明最重要的部分。
在本文的其余部分,我们将讨论针对 TensorFlow Serving 部署实验的关键注意事项,包括其动机、局限性以及我们的实验结果。
随着 Vertex AI 等无服务器产品的出现,部署模型并安全可靠地扩展它们变得前所未有的容易。这些服务有助于极大地缩短上市时间并提高开发人员的整体生产力。也就是说,您可能仍然希望对某些方面进行更细致的控制。这就是我们首先进行这些实验的原因之一。
TensorFlow Serving 有其自身的一套约束和设计选择,这些选择会影响部署。在本节中,我们将简要概述这些注意事项。
部署基础设施:我们选择 GKE,因为 Kubernetes 是使用 GCP 时的一种标准部署平台,而 GKE 使我们能够专注于 ML 部分,而不必担心基础设施,因为它是 Google Cloud Platform 的完全托管服务。我们主要关注如何在基于 CPU 的环境中部署模型,因此我们准备了一个针对 CPU 优化的 TensorFlow Serving 镜像。
更多或更少服务器之间的权衡:我们从使用最简单的 VM(配备 2 个 vCPU 和 4GB RAM)开始对 TensorFlow Serving 设置进行实验,然后逐渐将规格升级到 8 个 vCPU 和 64GB RAM。另一方面,我们将 Kubernetes 集群中的节点数量从 8 个减少到 2 个,因为这是在部署更便宜的服务器的成本与更少的昂贵服务器之间的权衡。
利用多核环境的选项:我们想看看高端 VM 是否能够优于简单 VM,即使节点更少,也能利用多核环境。为此,我们尝试了不同的 inter_op_parallelism
和 intra_op_parallelism
线程数,以根据 CPU 内核数量设置 TensorFlow Serving 部署。
动态批处理和其他注意事项:现代 ML 框架(例如 TensorFlow Serving)通常支持动态批处理、初始模型预热、不同模型的多个版本的多个部署等等。为了进行在线预测,我们没有仔细测试这些功能。但是,根据 官方文档,动态批处理功能也值得探索以提高性能。我们已经发现,默认的批处理配置可以稍微降低延迟,尽管本文中没有包含该结果。
我们已经准备了以下环境。在 TensorFlow Serving 中,intra_op_parallelism
_threads
的数量设置为 CPU 内核数量,而 inter_op_parallelism_threads
的数量设置为 2 到 8,用于实验目的,因为它控制用于并行执行独立操作的线程数量。下面我们提供了对每个 Kubernetes 集群的 vCPU 数量、RAM 大小和节点数量进行调整的详细信息。请注意,vCPU 数量和 RAM 大小适用于每个集群节点。
负载测试使用 Locust 进行。我们对每个负载测试进行了 5 分钟。请求数量由用户数量控制,它取决于客户端的具体情况。我们将用户数量每秒增加一个,直到达到 150 个(我们发现处理的请求数量在这个数字时已达到平台,并且请求以每秒一个的速度生成,以了解 TensorFlow Serving 的行为。因此,您可以假设请求/秒并不反映现实世界中客户在任何时间尝试发送请求的情况)。
我们对 Kubernetes 集群上的以下节点配置进行了实验。这些配置的阅读方式如下:{num_vcpus_per_node}-{ram}_{num_nodes}
您可以在上面提到的仓库中找到用于对这些不同配置进行实验的代码。每个实验的部署都是通过 Kustomize 来配置的,以覆盖基本配置,并且文件级配置是通过 ConfigMap 注入的。
本节介绍了上述每种配置的结果,并根据我们考虑的环境建议哪种配置是最佳的。根据图 1,基于结果,观察到的最佳配置和环境设置是 2 个节点、8 个 intra_op_parallelism_threads
、8 个 inter_op_parallelism_threads
、8 个 vCPU、16GB RAM。
图 1:不同 TensorFlow Serving 配置之间的比较(原始)。 |
我们通过选择最佳选项观察到了以下方面。
inter_op_parallelism_threads
数量非常重要,需要进行实验。即使节点配备了高容量硬件,更高数量也并不总是能保证更好的性能。TensorFlow Serving 更侧重于可靠性而不是吞吐量性能。我们认为它牺牲了一些吞吐量性能来实现可靠性,但这是 TensorFlow Serving 的预期行为,如 官方文档 中所述。即使尽可能处理更多请求非常重要,但在处理生产系统时,保持服务器尽可能可靠也是同样重要的。
性能和可靠性之间存在权衡,因此您必须谨慎选择合适的选项。但是,TensorFlow Serving 的吞吐量性能似乎与 其他框架(如 FastAPI)的结果 相当接近,如果您想考虑更丰富的功能(如动态批处理)并高效地共享模型之间的 GPU 资源,我们认为 TensorFlow Serving 是正确的选择。
我们正在处理用于部署的图像分类模型,并且模型的输入将包括图像。因此,请求有效负载的大小会随着图像分辨率和保真度的不同而发生变化。因此,确保消息传输尽可能轻量级尤其重要。通常,gRPC 中的消息传输比 REST 快很多。 这篇文章 对 REST 和 gRPC API 之间的关键差异进行了很好的讨论。
TensorFlow Serving 可以 使用 gRPC 提供模型服务,但比较 gRPC API 和 REST API 的性能并非易事。这就是为什么我们没有在本篇文章中包含该内容。感兴趣的读者可以查看 此仓库,它遵循类似的设置,但使用 gRPC 服务器。
为此,我们使用了 GCP 成本估算器。假设每个实验配置的定价每月 24 小时(这对我们的实验来说足够了)。
机器配置(E2 系列) |
定价(美元) |
2 个 vCPU,4GB RAM,8 个节点 |
11.15 |
4 个 vCPU,8GB RAM,4 个节点 |
11.15 |
8 个 vCPU,16GB RAM,2 个节点 |
11.15 |
8 个 vCPU,64GB RAM,2 个节点 |
18.21 |
在这篇文章中,我们讨论了从负载测试标准图像分类模型的经验中获得的一些关键教训。我们考虑了将模型暴露给最终用户的行业级框架 - TensorFlow Serving。虽然我们进行负载测试的设置可能并不完全类似于现实情况,但我们希望我们的发现至少可以作为社区的一个良好起点。即使这篇文章展示了我们使用图像分类模型的方法,这些方法应该在很大程度上与任务无关。
为了简洁起见,我们没有在 API 中过多地推动模型效率方面的改进。借助现代 CPU、软件栈和操作系统级别的优化,可以提高模型的延迟和吞吐量。我们将有兴趣的读者引导到以下可能相关的资源
我们感谢 ML 生态系统团队 提供了 GCP 积分来支持我们的实验。我们也感谢 Hannes Hapke 和 Robert Crowe 为我们提供了有益的反馈和指导。
2022 年 7 月 28 日 — 发布者 Chansung Park 和 Sayak Paul (ML-GDE) 在这篇文章中,我们将分享从对众多部署配置的图像分类模型进行负载测试中获得的经验和发现。这些配置涉及使用 TensorFlow Serving 进行基于 REST 的部署。通过这种方式,我们旨在使读者全面了解配置之间差异……