模型服务#

SkyServe 是 SkyPilot 的模型服务库。SkyServe 接受现有的服务框架,并将其部署到一个或多个区域或云上。

为什么选择 SkyServe?

  • 使用任何服务框架 (vLLM, TGI, FastAPI 等),并在多个区域/云上进行扩缩容

  • 通过利用多个/更便宜的位置和硬件(竞价实例),降低成本并提高服务副本的可用性

  • 服务副本的开箱即用负载均衡自动扩缩容

  • 隐私和控制:所有内容都在您的云账户和 VPC 内启动

  • 使用单一控制平面管理多云、多区域部署

工作原理

  • 每个服务都有一个端点,该端点会自动将请求分发给其副本。

  • 同一服务的副本可以在不同区域和云中运行——这可以降低云成本并提高可用性。

  • SkyServe 处理副本的负载均衡、恢复和自动扩缩容。

提示

要开始使用 SkyServe,请使用 SkyPilot 的 nightly build 版本: pip install -U skypilot-nightly

快速导览:LLM 服务#

这是一个提供 LLM 模型服务(在 vLLM 上运行 Mixtral-8x7B-Instruct-v0.1 或在 TGI 上运行 lmsys/vicuna-13b-v1.5)的简单示例

# service.yaml
service:
  readiness_probe: /v1/models
  replicas: 2

# Fields below describe each replica.
resources:
  ports: 8080
  accelerators: {L4:8, A10g:8, A100:4, A100:8, A100-80GB:2, A100-80GB:4, A100-80GB:8}

setup: |
  conda create -n vllm python=3.9 -y
  conda activate vllm
  pip install vllm

run: |
  conda activate vllm
  python -m vllm.entrypoints.openai.api_server \
    --tensor-parallel-size $SKYPILOT_NUM_GPUS_PER_NODE \
    --host 0.0.0.0 --port 8080 \
    --model mistralai/Mixtral-8x7B-Instruct-v0.1
# service.yaml
service:
  readiness_probe: /health
  replicas: 2

# Fields below describe each replica.
resources:
  ports: 8080
  accelerators: A100

run: |
  docker run --gpus all --shm-size 1g -p 8080:80 -v ~/data:/data \
    ghcr.io/huggingface/text-generation-inference \
    --model-id lmsys/vicuna-13b-v1.5

运行 sky serve up service.yaml 以部署具有自动价格和容量优化的服务。部署后,使用 sky serve status 检查服务状态

sky-serve-status-vllm
sky-serve-status-tgi

提示

请注意,这两个副本部署在不同的区域/云中,以实现最低成本和最高 GPU 可用性。这会自动执行,就像常规的 sky launch 一样。

如果看到 STATUS 列变为 READY,则服务已准备好接受流量!

只需 curl 服务端点即可,它会自动在两个副本之间进行负载均衡

$ curl 3.84.15.251:30001/v1/chat/completions \
    -X POST \
    -d '{"model": "mistralai/Mixtral-8x7B-Instruct-v0.1", "messages": [{"role": "user", "content": "Who are you?"}]}' \
    -H 'Content-Type: application/json'

# Example output:
{"id":"cmpl-80b2bfd6f60c4024884c337a7e0d859a","object":"chat.completion","created":1005,"model":"mistralai/Mixtral-8x7B-Instruct-v0.1","choices":[{"index":0,"message":{"role":"assistant","content":" I am a helpful AI assistant designed to provide information, answer questions, and engage in conversation with users. I do not have personal experiences or emotions, but I am programmed to understand and process human language, and to provide helpful and accurate responses."},"finish_reason":"stop"}],"usage":{"prompt_tokens":13,"total_tokens":64,"completion_tokens":51}}
$ curl 44.211.131.51:30001/generate \
    -X POST \
    -d '{"inputs":"What is Deep Learning?","parameters":{"max_new_tokens":20}}' \
    -H 'Content-Type: application/json'

# Example output:
{"generated_text":"\n\nDeep learning is a subset of machine learning that uses artificial neural networks to model and solve"}

教程:Hello, SkyServe!#

这里我们将通过一个示例来演示如何使用 SkyServe 部署一个简单的 HTTP 服务器。要启动一个服务,只需重用您的任务 YAML,并满足以下两个要求

  1. 一个 HTTP 端点(在 run 命令中启动)以及它监听的端口;

  2. 在您的任务 YAML 中添加一个额外的 service 部分来描述服务配置。

建议先使用 sky launch 进行测试。例如,我们有以下任务 YAML 适用于 sky launch

resources:
  ports: 8080
  cpus: 2

workdir: .

run: python -m http.server 8080

在同一个目录下,我们有一个 index.html 文件

<html>
<head>
    <title>My First SkyServe Service</title>
</head>
<body>
    <p>Hello, SkyServe!</p>
</body>
</html>

注意

workdir带有本地文件的文件挂载 将自动上传到 云存储桶。存储桶将在服务终止后创建并清理。

请注意,任务 YAML 中已有一个正在运行的 HTTP 端点,端口为 8080,并通过 resources 部分下的 ports 部分暴露。假设我们想用 SkyServe 将其扩展到多个区域/云中的多个副本。我们只需在 YAML 中添加一个 service 部分

