首页 文章 精选 留言 我的

精选列表

搜索[MaaS],共298篇文章
优秀的个人博客,低调大师

MaaS平台技术栈之Aibrix

Aibrix 是一个开源项目,目标是提供构建可扩展 GenAI 推理基础设施所需的”积木”。它解决的是 LLM 推理上云后的运维问题:模型怎么部署、流量怎么调度、GPU 显存怎么省、资源怎么扩缩容。 ## 1. 为什么不能直接用 Nginx 先把问题定义清楚。LLM 推理服务看起来和其他 HTTP 服务一样:前面挂个网关,后面一排后端 Pod。但把一个请求从网关打到后端,远不止”负载均衡”这么简单。 Aibrix 的官方文档把这类问题归为两个阶段: - **Prefill(预填充)**:一次性处理整个输入 prompt,计算密集,速度快。 - **Decode(解码)**:逐个生成输出 token,受内存带宽限制,速度慢。 一个关键事实是:这两个阶段的运行特征完全不同,而且 KV Cache 把请求变成了”有状态”的。如果网关把一个请求随机打到某个 Pod,而这个 Pod 里没有该请求 prompt 前缀的缓存,那么整个前缀都要重新走一遍 Prefill。这不仅浪费 GPU 算力,还直接拉高了首字延迟(TTFT)。 更麻烦的是,KV Cache 占用的显存远超想象。长上下文模型里,KV Cache 会挤占显存上限,即使最顶级的 GPU 也扛不住。所以社区出现了很多把 KV Cache”换出去”的方案(Dynamo、LMCache、Mooncake 都是这个思路),Aibrix 的 KVCache Offloading 框架也是其中之一。 Aibrix 的整套设计,核心就是应对”有状态、显存敏感、GPU 昂贵”这三个特征。下面按数据面、控制面两条线来拆开讲。 ## 2. 数据面:网关与路由 ### 2.1 网关基于 Envoy Gateway Aibrix 的网关不是自己写代理,而是构建在 **Envoy Gateway** 之上:Envoy 负责高性能转发,Aibrix 以 `external processing`(ext_proc)的方式插入一个网关插件(Gateway Plugin),在请求转发的路径上做路由决策。 网关承担几件事:模型动态发现、请求路由、限流、响应流式转发。 一个值得注意的工程点:**路由是在插件里做的,不是每个请求都去实时查询后端 Pod**。插件会维护一份高频本地缓存,通过周期性拉取 + 订阅的方式拿到各 Pod 的指标,让路由逻辑在热路径上不用阻塞查询,从而能撑到几千 QPS。 网关插件如何作为 Envoy 的 ext_proc 参与流量转发,我在另一篇《Aibrix 是如何利用 Envoy 进行 LLM API 流量转发的》里单独写过,这里不重复。 ### 2.2 路由策略是插拔式的 Aibrix 把路由算法做成了一个 `Router` 接口,内置了多套策略,分为几类: - **通用负载均衡**:`random`、`least-request`、`least-latency`、`least-kv-cache`、`least-gpu-cache`、`throughput`、`power-of-two`(两随机样本取优)。 - **KV Cache 感知**:`prefix-cache`,把请求路由到已缓存其 prompt 前缀 KV Cache 的 Pod;`prefix-cache-preble` 则同时考虑前缀命中率和 Pod 负载。 - **公平性**:`vtc-basic`,平衡按用户的 token 公平性和 Pod 利用率。 - **SLO 感知**:`slo` 系列,根据每个请求的 SLO 来选 Pod。 - **专用**:`pd`(Prefill/Decode 分离路由)、`session-affinity`(会话粘滞)。 策略的选择有明确的优先级,从高到低:config 里模型级 `lockedRoutingStrategy`(锁定后不可被请求覆盖)→ 请求头 `routing-strategy` → 所选 config profile 里的 `routingStrategy` → 启动参数 `ROUTING_ALGORITHM` 环境变量。config profile 可以嵌在模型 Pod 注解里,给每个 profile 配不同的路由策略和 QPS 上限。 想要新增一个算法,只需要实现接口、在 `init()` 里注册,就能通过请求头切换,不需要改框架。 ### 2.3 异构 GPU 推理:成本驱动 不同厂商、不同代际的 GPU 混在一个集群里很常见。异构推理解决两个问题:一是单一型号 GPU 供应紧张买不到,二是想用便宜、慢一点的 GPU 来省钱。 它由三部分组成:**LLM Request Monitoring**(监测历史请求模式)、**Heterogeneous GPU Optimizer**(决定用哪种 GPU、各多少张)、**Request Routing**(把请求路由到最优 GPU)。GPU Optimizer 会基于 profiling 数据做离线的资源分配建议,再交给 K8s 来执行。 需要说明,这个能力在官方文档里明确标注为 **Experimental**。 ### 2.4 语义路由:模型的选择 如果你有一堆模型,想按”问题类型”自动把请求分给不同的模型,Aibrix 提供了一个独立的语义路由器。它本身也是一个 Envoy ext_proc 过滤器,位于网关插件之前。 它会对每个请求做两件事: 1. 提取用户消息内容。 1. 用嵌入(embedding)分类器判断领域,或者用关键字快速命中(比如”step by step”)。 1. 把请求体里的 `model` 字段改写成真正要用的后端模型,必要时注入 system prompt、开启推理模式。 它的判断规则(routing decisions)写成 YAML,落在 ConfigMap 里,每条规则有优先级。这本质上是把”模型选择”从客户端上移到基础设施层。 ### 2.5 Prefill/Decode 分离(PD)路由 这是 Aibrix 在数据面最复杂的路由能力。前面提到 Prefill 和 Decode 特征不同,PD 分离就是把这两个阶段放到**不同的 Pod** 上,各自可以独立调优和扩缩容。它通过两组标签来组织 Pod: - `role-name: prefill|decode`:标识这个 Pod 负责哪个阶段。 - `roleset-name`:把一对 prefill、decode Pod 绑成一个”roleset”。 网关只在 prefill 和 decode 同时都可用时,才使用这个 roleset。KV Cache 通过高速互连从 prefill Pod 传到 decode Pod(GPU 场景用 SHFS,Neuron 场景用 NIXL)。decode Pod 直接把生成的 token 流式返回给客户端。 `pd` 策略下 Pod 的选取是一连串的判断:先按 prompt 长度分桶(bucket),再做 prefill 和 decode 两侧的负载失衡快速判断,最后基于评分策略选最优的 roleset。评分策略里有一种 `conductor`,会估算 prefill 的 TTFT 和 decode 的 token 间延迟(TBT),把 GPU 显存压力(缓存占用超 90% 会罚分)也算进去。 这套机制不只是给 PD 用的,Aibrix 里编排这类多角色部署用的是 **StormService**,一个三层 CRD:`StormService` → `RoleSet` → `Pods`。它支持 Rolling Update / InPlace Update,以及 replica / pooled 两种部署模式,还有 topology policy 用于把 prefill 和 decode Pod 通过亲和性放在同一个节点/可用区,减少跨域传输。这部分属于控制面编排,后面再展开。 ## 3. 控制面:编排、LoRA 与扩缩容 ### 3.1 AI Engine Runtime(引擎运行时) 控制面要和万花筒一样的推理引擎打交道——vLLM、SGLang、TensorRT-LLM 各有各的管理接口。Aibrix 的答案是 **AI Engine Runtime**,一个轻量 sidecar,把引擎的管理操作抽象统一:模型下载、加载/卸载、适配器配置、指标上报。 文档特意强调:这个 runtime 不是 Istio 那种拦截数据流量的 sidecar,数据面流量**不经过它**,它只给控制面提供管理能力。所以这叫”Management Layer”,不是服务网格。 目前它主要服务于 LoRA 部署和多引擎支持。普通场景不一定要装。 ### 3.2 高密度 LoRA:一种”模型”的管理 基础模型(base model)只有一个,但上面可以挂成千上万个 LoRA 适配器。如果为每个 LoRA 单独部署一个 Deployment,显存会浪费到没法看。 Aibrix 的做法是引入 `ModelAdapter` 这个 CRD,由 **Model Adapter Controller** 管理: - 用户提交一个 ModelAdapter,指定 `baseModel`、`podSelector`(选哪些 Pod 能挂)、`artifactURL`(LoRA 权重在哪)。 - Controller 选中匹配且 Readiness 通过的 Pod,把 LoRA 下载并加载到 vLLM 引擎里。 - 加载成功后,Controller 给这个 LoRA 创建一个 Kubernetes Service,让网关能用 adapter 的名字路由到它。 - vLLM 里用 `--max-loras` 控制单个 batch 里最多同时几个 adapter,用 `--max-cpu-loras` 控制 CPU 侧的 LoRA 缓存。 加载失败有重试机制:单 Pod 最多重试 5 次,指数退避(5s 起);某个 Pod 彻底失败了,Controller 会自动切到另一个健康 Pod。所以要求每个 base model 多副本部署,兜底容错。 `replicas` 字段很关键:省略时把 adapter 加载到**所有**匹配 Pod 上(高可用),设为 1 时只放到被调度选中的单个 Pod。 这里有个 K8s 原生设计与 LoRA 场景的冲突:K8s 里一个 Pod 通常只属于一个 Service,但一个 Pod 能挂多个 LoRA。Aibrix 通过自定义 endpoints 的方式,让承载了不同 LoRA 的同一个 Pod 同时属于多个 Service。 ### 3.3 扩缩容:Aibrix Autoscaler Aibrix 的自动扩缩容是个独立的框架,分两大类。 **基于指标(Metrics-based)**。又分三种: - **HPA**:标准 K8s HPA,按 CPU/内存/自定义指标。适合负载平稳的通用场景。 - **KPA**:Knative 风格,双重窗口(stable + panic),突发流量时快速扩容。Aibrix 做了一点增强,自己内部抓指标,不完全依赖 Prometheus 拉取。 - **APA**:Aibrix 自己的 LLM 专用扩缩器,加了波动容忍(fluctuation tolerance),减少扩缩容震荡。 它们消费的指标直接来自服务层,比如 vLLM 暴露的 `request_count`、`token_in_count` / `token_out_count`、`engine_latency_ms`、`kv_cache_size`、`engine_gpu_utilization`。 **基于优化器(Optimizer-based)**。不依赖在线阈值,而是用离线 profiling 数据 + 求解器,提前算出最优的 GPU 数量建议,再交给 K8s 执行。适合:有潮汐规律的调度任务、异构 GPU 的成本/性能优化、SLO 严格需要提前扩容的场景。 扩展方式是在 Deployment 规格里声明用哪种 autoscaler,方便实验不同策略。 ### 3.4 多节点推理与 RayClusterFleet 当模型大到单卡放不下,就需要多节点。Aibrix 复用了 KubeRay 来编排 Ray 集群,但提出了一组新的 CRD:`RayClusterReplicaSet` 和 `RayClusterFleet`(对应 K8s 原生的 ReplicaSet 和 Deployment,只是把”应用实例”从 Pod 换成了 Ray Cluster)。 设计思想是**粗细粒度分工**: - **Ray** 负责应用内部细粒度编排。每个应用实例对应一个 Ray Cluster,用 Ray 的 API 跑分布式计算(比如 vLLM 的 tensor parallel)。 - **Kubernetes** 负责外层粗粒度资源编排:拉起/销毁 Ray Cluster、滚动更新、扩缩容。 这样 K8s operator 不用去管应用内部的角色,能省掉大量复杂度。这套思路还在和 KubeRay 社区对接。 ### 3.5 StormService:服务编排 前面在 PD 路由那里碰到了 StormService,这里展开说。它是一个三层结构的 CRD,专门管理 PD 这类多角色部署的容器生命周期。 - **StormService**:顶层,定义整个服务的规格,记录 replica(RoleSet)数量、统一模板、更新策略。 - **RoleSet**:一组角色的集合,每个角色承担一个职能(prefill / decode)。 - **Pods**:实际执行推理的容器。 更新分两层: - StormService 层:`RollingUpdate`(replica 模式,滚动替换 RoleSet)或 `InPlaceUpdate`(pool 模式,就地更新,不重建 RoleSet)。 - Role 层:`Parallel`(同时更新)、`Sequential`(逐个更新)、`Interleaved`(交错同步推进)。 部署模式则由 `spec.replicas` 自动决定:>1 是 **Replica Mode**(每个 RoleSet 是独立副本),=1 是 **Pooled Mode**(每个 role 独立可扩展,prefill/decode 组成共享资源池)。注意 Pooled Mode 的独立扩缩容目前还不支持,官方是有 Issue 记录的,只能用 RoleSet 里的 replicas 手动调。 `ControllerRevision` 用于版本记录和回滚。Topology policy 用于控制 Pod 亲和性——把 prefill 和 decode Pod 放到同一个拓扑域,减少跨节点/跨可用区的 KV 传输。 ## 4. 显存与缓存:最有价值的部分 ### 4.1 KV Cache 卸载:分层的显存换出去 单节点内置的 KV Cache 有硬伤:容量受显存限制、存储引擎绑定导致无法跨实例共享、难以支撑 KV 迁移和 PD 分离。Aibrix 从 v0.3.0 引入 KVCache Offloading 框架,默认有一层 L1 + 可选一层 L2: - **L1 DRAM 缓存**:默认开启。把 KV Cache 从 GPU 显存换到 CPU 内存,虽然不能跨引擎共享,但能大幅缓解显存压力,性能提升明显。适合”更看重缓存容量而非跨节点共享”的场景。 - **L2 远程缓存**:可选,走分布式 KV Cache 服务(后端的比如字节的 InfiniStore),支持跨多个节点横向扩。这是实现**跨引擎 KV 复用**的基础,多个引擎共享一个分布式缓存,公共 prompt 前缀不用重复计算。 数据面通过 **Offloading Connector** 和推理引擎集成,用优化过的 CUDA kernel 加速 GPU↔CPU 的数据搬运;多层缓存管理器负责在不同存储层之间做负载均衡。驱逐策略可插拔(LRU、S3FIFO 等),后端存储也可插拔(InfiniStore 等)。新增后端只需实现 `Connector` 接口。 ### 4.2 KV Cache 事件同步 前缀路由的前提是网关知道”哪个 Pod 持有哪些前缀的 KV Cache”。Aibrix 用 KV Cache 事件同步来维护这个全局状态: - vLLM 实例通过 ZMQ pub/sub 发布 KV Cache 事件(`BlockStoredEvent`、`BlockRemovedEvent`)。 - 一个 KV Event Manager 订阅这些事件,维护一个 Sync Prefix Cache Indexer 的全局前缀状态。 - 网关路由据此做命中决策。 这个机制对 tokenizer 有严格要求:必须用 remote tokenizer,保证网关和 vLLM 的 token 化一致,否则前后缀对不上。 ### 4.3 ModelClaim:高密度 GPU 运行时池 ModelClaim 是另一个实验功能(文档明确标注 experimental),思路很不一样:与其一个模型一个 Deployment,不如先准备一个”暖”GPU Pod 池,然后多个模型共用这些 Pod。 优点是把几个独立管理的模型引擎塞进同一个暖 Pod 里,通过 kvcached 框架在共置引擎之间做弹性 KV Cache 内存分配,还能把空闲的 vLLM 引擎置为 sleep 模式(查询时唤醒,返回带 `Retry-After` 的 503)。这对于”模型很多、流量都很稀疏”的场景,能省下大量显存。 不过限制也明确:目前每个 claim 只支持一个引擎副本、固定 GPU 拓扑、要求专门的 kvcached runtime 镜像。 ## 5. 异步批处理:Batch API 如果你的任务不需要实时返回,Aibrix 提供了兼容 OpenAI Batch API 的异步批处理能力:上传 JSONL 文件,后台用 K8s Job 处理,之后拉取结果。 链路是:Files API(上传文件)→ Batch API(创建 batch job)→ Kubernetes Job Scheduler(起 worker)→ 存储后端(S3/TOS/MinIO/Redis/本地)。每个请求带 `custom_id` 用于精确对结果。状态流转 `validating → in_progress → finalizing → completed`,失败/取消/过期也有对应状态。纯自托管,用现有基础设施,不按请求计费。 ## 6. 小结 Aibrix 的组件不少,但如果只看主线,它就是两件事: 1. **数据面**:一个构建在 Envoy Gateway 上、路由算法可插拔的 LLM 网关,懂 KV Cache 前缀、懂 PD 分离、懂多模型(含语义路由)选择。 1. **控制面**:一堆 CRD 和 controller,帮你管 LoRA 适配器、管多角色部署(StormService)、管多节点推理(RayClusterFleet)、管 LLM 专用的扩缩容。 最值得研究的其实不是某一块具体功能,而是”KV Cache 是有状态的,网关和调度器要怎么感知它”这一整套工程思路:路由前缀匹配、事件同步、显存分层换出、跨引擎复用,环环相扣。 还有一点要注意:这篇博客里凡是涉及异构 GPU、ModelClaim、Pooled Mode 独立扩缩容的地方,官方文档都标注了实验状态或者有待实现,真上生产前得对着 release notes 确认版本支持范围,别被”看起来很好”冲昏头。

