测试 Ray 程序的技巧#

由于并行程序的特性,测试 Ray 程序可能有点棘手。我们整理了一份关于 Ray 程序常见测试实践的技巧和窍门列表。

技巧 1:使用 ray.init(num_cpus=...) 固定资源数量#

默认情况下,ray.init() 会检测本地机器/集群上的 CPU 和 GPU 数量。

然而,您的测试环境可能拥有显著更少的资源。例如,TravisCI 构建环境只有 2 个核心

如果测试的编写依赖于 ray.init(),它们可能被隐式地编写成依赖于更大的多核机器。

这很容易导致测试出现意外的、不稳定的或错误的、难以重现的行为。

为了克服这个问题,您应该通过在 ray.init 中设置资源来覆盖检测到的资源,例如: ray.init(num_cpus=2)

技巧 2:如果可能,跨测试共享 Ray 集群#

最安全的方法是为每个测试启动一个新的 Ray 集群。

import unittest

class RayTest(unittest.TestCase):
    def setUp(self):
        ray.init(num_cpus=4, num_gpus=0)

    def tearDown(self):
        ray.shutdown()

然而,启动和停止 Ray 集群实际上会产生不少延迟。例如,在典型的 Macbook Pro 笔记本电脑上,启动和停止可能需要近 5 秒钟。

python -c 'import ray; ray.init(); ray.shutdown()'  3.93s user 1.23s system 116% cpu 4.420 total

对于 20 个测试,这会增加 90 秒的额外开销。

跨测试重用 Ray 集群可以显著加快您的测试套件的速度。这会将开销减少到一个恒定的、平均化的数量。

class RayClassTest(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        # Start it once for the entire test suite/module
        ray.init(num_cpus=4, num_gpus=0)

    @classmethod
    def tearDownClass(cls):
        ray.shutdown()

根据您的应用程序,在某些情况下跨测试重用 Ray 集群可能不安全。例如:

  1. 如果您的应用程序依赖于为每个进程设置环境变量。

  2. 如果您的远程 Actor/任务设置了任何进程级别的全局变量。

技巧 3:使用 ray.cluster_utils.Cluster 创建一个迷你集群#

如果您正在为集群环境编写应用程序,您可能希望模拟一个多节点 Ray 集群。这可以使用 ray.cluster_utils.Cluster 工具来实现。

注意

在 Windows 上,对多节点 Ray 集群的支持目前是实验性的且未经测试。如果您遇到问题,请在 ray-project/ray#issues 上提交报告。

from ray.cluster_utils import Cluster

# Starts a head-node for the cluster.
cluster = Cluster(
    initialize_head=True,
    head_node_args={
        "num_cpus": 10,
    })

启动集群后,您可以在同一个进程中执行典型的 Ray 脚本。

import ray

ray.init(address=cluster.address)

@ray.remote
def f(x):
    return x

for _ in range(1):
    ray.get([f.remote(1) for _ in range(1000)])

for _ in range(10):
    ray.get([f.remote(1) for _ in range(100)])

for _ in range(100):
    ray.get([f.remote(1) for _ in range(10)])

for _ in range(1000):
    ray.get([f.remote(1) for _ in range(1)])

您还可以添加多个节点,每个节点具有不同的资源数量。

mock_node = cluster.add_node(num_cpus=10)

assert ray.cluster_resources()["CPU"] == 20

您还可以删除节点,这在测试故障处理逻辑时很有用。

cluster.remove_node(mock_node)

assert ray.cluster_resources()["CPU"] == 10

有关更多详细信息,请参阅 Cluster Util

技巧 4:在并行运行测试时要小心#

由于 Ray 会启动各种服务,如果一次启动的服务过多,很容易触发超时。因此,在使用 pytest xdist 等并行运行多个测试的工具时,应牢记这可能会在测试环境中引入不稳定性。