Ray多机启动实战:大模型推理GPU集群调度与排障

发布时间:2026/10/17 3:24:48

Ray多机启动实战:大模型推理GPU集群调度与排障 最近在做一套多机大模型推理服务模型权重分散在几台GPU机器上头一回用Ray往多个节点拉起进程的时候我犯了个很低级的错误只在head节点执行了ray start --head然后在另一台机器上敲了半天ray start --address...结果dashboard里始终只有一个节点模型的请求全部压在head这一台上GPU利用率没法看。排查了一整个下午才发现是网络端口和启动参数没对齐。这类问题在做多机Ray集群的人身上太常见了。很多人以为“多机启动”就是多执行几遍启动命令实际上一旦涉及大模型推理进程发现、资源上报、模型切分、NCCL通信这些环节都会咬合在一起任何一个环节错位整个集群看起来活着但推理就是不生效。这篇文章我会从Ray多机启动的底层机制聊起用一套完整的命令和验证脚本把流程串起来再讲大模型推理场景里怎么把模型进程真正调度到多张GPU上最后复盘几个我实际踩过的坑和完整的定位链路。1. 推理场景下为什么偏偏用Ray做多机进程调度1.1 单机推理卡在哪显存放不下吞吐上不去先说个最直接的场景。一个70B量级的稠密模型光FP16权重就有大概140GB就算做INT8量化也需要70GB左右。单机就算插满8张80GB的GPU想同时放下权重、KV cache和推理中间激活几乎不可能。这时候要么做张量并行把权重切到多卡上要么把模型拆成多个流水段放到多台机器上本质上都必须走向多机多卡。另一种更普遍的情况是并发问题。推理服务上线之后请求QPS一旦上来单机GPU的算力就变成瓶颈。我不可能为了抗住流量无限堆单机高配更合理的做法是同一份模型在多台机器上各放一个副本流量通过调度层分到不同节点。这里面绕不开一个多机器之间进程如何编排的问题。1.2 Ray解决的并不只是“多几张卡”看到这里你可能会说那我用Kubernetes或者直接用裸机上的systemd拉起多个进程不也一样确实可以但区别在于动态性和资源统一视图。大模型推理服务有两个很现实的诉求一是不同模型的流量有波峰波谷我想在某个时间点把A模型的3个副本缩成1个同时把B模型的副本从1个扩到4个这个过程最好不需要人工SSH到某台机器上敲命令二是集群里的GPU、CPU、内存资源需要被统一统计和调度否则很容易出现某个节点GPU闲置、另一个节点任务排队的情况。Ray这套框架在这一层做得比较顺手。它把整个集群抽象成一个资源池head节点负责全局调度每个worker节点定期上报本机资源用户代码通过ray.remote标注某个函数或类需要几张GPU、多少个CPU调度器会自动寻找满足条件的节点并且在运行的时候把节点IP、GPU ID等信息暴露给程序。对大模型推理来说这个抽象几乎是现成的。1.3 多机启动要解决的核心问题提炼一下多机启动本质上要解决三件事进程发现每台机器上的Ray进程如何知道彼此存在建立一个统一的集群视图。这靠head节点暴露一个固定端口worker节点主动连上来注册。资源上报每台机器上有多少GPU、多少CPU、多少内存必须准确汇报给调度器否则任务无法落到正确的节点。作业分发用户提交的推理任务通常是若干actor如何被拆分到不同节点的raylet进程上由raylet在本机拉起业务进程。这里有个容易混淆的点也是一开始困扰我的地方Ray多机启动拉起的是“Ray运行时进程”不是“模型推理进程”。Ray运行时进程包括每台机器上的raylet、object store等它们负责通信、存储和调度。模型推理进程是你后续提交流程里由Ray调度的业务actor。只有先把Ray运行时铺到所有机器上后续的推理任务才能被调度到不同节点。2. 启动多机Ray集群前先搞清楚这些角色2.1 head节点控制面、对象存储和Dashboard都在这台机器上在Ray的架构里head节点承担了全局控制服务GCS的作用所有节点的注册信息和资源信息都汇聚到这里。它同时还跑着Dashboard服务和对象存储服务。也就是说head节点不仅仅是“第一台启动的机器”它是整个集群的中枢。所以head节点的稳定性很重要。如果head挂了整个集群的调度就瘫痪了即使业务进程还在各节点上跑着也无法再接收新的任务。这也是很多人做生产级推理服务时会考虑给head单独留一台高可靠机器或者用容器化方式把它托管起来的原因。2.2 worker节点是如何“入群”的worker节点本身不承担全局调度职责它的核心进程是raylet。raylet负责管理本机资源、执行分配的task并且维护本机的对象存储。当你执行ray start --addresshead_ip:6379的时候worker节点上的raylet会向head的GCS发起注册请求完成握手之后把自己这台机器的CPU、GPU、内存等资源上报上去。此时你再回到head节点执行ray status就能看到集群节点数增加了。有一个参数值得多说一句如果你发现worker一直连不上head先别急着改业务代码去head机器上执行一下ss -lntp | grep 6379确认GCS端口监听在0.0.0.0而不是127.0.0.1。我遇到过双网卡机器启动时选了错误IP导致GCS只监听在内网回环地址上worker当然永远连不上。2.3 启动过程里到底起了哪些进程在head节点执行完ray start --head之后你可以用ps aux | grep ray看到这一堆进程gcs_server、raylet、dashboard、agent等。gcs_server是集群元数据中心raylet是本机资源管理和任务执行器dashboard提供Web界面。在worker节点执行ray start --address...之后主要看到的是raylet和agent。这里有个容易想当然的地方head节点本身也计算资源可用。ray start --head默认会在这台机器上也启动一个raylet也就是head节点既做控制面也参与计算。如果你不想让head节点跑推理任务需要在后续的ray.remote里通过资源约束把它排除比如给head节点的raylet设置--num-cpus0。3. 从零拉起多机进程完整命令与验证脚本3.1 准备阶段网络、版本、存储三项检查多机启动的准备工作其实比启动本身更重要我习惯按下面顺序检查网络连通性两台机器之间先互相ping通再用nc -vz head_ip 6379确认GCS端口通。如果有多网卡一定要确认业务网卡是哪个避免走管理网卡导致通信超时。版本一致性Ray对版本一致性要求非常严格。head节点是2.9.3worker节点是2.10.0启动大概率失败而且报错信息很隐晦。建议在每台机器上执行ray --version核对或者用同一份依赖lock文件安装。模型存储路径多机加载同一个模型文件时最好用NFS或并行文件系统挂载到各节点相同路径。如果只能本地放那就要保证每台机器上的模型路径完全一样比如统一放到/data/models/llm-70b否则不同节点拉起进程时会出现“有的节点加载成功、有的节点文件找不到”的诡异问题。3.2 在head节点启动控制面假设head节点IP是192.168.1.108张GPU物理内存256GB我建议这么启动ray start --head \ --port6379 \ --dashboard-host0.0.0.0 \ --dashboard-port8265 \ --num-gpus8 \ --object-store-memory30000000000这里几个参数分别说明一下--port6379是GCS端口worker节点后续要用这个端口连过来所以不要随意改动如果改了记得保持一致。--dashboard-host0.0.0.0很关键。默认Dashboard可能只绑定在本地回环地址你从浏览器访问head机器IP的8265端口会失败。显式设置成0.0.0.0之后就可以从任意机器的浏览器打开http://192.168.1.10:8265。--num-gpus8是告诉Ray这台机器有8张GPU。正常情况Ray能自动探测到但我遇到过容器环境里探测不准的情况手动指定更稳。--object-store-memory30000000000是对象存储内存上限我给了30GB。默认值通常是物理内存的一部分但大模型推理时每台节点还要加载权重如果对象存储占太多内存容易把节点拖死。这个值需要根据物理内存和模型加载需求调整原则是给模型加载和系统运行留足空间。3.3 在worker节点加入集群假设worker节点IP是192.168.1.11也是8张GPU执行ray start --address192.168.1.10:6379 --num-gpus8这里不需要再指定--port因为端口是head那边统一管理的。worker节点只需要知道head的地址和GCS端口剩下的资源上报、通信建立都会自动完成。启动之后回到head节点执行ray status如果输出里能看到两个节点的IP和各自的GPU资源就说明集群已经建立成功了。3.4 用一段脚本验证任务真的落到多机了很多人觉得启动命令执行完没有报错就万事大吉其实不一定。我见过“集群两个节点都Alive但所有任务全跑在head节点”的情况原因是调度时GPU资源不够或者代码里写入的节点约束不对。所以建议用一段很小的脚本验证调度是否真正分散import ray import time ray.remote(num_gpus1) def whoami(): time.sleep(1) return ray.util.get_node_ip() if __name__ __main__: ray.init(addressauto) results ray.get([whoami.remote() for _ in range(16)]) print(results)在head节点所在环境执行这个脚本ray.init(addressauto)会自动连到当前集群。如果调度正确输出里会同时出现两台机器的IP而不是只有head节点的IP。这里有个注意点addressauto只适用于已经在Ray集群节点上执行脚本的场景。如果你是从一台未加入集群的运维机器上提交任务就要用ray job submit --address http://192.168.1.10:8265 -- python test.py这种方式。3.5 停止集群时的顺序问题停集群最忌讳乱杀。我建议先停worker节点每个worker节点执行ray stop最后停head节点。如果进程卡死可以加--force参数。直接kill -9很容易留下残留的socket文件和锁导致下次启动时报端口占用或临时目录冲突。如果遇到重启后6379端口起不来多半是旧raylet进程没杀干净。执行ps aux | grep ray把残留进程处理掉再把/tmp/ray下对应session的临时目录清掉基本就能恢复。4. 模型推理任务如何真正分布到多机多卡4.1 用ray.remote封装模型worker集群建立只是第一步大模型推理业务进程怎么利用多机资源才是重点。我常用的方式是把一个模型推理服务封装成Ray actorimport os import ray ray.remote(num_gpus1, max_restarts1) class LLMWorker: def __init__(self, model_path: str, rank: int): self.rank rank # 拿到Ray分配给这个actor的GPU ID并绑定到当前进程可见设备 gpu_ids ray.get_gpu_ids() if gpu_ids: os.environ[CUDA_VISIBLE_DEVICES] ,.join(str(g) for g in gpu_ids) self.model self._load_model(model_path) def _load_model(self, model_path): # 这里调用具体的推理引擎加载模型传入rank等环境信息 pass def generate(self, prompt: str): return self.model.generate(prompt)这里有一个很多人忽略的细节Ray的num_gpus1只是给调度器看的资源约束它不会自动帮你设置CUDA_VISIBLE_DEVICES。如果不显式从ray.get_gpu_ids()里读取并设置环境变量actor里的推理框架很可能会去探测宿主机上的全部GPU导致多个actor共用同一张卡或者出现CUDA OOM。4.2 多机并行时模型切分和通信交给谁这里要把Ray的职责边界说清楚。Ray负责的是“哪台机器、哪张GPU上拉起哪个进程”但它不做模型张量并行或流水并行的计算图切分。真正的切分和跨节点NCCL通信由推理引擎自己完成。所以在多机推理场景里通常是这样协作的Ray启动N个actor每个actor代表一个模型分片或一个模型副本引擎初始化时通过环境变量比如MASTER_ADDR、MASTER_PORT、RANK、WORLD_SIZE建立NCCL通信域。如果模型必须跨节点张量并行head节点的IP通常会被当成MASTER_ADDR端口需要固定并且防火墙上开放。这里最常见的坑就是MASTER_ADDR填成了127.0.0.1导致多机之间的NCCL初始化卡住。4.3 用Ray Serve包装成HTTP服务如果要把多机推理暴露成在线服务我建议直接用Ray Serve的Deployment封装而不是自己起Flask再对接Rayfrom ray import serve import ray serve.deployment( num_replicas4, ray_actor_options{num_gpus: 1} ) class Inference: def __init__(self, model_path: str): self.model self._load(model_path) async def __call__(self, request): prompt request.query_params[prompt] return self.model.generate(prompt) serve.run(Inference.bind(model_path/data/models/llm-70b))num_replicas4会创建4个副本Ray会把它们分散到集群中满足num_gpus1的节点上负载均衡和健康检查由Serve自动处理。我在实践里比较推荐这个方案因为它把“多机启动、多副本调度、HTTP入口”这几个环节整合到了一套API里。4.4 资源绑定和节点分布的验证通过Serve部署之后我一般会在Dashboard的Actors页面确认每个副本所在的节点IP和GPU ID也可以写一行脚本遍历所有actor打印出节点分布from ray.serve import get_replica_context # 实际运行时在Replica内部可以通过get_replica_context获取当前副本信息这一步非常值得做。多机部署最大的风险不是“部署不上”而是“你以为在跑多机实际所有副本都挤在了一台机器上”。一旦确认分布正确再压流量才有意义。5. 多机启动过程中的经典故障与定位链路5.1 worker节点始终Alive不了从netstat到gRPC握手现象head节点正常worker节点执行启动命令也不报错但Dashboard里节点数不变。我的排查链路是这样的在head机器执行ss -lntp | grep 6379确认GCS监听地址。如果看到127.0.0.1:6379基本可以断定head启动时绑错了网卡。解决办法是重新用ray stop清理然后指定对外网卡启动ray start --head --node-ip-addresshead对外IP --port6379。在worker机器执行nc -vz head_ip 6379确认基本网络通。如果端口通还连不上检查两台机器的Ray版本是否一致。有一次我把worker的Ray升到2.10.0head还是2.9.3结果worker一直处于Registration状态日志里反复报协议不匹配。再看系统时间是否一致。Ray内部gRPC握手对时钟漂移敏感如果两台机器时间差太大认证会失败。用ntpdate同步一下时间再重试。5.2 模型初始化时NCCL卡死现象提交多机推理任务后日志一直停在Waiting for rendezvous所有GPU利用率都为0。这个坑我踩过不只一次。根因几乎总是出在跨节点通信环境变量上。多机分布式进程组初始化需要各个rank都能访问到MASTER_ADDR:MASTER_PORT这个地址必须是所有节点都能访问的对外IP不能是localhost。另外NCCL默认会选择它认为合适的网卡通信如果服务器有多个网卡比如一个业务网卡一个存储网卡NCCL可能选错。解决办法是在每个节点启动推理进程之前显式指定export NCCL_SOCKET_IFNAMEeth0eth0换成你实际业务网卡的名字。这一步能解决很多“明明IP能通却初始化卡死”的问题。5.3 对象存储内存占用过高导致节点OOM大模型推理时有人会把模型权重直接作为Ray对象传递比如对某个大tensor做ray.put。这其实是非常危险的操作。Ray对象存储默认基于共享内存跨节点传输几十GB的权重会瞬间占满对象存储空间触发eviction甚至让节点内存耗尽。我的建议是模型权重永远不要通过Ray对象存储传。正确做法是把模型放在共享文件系统或者各节点本地磁盘actor初始化时从路径加载。Ray只负责传字符串路径这种小对象大对象传输不仅慢还容易引发连锁故障。如果确实有中间结果需要传大对象那就需要精确调大--object-store-memory同时留意物理内存余量否则很容易把节点搞挂。5.4 明明有8张GPU任务却排队等资源现象ray status显示集群总共16张GPU但提交num_gpus2的任务却一直Pending资源池显示GPU为0/16。先看ray status里每个节点的GPU是不是0/8。如果节点显示0张GPU多半是Ray没探测到GPU检查启动时是否忘了--num-gpus或者NVIDIA驱动在容器里没有被正确映射。如果GPU显示正常但总是Pending那就要看是否已有其他actor占用了资源。我有一次就是因为上一个任务异常退出但actor没有释放GPU导致后续任务全部排队。这时候去Dashboard看Actor列表把僵尸actor删掉或者干脆重启整个Ray集群问题就解决了。还有一种情况是资源声明和实际不匹配。比如每台机器只有8张GPU但某个actor声明了num_gpus1同时因为代码里设置CUDA_VISIBLE_DEVICES失败导致进程实际占用了多卡后续任务自然就等不到资源了。所以每台节点上都要做一次“资源声明与实际占用”的对照确认没有偏差。5.5 建议养成的排查习惯多机Ray集群排障最重要的不是记住某个具体错误码而是养成一套标准动作。我的习惯是启动后先执行ray status保存资源快照提交任务后去Dashboard看每个Actor的节点分布日志优先看/tmp/ray/session_latest/logs/下的raylet.out和python-core-driver日志代码里在actor初始化时打印一行node_ip gpu_ids便于确认进程实际落在哪张卡上。这套动作帮我排查掉了很多看起来很玄学的问题大部分所谓“偶发失败”其实都是资源绑定或网络配置没对齐。多机启动Ray本身不难难的是每一步都验证到位。大多数启动失败并不是Ray框架的问题而是网络端口、版本一致性、GPU资源声明这些工程细节。在做大模型推理服务化的时候我逐渐形成了一个习惯任何多机启动之前先写一个只包含端口探测和版本核对的检查脚本把90%的启动问题挡在前面。如果你也在做类似的事情建议先把这两步固定成流程剩下的再交给Ray去调度。
延伸阅读

