部署多个应用程序#

Serve 支持部署多个独立的 Serve 应用程序。本用户指南将介绍如何生成多应用程序配置文件,并使用 Serve CLI 进行部署,以及使用 CLI 和 Ray Serve 仪表板监控您的应用程序。

上下文#

背景#

随着多应用 Serve 的引入,我们将带您了解应用的新概念,并指导您何时应该选择在每个集群中部署单个应用或多应用。

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

何时使用多个应用程序#

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

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

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

入门#

定义一个 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.options(route_prefix="/classify").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.options(route_prefix="/translate").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 应用程序的句柄,从而在应用程序之间进行调用,而无需通过 HTTP。这个句柄可以用来直接在应用程序上执行请求。以分类器和翻译器应用为例。你可以修改 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 仪表板:http://localhost:8265/#/serve

你可以查看部署在 Ray 集群上的所有应用程序:

应用程序

每个应用程序下的部署列表:

部署

以及每个部署的副本列表:

副本

有关 Ray Serve 仪表盘的更多详情,请参阅 Serve 仪表盘文档

添加、删除和更新应用程序#

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

备注

当你重新提交配置时,应用程序的就地更新行为与单个应用程序的行为相同。有关应用程序如何响应不同配置更改的信息,请参见更新Serve应用程序

从单应用程序配置迁移#

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

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

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

  • 命名应用程序。

  • 如果还没有设置,请将应用级别的 route_prefix 设置为应用中入口部署的路由前缀。在多应用配置中,您应该在应用级别设置路由前缀,而不是为每个应用中的入口部署设置。

  • 必要时,添加更多应用程序。

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

备注

你必须从应用程序入口中移除 hostport。在多应用程序配置中,在单个应用程序内指定集群级别选项是不适用的,也不被支持。