性能调优#

本节将帮助您

  • 了解 Ray Serve 的性能特点

  • 找到调试和调优 Serve 应用程序性能的方法

注意

本节提供了一些提高 Ray Serve 应用程序性能的技巧和窍门。请参阅 架构页面 以获取有帮助的背景信息,包括 HTTP 代理 Actor 和部署副本 Actor 的概述。

性能和基准测试#

Ray Serve 构建在 Ray 之上,因此其可伸缩性受限于 Ray 的可伸缩性。请参阅 Ray 的 可伸缩性包络,以了解有关节点数量和其他限制的更多信息。

调试请求路径中的性能问题#

您最可能遇到的性能问题是请求延迟高或吞吐量低。

设置好 Ray 和 Ray Serve 的 监控 后,这些问题可能会表现为:

  • serve_num_router_requests_total 保持不变,而您的负载在增加

  • serve_deployment_processing_latency_ms 飙升,因为查询在后台排队

以下是解决这些问题的方法:

  1. 确保您使用的是正确的硬件和资源

    • 您是否使用 ray_actor_options 为您的部署副本预留 GPU(例如,ray_actor_options={“num_gpus”: 1})?

    • 您是否使用 ray_actor_options 为您的部署副本预留一个或多个 CPU 核心(例如,ray_actor_options={“num_cpus”: 2})?

    • 您是否设置了 OMP_NUM_THREADS 来提高深度学习框架的性能?

  2. 尝试批处理您的请求。请参阅 动态请求批处理

  3. 考虑在您的可调用对象中使用 async 方法。请参阅 下面的部分

  4. 为 HTTP 请求设置端到端超时。请参阅 下面的部分

使用 async 方法#

注意

根据 FastAPI 文档def 端点函数在单独的线程池中调用,因此您可能会观察到在一个副本中同时运行许多请求,这种情况可能会导致 OOM 或资源耗尽。在这种情况下,您可以尝试使用 async def 来控制工作负载的性能。

您的可调用对象是否使用了 async def?如果您正在使用 asyncio 并遇到上述相同的排队问题,您可能需要增加 max_ongoing_requests。默认情况下,Serve 将此值设置为一个较低的值(5),以确保客户端接收到适当的反压。您可以在部署装饰器中增加此值;例如,@serve.deployment(max_ongoing_requests=1000)

设置端到端请求超时#

默认情况下,Serve 让客户端 HTTP 请求完成,无论它们需要多长时间。但是,慢请求可能会成为副本处理的瓶颈,阻塞其他正在等待的请求。设置端到端超时,以便可以终止并重试慢请求。

您可以通过在 Serve 配置的 http_options 字段中设置 request_timeout_s 参数来为 HTTP 请求设置端到端超时。HTTP 代理将在终止 HTTP 请求之前等待那么多秒。此配置对于您的 Ray 集群是全局的,并且您无法在运行时更新它。请使用 客户端重试 来重试因瞬态故障而超时的请求。

注意

当请求超时时,Serve 会返回状态码为 408 的响应。客户端在收到此 408 响应时可以重试。

选择副本时设置回退时间#

Ray Serve 允许您精细调整请求路由器的回退行为,这有助于在等待副本就绪时减少延迟。当重试路由临时不可用的副本时,它使用指数回退策略。您可以通过配置以下环境变量来优化此行为:

  • RAY_SERVE_ROUTER_RETRY_INITIAL_BACKOFF_S:重试请求之前的初始回退时间(秒)。默认值为 0.025

  • RAY_SERVE_ROUTER_RETRY_BACKOFF_MULTIPLIER:每次重试后应用于回退时间的乘数。默认值为 2

  • RAY_SERVE_ROUTER_RETRY_MAX_BACKOFF_S:重试之间的最大回退时间(秒)。默认值为 0.5

启用吞吐量优化的服务#

注意

在 Ray v2.54.0 中,RAY_SERVE_RUN_USER_CODE_IN_SEPARATE_THREADRAY_SERVE_RUN_ROUTER_IN_SEPARATE_LOOP 的默认值将更改为 0,以提高性能。

