部署多个应用#

Serve 支持部署多个独立的 Serve 应用。本用户指南将详细介绍如何生成多应用配置文件并使用 Serve CLI 部署它,以及如何使用 CLI 和 Ray Serve dashboard 监控你的应用。

上下文#

背景#

随着 Ray Serve 多应用功能的推出,我们将引导你了解应用的新概念,以及何时应选择在每个集群上部署单个应用或多个应用。

一个应用由一个或多个部署组成。应用中的部署通过模型组合绑定成一个有向无环图。应用可以通过指定的路由前缀通过 HTTP 调用,并且 ingress deployment 会处理所有此类入站流量。由于应用中部署之间的依赖性,一个应用是一个升级单元。

何时使用多个应用#

你可以使用模型组合或多应用来解决许多用例。然而,两者各有优点,并且可以一起使用。

假设你拥有多个模型和/或业务逻辑,它们都需要为一个请求执行。如果它们位于同一个代码库中,那么你很可能会将它们作为一个整体进行升级,因此我们建议将所有这些部署放在一个应用中。

另一方面,如果这些模型或业务逻辑具有逻辑分组,例如,相互通信但位于不同代码库中的模型组,我们建议将这些模型分隔到不同的应用中。多应用的另一个常见用例是独立分组的模型,它们可能不相互通信,但你想将它们共同托管以提高硬件利用率。因为一个应用是一个升级单元,拥有多个应用使你可以部署许多独立的模型(或模型组),每个都位于不同的端点后面。然后,你可以轻松地从集群中添加或删除应用,以及独立地升级各个应用。

入门#

定义一个 Serve 应用

import requests
import starlette

from transformers import pipeline
from io import BytesIO
from PIL import Image

from ray import serve
from ray.serve.handle import DeploymentHandle


@serve.deployment
def downloader(image_url: str):
    image_bytes = requests.get(image_url).content
    image = Image.open(BytesIO(image_bytes)).convert("RGB")
    return image


@serve.deployment
class ImageClassifier:
    def __init__(self, downloader: DeploymentHandle):
        self.downloader = downloader
        self.model = pipeline(
            "image-classification", model="google/vit-base-patch16-224"
        )

    async def classify(self, image_url: str) -> str:
        image = await self.downloader.remote(image_url)
        results = self.model(image)
        return results[0]["label"]

    async def __call__(self, req: starlette.requests.Request):
        req = await req.json()
        return await self.classify(req["image_url"])


app = ImageClassifier.bind(downloader.bind())

将此代码复制到名为image_classifier.py的文件中。

定义第二个 Serve 应用

import starlette

from transformers import pipeline

from ray import serve


@serve.deployment
class Translator:
    def __init__(self):
        self.model = pipeline("translation_en_to_de", model="t5-small")

    def translate(self, text: str) -> str:
        return self.model(text)[0]["translation_text"]

    async def __call__(self, req: starlette.requests.Request):
        req = await req.json()
        return self.translate(req["text"])


app = Translator.bind()

将此代码复制到名为text_translator.py的文件中。

生成一个包含这两个应用的多应用配置文件,并将其保存到config.yaml

serve build image_classifier:app text_translator:app -o config.yaml

这将生成以下配置

proxy_location: EveryNode

http_options:
  host: 0.0.0.0
  port: 8000

grpc_options:
  port: 9000
  grpc_servicer_functions: []

logging_config:
  encoding: JSON
  log_level: INFO
  logs_dir: null
  enable_access_log: true

applications:
  - name: app1
    route_prefix: /classify
    import_path: image_classifier:app
    runtime_env: {}
    deployments:
      - name: downloader
      - name: ImageClassifier

  - name: app2
    route_prefix: /translate
    import_path: text_translator:app
    runtime_env: {}
    deployments:
      - name: Translator

注意

每个应用的名称会自动生成为app1app2等。要为应用指定自定义名称,请在进入下一步之前修改配置文件。

部署应用#

要部署这些应用,请确保首先启动一个 Ray 集群。

$ ray start --head

$ serve deploy config.yaml
> Sent deploy request successfully!

在各自的端点/classify/translate查询应用。

bear_url = "https://cdn.britannica.com/41/156441-050-A4424AEC/Grizzly-bear-Jasper-National-Park-Canada-Alberta.jpg"  # noqa
resp = requests.post("http://localhost:8000/classify", json={"image_url": bear_url})

print(resp.text)
# 'brown bear, bruin, Ursus arctos'
text = "Hello, the weather is quite fine today!"
resp = requests.post("http://localhost:8000/translate", json={"text": text})

