新闻详情

新闻详情

首页 / 资讯中心 / 详情

三层调度解析:Kubernetes、Ray与vLLM如何协同驱动大模型推理

发布时间:2026/9/29 18:45:23来源:尧图网络
三层调度解析:Kubernetes、Ray与vLLM如何协同驱动大模型推理
手上跑着大模型推理服务你至少要打开三个控制台kubectl describe pod、ray status、vLLM 的监控面板。三套东西都在说“调度”含义却差着十万八千里Kubernetes 决定把 Pod 放到哪台机器上Ray 决定哪个 Actor 接活vLLM 决定同一块 GPU 上哪些 request 先算。如果你分不清楚这些“调度”各自管的边界线上出故障时很容易晕头Pod 明明正常请求还是卡死GPU 明明跑满QPS 却上不去。这篇文章从实际部署经验出发把这三层调度拆开讲清楚也顺便聊聊三个系统是怎么配合着把一个大模型服务跑起来的。1. 三层调度的分工从集群节点到 token 级1.1 Kubernetes集群资源的物理切分Kubernetes 调度器是我见过的最“笨”也最可靠的一层。它只做一件事当一个 Pod 进入 Pending 状态它就去找一个满足资源条件的 Node。requests里的 CPU、内存、GPU 数量是它的主要判断依据GPU 数量则通过设备插件上报为nvidia.com/gpu这类扩展资源。所以这一层决定了你的模型进程到底落在哪台物理机上以及这台物理机上被分配了多少张卡。比如你要部署一个 vLLM 服务YAML 至少得写成这样apiVersion: v1 kind: Pod metadata: name: vllm-deepseek spec: nodeSelector: gpu-type: a100 containers: - name: vllm image: vllm/vllm-openai:v0.27.1 resources: requests: nvidia.com/gpu: 1 limits: nvidia.com/gpu: 1 memory: 100Gi注意这里有个关键约束GPU 的requests和limits必须相等否则调度器没法保证资源独占。Kubernetes 只能看到“这张卡被某个 Pod 占用了”但它看不到这张卡里到底还有多少显存可以用也看不到 vLLM 的 KV cache 占了多满。换句话说Kubernetes 调度的是“整张卡”的资源配额不是“卡内的存储空间”。这一层做得好的地方在于它把节点选择、亲和性、污点容忍都变成了规则很容易实现“GPU 型号隔离”“在线推理任务和训练任务分池”这类需求。但它也有先天盲区——它不知道你的模型是多大的不知道一个 GPU 能否装下 DeepSeek 系的大参数也不知道两个请求在卡上会不会互相抢占。所以还需要下一层。1.2 Ray弹性资源池与应用编排Ray 的调度发生在 Kubernetes 已经分配好机器之后。它把你的推理服务拆成一个个 Actor 或 Task并负责决定这些 Actor 应该放到集群里的哪些节点上。你可以把 Ray 理解成“应用内的调度器”它只关心你声明了多少 CPU、多少 GPU然后在一个 Ray 集群内部进行匹配。比如用 Ray Serve 部署多个 vLLM 副本时你会写类似这样的代码from ray import serve from vllm import LLM, SamplingParams serve.deployment( num_replicas3, ray_actor_options{num_gpus: 1, num_cpus: 0}, autoscaling_config{ min_replicas: 3, max_replicas: 8, target_num_ongoing_requests: 100, } ) class VLLMDeployment: def __init__(self, model_dir): self.llm LLM(modelmodel_dir, gpu_memory_utilization0.9) async def __call__(self, request): ...这一段代码里ray_actor_options{num_gpus: 1}告诉 Ray每个副本需要一个完整的 GPU。Ray 调度器会去查看所有 Ray 节点上的资源登记表找到一个还有空闲 GPU 的节点把 Actor 放过去。如果集群里有 4 张 A100K8s 侧已经把 4 张卡都分配给了 Ray 的工作节点Ray 内部再把这 4 张卡分配给你创建的 3 个 vLLM 副本。Ray 比 Kubernetes 多做了几件事它知道 Actor 之间的依赖关系支持 Placement Group 的“同捆调度”还能根据分布式状态做 locality-aware 调度。什么意思如果你的模型用了 tensor parallel两个 Actor 需要各占一张卡并行推理Ray 会把它们尽量放在同一个节点上让 GPU 之间的 NVLink 通信发挥效果而不是跨节点走网络。这一层决定的是“推理副本放哪里、放几个、扩容缩容多快”。1.3 vLLM引擎内部的请求批处理真正决定一个请求能不能立刻被计算以及在 batch 里跟谁一起算的是 vLLM 引擎内部的 scheduler。vLLM 借鉴了 PagedAttention 的思路把所有序列分成 waiting、running、swapped 三类队列每个 step 都由 scheduler 做一次选择。简单说vLLM scheduler 做三件事入队新请求到达后先放到 waiting 队列检查是否有足够的 KV cache block。调度运行在每个 decode 阶段从 waiting 队列里挑若干新序列加入 batch同时确保所有 running 序列不会超出max_num_seqs和max_num_batched_tokens的限制。抢占恢复如果 KV cache 不够把低优先级或长尾请求踢出 running要么换到 CPU 内存swap要么直接丢弃重算recompute。这就是“连续批处理”的核心。传统 GPU 推理服务要等一个 batch 算完才接收新请求vLLM 则是“一边跑一边插队”——只要还有空闲计算槽位和 KV block新请求就能立刻进入当前 step 的 batch。你可以通过这些参数影响调度行为vllm serve /models/deepseek-7b \ --tensor-parallel-size 2 \ --max-model-len 8192 \ --gpu-memory-utilization 0.9 \ --max-num-seqs 64 \ --enforce-eager这里--max-num-seqs就是 vLLM scheduler 给 running 队列设的上限--gpu-memory-utilization决定用多大比例的显存做 KV cache--max-model-len过大则每个序列的显存预算会被拉高能并行的请求数反而变少。所以这一层不是“资源调度”而是“token 级请求调度”它的决策频率高到每个 step 一次响应速度以毫秒甚至微秒计。2. 为什么必须同时存在从一次请求的旅程看三层调度2.1 三者处理的时间尺度不同先看一张简单的对比表能快速理解三者的差异层级调度单位决策频率最终决定KubernetesPod / 容器创建、重建、更新时进程落在哪台机器占用几张卡RayActor / Task / Placement Group服务部署、扩缩容、请求路由时模型副本数量和摆放位置vLLMRequest / Sequence / Token每个 decode step这个 batch 算哪些请求谁先被抢占时间尺度完全不一样。Kubernetes 的调度动辄秒级而且只在资源生命周期变化时发生Ray 的调度在服务发布和流量波动时会发生毫秒到秒级vLLM 的调度是持续运转的“心跳”它不会去管节点挂了怎么办只关心 GPU 上当前的这批 token 怎么算最划算。三层解决的问题不重叠所以不是“替代关系”而是“配合关系”。2.2 一次“我要部署 DeepSeek”的调度旅程假设你想在裸金属集群上跑一个 DeepSeek 型号的大模型服务真实流程是这样的首先你把 Ray 集群的 Pod 提交给 Kubernetes。K8s scheduler 把 Ray head 和 worker 的 Pod 分别调度到有 GPU 的节点上通过nvidia.com/gpu把每张卡绑定给一个容器。这一步完成你拥有了一个“看起来有很多 GPU 资源”的 Ray 集群。接着你通过 Ray Serve 部署 vLLM 模型副本。Ray scheduler 检查各个 worker 节点上剩余的 GPU 资源决定启动几个 vLLM Actor以及每个 Actor 放在哪台机器。如果副本数超过现有空闲 GPURay 会等待直到 K8s 扩容出新的 worker Pod。然后vLLM 引擎在 Actor 内部初始化。它只关心自己拿到的这张卡把模型权重加载进去按照gpu_memory_utilization划出一块 KV cache 池。此刻外部 CPU 内存、整卡配额早就被前两层安排好了vLLM 开始全神贯注地处理推理请求。用户请求进来后Ray Serve 的路由层把请求转发到某个负载较低的 vLLM 副本vLLM scheduler 把这个请求放进 waiting 队列。如果这个 step 的 batch 还有空位并且 KV cache 够用它立刻被提上 running 队列参与 GPU 计算。至此三层调度各司其职完成了自己的那一步。所以在线上排查问题时我的习惯是先看最内层vLLM 的队列有没有堵。再往上层看 Ray 的副本是否正常、负载是否均衡最后才看 K8s 的 Pod 调度有没有被资源卡住。先微观后宏观往往比反过来更快。3. 实战配置与调优三层调度器的使用经验3.1 Kubernetes 侧把 GPU 台账管明白Kubernetes 调度器在 GPU 场景下有大量坑。最常见的是“设备插件不装、资源不显示、Pod 永远 Pending”。你要先确认节点上能看到 GPUkubectl describe node gpu-node-01 | grep nvidia.com/gpu如果输出中没有nvidia.com/gpu: 4说明 NVIDIA device plugin 没装好K8s 根本不知道这张卡存在。更麻烦的是K8s 只知道“卡数量”不知道每张卡的显存型号。你给 Pod 分配 1 张 A100 与分配 1 张 V100在 yaml 里看起来完全相同。所以我建议用 node label 区分 GPU 型号然后用 nodeSelector 或 affinity 锁定spec: templates: spec: nodeSelector: gpu-vendor: nvidia gpu-model: a100-80g同时给不同型号的节点打 taint防止不相关的任务混进来kubectl taint nodes gpu-node-01 gpu-typellm:NoSchedulePod 写上对应的 toleration 后K8s scheduler 才会放你进去。这一套配置的价值在于资源底座是稳定的后面 Ray 和 vLLM 的调度才能拿到确定性的物理资源。3.2 Ray 侧别让显存碎片化Ray 内部调度会为每个 Actor 登记资源你的 vLLM 副本声明num_gpus1后Ray 眼中就是“这张卡已占用 100%”。但如果一个高显存型号占不满 80GB另一个模型可以共享同一张卡直接声明num_gpus1就会浪费一半卡。这种场景我更常用分数 GPU 配合 vLLM 本身去限制显存占用ray_actor_options{num_gpus: 0.5, num_cpus: 1}不过要注意声明分数 GPU 会让 Ray 把两个 Actor 放到同一张卡上此时你得想清楚显存分配假设一张 A100 80G第一个模型需要用 40G第二个用 30G勉强放得下但这需要 vLLM 的gpu_memory_utilization分别设置为 0.5 和 0.375同时给模型留出上下文的余量。否则 Ray 以为资源够实际跑起来直接 OOM。还有一个我踩过好几次的坑K8s 给 Pod 分配了 1 张 GPU但 Pod 里的多个 Ray worker 都声明了num_gpus1Ray 调度不到足够资源表现为ray status显示资源不足、Actor 一直 Pending但 K8s 层面一切都正常。这种时候先确认 K8s GPU 资源和 Ray 内部资源声明是否一一对应。Ray 管理的是“虚拟 GPU 账本”最终物理资源还是以 K8s 设备插件上报为准。3.3 vLLM 侧Scheduler 与 Executor 的协作细节很多人在看 vLLM 源码时会被 EngineCore 的调度交互绕晕。实际流程其实很清晰LLM 引擎收到一批请求把它们包装成 SequenceGroup。调用scheduler.schedule()根据 running/waiting/swapped 队列和 KV block 余量选出一批可执行的序列。对选中的序列调用 executorTensor Parallel 下还要跨卡做 AllReduce这些序列会被拼成一个 batch。Executor 跑一次 forward把 logits 和采样结果返回给引擎。引擎更新 KV cache并把本轮生成的 token 送回给调度器等待下一次 schedule。这中间最核心的原则是调度器决策得越快GPU 空转越少。所以 vLLM scheduler 不是逐个请求做复杂规划而是用一套简单的启发式策略快速判谁有资格跑。你调优时关注几个指标就够了vllm:num_requests_running当前正在跑多少序列。vllm:num_requests_waiting排队的有多少。vllm:num_requests_swapped被换出到 CPU 内存的有多少。如果 swapped 很高说明 KV cache 不够每个请求都在被反复换出换进这时应该调大gpu_memory_utilization或者让模型上下文窗口小一点如果 running 已经顶到max_num_seqs而 waiting 还在涨那就不是调度问题是你副本数量不够该找 Ray 扩容了。3.4 调度参数组合的真实经验我一般不会直接抄默认参数。如果服务目标是低延迟我会把--max-num-seqs压低到 32 左右牺牲一点吞吐换稳定响应如果目标是高吞吐、能容忍长尾延迟可以拉到 128 甚至 256但一定要搭配监控看 p99 是否劣化。--max-num-batched-tokens同样值得调它限制每个 step 最多计算的 token 数量过高会拖慢单个 step 的耗时过低又发挥不出连续批处理的优势。之前有一次我部署 7B 模型同事把--max-model-len设为 32768明明只跑 2K 的请求结果每个序列预留了一大堆 KV block实际并发从 64 掉到 16。后来把模型长度改成 8192立刻正常。调度器的决策都建立在资源预算上而 KV cache 预算极其敏感别给不需要的长上下文买单。4. 问题排查与避坑三个调度器同时出问题时怎么定位4.1 Pod 一直 Pending先看 K8s 事件不熟悉的同学看到pod pending第一反应是“是不是代码跑挂了”其实大概率是调度失败kubectl describe pod vllm-deepseek-xxx Events: Type Reason Message Warning FailedScheduling 0/4 nodes available: 1 Insufficient nvidia.com/gpu, 3 node(s) didnt match node selector这种信息非常直白。要么节点上没有足够的 GPU 配额要么节点标签没打对要么设备插件没上报资源。修好之后 apply 一下就行不用怀疑 Ray 和 vLLM。4.2 Ray 服务质量下降先看副本队列如果 K8s 一切正常但请求偶尔出现 503 或者响应时间抖动多半是 Ray Serve 的缓冲队列满了。执行serve status可以看到每个 Deployment 的 replicas 和 pending 数量。serve status # deployment: VLLMDeployment # replicas: # - actor_1: pending # - actor_2: running # - actor_3: running如果副本一直有 pending回到ray status看资源有没有 GPU 空闲是不是某个节点上有 GPU 但射线资源没释放常见原因是上次缩容后旧 Actor 还在释放资源Ray scheduler 需要一点时间回收。别上来就重启整条链路。4.3 GPU 占满但 QPS 上不去直接怀疑 vLLM scheduler有一次我们发现 4 张 A100 利用率接近 95%但线上 QPS 只有预期的一半。打开 vLLM 的 Prometheus 指标发现vllm:num_requests_swapped一直在十几个左右running只有个位数。原因是一个请求的 prompt 特别长占了大量 KV block后续请求只能排队或换出。处理方式是把长上下文请求分流到单独的服务主服务限制--max-model-len或者调高gpu_memory_utilization给 KV cache 留更多空间。还有一个我之前没注意到的细节vLLM 的 preemption 策略默认可能优先抢占最早请求也可能优先抢占最晚请求具体行为跟版本相关。因此调度层面老是有长尾不要光看平均延迟要专门看num_requests_swapped这个指标。4.4 三层联调时的排查清单我在实战中整理了一个“先查内再查外”的清单每次遇到推理性能问题都按顺序走确认 vLLM metrics 中 running、waiting、swapped 是否健康。如果 waiting 高看 Ray Serve 的并发和副本数是否需要扩容。如果 Actor 无法扩容看 Ray 有没有资源碎片执行ray status和ray placement-group list。如果 Ray 资源不够检查 K8s Pod 是否 Pending节点上 GPU 是否被其他任务占用。如果节点 GPU 空着却不可用检查 node label、taint、device plugin 是否正常。这套流程基本能覆盖 90% 以上的调度问题核心思想就是不要跳过某一层去解决另一层的问题。5. 我对这三层调度的一点心得做了很久的 LLM 服务部署后我最大的感受是调度不是一道“谁最优”的单选题而是一套“谁在哪一层负责什么”的协作机制。Kubernetes 管物理边界给你稳定可靠的资源底座Ray 管应用编排让模型副本能伸缩、能路由vLLM 管 token 流转用连续批处理把 GPU 吃干榨净。三层层级分明但组合起来又互相牵制K8s 少给一张卡Ray 就扩不了容Ray 多塞一个副本vLLM 的 KV cache 预算就要重新算vLLM 一个参数改动可能反过来说明 K8s 节点规格不够了。所以现在的我遇到任何推理性能问题第一件事不是翻代码而是先问自己这个现象到底是哪一层决定出来的想清楚这个问题排障就已经完成一半了。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

