news 2026/8/14 5:38:23

K8s+Ray+vLLM打穿大模型全生命周期(有实践步骤)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
K8s+Ray+vLLM打穿大模型全生命周期(有实践步骤)

摘要

K8s + Ray + PyTorch + vLLM 是当前大模型工程里出现频率很高的一套组合,但网上大多数文章只谈"为什么好",很少给出真正能跑、能验证的完整链路。这篇文章分两部分:先做定位分析——四个组件到底各自解决什么问题、边界在哪里;再给出一条贯穿数据处理、预训练、后训练(RLHF)、在线推理、Agent 五个阶段的最小可运行示例,每一段代码和命令都对照官方文档(Ray、vLLM、KubeRay、verl)核实过,不是道听途说的拼凑。

背景与问题

"K8s+Ray+vLLM 三件套"这个说法这两年很火,但仔细看会发现两个常见误区:

第一个误区是把四个组件当成平级的可替换选项,而不是分层职责。K8s 管的是物理资源调度(Pod 放哪台机器),Ray 管的是应用层调度(进程/Actor 放哪个 Pod、CPU/GPU 阶段怎么编排),PyTorch 是训练框架,vLLM 是推理引擎。四者不是"选一个就够"的关系,而是叠在一起才完整——单独拿 vLLM 出来是没法处理数据、也没法调度多机训练的,它只负责"给定一个模型和一批请求,尽可能快地把结果吐出来"。

第二个误区是只看"训练"和"推理"两端,忽略了中间的后训练(RLHF/post-training)阶段其实是这套组合价值最集中的地方。训练用 PyTorch 生态(FSDP、Megatron),推理用 vLLM,这两端分开看都好理解;难的是 RLHF 这种训练和推理要在同一个训练循环里反复切换的场景——策略模型每轮既要用 FSDP 算梯度,又要用 vLLM 生成 rollout,这正是 Ray 作为"全局调度层"存在的意义:由 Ray 的 Single-Controller 模式把训练 worker 和推理 worker 编排在一起,而不是各自为政。

这篇文章会按四层职责把每个组件的定位说清楚,再用一条完整链路把它们串起来验证。

核心思路与优势

四层职责定位

层级组件具体职责
物理资源调度Kubernetes把 Pod 调度到具体节点,管理 GPU 资源配额、网络、存储卷
应用层调度与编排Ray(KubeRay 负责在 K8s 上管理 Ray 集群)把 Actor/Task 分配到 Pod 内的具体进程,支持异构 CPU/GPU 资源请求、动态伸缩、细粒度容错
训练框架PyTorch 生态(FSDP2、Megatron-LM 等)提供分布式训练的具体实现——模型分片、梯度同步、混合精度
推理引擎vLLM用 PagedAttention 和 continuous batching 做高吞吐推理,同时提供 OpenAI 兼容 API

这四层里,K8s 和 Ray 的边界最容易混淆:K8s 的调度粒度是 Pod,一旦 Pod 起来了,K8s 不管这个 Pod 内部怎么再分配任务;Ray 恰好接着往下管,把 Pod 内的资源再细分给不同的 Ray Actor,这才能实现"同一个 Pod 里跑 CPU 解码 Actor 和 GPU 推理 Actor"这种细粒度混合调度。KubeRay 就是这两层的粘合剂——它是一个 K8s Operator,提供RayClusterRayJobRayService三种 CRD,让 K8s 原生地管理 Ray 集群的生命周期。

安装配置

安装 KubeRay operator(管理 Ray 集群的 K8s Operator):

helm repoaddkuberay https://ray-project.github.io/kuberay-helm/ helminstallkuberay-operator kuberay/kuberay-operator--version1.6.2 kubectl get pods# 确认 kuberay-operator Pod 处于 Running

本地开发环境安装 Ray(用于编写/调试训练和数据处理代码):

pipinstall-U"ray[data,train,tune,serve]"# 支持 Python 3.10-3.13

[data,train,tune,serve]是按用途拆分的可选依赖组,不需要 RLlib 或 Ray Core 的最小子集就不用装全量。

安装 vLLM(推理引擎,推荐用 uv 管理环境):