print(resp.text)
# 'Hallo, das Wetter ist heute ziemlich gut!'

使用serve run的开发工作流程#

你还可以使用 CLI 命令serve run轻松地运行和测试你的应用,无论是在本地还是在远程集群上。

$ serve run config.yaml
> 2023-04-04 11:00:05,901 INFO scripts.py:327 -- Deploying from config file: "config.yaml".
> 2023-04-04 11:00:07,505 INFO worker.py:1613 -- Started a local Ray instance. View the dashboard at http://127.0.0.1:8265
> 2023-04-04 11:00:09,012 SUCC scripts.py:393 -- Submitted deploy config successfully.

serve run命令会阻塞终端,从而允许 Serve 的日志流式传输到控制台。这有助于你轻松测试和调试应用。如果你想更改代码,可以按 Ctrl-C 中断命令并关闭 Serve 及其所有应用,然后重新运行serve run

注意

serve run仅支持运行多应用配置文件。如果你想通过直接传入导入路径来运行应用,serve run一次只能运行一个应用导入路径。

检查状态#

通过运行serve status来检查应用的状态。

$ serve status
proxies:
  2e02a03ad64b3f3810b0dd6c3265c8a00ac36c13b2b0937cbf1ef153: HEALTHY
applications:
  app1:
    status: RUNNING
    message: ''
    last_deployed_time_s: 1693267064.0735464
    deployments:
      downloader:
        status: HEALTHY
        replica_states:
          RUNNING: 1
        message: ''
      ImageClassifier:
        status: HEALTHY
        replica_states:
          RUNNING: 1
        message: ''
  app2:
    status: RUNNING
    message: ''
    last_deployed_time_s: 1693267064.0735464
    deployments:
      Translator:
        status: HEALTHY
        replica_states:
          RUNNING: 1
        message: ''

在应用之间发送请求#

你还可以使用 Serve API serve.get_app_handle获取集群上任何正在运行的 Serve 应用的 handle,从而在应用之间进行调用而无需通过 HTTP。此 handle 可用于直接在应用上执行请求。以上面的分类器和翻译器应用为例。你可以修改ImageClassifier__call__方法,检查 HTTP 请求中的另一个参数,然后向翻译器应用发送请求。

    async def __call__(self, req: starlette.requests.Request):
        req = await req.json()
        result = await self.classify(req["image_url"])

        if req.get("should_translate") is True:
            handle: DeploymentHandle = serve.get_app_handle("app2")
            return await handle.translate.remote(result)

        return result

然后,向分类器应用发送请求,并将should_translate标志设置为 True

bear_url = "https://cdn.britannica.com/41/156441-050-A4424AEC/Grizzly-bear-Jasper-National-Park-Canada-Alberta.jpg"  # noqa
resp = requests.post(
    "http://localhost:8000/classify",
    json={"image_url": bear_url, "should_translate": True},
)

print(resp.text)
# 'Braunbär, Bruin, Ursus arctos'

深入检查#

要更深入地了解集群上运行的应用,请访问 Ray Serve dashboard,地址为 http://localhost:8265/#/serve

你可以看到部署在 Ray 集群上的所有应用

applications

每个应用下的部署列表

deployments

以及每个部署的副本列表

replicas

有关 Ray Serve dashboard 的更多详细信息,请参阅Serve dashboard 文档

添加、删除和更新应用#

你可以添加、删除或更新applications字段下的条目,以在集群中添加、删除或更新应用。这不会影响集群上的其他应用。要更新应用,请修改applications字段下对应条目中的配置选项。

注意

当你重新提交配置时,应用的就地更新行为与单应用的行为相同。有关应用对不同配置更改的反应方式,请参阅更新 Serve 应用

从单应用配置迁移#

将单应用配置ServeApplicationSchema迁移到多应用配置格式ServeDeploySchema非常简单。applications字段下的每个条目都匹配旧的单应用配置格式。要将单应用配置转换为多应用配置格式

  • 将整个旧配置复制到applications字段下的一个条目中。

  • 从该条目中删除hostport,并将它们移动到http_options字段下。

  • 为应用命名。

  • 如果尚未设置,请将应用级别的route_prefix设置为应用中 ingress deployment 的路由前缀。在多应用配置中,应在应用级别设置路由前缀,而不是在每个应用的 ingress deployment 中设置。

  • 如有需要,添加更多应用。

有关多应用配置格式的更多详细信息,请参阅ServeDeploySchema的文档。

注意

你必须从应用条目中删除hostport。在多应用配置中,在单个应用中指定集群级别选项是不适用且不受支持的。