# hello-sky-serve.yaml
service:
  readiness_probe: /
  replicas: 2

resources:
  ports: 8080
  cpus: 2

workdir: .

run: python -m http.server 8080

此示例将启动服务的两个副本,每个副本监听端口 8080。当副本响应 GET / 并返回 200 状态码时,该副本被视为准备就绪。您可以通过在 readiness_probe 字段中指定不同的路径来定制就绪探针。您可以在 服务 YAML 规范 中找到更多配置选项。

使用 sky serve up 启动服务

$ sky serve up hello-sky-serve.yaml

SkyServe 将启动(或重用)一个中心控制器/负载均衡器,并将服务副本部署到价格和可用性最佳的云位置。SkyServe 还会监控服务状态,如果其中一个副本失败,则会重新启动一个新副本。

在底层,sky serve up

  1. 启动一个控制器,负责自动扩缩容、监控和负载均衡;

  2. 返回一个服务端点,该端点将用于接收流量;

  3. 同时,控制器调配副本虚拟机,这些虚拟机稍后运行服务;

  4. 一旦有任何副本准备就绪,发送到服务端点的请求将被分发到其中一个端点副本。

注意

SkyServe 使用最少连接负载均衡(least load load balancing)将流量分发到副本。它跟踪每个副本处理的请求数量,并将下一个请求路由到负载最少的副本。

控制器调配完成后,您将在 sky serve status 输出中看到以下内容

sky-serve-status-output-provisioning

您可以使用 watch 命令监控服务状态

$ watch -n10 sky serve status