uv venv--python3.12--seed--managed-pythonsource.venv/bin/activate uv pipinstallvllm --torch-backend=auto

官方明确建议不要从 conda 装 PyTorch 再装 vLLM——conda 版 PyTorch 会静态链接 NCCL,容易和 vLLM 自带的 NCCL 冲突,最好用全新虚拟环境安装。vLLM 预编译二进制默认基于 CUDA 12.9,支持 Python 3.10-3.13。

KubeRay 上拉起一个最小 Ray 集群:一个RayClusterCRD 由headGroupSpec(头节点,暴露 6379 GCS 端口、8265 Dashboard 端口、10001 Client 端口)和一至多个workerGroupSpecs(工作节点组,可配置minReplicas/maxReplicas做自动伸缩)组成,官方提供了从最小示例到生产级配置的完整样例(ray-cluster.complete.yaml),实际部署时建议直接改这个文件而不是从零写。

面向人群

  • 正在搭建或评估大模型训练/推理基础设施的平台工程师,想知道这套组合具体怎么落地而不是停留在架构图层面
  • 做 RLHF/后训练研究,需要在训练和推理之间高效切换资源的算法工程师
  • 想理解"为什么不能只用 vLLM 就够了"这类定位问题的技术决策者

实践步骤:贯穿大模型全生命周期的最小可运行示例

以下五段代码覆盖数据处理→预训练→后训练→在线推理→Agent。受限于篇幅和硬件门槛,"预训练"和"后训练"两段用的是小模型/小规模配置做可验证的最小示例,不是生产级训练脚本——但用的都是官方真实 API 和真实命令行入口,把模型和数据规模换大就是生产配置,架构不需要重新设计。

阶段一:数据处理(Ray Data)

Ray Data 是这条链路里真正干"处理"这个词的引擎——不是 vLLM。它用流式执行模型,支持在同一条 pipeline 里混合 CPU 算子(解码、清洗)和 GPU 算子(embedding 提取),并按 stage 自动做背压控制:

importray ray.init()# 读取 Parquet 格式的文本数据(也支持 read_images / read_videos 等多模态格式)ds=ray.data.read_parquet("s3://your-bucket/raw-corpus/")defclean_and_tokenize(batch):# 这里放实际的清洗/分词逻辑,batch 是一个 dict of numpy arraysbatch["text"]=[t.strip()fortinbatch["text"]]returnbatch# CPU 阶段:清洗ds=ds.map_batches(clean_and_tokenize,batch_format="numpy",num_cpus=1)# 写出处理后的分片,供下一阶段(预训练)读取ds.write_parquet("s3://your-bucket/processed-corpus/")

map_batchesnum_cpus/num_gpus参数就是前面说的"Ray 在 Pod 内做细粒度资源分配"的具体体现——同一条ds链上不同的map_batches调用可以请求不同的资源,Ray Data 会自动做流水线编排。

阶段二:预训练(Ray Train + PyTorch FSDP2)

这一段用 Ray 官方验证过的TorchTrainer+ScalingConfigAPI,配合 PyTorch 2.x 的 FSDP2(torch.distributed.fsdp.fully_shard)做模型分片训练:

importtorchimporttorch.nnasnnfromtorch.distributed.fsdpimportfully_shardimportray.trainfromray.train.torchimportTorchTrainerfromray.trainimportScalingConfig,RunConfigclassToyTransformerBlock(nn.Module):def__init__(self,dim=256):super().__init__()self.attn=nn.MultiheadAttention(dim,num_heads=4,batch_first=True)self.ffn=nn.Sequential(nn.Linear(dim,dim*4),nn.GELU(),nn.Linear(dim*4,dim))defforward(self,x):a,_=self.attn(x,x,x)returnself.ffn(x+a)classToyModel(nn.Module):def__init__(self,dim=256,n_layers=4):super().__init__()self.blocks=nn.ModuleList([ToyTransformerBlock(dim)for_inrange(n_layers)])defforward(self,x):forblockinself.blocks:x=block(x)returnxdeftrain_loop_per_worker(config):model=ToyModel()# FSDP2 用法:自底向上逐层包裹,再包裹根模块(PyTorch 官方推荐顺序)forblockinmodel.blocks:fully_shard(block)fully_shard(model)optimizer=torch.optim.AdamW(model.parameters(),lr=config["lr"])ctx=ray.train.get_context()print(f"worker rank={ctx.get_world_rank()}world_size={ctx.get_world_size()}")forstepinrange(config["train_steps"]):x=torch.randn(8,32,256)# 演示用随机数据,真实场景替换为阶段一产出的分片loss=model(x).mean()loss.backward()optimizer.step()optimizer.zero_grad()trainer=TorchTrainer(train_loop_per_worker=train_loop_per_worker,train_loop_config={"lr":1e-4,"train_steps":100},scaling_config=ScalingConfig(num_workers=4,use_gpu=True),run_config=RunConfig(name="toy-pretrain-demo"),)result=trainer.fit()

