资源#

Ray 允许您在不更改代码的情况下,将应用程序从笔记本电脑无缝扩展到集群。Ray 资源是实现这一功能的核心。它们抽象了物理机器,让您能够根据资源来表达计算,而系统则根据资源请求来管理调度和自动扩展。

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

物理资源与逻辑资源#

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

Ray 资源是逻辑的,不需要与物理资源一一对应。例如,您可以通过 ray start --head --num-cpus=0 来启动一个逻辑 CPU 为 0 的 Ray head 节点,即使它物理上拥有八个 CPU (这会指示 Ray 调度器不要在 head 节点上调度任何需要逻辑 CPU 资源的任务或 actor,主要是为了保留 head 节点运行 Ray 系统进程)。它们主要用于调度过程中的准入控制。

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

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

  • Ray 不为任务或 actor 提供 CPU 隔离。例如,Ray 不会独占一个物理 CPU 并将其固定给一个 num_cpus=1 的任务。Ray 会让操作系统来调度和运行该任务。如果需要,您可以使用操作系统 API,如 sched_setaffinity,将任务固定到物理 CPU。

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

注意

如果通过 ray.remote()task.options()/actor.options() 为任务/actor 设置了 num_cpus,Ray 会设置环境变量 OMP_NUM_THREADS=<num_cpus>。如果未指定 num_cpus,Ray 会设置 OMP_NUM_THREADS=1 (这是为了避免多个 worker 导致性能下降 (issue #6998))。您也可以通过显式设置 OMP_NUM_THREADS 来覆盖 Ray 的默认设置。 OMP_NUM_THREADS 通常用于 numpy、PyTorch 和 Tensorflow 进行多线程线性代数运算。在多 worker 设置中,我们希望每个 worker 只有一个线程,而不是每个 worker 有多个线程,以避免争用。其他一些库可能有自己的并行配置方式。例如,如果您使用的是 OpenCV,您应该使用 cv2.setNumThreads(num_threads) 手动设置线程数 (设置为 0 可禁用多线程)。

../../_images/physical_resources_vs_logical_resources.svg

物理资源与逻辑资源#

自定义资源#

您可以为 Ray 节点指定自定义资源,并引用它们来控制任务或 actor 的调度。

当您需要使用数值管理调度时,请使用自定义资源。如果您需要简单的基于标签的调度,请使用标签。请参阅使用标签控制调度

指定节点资源#

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

警告

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

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

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

  • 内存 (memory):设置为 Ray runtime 启动时“可用内存”的 70%。

  • 对象存储内存 (object_store_memory):设置为 Ray runtime 启动时“可用内存”的 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}"'

指定任务或 actor 的资源需求#

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

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

您还可以通过 ray.remote()task.options()/actor.options() 显式指定任务或 actor 的逻辑资源需求(例如,一个任务可能需要一个 GPU),而不是使用默认值。

# 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();

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

分数资源需求#

Ray 支持分数资源需求。例如,如果您的任务或 actor 是 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 是无效的。

提示

除了资源需求,您还可以为任务或 actor 指定运行环境,其中可以包括 Python 包、本地文件、环境变量等。有关详细信息,请参阅运行时环境