ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Ray 反模式解析:在循环中重复定义 Remote Function 与 Actor 类的性能陷阱

Ray 反模式解析:在循环中重复定义 Remote Function 与 Actor 类的性能陷阱 人工智能分布式训练强化学习任务调度模型推理服务【免费下载链接】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.remote装饰器在循环内部重复定义同一个远程函数或 Actor 类会导致每次迭代都产生一次序列化pickle→ 上传 GCS → 下载 → 反序列化unpickle的完整开销显著拖慢任务提交性能。本篇基于 doc/source/ray-core/patterns/redefine-task-actor-loop.rst 展开先讲清该反模式的行为与危害再结合 python/ray/remote_function.py 与 python/ray/_private/function_manager.py 的源码从函数描述符、GCS KV 函数表等底层机制解释为什么每次装饰都会重新上传最后给出任务与 Actor 两种场景下可复制、可运行的修正写法。一句话结论TLDR避免重复定义同一个远程函数或类。即不要把ray.remote装饰器放进for循环、递归调用或任何可能多次执行的路径里应把装饰后的定义提升到循环之外让 Ray 只对它做一次 pickle 与上传。反模式为什么慢从装饰到执行的四步链路原文档指出对于每一个 Ray 远程函数或类Ray 都会将其 pickle 并上传到 GCS随后真正执行任务或 Actor 的 worker 再下载并反序列化。从 Ray 的视角来看对同一个函数或类的每一次装饰都会生成一个全新的远程函数/类因此上述 pickle、上传、下载、反序列化的整套工作会在每次重新定义并运行该远程函数/类时全部重来一遍。对应到源码实现这一步可以由两个关键文件印证python/ray/remote_function.py 中的RemoteFunction类每次执行ray.remote装饰都会构造一个新的RemoteFunction实例并在__init__中通过self._uuid uuid.uuid4()生成一个全新的 UUID见 python/ray/remote_function.py#L91-L171。由于函数描述符PythonFunctionDescriptor由函数与_uuid共同计算见_remote中的PythonFunctionDescriptor.from_function(self._function, self._uuid)python/ray/remote_function.py#L392-L416每次装饰得到的描述符都不同Ray 会把它当成一个从未见过的全新函数。python/ray/_private/function_manager.py 中的FunctionManager.exportpython/ray/_private/function_manager.py#L219-L267真正执行pickle 远程函数并写入 GCS 内部 KV的地方。它先取出remote_function._pickled_function调用check_oversized_function做体积检查再以make_function_table_key(bRemoteFunction, ...)生成的键写入KV_NAMESPACE_FUNCTION_TABLE。每次新装饰的RemoteFunction首次被调用时都会走一遍这条 export 路径。更具体地说每次重新装饰后再调用.remote()_remote方法会检查self._last_export_cluster_and_job ! worker.current_cluster_and_job新实例该字段为None必然成立随后执行pickle_dumps序列化函数体再调用worker.function_actor_manager.export(self)上传到 GCSpython/ray/remote_function.py#L392-L416。这意味着循环 10 次定义同一个函数就有 10 次独立的序列化与 GCS 上传而定义在循环外则只有 1 次。反模式代码示例原文档给出的完整示例位于 doc/source/ray-core/doc_code/anti_pattern_redefine_task_actor_loop.py以下为反模式部分__anti_pattern_start__与__anti_pattern_end__之间import ray ray.init() outputs [] for i in range(10): ray.remote def double(i): return i * 2 outputs.append(double.remote(i)) outputs ray.get(outputs) # The double remote function is pickled and uploaded 10 times.问题所在每次循环迭代都会执行一次ray.remote装饰产生一个新的RemoteFunction实例、一个新的 UUID 与函数描述符每个实例第一次调用double.remote(i)时都会触发一次 pickle GCS 上传10 次迭代意味着double被序列化并上传 10 次worker 侧还要相应下载与反序列化 10 次。注意虽然该例最终assert outputs [i * 2 for i in range(10)]依然成立、结果正确但正确性不受影响不代表性能不受损——这正是反模式与错误的本质区别它不产生错误结果却白白付出不必要的分布式序列化开销。正确做法把定义提升到循环之外还是同一份文件__better_approach_start__与__better_approach_end__之间ray.remote def double(i): return i * 2 outputs [] for i in range(10): outputs.append(double.remote(i)) outputs ray.get(outputs) # The double remote function is pickled and uploaded 1 time.关键变化只有一点ray.remote装饰发生在循环之前循环体内仅重复调用double.remote(i)。这样double对应的RemoteFunction实例只构造一次、只 pickle 一次、只上传一次后续 9 次调用直接复用已导出的函数定义避免重复开销。原文档明确提示应在循环之外定义同一个远程函数或类而不是在循环内多次定义这样它只被 pickle 和上传一次。同规则应用于 Actor 类原文档标题与正文均同时覆盖remote function与class即该反模式对ray.remote装饰的 Actor 类同样成立。把上面的原则套用到 Actor 场景反模式写法import ray ray.init() actors [] for i in range(10): ray.remote class Counter: def __init__(self): self.value 0 def inc(self): self.value 1 return self.value actors.append(Counter.remote())正确写法import ray ray.init() ray.remote class Counter: def __init__(self): self.value 0 def inc(self): self.value 1 return self.value actors [Counter.remote() for _ in range(10)]从源码看Actor 类的导出路径与远程函数类似FunctionManager.export_actor_class会通过pickle_dumps序列化 Actor 类并将其写入 GCS KV见 python/ray/_private/function_manager.py#L488-L542。因此每次在循环内重新装饰 Actor 类同样意味着 Actor 类会被重新序列化并上传而循环外的单次定义只需导出一次所有实例共享同一份类定义。底层原理再深入为什么新装饰 新函数函数描述符由函数 UUID 共同决定在 python/ray/remote_function.py 中RemoteFunction.__init__会记录self._function_name function.__module__ . function.__name__并生成self._uuid uuid.uuid4()python/ray/remote_function.py#L166-L171。只有当函数第一次被调用时才会基于函数与 UUID 计算PythonFunctionDescriptorpython/ray/remote_function.py#L398-L400。由于每次装饰都伴随新的 UUID即便底层 Python 函数对象完全相同Ray 层面的函数 ID 也各不相同。幂等导出只对同一个实例生效_remote里通过self._last_export_cluster_and_job判断本集群 本 Job 是否已导出过该实例python/ray/remote_function.py#L392-L397。这个缓存机制只针对同一个RemoteFunction实例同一个实例反复调用.remote()不会重复上传但循环内每次新装饰产生的新实例其缓存为空必然再次上传。GCS 函数表的写入与去重FunctionManager.export会先检查internal_kv_exists(key, KV_NAMESPACE_FUNCTION_TABLE)若键已存在则直接返回python/ray/_private/function_manager.py#L252-L253。但由于每次装饰生成的函数 ID 不同make_function_table_key(bRemoteFunction, job_id, function_id)计算出的键也不同去重检查形同虚设——10 个新实例会写入 10 个不同的键。此外每次 export 还会执行check_oversized_function对序列化结果做体积检查python/ray/_private/function_manager.py#L241-L246这意味着若被循环装饰的函数体很大例如闭包捕获了大对象参见同目录下的 closure-capture-large-objects.rst 反模式重复 pickle 检查 上传的代价会被进一步放大。如何识别与定位这类问题审视代码结构如果ray.remote出现在for/while循环、递归函数、或可能被多次执行的辅助函数体内且装饰对象是同一个函数/类就命中该反模式。作为对比如果循环内需要为不同的函数动态装饰例如基于配置批量注册任务则不在此反模式讨论范围内。观察任务日志与 Dashboard重复导出会使 Driver 侧的序列化耗时与 GCS 写入频繁出现。结合 ray-get-loop.rst 等其他性能反模式一起排查可以更系统地找出任务提交阶段的性能瓶颈。数值验证若函数体内包含大对象或复杂闭包可以用sys.getsizeof或手动pickle.dumps评估单次序列化成本再乘以循环次数估算被浪费的开销——当然更直接的做法是参照本文正确做法改写后对比整体吞吐。相关模式索引该反模式属于 Ray Core 官方设计与反模式系列文档之一完整的模式目录见 doc/source/ray-core/patterns/index.rst。与本文主题相关的相邻模式还包括too-fine-grained-tasks.rst任务粒度过细会放大单任务固定开销与重复导出有类似的性能成因closure-capture-large-objects.rst闭包捕获大对象会显著增加每次 pickle 的体积放大本反模式的成本return-ray-put.rst涉及 ObjectRef 传递的常见反模式。小结在循环中重复定义远程函数或 Actor 类是 Ray 任务提交阶段最常见的性能反模式之一。其根因在于每次ray.remote装饰都会创建带全新 UUID 与函数描述符的新RemoteFunction实例从而触发完整的 pickle → GCS 上传 → worker 下载 → unpickle 链路。修复方式极其简单——把装饰语句移到循环外让同一个远程函数/类只被序列化与上传一次。理解背后的RemoteFunction、函数描述符与 GCS 函数表机制python/ray/remote_function.py、python/ray/_private/function_manager.py有助于举一反三写出可扩展的高性能 Ray 应用。赞分享人工智能分布式训练强化学习任务调度模型推理服务【免费下载链接】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点击查看免费下载相关推荐终极指南解决RefineNext.js无限重定向陷阱从根源修复认证循环问题终极指南解决RefineNext.js无限重定向陷阱从根源修复认证循环问题 在使用Refine和Next.js构建管理面板或B2B应用时开发者常遇到一个前端企业应用Ray 反模式解析为什么全局变量无法在任务与 Actor 之间共享状态Ray 反模式解析为什么全局变量无法在任务与 Actor 之间共享状态 Ray 的 Driver、Task 与 Actor 各自运行在独立进程中彼此不共享地人工智能分布式训练强化学习任务调度模型推理服务创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表