调试内存问题#
调试内存不足#
在阅读本节之前,请先熟悉 Ray 的 内存管理 模型。
如果您的集群存在内存不足问题,请参阅 如何检测内存不足错误。
要定位内存泄漏的根源,请参阅 查找每个任务和 Actor 的内存使用情况。
如果您的主节点内存使用率很高,请参阅 主节点内存不足错误。
如果由于高并行度导致内存使用率很高,请参阅 降低并行度。
如果您想分析每个任务和 Actor 的内存使用情况,请参阅 分析任务和 Actor 的内存使用情况。
什么是内存不足错误?#
内存是有限的资源。当一个进程请求内存而操作系统无法分配时,操作系统会执行一个例程,通过终止一个内存使用率高的进程(通过 SIGKILL)来释放内存,以避免操作系统变得不稳定。这个例程被称为 Linux 内存不足杀手 (OOM killer)。
Linux 内存不足杀手常见的其中一个问题是,SIGKILL 会在 Ray 未察觉的情况下杀死进程。由于 SIGKILL 无法被进程处理,Ray 很难发出适当的错误消息并采取适当的容错措施。为了解决这个问题,Ray(从 Ray 2.2 开始)拥有一个应用程序级别的 内存监视器,它会持续监视主机的内存使用情况,并在 Linux 内存不足杀手执行之前杀死 Ray Worker。
检测内存不足错误#
如果 Linux 内存不足杀手终止了任务或 Actor,Ray Worker 进程将无法捕获并显示确切的根本原因,因为 SIGKILL 无法被进程处理。当您对从死去的 Worker 执行的任务和 Actor 调用 ray.get 时,它会引发一个异常,其中包含以下错误消息之一(这表明 Worker 已被意外终止)。
Worker exit type: UNEXPECTED_SY STEM_EXIT Worker exit detail: Worker unexpectedly exits with a connection error code 2. End of file. There are some potential root causes. (1) The process is killed by SIGKILL by OOM killer due to high memory usage. (2) ray stop --force is called. (3) The worker is crashed unexpectedly due to SIGSEGV or other unexpected errors.
Worker exit type: SYSTEM_ERROR Worker exit detail: The leased worker has unrecoverable failure. Worker is requested to be destroyed when it is returned.
您也可以使用 dmesg CLI 命令来验证进程是否被 Linux 内存不足杀手终止。
如果 Ray 的内存监视器杀死了 Worker,它会被自动重试(有关详细信息,请参阅 链接)。如果任务或 Actor 无法重试,当您对其调用 ray.get 时,它们会引发一个带有更清晰错误消息的异常。
ray.exceptions.OutOfMemoryError: Task was killed due to the node running low on memory.
Task was killed due to the node running low on memory.
Memory on the node (IP: 10.0.62.231, ID: e5d953ef03e55e26f13973ea1b5a0fd0ecc729cd820bc89e4aa50451) where the task (task ID: 43534ce9375fa8e4cd0d0ec285d9974a6a95897401000000, name=allocate_memory, pid=11362, memory used=1.25GB) was running was 27.71GB / 28.80GB (0.962273), which exceeds the memory usage threshold of 0.95. Ray killed this worker (ID: 6f2ec5c8b0d5f5a66572859faf192d36743536c2e9702ea58084b037) because it was the most recently scheduled task; to see more information about memory usage on this node, use `ray logs raylet.out -ip 10.0.62.231`. To see the logs of the worker, use `ray logs worker-6f2ec5c8b0d5f5a66572859faf192d36743536c2e9702ea58084b037*out -ip 10.0.62.231.`
Top 10 memory users:
PID MEM(GB) COMMAND
410728 8.47 510953 7.19 ray::allocate_memory
610952 6.15 ray::allocate_memory
711164 3.63 ray::allocate_memory
811156 3.63 ray::allocate_memory
911362 1.25 ray::allocate_memory
107230 0.09 python test.py --num-tasks 2011327 0.08 /home/ray/anaconda3/bin/python /home/ray/anaconda3/lib/python3.9/site-packages/ray/dashboard/dashboa...
Refer to the documentation on how to address the out of memory issue: https://docs.rayai.org.cn/en/latest/ray-core/scheduling/ray-oom-prevention.html.
Ray 内存监视器还会定期将聚合的内存不足杀手摘要打印到 Ray 驱动程序。
(raylet) [2023-04-09 07:23:59,445 E 395 395] (raylet) node_manager.cc:3049: 10 Workers (tasks / actors) killed due to memory pressure (OOM), 0 Workers crashed due to other reasons at node (ID: e5d953ef03e55e26f13973ea1b5a0fd0ecc729cd820bc89e4aa50451, IP: 10.0.62.231) over the last time period. To see more information about the Workers killed on this node, use `ray logs raylet.out -ip 10.0.62.231`
(raylet)
(raylet) Refer to the documentation on how to address the out of memory issue: https://docs.rayai.org.cn/en/latest/ray-core/scheduling/ray-oom-prevention.html. Consider provisioning more memory on this node or reducing task parallelism by requesting more CPUs per task. To adjust the kill threshold, set the environment variable `RAY_memory_usage_threshold` when starting Ray. To disable worker killing, set the environment variable `RAY_memory_monitor_refresh_ms` to zero.
Ray Dashboard 的 指标页面 和 事件页面 也提供了内存不足杀手特定的事件和指标。
查找每个任务和 Actor 的内存使用情况#
如果任务或 Actor 由于内存不足错误而失败,它们将根据 重试策略 进行重试。然而,通常更倾向于找到内存问题的根本原因并修复它们,而不是依赖容错机制。本节将介绍如何在 Ray 中调试内存不足错误。
首先,找到内存使用率高的任务和 Actor。有关更多详细信息,请参阅 每个任务和 Actor 的内存使用率图。每个组件图中的内存使用率使用 RSS - SHR。原因见下文。
或者,您也可以使用 CLI 命令 htop。
查看 allocate_memory 行。查看 RSS 和 SHR 两列。
SHR 使用量通常是 Ray 对象存储的内存使用量。Ray 对象存储分配 30% 的主机内存给共享内存(`/dev/shm`,除非您指定了 `--object-store-memory`)。如果 Ray worker 通过 `ray.get` 访问对象存储中的对象,SHR 使用量会增加。由于 Ray 对象存储支持 零拷贝 反序列化,多个 worker 可以访问同一个对象而无需将其复制到进程内内存。例如,如果 8 个 worker 访问 Ray 对象存储中的同一个对象,每个进程的 `SHR` 使用量都会增加。但是,它们并没有使用 8 * SHR 的内存(共享内存中只有一个副本)。另请注意,当对象使用量超过限制时,Ray 对象存储会触发 对象溢出,这意味着共享内存的内存使用量不会超过主机内存的 30%。
主机的内存不足问题是由于每个 worker 的 RSS 使用量造成的。通过 RSS - SHR 计算每个进程的内存使用量,因为如上所述,SHR 用于 Ray 对象存储。总内存使用量通常是 `SHR`(对象存储内存使用量,占内存的 30%)+ `sum(每个 ray 进程的 RSS - SHR)` + `sum(系统组件的 RSS - SHR,例如 raylet、GCS。通常较小)`。
主节点内存不足错误#
首先,从指标页面查看主节点的内存使用情况。从集群页面找到主节点的地址。
然后,在 Dashboard 的 指标视图 中,查看节点内存使用情况,来检查主节点的内存使用情况。
Ray 主节点有更多内存消耗的系统组件,例如 GCS 或 Dashboard。此外,默认情况下驱动程序也在主节点上运行。如果主节点的内存容量与工作节点相同,并且您从主节点上执行相同数量的任务和 Actor,它很容易出现内存不足问题。在这种情况下,请不要在主节点上运行任何任务和 Actor,方法是在启动主节点时通过 `ray start --head` 指定 `--num-cpus=0`。如果您使用 KubeRay,请在此处查看 这里。
降低并行度#
高并行度可能会触发内存不足错误。例如,如果您有 8 个执行数据预处理 -> 训练的训练 worker。如果您将过多的数据加载到每个 worker 中,总内存使用量(`训练 worker 内存使用量 * 8`)可能会超过内存容量。
通过查看 每个任务和 Actor 的内存使用率图 和任务指标来验证内存使用情况。
首先,查看 `allocate_memory` 任务的内存使用情况。总共为 18GB。同时,验证正在运行的 15 个并发任务。
每个任务使用大约 18GB / 15 == 1.2 GB。要降低并行度
增加 `ray.remote()` 的 `num_cpus` 选项。现代硬件通常每个 CPU 有 4GB 内存,因此您可以相应地选择 CPU 要求。此示例为每个 `allocate_memory` 任务指定 1 个 CPU。将 CPU 要求加倍,同时只运行一半(7)的任务,内存使用量不会超过 9GB。
分析任务和 Actor 的内存使用情况#
任务和 Actor 也可能使用比您预期的更多的内存。例如,Actor 或任务可能存在内存泄漏或不必要的副本。
请参阅下面的说明,了解如何为单个 Actor 和任务进行内存分析。
内存分析 Ray 任务和 Actor#
要对 Ray 任务或 Actor 进行内存分析,请使用 memray。请注意,您也可以使用其他支持类似 API 的内存分析工具。
首先,安装 `memray`。
pip install memray
`memray` 支持 Python 上下文管理器来启用内存分析。您可以将 `memray` 分析文件写在您喜欢的任何位置。但在本例中,我们将它们写入 `/tmp/ray/session_latest/logs`,因为 Ray Dashboard 允许您下载日志文件夹中的文件。这将允许您从其他节点下载分析文件。
import memray
import ray
@ray.remote
class Actor:
def __init__(self):
# Every memory allocation after `__enter__` method will be tracked.
memray.Tracker(
"/tmp/ray/session_latest/logs/"
f"{ray.get_runtime_context().get_actor_id()}_mem_profile.bin"
).__enter__()
self.arr = [bytearray(b"1" * 1000000)]
def append(self):
self.arr.append(bytearray(b"1" * 1000000))
a = Actor.remote()
ray.get(a.append.remote())
请注意,任务的生命周期较短,因此可能会有很多内存分析文件。
import memray # noqa
import ray # noqa
@ray.remote
def task():
with memray.Tracker(
"/tmp/ray/session_latest/logs/"
f"{ray.get_runtime_context().get_task_id()}_mem_profile.bin"
):
arr = bytearray(b"1" * 1000000) # noqa
ray.get(task.remote())
一旦任务或 Actor 运行完毕,请转到 Dashboard 的 日志视图。找到并点击日志文件名。
点击下载按钮。
现在,您已经获得了内存分析文件。运行
memray flamegraph <memory profiling bin file>
您就可以看到内存分析的结果了!