AD2428 A2B主节点EEPROM自动配置实战指南 2026/9/29 19:51:01

AD2428 A2B主节点EEPROM自动配置实战指南

1. 项目概述:这不是一个“调通I2C”的小实验,而是一套可量产落地的音频系统启动方案AD2428——这颗ADI(亚德诺)推出的A2B(Audio Bus)主节点收发器芯片,在车载音响、智能座舱、高端会议系统里已经…

阅读更多 →
英特尔端侧AI实战:从智能体到具身智能的部署指南 2026/9/29 19:50:48

英特尔端侧AI实战:从智能体到具身智能的部署指南

1. 从对话框到物理世界:智能体落地的核心命题智能体这个词在过去两年被聊烂了。打开任何一个技术社区,满屏都是智能体搭建、智能体开发、智能体框架的教程,但如果你真正动手做过端侧部署,就会发现一个尴尬的现实:绝大多…

阅读更多 →
人机协同才是AI进入工业的终局:MCP、VLA与知识流转的三大变革 2026/9/29 19:50:48

人机协同才是AI进入工业的终局:MCP、VLA与知识流转的三大变革

工业现场待久了,对"AI进入工业"这件事的看法会和纯互联网圈子里很不一样。互联网上讨论AI,焦点往往是模型参数、榜单排名、生成效果有多惊艳;但真正在产线边上站过的人关心的完全是另一套东西——节拍能不能跟上、误报率能不能压住…

