LLM 推理服务系列 · 第 5 篇
KServe 的思路是把推理包装成 Kubernetes 原语------Deployment、Service、HPA、Istio VirtualService。这套方案在"模型即微服务"的场景里非常成熟,但它有一个隐含假设:推理是独立的、无状态的、请求-响应的。
Ray Serve 推翻了这一套------它不做 YAML 配置,不依赖 Kubernetes(虽然可以跑在 K8s 上),不用 Knative。它用 Python 装饰器把一个函数或类变成可水平扩展的推理服务,然后由一个分布式调度器在集群里分配计算资源。
Ray:更底层的分布式计算引擎
Ray Serve 是 Ray 生态的一部分。先理解 Ray:
ini
Ray = 分布式 Python 运行时
核心抽象:
Task (Ray Task): 无状态函数,在集群中任意节点执行
Actor (Ray Actor): 有状态对象,在集群中固定节点常驻
Ray 本身不是为"推理服务"设计的------它是为"分布式 Python 计算"设计的。它最著名的用途是训练大模型(RLHF 中的 Ray Train)、超参调优(Ray Tune)、数据处理(Ray Data)。推理服务(Ray Serve)是后来加上的能力。
但正是因为这个出身------Ray Serve 做 LLM 推理时有一些其他框架做不到的灵活性。
Ray Serve 的核心抽象:Deployment
不需要 YAML,不需要 config.pbtxt,不需要 InferenceService CRD------一个 Python 类加一个装饰器就是完整的推理服务:
python
from ray import serve
from vllm import LLM, SamplingParams
@serve.deployment(
name="llama-8b", # 服务名
num_replicas=2, # 2 个副本
ray_actor_options={
"num_gpus": 1 # 每个副本用 1 个 GPU
}
)
class LlamaInference:
def __init__(self):
self.llm = LLM(model="meta-llama/Llama-3.1-8B-Instruct")
async def __call__(self, request):
prompt = await request.json()
result = self.llm.generate(
prompt["text"],
SamplingParams(temperature=0.7, max_tokens=512)
)
return result[0].outputs[0].text
# 部署
serve.run(LlamaInference.bind())
@serve.deployment 背后发生的事情:
- 类被序列化,分发到 Ray Cluster 中所有带 GPU 的节点
- 每个节点根据
num_replicas启动对应数量的 Actor 进程 - 每个 Actor 调用
__init__加载模型(只加载一次) - HTTP 端点自动注册------默认
http://localhost:8000/LlamaInference - 请求自动负载均衡到所有副本
跟 KServe 的差异一目了然:Ray Serve 是代码驱动的,KServe 是 YAML 驱动的。 你不需要理解 Kubernetes,你只要写 Python。
毫秒级调度 vs 容器级调度
这是 Ray Serve 最底层的差异:
arduino
Kubernetes 调度粒度:Pod(容器)
扩缩一个副本 = 启动一个新容器 = 拉镜像 + 容器启动 + 模型加载 = 30s ~ 2min
适合:模型作为微服务、流量变化缓慢、可以接受秒级扩容延迟
Ray Serve 调度粒度:Actor(进程)
扩缩一个副本 = 在当前或相邻节点启动一个进程 = 1-2 秒
适合:动态推理负载、实时特征计算、需要在<5s 内响应流量突发
因为 Ray Cluster 里所有节点都已经运行了 Ray Runtime------启动一个 Actor 只需要 fork 一个进程------不需要拉镜像、不需要创建容器、不需要网络配置------纯进程级别的调度。
Ray Serve 真正独特的功能:多模型编排在代码里
前面 Triton 用 config.pbtxt 声明 DAG,KServe 用 InferenceGraph 声明。Ray Serve 直接用 Python 函数的调用关系:
python
@serve.deployment(name="intent-classifier")
class IntentClassifier:
def __init__(self):
self.model = load_bert_model()
async def classify(self, text: str) -> str:
return self.model(text) # 返回 "query_order" | "chat" | "support"
@serve.deployment(name="llm-generator")
class LLMGenerator:
def __init__(self):
self.llm = LLM(model="llama-3.1-8b")
async def generate(self, intent: str, text: str) -> str:
if intent == "query_order":
order = query_database(text) # 查数据库
prompt = f"根据订单信息回复:{order}\n用户问题:{text}"
else:
prompt = text
return self.llm.generate(prompt)
# 组装 Pipeline
classifier = IntentClassifier.bind()
generator = LLMGenerator.bind()
@serve.deployment(name="orchestrator")
class Orchestrator:
def __init__(self, classifier, generator):
self.classifier = classifier
self.generator = generator
async def __call__(self, request):
text = await request.body()
intent = await self.classifier.classify.remote(text)
response = await self.generator.generate.remote(intent, text)
return response
serve.run(Orchestrator.bind(classifier, generator))
这放在 KServe 里需要:一个 Classification 的 InferenceService + 一个 LLM 的 InferenceService + 一个 Istio VirtualService 做路由 + 一个中间服务做编排。Ray Serve 里------全在一个 Python 文件里。
跟 vLLM 原生结合:Ray Serve + vLLM 是官方推荐架构
vLLM 本身支持 Ray 作为分布式通信后端------当 vLLM 以 Tensor Parallel 模式跨多张 GPU 部署时,用 Ray 做 GPU 间通信比 NCCL 更灵活。
bash
# vLLM 启动时指定后端为 Ray
vllm serve meta-llama/Llama-3.1-70B-Instruct \
--tensor-parallel-size 8 \ # 8 张 GPU
--distributed-executor-backend ray # Ray 后端
Ray Serve + vLLM 组合的作用:
- Ray 管理 GPU 间的通信拓扑(vLLM 的张量并行)
- Ray Serve 管理请求的路由和负载均衡
- 当集群拓扑变化(GPU 节点增减),Ray 自动重调度 Actor
什么时候选 Ray Serve
| 场景 | Ray Serve 适合? |
|---|---|
| 全 Python 团队,不想写 YAML | 首选 |
| LLM + 在线特征计算 + 召回等同一 Pipeline | 最佳选择------代码级别的编排 |
| 已有 Ray Cluster(做训练/数据处理) | 零成本复用,推理做在同集群上 |
| 需要 1-2 秒级别的快速扩缩 | Actor 级别调度,不是 Pod |
| 多模型动态编排(不属于简单的 request/response) | Python 代码灵活性碾压配置驱动 |
| 需要 Kubernetes 级别的多租户+配额+RBAC | 不适用------KServe 更适合 |
| 团队没有 Python 背景(Go/Java 为主) | 不适用------Ray 是 Python-first |
| 需要 Scale to Zero | 不内置支持------Ray 的设计假设常驻集群 |
五个框架的选择速查
你要做的事 → 用什么
─────────────────────────────────────────────────
单机极致 LLM 推理吞吐 → vLLM
HuggingFace 模型 + LoRA + 多模态 → TGI
多种模型类型(LLM + CV + 树模型) → Triton
多团队多模型的 K8s 平台 → KServe
Python 原生、代码驱动编排 → Ray Serve
一句话总结
Ray Serve 不把推理当成"服务"来管理------而当成"分布式 Actor"来调度。它的优势不在 LLM 推理性能(那是 vLLM/TGI 的活),而在编排灵活性------Python 纯代码编排、毫秒级扩缩、跟 Ray 生态(Train/Tune/Data)的原生整合。如果你对 YAML 驱动的方式感到不耐------Ray Serve 是让你回到"写代码部署"节奏的选项。
推理服务系列完结。五篇文章覆盖了 LLM 推理服务的完整版图:
- vLLM:PagedAttention + Continuous Batching------单机推理吞吐的天花板
- TGI:HuggingFace 生态原生------LoRA 热切换 + Chunked Prefill + 多模态
- Triton:通用推理平台------Backend 架构 + Model Ensemble + TensorRT-LLM
- KServe:Kubernetes 原生------Serverless 扩缩容 + 金丝雀发布 + ModelMesh
- Ray Serve:Python 原生------Actor 调度 + 代码编排 + 毫秒级扩缩
从头到尾的递进:单进程 → 单服务 → 多模型 → 平台化 → 分布式编程模型。选什么框架,取决于你的团队在"自己做基础设施"和"用平台"之间的偏好。但无论选哪个------底层做推理计算的,基本还是 vLLM。