Ray Serve与ONNX Runtime的结合提供了分布式推理服务部署方案。通过Ray的Actor模型管理ONNX Runtime实例,可以实现模型水平扩展、负载均衡和故障恢复,满足大规模在线推理的服务需求和高可用性要求,在生产环境中得到验证。
需要学习Ray Serve推理部署,Ray Serve与ONNX Runtime的结合提供了分布式推理服务部署方案。作为AI Runtime系统 > ONNX Runtime的基础概念,Ray Serve推理部署为实际应用场景提供理论依据。
目录
可视化演示
展示Ray Serve结合ONNX Runtime的分布式推理部署:请求路由负载均衡、基于并发请求的自动扩缩容及副本故障恢复。
本节配有交互动效(进入视口后自动加载,约 3KB)。
请求路由:负载均衡分发 路由器将请求分发到ONNX副本 Actor模型管理实例水平扩展 replica=serve.deployment 负载均衡提升吞吐与可用性
Ray Serve的核心理念与架构设计
Ray Serve是Ray框架原生的模型推理部署库,专为生产环境的AI推理服务设计。其核心理念是将模型部署抽象为可编程的部署单元(Deployment),每个Deployment可以独立配置副本数、资源配额、自动扩缩容策略和请求处理逻辑。与传统的Flask+Gunicorn方案相比,Ray Serve原生支持分布式部署、GPU调度和自动扩缩容,无需额外搭建Kubernetes HPA或Istio服务网格。
架构上,Ray Serve运行在Ray集群之上,利用Ray的分布式调度能力实现跨节点的请求路由、负载均衡和故障恢复。Serve的架构包含三个核心组件:HTTP代理(Ingress)负责接收外部HTTP请求并路由到对应的Deployment;控制器(Controller)负责管理Deployment的生命周期、部署更新和扩缩容决策;Worker副本(Replica)是实际执行模型推理的Ray Actor,每个副本持有模型实例并处理推理请求。
Ray Serve的请求处理流程为:客户端发送HTTP请求到Serve的HTTP代理,代理根据路由规则将请求转发到目标Deployment的某个Worker副本,副本调用模型的call方法完成推理,将结果通过HTTP响应返回客户端。整个流程中,Ray Serve自动处理序列化/反序列化、负载均衡、重试和故障转移,开发者只需关注模型推理逻辑本身。
Ray Serve与Kubernetes的关系是互补而非替代。在Kubernetes上部署Ray Serve(通过KubeRay operator)可以同时获得Kubernetes的容器编排能力和Ray的模型服务化能力。Ray Serve屏蔽了K8s中Pod、Service、Ingress、HPA等复杂概念,用户只需通过Python API定义Deployment即可。
from ray import serve @serve.deployment(num_replicas=2, ray_actor_options={"num_cpus": 2, "num_gpus": 1})class ModelDeployment: def __init__(self, model_path): self.model = load_model(model_path) def __call__(self, request): input_data = preprocess(request) output = self.model(input_data) return postprocess(output)
Deployment定义与配置
Deployment是Ray Serve的核心抽象单元。用户通过@serve.deployment装饰器将一个Python类标记为Deployment,在类中定义模型的加载(init)、预处理、推理和后处理逻辑。Deployment的配置参数包括:num_replicas(副本数,决定并发处理能力)、ray_actor_options(每个副本的资源配额,如CPU和GPU数量)、max_concurrent_queries(每个副本的最大并发请求数)、以及auto_scaling_config(自动扩缩容策略)。
自动扩缩容配置(auto_scaling_config)支持基于多种指标的弹性伸缩。最常用的是基于QPS的扩缩容:当每个副本的平均QPS超过目标值时触发扩容,低于下限时触发缩容。配置参数包括:min_replicas(最小副本数)、max_replicas(最大副本数)、target_num_ongoing_requests_per_replica(每副本目标并发请求数)、upscale_delay_s(扩容延迟秒数,避免瞬时抖动)和downscale_delay_s(缩容延迟秒数)。
Deployment配置支持热更新。通过serve.run()重新部署一个新版本的Deployment,Ray Serve会执行滚动更新(Rolling Update):先启动新副本,健康检查通过后逐步替换旧副本,确保服务不中断。这使得A/B测试和金丝雀发布变得简单——只需调整不同版本Deployment的流量权重即可。
资源配额通过ray_actor_options设置。对于GPU推理,通常配置{“num_gpus“: 1}表示每个副本独占一张GPU;对于CPU推理,配置{”num_cpus”: 2}表示每个副本使用2个CPU核心。Ray的调度器会根据这些配额自动将副本调度到合适的节点上。
@serve.deployment( num_replicas=4, ray_actor_options={"num_cpus": 2, "num_gpus": 1}, max_concurrent_queries=10, autoscaling_config={ "min_replicas": 2, "max_replicas": 12, "target_num_ongoing_requests_per_replica": 5, "upscale_delay_s": 30, "downscale_delay_s": 60, })class BERTInferenceDeployment: def __init__(self): self.tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese") self.model = AutoModel.from_pretrained("bert-base-chinese").cuda() def __call__(self, request): text = (await request.json())["text"] inputs = self.tokenizer(text, return_tensors="pt").to("cuda") with torch.no_grad(): outputs = self.model(**inputs) return {"embeddings": outputs.last_hidden_state.cpu().numpy().tolist()}
请求处理管道与Deployment组合
一个完整的模型推理请求通常包含预处理(将原始HTTP请求转换为模型输入张量)、模型推理(调用模型前向传播)、后处理(将模型输出转换为用户可读响应)三个阶段。Ray Serve支持通过Deployment的call方法定义完整的处理逻辑,也支持将不同处理阶段拆分为独立Deployment组合成管道(Pipeline)。
Deployment组合通过Serve Handle机制实现。一个Deployment可以通过serve.get_deployment_handle()获取另一个Deployment的句柄(Handle),然后通过await handle.remote(request)异步调用。这种设计允许不同阶段使用不同资源:预处理用CPU Deployment、推理用GPU Deployment、后处理用CPU Deployment,各自独立扩缩容。
管道设计的优势在于资源隔离和独立扩缩容。例如,预处理和后处理阶段通常是CPU密集型,可以配置多个CPU副本;推理阶段是GPU密集型,副本数较少但每个副本配置GPU。当推理成为瓶颈时,只需扩容推理Deployment,不影响其他阶段。这种架构特别适合多模态AI服务,不同模态的模型可以独立部署和扩缩容。
Ray Serve还支持图式部署(Deployment Graph),通过@serve.deployment_graph装饰器将多个Deployment组合成DAG(有向无环图),自动处理节点间的数据传递和异步调度。Deployment Graph适合复杂的推理流程,如多模型ensemble、级联推理等场景。
@serve.deployment(num_replicas=2, ray_actor_options={"num_cpus": 2})class PreprocessDeployment: def __call__(self, raw_text): return self.tokenizer(raw_text, return_tensors="pt") @serve.deployment(num_replicas=4, ray_actor_options={"num_gpus": 1})class InferenceDeployment: def __init__(self, preprocess_handle): self.preprocess = preprocess_handle self.model = load_model() async def __call__(self, request): text = (await request.json())["text"] inputs = await self.preprocess.remote(text) output = self.model(inputs) return postprocess(output)
自动扩缩容机制详解
Ray Serve的自动扩缩容基于副本的并发请求数(ongoing requests)进行决策。控制器定期检查每个副本当前正在处理的请求数,如果平均值超过target_num_ongoing_requests_per_replica,则触发扩容;如果低于该值,则触发缩容。这种基于并发数的策略比基于CPU利用率的策略更适合推理场景,因为推理的瓶颈通常是GPU而非CPU。
扩缩容决策引入了延迟机制以避免抖动。upscale_delay_s(默认30秒)表示连续超过阈值多久后才执行扩容;downscale_delay_s(默认60秒)表示连续低于阈值多久后才执行缩容。缩容延迟通常比扩容延迟长,因为缩容后立即扩容的代价更高(需要重新加载模型)。
自动扩缩容的局限性在于:1) 受限于集群总资源——如果集群GPU不足,扩容请求会等待资源释放;2) 存在启动延迟——新副本需要加载模型权重,对于大模型(如70B LLM)可能需要数分钟;3) 无法预测突发流量——对于瞬时流量尖峰,可能来不及扩容。因此,合理的容量规划仍然必要,建议预留20-30%的缓冲容量。
对于大模型推理,可以通过模型预热(warm-up)减少首次请求延迟。在Deployment的init中执行一次dummy推理,确保CUDA上下文和模型权重已加载到GPU显存中。同时,配置max_concurrent_queries为1(大模型推理通常不支持并发),通过增加num_replicas来提升吞吐量。
多版本管理与灰度发布
Ray Serve原生支持多版本管理和流量路由。通过部署多个版本的Deployment并配置流量权重,可以实现金丝雀发布和A/B测试。例如,将90%流量路由到v1版本,10%流量路由到v2版本,逐步验证新版本的效果。如果v2表现异常,可以立即将流量权重回滚到0%。
灰度发布的实现方式是通过serve.run()的traffic参数配置。每个版本对应一个Deployment实例,通过设置名称后缀(如model_v1, model_v2)区分。HTTP代理根据权重将请求路由到不同版本的副本。版本切换是原子的——更新流量权重的操作在控制器中同步执行,确保不会出现请求丢失。
A/B测试场景中,Ray Serve支持在响应中返回模型版本标识,便于后端分析不同版本对业务指标的影响。通过在Deployment的call中添加版本号到响应头或响应体,可以追踪每个请求使用的模型版本,进而计算各版本的点击率、转化率等业务指标。
对于需要精确控制流量分配的场景(如按用户ID分流),Ray Serve支持自定义路由逻辑。通过在Ingress Deployment中实现路由函数,可以根据请求特征(如用户ID的哈希值)将请求路由到特定版本,确保同一用户的请求始终路由到同一版本(粘性会话)。
# 部署v1和v2版本,配置流量权重serve.run(model_v1, name="model", route_prefix="/predict", traffic={"v1": 0.9, "v2": 0.1})# 逐步调整权重完成灰度发布serve.run(model_v2, name="model", route_prefix="/predict", traffic={"v1": 0.5, "v2": 0.5})# 全量发布serve.run(model_v2, name="model", route_prefix="/predict", traffic={"v2": 1.0})
Ray Serve与Triton集成及性能优化
Ray Serve可以与NVIDIA Triton Inference Server集成,将Triton作为推理后端。在这种架构中,Ray Serve负责请求路由、扩缩容和流量管理,Triton负责GPU推理优化(如动态batching、TensorRT加速、多模型共享GPU)。这种分工使得两者各司其长——Ray Serve擅长大规模分布式调度,Triton擅长单GPU上的推理性能优化。
Triton的动态batching(Dynamic Batching)可以将多个并发请求合并为一个batch执行,显著提升GPU利用率。配合Ray Serve的max_concurrent_queries设置,可以控制积压的请求数量,使Triton有足够的请求进行batch合并。对于延迟敏感的场景,可以配置Triton的max_queue_delay_microseconds限制batch等待时间。
性能优化建议:1) 使用Ray Serve的批量处理API(@serve.batch装饰器)将多个请求自动合并为batch,减少模型调用的次数;2) 对大模型使用模型并行(Pipeline Parallel或Tensor Parallel),将模型切分到多个GPU上;3) 配置合理的max_concurrent_queries——GPU推理通常不支持真正的并发,设为1可避免资源竞争;4) 使用Ray的placement group确保GPU副本调度到有GPU的节点上。
监控方面,Ray Serve集成了Prometheus指标导出。默认暴露的指标包括:请求QPS、P50/P99延迟、副本数、并发请求数、错误率等。可以配置Grafana仪表盘实时监控这些指标,并设置告警规则(如P99延迟超过阈值时触发扩容)。
from ray import serve @serve.deployment(num_replicas=4, ray_actor_options={"num_gpus": 1})@serve.batch(max_batch_size=32, batch_wait_timeout_s=0.1)async def batch_inference(inputs: list): batch_tensor = torch.stack([preprocess(x) for x in inputs]).cuda() with torch.no_grad(): outputs = model(batch_tensor) return [postprocess(o) for o in outputs]
生产环境部署最佳实践
在生产环境中部署Ray Serve需要考虑高可用、监控告警和CI/CD集成。高可用方面,建议部署多个Ray Serve HTTP代理(通过nginx或HAProxy做前置负载均衡),避免单点故障。Ray集群本身应配置多节点,确保Deployment的副本分布在不同的物理节点上,单节点故障不会导致服务中断。
CI/CD集成方面,Ray Serve的部署代码可以版本化管理。通过serve.run()的name参数指定Deployment名称,每次代码更新后执行serve.run()即可触发滚动更新。可以集成到GitHub Actions或GitLab CI中,在模型审核通过后自动部署到预发环境,验证通过后灰度发布到生产环境。
成本优化策略:1) 使用自动扩缩容在低峰期自动缩减GPU副本,可节省50-80%的GPU成本;2) 对于延迟不敏感的批量推理任务,使用Spot实例(可被抢占的低成本实例);3) 多模型共享GPU——通过Ray Serve的Deployment设计,将多个小模型部署在同一GPU上(每个Deployment配置num_gpus=0.5),提高GPU利用率;4) 使用模型量化(INT8/FP8)减少显存占用,允许在同一GPU上部署更多副本。
安全方面,Ray Serve支持TLS/SSL加密和认证。通过配置HTTPS证书确保传输安全,通过Ray Auth进行客户端身份验证。对于多租户场景,可以通过Ray Serve的路由前缀(route_prefix)实现命名空间隔离,不同团队使用不同的路由前缀,互不干扰。
AI 应用场景
大规模AI推理服务的Ray Serve部署方案
阶段:模型部署与推理服务
解决的问题:传统部署方案(Flask + Kubernetes)需要手动配置扩缩容规则、负载均衡、监控告警等基础设施,运维成本高且难以应对流量波动大的推理场景。Ray Serve通过统一编程模型和自动扩缩容机制显著降低了部署和运维复杂度。
具体案例:一家在线教育平台需要部署AI作文批改服务,每天处理约500万次推理请求。请求量在白天上课时段是夜间的20倍。平台使用Ray Serve部署了三个Deployment:预处理Deployment(2个CPU副本)、推理Deployment(BERT评分模型,4个GPU副本,配置自动扩缩容2-12个副本)、后处理Deployment(2个CPU副本)。推理Deployment配置了基于QPS的自动扩缩容,当QPS超过200时自动扩容。白天高峰期扩展到12个GPU副本,P99延迟稳定在180ms以内;夜间缩减到2个副本,节省约80%的GPU成本。整个部署仅需约200行Python代码,相比Kubernetes方案减少了约80%的配置工作。
多模型A/B测试与灰度发布平台
阶段:模型部署与推理服务
解决的问题:AI模型需要持续迭代,但新模型离线评估效果不一定反映在线表现。通过A/B测试和金丝雀发布可以在真实流量中验证新模型,但传统方案需要维护多套部署环境。Ray Serve的原生多版本管理和流量路由能力简化了这一过程。
具体案例:一家电商推荐系统团队开发了新的推荐模型v2(Transformer架构),需要与线上v1模型(DIN模型)进行在线A/B测试。团队使用Ray Serve部署两个版本:v1权重90%,v2权重10%。Serve的HTTP代理自动将10%请求路由到v2,并在响应头返回模型版本标识。A/B测试一周后数据显示v2点击率提升12%,团队通过逐步调整权重为25%、50%、75%、100%完成灰度发布。如果发现异常,可将v2权重设为0%快速回滚。整个过程无需维护多套K8s部署,仅通过修改配置参数即可。
多模态AI服务的统一部署网关
阶段:模型部署与推理服务
解决的问题:现代AI应用需要调用多个不同类型的模型(文本、图像、语音),每个模型有不同的部署需求和资源消耗模式。单独部署导致管理分散、资源利用率低。Ray Serve可以将多个模型统一部署在同一个集群上,通过共享集群资源提高整体利用率。
具体案例:一家智能客服公司需要同时部署三个AI模型:意图识别模型(BERT,1 GPU)、情感分析模型(RoBERTa,1 GPU)、语音转文本模型(Whisper,2 GPU)。三个模型请求量模式不同:NLP模型全天平稳,ASR服务白天高峰激增。使用Ray Serve将三个模型部署为独立Deployment,共享同一个Ray集群(8个GPU节点)。ASR Deployment配置自动扩缩容(2-6个副本),白天扩展到6个副本利用集群剩余GPU资源,夜间缩减到2个副本释放资源给NLP模型的批量推理任务。集群GPU平均利用率从35%提升到68%,运维从管理三套独立K8s服务简化为管理一个Ray集群。
Python 演示
from ray import serveimport torchfrom transformers import AutoTokenizer, AutoModelimport asyncio @serve.deployment(num_replicas=2, ray_actor_options={"num_cpus": 2})class PreprocessDeployment: def __init__(self): self.tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese") def __call__(self, text: str): inputs = self.tokenizer(text, return_tensors="pt", padding=True, truncation=True) return {k: v.tolist() for k, v in inputs.items()} @serve.deployment( num_replicas=4, ray_actor_options={"num_gpus": 1}, max_concurrent_queries=10, autoscaling_config={ "min_replicas": 2, "max_replicas": 12, "target_num_ongoing_requests_per_replica": 5, "upscale_delay_s": 30, "downscale_delay_s": 60, })class InferenceDeployment: def __init__(self, preprocess_handle): self.preprocess = preprocess_handle self.tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese") self.model = AutoModel.from_pretrained("bert-base-chinese").cuda() dummy = self.tokenizer("warmup", return_tensors="pt").to("cuda") with torch.no_grad(): _ = self.model(**dummy) async def __call__(self, request): data = await request.json() text = data["text"] inputs = await self.preprocess.remote(text) input_ids = torch.tensor(inputs["input_ids"]).cuda() attention_mask = torch.tensor(inputs["attention_mask"]).cuda() with torch.no_grad(): outputs = self.model(input_ids=input_ids, attention_mask=attention_mask) embeddings = outputs.last_hidden_state.mean(dim=1) return {"embedding": embeddings.cpu().numpy().squeeze().tolist()} app = InferenceDeployment.bind(PreprocessDeployment.bind()) if __name__ == "__main__": serve.run(app, route_prefix="/embed") import time while True: time.sleep(10)
@serve.deployment(num_replicas=2, ray_actor_options={"num_cpus": 2})定义预处理Deployment,2个CPU副本,每个副本使用2个CPU核心class PreprocessDeployment:预处理Deployment类,负责文本分词和tokenizationself.tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese")在init中加载BERT中文分词器,模型加载只在副本启动时执行一次@serve.deployment(num_replicas=4, ray_actor_options={"num_gpus": 1})定义GPU推理Deployment,4个GPU副本,每个副本独占1张GPUmax_concurrent_queries=10,每个副本最大并发请求数为10,防止过载导致OOM"target_num_ongoing_requests_per_replica": 5,自动扩缩容目标:每副本平均5个并发请求时触发扩容"upscale_delay_s": 30,扩容延迟30秒,避免瞬时流量抖动触发不必要的扩容def __init__(self, preprocess_handle):通过构造函数注入预处理Deployment的Handle,实现Deployment间通信self.model = AutoModel.from_pretrained("bert-base-chinese").cuda()加载BERT模型到GPU显存,cuda()确保模型在GPU上运行with torch.no_grad(): _ = self.model(**dummy)模型预热(warmup),执行一次dummy推理确保CUDA上下文已初始化inputs = await self.preprocess.remote(text)通过Handle异步调用预处理Deployment,remote()是非阻塞的app = InferenceDeployment.bind(PreprocessDeployment.bind())使用bind()将预处理和推理Deployment组合成应用管道serve.run(app, route_prefix="/embed")部署应用并配置HTTP路由前缀为/embed,客户端通过http://host:8000/embed访问
常见问答
Ray Serve和Flask部署模型有什么区别?
Ray Serve的自动扩缩容和Kubernetes HPA有什么区别?
Ray Serve部署大模型(如70B LLM)时有什么注意事项?
如何在Ray Serve中实现A/B测试?
Ray Serve支持非Python模型部署吗?
常见误区
误区认为Ray Serve需要Ray框架的完整安装和集群配置,部署起来比Kubernetes + Flask更复杂
正解Ray Serve实际上比Kubernetes + Flask方案更简单。单机开发只需pip install ray[serve]即可启动。集群部署可通过Ray Cluster Launcher用YAML配置文件一键在AWS/GCP/Azure或Kubernetes上创建。Ray Serve屏蔽了K8s的Pod、Service、Ingress、HPA等复杂概念,用户只需关注Deployment配置。对于已使用K8s的团队,Ray Serve可通过KubeRay operator运行在K8s之上。
为何常见:Ray是一个分布式计算框架,听起来比Flask更重,但Ray Serve专为模型部署优化,提供了开箱即用的生产级功能,实际上比从头搭建K8s部署方案简单得多。
误区认为Ray Serve的自动扩缩容可以完全替代容量规划,无需预估服务所需资源量
正解自动扩缩容受限于集群总资源。如果集群GPU不足以支撑高峰期最大需求,扩容只能达到集群物理上限。此外扩容存在30秒到数分钟的滞后,对于突发流量尖峰可能来不及。合理的容量规划仍然必要:根据峰值QPS、推理延迟和单副本处理能力规划最小GPU数量,预留20-30%缓冲容量。
为何常见:自动扩缩容的概念容易被误解为无限弹性,忽略了任何系统的扩缩容都受限于集群总资源这一基本约束。
误区认为Ray Serve只适合Python模型部署,不支持其他语言或非Python推理框架
正解Ray Serve支持部署任何可通过HTTP/gRPC接口调用的模型服务。它支持将请求转发到任何HTTP端点(包括Java、Go、Rust服务)、通过gRPC集成C++/Java的Triton Inference Server、以及通过subprocess或跨语言Actor调用外部推理进程。还可以与NVIDIA Triton集成,Triton负责GPU推理优化,Ray Serve负责路由和扩缩容。
为何常见:Ray Serve的Python API让人误以为它只能部署Python模型,但实际上它通过HTTP/gRPC集成和外部进程委托机制实现了语言无关的部署能力。
误区认为每个Deployment副本必须独占一张GPU
正解Ray Serve支持通过ray_actor_options中的num_gpus参数配置小数GPU。例如配置num_gpus=0.5表示每个副本使用半张GPU,可以在同一GPU上部署两个副本。这对于小模型(如BERT-base)特别有用,可以提高GPU利用率。但需要注意显存管理和CUDA上下文切换的开销。
为何常见:在Kubernetes中GPU通常是独占分配的(通过nvidia.com/gpu资源),开发者习惯了一副本一GPU的模式。
参考来源
- docRay Serve DocumentationRay Serve官方文档,包含Deployment配置、自动扩缩容、多版本管理等详细说明
- paperRay: A Distributed Framework for Scaling AI ApplicationsRay框架论文,介绍了Ray的分布式调度架构和Serve的部署模型设计
- docRay Serve Deployment Graph GuideRay Serve Deployment Graph官方指南,介绍DAG式部署的API和使用方法
- docRay Serve Autoscaling GuideRay Serve自动扩缩容官方指南,详细介绍autoscaling_config参数和扩缩容策略
- docRay Serve Source CodeRay Serve源码仓库,包含HTTP代理、控制器、Worker副本的实现代码