一旦任何副本准备好提供服务(READY),您就可以开始向 <endpoint-url> 发送请求(例如,44.201.119.3:30001

$ curl <endpoint-url>
<html>
<head>
    <title>My First SkyServe Service</title>
</head>
<body>
    <p>Hello, SkyServe!</p>
</body>
</html>

教程:部署聊天机器人 LLM 服务!#

让我们使用 FastChat + Vicuna 部署一个真正的 LLM 聊天服务。我们将使用 Vicuna OpenAI API Endpoint YAML 作为示例

resources:
  ports: 8080
  accelerators: A100:1
  disk_size: 1024
  disk_tier: best

setup: |
  conda activate chatbot
  if [ $? -ne 0 ]; then
    conda create -n chatbot python=3.9 -y
    conda activate chatbot
  fi

  # Install dependencies
  pip install "fschat[model_worker,webui]==0.2.24"
  pip install protobuf

run: |
  conda activate chatbot

  echo 'Starting controller...'
  python -u -m fastchat.serve.controller --host 127.0.0.1 > ~/controller.log 2>&1 &
  sleep 10
  echo 'Starting model worker...'
  python -u -m fastchat.serve.model_worker \
            --model-path lmsys/vicuna-${MODEL_SIZE}b-v1.3 2>&1 \
            --host 127.0.0.1 \
            | tee model_worker.log &

  echo 'Waiting for model worker to start...'
  while ! `cat model_worker.log | grep -q 'Uvicorn running on'`; do sleep 1; done

  echo 'Starting openai api server...'
  python -u -m fastchat.serve.openai_api_server \
    --host 0.0.0.0 --port 8080 | tee ~/openai_api_server.log

envs:
  MODEL_SIZE: 7

上述 SkyPilot 任务 YAML 将使用 Vicuna 7B 模型启动一个 OpenAI API 端点。此 YAML 可用于常规的 sky launch 命令,以启动服务的单个副本。

然而,通过在 YAML 中添加一个 service 部分,我们可以将其扩展到多个区域/云中的多个副本

# vicuna.yaml
service:
  readiness_probe: /v1/models
  replicas: 3

resources:
  ports: 8080
  # Here goes other resources config

# Here goes other task config

现在我们有了可用于 SkyServe 的服务 YAML!只需运行

$ sky serve up vicuna.yaml -n vicuna

来部署服务(使用 -n 为您的服务命名!)。一段时间后,将有一个与 OpenAI 兼容的 API 端点准备好接受流量(以下示例中为 44.201.113.28:30001

sky-serve-status-vicuna-ready

使用以下 cURL 命令发送请求

$ curl http://<endpoint-url>/v1/chat/completions \
    -X POST \
    -d '{"model":"vicuna-7b-v1.3","messages":[{"role":"system","content":"You are a helpful assistant."},{"role":"user","content":"Who are you?"}],"temperature":0}' \
    -H 'Content-Type: application/json'

# Example output:
{"id":"chatcmpl-gZ8SfgUwcm9Xjbuv4xfefq","object":"chat.completion","created":1702082533,"model":"vicuna-7b-v1.3","choices":[{"index":0,"message":{"role":"assistant","content":"I am Vicuna, a language model trained by researchers from Large Model Systems Organization (LMSYS)."},"finish_reason":"stop"}],"usage":{"prompt_tokens":19,"total_tokens":43,"completion_tokens":24}}

您也可以使用简单的聊天机器人 Python 脚本发送请求

import openai

stream = True
model = 'vicuna-7b-v1.3' # This is aligned with the MODEL_SIZE env in the YAML
init_prompt = 'You are a helpful assistant.'
history = [{'role': 'system', 'content': init_prompt}]
endpoint = input('Endpoint: ')
openai.api_base = f'http://{endpoint}/v1'
openai.api_key = 'placeholder'

try:
    while True:
        user_input = input('[User] ')
        history.append({'role': 'user', 'content': user_input})
        resp = openai.ChatCompletion.create(model=model,
                                            messages=history,
                                            stream=True)
        print('[Chatbot]', end='', flush=True)
        tot = ''
        for i in resp:
            dlt = i['choices'][0]['delta']
            if 'content' not in dlt:
                continue
            print(dlt['content'], end='', flush=True)
            tot += dlt['content']
        print()
        history.append({'role': 'assistant', 'content': tot})
except KeyboardInterrupt:
    print('\nBye!')

常用 CLI 命令#

以下是一些 SkyServe 命令。请查阅 sky serve --help 以获取更多详情。

查看所有正在运行的服务

$ sky serve status
sky-serve-status-full

流式传输服务的日志

$ sky serve logs vicuna 1 # tail logs of replica 1, including provisioning and running logs
$ sky serve logs vicuna --controller # tail controller logs
$ sky serve logs vicuna --load-balancer --no-follow # print the load balancer logs so far, and exit

终止服务

$ sky serve down http-server # terminate the http-server service
$ sky serve down --all # terminate all services

自动扩缩容#

有关更多信息,请参阅 自动扩缩容

授权#

有关更多信息,请参阅 授权

SkyServe 架构#

SkyServe Architecture

SkyServe 有一个中心控制器虚拟机,用于管理您的服务部署。每个服务将有一个进程组来管理其副本并向其路由流量。

它由以下组件组成

  1. 控制器:控制器会监控副本的状态,如果其中一个副本失败,则会重新启动一个新副本。如果设置了自动扩缩容配置,它还会自动扩缩副本数量(更多信息请参阅 服务 YAML 规范)。

  2. 负载均衡器:负载均衡器会将流量路由到所有准备就绪的副本。它是一个轻量级的 HTTP 服务器,监听服务端点并将请求分发到其中一个副本。

所有进程组共享一个控制器虚拟机。控制器虚拟机将在具有最佳性价比的云中启动。您也可以根据需要定制控制器资源

SkyServe 控制器#

SkyServe 控制器是一个运行在云中的小型按需 CPU 虚拟机,它负责

  1. 管理您的服务部署;

  2. 监控您的服务状态;

  3. 将流量路由到您的服务副本。

当部署第一个服务时,它会自动启动,并在空闲 10 分钟后自动停止(即所有服务终止后)。因此,管理其生命周期无需用户干预

您可以使用 sky status 查看控制器,并使用 -r/--refresh 标志刷新其状态。

定制 SkyServe 控制器资源#

您可能出于以下几个原因想要定制 SkyServe 控制器的资源

  1. 使用成本较低的控制器。(如果运行的服务较少)

  2. 强制控制器在特定位置运行。当您希望服务端点位于特定地理区域内时,这特别有用。(默认:最便宜的位置)

  3. 更改可并发运行的最大服务数量,该数量是控制器 vCPU 数量的 4 倍与控制器内存(以 GiB 为单位)之间的最小值。(默认:16)

  4. 更改控制器的 disk_size 以存储更多日志。(默认:200GB)

要实现上述目的,您可以在 ~/.sky/config.yaml 文件中指定自定义配置,使用以下字段

serve:
  # NOTE: these settings only take effect for a new SkyServe controller, not if
  # you have an existing one.
  controller:
    # Enable high availability mode for the controller (optional).
    #
    # When set to true, the controller will be deployed with high availability
    # capabilities on Kubernetes using Deployments. This allows the controller and load balancer
    # to automatically recover from failures (e.g., node failures, pod crashes)
    # and maintain service continuity.
    #
    # NOTE: This feature is ONLY supported when Kubernetes cloud is enabled. To enable kubernetes, see :ref:`Kubernetes Setup <kubernetes-setup>`.
    # The service controller cluster will be scheduled to kubernetes. The k8s deployment needs to be always-on to keep the controller running. If using a local kubernetes deployment (e.g. `sky local up`), keeping the laptop/machines up is required.
    # When enabled:
    # - The controller is deployed as a Kubernetes Deployment instead of a Pod
    # - Automatic pod rescheduling and recovery is handled by Kubernetes
    #
    # Default: false.
    high_availability: true

    resources:
      # All configs below are optional.
      # Specify the location of the SkyServe controller.
      cloud: gcp
      region: us-central1
      # Specify the maximum number of services that can be run concurrently.
      cpus: 2+  # number of vCPUs, max concurrent services = min(4 * cpus, memory in GiB)
      # Specify the disk_size in GB of the SkyServe controller.
      disk_size: 1024

resources 字段与正常的 SkyPilot 作业具有相同的规范;请参阅此处

注意

如果您已有控制器(无论是已停止还是正在运行),这些设置将不会生效。要使其生效,请先拆除现有控制器,这需要终止所有服务。