AI-Chain

从单机 Python 到 AI 丛集:Ray 如何把扩展成本藏进程式模型

分享:
从单机 Python 到 AI 丛集:Ray 如何把扩展成本藏进程式模型

从单机 Python 到 AI 丛集:Ray 如何把扩展成本藏进程式模型

很多机器学习专案的第一版都很顺利:在笔电上读资料、呼叫模型、跑一个训练回圈,几分钟后就能看到结果。真正的问题通常发生在第二版。资料量变大、实验次数增加、模型推论需要并发,原本单机上的 Python 程式开始被记忆体、CPU、GPU 或等待时间卡住。此时团队面对的不是单纯「换一台更大的机器」,而是要重新处理任务分派、状态保存、资料传递、失败重试与服务部署。

Ray 是一个很适合用来理解这个转折点的开源专案。官方 README 将它定位为统一的 AI 与 Python 应用扩展框架:底层是分散式 runtime,上层则提供 Data、Train、Tune、RLlib 与 Serve 等 AI libraries。它的目标不是把每个人都变成分散式系统专家,而是让相同的 Python 程式可以先在笔电执行,再逐步搬到丛集。

截至 2026 年 9 月 1 日,GitHub repository 显示 Ray 已累积超过四万颗 Star,且仍在近期持续推送。对 AI Chain 读者而言,它的价值不只在于「能跑更多 GPU」,而在于提供一个清楚的工程抽象:先描述工作,再决定工作要落在哪些资源上。

先讲结论:Ray 解决的是执行层,不是所有资料与模型问题

我会先把 Ray 的角色说清楚。它不是一个新的模型,也不是单独的资料库或容器平台;它比较像把 Python 工作负载搬进分散式执行环境的底座。你仍然需要选择模型、资料格式、储存系统、部署环境与监控方式,但 Ray 可以把任务平行化、长驻状态、丛集物件与服务部署这些工作,放进一致的程式介面。

这个定位很重要,因为「分散式」不等于「一定更快」。如果任务太小,跨程序与跨机器的协调成本可能比计算本身更高;如果资料搬移太频繁,网路就会成为瓶颈;如果工作没有明确的失败边界,丛集只会把问题放大。Ray 提供的是可扩展的执行模型,效能与可靠性仍然需要由应用程式设计来负责。

Ray Core 的三个基本抽象

官方 README 用 tasks、actors 与 objects 说明 Ray Core。这三个概念足以建立第一个心智模型。

Tasks:把无状态函式交给丛集

Task 适合描述一次性的、没有长期内部状态的工作,例如处理一个档案、计算一批特征、呼叫一次模型或对一个资料分片做转换。你可以在 Python 函式上加上 @ray.remote,然后用 .remote() 送出工作。函式的回传值不是直接的 Python 物件,而是 Ray object reference;这让 runtime 有机会安排依赖与执行位置。

import ray
ray.init()
@ray.remote
def square(value):
    return value * value
references = [square.remote(i) for i in range(8)]
results = ray.get(references)
print(results)
ray.shutdown()

这段程式的重点不是把 square 写得多复杂,而是呼叫端不需要自己建立 thread pool、处理程序伫列或追踪每个工作。等到要连到丛集时,程式的分工方式仍可维持,改变的是 Ray 的初始化与资源配置。

Actors:让有状态的 worker 长驻

有些工作不能只靠一个个独立函式完成,例如模型需要载入一次后反复推论、资料库连线要保持、或某个 worker 需要累积快取。Actor 将一个 Python class 变成长驻的远端程序,方法呼叫会在这个状态所在的 worker 上执行。

import ray
ray.init()
@ray.remote
class Counter:
    def __init__(self):
        self.value = 0
    def increment(self):
        self.value += 1
        return self.value
counter = Counter.remote()
print(ray.get(counter.increment.remote()))
print(ray.get(counter.increment.remote()))
ray.shutdown()

对 LLM 或影像模型服务来说,Actor 的直觉用途是把昂贵的载入动作从每次请求中移走。不过,状态也代表新的责任:Actor 失败后如何恢复,请求是否能重试,状态是否需要持久化,以及多个 Actor 之间是否会产生一致性问题,都不能只靠 decorator 解决。

Objects:在工作之间传递不可变结果

Task 与 Actor 之间常常需要交换大型资料。Ray object store 让结果可以由多个工作透过 object reference 取得,而不是每次都把完整 Python 物件复制到呼叫端。这是 Ray 能处理资料与模型工作负载的关键之一,但它不会消除记忆体限制。大型 object 仍然需要放进可用的 object store,生命周期与资料大小仍应被监控。

AI libraries 把相同底座变成不同工作流

只使用 Ray Core 可以建立自订分散式程式,但多数 AI 团队需要的是更高阶的工作流。官方列出的 AI libraries,反映了几种常见的扩展方向。

  • Ray Data:处理可扩展的资料集与机器学习资料处理。它适合把资料读取、转换、批次推论或前处理拆成可平行的阶段。
  • Ray Train:支援分散式训练,把 worker 启动、资料分片与训练协调抽离出来。
  • Ray Tune:把超参数搜寻与实验管理变成可平行的试验集合,避免手动逐一等待。
  • RLlib:针对可扩展的 reinforcement learning 工作负载提供元件。
  • Ray Serve:把模型或 Python 函式包成可程式化的 serving 层,处理部署、路由与扩展相关问题。

