
两台机器能合起来跑一个模型群里不少朋友在问尤其是手里的模型越来越大比如要部署 DeepSeek 这类几十 B 参数的开源模型单机 4 卡、8 卡都塞不下自然而然就打起多机的主意。vLLM 作为目前最常见的推理框架本身也依赖 Ray 来做多机编排但 Ray 不是必须品它更像一个“调度中台”只有资源需求真的超过单机能力时引入它才划算。我前后在几套环境里试过两种方案单机多卡硬撑以及两台机器组 Ray 集群跑同一套 vLLM结论很明确这取决于模型大小、并发量、网络带宽和你想不想半夜爬起来改防火墙。这篇就我从实际部署里摸出来的门道完整讲清楚什么时候值得上 Ray以及真的上了之后怎么稳。1. 先搞清楚 vLLM 单机多卡是怎么工作的1.1 vLLM 的并行方式张量并行、流水线并行、数据并行很多朋友以为“多卡跑模型”是自动的其实不然。vLLM 默认在单卡跑你要用多卡必须明确指定并行策略。最常见的是张量并行Tensor ParallelismTP把每一层的权重矩阵切成几块分别放到不同 GPU 上靠高带宽的卡间通信NVLink / PCIe Switch协同计算。这个方式对通信要求极高因为每个 Transformer 层里的前向计算都需要同步聚合全部 tensor如果卡间带宽不够性能反而会跌。NVLink 大约 600GB/s 的速度能很好支撑 8 卡以内 TP但跨机器走万兆网或 IBInfiniBand速度就掉到 10~200GB/s 级别TP 跨机基本是自找苦吃。流水线并行Pipeline ParallelismPP则不同它按层切分机器 A 跑前一半 Transformer 层机器 B 跑后一半每一层之间只传中间激活值hidden state通信量比 TP 小好几个量级。多机分布式推理通常优先考虑 PP这也是“两台机器跑 vLLM”最切实际的方式。vLLM 同时支持将 TP 与 PP 组合使用比如每台机器内部用 TP4机器之间用 PP2总体上就是 8 GPU 并行兼顾卡间高速通信与跨机低带宽需求。还有一种叫数据并行DP它是把不同的请求分给不同 GPU各自跑完整模型副本只做结果汇总跟“一台大模型”不一样。DP 适合并发量极高的场景但前提是每个副本的模型能塞进单卡/单机显存。所以你看当我们在说“多机跑一个模型”时要么用 PP要么用 DP 叠加多机而 TP 通常只在物理机内部使用。1.2 调度器逻辑continuous batching 与多机间的隐性同步vLLM 之所以吞吐高核心在于一个叫 continuous batching 的调度机制。传统推理框架会等一个 batch 的所有请求全部生成结束再处理下一批vLLM 的调度器则是“边生边调”——每个 step 只对 seq list 里的一条序列做解码做完就立刻让位给新请求相当于把算力切成细碎的时间片最大化 GPU 利用率。这个调度器在单机多卡下运行得不错因为所有 GPU 都在同一台机器上KV cache每序列的 key/value 缓存都放在本机显存调度器只需要读本地显存信息。一旦引入多机事情就变了。假设你用 PP2 把 70B 模型的前半段放在机器 A后半段在机器 B每生成一个 token机器 A 要把中间激活值送往机器 B而且两个 rank 的调度器必须保持同步。vLLM 的 scheduler 在分布式模式下会做全局统一决策每个 step 生成多少个 token、哪些序列应当被 evict都需要在所有 worker 上达成一致。这意味着一台机器网络卡一下整个生成过程都会停顿如果网络抖动频繁实际有效吞吐可能比单机多卡还低。所以这里有一个重要判断切换多机之前先量一下机器到机器之间的 RTT延迟和带宽不要凭想象觉得“反正有万兆网就行”。2. Ray 在这里扮演什么角色2.1 Ray 到底是什么一套资源调度与进程编排框架Ray 不是一个推理库它是一套通用的分布式计算框架。常见的理解方式是你在一堆机器上各装一个 Ray 进程其中一台是 head 节点负责资源统筹其他是 worker 节点向 head 注册自己的资源CPU、GPU、内存。应用程序通过 ray.remote 装饰器或者 Ray 的资源管理 API 去申请资源Ray 会帮你在合适的机器上拉起进程。vLLM 在设计多机推理时并没有去重复造一套“跨机拉起 GPU 进程”的轮子而是直接复用 Ray每个 GPU worker 对应一个 Ray ActorvLLM 把模型切分逻辑与请求调度逻辑留给自身Ray 只负责最小粒度的进程管理与故障重启。你可以把 Ray 想象成一个“宿舍管理员”它不关心你在房间里做实验但知道哪个房间住了谁、哪个床位空着。当你需要两台机器八块卡跑一个模型时vLLM 会跟 Ray 说“我需要八个有权使用 GPU 的 worker”Ray 就在已经注册的节点列表里找空闲 GPU然后远程启动 Python 进程这些进程统一执行 vLLM 的 worker 代码。2.2 vLLM 用 Ray 的具体方式Actor 与资源组在实际代码中vLLM 初始化 Ray 后会调用一种叫 ResourceGroupMapping 的机制把分布式推理所需的并行 rank 映射到不同 Ray Actor 上。每个 Actor 会承载一个 GPU worker即使是在同一台机器也有多个 Ray Actor。它面向的并非“一台机器上的 GPU 进程”而是 Ray 集群中的全局资源。因此只有当 Ray 集群里存在两节点时vLLM 才能从多个节点申请资源否则它就只能从单节点申请白白多了一层开销。从 vLLM 0.27 开始Ray 依赖被做成可选的没有装 Ray 就用单机模式装了 Ray 才做分布式。这个设计很值得点赞不需要 Ray 的用户能省去很多包冲突需要扩展的用户又可以直接使用。启动时 vLLM 会在逻辑上检测 Ray 集群有没有导致环境和版本不匹配的问题若没问题就启动对应数量的 Actor。如果你用 Docker 镜像vllm/vllm-openai:v0.27.1 默认已经装好 Ray不需要额外 pip install。2.3 什么时候引入 Ray 是划算的一道明确的决策门不是所有多机场景都需要 Ray。比如你有两台机器想分别跑两个不同模型那就独立启动两个 vLLM 服务即可完全不需要 Ray。真正的决策信号只有一个你的单个模型要想获得低延迟高吞吐但单机显存或内存已经不够放下它的 KV cache 和权重。我给出一个常用的判断公式模型权重显存 KV cache 显存 激活显存推理临时占用 单机总显存 × 0.9就该考虑 Ray 或其他分布式方案。比如一块 A100 80G4 卡总共 320G70B 模型 fp16 权重就需要约 140G算上 2048 序列长度的 KV cache约 20~30G再留一些激活缓冲4 卡能塞进去但很紧如果序列长度调到 8192KV cache 会冲上 80G 以上单机就会出问题。这时候你用两台 4 卡机器组 Ray 集群通过 PP2 把模型分层放在两台机器上每台只需承担一半权重和一半 KV cache单卡压力就小很多。如果并发量进一步飙升甚至可以把两台机器做成 DP2各跑一个完整模型负载均衡输出吞吐可以直接翻倍但需要每台机器单独放下完整模型显存要求可能不现实。什么时候不要上 Ray模型本身单机就能装下且并发量在 10~50 的常规区间用单机多卡足够。引入 Ray 会带来多一层调度开销每次请求都要经过 Ray 的远程调用增加 1~3ms 延迟对于需要个位数毫秒响应的在线服务来说得不偿失。3. 实际部署步骤两台机器跑通 vLLM Ray3.1 环境准备硬件、网络、存储、软件栈我实测的环境是两台云主机每台 4×A100 80G操作系统 Ubuntu 22.04机器 A 内网 IP 192.168.1.10机器 B 192.168.1.11之间跑的是 25Gbps 内网。为了减少不可控因素我把两台机器的主机名分别改成 node-a、node-b并在 /etc/hosts 里互相写死 IP 映射。如果你是本地机房或云 VPC务必保证 Ray 集群所有节点的 GPU 驱动和 CUDA 版本一致。我一开始偷懒一台机器驱动 535 另一台 545结果 Ray 启动后 vLLM 报 CUDA 版本不一致白折腾一小时。驱动、CUDA、容器内的 vLLM 镜像版本都需要对齐。模型文件放在两台机器都能访问的共享存储上这是多机推理最核心的细节。最简单做法是机器 A 放一份模型文件通过 NFS 共享给机器 B再或直接把模型拷到两台机器本地目录。vLLM 加载模型时需要每台机器都能读到对应层的权重单机共享路径会造成 IO 瓶颈但胜在免拷贝。实测中模型文件 200G用本地 NVMe 加载比网络盘快得多所以我最后选择在两台机器各放一份完整权重。软件方面使用 nvidia/cuda:12.1.1-base 或者 vllm/vllm-openai:v0.27.1 镜像后者开箱即用已经预装 Ray 和 vLLM 相关的依赖。建议别自己从源码编译 vLLM除非你要改调度器不然纯浪费时间。3.2 初始化 Ray 集群Head 与 Worker 的启动参数Ray 的启动顺序很关键必须先启动 head再让 worker 节点连过去。第一台机器执行ray start --head --port6379 --dashboard-host0.0.0.0 --num-cpus64 --num-gpus4--port6379是 head 节点 GCSGlobal Control Service的端口也就是 Ray 集群的“控制面”--dashboard-host0.0.0.0是为了能在另一台机器访问 dashboard排查问题方便--num-cpus和--num-gpus告诉 Ray 这台节点有多少资源可以调度。然后第二台机器执行ray start --address192.168.1.10:6379 --num-cpus64 --num-gpus4这里就用到了 hosts 里的映射最好别用 localhost。在第二台机器的/etc/hosts里加一条192.168.1.10 node-a然后在启动命令里使用ray start --addressnode-a:6379这样 Ray 在解析节点 IP 的时候不容易踩坑。启动完成后在任意节点执行ray status应当能看到Node ID、Resources一栏里两颗节点各显示GPU: 4总资源GPU: 8。只有看到 8 个 GPU 的时候vLLM 才能知道它可以申请 8 个 worker。如果这里只有 4说明第二台节点没连接成功先别继续回到网络和防火墙排查。3.3 用 vLLM 加载模型并利用 Ray 跑起来理论上拿到一个大模型比如 DeepSeek 系列之后你可以直接用 vLLM 启动 OpenAI 兼容的 API 服务python -m vllm.entrypoints.openai.api_server \ --model /workspace/models/deepseek-70b \ --tensor-parallel-size 4 \ --pipeline-parallel-size 2 \ --ray-enabled true注意几点tensor-parallel-size表示每台机器内部的 GPU 切成 4 份也就是 TP4pipeline-parallel-size表示跨机器流水线切 2 份PP2这两个参数相乘总和刚好等于 Ray 集群里的 GPU 总数4 × 2 8。vLLM 会通过 Ray 向所有节点申请 GPU 资源然后在每块 GPU 上初始化 worker。启动日志里会看到类似[ray.worker] started Ray worker、[vllm] rank 0 ... rank 7只要八条 worker 全部启动模型加载完成后就可以发送请求了。如果是自定义 CV 或 embedding 模型比如热词里提到的 qwen3-embedding-0.6b你同样可以走 vLLM 的--task embedding参数但显存占用很小单机即可搞定没必要上 Ray。这里顺便说清楚嵌入模型本就是小负载单卡推理足够不需要把多机方案往小模型上套。3.4 关键参数与运行效果5 个值得盯住的配置项使用 Ray 之后vLLM 并不需要你在 Python 脚本里额外写 Ray 调用代码框架会自动处理。但有一些环境变量和参数会影响总体性能建议在启动时统一配置。配置项建议值说明--max-num-batched-tokens4096 或 8192控制单 step 最多处理多少个 token数值越大吞吐越高但显存占用也增大结合 KV cache 显存判断--max-model-len4096 或 8192决定 KV cache 预留显存多机下每台机器各算一半--gpu-memory-utilization0.9控制在每块 GPU 上留多少显存给 KV cache不能设 1.0否则推理时显存溢出--disable-log-requeststrue压测时关闭请求日志避免打印拖慢吞吐--ray-enabledtrue明确告诉 vLLM 使用 Ray 分布式模式否则即使集群建立它也可能走单机逻辑用 Python 直接调用 vLLM engine 时需要显式初始化 Ray 并设置 global resource limit这样可以让本次 python 进程嵌入现有的 Ray 集群import ray ray.init(addressauto, namespacevllm) from vllm import LLM, SamplingParams llm LLM( model/workspace/models/deepseek-70b, tensor_parallel_size4, pipeline_parallel_size2, trust_remote_codeTrue, ) outputs llm.generate([请解释一下多机推理的原理], SamplingParams(temperature0.7))这样做的好处是可以自己掌握 Ray 初始化时机避免 vLLM 在进程内自动启动一套独立集群。我推荐在生产环境里用ray.init(addressauto)它会自动寻找当前环境中已存在的 Ray head。压测时观察 GPU 利用率两台机器的利用率应该都维持在 85%~95%而不是一台流量爆满另一台闲置。因为 PP 模式是严格串行的一台机器计算时另一台在等它的中间结果正常 GPU 监控会出现“你方唱罢我方登场”的交替现象这个意味着流水线已经在同步工作若两机完全对齐反而是异常。4. 踩坑与排查Ray 集群的常见错误整理4.1 error 1033 与 Ray 连接失败使用 Ray 集群实测的时候我最常碰到的一类错误类似error: (UnexpectedMessageError) 1033 ray id: ... ...这个错误本质上不是单一故障而是 Ray 内部在通信时接收到了一个无法处理的 message或者节点间握手失败。出现场景多为head 节点与 worker 节点网络互相 ping 不通防火墙将 6379 和 8080 端口屏蔽Ray 版本不一致导致控制信息格式不兼容。排查思路很简单先在任何节点执行ray status如果只有 head 节点信息说明 worker 没连上。然后检查两台机器之间能否互访 6379 端口nc -vz 192.168.1.11 6379如果超时去看防火墙sudo ufw status # 或 sudo iptables -L -n | grep 6379需要放行的端口通常有 6379GCS、8265dashboard、? 30000-? 0000Ray 为各 worker 分配的随机端口。这里有个技巧直接在启动 worker 时把临时端口范围缩小例如ray start --addressnode-a:6379 --node-ip-address192.168.1.11 --worker-port-min20000 --worker-port-max22000然后把 20000-22000 加进防火墙白名单能有效减少分布式环境的端口冲突。4.2 Docker 网络模式host 还是 bridge很多朋友用 Docker 部署 vLLM常见问题是容器里跑 Ray 时容器之间网络不通。默认 bridge 网络意味每个容器有独立 IPhead 容器无法直接访问 worker 容器需要端口映射-p 6379:6379但这对于 Ray 动态分配的随机端口完全不够友好。我给出的最佳方案是--network host。在宿主机上直接运行容器让 Ray 看到的就是宿主机网络。两个节点启动容器的命令如下docker run --runtime nvidia --network host \ --ipchost \ --shm-size32g \ -v /models:/models \ vllm/vllm-openai:v0.27.1 \ bash -c ray start --head --port6379--ipchost是另一处关键PyTorch 和 Ray 大量使用共享内存来传递张量如果 IPC 空间不够会频繁报 shared memory 相关错误。容器镜像本身不带模型/models挂载到两台机器的对应目录后vLLM 才能加载到模型权重。所以你在网上看到“docker vllm 镜像中带模型吗”这类问题答案是不带必须挂载。4.3 显存碎片与 KV cache 分配不均用两台 4 卡机器跑 PP 时一个常见现象是机器 A 显存占用溢出而机器 B 还有不少空闲。原因在于 PP 每一层的 KV cache 大小并不完全等同特别是 Attention 层在前后两段模型中的数量一致但某些层有额外状态。vLLM 调度器的默认策略是按 rank 平均分配显存这并不总是最优。解决办法手动调整--gpu-memory-utilization在权重更重的那台机器上降低这个值给 KV cache 留更多。或者在 vLLM 内使用--profile-memory自动计算各 rank 所需的显存让它自己决定。实际跑起来我更喜欢留 8~10% 余量毕竟多机环境里任何一卡 OOM整个请求队列都会受影响。4.4 版本兼容性清单多机部署最忌各玩各的。我建议在部署前锁死以下版本组件推荐版本说明vLLMv0.27.1Docker 镜像官方镜像自带 Ray 兼容版本Ray2.93.0vLLM 0.27 依赖 Ray 2.9 以上 APIPython3.10 / 3.11镜像自带不建议自定义 PythonCUDA Driver535 或更高所有节点相同PyTorch2.1.0与 vLLM 0.27.1 官方匹配虽然题外话但忍不住说一句vLLM 迭代速度很快0.27.1 之后不少 API 有变化社区里很多“最新版”配置反而在稳定场景里埋坑。生产环境我倾向采用官方 Docker 镜像至少保证环境一致。4.5 一些实测心得值得分享的“土办法”最后讲几条平时文档不会写的经验。第一多机之间优先用 PP 而不要盲目上 TP。我在两台机器之间用 TP8 做了一次对比结果吞吐只有 PP2 的一半不到因为网络带宽完全扛不住每步的权重同步。用千兆网时 TP 跨机甚至不如单机 4 卡性能。第二控制批大小。多机模式下 batch size 太大每 step 的中间激活体积变大跨机传输耗时增加流水线重叠效果变差。建议从--max-num-batched-tokens2048开始跑压测再慢慢往上调找到一个吞吐上涨但延迟不爆的动作区间。第三尽量把 head 节点放在离模型存储更近的机器上。Ray head 虽然不存模型但 driver 拿到结果后会汇总到 headhead 所在机器成为逻辑上的 controller。如果 head 发生故障整个集群调度停摆有条件就再搞一个 standby head或定期备份ray.get()的状态。第四有一个很多人忽略的问题共享内存大小。多机多卡推理时Ray 在各 worker 中传递数据会用到/dev/shmDocker 里的默认/dev/shm只有 64M跑复杂模型几乎必崩。我在启动时加--shm-size32g两个节点都如此很多莫名其妙的内存错误就这样消失。回到标题那个问题什么时候才值得引入 Ray我的答案很简单——当单台机器明确装不下模型或者需要多机水平扩容共享算力时Ray 是最成熟的选择。它解决的是分布式进程管理的脏活而不是把 vLLM 变成灵丹妙药。多机并行带来吞吐增长的同时也引入网络同步代价因此不要轻易上但如果决定要上就先把版本、网络、存储等基建理顺按上面的步骤一步一步来你会比我第一次配置时少熬夜。后续如果你只是在两台机器上跑不同的模型真没必要上 Ray别给自己找事。