假如你是『智慧餐厅』的老板。开业第一天只有一位厨师,生意冷清时绰绰有余;可当外卖平台把你的招牌菜推上首页,门口瞬间排起长队,一位厨师累到冒火,出菜还是赶不上客流。你开始琢磨三件事:忙时临时加聘几位厨师(自动扩缩容);把点单分给不同档口,让川菜师傅做川菜、粤菜师傅做粤菜(流量路由);研发出一道新菜,先只让 3 桌熟客试吃,确认没人吃坏肚子再全店推广(金丝雀发布)。这套餐饮管理智慧,放大到『大模型推理』的世界,就是 Ray Serve 要做的事:把一个需要 GPU 的大模型,变成一支能自动增减人手、能灵活分流、能安全换新的『AI 厨师团队』。本文不聊晦涩术语,只把分布式推理讲成一家餐厅的经营故事,带你从零掌握 Ray Serve 的核心实战技能。
学会 Ray Serve,等于拿下『大模型高并发推理』的实战钥匙,是推理平台工程师和大模型应用架构师的必备功底。
可视化演示
展示最近最少使用(LRU)页面置换算法的工作过程:访问页面时命中则更新使用时间,未命中且帧满则淘汰最久未使用的页面。
本节配有交互动效(进入视口后自动加载,约 3KB)。
LRU页面置换 访问页面时检查是否在帧中 命中则更新最近使用时间 淘汰 = argmin(last_used) 缺页且帧满时淘汰最久未使用页
推理服务基础
『智慧餐厅』开业第三个月,中午 12 点的排队队伍从门口排到了街角。你发现一个残酷事实:再快的厨师,一秒钟也只能出一份菜。单个厨师的出菜速度,决定了每一桌顾客的等待时延;而餐厅一天能接待多少客人,却由厨师数量决定。你想明白了两件事:想减少客人等待,得让单个厨师变快;想增加接待总量,得多请几位厨师。
在线推理同样面临这对矛盾。大模型推理有『时延』和『吞吐』两个指标:时延是一个用户从发问到收到回答的秒数,吞吐是系统每秒能处理的请求数。前者靠优化模型、升级 GPU 来压,后者靠并行——把请求分散到多个进程、多块 GPU 上同时算。
Ray Serve 把『多进程并行』包装成了非常自然的开发体验:你不需要自己写线程池、消息队列,只要描述『这是个推理服务,我要它有几个副本』,剩下的分发交给框架。两个重要的工程细节是批处理请求和请求队列:批处理是把多条 prompt 拼成一个 batch 喂给 GPU,好比一锅出多份菜;请求队列则在高峰期把多出来的请求暂时存起来,好比门口拿号等位。
所以你想让餐厅接待更多客人,先别急着让厨师练出火影手速,而是多请几位厨师——这就是水平扩展,也是 Ray Serve 的第一课。
- R(单副本吞吐)单个副本每秒能处理多少个请求,由模型推理速度和硬件决定。
- N(副本数量)同一个模型起了多少个独立进程,每个进程就是一位厨师。
- T(总吞吐)整支厨师团队每秒合力完成的请求总数。
Ray Serve架构
餐厅要从 20 桌扩到 200 桌,光加厨师会乱成一锅粥。你需要一个店长负责排班调度,需要传菜员把订单送到对应档口,还需要每一位厨师只专注自己的灶台。角色分明,厨房才不会在晚高峰互相撞车。
Ray Serve 不是从零发明的分布式系统,它站在 Ray 的肩膀上。Ray 是一个通用的分布式计算框架,好比餐饮集团的『总部』,负责管理集群里所有机器的资源调度;Serve 是总部旗下专门做『在线服务』的部门,专攻低时延、高可用的推理请求。两者关系是:Serve 内嵌在 Ray 集群中,你只要启动了 Ray,Serve 天然就生活在里面。
集群里有两类核心角色。Head 节点是店长:保存集群状态、负责 Actor 调度、承接 serve.run 等管理命令;Worker 节点是普通厨师:数量可以弹性增减,真正执行模型推理。客户端请求进来后,由内建的 HTTP Router 做负载均衡,把请求转发到某个副本的 HTTP 端口——相当于领位伙计把顾客带到有空位的桌前。控制面(排班、状态管理)与数据面(传菜、张量传输)分离,是这套架构在大规模下不堵车的关键。
店长只管排班,厨师只管做菜,两者不互相抢灶台。正是这套『各司其职』的架构,让 Ray Serve 能从小餐厅一路开到全国连锁。
- λ(请求到达率)每分钟到店排队的顾客数,也就是系统每秒收到多少请求。
- μ × N(总服务能力)N 位厨师每分钟总共能做完的菜数,表示整个集群的算力。
- U(系统利用率)服务台被占用的比例,超过 0.8 就要警惕队列无限膨胀。
Deployment定义
你决定把招牌菜固化成标准菜谱:配料多少克、火候几分、出盘时间,全部白纸黑字写进 SOP。有了菜谱,不管今天来的是哪位厨师,做出来的都是同一个味儿。Deployment 就是 AI 世界的标准菜谱。
在 Ray Serve 里,定义一个推理服务的最低门槛,是在一个类上贴装饰器 @serve.deployment。装饰器告诉框架:这个类未来会被部署成一组副本,每个副本都能接收 HTTP 请求。类里只需要实现两个关键方法:init 负责把大模型加载进内存(开店前把菜备好放冷柜),call 负责处理每一次具体请求(来一位客人炒一份菜)。
Deployment 还能声明资源需求:ray_actor_options 里可以指定 num_cpus、num_gpus,框架会像店长排班一样,把副本调度到有对应闲置资源的机器上。多个 Deployment 还能互相调用,比如『入口服务』调用『翻译模型』和『生成模型』,最后用 .bind() 把它们组装成一个可运行的 app。bind 有点像是把菜谱挂上今日菜单,serve.run 负责正式开张。
菜谱写得越标准,餐厅开到第 100 家也是同一个味道。Deployment 把『模型推理』变成了可复制、可管理、可调度的服务单元,这是后面所有玩法的地基。
- λ(请求到达率)每秒有多少新订单进入系统,代表餐厅门口的新客流。
- W(平均处理时间)一道菜从下单到上桌的平均耗时,对应模型单次推理耗时。
- L(在途请求数)厨房里同时正在做的菜的总量,等于每个副本的在途请求之和。
自动扩缩容
餐厅装了一套客流感应系统:排队超过 15 桌,系统自动向兼职群发招募,加聘 3 位厨师;队伍清零后,自动让多余厨师下班回家。这套感应系统的学名,叫自动扩缩容。
autoscaling_config 里有三个核心旋钮。min_replicas 是保底厨师数:哪怕一个客人都没有,也至少留几个人守着灶台;max_replicas 是团队上限:防止客流异常时把餐厅挤爆,也保护你的 GPU 预算;target_num_ongoing_requests_per_replica 是最关键的『目标并发度』——每位厨师手头同时有几个订单最舒服,超过这个数说明累瘫了要加人,长时间远低于这个数说明养闲人了要减人。
框架会周期性采样『每个副本的在途请求数』,把平均值和目标并发度比较,再决定扩缩。注意两件事:一是扩容不是瞬时的,有 upscale_delay_s 观测窗口,且新副本要冷启动加载模型,通常需要几十秒;二是缩容要更保守,因为拉起模型很贵,downscale_delay_s 往往设得更大,防止客流一抖动副本就反复横跳。
客流感应玻璃门替你省下了真金白银:高峰不丢客,闲时不烧卡。这正是大模型推理服务最值钱的成本控制能力。
- Q(当前在途请求)现在厨房里所有正在做的菜的总量,来自 Little 定律里的 L。
- C_t(目标并发度)每个厨师手里同时最适合拿几个订单,太少浪费人力,太多手忙脚乱。
- ⌈·⌉(向上取整)排队菜品 7 份、每厨师目标 2 份,就需要 ⌈7/2⌉ = 4 位厨师,宁可多一个不能少一个。
流量路由策略
店里现在有川菜档口和粤菜档口,门口摆着红绿两色号牌。领位伙计怎么决定把顾客带到哪个档口?答案是看号牌比例——今天想多推川菜,就把红色号牌多放几张进牌堆。
Ray Serve 支持两类路由方式。一类是路由前缀(route_prefix):不同路径对应不同 Deployment,比如 /v1/chat 走聊天模型,/v1/embed 走向量模型,互不干扰;另一类是权重分配:同一个路径背后挂着多个版本,每个版本配置一个权重,框架按比例把请求转发过去。
权重路由是 A/B 测试和灰度发布的基石。比如 stable 版本权重 95,canary 版本权重 5,就有 5% 的请求打到新版本上,用来收集真实用户反馈。负载均衡由内建的 HTTP Router 完成,它会在多个副本之间均匀分发请求,并把不健康的副本自动摘除。多模型路由同样实用:把不同规格模型挂在不同前缀,或按权重组合,一套集群就能同时服务多种业务。
领位伙计手里的牌比例一变,客流方向就变,厨房结构完全不用动。流量路由就这样轻巧地控制着『谁在服务谁』。
- W_i(第 i 个版本权重)这个档口在传菜口挂出的『接单份额』数字,设定流量热度。
- Σ W_j(权重总和)所有档口份额加在一起,构成 100% 的客流总量。
- P_i(流量比例)每 100 个请求里会有几个被领到这个档口。
金丝雀发布
新菜『麻辣冰淇淋』研发完成,你不敢直接上架。你让店员先请 3 桌胆子大的老顾客试吃,记录每桌反应和有没有人进医院;第一轮安全,扩大到 10 桌;再安全,50 桌……直到所有人都拍手叫好,才把菜单上的旧甜品全部换掉。这招在互联网界叫金丝雀发布,第一批试吃员就是金丝雀。
金丝雀发布的目标是:在真实流量下验证新版本模型,同时把爆炸半径控制在最小。Ray Serve 里你可以通过权重路由一步步完成:先把新版本权重设成 5,观察错误率和时延;一切正常,调到 20、50、100,完成全量切换。重点不是新版本是否部署了,而是『每一步都有健康检查,任何一步异常都能回滚』。
完整流程分四步:部署金丝雀(新版本拥有独立副本和权重)→ 放量观察(监控 p99 时延、错误率、GPU 利用率)→ 逐步切换(权重梯度上调)→ 稳定后下线旧版。回滚机制同样关键:只要异常,立刻把权重切回 100% 给稳定版,旧版本随时待命,比重部署快得多。
金丝雀用生命帮矿工试毒,新模型用 5% 流量帮你的线上服务试错。尊重这套流程,你在生产环境换模型时就不再是赌博。
- P_0(初始金丝雀流量)通常从 5% 开始小范围试水,爆炸半径最小。
- r(增速)每轮健康检查无异常后,可以增加的流量百分比。
- t(观察轮次)健康检查通过一次,轮次加一,流量就往上抬一级。
AI 应用场景
大模型聊天服务扛住晚高峰
阶段:模型部署与推理
解决的问题:传统单机方案把 Qwen 模型挂在 8 卡 GPU 上,晚高峰并发 200 时直接 502。Ray Serve 把模型部署成 5 个副本并开启自动扩缩容,目标是高峰不丢请求、低谷只留 1 个副本省电费,像餐厅晚高峰自动加聘兼职厨师一样。
具体案例:某 AI 创业公司用 serve.run 启动 Qwen2-7B,autoscaling_config 设置 min_replicas=1、max_replicas=5、target_num_ongoing_requests_per_replica=4,配合请求队列扛住了 1000 QPS 的夜间峰值。
大模型版本灰度上线
阶段:模型版本管理与发布
解决的问题:新微调版本直接全量替换,一旦出现幻觉失控就是全站事故。用权重路由把新版本放在 5% 流量上观察一小时,再逐步上调,相当于新菜先请 3 桌熟客试吃。
具体案例:客服机器人团队微调后的模型,通过 Ray Serve 按 5% → 20% → 100% 三批切换,中途发现回答格式异常,立即把权重切回旧版,全程零事故。
多模型规格按用户分级路由
阶段:多模型服务编排
解决的问题:免费用户用 7B 小模型省钱,VIP 用户用 72B 大模型保质量。用 route_prefix 分别暴露 /v1/free 和 /v1/vip,一套 Ray 集群同时盘活两个模型,像川菜档口和粤菜档口共用一家后厨。
具体案例:某出海 SaaS 公司将 Embedding 模型和 Chat 模型部署在同一 Ray 集群,按前缀分流,GPU 利用率从 30% 提升到 70%。
Python 演示
import requestsimport rayfrom ray import serve @serve.deployment( num_replicas=1, autoscaling_config={ "min_replicas": 1, "max_replicas": 5, "target_num_ongoing_requests_per_replica": 2, "upscale_delay_s": 3, "downscale_delay_s": 30, }, ray_actor_options={"num_cpus": 2},)class LLMInference: def __init__(self, model_name: str): self.model_name = model_name async def __call__(self, request): payload = await request.json() prompt = payload.get("prompt", "") return {"model": self.model_name, "answer": "echo: " + prompt} app = LLMInference.bind(model_name="qwen2-7b")serve.run(app, host="0.0.0.0", port=8000) for i in range(6): resp = requests.post( "http://localhost:8000/", json={"prompt": f"请介绍一下第 {i} 个问题"}, timeout=10, ) print(resp.json()) print(serve.status())
@serve.deployment(...)把类包装成可部署的推理服务单元,相当于写下一份标准菜谱。"min_replicas": 1, "max_replicas": 5设置厨师团队的保底人数与上限,控制成本和容量。"target_num_ongoing_requests_per_replica": 2每个副本同时在途 2 个请求最舒适,超过就扩容。"upscale_delay_s": 3连续超标 3 秒才触发扩容,防止客流毛刺引起抖动。"downscale_delay_s": 30空闲要观察 30 秒才缩容,避免副本反复横跳。def __init__(self, model_name: str)构造函数里加载模型,开店前备好菜,只做一次。async def __call__(self, request)每个请求进来都会走这里,相当于顾客点菜才下锅。app = LLMInference.bind(model_name="qwen2-7b")把菜谱挂上今日菜单,组装成应用。serve.run(app, host="0.0.0.0", port=8000)正式开张,监听 8000 端口对外服务。requests.post(...)客户端模拟用户发请求,验证整套服务链路。
常见误区
误区我觉得并发扛不住就疯狂加副本,加得越多,线上每个用户的响应越快,这是最直接的优化手段。
正解副本增加提升的是吞吐(单位时间总接客量),单请求时延由模型推理速度和硬件决定,加副本不会让单个请求变快,反而可能因调度开销略微变慢。好比加厨师让饭店同时接待更多人,但每一桌的上菜速度并不会因此变快——想降时延得优化模型和升级 GPU。
为何常见:人们容易把『系统变快了』的感觉归因于吞吐提升,混淆了时延和吞吐两个不同指标。
误区我在每个请求里都加载一遍模型,反正代码能跑,放在构造函数里和请求函数里没区别。
正解大模型参数量动辄几 B 到几百 B,冷加载要几十秒甚至几分钟。如果放在 call 里,每个请求都要先等加载完成,服务直接卡死;应该放在 init 里只加载一次,好比备菜在开门前做好,而不是顾客一点菜才去菜市场买。
为何常见:小模型或本地调试时加载只要几百毫秒,问题不明显;换到几十 GB 的大模型立刻暴露,属于典型的演示环境与生产环境差异。
误区我把新版本和旧版本都部署起来,两个模型同时跑,这不就是金丝雀发布了吗?
正解金丝雀的核心是『流量控制 + 健康观测 + 可回滚』的完整编排,不是简单并存两个版本。没有权重控制、没有健康检查、没有回滚预案,那只是两台机器各跑各的,跟试吃完全不沾边。试吃要控制在 3 桌,而不是把新菜直接端给全店 100 桌。
为何常见:只看到『新旧两版共存』的表象,忽略了决定安全性的放量节奏与回滚机制。
误区负载一高,新副本应该立刻出现,我看控制台的指标曲线觉得扩缩是即时的。
正解扩缩容包含观测窗口(upscale_delay_s)、拉起 Actor、加载模型、通过健康检查等步骤,实际需要几十秒到几分钟;缩容还要等待更长的冷却期。好比客流感应玻璃门反应再快,也架不住门外十米排队的速度,系统总要时间反应。
为何常见:看多了监控图表上平滑的曲线,容易忽略背后真实的调度延迟和冷启动成本。
面试题
Q1. Ray Serve 是构建在哪个分布式计算框架之上的?
- A.基于Ray的分布式框架
- B.基于Spark的分布式框架← 正确答案
- C.基于Hadoop的分布式框架
- D.基于Flink的分布式框架
答案:B。Ray Serve 内嵌在 Ray 集群中运行,Ray 是它的分布式底座。Spark、Hadoop、Flink 是其他大数据框架,与 Ray Serve 没有血缘关系。就像 Serve 是 Ray 旗下专做在线服务的部门。
Q2. 大模型加载应该放在 Deployment 类的哪个方法中?
- A.请求处理函数call
- B.初始化函数init← 正确答案
- C.对象创建函数new
- D.销毁清理函数del
答案:B。模型加载是昂贵的冷启动操作,应该放在 init 里只执行一次,相当于开店前备好菜。call 是每次请求都会执行的热路径,放模型加载会让每个请求都卡在加载上。
Q3. autoscaling_config 中用于控制『每个副本同时处理多少个请求』的参数是?
- A.最小副本数min_replicas
- B.最大副本数max_replicas
- C.单副本目标并发请求数← 正确答案
- D.单副本CPU核数限制
答案:C。target_num_ongoing_requests_per_replica 指定每个副本最舒适的在途请求数,超过则扩容、低于则缩容。min/max_replicas 只定义边界,CPU 核数则由 ray_actor_options 管理。
Q4. 下列哪些属于 Ray Serve 自动扩缩容需要配置的核心参数?
- A.最小副本数min_replicas← 正确答案
- B.最大副本数max_replicas← 正确答案
- C.目标并发请求数target值← 正确答案
- D.路由前缀route_prefix值
答案:ABC。min_replicas、max_replicas 定义副本数的上下界,target_num_ongoing_requests_per_replica 是扩缩容的决策依据。route_prefix 属于流量路由范畴,与扩缩容无关。
Q5. 金丝雀发布中,新版本模型最初通常承接多少流量比较合理?
- A.一次性承接全部流量
- B.承接一半左右流量
- C.先承接少量比例流量← 正确答案
- D.完全不承接任何流量
答案:C。金丝雀讲究小流量试水,通常从 5% 开始,把爆炸半径降到最小。一开始就接全部流量等于全量上线;完全无流量则测不出真实效果。这是『先请 3 桌熟客试吃』的工程化体现。
Q6. 关于 Ray Serve 集群架构,下列说法正确的有?
- A.Head节点负责调度管理← 正确答案
- B.Worker节点执行推理计算← 正确答案
- C.控制面与数据面高度耦合
- D.副本间由路由负载均衡← 正确答案
答案:ABD。Head 节点是店长,管理集群状态与 Actor 调度;Worker 节点是厨师,执行真正的模型推理;HTTP Router 负责副本间的负载均衡。控制面与数据面是分离的,这是避免大规模下拥堵的关键,所以 C 错误。
Q7. 在 Ray Serve 中,把 Deployment 组装成应用并启动服务的 API 是?
- A.serve.run启动应用← 正确答案
- B.serve.start启动服务
- C.serve.deploy部署
- D.serve.serve启动
答案:A。serve.run(app) 负责把 bind 好的应用正式部署并对外提供服务。其他 API 不是 Ray Serve 的标准入口,就像正式开张只有『serve.run』这一道开业剪彩仪式。
Q8. 扩缩容公式 N = ⌈Q / C_t⌉ 中,Q 代表什么?
- A.每秒抵达的请求数量
- B.当前在途请求的总数← 正确答案
- C.模型参数量的大小
- D.每副本并发度上限
答案:B。Q 是在途请求总数,来自 Little 定律 L=λW,即当前正在被处理但尚未完成的请求总量。每秒抵达数 λ 只是计算它的一个因子;参数量、并发度上限都与 Q 定义无关。
Q9. 按权重分配流量的典型用途包括哪些?
- A.支持新老版本A/B测试← 正确答案
- B.支持金丝雀比例灰度← 正确答案
- C.支持模型参数训练加速
- D.支持数据清洗批处理
答案:AB。权重路由把流量按比例切给不同版本,天然支持 A/B 测试(对比两个版本效果)和金丝雀灰度(新版本小比例放量)。模型训练加速和数据清洗是其他框架的职责,与流量路由无关。
Q10. Little 定律 L = λ × W 中,W 表示什么?
- A.请求平均等待时间
- B.请求平均处理时长← 正确答案
- C.请求每秒到达速率
- D.餐厅厨师的总人数
答案:B。W 是每个请求从开始处理到完成的平均时长,即单次推理耗时。λ 才是每秒到达速率;平均等待时间只是 W 的一部分;厨师人数对应副本数 N,不在这条公式里。