副本调度#
本指南解释了 Ray Serve 如何在集群中调度部署副本,以及可用于控制放置行为的 API 和环境变量。
快速参考:选择正确的方法#
目标 |
解决方案 |
示例 |
|---|---|---|
多 GPU 推理,具有张量并行 |
|
vLLM,带有 |
定位特定 GPU 类型或区域 |
|
仅在 A100 节点上调度 |
限制每个节点上的副本数,以提高可用性 |
|
每个节点上每个部署最多 2 个副本 |
通过打包节点降低云成本 |
|
许多小型模型共享节点 |
为工作节点预留资源 |
|
副本生成 Ray 数据工作节点 |
跨节点分片大型嵌入 |
|
推荐模型,带有分布式嵌入表 |
简单部署,无特殊需求 |
默认(仅 |
单 GPU 模型 |
副本调度工作原理#
部署应用程序时,Ray Serve 的部署调度器会决定将每个副本 actor 放置在 Ray 集群的可用节点上。调度器运行在 Serve Controller 上,并在每个更新周期做出批量调度决策。有关配置副本的 CPU、GPU 和其他资源需求的信息,请参阅 资源分配。
┌──────────────────────────────────┐
│ serve.run(app) │
└────────────────┬─────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Serve Controller │
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
│ │ Deployment Scheduler │ │
│ │ │ │
│ │ 1. Check placement_group_bundles ──▶ PlacementGroupSchedulingStrategy │ │
│ │ 2. Check target node affinity ──▶ NodeAffinitySchedulingStrategy │ │
│ │ 3. Use default strategy ──▶ SPREAD (default) or PACK │ │
│ └───────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────┴─────────────────────────────────┐
│ │
▼ ▼
┌─────────────────────────────────────┐ ┌─────────────────────────────────────┐
│ SPREAD Strategy (default) │ │ PACK Strategy │
│ │ │ │
│ Distributes replicas across nodes │ │ Packs replicas onto fewer nodes │
│ for fault tolerance │ │ to minimize resource waste │
│ │ │ │
│ ┌─────────┐ ┌─────────┐ ┌───────┐ │ │ ┌─────────┐ ┌─────────┐ ┌───────┐ │
│ │ Node 1 │ │ Node 2 │ │Node 3 │ │ │ │ Node 1 │ │ Node 2 │ │Node 3 │ │
│ │ ┌─────┐ │ │ ┌─────┐ │ │┌─────┐│ │ │ │ ┌─────┐ │ │ │ │ │ │
│ │ │ R1 │ │ │ │ R2 │ │ ││ R3 ││ │ │ │ │ R1 │ │ │ idle │ │ idle │ │
│ │ └─────┘ │ │ └─────┘ │ │└─────┘│ │ │ │ │ R2 │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │ │ R3 │ │ │ │ │ │ │
│ └─────────┘ └─────────┘ └───────┘ │ │ └─────────┘ └─────────┘ └───────┘ │
│ │ │ ▲ ▲ │
│ ✓ High availability │ │ └───────────┘ │
│ ✓ Load balanced │ │ Can be released │
│ ✓ Reduced contention │ │ ✓ Fewer nodes = lower cloud costs │
└─────────────────────────────────────┘ └────────────────────────────────────┘
默认情况下,Ray Serve 使用 **分散调度策略**,尽力将副本分散到各个节点。这种方法
最大化容错性,避免副本集中在单个节点上
平衡集群负载
有助于防止副本之间的资源争用
调度优先级#
调度副本时,调度器按以下优先级顺序评估策略:
放置组:如果您指定了
placement_group_bundles,调度器将使用PlacementGroupSchedulingStrategy将副本与其所需资源共置。打包调度与节点亲和性:如果启用了打包调度,调度器会通过优先选择非空闲节点(已运行副本的节点)并使用最佳拟合算法来最小化资源碎片,从而识别最佳可用节点。然后,它使用带有软约束的
NodeAffinitySchedulingStrategy将副本调度到该节点上。默认策略:当未启用打包调度时,回退到
SPREAD。
缩减行为#
当 Ray Serve 缩减部署时,它会智能地选择要停止的副本。
优先停止非运行副本:待定、启动中或恢复中的副本在运行副本之前被停止。
最小化节点数:从所有部署中总副本数最少的节点停止运行副本,有助于更快地释放节点。在同一节点上的副本之间,新副本比旧副本优先停止。
主节点保护:主节点上的副本优先级最低,因为主节点无法被释放。在主节点上的副本之间,新副本比旧副本优先停止。
注意
不建议在生产部署中使用主节点上的运行副本。主节点运行 GCS 和 Serve Controller 等关键集群进程,副本工作负载可能会争夺资源。
控制副本放置的 API#
Ray Serve 提供了多种选项来控制副本的调度位置。这些参数通过 @serve.deployment 装饰器进行配置。有关完整的 API 参考,请参阅 部署装饰器文档。
使用 max_replicas_per_node 限制每个节点上的副本数#
使用 max_replicas_per_node 来限制可以运行在单个节点上的部署副本数量。这在以下情况很有用:
您想通过将副本分散到不同节点来确保高可用性
您想避免同一部署的副本之间发生资源争用
from ray import serve
@serve.deployment(num_replicas=6, max_replicas_per_node=2, ray_actor_options={"num_cpus": 0.1})
class MyDeployment:
def __call__(self, request):
return "Hello!"
app = MyDeployment.bind()
在此示例中,如果您有 6 个副本且 max_replicas_per_node=2,Ray Serve 则需要至少 3 个节点来调度所有副本。
注意
max_replicas_per_node 的有效值为 None(默认,无限制)或整数。您不能同时设置 max_replicas_per_node 和 placement_group_bundles。
您也可以在配置文件中指定
applications:
- name: my_app
import_path: my_module:app
deployments:
- name: MyDeployment
num_replicas: 6
max_replicas_per_node: 2
使用放置组预留资源#
有关放置组策略的更多详细信息,请参阅 Ray Core 放置组文档。
**放置组** 是 Ray 的一个原始组件,它在集群的一个或多个节点上预留一组资源(称为 **捆绑包**)。当您为 Ray Serve 部署配置 placement_group_bundles 时,Ray 会为 **每个副本** 创建一个专用的放置组,确保这些资源得到预留并可供该副本使用。
**捆绑包** 是一个指定资源需求的字典,例如 {"CPU": 2, "GPU": 1}。当您定义多个捆绑包时,您是在告诉 Ray 预留多组资源,这些资源可以根据您选择的策略进行放置。
放置组和捆绑包的含义#
下图说明了具有 placement_group_bundles=[{"GPU": 1}, {"GPU": 1}, {"CPU": 4}] 并且 placement_group_strategy 设置为 "STRICT_PACK" 的部署是如何调度的。
┌─────────────────────────────────────────────────────────────────────────────┐
│ Node (8 CPUs, 4 GPUs) │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ Placement Group (per replica) │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │ │
│ │ │ Bundle 0 │ │ Bundle 1 │ │ Bundle 2 │ │ │
│ │ │ {"GPU": 1} │ │ {"GPU": 1} │ │ {"CPU": 4} │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────────┐ │ │ │
│ │ │ │ Replica │ │ │ │ Worker │ │ │ │ Worker Tasks │ │ │ │
│ │ │ │ Actor │ │ │ │ Actor │ │ │ │ (preprocessing)│ │ │ │
│ │ │ │ (main GPU) │ │ │ │ (2nd GPU) │ │ │ │ │ │ │ │
│ │ │ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────────┘ │ │ │
│ │ └─────────────────┘ └─────────────────┘ └─────────────────────┘ │ │
│ │ ▲ │ │
│ │ │ │ │
│ │ Replica runs in │ │
│ │ first bundle │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
With STRICT_PACK: All bundles guaranteed on same node
考虑一个部署,其 placement_group_bundles=[{"GPU": 1}, {"GPU": 1}, {"CPU": 4}]
Ray 为每个副本预留 3 个捆绑包的资源。
副本 actor 运行在 **第一个捆绑包** 中(因此
ray_actor_options必须适合其中)。其余捆绑包可用于副本生成的 worker actor/任务。
所有子 actor 和任务都会自动调度到放置组内。
这与仅在 ray_actor_options 中请求资源不同。使用 ray_actor_options={"num_gpus": 2},您的副本 actor 会获得 2 个 GPU,但您无法控制其他工作进程的运行位置。使用放置组,您可以为副本及其工作进程明确预留资源。
何时使用放置组#
场景 |
放置组如何提供帮助 |
|---|---|
模型并行 |
张量并行或流水线并行需要多个 GPU,这些 GPU 必须进行高效通信。使用 |
副本生成工作节点 |
您的部署会生成 Ray actor 或任务进行并行处理。放置组为这些工作节点预留资源。例如,一个视频处理服务,它生成 Ray 任务来并行解码帧;或者一个批处理推理服务,它使用 Ray Data 在模型推理之前预处理输入。 |
跨节点分发 |
您需要将捆绑包分发到不同的节点。使用 |
不应使用放置组的情况:
您的副本是自包含的,并且不生成额外的 actor/任务。
您只需要简单的资源需求(请改用
ray_actor_options)。您想使用
max_replicas_per_node。这两个选项的组合目前不受支持。
注意
max_replicas_per_node 的工作原理: Ray Serve 为每个部署创建一个合成自定义资源。每个节点隐含地拥有此资源的 1.0。每个副本请求此资源的 1.0 / max_replicas_per_node。例如,当 max_replicas_per_node=3 时,每个副本请求约 0.33 的资源,因此在资源耗尽之前,一个节点最多只能容纳 3 个副本。此机制依赖于 Ray 的标准资源调度,这与放置组调度相冲突。
配置放置组#
以下示例使用严格打包策略为每个副本预留 2 个 GPU。
from ray import serve
@serve.deployment(
ray_actor_options={"num_cpus": 0.1},
placement_group_bundles=[{"CPU": 0.1}, {"CPU": 0.1}],
placement_group_strategy="STRICT_PACK",
)
class MultiCPUModel:
def __call__(self, request):
return "Processed with 2 CPUs"
multi_cpu_app = MultiCPUModel.bind()
副本 actor 调度在第一个捆绑包中,因此 ray_actor_options 中指定的资源必须是第一个捆绑包资源的子集。副本创建的所有 actor 和任务默认都调度在放置组内(placement_group_capture_child_tasks=True)。
定位具有自定义资源的节点#
您可以在 ray_actor_options 中使用自定义资源来定位副本到特定节点。这是控制哪些节点运行副本的推荐方法。
然后配置您的部署以要求特定资源:
from ray import serve
# Schedule only on nodes with A100 GPUs
@serve.deployment(ray_actor_options={"resources": {"A100": 1}})
class A100Model:
def __call__(self, request):
return "Running on A100"
# Schedule only on nodes with T4 GPUs
@serve.deployment(ray_actor_options={"resources": {"T4": 1}})
class T4Model:
def __call__(self, request):
return "Running on T4"
a100_app = A100Model.bind()
t4_app = T4Model.bind()
首先,使用识别其功能的自定义资源启动 Ray 节点:
if __name__ == "__main__":
ray.init(
resources={
"A100": 1,
"T4": 1,
}
)
serve.run(a100_app, name="a100", route_prefix="/a100")
serve.run(t4_app, name="t4", route_prefix="/t4")
serve.shutdown()
ray.shutdown()
自定义资源为 Ray Serve 部署提供了多项优势:
可量化:您可以请求特定数量(例如,2 个 GPU 的
{"A100": 2},或 2 个副本共享 GPU 的{"A100": 0.5}),而标签是二元的(存在或不存在)。自动缩放器感知:Ray 自动缩放器了解自定义资源,并可以自动提供具有所需资源的节点。
调度保证:在具有所需自定义资源的节点可用之前,不会调度副本,从而防止放置在不兼容的节点上。
提示
使用描述性的资源名称,反映节点的特性,例如 GPU 类型、可用区域或硬件代际。
环境变量#
这些环境变量会修改 Ray Serve 的调度行为。在启动 Ray 之前设置它们。
RAY_SERVE_USE_PACK_SCHEDULING_STRATEGY#
默认:0(禁用)
启用后,将从分散调度切换到 **打包调度**。打包调度
将副本打包到更少的节点上,以最小化资源碎片。
按资源需求对待定副本进行排序(最大的优先)。
优先在已包含副本的节点上进行调度(非空闲节点)。
使用最佳拟合装箱算法找到每个副本的最佳节点。
export RAY_SERVE_USE_PACK_SCHEDULING_STRATEGY=1
ray start --head
何时使用打包调度: 当您运行许多小型部署(例如,每个部署需要 0.5 CPU 的 10 个模型)时,分散调度会将它们散布到各个节点,浪费容量。打包调度在利用新节点之前高效地填满节点。云提供商按节点小时计费。将副本打包到更少的节点上,可以让自动缩放器释放空闲节点,直接降低您的账单。
何时避免打包调度: 高可用性至关重要,并且您希望副本分散到各个节点。
注意
当任何部署使用具有 PACK、SPREAD 或 STRICT_SPREAD 策略的放置组时,打包调度会自动回退到分散调度。发生这种情况是因为打包调度需要预测资源将在哪里消耗才能有效装箱。对于 STRICT_PACK,所有捆绑包都保证落在同一个节点上,从而使资源消耗可预测。对于其他策略,捆绑包可能会以不可预测的方式分布到多个节点上,因此调度器无法准确跟踪每个节点上的可用资源。
RAY_SERVE_HIGH_PRIORITY_CUSTOM_RESOURCES#
默认:空
逗号分隔的自定义资源名称列表,在打包调度排序副本时应优先考虑。列表中较早的资源具有更高的优先级。
export RAY_SERVE_HIGH_PRIORITY_CUSTOM_RESOURCES="TPU,custom_accelerator"
ray start --head
当打包调度按资源需求对副本进行排序时,优先级顺序为:
在
RAY_SERVE_HIGH_PRIORITY_CUSTOM_RESOURCES中的自定义资源(按顺序)GPU
CPU
内存
其他自定义资源
这确保了需要高优先级资源的副本首先被调度,从而降低了资源碎片化的可能性。