news 2026/10/4 4:37:48

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
共享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 张 A100,13B 模型最多用 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_m,P_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-type=a100、gpu-memory=80g。然后每个模型副本的 Pod 里声明自己需要的 GPU 资源,比如 70B 模型声明nvidia.com/gpu: 2,13B 模型声明nvidia.com/gpu: 1。调度器根据当前各节点的 GPU 空闲情况,把 Pod 调度到合适的节点上。

这里有个坑:Kubernetes 默认的 GPU 调度是整卡分配的,一张 A100 要么全给一个 Pod,要么不给。但 LLM 推理有时候可以用 MIG(Multi-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 秒跑一次:

  1. 从预测模块获取每个模型未来 3 分钟需要的副本数 c_m'。
  2. 计算当前总副本数 sum(c_m) 和集群最大副本容量 N。
  3. 如果 sum(c_m') <= N,直接按 c_m' 调整。
  4. 如果 sum(c_m') > N,按边际成本排序,优先满足高成本模型。
  5. 生成扩缩容指令,发给执行器。

边际成本的计算我们简化成了SLO 权重。每个模型配置一个权重 w_m,表示 SLO 违反的相对代价。70B 模型权重设为 10,13B 设为 5,7B 设为 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 的重新调度,速度较慢。

如果你也在做类似的事情,我的建议是先从统一资源池和模型级负载采集做起,这两步是基础,没有它们后面的决策和迁移都无从谈起。决策器可以先从简单的规则引擎开始,跑通之后再考虑复杂模型。冷启动优化是投入产出比最高的环节,优先做权重缓存和热备池,效果立竿见影。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/4 4:35:26

五路灰度传感器原理与STM32循迹小车工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/4 4:34:18

Godot编辑器移植鸿蒙PC实战:三周编译验证与核心适配难点

Godot 编辑器能不能跑在鸿蒙 PC 上&#xff0c;这个问题在游戏开发圈和鸿蒙开发圈里被反复提起。我前后花了大概三周时间&#xff0c;把 Godot 4.x 的源码在鸿蒙 PC 环境下做了几轮编译和运行验证&#xff0c;从最初的“完全跑不起来”到后来“编辑器主界面能出来但交互有问题”…

作者头像 李华
网站建设 2026/10/4 4:33:46

麒麟服务器排障实战:版本识别、软件源与高频问题处理

简介&#xff1a;面向运维人员与系统管理员的银河麒麟高级服务器操作系统V10 SP2常见问题手册&#xff0c;覆盖从系统安装配置、本地源搭建到服务部署的完整链路。资源以PDF格式提供&#xff0c;共1个文件&#xff0c;包大小2.76MB&#xff0c;内容包含ftp、内网源、nfs、ntp、…

作者头像 李华
网站建设 2026/10/4 4:33:40

Packet Tracer烟雾传感器为何总显示0?真相与替代方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/4 4:29:45

城市道路井盖破损丢失数据集VOC+YOLO格式及YOLOv8训练避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/4 4:29:09

PIC32+MRAM工业现场数据可靠存储方案与SPI实现要点

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华