本节详细介绍了如何启用专注于提高吞吐量和降低延迟的 Ray Serve 选项。这些配置侧重于以下方面:

  • 减少与频繁日志记录相关的开销。

  • 禁用允许 Serve 应用程序包含阻塞操作的行为。

如果您的 Ray Serve 代码包含线程阻塞操作,您必须重构代码才能实现更高的吞吐量。下表显示了阻塞和非阻塞代码的示例:

阻塞操作 (❌) 非阻塞操作 (✅)
from ray import serve
from fastapi import FastAPI
import time

app = FastAPI()

@serve.deployment
@serve.ingress(app)
class BlockingDeployment:
    @app.get("/process")
    async def process(self):
        # ❌ Blocking operation
        time.sleep(2)
        return {"message": "Processed (blocking)"}

serve.run(BlockingDeployment.bind())
from ray import serve
from fastapi import FastAPI
import asyncio

app = FastAPI()

@serve.deployment
@serve.ingress(app)
class NonBlockingDeployment:
    @app.get("/process")
    async def process(self):
        # ✅ Non-blocking operation
        await asyncio.sleep(2)
        return {"message": "Processed (non-blocking)"}

serve.run(NonBlockingDeployment.bind())

要将所有选项配置为推荐设置,请设置环境变量 RAY_SERVE_THROUGHPUT_OPTIMIZED=1

您也可以单独配置每个选项。下表详细介绍了推荐的配置及其影响:

配置值

影响

RAY_SERVE_RUN_USER_CODE_IN_SEPARATE_THREAD=0

您的代码在与副本主事件循环相同的事件循环中运行。您必须避免在请求路径中进行阻塞操作。将此配置设置为 1 以在单独的事件循环中运行代码,这可以保护副本与 Serve 控制器通信的能力,以防您的代码中有阻塞操作。

RAY_SERVE_RUN_ROUTER_IN_SEPARATE_LOOP=0

请求路由器在与您的代码的事件循环相同的事件循环中运行。您必须避免在请求路径中进行阻塞操作。将此配置设置为 1 以在单独的事件循环中运行路由器,这可以在您的代码中存在阻塞操作时保护 Ray Serve 的请求路由能力。

RAY_SERVE_REQUEST_PATH_LOG_BUFFER_SIZE=1000

将日志缓冲区设置为每 1000 条日志批量写入,在写入时刷新缓冲区。当系统检测到级别为 ERROR 的行时,将始终刷新缓冲区并写入日志。将缓冲区大小设置为 1 以禁用缓冲并立即写入日志。

RAY_SERVE_LOG_TO_STDERR=0

仅将日志写入 logs/serve/ 目录下的文件。代理、控制器和副本日志不再出现在控制台、工作文件或 Ray Dashboard 的 Actor Logs 部分。将此属性设置为 1 以启用其他日志记录。

您可能希望在自定义上述选项的同时启用吞吐量优化的服务。您可以通过设置 RAY_SERVE_THROUGHPUT_OPTIMIZED=1 并覆盖特定选项来实现此目的。例如,要启用吞吐量优化的服务并继续将日志记录到 stderr,您应该设置 RAY_SERVE_THROUGHPUT_OPTIMIZED=1 并使用 RAY_SERVE_LOG_TO_STDERR=1 进行覆盖。

调试控制器中的性能问题#

Serve 控制器运行在 Ray 的主节点上,负责各种任务,包括接收来自其他 Ray Serve 组件的自动伸缩指标。如果 Serve 控制器过载(症状可能包括高 CPU 使用率和大量待处理的 ServeController.record_autoscaling_metrics_from_handle 任务),您可以通过设置 RAY_SERVE_CONTROL_LOOP_INTERVAL_S 环境变量(默认为 0.1 秒)来增加控制循环周期之间的间隔。此设置使控制器有更多时间处理请求,并可能有助于缓解过载。