阅读更多 →
RTOS下状态机设计四原则:解耦、非阻塞、隔离、可控 2026/9/29 19:50:48

RTOS下状态机设计四原则:解耦、非阻塞、隔离、可控

1. 项目概述:状态机不是“画个图就完事”,RTOS也不是“开个任务就跑” 状态机与RTOS的融合实践——这个标题里藏着嵌入式开发中最常被轻描淡写、却最容易在量产阶段暴雷的核心矛盾。我带过三届校招新人,也接手过五个濒临交付失败的工业控制项…

阅读更多 →
Cursor、Copilot、Claude Code深度对比:AI编程工具如何真正提升研发效率 2026/9/29 19:50:48

Cursor、Copilot、Claude Code深度对比:AI编程工具如何真正提升研发效率

1. 从“代码补全”到“意图交付”:AI编程工具到底改变了什么先把结论摆在前面:AI编程工具确实提高了软件研发效率,但这个“提高”有非常明确的边界。它提高的是从意图到可运行代码的转化速度,而不是从模糊需求到正确系统的交付能力…

阅读更多 →
RA6M4驱动MPU6050实战:I2C时序控制与DMP固件加载 2026/9/29 19:50:48

RA6M4驱动MPU6050实战:I2C时序控制与DMP固件加载

1. 项目概述:为什么在RA6M4上啃下MPU6050这块硬骨头?瑞萨RA6M4——这颗基于Arm Cortex-M33内核、主打工业物联网与边缘智能的高性能MCU,最近在工控、机器人和高精度传感领域越来越常见。但光有芯片性能还不够,真正让设备“活”起来…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