更多相关文章

2026/10/17 3:24:48

Bootstrap 3 侧边导航栏生产级实现方案

简介:本资源是一份基于Bootstrap 3框架实现的响应式侧边导航栏完整示例,面向前端初学者与Web开发人员,解决在传统网页中快速构建结构清晰、适配多端(尤其移动端折叠交互)的垂直导航结构问题。压缩包共9个文件&#xff…

2026/10/17 3:24:48

OpenCV文档图像增强实战:从光照不均到高精度OCR预处理

1. 文档图像增强到底在解决什么问题做过文档扫描、拍照存档或者OCR识别的人,大概率都遇到过这样的场景:手机拍了一张纸质合同,光线不均匀,左边亮右边暗,中间还有一道阴影;或者用扫描仪扫出来的老文档&#…

2026/10/17 3:24:48

桌面端微信接入实战:用Claw中间件打通个人微信消息自动化

身边不少折腾过微信自动化的朋友,最后都卡在同一个地方:手机端的机器人方案要么要求系统环境特殊,要么长期挂着不稳定;云端方案又拿不到个人微信的完整会话数据,用起来总觉得隔了一层。我自己的需求其实很朴素——想把…

2026/10/17 4:44:52

Python与Hive数据交互实战:读取、写回与性能优化

1. 为什么Python和Hive之间的数据搬运值得单独拿出来聊但凡做过数据开发或者数据分析的人,迟早都会遇到这样一个场景:上游数仓团队把清洗好的数据落在Hive表里,而你手上跑的是Python脚本——可能是做特征工程、可能是跑机器学习模型、也可能只…

