资源#

Ray 允许你无缝地将应用程序从笔记本电脑扩展到集群,而无需更改代码。Ray 资源 是这一能力的关键。它们抽象了物理机器,让你可以根据资源表达计算,而系统则根据资源请求管理调度和自动扩展。

Ray 中的资源是一个键值对,其中键表示资源名称,值是一个浮点数。为了方便,Ray 原生支持 CPU、GPU 和内存资源类型;CPU、GPU 和内存被称为 预定义资源。除此之外,Ray 还支持 自定义资源

物理资源和逻辑资源#

物理资源是机器实际拥有的资源,如物理CPU和GPU,而逻辑资源是由系统定义的虚拟资源。

Ray 资源是 逻辑 的,不需要与物理资源有一对一的映射。例如,即使物理上有八个CPU,你也可以通过 ray start --head --num-cpus=0 启动一个逻辑CPU为0的Ray头节点(这向Ray调度器发出信号,不在头节点上调度任何需要逻辑CPU资源的任务或角色,主要是为了保留头节点用于运行Ray系统进程。)。它们主要用于调度期间的准入控制。

资源是逻辑的这个事实有几个含义:

  • 任务或角色的资源需求并不限制实际的物理资源使用。例如,Ray 不会阻止一个 num_cpus=1 的任务启动多个线程并使用多个物理CPU。确保任务或角色使用的资源不超过通过资源需求指定的数量是您的责任。

  • Ray 不为任务或角色提供 CPU 隔离。例如,Ray 不会专门保留一个物理 CPU 并将 num_cpus=1 任务固定到该 CPU 上。Ray 会让操作系统来调度并运行该任务。如果需要,您可以使用操作系统 API 如 sched_setaffinity 来将任务固定到物理 CPU 上。

  • Ray 确实提供了 GPU 隔离,形式为 可见设备,通过自动设置 CUDA_VISIBLE_DEVICES 环境变量,大多数机器学习框架会尊重此设置以进行GPU分配。

../../_images/physical_resources_vs_logical_resources.svg

物理资源 vs 逻辑资源#

自定义资源#

除了预定义的资源外,您还可以指定 Ray 节点的自定义资源,并在任务或角色中请求它们。自定义资源的一些用例包括:

  • 您的节点具有特殊硬件,您可以将其表示为自定义资源。然后,您的任务或角色可以通过 @ray.remote(resources={"special_hardware": 1}) 请求自定义资源,Ray 将把任务或角色调度到具有该自定义资源的节点。

  • 你可以使用自定义资源作为标签来标记节点,并且可以实现基于标签的亲和调度。例如,你可以通过 ray.remote(resources={"custom_label": 0.001}) 将任务或角色调度到具有 custom_label 自定义资源的节点上。对于这种情况,实际数量并不重要,惯例是指定一个极小的数字,以确保标签资源不会成为并行性的限制因素。

指定节点资源#

默认情况下,Ray 节点启动时会预定义 CPU、GPU 和内存资源。每个节点上的这些逻辑资源的数量设置为 Ray 自动检测到的物理数量。默认情况下,逻辑资源按以下规则配置。

警告

Ray 不允许在节点上启动Ray后动态更新资源容量

  • 逻辑CPU数量 (``num_cpus``): 设置为机器/容器的CPU数量。

  • 逻辑GPU数量 (``num_gpus``):设置为机器/容器的GPU数量。

  • 内存 (``memory``):当 ray 运行时启动时,设置为“可用内存”的 70%。

  • 对象存储内存 (``object_store_memory``):当 ray 运行时启动时,设置为“可用内存”的 30%。请注意,对象存储内存不是逻辑资源,用户不能将其用于调度。

然而,您总是可以通过手动指定预定义资源的数量并添加自定义资源来覆盖它。根据您启动Ray集群的方式,有几种方法可以做到这一点:

如果你使用 ray.init() 来启动一个单节点 Ray 集群,你可以按照以下方式手动指定节点资源:

# This will start a Ray node with 3 logical cpus, 4 logical gpus,
# 1 special_hardware resource and 1 custom_label resource.
ray.init(num_cpus=3, num_gpus=4, resources={"special_hardware": 1, "custom_label": 1})

如果你使用 ray start 来启动一个 Ray 节点,你可以运行:

ray start --head --num-cpus=3 --num-gpus=4 --resources='{"special_hardware": 1, "custom_label": 1}'

如果你使用 ray up 来启动一个 Ray 集群,你可以在 yaml 文件中设置 resources 字段

available_node_types:
  head:
    ...
    resources:
      CPU: 3
      GPU: 4
      special_hardware: 1
      custom_label: 1

如果你使用 KubeRay 来启动一个 Ray 集群,你可以在 yaml 文件中设置 rayStartParams 字段

headGroupSpec:
  rayStartParams:
    num-cpus: "3"
    num-gpus: "4"
    resources: '"{\"special_hardware\": 1, \"custom_label\": 1}"'

指定任务或角色资源需求#

Ray 允许指定任务或角色的逻辑资源需求(例如,CPU、GPU 和自定义资源)。任务或角色只有在节点上有足够的所需逻辑资源可用时才会运行。

默认情况下,Ray 任务使用 1 个逻辑 CPU 资源进行调度,Ray 角色使用 1 个逻辑 CPU 进行调度,运行时使用 0 个逻辑 CPU。(这意味着,默认情况下,角色不能在零 CPU 节点上调度,但可以在任何非零 CPU 节点上运行无限数量的角色。角色的默认资源需求是出于历史原因选择的。建议始终为角色显式设置 num_cpus 以避免任何意外。如果资源被显式指定,它们在调度和执行时都是必需的。)

你也可以明确指定任务或角色的逻辑资源需求(例如,一个任务可能需要GPU),而不是通过 ray.remote()task.options()/actor.options() 使用默认的资源。

# Specify the default resource requirements for this remote function.
@ray.remote(num_cpus=2, num_gpus=2, resources={"special_hardware": 1})
def func():
    return 1


# You can override the default resource requirements.
func.options(num_cpus=3, num_gpus=1, resources={"special_hardware": 0}).remote()


@ray.remote(num_cpus=0, num_gpus=1)
class Actor:
    pass


# You can override the default resource requirements for actors as well.
actor = Actor.options(num_cpus=1, num_gpus=0).remote()
// Specify required resources.
Ray.task(MyRayApp::myFunction).setResource("CPU", 1.0).setResource("GPU", 1.0).setResource("special_hardware", 1.0).remote();

Ray.actor(Counter::new).setResource("CPU", 2.0).setResource("GPU", 1.0).remote();
// Specify required resources.
ray::Task(MyFunction).SetResource("CPU", 1.0).SetResource("GPU", 1.0).SetResource("special_hardware", 1.0).Remote();

ray::Actor(CreateCounter).SetResource("CPU", 2.0).SetResource("GPU", 1.0).Remote();

任务和执行者资源需求对 Ray 的调度并发性有影响。特别是,在给定节点上所有并发执行的任务和执行者的逻辑资源需求总和不能超过该节点的总逻辑资源。这一特性可以用来 限制并发运行的任务或执行者的数量,以避免诸如 OOM 等问题

分数资源需求#

Ray 支持分数资源需求。例如,如果你的任务或角色是IO绑定的且CPU使用率低,你可以指定分数CPU num_cpus=0.5 甚至零CPU num_cpus=0。分数资源需求的精度为0.0001,因此你应该避免指定超出该精度的双精度数。

@ray.remote(num_cpus=0.5)
def io_bound_task():
    import time

    time.sleep(1)
    return 2


io_bound_task.remote()


@ray.remote(num_gpus=0.5)
class IOActor:
    def ping(self):
        import os

        print(f"CUDA_VISIBLE_DEVICES: {os.environ['CUDA_VISIBLE_DEVICES']}")


# Two actors can share the same GPU.
io_actor1 = IOActor.remote()
io_actor2 = IOActor.remote()
ray.get(io_actor1.ping.remote())
ray.get(io_actor2.ping.remote())
# Output:
# (IOActor pid=96328) CUDA_VISIBLE_DEVICES: 1
# (IOActor pid=96329) CUDA_VISIBLE_DEVICES: 1

备注

GPU、TPU 和 neuron_cores 资源需求大于 1 的,需要是整数。例如,num_gpus=1.5 是无效的。

小技巧

除了资源需求,您还可以为任务或角色指定运行环境,这可以包括Python包、本地文件、环境变量等。详情请参阅 运行时环境