ScalingConfig(num_workers=4, use_gpu=True)就是"要几台机器、要不要 GPU"这个决策的完整代码化——把num_workers从 4 改成 400,把ToyModel换成真实的模型定义,就是从演示脚本变成生产预训练任务的完整路径,中间不需要改架构。完整的官方 FSDP2 教程见 docs.ray.io 的 Ray Train PyTorch FSDP2 示例。

阶段三:后训练/RLHF(verl,Ray + FSDP + vLLM 协同的地方)

这是整条链路里 Ray 价值最直接的一段:RLHF 训练循环里,策略模型既要用 FSDP 算梯度更新参数,又要频繁用 vLLM 生成 rollout 样本,两个角色的资源需求完全不同(训练要显存装优化器状态,推理要显存装 KV cache),verl(字节跳动开源的 HybridFlow 论文实现)用 Ray 的 Single-Controller 模式把这两者编排在同一个训练循环里:

# 安装(官方推荐 Docker,这里给出核心两步)gitclone https://github.com/verl-project/verl&&cdverl pip3install-e".[vllm]"# 用 GRPO 算法跑后训练,rollout 阶段用 vLLM 做推理生成python3-mverl.trainer.main_ppo\algorithm.adv_estimator=grpo\algorithm.use_kl_in_reward=False\actor_rollout_ref.model.path=Qwen/Qwen2.5-0.5B-Instruct\actor_rollout_ref.actor.use_kl_loss=True\actor_rollout_ref.actor.fsdp_config.param_offload=False\actor_rollout_ref.rollout.name=vllm\actor_rollout_ref.rollout.tensor_model_parallel_size=1\actor_rollout_ref.rollout.gpu_memory_utilization=0.5\data.train_files=$HOME/data/gsm8k/train.parquet\data.val_files=$HOME/data/gsm8k/test.parquet\trainer.n_gpus_per_node=1\trainer.nnodes=1\trainer.total_epochs=1

actor_rollout_ref.rollout.name=vllm这一行就是训练和推理交接的地方——verl 把 vLLM 作为可插拔的 rollout 后端(另一个选项是 SGLang),训练阶段用的是actor_rollout_ref.actor.fsdp_config.*系列参数控制的 FSDP/FSDP2(大规模场景可换成 Megatron-LM 参数)。这个命令是从 verl 官方仓库的真实 PPO / GRPO 示例脚本精简而来,把模型路径和trainer.n_gpus_per_node/trainer.nnodes换成实际规模,就是生产配置——verl 官方给出的参考规模是最高可扩展到 671B 模型和数百张 GPU。

阶段四:在线推理(Ray Serve LLM,vLLM 作为后端引擎)

单机部署直接vllm serve就够;需要多副本自动伸缩、和其他 Ray 应用共享集群资源时,用 Ray Serve LLM 把 vLLM 包一层:

fromrayimportservefromray.serve.llmimportLLMConfig,build_openai_app llm_config=LLMConfig(model_loading_config={"model_id":"qwen2.5-7b-instruct","model_source":"Qwen/Qwen2.5-7B-Instruct",},deployment_config={"autoscaling_config":{"min_replicas":1,"max_replicas":2},},accelerator_type="A10G",engine_kwargs={"tensor_parallel_size":1},)app=build_openai_app({"llm_configs":[llm_config]})serve.run(app,blocking=True)