2026/10/17 4:44:52

MIT-BIH心电数据解析与MATLAB绘图:从212格式到R峰标注

简介:MIT-BIH ECG心电数据库由麻省理工学院与波士顿布莱根妇女医院联合发布,是临床心律失常研究和ECG信号处理的经典数据源。这份资源面向心电信号分析的学习者与研究人员,内含1分钟心电记录及采样率等信息,可用于理解数据特性&am…

2026/10/17 4:44:52

冷热数据分离存储实践:从分层设计到迁移避坑与成本核算

在大数据领域待得久了,你会发现一个特别扎心的现实:整个集群里真正高频访问的数据,可能连20%都不到,剩下那80%的数据躺在昂贵的存储介质上,每个月烧掉的都是真金白银。冷热数据分离存储就是专门来解决这个问题的&#…

2026/10/17 4:44:52

FTP服务器从vsftpd搭建到Python脚本化上传下载全攻略

简介:这是一份面向Java开发者的FTP/SFTP文件传输工具包,解决从服务器上传、下载文件这一常见需求,适合需要快速集成文件传输能力的初学者、运维人员及项目开发人员参考复用。包内共2个文件,包含一个Java源码文件与一个JSch依赖JAR…

2026/10/17 4:44:52

微服务全链路蓝绿发布实战:Nepxion Discovery 路由机制与避坑指南