优秀的个人博客,低调大师

MaaS平台技术栈之Ray

Ray 是一个面向 Python 的分布式计算框架,最初作为 UC Berkeley RISELab 的研究项目发展而来,如今已演进为覆盖数据加载、分布式训练、超参调优、在线推理等环节的完整 AI 计算生态。本文从核心抽象、总体架构、底层机制与上层生态四个层面梳理 Ray 的设计与实现。 ## 一、 引言:Ray 解决的问题 在 Ray 出现之前,机器学习流水线通常需要拼接多套系统:用 Spark 处理数据,用 Horovod 进行分布式训练,用 Celery 执行异步任务,用 Kubernetes 部署在线推理服务。这种组合带来几个问题: 1. 多套系统的学习与运维成本叠加。 1. 系统之间的数据交换依赖磁盘或网络序列化,存在额外开销。 1. Python 本身缺少细粒度的分布式原语。 Ray 提供了一套统一的编程模型:开发者只需在普通 Python 函数或类上添加 `@ray.remote` 装饰器,即可将单机代码扩展到多节点集群。它同时支持无状态的任务(Task)和有状态的服务(Actor),用同一套底座承载不同类型的 AI 计算负载。 --- ## 二、 Ray 的核心概念与编程模型 Ray 的三个核心抽象是 **Task(任务)、Actor(参与者)和 Object(对象)**。 ### 1. Task(无状态任务) Task 是 Ray 调度的基本单位。任何 Python 函数加上 `@ray.remote` 装饰器后即成为远程函数。调用该函数(通过 `.remote()`)时,Ray 会在集群的某个节点上异步执行它,并立即返回一个 ObjectRef(对象引用),而非阻塞等待结果。 ### 2. Actor(有状态参与者) Task 是无状态的。若需要在多次操作之间保持状态(例如维护模型权重或数据库连接),则使用 Actor。Python 类加上 `@ray.remote` 后,实例化时会在集群中创建一个常驻进程,即 Actor。对 Actor 方法的调用同样是异步的,且在同一 Actor 内按调用顺序串行执行。 ### 3. Object 与 ObjectRef(分布式对象) Task 的返回值以及通过 `ray.put()` 显式放入集群的数据都属于 Ray 的 Object。Object 不可变(Immutable),存储在 Ray 的分布式内存对象存储中。Ray 返回一个 `ObjectRef`,用于跨节点安全地引用该数据,而无需主动拷贝。 ### 概念关系连线图 下面的关系图展示了这三个概念如何协同工作的: ``` graph TD UserCode[客户端/驱动程序] -->|提交无状态计算| Task1(Ray Task) UserCode -->|提交无状态计算| Task2(Ray Task) UserCode -->|创建状态副本| Actor[Ray Actor] Task1 -->|输出| Obj1[(Object 1)] Task2 -->|输出| Obj2[(Object 2)] Actor -->|维护内部状态| State{State} Actor -->|处理请求并输出| Obj3[(Object 3)] Obj1 -.->|作为输入引用 ObjectRef| Task2 Obj2 -.->|作为输入引用 ObjectRef| Actor style UserCode fill:#f9f,stroke:#333,stroke-width:2px style Task1 fill:#bbf,stroke:#333 style Task2 fill:#bbf,stroke:#333 style Actor fill:#bfb,stroke:#333 ``` 上图刻画了 Ray 编程模型中三类核心抽象之间的完整数据流,可以从以下三个层次来理解: 图中的粉色节点客户端/驱动程序是整个计算图的数据源,它通过两种方式向集群提交工作:一是将无状态的计算逻辑作为 **Task** 提交(对应 `Task1`、`Task2` 两条实线箭头,标注为”提交无状态计算”);二是通过 `@ray.remote` 装饰器创建带内部状态的 **Actor**(对应”创建状态副本”实线箭头)。值得注意的是,所有提交动作都是**异步**的——`Task.remote()` 与 Actor 方法调用都会立即返回,不会阻塞客户端。 每个 Task 执行完毕后,其返回值会被写入 Ray 的分布式内存对象存储,形成不可变的 **Object**(图中圆柱体 `Obj1`、`Obj2`、`Obj3`)。客户端拿到的只是指向这些对象的 **ObjectRef** 句柄,而非数据本身。`Task1` 的输出成为 `Obj1`,`Task2` 的输出成为 `Obj2`,这两个对象随后通过虚线箭头(”作为输入引用 ObjectRef”)分别被 `Task2` 和 `Actor` 消费——这正是 Ray 数据依赖的传递方式:**Task 之间无需直接通信,只需引用彼此输出的 ObjectRef 即可建立依赖关系**,Ray 调度器会根据这些依赖自动决定任务的执行顺序与数据放置位置。 绿色节点 `Actor` 的行为与 Task 有本质区别:它在集群中作为常驻进程运行,通过 `State`(菱形节点”维护内部状态”)保存跨调用持久化的成员变量——例如模型权重、数据库连接等;同时它对外”处理请求并输出” `Obj3`。由于 Actor 方法在同一实例内按调用顺序串行执行,天然提供了有状态服务所需的并发一致性。 实线箭头表示”提交/创建/输出”这类**触发型关系**(客户端→Task/Actor,Task/Actor→Object);虚线箭头表示”作为输入引用”这类**依赖型关系**(ObjectRef→Task/Actor);菱形 `State` 节点则代表 Actor 特有的**内部状态保持关系**。理解了这幅图,就把握住了 Ray 的核心设计理念:**无状态计算用 Task、有状态服务用 Actor、跨节点传数据一律走 ObjectRef**。 --- ## 三、 Ray 的总体架构设计 Ray 的架构采用去中心化与中心化相结合的设计。整体由三个主要部分构成:Global Control Store (GCS)、Raylet 和 Client/Worker。 ### 1. 架构示意图 ``` graph TB subgraph HeadNode["Head Node (头节点)"] GCS[(Global Control Store)] Raylet_Head[Raylet] Driver[Driver Process] end subgraph WorkerNode1["Worker Node 1"] Raylet1[Raylet] Plasma1[(Plasma Object Store)] Worker1_A[Worker Process] Worker1_B[Worker Process] Actor1[Actor Process] Raylet1 Plasma1 Worker1_A Raylet1 Worker1_B Raylet1 Actor1 Raylet1 end subgraph WorkerNode2["Worker Node 2"] Raylet2[Raylet] Plasma2[(Plasma Object Store)] Worker2_A[Worker Process] Raylet2 Plasma2 Worker2_A Raylet2 end GCS Raylet_Head GCS Raylet1 GCS Raylet2 Plasma1 |对象的 P2P 传输| Plasma2 style HeadNode fill:#EDE7F6,stroke:#7E57C2,stroke-width:2px,color:#4A148C style WorkerNode1 fill:#E3F2FD,stroke:#42A5F5,stroke-width:2px,color:#0D47A1 style WorkerNode2 fill:#E0F2F1,stroke:#26A69A,stroke-width:2px,color:#004D40 style GCS fill:#7E57C2,stroke:#4527A0,color:#FFFFFF,stroke-width:2px style Raylet_Head fill:#B39DDB,stroke:#5E35B1,color:#311B92 style Raylet1 fill:#B39DDB,stroke:#5E35B1,color:#311B92 style Raylet2 fill:#B39DDB,stroke:#5E35B1,color:#311B92 style Driver fill:#FFF176,stroke:#F9A825,color:#F57F17 style Worker1_A fill:#90CAF9,stroke:#1976D2,color:#0D47A1 style Worker1_B fill:#90CAF9,stroke:#1976D2,color:#0D47A1 style Worker2_A fill:#90CAF9,stroke:#1976D2,color:#0D47A1 style Plasma1 fill:#A5D6A7,stroke:#43A047,color:#1B5E20 style Plasma2 fill:#A5D6A7,stroke:#43A047,color:#1B5E20 style Actor1 fill:#FFAB91,stroke:#E64A19,color:#BF360C ``` 上图展示了 Ray 集群的基本拓扑。整个集群由一个 Head Node 和多个 Worker Node 组成,图中以两个 Worker Node 为例。三类节点、三类连线,下面分别说明。 先看节点。**Head Node** 承担管理职责,内部运行三个进程: - **GCS**:中央元数据服务,保存集群级状态(节点健康、Actor 位置、Placement Group 等)。 - **Raylet**:头节点自身的 Raylet,负责本节点的资源管理与任务调度。 - **Driver**:客户端驱动程序,是用户代码提交 Task、创建 Actor 的入口。 **Worker Node** 负责实际的计算,每个节点运行以下进程: - **Raylet**:节点内的调度中枢,同时是与 GCS 通信的桥梁。 - **Plasma**:节点内的内存对象存储,存放本节点 Worker 产生的 Object。同一节点上的 Worker 通过共享内存读写该存储,无需跨进程拷贝。 - **Worker Process**:执行无状态 Task 的工作进程,一个节点可以有多个(如 Worker Node 1 中的 `Worker1_A`、`Worker1_B`)。 - **Actor Process**:承载有状态服务的常驻进程。并非每个节点都必须有 Actor——图中 Worker Node 1 有一个 Actor,Worker Node 2 只有 Worker,没有 Actor。 再看连线,图中包含三类通信关系: - **GCS 与所有 Raylet 之间的双向连线**(GCS ↔ Raylet_Head / Raylet1 / Raylet2):这是控制面通信。每个 Raylet 启动时向 GCS 注册自身,之后持续上报心跳与资源使用情况;各节点也通过这条链路查询 GCS 保存的 Actor 位置注册表等集群级信息。 - **节点内部 Raylet 与 Plasma、Worker、Actor 之间的双向连线**:这是数据面与执行面的通信。Raylet 把任务分发给本节点的 Worker 并接收结果;Worker/Actor 读写 Object 时经由 Raylet 协调对应的 Plasma 对象存储。因此 Raylet 是节点内唯一的调度与协调入口。 - **Plasma1 与 Plasma2 之间的虚线双向连线**:标注为”对象的 P2P 传输”。当一个节点的 Task 需要读取存储在另一个节点上的 Object 时,由本节点的 Object Manager 直接从远端 Plasma 拉取数据,不经过 GCS 中转。这也是跨节点数据流的主要通道。 整体来看,GCS 与 Raylet 构成星形的控制面,各节点内部的 Raylet、Plasma、Worker/Actor 构成自洽的执行面,节点之间的对象传输走 P2P 链路,三者各司其职、互不干扰。 ### 2. 核心组件解析 #### 2.1 Global Control Store (GCS) GCS 是 Ray 集群的中央元数据服务。早期版本基于 Redis 实现,后续版本改为自研的独立服务。它存放的是集群级信息,包括: - 各节点的健康状态与资源可用量。 - Actor 的位置注册表。 - Job 与 Placement Group 等元数据。 注意,单个任务(Task)的运行状态并不集中存在 GCS 里,而是由创建它的 worker 通过所有权模型自行管理(见下文第四节的“对象所有权模型”)——GCS 只负责集群层面这些跨节点共享的元数据。 GCS 简化了容错实现:节点失效后,新节点通过查询 GCS 即可重建集群状态。 #### 2.2 Raylet(节点管理器) 每个计算节点上运行一个 Raylet 守护进程,负责节点内的资源管理与任务调度,包含两个子组件: - **Node Manager(节点管理器)**:接收来自本地 Worker 或其他 Raylet 的任务,根据本地资源情况分配给本地 Worker 进程执行;本地资源不足时,将任务转发(Spillback)给其他节点。 - **Object Manager(对象管理器)**:管理当前节点的对象存储,并在需要时从其他节点拉取对象数据(P2P 传输)。 #### 2.3 内存对象存储 Ray 通过共享内存实现对象存储。同一节点上的不同 Worker 读取同一份数据时,往往可以避免数据在进程间复制——尤其是 numpy 这类基于缓冲区的对象,做的是零拷贝(Zero-copy)访问;一般 Python 对象仍需反序列化。这一机制对海量数据与模型预加载场景尤为重要。 > 说明:早期版本中对象存储由独立的 Plasma 进程提供;Ray 2.x 起 Plasma 被移除,对象存储改为内嵌于 Raylet 进程内实现,但共享内存与零拷贝的语义保持不变。上图中的 Plasma 对应的是这一对象存储组件。 --- ## 四、 关键技术细节剖析 在相对简单的 API 背后,Ray 在底层做了若干针对性优化。以下分析几个重点机制。 ### 1. 分布式的自下而上调度策略(Bottom-up Scheduling) 与 Hadoop/Spark 这类由中心调度器统一分配任务的“自上而下”模式不同,Ray 采用分布式调度,并配合自下而上的 Spillback 机制,以避免中心调度器在高并发下成为瓶颈: 1. Driver 提交 Task 时,先提交给本地节点的 Raylet(Node Manager)。 1. 本地 Raylet 优先尝试在本地执行该 Task(检查 CPU/GPU 资源与依赖的 Object 是否就绪)。 1. 若满足条件,直接调起本地 Worker 执行,无需跨节点网络通信。 1. 若本地无法执行(资源耗尽,或依赖数据位于远端且体积较大),本地 Raylet 将 Task 溢出(Spillback)给有剩余资源的远端 Raylet。 该策略降低了任务调度的延迟:单机场景下本地 Task 的提交开销很小(亚毫秒级),吞吐量也高于中心化调度。 ### 2. 对象所有权模型(Ownership Model)与分布式 GC 早期 Ray 将对象元数据集中维护在 GCS 中,带来较大的通信开销。后续版本引入所有权模型:**调用创建任务 API 的 Worker 即“拥有”该任务返回值的 ObjectRef**。 Owner 进程负责: - 记录 Object 的位置、状态与生命周期。 - 在 Task 失败时决定重试逻辑(而非由全局中心决定)。 - 管理垃圾回收(GC):当作用域内的 `ObjectRef` 在 Python 层的引用计数归零后,Owner 通过内部 RPC 通知所在节点的 Raylet,从对象存储中删除该 Object。 当对象总量超过内存容量时,Ray 的对象存储支持 **Object Spilling(对象溢出)**:按 LRU 策略将最冷的数据异步序列化并持久化到本地磁盘或云存储(如 AWS S3),对上层应用透明。 ### 3. Actor 的生命周期与容错恢复(Fault Tolerance) Ray 的容错分为数据容错与计算容错: - **Lineage Reconstruction(血统重建)**:对于无状态 Task,若节点宕机导致 Object 丢失,只要 Owner 仍在,Owner 知道该 Object 的生成方式,可重新提交 Task 重新计算。 - **Actor 重启机制**:当包含 Actor 的节点宕机,GCS 通过心跳检测到失效后,会在其他存活节点重新调度并启动该 Actor 进程。但这只重建了 Actor 本身,**不保证其运行时的内部状态(如成员变量)**,业务级容错需要开发者结合 Checkpoint 机制自行实现。 --- ## 五、 Ray 的上层生态系统 Ray 的价值不仅在于底层的 Core,还在于其官方构建的覆盖机器学习各环节的组件。 ``` mindmap root((Ray Ecosystem)) Data Processing [Ray Data] 分布式数据读取 流式Map/Filter处理 Model Training [Ray Train] 桥接PyTorch DDP 桥接DeepSpeed/Horovod 容错训练 Tuning [Ray Tune] 超参数搜索 ASHA/PBT等高级统筹算法 Serving [Ray Serve] 模型多路复用 请求批处理 (Batching) 复杂的推理DAG图 (Ensemble) RL [RLlib] 强化学习/PPO算法 分布式RL训练 ``` 1. **Ray Data**:负责数据加载与预处理。它不取代 Spark 处理复杂 SQL 查询,而是将清洗后的特征数据或大模型语料以 Pipeline 形式高效喂入 GPU 进行训练。 1. **Ray Train**:对 PyTorch 或 TensorFlow 的分布式训练进行封装,屏蔽节点变更导致的 RANK 更新等细节,并集成 Ray 的容错机制。 1. **Ray Serve**:面向复杂模型流的在线推理框架。实际推理可能涉及多个模型(如文生图、文生文、审核模型)组成的 DAG。Ray Serve 可为 DAG 上不同节点分配不同资源(如文本节点用 CPU、图像节点用 GPU),实现资源差异化分配。 1. **RLlib**:面向强化学习的分布式训练库,内置 PPO 等常用算法,用于大规模 RL 训练场景。 --- ## 六、 生产环境实践:KubeRay 的结合 Ray 自带资源管理能力,但企业级数据中心普遍以 Kubernetes 作为资源底座。当前主流的部署方式是 **KubeRay**。 KubeRay 是一个 Kubernetes Operator,通过 `RayCluster` 这个 CRD(Custom Resource Definition)管理 Ray 集群: - 开发者提交 YAML 声明集群规模,例如”1 个 Head、10 个带 GPU 的 Worker”。 - KubeRay 据此拉起对应的 Pods。 - **自动扩缩容(Autoscaling)**:这里的扩缩容分几个层级。当 Ray 调度器检测到大量 Task 因 GPU 不足而排队时,**Ray Autoscaler**(运行在 head Pod 内的 sidecar 容器)会请求新增 worker Pod——具体做法是递增 `RayCluster` CR 里的 `replicas` 字段;随后 **KubeRay operator** 按新的 `replicas` 创建对应的 Ray worker Pod。如果 K8s 集群本身的节点(虚拟机)也不够,则由用户自行配置的 **Kubernetes Cluster Autoscaler** 去云厂商购买新的底层节点。这样从 Task 排队一直到云底座 IaaS,形成了一条完整的弹性伸缩链路。 --- ## 七、 总结 Ray 通过分布式调度、共享内存对象存储与统一的 Task/Actor 抽象,将机器学习流水线中的多套系统收敛到单一框架。在 LLM 时代,Ray 被广泛用于大规模分布式训练、RLHF 与多模态模型的并行推理部署。 理解 Ray 的底层机制,有助于编写高性能的并行 Python 代码,也有助于构建高可用、高吞吐的 AI 基础设施。随着算力协同需求的增长,Ray 已成为 AI 云原生基础设施中的常见组成部分。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册