Ray Serve 自动扩缩容#
默认情况下,每个 Ray Serve 部署 有一个 副本。这意味着有一个工作进程在运行模型并为请求提供服务。当您的部署流量增加时,单个副本可能会过载。为了保持服务的性能,您需要扩缩容您的部署。
手动扩缩容#
在深入了解更复杂的自动扩缩容之前,另一个可供考虑的选项是手动扩缩容。您可以设置更高的 num_replicas 值来增加副本数量,可以通过 就地更新 在部署选项中进行设置。默认情况下,num_replicas 为 1。增加副本数量将水平扩缩容您的部署,并在流量增加时提高延迟和吞吐量。
# Deploy with a single replica
deployments:
- name: Model
num_replicas: 1
# Scale up to 10 replicas
deployments:
- name: Model
num_replicas: 10
自动扩缩容基本配置#
您可以配置部署以根据传入流量进行自动扩缩容,而不是为部署设置固定的副本数量并手动更新。Serve 自动扩缩容器通过监控队列大小来响应流量峰值,并做出添加或移除副本的扩缩容决策。通过设置 num_replicas="auto" 来为部署开启自动扩缩容。您可以通过调整部署选项中的 autoscaling_config 来进一步配置它。
以下配置是我们将在下一节的示例中使用到的。
- name: Model
num_replicas: auto
设置 num_replicas="auto" 等同于以下部署配置。
- name: Model
max_ongoing_requests: 5
autoscaling_config:
target_ongoing_requests: 2
min_replicas: 1
max_replicas: 100
注意
当您设置 num_replicas="auto" 时,Ray Serve 会应用上面显示的默认值,包括 max_replicas: 100。但是,如果您手动配置自动扩缩容而不使用 num_replicas="auto",则 max_replicas 的基本默认值为 1,这意味着除非您明确设置更高的值,否则不会发生自动扩缩容。即使在使用 num_replicas="auto" 时,您也可以通过指定 autoscaling_config 来覆盖这些默认值。
让我们深入了解这些参数的作用。
target_ongoing_requests 是 Serve 自动扩缩容器试图保证的每个副本的平均进行中请求数。您可以根据请求处理时长(请求越长,此数字应越小)以及您的延迟目标(延迟越短,此数字应越小)进行调整。
max_ongoing_requests 是每个副本允许的最大进行中请求数。请注意,此参数不包含在自动扩缩容配置中,因为它与所有部署相关,但如果您为部署开启了自动扩缩容,相对目标值设置此参数很重要。
min_replicas 是部署的最小副本数。如果没有长时间的流量,可以将此设置为 0,并且在扩容期间会接受一些额外的尾部延迟。否则,请将其设置为您认为低流量所需的值。
max_replicas 是部署的最大副本数。将其设置为您认为峰值流量所需值的约 20% 以上。
这些指南是一个很好的起点。如果您决定为您的应用程序进一步调整自动扩缩容配置,请参阅 Ray Serve 高级自动扩缩容。
基本示例#
此示例是一个同步工作负载,运行 ResNet50。应用程序代码及其自动扩缩容配置如下。或者,请参阅第二个标签页,通过 YAML 文件指定自动扩缩容配置。
import requests
from io import BytesIO
from PIL import Image
import starlette.requests
import torch
from torchvision import transforms
import torchvision.models as models
from torchvision.models import ResNet50_Weights
from ray import serve
@serve.deployment(
ray_actor_options={"num_cpus": 1},
num_replicas="auto",
)
class Model:
def __init__(self):
self.resnet50 = (
models.resnet50(weights=ResNet50_Weights.DEFAULT).eval().to("cpu")
)
self.preprocess = transforms.Compose(
[
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]
),
]
)
resp = requests.get(
"https://raw.githubusercontent.com/pytorch/hub/master/imagenet_classes.txt"
)
self.categories = resp.content.decode("utf-8").split("\n")
async def __call__(self, request: starlette.requests.Request) -> str:
uri = (await request.json())["uri"]
image_bytes = requests.get(uri).content
image = Image.open(BytesIO(image_bytes)).convert("RGB")
# Batch size is 1
input_tensor = torch.cat([self.preprocess(image).unsqueeze(0)]).to("cpu")
with torch.no_grad():
output = self.resnet50(input_tensor)
sm_output = torch.nn.functional.softmax(output[0], dim=0)
ind = torch.argmax(sm_output)
return self.categories[ind]
app = Model.bind()
applications:
- name: default
import_path: resnet:app
deployments:
- name: Model
num_replicas: auto
此示例使用 Locust 对此应用程序运行负载测试。Locust 负载测试运行一定数量的“用户”,这些用户会 ping ResNet50 服务,其中每个用户具有 恒定的等待时间 为 0。每个用户(重复地)发送一个请求,等待响应,然后立即发送下一个请求。下面的图形显示了随着时间的推移运行的用户数量。

负载测试结果如下
副本 |
||
QPS |
|
|
P50 延迟 |
|
注意以下几点
每个 Locust 用户不断发送一个请求并等待响应。因此,随着 Serve 尝试满足
target_ongoing_requests=2的设置,自动扩缩容副本的数量大致是随时间推移的 Locust 用户数的一半。系统的吞吐量随着用户和副本数量的增加而增加。
延迟在流量增加时会短暂飙升,但其他时间保持相对稳定。
Ray Serve 自动扩缩容器与 Ray 自动扩缩容器#
Ray Serve 自动扩缩容器是一个应用程序级别的自动扩缩容器,它构建在 Ray 自动扩缩容器 之上。具体来说,这意味着 Ray Serve 自动扩缩容器会根据请求需求要求 Ray 启动一定数量的副本 actor。如果 Ray 自动扩缩容器确定没有足够的可用资源(例如 CPU、GPU 等)来放置这些 actor,它将通过请求更多 Ray 节点来响应。底层的云提供商随后会通过添加更多节点来响应。同样,当 Ray Serve 缩容并终止副本 actor 时,它会尝试使尽可能多的节点处于空闲状态,以便 Ray 自动扩缩容器可以移除它们。要了解有关 Ray Serve 自动扩缩容基础架构的更多信息,请参阅 Ray Serve 自动扩缩容架构。


