Ray 内存管理完全指南:ObjectRef 引用计数、ray memory 调试与内存感知调度

📅 发布时间:2026/9/20 3:08:41
Ray 内存管理完全指南:ObjectRef 引用计数、ray memory 调试与内存感知调度
人工智能分布式训练强化学习任务调度模型推理服务【免费下载链接】rayRay is an AI compute engine. Ray consists of a core distributed runtime and a set of AI Libraries for accelerating ML workloads.项目地址https://gitcode.com/gh_mirrors/ra/ray点击查看免费下载Ray 作为 AI 计算引擎其分布式对象存储与引用计数机制直接决定了大规模任务的内存行为。本文基于 doc/source/ray-core/scheduling/memory-management.rst 展开系统讲解 Ray 中各类内存的划分方式、对象存储默认容量30% 比例 / 200 GB 上限的推导逻辑、ray memory命令的五类引用排查技巧以及通过memory资源参数实现内存感知调度的方法。读完本文你将能够准确解释对象为什么没有被释放并熟练使用命令与配置解决ObjectStoreFullError与内存泄漏问题。一、Ray 内存使用的基础概念Ray 应用对内存的使用可以分为两大类别Ray 系统内存Ray 自身进程消耗与应用内存你的业务代码消耗。二者在监控与调优时关注点完全不同。Ray 系统内存Ray system memoryRay 系统内存是 Ray 运行时内部消耗的内存主要包括两个组成部分GCSGlobal Control Store用于存储集群中的节点列表与 Actor 列表。这部分内存使用量通常非常小但它是集群元数据的中枢所有节点注册、Actor 存活状态都由 GCS 维护。Raylet每个节点上运行的 C raylet 进程所占用的内存。这部分内存不可由用户直接控制但通常占用也很小。从源码结构看raylet 承担了节点资源管理、对象存储交互与任务调度等核心职责其实现位于 src/ray 目录下。虽然用户无法直接调优 raylet 内存但理解其存在有助于在top等工具中区分系统开销与应用开销。应用内存Application memory应用内存是用户业务代码如 Python 逻辑、TensorFlow 模型实际消耗的内存进一步细分为三类Worker 堆Worker heap即应用进程本身使用的内存。最佳度量方式是使用top等命令观察应用进程的常驻内存集RSS减去共享内存SHR。之所以要减去 SHR是因为对象存储的共享内存在操作系统中会同时算在每个关联 worker 头上不减去 SHR 会导致内存用量被重复统计。对象存储内存Object store memory当应用通过ray.put创建对象、或远程函数返回值时对象被放入对象存储。对象采用引用计数管理一旦引用超出作用域就会被逐出。每个节点上都运行着一个对象存储服务。默认情况下启动实例时 Ray 会预留可用内存的30%作为对象存储容量容量大小可通过--object-store-memory参数控制对应 ray start 命令行选项。在 Linux 上对象存储默认分配在/dev/shm共享内存在 macOS 上则使用/tmp磁盘因此性能会低于 Linux。自 Ray 1.3 起当对象存储写满时对象会被溢出spill到磁盘。对象存储共享内存Object store shared memory当应用通过ray.get读取对象时消耗的内存。需要注意的是如果对象已存在于本节点读取并不会产生额外的内存分配。正是这一特性使得大对象可以在众多 Actor 与任务之间被高效共享。二、ObjectRef 分布式引用计数对象何时被保留Ray 实现了分布式引用计数机制只要集群中任何一个ObjectRef仍处于作用域内该对象就会被钉住pinned在对象存储中不会被逐出。这里的在作用域内涵盖三种情况本地 Python 变量中持有的ObjectRef尚未执行完的任务参数中引用的ObjectRef被序列化并嵌套进其他对象内部的ObjectRef。这一设计是理解内存为什么没被释放的关键即使你在某个 driver 中del了变量只要对象 ID 仍被某个 pending 任务、某个 Actor 成员变量或另一个对象内部引用着它在对象存储中的生命周期就不会结束。下文ray memory章节将展示如何逐条列出这些引用。三、对象存储内存大小默认策略与调整方式默认大小的推导逻辑Ray 默认将每个节点的对象存储设置为该节点可用内存的30%上限为 200 GB。在 Linux 上Ray 还会将对象存储额外限制在/dev/shm大小的 95% 以内因为对象存储内存分配在共享内存上。200 GB 上限意味着大内存节点只会获得 200 GB 的对象存储而非总内存的 30%。例如一台 2 TB 内存的节点对象存储是 200 GB 而不是 600 GB。这一逻辑在源码中有精确对应。在 python/ray/_private/ray_constants.py 中定义了三个关键默认值DEFAULT_OBJECT_STORE_MAX_MEMORY_BYTES默认200 * 10**9200 GBDEFAULT_OBJECT_STORE_MEMORY_PROPORTION默认0.330% 比例OBJECT_STORE_MINIMUM_MEMORY_BYTES对象存储的最小容量下限为 75 MB。而在 python/ray/_private/utils.py 的resolve_object_store_memory函数中完整实现了默认容量推导流程未显式指定时先用可用内存 × 0.3得到基准值在 Linux 上进一步取/dev/shm大小乘 0.95 留出余量与最小阈值REQUIRE_SHM_SIZE_THRESHOLD10**10字节的较大值作为 shm 上限与 200 GB 取较小者作为最终上限在 macOS 上还会将对象存储压缩到 2 GBMAC_DEGRADED_PERF_MMAP_SIZE_LIMIT以避免大对象存储在 Mac 上引发的性能劣化。通过参数显式指定绝对大小如果你需要为节点设置确定的对象存储大小可以传入字节数命令行方式ray start --object-store-memory bytesPython 方式ray.init(object_store_memorybytes)两种方式都接受以字节为单位的数值。通过环境变量调整默认策略如果你不想固定一个绝对值而是希望调整默认的比例或上限可以在 Ray 启动前设置以下环境变量环境变量作用示例RAY_DEFAULT_OBJECT_STORE_MEMORY_PROPORTION覆盖 30% 的默认比例设为0.5表示预留可用内存的一半RAY_DEFAULT_OBJECT_STORE_MAX_MEMORY_BYTES覆盖 200 GB 的默认上限按字节数设置新的上限Ray 在启动时读取这两个变量因此必须在每个节点、Ray 进程启动之前设置它们。集群启动之后再修改环境变量不会生效。四、使用 ray memory 调试对象引用当应用抛出ObjectStoreFullError对象存储已满时首要任务是找出到底是谁还持有对象引用导致对象无法被逐出。ray memory命令正是为此设计的工具。在 Ray 应用运行期间于命令行执行ray memory会输出集群中 driver、Actor、任务当前持有的全部ObjectRef引用快照。一个典型的输出如下 Object references status: 2021-02-23 22:02:22.072221 Grouping by node address... Sorting by object size... --- Summary for node address: 192.168.0.15 --- Mem Used by Objects Local References Pinned Count Pending Tasks Captured in Objects Actor Handles 287 MiB 4 0 0 1 0 --- Object references for node address: 192.168.0.15 --- IP Address PID Type Object Ref Size Reference Type Call Site 192.168.0.15 6465 Driver ffffffffffffffffffffffffffffffffffffffff0100000001000000 15 MiB LOCAL_REFERENCE (put object) | test.py: module:17 192.168.0.15 6465 Driver a67dc375e60ddd1affffffffffffffffffffffff0100000001000000 15 MiB LOCAL_REFERENCE (task call) | test.py: :module:18 192.168.0.15 6465 Driver ffffffffffffffffffffffffffffffffffffffff0100000002000000 18 MiB CAPTURED_IN_OBJECT (put object) | test.py: module:19 192.168.0.15 6465 Driver ffffffffffffffffffffffffffffffffffffffff0100000004000000 21 MiB LOCAL_REFERENCE (put object) | test.py: module:20 192.168.0.15 6465 Driver ffffffffffffffffffffffffffffffffffffffff0100000003000000 218 MiB LOCAL_REFERENCE (put object) | test.py: module:20 --- Aggregate object store stats across all nodes --- Plasma memory usage 0 MiB, 4 objects, 0.0% full输出的每个条目对应一个正在把对象钉在对象存储中的ObjectRef包含以下关键信息引用所在的位置driver、worker 等进程引用类型即下文五种类型之一对象大小字节对象实例化所在的进程 ID 与 IP 地址引用在应用代码中的创建位置Call Site。ray memory还提供增强调试体验的参数例如sort-byOBJECT_SIZE与group-bySTACK_TRACE后者对定位内存泄漏发生的具体代码行尤为有效。完整的选项列表可通过ray memory --help查看。五种可钉住对象的引用类型1. 本地 ObjectRef 引用Local ObjectRef referencesimport ray ray.remote def f(arg): return arg a ray.put(None) b f.remote(None)上面的示例创建了对两个对象的引用一个是通过ray.put()放入对象存储的对象另一个是f.remote()的返回值。对应输出中两者在 driver 进程内都被标记为LOCAL_REFERENCE区别仅在 Reference Creation Site 的注释前者标注为 put object后者标注为 task call。--- Summary for node address: 192.168.0.15 --- Mem Used by Objects Local References Pinned Count Pending Tasks Captured in Objects Actor Handles 30 MiB 2 0 0 0 0 --- Object references for node address: 192.168.0.15 --- IP Address PID Type Object Ref Size Reference Type Call Site 192.168.0.15 6867 Driver ffffffffffffffffffffffffffffffffffffffff0100000001000000 15 MiB LOCAL_REFERENCE (put object) | test.py: module:12 192.168.0.15 6867 Driver a67dc375e60ddd1affffffffffffffffffffffff0100000001000000 15 MiB LOCAL_REFERENCE (task call) | test.py: :module:132. 内存中钉住的对象Objects pinned in memoryimport numpy as np a ray.put(np.zeros(1)) b ray.get(a) del a该示例先把 numpy 数组放入对象存储再通过ray.get取回然后删除其ObjectRef。此时对象依然被钉在对象存储中因为反序列化副本存在b中直接指向对象存储中的内存。ray memory输出会将此对象标记为PINNED_IN_MEMORY一旦执行del b该引用即可被释放。--- Summary for node address: 192.168.0.15 --- Mem Used by Objects Local References Pinned Count Pending Tasks Captured in Objects Actor Handles 243 MiB 0 1 0 0 0 --- Object references for node address: 192.168.0.15 --- IP Address PID Type Object Ref Size Reference Type Call Site 192.168.0.15 7066 Driver ffffffffffffffffffffffffffffffffffffffff0100000001000000 243 MiB PINNED_IN_MEMORY test. py:module:193. 待执行任务引用Pending task referencesray.remote def f(arg): while True: pass a ray.put(None) b f.remote(a)先通过ray.put()创建对象再提交一个依赖该对象的任务。任务运行期间ray memory显示 driver 进程同时持有LOCAL_REFERENCE与USED_BY_PENDING_TASK两类引用而 worker 进程因为 Python 参数arg直接引用了 plasma 中的内存、对象无法被逐出因此显示为PINNED_IN_MEMORY。--- Summary for node address: 192.168.0.15 --- Mem Used by Objects Local References Pinned Count Pending Tasks Captured in Objects Actor Handles 25 MiB 1 1 1 0 0 --- Object references for node address: 192.168.0.15 --- IP Address PID Type Object Ref Size Reference Type Call Site 192.168.0.15 7207 Driver a67dc375e60ddd1affffffffffffffffffffffff0100000001000000 ? LOCAL_REFERENCE (task call) | test.py: :module:29 192.168.0.15 7241 Worker ffffffffffffffffffffffffffffffffffffffff0100000001000000 10 MiB PINNED_IN_MEMORY (deserialize task arg) __main__.f 192.168.0.15 7207 Driver ffffffffffffffffffffffffffffffffffffffff0100000001000000 15 MiB USED_BY_PENDING_TASK (put object) | test.py: module:284. 序列化的 ObjectRef 引用Serialized ObjectRef referencesray.remote def f(arg): while True: pass a ray.put(None) b f.remote([a])与上一个示例的区别在于对象a不是直接作为参数而是被包在另一个对象此处为列表中传给任务。此时 driver 和运行任务的 worker 进程各自持有一个LOCAL_REFERENCEdriver 上还同时标记USED_BY_PENDING_TASK。注意如果是 Actor 任务Actor 还可以在任务完成后通过把ObjectRef存入成员变量来长期持有LOCAL_REFERENCE——这正是 Actor 状态导致对象无法释放的常见场景。--- Summary for node address: 192.168.0.15 --- Mem Used by Objects Local References Pinned Count Pending Tasks Captured in Objects Actor Handles 15 MiB 2 0 1 0 0 --- Object references for node address: 192.168.0.15 --- IP Address PID Type Object Ref Size Reference Type Call Site 192.168.0.15 7411 Worker ffffffffffffffffffffffffffffffffffffffff0100000001000000 ? LOCAL_REFERENCE (deserialize task arg) __main__.f 192.168.0.15 7373 Driver a67dc375e60ddd1affffffffffffffffffffffff0100000001000000 ? LOCAL_REFERENCE (task call) | test.py: :module:38 192.168.0.15 7373 Driver ffffffffffffffffffffffffffffffffffffffff0100000001000000 15 MiB USED_BY_PENDING_TASK (put object) | test.py: module:375. 被捕获的 ObjectRef 引用Captured ObjectRef referencesa ray.put(None) b ray.put([a]) del a先创建一个对象再把它的ObjectRef捕获进另一个ray.put()的对象中然后删除第一个ObjectRef。由于第一个对象的 ID 被序列化保存在第二个对象内部两个对象仍然都会被钉住。输出中第二个对象显示为普通的LOCAL_REFERENCE而第一个对象显示为CAPTURED_IN_OBJECT。--- Summary for node address: 192.168.0.15 --- Mem Used by Objects Local References Pinned Count Pending Tasks Captured in Objects Actor Handles 233 MiB 1 0 0 1 0 --- Object references for node address: 192.168.0.15 --- IP Address PID Type Object Ref Size Reference Type Call Site 192.168.0.15 7473 Driver ffffffffffffffffffffffffffffffffffffffff0100000001000000 15 MiB CAPTURED_IN_OBJECT (put object) | test.py: module:41 192.168.0.15 7473 Driver ffffffffffffffffffffffffffffffffffffffff0100000002000000 218 MiB LOCAL_REFERENCE (put object) | test.py: module:42五、内存感知调度Memory Aware Scheduling默认情况下Ray 调度器在调度任务与 Actor 时不计算其潜在内存使用量——原因很简单它无法在调度前估算一个任务到底需要多少内存。但如果你明确知道自己任务或 Actor 的内存需求可以在ray.remote装饰器的资源要求中指定从而启用内存感知调度。重要提示指定内存需求并不会对实际内存使用施加任何限制。该需求仅用于调度阶段的准入控制与 Ray 中 CPU 调度的逻辑类似。任务本身必须自觉不超过其申请的内存额度。静态指定装饰器中的 memory 参数# 预留 500MiB 可用内存来放置该任务 ray.remote(memory500 * 1024 * 1024) def some_function(x): pass # 预留 2.5GiB 可用内存来放置该 Actor ray.remote(memory2500 * 1024 * 1024) class SomeActor: def __init__(self, a, b): pass调度器会在调度时像对待 CPU、GPU 资源一样为任务或 Actor 预留指定大小的可用内存。动态覆盖使用 .options()内存配额也可以在运行时动态指定或覆盖# 提交任务时把内存配额覆盖为 100MiB some_function.options(memory100 * 1024 * 1024).remote(x1) # 创建 Actor 时把内存配额覆盖为 1GiB SomeActor.options(memory1000 * 1024 * 1024).remote(a1, b2)这种装饰器默认 options()覆盖的模式与 Ray 其他资源如num_cpus、num_gpus的使用方式保持一致便于在同一函数的多次调用中按需调整配额。六、OOM 排查与预防的延伸路径当对象存储写满、或节点物理内存耗尽时单纯的引用分析往往不够还需要配套的预防与诊断手段对象溢出Object Spilling自 Ray 1.3 起对象存储写满后对象会被溢出到磁盘避免应用直接失败但磁盘 I/O 会显著影响性能因此在容量规划时仍需合理设置对象存储大小。OOM 预防机制Ray 内置了基于 cgroup 的资源隔离与 OOM 检测机制详见 ray-oom-prevention.rst其中说明了如何利用内存监控来主动杀停超用内存的任务而不是拖垮整个节点。OOM 系统排查指南从如何检测 OOM 错误、如何定位每个任务/Actor 的内存使用到如何消除 worker OOM可参考 debug-memory.rst。该文档还介绍了与ray memory几乎等价但返回结构化数据的 API 调用方式更适合在自动化脚本中集成。在源码层面内存监控由 python/ray/_private/memory_monitor.py 中的MemoryMonitor类实现并通过RayOutOfMemoryError异常向用户暴露 OOM 状态结合ray memory的引用快照即可形成监控检测 → 引用定位 → 代码修复的完整闭环。七、常见问题与社区支持如果在实践中遇到与本文所述机制不符的行为例如引用计数未按预期释放、对象存储大小与预期不符可以携带以下信息向 Ray 社区反馈ray memory的完整输出、ray.init/ray start时的内存相关参数、RAY_DEFAULT_OBJECT_STORE_MEMORY_PROPORTION与RAY_DEFAULT_OBJECT_STORE_MAX_MEMORY_BYTES的取值以及复现脚本。详细的帮助与参与贡献的指引见仓库根目录的 CONTRIBUTING.rst 与 AGENTS.md。赞分享人工智能分布式训练强化学习任务调度模型推理服务【免费下载链接】rayRay is an AI compute engine. Ray consists of a core distributed runtime and a set of AI Libraries for accelerating ML workloads.项目地址https://gitcode.com/gh_mirrors/ra/ray点击查看免费下载相关推荐【免费下载】 Ray项目内存问题调试完全指南Ray项目内存问题调试完全指南 内存问题概述 在分布式计算框架Ray中内存管理是一个关键问题。当系统内存不足时可能会导致任务失败、性能下降甚至系统崩溃。理解人工智能分布式训练强化学习任务调度模型推理服务终极内存调试指南Dr. Memory 快速定位内存错误终极内存调试指南Dr. Memory 快速定位内存错误 在软件开发过程中内存错误是最常见也最令人头疼的问题之一。无论是内存泄漏、缓冲区溢出还是未初始化内存访开发工具Ray Data 内部机制深度解析执行模型、Shuffle 算法、调度与内存模型Ray Data 内部机制深度解析执行模型、Shuffle 算法、调度与内存模型 本文面向 Ray Data 的高级用户与开发者深入剖析 Ray Data人工智能分布式训练强化学习任务调度模型推理服务创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考