1. 蓝绿发布为什么在微服务架构下变得如此棘手单体应用时代的蓝绿发布,本质上就是运维在负载均衡器上切一下流量,把新版本集群挂上去、老版本集群摘下来,出问题再切回去。整个过程对业务代码几乎零侵入,因为所有逻辑都跑在一个进程…

2026/10/17 4:39:51

AI Agent 桥接真实浏览器:CDP 会话复用与编码工具链

1. 编码 Agent 卡在浏览器门前:登录态、验证码和真实环境的断层最近在折腾 Tencent BrowserSkill 这个开源项目时,我突然想明白了一个困扰自己很久的问题:AI Agent 和真实浏览器之间的那道墙,其实不是模型能力不够,而是…

2026/10/16 5:33:43

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/16 4:03:48

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/15 13:42:16

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/17 0:04:32

PlanarSLAM:面向曼哈顿世界的点线面联合SLAM系统

简介:本资源是面向机器人视觉与SLAM研究者的C开源RGB-D SLAM系统实现,专为结构化室内场景(如办公室、走廊)设计,通过融合点、线、平面三类几何特征并引入“曼哈顿世界”假设,显著提升位姿跟踪鲁棒性与稠密地…

2026/10/17 0:04:32

TDH Workflow调度实战:从DAG依赖驱动到任务编排与故障排查

1. 从一个调度脚本说起:TDH Workflow到底在解决什么问题第一次接触TDH的Workflow,是在一个数据清洗项目里。当时团队每天要跑几十个ETL任务,任务之间有严格的先后依赖——A表清洗完才能跑B表的关联,B表跑完才能触发C表的聚合。最开…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