ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

OpenRig实战:统一编排LLM推理服务,破解多模型管理难题

OpenRig实战:统一编排LLM推理服务,破解多模型管理难题 如果你和我一样手里同时管着好几个模型服务一定经历过这种场面vLLM 起的服务监听 8001FastAPI 自己包了一层又监听 8002Ollama 那边还占着 11434API 风格各不相同。调用方每次都要翻文档才能接对出了故障没人知道去哪个日志里查流量一上来只能靠手动加副本。我最近认真评估并部署了 OpenRig 这个开源项目发现它正是冲着这堆烂账来的。简单说OpenRig 是一个面向 LLM/深度学习推理服务的编排与管理工具它把模型注册、运行时调度、路由分发、监控告警收敛成一套统一操作帮我把散落各处的模型服务重新拉回可控状态。这篇就完整记录我从评估、部署、压测到填坑的全过程适合正在做多模型服务管理、推理平台建设或者单纯被一堆推理后端折腾得头疼的人。1. OpenRig在解决什么问题模型服务管理的三笔烂账1.1 没上OpenRig之前的日子先说没有编排层的时候我的实际状态。项目早期只有两三个模型我还能靠手工管理一个 shell 脚本启动 vLLM一个 systemd 服务跑 FastAPI 封装另一个容器里塞着 ONNX Runtime 的 embedding 服务。每个服务各自暴露端口、各自记录日志、各自处理健康检查调用关系全靠人脑记忆。问题在三四个模型时勉强能忍一旦超过五个就很痛苦。新模型上线要手动改 Nginx 路由模型版本回退要在代码仓库里翻 commit某个副本 OOM 了只能靠 Prometheus 告警邮件兜底排查时还要登录每台机器翻 journalctl。真正捅破窗户纸的是一次线上故障embedding 服务的旧版本还在被老业务调用新版本已经换了模型名结果两边语义不一致下游检索结果全部偏差。我盯着两套配置和一份接口文档看了近一个小时才确认问题出在服务名和模型名没有统一映射上。从那一刻起我就认定缺的不是某个推理框架而是模型服务的中控层。OpenRig 就是在这样的背景下进入我的视野的。它给我的第一印象是模型不再是裸进程而是被纳管的对象。所有模型通过统一配置注册进来OpenRig 负责调度后端 runtime 实例、处理健康检查、暴露统一 API并把路由规则、扩缩容策略、监控指标集中管理。这个抽象层的价值在于调用方不用再关心某个模型到底跑在哪个进程、哪个端口、什么框架上只需要知道模型名。1.2 核心抽象模型、部署单元与运行时插件OpenRig 有三个核心概念和我之前熟悉的系统都不一样模型Model、部署单元Deployment和运行时Runtime。模型是逻辑概念它描述我要提供一个叫 llama-3-8b-instruct 的推理能力。模型本身不直接对应进程它更像注册中心里的一条服务定义包含模型路径、版本、量化方式、资源要求。部署单元则是模型的具体运行形态一个模型可以有一个或多个部署单元每个部署单元对应一个推理进程实例。运行时是真正干活的推理引擎OpenRig 通过插件方式对接 vLLM、TGI、Ollama 这类后端它对模型进程的统一启停、健康检查、指标抓取都通过 runtime 插件完成。这三层抽象设计得很像 Kubernetes 里的 Service、Deployment 和 Container但 OpenRig 不是为了管理容器而是为了管理推理语义。K8s 只管容器死活不知道什么叫 prefill、什么叫 KV Cache、什么叫首 token 延迟。OpenRig 在这一层的基础设施之上把推理特有的调度和观测能力补上了。1.3 这类工具的价值边界也要泼一点冷水。OpenRig 不是替代 vLLM/TGI也不是替代 Kubernetes它的作用是在模型与基础设施之间搭桥。模型进程还是由真正的推理引擎跑容器的生命周期还是由 K8s 管OpenRig 解决的是模型服务统一管理这一层。把它理解成模型服务层的 Nginx可能更合适Nginx 不产生业务数据但把流量治理干得很干净OpenRig 同理。想清楚这层边界之后我评估的重点就变成了三件事配置抽象是否完整、Runtime 插件是否稳定、监控和路由能力是否够用。后面几章基本都围绕这三件事展开。2. 部署前先做这笔账硬件、依赖与运行环境2.1 显存估算一张卡到底能塞几个模型部署之前最该算清楚的是显存账。很多人的第一反应是7B 模型 14GB 显存一张 24GB 的卡能跑一个实际上根本没那么简单。推理时显存消耗由三块组成模型权重、KV Cache、激活与临时缓冲区。权重大小容易算模型参数量乘以每个参数占用的字节数。FP16 是 2 字节7B 模型约 14GBINT8 量化后约 7GBINT4/AWQ 量化后约 3.5GB。KV Cache 才是隐藏变量它由层数、隐藏层维度、最大上下文长度和并发 batch 共同决定。粗略公式是KV Cache 大小 ≈ 2 × 层数 × 隐藏层维度 × 最大序列长度 × batch 大小 × 每个元素字节数用 7B 模型举例假设 32 层、hidden size 4096、最大序列长度 8192、并发 batch 8、FP16 存储那么 KV Cache 大约是2 × 32 × 4096 × 8192 × 8 × 2 字节 ≈ 34.4GB这个数字相当夸张所以实际推理引擎都做了 KV Cache 按需分配和量化。vLLM 的 PagedAttention 能大幅降低浪费但估算时不能只看权重必须看 max_model_len 和 max_num_seqs 这两个参数。我给自己的选型标准是24GB 显存的卡跑 7B/8B 模型 FP16 时单副本并发别超过 32如果量化到 INT4并发可以放到 64 到 128。一块 80GB 的 A100/H100跑 70B 模型 FP16 勉强单副本跑量化版本才能谈并发。2.2 依赖链的版本配合OpenRig 本身不生产推理能力所以环境准备的重点其实是 runtime 依赖。我当时的依赖长这样CUDA 12.2驱动 535 及以上Python 3.10使用 uv 管理虚拟环境PyTorch 2.1.x与所选 vLLM 版本强绑定vLLM 版本锁定在某个 tag不跟随 latest这里最大的教训是不要顺手装最新版。vLLM 更新非常频繁0.4 和 0.5 之间的内部 API 就有破坏性变更OpenRig 的 runtime 插件不可能永远第一时间适配。我见过同事因为装了一个刚发布的 vLLM 版本结果启动时直接报AttributeError的例子最后查了半天才发现是版本接口变了。建议用确定性的锁文件或者固定 tag例如把依赖写死为vllm0.4.2这类具体版本稳定优先。2.3 环境管理uv 比 conda 省心我推荐用 uv 而不是直接 pip install更不是往 conda base 环境里塞东西。uv 的依赖解析速度快而且支持锁文件能在团队协作里保证环境一致。实际踩坑时我发现 grpcio 和 protobuf 的版本冲突在推理项目里非常常见尤其是 runtime 插件走 gRPC 通信时protobuf 版本不匹配会在启动阶段报TypeError: Descriptors cannot be created directly。解决方法是让 uv 统一解析一遍或者干脆给 protobuf 固定一个和 grpcio 匹配的版本。如果你已经在用 conda也不是不行但建议为 OpenRig 单开一个环境不要复用训练环境。训练环境里往往有一堆和推理无关的包版本纠缠容易让一个本来很干净的部署排查变成考古现场。3. OpenRig核心工作流模型注册、路由与监控的三板斧3.1 模型注册配置别偷懒OpenRig 里上线一个模型本质是提交一份注册配置。我基于实际使用整理了一份比较接近自己生产用法的 YAMLmodel: name: llama-3-8b-instruct path: /data/models/llama-3-8b-instruct revision: main runtime: vllm resources: gpu_memory_utilization: 0.85 max_model_len: 8192 max_num_seqs: 64 quantization: type: awq path: /data/models/llama-3-8b-awq health_check: path: /health timeout: 60 interval: 10逐字段说下为什么重要。gpu_memory_utilization控制 runtime 进程对显存的占用上限0.85 是我比较习惯的保守值留出余量给 CUDA context 和临时 buffermax_model_len决定 KV Cache 怎么分配设太大会浪费显存设太小会拒掉长文本请求max_num_seqs决定并发序列数直接影响吞吐和显存峰值。health_check很多人会忽略但后面坑就出在这里。健康检查路径、超时时间、检查间隔这三个值需要和模型加载时长匹配。大模型加载有时要一两分钟如果超时时间设成 5 秒OpenRig 会在模型还没就绪时就把副本标记为失败然后一遍遍重启形成死循环。我在测试环境把 timeout 设 60 秒就再没出现误判。3.2 路由、多副本与自动扩缩模型注册完成后OpenRig 会给模型分配一个统一访问入口调用方只需要用模型名请求。路由层会在所有可用副本之间做分发并在某个副本健康检查失败时自动摘除流量。这一步彻底解决了我之前每次上线新模型都要手改路由配置的问题。多副本的经验是生产环境至少跑两个副本。这不是为了吞吐而是为了无感升级。模型版本更新时OpenRig 可以一个副本一个副本地滚动替换始终保留一个可用实例对外服务。我见过单副本部署导致的中断事故教训是不要为了省显存把可用性抠没了。自动扩缩方面OpenRig 支持按队列长度和 TTFT 这类推理指标扩缩这比按 CPU 利用率扩缩合理得多。GPU 推理的瓶颈通常在显存和调度器排队CPU 可能一直不高盲目按 CPU 扩容会反应迟钝。我的告警规则里队列平均等待时间超过 2 秒就触发扩容TTFT 超过 5 秒则马上告警这两条比任何基础设施指标都直接。3.3 接入监控三个必须看的指标OpenRig 接入 Prometheus 之后我主要盯三个指标它们比单纯看 GPU 利用率实用得多openrig_queue_time_seconds推理请求在调度器里排队的时间。这个值上涨说明副本数不够或单副本并发打满是扩缩容最灵敏的信号。openrig_tftt_seconds首 token 延迟衡量用户发出请求到看到第一个 token的耗时。交互式应用尤其在意它。openrig_itl_seconds或解码吞吐token 与 token 之间的间隔衡量生成阶段的流畅度。三个指标要放在一起看TTFT 高而 queue time 低说明 prefill 计算过重ITL 高而显存利用率高说明 decode 阶段被长上下文请求拖慢queue time 全面上涨说明该扩容了。这种组合判断比单看任何一个指标都靠谱。4. 混合负载实测同一批模型在不同配置下的表现4.1 压测工具怎么选我在压测时用了 oha 和一段自写的小脚本。oha 适合快速打并发请求简单命令就能产生稳定的每秒请求数。它的局限是不好精细控制 prompt 长度和生成 token 数所以我另外写了一个 Python 脚本用asyncio并发发请求每个请求指定不同的输入长度和max_tokens模拟真实场景里的长短混用。压测前一定要做 10 到 20 分钟预热。推理服务的显存分配、KV Cache 池在初始阶段不稳定预热后指标才可信。我见过有人刚启动模型就打压测结果前几十个请求慢得离谱最后被误判为模型质量不行。4.2 一次对比压测同样的模型三种配置三组数据我用 Llama-3-8B 做了三组配置压测时长 15 分钟混合负载中 20% 是 128 token 短输入、80% 是 1024 token 中长输入生成长度固定在 256 token。数据如下配置QPSTTFT(p50)TTFT(p99)显存峰值AFP16单副本max_num_seqs12842780ms2.1s18.2GBBINT8量化max_num_seqs6436640ms1.7s10.4GBCFP16双副本每副本max_num_seqs6455520ms1.4s36.1GB结果可以解释清楚。配置 A 单副本拉满并发吞吐不错但 p99 延迟偏高说明调度器里存在严重的队头阻塞配置 B 量化后显存峰值下降明显但吞吐反而没有显著提升瓶颈从显存变成了 decode 的计算和显存带宽配置 C 靠双副本分散负载TTFT 下降最明显代价是显存翻倍。这里的关键认知是推理服务调优不是一个全局最优问题而是一个为场景做取舍的问题。如果模型被用于实时对话TTFT 是核心双副本更合适如果模型被用于离线批量打分吞吐优先单副本大并发更好如果显存是硬约束量化就是唯一选择。4.3 压测后值得做的两个调优动作第一件是限制最大输入长度。我在压测里发现有些请求带了 4000 token 的输入prefill 阶段计算量巨大会阻塞同卡上其他短请求的调度。后来在 OpenRig 配置里把该模型 max_model_len 限制在 4096同时给超长文本单独开了一个专用模型服务问题立刻缓解。这相当于把所有流量走同一条路改成按请求特征分路对混合负载效果明显。第二件是连接复用。压测时用短连接打丢了很多性能在 TCP 握手和服务端的线程切换上。把客户端改成 HTTP keep-alive 长连接后QPS 提升接近 20%。如果你用 oha可以加-H Connection: keep-alive这类参数如果用自写脚本直接复用aiohttp.ClientSession即可。5. 上手之后避不开的五个坑5.1 Runtime 版本不一致第一天就崩溃我刚开始装的是最新 vLLM结果 OpenRig 启动模型后进程起来又立刻退出。翻日志看到一段 traceback指向 vLLM 某个内部类已经被拆掉。这属于典型的 runtime 接口漂移OpenRig 插件跟不上 vLLM 的更新节奏。排查链路不复杂先看 OpenRig 日志确认模型实例反复重启再逐条看 runtime 子进程的 stderr最终锁定是版本问题。修复方式不是等新版适配而是把 vLLM 固定到 OpenRig 官方文档验证过的 tag。这让我养成了习惯任何 runtime 升级前先查 OpenRig 的 release notes 里有没有提到兼容性变化绝不做顺手升级。5.2 长文本请求把整卡拖垮有一次上线后发现某个节点上的模型延迟突然飙升但副本数和显存看起来都正常。排查后才发现有个调用方把一份 16K token 的文档直接丢进来prefill 阶段算了快十秒期间同一副本上的在线请求全部排队等这一份长文本做完用户体感就是卡死。修复动作有两个层面OpenRig 配置层把该服务max_model_len调低防止超长请求进入架构层把长文档离线处理和短文本实时对话拆成两个模型服务避免互相干扰。之后我在设计新服务时都会先问一句这个模型服务的请求长度分布到底是什么样的这个问题的答案直接决定显存和调度配置。5.3 显存碎片和 KV Cache 浪费压测时遇到过一个诡异现象模型只用了 13GB 显存但再启动一个副本却报 CUDA out of memory。原因在于 GPU 显存碎片化分割了剩余空间单个连续块不够模型权重加 KV Cache 使用。vLLM 的 PagedAttention 能缓解一部分碎片问题但如果日志里出现大量 non-contiguous memory 相关提示就要考虑用一定显存整理手段或者干脆把两个副本放到不同的物理卡上。KV Cache 浪费也是容易被忽略的。默认情况下vLLM 会把显存按比例预留给 KV Cache如果缓冲区的gpu_memory_utilization设太高而实际并发很低大量 KV Cache 被白白占有。我把利用率从 0.9 调回 0.85 后模型照常跑显存还多出几个 GB 给其他服务。5.4 健康检查超时太短导致副本反复重启这个坑前面提过值得专门讲一次。我的失误是把health_check.timeout设成 5 秒结果模型加载需要 30 秒OpenRig 每 10 秒探一次5 秒没有响应就标记失败然后拉起一个新副本新副本又要加载模型于是陷入重启—加载—超时—再重启的循环。界面看起来状态一直 flapping服务却始终没就绪。排查链路是先看副本事件历史发现一小时内重启几十次再看健康检查日志发现全是超时再对比模型加载日志确认加载时间远超阈值。修复就是把超时调到 60 秒并让健康检查接口返回model_ready字段OpenRig 在模型加载完成前不把实例标记为可用。5.5 健康检查探活通过但模型实际不可用最后一个坑很有意思健康检查返回 200但请求进去后直接连接超时。原因是默认的健康检查只验证了 HTTP 端口存活没有验证模型是否真正完成加载并可以推理。正确的做法是给健康检查路径加上真实逻辑比如让检查接口在内存里维护一个ready标志只有模型加载完成、首次推理测试通过后才设置为 true。OpenRig 能识别这个状态并据此决定是否分发流量。这个改动几乎零成本但直接避免了端口活着、模型死了这种最隐蔽的故障形态。6. 什么场景该用OpenRig什么场景不用硬上6.1 先看规模再选择我用下来的体会是OpenRig 这类工具不是银弹它有非常明确的适用边界。如果团队只有一两个模型调用方也就两三个那用 docker compose 加一个 Nginx 完全够用硬上 OpenRig 反而多了一层要维护的系统。但如果你的模型数量超过五个或者同一个模型有多个版本在对外提供服务或者你希望让算法工程师自己上线模型而不必麻烦运维那就值得引入。我的判断标准很简单当服务之间怎么连接这个问题开始频繁占用人的精力时就需要一个编排层来接管这件事。它帮我省去的是大量重复的人肉路由管理和排障时间收益是实打实的。6.2 不要以为有了OpenRig就不需要K8sOpenRig 和 Kubernetes 不是替代关系。OpenRig 负责推理服务层的模型路由、扩缩容策略、健康检查和观测指标K8s 负责真正的容器调度、资源隔离、网络和存储。在一个成熟的环境里OpenRig 可以运行在 K8s 之上由它里面的控制器来管理 Deployment两者各管一层。如果你现在还在裸机上跑服务可以先用 OpenRig 把模型生命周期管理起来如果你已经有 K8s建议把 OpenRig 部署进去让它只做模型编排其他交给平台层。这个组合是目前我看到最稳的架构。6.3 下一步我会怎么扩展OpenRig 用稳定之后我最想做的扩展是接入请求日志采样和 A/B 路由。请求日志采样能记录每次请求的模型版本、输入长度、TTFT 和输出质量这对线上模型评估非常有价值。A/B 路由则能基于模型名实现新旧版本按流量比例分发灰度回归会变得很轻松。另外还有一个观察OpenRig 这类推理服务中控未来很可能会和向量数据库、RAG 应用的元数据打通形成更上层的智能体基础设施。到那时候模型服务的编排就不再是单独的部署问题而是整个 AI 应用平台的一部分。最后分享一个我在实际使用中比较顺手的小技巧OpenRig 的日志里有个调度器字段每次请求都会打印排队等待时长。你在人肉排查模型怎么这么慢的时候先看这个字段有没有上涨。如果上涨问题往往在并发和调度层而不是模型本身。这个小习惯能帮你少走很多弯路。
返回列表