使用 Ray 调试器#

Ray 内置了一个调试器,允许您调试分布式应用程序。它允许在 Ray 任务和 actor 中设置断点,当命中断点时,您可以进入 PDB 会话,然后可以使用该会话来

  • 检查该上下文中的变量

  • 单步执行任务或 actor

  • 在堆栈中向上或向下移动

警告

Ray 调试器已弃用。请改用 Ray 分布式调试器。从 Ray 2.39 开始,新的调试器是默认设置,您需要设置环境变量 RAY_DEBUG=legacy 来使用旧调试器(例如,通过使用运行时环境)。

入门#

以下面的示例为例

import ray

ray.init(runtime_env={"env_vars": {"RAY_DEBUG": "legacy"}})

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

futures = [f.remote(i) for i in range(2)]
print(ray.get(futures))

将程序放入名为 debugging.py 的文件中,并使用以下命令执行它

python debugging.py

执行的 2 个任务中的每一个都将在执行 breakpoint() 行时进入断点。您可以通过在集群的 head 节点上运行以下命令来附加到调试器

ray debug

ray debug 命令将打印类似如下的输出

2021-07-13 16:30:40,112     INFO scripts.py:216 -- Connecting to Ray instance at 192.168.2.61:6379.
2021-07-13 16:30:40,112     INFO worker.py:740 -- Connecting to existing Ray cluster at address: 192.168.2.61:6379
Active breakpoints:
index | timestamp           | Ray task | filename:lineno
0     | 2021-07-13 23:30:37 | ray::f() | debugging.py:6
1     | 2021-07-13 23:30:37 | ray::f() | debugging.py:6
Enter breakpoint index or press enter to refresh:

您现在可以输入 0 并按 Enter 键跳转到第一个断点。您将被置于断点的 PDB 中,可以使用 help 查看可用操作。运行 bt 查看执行的回溯

(Pdb) bt
  /home/ubuntu/ray/python/ray/workers/default_worker.py(170)<module>()
-> ray.worker.global_worker.main_loop()
  /home/ubuntu/ray/python/ray/worker.py(385)main_loop()
-> self.core_worker.run_task_loop()
> /home/ubuntu/tmp/debugging.py(7)f()
-> return x * x

您可以使用 print(x) 检查 x 的值。您可以使用 ll 查看当前源代码,并使用 updown 更改堆栈帧。现在,我们使用 c 继续执行。

继续执行后,按 Control + D 返回断点列表。选择另一个断点,然后再次按 c 继续执行。

Ray 程序 debugging.py 现在已完成,并且应该打印了 [0, 1]。恭喜您,您已完成了第一次 Ray 调试会话!

在集群上运行#

Ray 调试器支持在跨 Ray 集群运行的任务和 actor 中设置断点。为了从集群的 head 节点使用 ray debug 附加到这些断点,您需要确保在启动集群时将 --ray-debugger-external 标志传递给 ray start(可能在您的 cluster.yaml 文件或 k8s Ray 集群规范中)。

请注意,此标志将导致 worker 在面向外部的 IP 地址上监听 PDB 命令,因此只有在集群位于防火墙后面时才应使用此标志。

调试器命令#

Ray 调试器支持 与 PDB 相同的命令

在 Ray 任务之间单步执行#

您可以使用调试器在 Ray 任务之间单步执行。让我们以以下递归函数为例

import ray

ray.init(runtime_env={"env_vars": {"RAY_DEBUG": "legacy"}})

@ray.remote
def fact(n):
    if n == 1:
        return n
    else:
        n_ref = fact.remote(n - 1)
        return n * ray.get(n_ref)

@ray.remote
def compute():
    breakpoint()
    result_ref = fact.remote(5)
    result = ray.get(result_ref)

ray.get(compute.remote())

在执行 Python 文件并调用 ray debug 后,您可以按 0 然后 Enter 选择断点并进入。这将产生以下输出

Enter breakpoint index or press enter to refresh: 0
> /home/ubuntu/tmp/stepping.py(16)<module>()
-> result_ref = fact.remote(5)
(Pdb)

您可以使用 Ray 调试器中的 remote 命令进入调用。在函数内部,使用 p(n) 打印 n 的值,产生以下输出

-> result_ref = fact.remote(5)
(Pdb) remote
*** Connection closed by remote host ***
Continuing pdb session in different process...
--Call--
> /home/ubuntu/tmp/stepping.py(5)fact()
-> @ray.remote
(Pdb) ll
  5  ->     @ray.remote
  6         def fact(n):
  7             if n == 1:
  8                 return n
  9             else:
 10                 n_ref = fact.remote(n - 1)
 11                 return n * ray.get(n_ref)
