这篇文章最初发表在 NVIDIA 技术博客上。
缓存与数组、符号或字符串一样是计算的基础。整个堆栈中的各种缓存层在您的 CPU 上挂起时保存来自内存的指令。它们使您能够在离开时快速重新加载页面,而无需重新验证。它们还显著降低了应用程序的工作负载,并通过不重复运行相同的查询来提高吞吐量。
NVIDIA Triton Inference Server 对于缓存来说,是一个调整为以张量推理的形式回答问题的系统。运行推理是一项计算成本相对较高的任务,它经常调用相同的推理来重复运行。这自然有助于使用缓存模式。
NVIDIA Triton 团队最近实施了 Triton response cache,并使用了 Triton local cache 库。他们还建立了 cache API,以使该缓存模式在 Triton 中可扩展。然后,Redis 团队利用 API 构建了 NVIDIA Triton 的 Redis cache。
在这篇文章中,Redis 团队探讨了 Redis 的实现 API 的 Triton 缓存。我们将探讨如何开始使用 Redis 来增强 NVIDIA Triton 实例的一些最佳实践。
什么是 Redis?
Redis 是 REmote DIctionary Server 的缩写。它是一个作为键值数据结构存储操作的 NoSQL 数据库。Redis 是内存优先的,这意味着 Redis 中的整个数据集都存储在内存中,并可以根据配置持久化到磁盘。因为 Redis 是一个完全保存在内存中的键值数据库,所以速度非常快。执行时间以微秒为单位,吞吐量以每秒数万次操作为单位。
Redis 卓越的速度和典型的访问模式使其成为缓存的理想选择。Redis 是缓存的代名词,因此是各种开发人员社区中大多数主要应用程序框架的内置分布式缓存之一。
什么是本地缓存?
本地缓存是最常见的缓存模式(缓存除外)的内存派生。它简单高效,易于掌握和实施。 NVIDIA Triton 收到查询后:
- 计算输入查询的哈希,包括张量和一些元数据。这就成为了推理的关键。
- 检查该键处该张量的先前推断结果。
- 返回找到的任何结果。
- 如果未找到结果,则执行推理。
- 使用用于存储的键将推理缓存在内存中。
- 返回推理。
"本地"意味着它保持在进程的本地,并将缓存存储在系统的主内存中。图 1 显示了该模式的实现。
图 1。 NVIDIA Triton 使用本地缓存
本地缓存的好处
使用这种模式自然会带来各种好处。因为查询是缓存的,所以可以很容易地再次检索它们,而无需通过模型重新运行张量。因为所有东西都在进程内存中本地维护,所以不需要离开进程或机器来检索缓存的数据。这两者协同作用可以显著提高吞吐量,同时降低计算成本。
本地缓存的缺点
这种技术确实有缺点。因为缓存直接绑定到进程内存中,所以每次 Triton 进程重新启动时,它都会从平方开始(通常称为冷启动)。当缓存预热时,您将看不到缓存带来的好处。此外,由于缓存被进程锁定, Triton 的其他实例将无法共享缓存,导致每个节点的缓存重复。
另一个主要缺点是资源争用。由于本地缓存与进程绑定,因此它仅限于 Triton 运行的系统的资源。这意味着不可能水平扩展分配给缓存的资源(将缓存分布在多台机器上),这限制了将本地缓存扩展到垂直扩展的选项。这使得运行 Triton 的服务器变得更大。
Redis 分布式缓存的好处
与本地缓存不同,分布式缓存利用外部服务(如 Redis)从本地服务器分发缓存。这为 NVIDIA Triton 缓存 API 带来了几个优势:
- Redis 不与 Triton 绑定到同一台机器的可用系统资源,也不与单个机器绑定。
- Redis 与 Triton 的进程生命周期解耦,使多个 Triton instance 能够利用同一个缓存。
- Redis 速度极快(执行时间通常在几毫秒以下)。
- 与 Triton 本地缓存相比,Redis 是一种更加专业化、功能丰富且可调的缓存服务。
- Redis 提供了对久经考验的高可用性、水平扩展和缓存清除功能的即时访问。
Redis 的分布式缓存与本地缓存的工作方式大致相同。它没有停留在同一个进程中,而是跨出 Triton 服务器进程到 Redis 来检查缓存并存储推断。 NVIDIA Triton 收到查询后:
- 计算输入查询的哈希,包括张量和一些元数据。这就成为了推理的关键。
- 检查 Redis 是否有以前的运行推断。
- 返回该推理(如果存在)。
- 如果推理不存在,则通过 Triton 运行张量。
- 将推理存储在 Redis 中。
- 返回推理。
在体系结构上,如图 2 所示。
图 2: NVIDIA Triton 使用 Redis 作为缓存层
分布式缓存设置和配置
要设置分布式 Redis 缓存,需要两个顶级步骤:
- 部署您的 Redis 实例。
- 将 NVIDIA Triton 配置为指向 Redis 实例。
Triton 将帮助你处理剩下的事情。如果想了解更多关于 Redis 的信息,请参阅 redis.io,docs.redis.com 和 Redis University。
要将 Triton 配置为指向 Redis 实例,请使用 --- 缓存配置选项。在模型配置中,为模型启用响应缓存,具有{{response_cache{enable:true}}。
arduino
tritonserver --cache-config redis,host=localhost --cache-config redis,port=6379
Redis 缓存要求您至少配置 Redis 实例的主机和端口。要查看所有配置选项,请参阅 Triton Redis Cache GitHub。
Redis 的最佳实践
Redis 重量轻,易于使用,速度极快。即使 Redis 占地面积小且简单,您也可以在 Redis 中及其周围进行大量配置,以根据您的用例对其进行优化。本节重点介绍使用和配置 Redis 的最佳实践。
最小化往返时间
在进程内内存缓存上使用像 Redis 这样的外部服务的唯一真正缺点是,对 Redis 的查询至少必须跨进程。它们通常也需要跨越服务器边界。
因此,最小化往返时间(RTT)对于优化 Redis 作为缓存的使用至关重要。如何最大限度地减少 RTT 的主题太复杂了,这篇文章无法深入探讨。几个关键提示:维护 Redis 服务器与 Triton 服务器的位置,并使它们在物理上彼此靠近。如果它们在数据中心,请尝试将它们放在同一机架或可用区域。
可扩展性和高可用性
Redis 集群使您能够在多个分片上进行 Redis 实例的水平扩展。集群包括复制 Redis 实例的功能。如果主分片出现故障,可以升级副本以实现高可用性。
最大内存和逐出
如果 Redis 的内存没有上限,它将使用操作系统释放给它的所有可用内存,这是由maxmemory 这个在 redis.conf 中的配置键控制的。但是,如果设置了 maxmemory,那么当 Redis 的内存用完时会发生什么呢?正如您所期望的,它会默认停止接受对 Redis 的新写入。
但是,您也可以设置驱逐政策。驱逐政策使用一些基本的情报来决定哪些密钥可能是从 Redis 中驱逐出去的好候选者。允许 Redis 收回对存储不再有意义的密钥,使其能够在内存充满时继续接受新的写入,而不会中断。
有关不同 Redis 驱逐策略的完整解释,请参阅 Redis 手册中的 key eviction 。
耐久性和持久性
Redis 是内存优先,意味着一切都存储在内存中。如果你不配置持久性,Redis 进程就会死亡,它基本上会返回到冷启动状态。(在您从缓存中获得好处之前,缓存需要"w Arm 向上"。)
有两种选项可以持久化 Redis:一种是在 .rdb 文件中定期拍摄 Redis 状态的快照,另一种是在仅追加文件中记录所有写入命令。有关这些方法的完整说明,请参阅 Redis 手册中的持久化部分。
速度比较
言归正传,本节将探讨 Triton(无 Redis)和 Triton(有 Redis)之间性能的全面差异。为了简洁起见,我们使用了 perf_analyzer,这是 Triton 团队为测量 Triton 性能而构建的工具。我们测试了两个独立的模型,DenseNet 和 Simple。
我们在谷歌云平台(GCP)n1-标准-4 虚拟机上运行了 Docker 服务器 23.06 版,该虚拟机带有一个 NVIDIA T4 GPU 。我们还在 GCP n2-standard-4 虚拟机上运行了一个普通的开源 Redis 实例。最后,我们在 GCP e2 介质 VM 上运行了 Triton 中的 Triton 客户端映像。
我们对 DenseNet 和 Simple 型号进行了性能分析,每种缓存配置运行 10 次,包括无缓存,使用 Redis 作为缓存,以及使用本地缓存。然后我们对这些运行结果取平均值。
需要注意的是,这些运行假定缓存命中率为 100%。因此,测量是 Triton 在过去遇到入口时和没有遇到入口时的性能之间的差异。
我们对 DenseNet 模型使用了以下命令:
vbscript
perf_analyzer -m densenet_onnx -u triton-server:8000
我们对 Simple 模型使用了以下命令:
vbscript
perf_analyzer -m simple -u triton-server:8000
在 DenseNet 模型的情况下,结果表明,使用任一缓存都比不使用缓存要好得多。在没有缓存的情况下, Triton 每秒能够处理 80 个推断(推断/秒),平均延迟为 12680µs。使用 Redis,它的速度大约快了 4 倍,每秒处理 329 个推理,平均延迟为 3030µs。
有趣的是,虽然本地缓存比 Redis 快一些,正如你所期望的那样,但它只是稍微快一些。本地缓存的吞吐量为 355 推理/秒,延迟为 2817µs,仅快约 8%。在这种情况下,很明显,本地缓存与 Redis 中缓存的速度权衡是微不足道的。考虑到使用分布式缓存与本地缓存相比所带来的所有额外好处,在处理这些类型的数据时,几乎可以肯定的是分布式的。
图 3。DenseNet 吞吐量比较,表明 Redis 的吞吐量与本地缓存的计算成本相当
图 4。DenseNet 延迟比较,证明 Redis 延迟可与本地缓存进行计算成本高昂的推断
简单模型告诉了一个稍微复杂一些的故事。在简单模型的情况下,不使用任何缓存可以实现 1358 推理/秒的吞吐量和 735µs 的延迟。Redis 速度更快,吞吐量为 1639 推理/秒,延迟为 608µs。Local 比 Redis 更快,吞吐量为 2753 推理/秒,延迟为 363µs。
这是一个需要注意的重要案例,因为并非所有用途都是平等的。在这种情况下,记录系统可能足够快,不值得为 Redis 的吞吐量增加 20%的额外系统。即使在本地缓存的情况下延迟减半,也可能不值得进行资源争用,这取决于缓存命中率和可用系统资源等其他因素。
图 5。简单模型吞吐量。对于计算成本较低的推断,Redis 相对于本地缓存的吞吐量优势较小
图 6。简单的模型延迟。对于计算成本较低的推断,Redis 相对于本地缓存的延迟优势较小
管理权衡的最佳实践
如实验所示,模型、预期输入和预期输出之间的差异对于评估什么(如果有的话)缓存适合您的 Triton 实例至关重要。
缓存是否增加值在很大程度上取决于查询的计算成本。查询的计算成本越高,每个查询从缓存中获得的好处就越多。
局部与 Redis 的相对性能在很大程度上取决于模型的输出张量有多大。输出张量越大,传输成本对 Redis 允许的吞吐量的影响就越大。
当然,输出张量越大,在空间用完并开始与 Triton 争夺资源之前,您能够存储在本地缓存中的输出张量就越少。从根本上讲,在评估哪种缓存解决方案最适合 Triton 的部署时,需要平衡这些因素。
好处 | 缺点 |
1.横向可扩展2.有效的无限制内存访问3.实现高可用性和灾难恢复4.消除资源争用5.最大限度地减少冷启动 | 分布式 Redis 缓存需要调用通过网络。当然,你可以预计吞吐量会有所降低与本地高速缓存相比具有更高的延迟。 |
表 1。使用 Redis 作为缓存层而不是本地缓存的优点和缺点
总结
分布式缓存是开发人员用来提高系统性能的一个老技巧,同时实现水平可扩展性和关注点分离。随着为 Triton 推理服务器引入 Redis 缓存,您现在可以利用这项技术大大提高 Triton instance 的性能和效率,同时管理更重的工作负载,并使多个 Triton -instance 共享在同一个缓存中。从根本上讲,通过将缓存卸载到 Redis, Triton 可以将其资源集中在其基本角色上------运行推断。
开始使用 Triton Redis Cache 和 NVIDIA Triton Inference Server。有关设置和管理 Redis 实例的更多信息,请参阅 redis.io 和 docs.redis.com。