ARTICLE DETAIL

资讯详情

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

共享LLM服务跨模型自动扩缩容:从线上事故到GPU利用率翻倍的实战

共享LLM服务跨模型自动扩缩容:从线上事故到GPU利用率翻倍的实战 1. 从一次线上事故说起为什么共享 LLM 服务需要跨模型扩缩容去年冬天我们团队负责的一个多模型推理平台在凌晨两点崩了。原因说出来有点丢人一个客户在深夜批量提交了上万条长文本摘要请求全部打到了我们部署的 70B 模型实例上。而与此同时另外三个 13B 和 7B 的实例池几乎处于空转状态GPU 利用率不到 15%。流量全挤在一个模型上排队延迟从 800ms 飙到 12 秒最终触发熔断整个服务不可用。事后复盘问题根源很清晰我们的扩缩容策略是按模型独立配置的。每个模型有自己的最小副本数、最大副本数、扩容阈值彼此之间完全隔离。这种设计在单模型场景下没问题但一旦多个模型共享同一个 GPU 集群就会出现“旱的旱死、涝的涝死”的局面。70B 模型那边 GPU 显存吃紧13B 这边资源闲置但调度器不会把 13B 的卡挪给 70B 用因为它们是两套独立的扩缩容策略。这就是共享 LLM 服务场景下的跨模型自动扩缩容要解决的核心问题。它研究的不是“单个模型怎么扩缩容”而是“多个模型共享资源池时如何跨模型动态调配 GPU 资源在满足各模型 SLO 的前提下最大化整体利用率”。适合正在做 LLM 推理平台、模型服务化、GPU 集群调度的同学参考也适合对 Kubernetes 自动扩缩容机制感兴趣但还没接触过 LLM 服务场景的工程师。我花了大概两周时间精读了这篇论文结合我们平台的实际改造经验把里面的核心思路、关键设计、实操要点和踩过的坑整理出来。文章会比较长但如果你正在被多模型共享 GPU 集群的资源调度问题困扰应该能帮你省下不少试错时间。2. 论文核心思路拆解跨模型扩缩容到底在解决什么2.1 传统扩缩容方案在共享 LLM 场景下的三个致命缺陷先说清楚传统方案为什么不行。目前主流的 LLM 服务扩缩容基本沿用两种思路一种是基于 Kubernetes HPA 的 CPU/GPU 利用率阈值触发另一种是基于请求队列长度的自定义指标扩缩容。这两种方案在单模型独占资源池的场景下都能跑通但放到共享场景就会暴露三个问题。第一个缺陷是资源孤岛。每个模型独立配置扩缩容策略意味着 GPU 资源被静态划分。70B 模型最多用 8 张 A10013B 模型最多用 4 张7B 模型最多用 2 张。即使 7B 模型那边一张卡都没跑满70B 模型也不能借用。论文里给了一组数据在典型的多模型共享集群中静态划分导致的平均 GPU 利用率只有 35% 到 45%峰值时段部分模型过载、部分模型闲置的情况持续存在。第二个缺陷是扩容决策的滞后性。LLM 推理的请求模式和传统 Web 服务完全不同。传统服务的请求处理时间通常在毫秒级扩容后几十秒内就能见效。但 LLM 推理尤其是长文本生成单个请求可能占用 GPU 几十秒甚至几分钟。等你发现队列积压再触发扩容新实例冷启动加载模型权重又要几分钟用户早就超时了。论文里提到在 70B 模型上从触发扩容到新实例 ready平均需要 4 到 7 分钟这期间 SLO 基本必然被打破。第三个缺陷是模型间的资源竞争不可预测。多个模型共享 GPU 时显存是最紧张的资源。70B 模型在 FP16 精度下光权重就要占约 140GB 显存至少需要 2 张 80GB 的 A100。13B 模型权重约 26GB一张 A100 就能跑。当 70B 模型需要扩容时它需要的是整张 GPU 卡而不是零碎的算力。但传统扩缩容方案是按“副本数”来调的一个副本可能对应半张卡或一张卡调度器很难在模型之间做显存级别的精确调配。2.2 跨模型扩缩容的核心洞察把模型副本当作可迁移的资源单元论文的核心洞察其实一句话就能概括不要把 GPU 资源静态分配给模型而是把模型副本本身当作可以在 GPU 池中动态迁移和重新编排的资源单元。这个思路的转变很关键。传统方案里资源分配和模型部署是绑定的你给 70B 模型分配 8 张卡这 8 张卡就归它管别的模型不能用。跨模型扩缩容的做法是整个集群的 GPU 组成一个统一资源池每个模型副本声明自己需要多少显存、多少算力调度器根据当前各模型的负载情况动态决定哪个模型应该获得更多副本、哪个模型应该释放副本。论文里把这个过程拆成了三个子问题负载感知的副本需求预测、跨模型的资源再分配决策、副本迁移的执行与冷启动优化。这三个子问题对应了跨模型扩缩容的三个核心模块后面我会逐一展开。2.3 为什么不能简单用“全局 HPA”代替有人可能会想那我把所有模型放在一个 Deployment 里用一个全局 HPA 根据总队列长度来扩缩容不就行了论文专门讨论了这种朴素方案的局限性。全局 HPA 的问题在于它无法区分不同模型的 SLO 差异。70B 模型的用户可能能接受 2 秒的首 token 延迟但 7B 模型的用户期望是 500ms。如果只看总队列长度全局 HPA 可能会优先扩容 7B 模型因为它的请求量大、队列增长快而 70B 模型虽然队列不长但每个请求处理慢反而得不到资源。结果就是 70B 模型的 SLO 被牺牲而它的用户往往是付费更高的大客户。另一个问题是扩缩容的粒度不匹配。全局 HPA 调整的是总副本数但不同模型副本的资源需求差异巨大。一个 70B 副本可能需要 2 张 A100一个 7B 副本只需要 1 张 A10。全局 HPA 说“扩容 3 个副本”调度器根本不知道这 3 个副本应该给谁、需要多少卡。所以跨模型扩缩容必须做模型级别的细粒度决策而不是集群级别的粗粒度调整。3. 核心模块拆解负载预测、资源再分配与副本迁移3.1 负载感知的副本需求预测怎么算每个模型“应该”有多少副本跨模型扩缩容的第一步是预测每个模型在当前负载下需要多少副本才能满足 SLO。论文用的方法结合了短期请求速率预测和排队论模型。具体来说对于每个模型 m系统会持续采集过去 5 分钟的请求到达速率 λ_m、平均请求处理时间 T_m包括 prefill 和 decode 阶段、以及当前队列长度 Q_m。然后用一个轻量级的时序预测模型论文用的是 ARIMA实际落地也可以用 Prophet 或简单的指数平滑预测未来 2 分钟的请求速率 λ_m。有了 λ_m 和 T_m就可以用M/M/c 排队模型估算不同副本数 c 下的平均等待时间 W_c。目标是找到最小的 c使得 W_c 小于该模型的 SLO 阈值。论文里给的公式是W_c ≈ (P_wait) / (c * μ - λ)其中 μ 1/T_mP_wait 是请求需要排队的概率。这个计算本身不复杂但有几个实操细节需要注意。T_m 的测量必须区分 prefill 和 decode 阶段因为这两个阶段的 GPU 占用模式完全不同。Prefill 是计算密集型的decode 是显存带宽密集型的。如果只用一个平均处理时间预测会不准。论文建议分别采集 prefill 延迟和 decode 延迟然后按请求的平均输入长度和输出长度加权。另一个细节是冷启动时间的补偿。预测未来 2 分钟的需求时必须把新副本的冷启动时间考虑进去。如果 70B 模型冷启动需要 5 分钟那你预测 2 分钟后的需求再扩容就来不及了。论文的做法是给每个模型维护一个冷启动时间表预测窗口至少覆盖冷启动时间加上 SLO 容忍的排队时间。3.2 跨模型资源再分配谁该让出 GPU谁该获得 GPU预测出每个模型需要的副本数之后下一步是决定怎么在模型之间调配 GPU。论文把这个决策建模成一个多目标优化问题目标是在满足所有模型 SLO 的前提下最小化总 GPU 使用量。具体来说假设集群有 N 张 GPU每个模型 m 当前有 c_m 个副本预测需要 c_m 个副本。如果所有模型的 c_m 之和小于等于 N那就直接按需分配。但现实中往往是超出的这时候就需要做取舍。论文的取舍策略是按 SLO 违反的边际成本排序。具体来说对于每个模型计算“少给一个副本会导致 SLO 违反概率增加多少”。这个边际成本越高的模型越优先获得资源。边际成本低的模型即使暂时低于预测需求也可以先扛一扛。这个策略背后的逻辑是不同模型的 SLO 违反代价不同。一个 70B 模型如果 SLO 违反可能导致大客户投诉甚至流失一个 7B 模型如果 SLO 轻微违反可能只是少量用户感觉慢了一点。所以资源应该优先给“违反代价高”的模型。实操中这个边际成本很难精确计算。论文给了一个近似方法用历史 SLO 违反率和对应的业务损失来估算。比如 70B 模型过去一个月 SLO 违反 3 次每次导致 2 个客户投诉每个客户年付费 10 万那单次违反的期望损失就是 20 万/3 ≈ 6.7 万。7B 模型 SLO 违反 10 次每次导致 5 个用户流失每个用户年付费 1000单次违反损失约 500。这样一对比资源优先给谁就很清楚了。3.3 副本迁移的执行怎么把 GPU 从一个模型“挪”给另一个模型决策做完之后执行层面还有一个大问题GPU 资源不是橡皮泥不能随便捏。一个 70B 模型副本占着 2 张 A100你要把它挪给 13B 模型用必须先把这个 70B 副本停掉、释放显存然后 13B 模型才能加载。论文把这个过程叫做副本迁移并设计了三种迁移策略策略一优雅驱逐。当需要释放某个模型的副本时先把这个副本从负载均衡池里摘掉不再接收新请求。等它处理完当前所有进行中的请求再停掉容器、释放 GPU。这个策略对用户完全透明但等待时间可能很长因为 LLM 请求可能跑几十秒。策略二强制抢占。如果 SLO 已经严重违反等不及优雅驱逐就直接杀掉副本上的进行中请求释放 GPU。被杀的请求会返回错误由客户端重试。这个策略快但会影响用户体验。论文建议只在紧急情况下使用并且要配合请求重试机制。策略三预迁移。在负载预测显示某个模型即将需要更多资源时提前把低优先级模型的副本迁移到其他节点或直接缩容腾出 GPU 给高优先级模型。这个策略最平滑但依赖预测准确性。预测错了就会导致资源浪费或频繁迁移。我们平台实际落地时主要用的是策略一和策略三的组合。策略二只在极端情况下手动触发。实测下来优雅驱逐的平均等待时间是 15 到 45 秒取决于当前进行中的请求数量和长度。预迁移的准确率大概在 70% 左右也就是说有 30% 的预迁移是多余的但整体收益仍然为正。4. 实操落地从零搭建跨模型扩缩容的五个关键步骤4.1 第一步统一 GPU 资源池与模型副本抽象落地跨模型扩缩容的第一步是把 GPU 资源从“按模型静态划分”改成“统一资源池”。我们用的是 Kubernetes 加自定义调度器的方式。具体做法是给所有 GPU 节点打上统一的 label比如gpu-typea100、gpu-memory80g。然后每个模型副本的 Pod 里声明自己需要的 GPU 资源比如 70B 模型声明nvidia.com/gpu: 213B 模型声明nvidia.com/gpu: 1。调度器根据当前各节点的 GPU 空闲情况把 Pod 调度到合适的节点上。这里有个坑Kubernetes 默认的 GPU 调度是整卡分配的一张 A100 要么全给一个 Pod要么不给。但 LLM 推理有时候可以用 MIGMulti-Instance GPU把一张卡切成多个小实例。论文里没有深入讨论 MIG但我们实测发现对于 7B 以下的模型用 MIG 切分可以显著提升利用率。比如一张 A100 切成 3 个 20GB 的实例可以同时跑 3 个 7B 模型副本。不过 MIG 的配置比较复杂而且不是所有 GPU 都支持建议先在小规模环境验证。另一个坑是显存碎片化。当集群里同时有 70B、13B、7B 模型时70B 需要连续 2 张卡13B 需要 1 张卡7B 需要半张卡。如果调度不当可能会出现“总空闲显存够但凑不出连续 2 张卡”的情况。我们的解决办法是给调度器加一个碎片整理逻辑当检测到某个模型因为碎片化无法调度时触发一次低优先级副本的迁移把碎片合并成连续资源。4.2 第二步部署负载采集与预测模块负载采集模块需要采集三类数据请求级别数据到达时间、模型名、输入长度、输出长度、处理延迟、副本级别数据每个副本的 GPU 利用率、显存占用、队列长度、集群级别数据总 GPU 数、已分配数、空闲数。我们用的是 Prometheus 加自定义 Exporter 的方式。每个模型副本的 sidecar 容器负责采集本副本的指标暴露成 Prometheus 格式。请求级别的数据通过 API Gateway 的日志采集写入时序数据库。预测模块我们一开始用 ARIMA后来换成了 Prophet因为 Prophet 对节假日和周期性波动的处理更好。预测窗口设为 3 分钟每 30 秒更新一次预测结果。这里的关键是预测粒度要细到模型级别不能只预测总请求量。因为不同模型的请求模式差异很大70B 模型的请求可能集中在工作时间7B 模型的请求可能全天均匀分布。注意预测模块的冷启动是个问题。新上线的模型没有历史数据预测会不准。我们的做法是给新模型一个保守的初始副本数然后在前 24 小时用实际负载快速校准预测模型。4.3 第三步实现跨模型资源再分配决策器决策器是跨模型扩缩容的核心。我们实现了一个简单的决策循环每 30 秒跑一次从预测模块获取每个模型未来 3 分钟需要的副本数 c_m。计算当前总副本数 sum(c_m) 和集群最大副本容量 N。如果 sum(c_m) N直接按 c_m 调整。如果 sum(c_m) N按边际成本排序优先满足高成本模型。生成扩缩容指令发给执行器。边际成本的计算我们简化成了SLO 权重。每个模型配置一个权重 w_m表示 SLO 违反的相对代价。70B 模型权重设为 1013B 设为 57B 设为 1。当资源不足时按 w_m * (c_m - c_m) 排序优先满足乘积大的模型。这个简化版决策器在实际运行中效果不错但有一个问题权重是静态配置的不能反映实时业务变化。比如某个大客户临时升级了服务等级它的模型权重应该动态提高。我们后来的改进是接入业务系统的客户等级数据每小时更新一次权重。4.4 第四步副本迁移与冷启动优化副本迁移的执行我们用了 Kubernetes 的 Deployment 缩容加自定义控制器。当决策器说“70B 模型从 4 个副本缩到 3 个”控制器会选一个副本先把它从 Service 的 Endpoints 里摘掉然后等待进行中请求完成最后删除 Pod。冷启动优化是另一个重点。LLM 模型加载权重很慢70B 模型从零启动要 5 到 7 分钟。我们的优化手段有三个第一模型权重缓存。把模型权重文件放在高速本地 SSD 上Pod 启动时直接从本地加载不走网络。这个优化能把加载时间从 5 分钟降到 2 分钟左右。第二预热副本池。维护一个“热备”副本池里面是已经加载好权重但没接收流量的副本。当需要扩容时直接从热备池里拿省去加载时间。热备池的大小根据历史扩容频率动态调整一般保持 1 到 2 个热备副本。第三增量加载。对于同一个模型的不同版本如果权重差异不大可以只加载差异部分。这个优化我们还在实验阶段效果不太稳定暂时没上生产。4.5 第五步SLO 监控与反馈闭环跨模型扩缩容不是一锤子买卖需要持续的监控和调优。我们建了一个 SLO 监控看板实时展示每个模型的首 token 延迟、端到端延迟、SLO 违反率、GPU 利用率。关键指标是SLO 违反率和GPU 利用率的平衡。如果 SLO 违反率很低但 GPU 利用率也很低说明资源给多了可以适当缩容。如果 SLO 违反率很高但 GPU 利用率也很高说明资源不够需要扩容或优化模型推理效率。我们设的告警阈值是任一模型 SLO 违反率连续 5 分钟超过 1%触发告警集群整体 GPU 利用率连续 30 分钟低于 40%触发资源浪费告警。这两个告警帮助我们及时发现扩缩容策略的问题。5. 常见问题与排查技巧实录5.1 扩容后 SLO 反而变差可能是冷启动拖累我们遇到过好几次这样的情况决策器判断 70B 模型需要扩容从 3 个副本加到 4 个。但扩容后首 token 延迟反而从 1.8 秒涨到了 2.5 秒。排查后发现新副本冷启动期间会占用大量 CPU 和 IO 资源加载权重导致同节点上其他副本的推理速度变慢。解决办法是给冷启动副本设置资源隔离。在 Kubernetes 里给新启动的 Pod 设置较低的 CPU 优先级或者把冷启动 Pod 调度到专门的“加载节点”上加载完成后再迁移到推理节点。我们后来用了第二种方案效果比较明显。5.2 模型间显存竞争导致 OOM共享 GPU 集群里显存是最容易出问题的资源。我们遇到过 70B 模型扩容时调度器把它调度到了一个已经有 13B 模型运行的节点上结果 70B 模型加载到一半显存不够触发 OOM把 13B 模型也带崩了。根因是调度器没有做显存预留。Kubernetes 默认的 GPU 调度只检查“卡是否空闲”不检查“显存是否够用”。我们的修复方案是给调度器加了一个显存预检逻辑在调度 Pod 之前先计算目标节点上所有已运行 Pod 的显存占用总和加上新 Pod 的显存需求如果超过节点总显存就拒绝调度。5.3 预测模型频繁抖动导致扩缩容震荡扩缩容震荡是自动扩缩容的经典问题。我们的预测模块一开始每 30 秒更新一次导致副本数在 3 和 4 之间反复横跳。每次扩容要 2 分钟缩容要 1 分钟系统一直在迁移副本反而影响了稳定性。解决办法是加滞后窗口和最小变更间隔。滞后窗口是指只有当预测需求连续 3 次超过当前副本数时才触发扩容连续 5 次低于当前副本数时才触发缩容。最小变更间隔是指两次扩缩容之间至少间隔 5 分钟。这两个措施把震荡频率降低了 80% 以上。5.4 常见问题速查表问题现象可能原因排查方法解决方案扩容后延迟反而升高冷启动占用资源查看新 Pod 的 CPU/IO 使用率冷启动资源隔离或专用加载节点模型加载 OOM显存预留不足检查节点显存分配记录调度器加显存预检副本数频繁震荡预测抖动查看预测曲线和副本数变化加滞后窗口和最小变更间隔部分模型长期饥饿权重配置不合理对比各模型 SLO 违反率动态调整模型权重迁移后请求失败强制抢占查看请求错误日志改用优雅驱逐或加请求重试6. 实际落地效果与后续优化方向我们平台在引入跨模型扩缩容之后整体 GPU 利用率从原来的 38% 提升到了 62%峰值时段的 SLO 违反率从 4.7% 降到了 1.2%。70B 模型的平均首 token 延迟从 2.3 秒降到了 1.6 秒13B 和 7B 模型的延迟基本持平。这些数字看起来不算惊艳但考虑到我们集群规模不大总共 32 张 A100提升已经比较明显了。后续优化方向主要有三个。一是引入更细粒度的 GPU 共享比如用 MIG 或时间片轮转让多个小模型共享一张卡。二是把决策器从规则引擎升级成强化学习模型用历史数据训练一个策略网络直接输出扩缩容动作。三是支持跨节点的模型副本迁移目前我们的迁移只能在同一个节点内做跨节点迁移还依赖 Kubernetes 的重新调度速度较慢。如果你也在做类似的事情我的建议是先从统一资源池和模型级负载采集做起这两步是基础没有它们后面的决策和迁移都无从谈起。决策器可以先从简单的规则引擎开始跑通之后再考虑复杂模型。冷启动优化是投入产出比最高的环节优先做权重缓存和热备池效果立竿见影。
返回列表