(Pdb) p(n)
5
(Pdb)

现在再次使用 remote 单步执行下一个远程调用并打印 n。您现在可以通过再次调用 remote 来继续递归调用函数,或者可以使用 get 调试器命令跳转到调用 ray.get 的位置。再次使用 get 跳转回原始调用站点,并使用 p(result) 打印结果

Enter breakpoint index or press enter to refresh: 0
> /home/ubuntu/tmp/stepping.py(14)<module>()
-> result_ref = fact.remote(5)
(Pdb) remote
*** Connection closed by remote host ***
Continuing pdb session in different process...
--Call--
> /home/ubuntu/tmp/stepping.py(5)fact()
-> @ray.remote
(Pdb) p(n)
5
(Pdb) remote
*** Connection closed by remote host ***
Continuing pdb session in different process...
--Call--
> /home/ubuntu/tmp/stepping.py(5)fact()
-> @ray.remote
(Pdb) p(n)
4
(Pdb) get
*** Connection closed by remote host ***
Continuing pdb session in different process...
--Return--
> /home/ubuntu/tmp/stepping.py(5)fact()->120
-> @ray.remote
(Pdb) get
*** Connection closed by remote host ***
Continuing pdb session in different process...
--Return--
> /home/ubuntu/tmp/stepping.py(14)<module>()->None
-> result_ref = fact.remote(5)
(Pdb) p(result)
120
(Pdb)

事后调试#

通常我们无法预知错误发生在哪里,因此无法设置断点。在这些情况下,我们可以在发生错误或抛出异常时自动进入调试器。这称为事后调试

将以下代码复制到一个名为 post_mortem_debugging.py 的文件中。标志 RAY_DEBUG_POST_MORTEM=1 将具有这样的效果:如果发生异常,Ray 将进入调试器而不是进一步传播它。

import ray

ray.init(runtime_env={"env_vars": {"RAY_DEBUG": "legacy", "RAY_DEBUG_POST_MORTEM": "1"}})

@ray.remote
def post_mortem(x):
    x += 1
    raise Exception("An exception is raised.")
    return x

ray.get(post_mortem.remote(10))

让我们开始运行程序

python post_mortem_debugging.py

现在运行 ray debug。执行此操作后,我们将看到类似如下的输出

Active breakpoints:
index | timestamp           | Ray task                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | filename:lineno
0     | 2024-11-01 20:14:00 | /Users/pcmoritz/ray/python/ray/_private/workers/default_worker.py --node-ip-address=127.0.0.1 --node-manager-port=49606 --object-store-name=/tmp/ray/session_2024-11-01_13-13-51_279910_8596/sockets/plasma_store --raylet-name=/tmp/ray/session_2024-11-01_13-13-51_279910_8596/sockets/raylet --redis-address=None --metrics-agent-port=58655 --runtime-env-agent-port=56999 --logging-rotate-bytes=536870912 --logging-rotate-backup-count=5 --runtime-env-agent-port=56999 --gcs-address=127.0.0.1:6379 --session-name=session_2024-11-01_13-13-51_279910_8596 --temp-dir=/tmp/ray --webui=127.0.0.1:8265 --cluster-id=6d341469ae0f85b6c3819168dde27cceda12e95c8efdfc256e0fd8ce --startup-token=12 --worker-launch-time-ms=1730492039955 --node-id=0d43573a606286125da39767a52ce45ad101324c8af02cc25a9fbac7 --runtime-env-hash=-1746935720 | /Users/pcmoritz/ray/python/ray/_private/worker.py:920
Traceback (most recent call last):

File "python/ray/_raylet.pyx", line 1856, in ray._raylet.execute_task

File "python/ray/_raylet.pyx", line 1957, in ray._raylet.execute_task

File "python/ray/_raylet.pyx", line 1862, in ray._raylet.execute_task

File "/Users/pcmoritz/ray-debugger-test/post_mortem_debugging.py", line 8, in post_mortem
    raise Exception("An exception is raised.")

Exception: An exception is raised.

Enter breakpoint index or press enter to refresh:

我们现在按 0 然后 Enter 进入调试器。使用 ll 我们可以看到上下文,使用 print(x) 我们可以打印 x 的值。

以类似的方式,您也可以调试 Ray actor。祝您调试愉快!

调试 API#

请参阅 调试