这段代码的整体结构(LLMConfig字段、build_openai_app用法)逐字来自 Ray 官方文档(docs.ray.io/en/latest/serve/llm),官方示例原本用的是Qwen2.5-0.5B-Instruct+tensor_parallel_size=2,这里换成Qwen2.5-7B-Instruct+tensor_parallel_size=1(7B 模型单张 A10G 显存足够,不需要再切两张卡),原因见阶段五末尾的说明。engine_kwargs里能传的参数和vllm serve命令行的参数基本对齐,也就是说 vLLM 本身的能力(PagedAttention、量化、多模态支持)在 Ray Serve LLM 这层完全没有损耗,Ray 只是加了一层自动伸缩和多模型路由。跑起来之后,serve.run暴露的就是一个标准 OpenAI 兼容的 HTTP 端点,服务已经在跑,阶段五不需要再起一个新的。

阶段五:Agent(复用阶段四的服务,追加工具调用能力)

Agent 应用本质上是"LLM 推理 + 工具调用循环"。阶段四已经有一个 Ray Serve LLM 服务在跑了,这里不需要另起一个 vLLM 进程——Ray Serve LLM 的engine_kwargs会把 vLLM 的工具调用相关参数原样透传给底层引擎,只需要在阶段四的LLMConfig里追加两个 key,重新serve.run一次,同一个服务就同时具备了普通推理和工具调用能力(这个透传机制来自 Ray/Anyscale 官方文档:docs.anyscale.com/llm/serving/tool-function-calling):