这些 library 不应被看成五个互不相关的产品。它们共用 Ray 的 runtime 与资源概念,因此资料前处理、训练、调参与服务之间有机会使用一致的部署方式。另一方面,也不要为了「全套整合」而把所有 library 一次导入;先解决目前最大的瓶颈,通常比较容易维护。

从笔电到丛集,真正改变的是边界

Ray 的吸引力在于同一份 Python 程式可以从本地开始。官方 README 的安装入口是 pip install ray,文件则提供安装页与各个 library 的 getting started。第一次尝试时,我会先把范例限制在单机模式,确认任务依赖、回传结果与错误处理,再启动本地丛集或连到远端环境。

一个可靠的上手顺序如下:

1. 安装与版本固定:确认 Python、Ray 与底层 ML framework 的版本,避免本地与丛集使用不同套件。

1. 先跑最小 Task:用少量输入确认 .remote()、ray.get() 与例外处理行为。

1. 加入资源宣告:只有当工作真的需要 GPU 或多个 CPU 时,才透过 Ray 的资源选项描述需求。

1. 观察 Dashboard:官方文件提供 Ray Dashboard,可用来查看应用程式、丛集与资源使用情况。

1. 再移到丛集:把初始化、环境封装、资料路径、网路与权限一项项验证,不要把「本地能跑」当成丛集已准备完成。

若程式直接在本机执行却无法在丛集工作,常见原因不一定是 Ray API。检查项目包括 worker 是否能取得相同的 Python 依赖、资料路径是否只存在于 driver、物件是否太大、GPU 资源名称是否符合环境,以及 firewall 或 Kubernetes 网路是否阻挡节点互通。

一个 AI 推论管线可以怎么拆

假设我们要对大量文件做 embedding 或摘要,单机版本可能是读档、载入模型、回圈处理、写回结果。用 Ray 思考时,可以先把流程拆成三段:资料读取与切片由 Ray Data 处理;模型载入与批次推论由 actor 或高阶推论介面处理;结果写入则透过明确的 output sink 完成。

这种拆法比「把整个 main function 加上 remote」更健康,因为每个阶段的输入输出、资源需求与失败行为都清楚。资料阶段应能重跑,模型 worker 应避免重复载入,写入阶段应该具备幂等性。当某个批次失败时,系统才有机会只重试失败部分,而不是从头重做全部工作。

对 LLM serving 也一样。Ray Serve 可以作为模型服务的部署抽象,但模型推论延迟、batching、并发上限、GPU 记忆体与流量尖峰仍然需要压测。把请求送进丛集,不会自动让 token throughput 变好;你仍要量测 queue time、model time、first token latency、完整回应延迟与错误率。

我会先检查的五个工程风险

资料搬移

分散式任务最容易被忽略的是资料位置。若每个 worker 都要从 driver 拿一份大型资料,网路与记忆体会迅速成为瓶颈。优先让 worker 就地读取可共享的资料来源,或明确使用 object reference 管理资料生命周期。

失败与重试

重试不是免费的。对外部 API、写入资料库或非幂等操作,重试可能造成重复请求与重复结果。每个 task 或 actor 方法都应先定义可以安全重试的范围,并把失败记录带上输入识别码。

资源隔离

同一个丛集可能同时跑训练、推论与资料处理。如果没有清楚宣告 CPU、GPU、记忆体与并发限制,一个工作流就可能抢走其他服务的资源。资源配置应和 SLO、成本与优先级一起设计。

可观测性

Dashboard 是入口,不是完整监控策略。正式环境还需要集中式 logs、metrics、trace、任务输入输出摘要与版本资讯,才能回答某一批结果为何变慢或变错。

版本与环境

分散式执行会放大依赖不一致问题。Ray 的 runtime 能协助排程,但你仍要管理 runtime environment、容器映像、模型档案与资料 schema。把环境定义放进可版本控制的设定,比在每台 worker 手动安装套件可靠得多。

什么时候不该使用 Ray

如果工作负载是一个很小的同步脚本,执行时间短、资料量小、没有并发需求,直接使用 Python 或一个简单的 job runner 通常更好。导入分散式 runtime 会增加依赖、除错面与部署成本,不能只因为「未来可能变大」就提前支付。

如果团队没有能力维护丛集网路、资源配额、版本与监控,也应先评估托管服务或更窄的专用工具。Ray 的弹性很高,但弹性本身也是责任。尤其在涉及个资、金融资料或高风险模型时,权限、资料落地位置与隔离政策应先于效能决策。

结语:把扩展从重写系统,变成重新描述工作

Ray 最值得学习的地方,是它把单机 Python 与分散式 AI 应用之间的距离,缩小成一组可理解的抽象:无状态工作用 tasks,长驻状态用 actors,跨工作传递结果用 objects,再用 Data、Train、Tune、RLlib 与 Serve 对应常见的 AI 工作流。

这并不代表所有程式都应该立刻搬进丛集。我的建议是从一个有明确瓶颈、可量测输入输出、可以安全重试的工作开始,先在本地验证,再逐步加入资源宣告、Dashboard、丛集与正式监控。当程式的执行边界被描述清楚,扩展才不会只是增加机器,而是让系统在资料量、实验数量与服务流量增加时,仍然保持可理解、可观测与可维护。


参考资料