llm_config=LLMConfig(model_loading_config={"model_id":"qwen2.5-7b-instruct","model_source":"Qwen/Qwen2.5-7B-Instruct",},deployment_config={"autoscaling_config":{"min_replicas":1,"max_replicas":2},},accelerator_type="A10G",engine_kwargs={"tensor_parallel_size":1,"enable_auto_tool_choice":True,# 对应 vllm serve 的 --enable-auto-tool-choice"tool_call_parser":"hermes",# 对应 vllm serve 的 --tool-call-parser},)app=build_openai_app({"llm_configs":[llm_config]})serve.run(app,blocking=True)# 重新部署同一个 app,端口和地址不变

tool_call_parserhermes是因为 Qwen2.5 系列的tokenizer_config.json已经内置了 Hermes 风格的工具调用模板(这一点来自 vLLM 官方工具调用文档 的模型支持列表),不需要像部分模型那样额外指定--chat-template文件——这也是选 Qwen2.5-7B 而不是别的模型的原因,能让阶段四和阶段五的配置差异只有这两行engine_kwargs,不用节外生枝地处理模板文件。

客户端代码和标准 OpenAI SDK 工具调用完全一样,直接连阶段四/五共用的那个地址:

fromopenaiimportOpenAIimportjson client=OpenAI(base_url="http://localhost:8000/v1",api_key="dummy")tools=[{"type":"function","function":{"name":"get_weather","description":"获取给定位置的当前天气","parameters":{"type":"object","properties":{"location":{"type":"string"},"unit":{"type":"string","enum":["celsius","fahrenheit"]},},"required":["location","unit"],},},}]messages=[{"role":"user","content":"旧金山现在天气怎么样?"}]response=client.chat.completions.create(model="qwen2.5-7b-instruct",messages=messages,tools=tools,tool_choice="auto",)tool_call=response.choices[0].message.tool_calls[0].functionprint(f"模型请求调用函数:{tool_call.name},参数:{tool_call.arguments}")# Agent 循环的下一步:实际执行 get_weather(**json.loads(tool_call.arguments)),# 把结果作为 role="tool" 的消息追加回 messages,再发一次 chat.completions.create# 拿到模型基于工具结果生成的最终回答——这就是最小可运行的 Agent 闭环。

这段客户端代码的结构(tools定义、tool_choice="auto"、解析tool_calls)参照 vLLM 官方工具调用文档 的示例写法,model字段换成了阶段四/五LLMConfig里定义的model_id"qwen2.5-7b-instruct"),因为请求打到的是 Ray Serve LLM 暴露的端点,模型标识要和部署时注册的model_id对齐,而不是 HuggingFace 上的原始模型路径。这也是整条链路首尾呼应的地方:数据处理产出的语料喂给预训练,预训练模型经 verl 做 RLHF 后训练,训练完的模型经 Ray Serve LLM 上线,上线后的同一个 OpenAI 兼容端点,加两行engine_kwargs就直接被 Agent 应用复用,全程只有一个服务、一套 K8s + Ray 调度,没有另起炉灶。

(如果只是想脱离 Ray、单独验证 vLLM 的工具调用能力,vllm serve Qwen/Qwen2.5-7B-Instruct --enable-auto-tool-choice --tool-call-parser hermes单独跑起来也能达到同样效果——但这是一条独立的调试路径,不是这条全链路 pipeline 的一部分。)

小结

回到最初的问题:这套组合不是"四选一",而是分层叠加——K8s 管物理资源,Ray 管应用层编排(尤其是训练和推理要交替的 RLHF 场景),PyTorch 提供训练能力,vLLM 提供推理能力。上面五段代码里,除了预训练和后训练用的是小规模演示配置,其余的安装命令和 API 调用都是从官方文档和真实仓库逐字核实的——把演示配置换成生产规模的机器数和模型大小,就是一条能实际跑起来的生产链路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/14 5:38:14

MathorCup 2023赛题解析:从数据挖掘到优化建模的实战指南

1. 从参赛者视角看2023年MathorCup赛题的整体印象 又到了一年一度数学建模竞赛的复盘季。作为一项在国内高校圈子里颇有分量的赛事,MathorCup的题目一直以其鲜明的“应用导向”和“数据驱动”特色著称。2023年的A、B、C、D四道题,可以说将这种风格展现得…

作者头像 李华
网站建设 2026/8/14 5:37:51

易宝支付集成金蝶云星空解决方案

业务背景源系统成集云目标系统易宝支付金蝶云星空企业通过易宝支付平台处理在线支付、转账及结算业务,同时使用金蝶云星空进行财务核算与业财一体化管理。两套系统数据独立、流程割裂,导致支付数据人工导出录入效率低、转账记录核对复杂、多账户资金归集…

作者头像 李华
网站建设 2026/8/14 5:36:33

LaneDetection_End2End项目全解析:从ICCV 2019论文到实战落地

LaneDetection_End2End项目全解析:从ICCV 2019论文到实战落地 【免费下载链接】LaneDetection_End2End End-to-end Lane Detection for Self-Driving Cars (ICCV 2019 Workshop) 项目地址: https://gitcode.com/gh_mirrors/la/LaneDetection_End2End LaneDet…

作者头像 李华
网站建设 2026/8/14 5:34:58

安卓Magisk Root安装与配置全指南:从解锁Bootloader到模块管理

1. 从解锁到Root:为什么我们需要Magisk?如果你玩安卓手机有一段时间了,肯定听过“Root”这个词。它意味着获取安卓系统的最高管理员权限,就像Windows电脑上的Administrator或者Linux上的root用户。有了这个权限,你就能…

作者头像 李华
网站建设 2026/8/14 5:34:26

主流代码编辑器深度横评:从VS Code到Vim,如何选择与高效配置

1. 项目概述:为什么代码编辑器是程序员的“主战场”聊到写代码,你第一个想到的工具是什么?对于绝大多数开发者而言,答案不是某个复杂的IDE,而是一个更基础、更灵活的存在——代码编辑器。它就像作家的笔,画…

作者头像 李华
网站建设 2026/8/14 5:34:20

谷歌 Pixel 11 升级:存储翻倍、性能提升,价格上涨是否值得?

谷歌 Pixel 11 评测:升级是否值得? 谷歌 Pixel 9 和全新的 Pixel 11 有很多相似之处,比如都是 6.3 英寸的机身尺寸和 12GB 的运行内存(RAM)。不过,全新的谷歌 Pixel 11 基础存储容量翻倍,处理器…

作者头像 李华