共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南
1. 共享推理服务里扩缩容到底难在哪做过大模型推理服务部署的人大概都经历过这样的场景一个团队共用一套推理集群A 业务跑的是 7B 的小模型B 业务跑的是 72B 的大模型C 业务白天流量高、晚上几乎归零。一开始大家各占一块卡井水不犯河水结果 GPU 利用率长期趴在 30% 以下老板看着账单直皱眉。于是有人提出干脆把模型都塞到一个共享服务里谁有请求谁用卡按需扩缩容。听起来很美真做起来问题一大堆。这篇要聊的就是论文里那个很具体的问题——共享 LLM 服务下的跨模型自动扩缩容Cross-Model Autoscaling。关键词里出现的 LLM、自动扩缩容、Kubernetes、Shared LLM Serving基本勾勒出了它的全貌在一个 Kubernetes 集群里多个不同的大模型共享同一批 GPU 资源系统要根据实时请求负载动态决定给哪个模型加副本、从哪个模型减副本。它解决的不是单个模型的扩容问题而是多个模型抢同一池子资源时怎么分配才不浪费、又不掉服务质量。为什么这件事比传统微服务扩缩容难因为传统 Web 服务的副本是无状态的、等价的加一个副本就是线性加一份算力。但 LLM 推理不一样不同模型的单副本吞吐差异巨大一个 7B 模型单卡能扛几百 QPS一个 70B 模型可能只有几十而且请求的 token 长度分布极不均匀长 prompt 和短 prompt 对显存和计算时间的占用能差一个数量级。更麻烦的是模型加载本身要花几十秒甚至几分钟扩出来的副本不能立刻干活缩容又怕把正在处理的请求掐断。所以这篇文章适合三类人看一是正在做 LLM 推理平台、被 GPU 成本压得喘不过气的工程同学二是用 Kubernetes 部署过模型服务、想进一步做精细化调度的运维同学三是对自动扩缩容机制本身感兴趣、想理解跨模型这个新维度到底新在哪的技术爱好者。我会把论文的核心思路拆开补上论文里没细说但实操中一定会遇到的坑尽量让你读完能对着自己的集群动手试。2. 从单模型 HPA 到跨模型调度问题定义的变化2.1 传统扩缩容为什么直接搬过来不好使Kubernetes 原生的 HPAHorizontal Pod Autoscaler逻辑很朴素看某个 Deployment 的平均 CPU 或自定义指标超过阈值就加副本低于阈值就减副本。这套东西用在无状态 Web 服务上很稳但搬到 LLM 推理上就露怯了。第一个问题是指标选得不对。LLM 推理的瓶颈往往不在 CPU而在 GPU 显存和计算单元。你拿 CPU 利用率去触发扩容可能 GPU 早就打满了CPU 还在那悠哉游哉。业界的常见做法是换成 GPU 利用率、显存占用、或者更贴近业务的指标——比如请求排队时长queue time、首 token 延迟TTFT、每秒生成 token 数TPOT。论文里通常会用排队延迟作为核心信号因为它直接反映用户体验而且对负载变化敏感。第二个问题是扩容的滞后性。LLM 副本从决定扩容到真正能接请求中间要经历调度、拉镜像、加载权重、预热这几个阶段。一个 70B 模型冷启动动辄一两分钟等你扩出来流量高峰可能都过去了。这就引出了预测式扩容和预留缓冲副本的思路后面会细讲。第三个问题也是最关键的多模型共享时扩容决策不再是独立的。单模型场景下你给模型 A 加副本不影响模型 B。但在共享池子里GPU 总数是固定的给 A 加一张卡就意味着 B 少一张卡。这时候给谁加变成了一个资源分配的博弈问题而不是简单的阈值触发。2.2 跨模型扩缩容的形式化描述把问题说清楚一点。假设集群里有 N 个模型共享 M 张 GPU。每个模型 i 当前有 r_i 个副本每个副本占 g_i 张卡大模型可能一张卡放不下要张量并行。系统在每个决策周期观测到每个模型的请求到达率、排队长度、平均处理时间然后决定下一周期每个模型的副本数 r_i。目标通常有两个而且是矛盾的一是服务质量约束比如每个模型的 P99 排队延迟不能超过某个阈值二是资源效率比如整体 GPU 利用率要尽量高或者说在满足服务质量的前提下用的卡越少越好。这里有个容易被忽略的细节副本数不是连续可调的整数那么简单。因为 g_i 可能大于 1而且不同模型的 g_i 不同所以资源分配是个带约束的整数规划问题。比如总共 8 张卡模型 A 每副本 2 卡模型 B 每副本 1 卡那可能的组合是有限的几种。论文里一般会把这个约束显式写出来实操中你也要在调度器里体现否则扩出来的副本可能因为凑不齐卡而一直 Pending。2.3 跨模型三个字的价值到底在哪如果只是把多个模型各自跑一套 HPA那叫多模型扩缩容不叫跨模型。跨模型的核心在于共享与腾挪当模型 A 的流量低谷时它占着的卡可以被回收临时借给正在高峰的模型 B等 A 的流量回来再从 B 那里要回来。这种动态腾挪才是把 GPU 利用率从 30% 拉到 70% 的关键。但腾挪是有代价的。模型 B 的副本被缩掉正在处理的请求要么等它处理完drain要么被迁移到其他副本重试。前者会让缩容变慢后者会浪费已经算了一半的计算。所以论文里通常会设计一套优雅缩容机制先停止给某个副本派新请求等它把在途请求处理完再真正释放资源。这个 drain 的超时时间设多少是个需要根据业务调的参数设短了会掐断请求设长了资源回收不及时。3. 论文方案拆解控制器、预测与调度三层结构3.1 整体架构谁在观测谁在决策谁在执行论文这类系统一般会拆成三层我按自己的理解重新组织一下方便你对照自己的集群。最上面是指标采集层。它从每个模型副本那里收集请求队列长度、处理延迟、GPU 利用率等信号聚合成每个模型的全局状态。这一层的关键是采样频率和聚合窗口。采太快指标抖动大扩缩容会来回震荡采太慢反应迟钝高峰来了还没察觉。实践中常用 15 到 30 秒的窗口做平滑再配合一个短周期的快速响应通道处理突发流量。中间是决策层也就是论文的核心。它拿到所有模型的状态后跑一个分配算法算出每个模型下一周期应该有多少副本。这个算法要同时考虑服务质量约束和资源效率还要处理副本数必须是整数每副本占卡数固定这些约束。论文里常见的做法是把它建模成一个优化问题用启发式或近似算法求解因为精确求解在模型数量多的时候太慢。最下面是执行层对接 Kubernetes。决策层给出目标副本数执行层通过修改 Deployment 或自定义 CRD 的 replicas 字段来落地。这里要注意Kubernetes 的调度和实际 Pod 就绪之间有延迟所以执行层最好能感知 Pod 的真实状态而不是发完指令就当完成了。3.2 决策算法怎么在约束下做分配决策算法是论文最值得细看的部分。我把它拆成几个关键设计点。第一服务质量怎么量化。常见做法是用排队论近似。把每个模型副本看成一个服务台请求到达服从泊松分布服务时间服从某个分布然后用 M/M/c 或者更复杂的模型估算排队延迟。但 LLM 推理的服务时间分布很怪长尾特别严重简单排队论会低估延迟。所以论文里往往会用实测数据拟合或者干脆用一个轻量的延迟预测模型。第二资源怎么在模型间分配。一个直观的思路是按需分配 保底。先给每个模型分配一个最小副本数保证低谷时也能服务剩下的卡作为共享池按各模型当前的压力比例分配。压力可以用排队长度乘以平均处理时间来衡量也就是系统里积压的总工作量。谁积压得多谁多拿卡。第三怎么避免震荡。如果每个周期都重新分配模型之间会来回抢卡系统不稳定。常见的抑制手段有三种一是加滞后带只有压力差异超过一定比例才调整二是加调整步长限制每次最多增减一个副本三是加冷却时间刚调整过的模型在一段时间内不再动。这几个手段我在实际系统里都见过效果最好的是步长限制加冷却时间组合。第四冷启动怎么处理。因为扩容有延迟决策层不能等压力真的上来了才扩。论文里一般会用预测根据历史流量模式提前在高峰到来前把副本扩好。预测可以是简单的周期性模型比如每天几点是高峰也可以是时间序列模型。实操中周期性规律对大多数业务已经够用上复杂的预测模型反而容易过拟合。3.3 与 Kubernetes 的对接细节论文往往对 Kubernetes 的对接一笔带过但这恰恰是落地时最花时间的地方。我补充几个关键点。自定义指标怎么喂给决策层。Kubernetes 原生的 metrics server 只提供 CPU、内存这类基础指标LLM 的排队延迟、GPU 利用率需要你自己采集并暴露。常见做法是用 Prometheus 采集然后决策层直接查 Prometheus或者通过 custom metrics API 把指标接进 HPA。如果决策逻辑复杂建议不要硬塞进 HPA而是自己写一个控制器用 Operator 模式管理。副本数怎么改。直接改 Deployment 的 replicas 最简单但 Deployment 的滚动更新语义和 LLM 的优雅缩容需求不完全匹配。更精细的做法是用 StatefulSet 或者自定义 CRD自己控制 Pod 的生命周期在缩容前先给 Pod 发一个准备下线的信号等它 drain 完再删。GPU 怎么调度。多模型共享 GPU 时调度器要保证每个 Pod 拿到的卡数符合模型需求。Kubernetes 默认调度器对 GPU 的支持是整卡分配如果你要做显存切分或者 MIG需要额外的设备插件。论文里假设的是整卡分配实操中如果你的模型小可以考虑用 MIG 把一张卡切成几份提高利用率但这会引入新的复杂度。4. 实操中绕不开的坑与调参经验4.1 指标采集的坑你以为的延迟不是用户感受的延迟我踩过最深的坑是指标口径和用户体验对不上。系统里统计的处理延迟是从请求进入推理引擎开始算的但用户感受到的延迟是从发出请求到收到第一个 token。中间还隔着网关排队、负载均衡、网络传输。如果你只用引擎内的延迟做扩缩容网关那一层的排队会被忽略结果就是引擎看着很闲用户却觉得慢。正确的做法是端到端埋点。在网关层记录请求进入时间在推理引擎记录首 token 输出时间两者相减才是真实的 TTFT。扩缩容决策应该以这个端到端指标为准。这个改动不大但效果立竿见影我调过一次之后高峰期的用户投诉明显下降。另一个坑是指标聚合的粒度。如果你把所有副本的延迟简单平均长尾会被掩盖。一个副本卡死其他副本正常平均值可能还是好看的。所以要看 P95、P99甚至要看最大排队长度。论文里一般用 P99实操中我建议同时监控 P99 和队列最大长度两个都超阈值才触发扩容避免误判。4.2 缩容的坑drain 超时设多少才合适缩容比扩容难因为扩容只是加资源缩容要处理在途请求。核心参数是 drain 超时给副本多长时间把手上的请求处理完。设太短比如 10 秒长请求会被掐断用户收到错误。设太长比如 5 分钟资源回收慢腾挪效率低。我的经验是按 P99 处理时间的两倍来设。比如你的 P99 请求处理时间是 20 秒那 drain 超时设 40 秒左右比较稳。同时要配合一个停止派发的机制决定缩容某个副本后先把它从负载均衡里摘掉不再给它新请求等它自然处理完。还有个细节流式请求的 drain 判断。LLM 很多是流式输出一个请求可能持续几十秒一直在吐 token。判断它处理完不能只看连接是否关闭要看是否输出了结束标记。这个在实现时要和推理引擎的协议对齐否则会误判。4.3 冷启动的坑预热不只是加载权重很多人以为冷启动就是加载模型权重其实远不止。一个 LLM 副本从启动到能稳定服务要经历拉镜像、启动进程、加载权重到显存、编译计算图如果用 TorchScript 或 TensorRT、跑几个预热请求把 kernel 都激活。最后一步特别容易被忽略如果不预热第一批真实请求会特别慢因为很多计算路径是懒加载的。我的做法是在副本就绪探针里加预热逻辑。Pod 启动后先自己发几个不同长度的请求跑一遍确认延迟稳定了再把就绪探针置为通过。这样负载均衡只会把流量发给真正准备好的副本。预热请求的长度要覆盖你的典型场景短 prompt 和长 prompt 都跑一下。4.4 震荡的坑为什么你的副本数一直在跳扩缩容震荡是共享服务里最烦人的问题。表现是副本数每隔几分钟就上下跳一次GPU 利用率看着也不稳定。根因通常是决策周期和指标窗口不匹配。举个例子你的决策周期是 30 秒指标窗口也是 30 秒。这一周期看到压力大扩了一个副本下一周期新副本还没完全接流量压力看着还是大又扩一个再下一周期流量被消化了压力骤降一下缩两个。来回震荡。解决办法是让指标窗口远大于决策周期。比如决策周期 30 秒指标窗口用 5 分钟。这样每次决策看到的是平滑后的趋势不会被瞬时波动带偏。再配合前面说的步长限制每次最多动一个副本和冷却时间调整后 2 分钟内不再动基本能压住震荡。我调这套参数花了大概两周最后稳定下来的配置是决策周期 30 秒指标窗口 5 分钟步长 1冷却 120 秒。4.5 多模型公平性的坑别让大模型饿死小模型共享池子里大模型因为单副本吞吐低、排队容易积压往往在抢卡时占优势。如果不加约束小模型可能长期拿不到卡服务质量崩掉。这就是公平性问题。论文里一般会给每个模型设一个最小副本数作为保底保证它在最坏情况下也能服务。但最小副本数设多少有讲究设太高低谷时浪费设太低高峰时抢不过别人。我的经验是按每个模型的基线流量来设也就是它日常低谷时的请求量对应的副本数再留一点余量。这样保底够用又不至于占着卡不干活。另外可以引入优先级。核心业务的模型给高优先级抢卡时优先满足非核心的可以容忍更高延迟。这个在决策算法里体现为一个权重压力相同时优先给高优先级模型加卡。5. 落地路线与效果验证思路5.1 从零搭一套的最小可行路径如果你现在就想动手我建议分三步走别一上来就追求论文里的完整方案。第一步先把指标打通。用 Prometheus 采集每个模型副本的排队长度、TTFT、GPU 利用率在 Grafana 上画出来。这一步不涉及任何自动决策但能让你看清流量的真实形态为后面调参打基础。很多人跳过这步直接上自动扩缩容结果参数全靠猜效果很差。第二步单模型自动扩缩容跑通。先拿一个模型试用自定义指标驱动副本数变化把前面说的滞后带、步长、冷却都加上。这一步的目标是验证你的指标口径对不对、drain 机制好不好使、冷启动要多久。跑稳了再谈多模型。第三步引入共享池和跨模型腾挪。把多个模型放进同一个资源池实现最小副本保底加共享池按压力分配。这一步最容易出问题的是资源约束要确保决策层算出来的副本组合真的能在集群里调度出来否则会出现决策说扩实际 Pending的尴尬。5.2 怎么验证扩缩容真的有效验证不能只看 GPU 利用率上去了没有那可能只是把卡占满了但服务质量崩了。要同时看两组指标。一组是效率指标整体 GPU 利用率、单位请求的 GPU 成本、资源闲置率。这些反映你省了多少钱。另一组是质量指标各模型的 P99 TTFT、P99 端到端延迟、错误率、超时率。这些反映用户体验有没有受损。理想的结果是效率指标上升的同时质量指标不劣化。如果质量指标变差说明你的扩缩容太激进缩得太多或者扩得太慢。这时候要回看决策参数通常是保底副本数设低了或者扩容触发阈值设高了。我一般会做一个对照实验固定副本数的静态部署 vs 自动扩缩容跑同样的流量回放对比两组指标。流量回放可以用线上的真实请求日志去掉敏感信息后重放。这样对比最有说服力也能帮你找到参数的最优区间。5.3 这套方案不适合什么场景最后说点泼冷水的话。跨模型自动扩缩容不是万能的有些场景硬上反而添乱。如果你的模型数量很少比如就一两个而且流量很平稳那静态分配加简单的单模型 HPA 就够了跨模型调度带来的复杂度不划算。如果你的模型冷启动特别慢比如几百 B 的模型加载要十几分钟那动态腾挪的意义不大因为等你腾出来高峰都过了。这种场景更适合预留固定资源用队列和批处理来削峰。还有就是强隔离要求的场景。有些业务对延迟极其敏感不能容忍和其他模型共享资源带来的干扰。这种就得单独划资源池跨模型调度在这里是负优化。判断标准很简单你的 GPU 利用率是不是长期低于 50%同时各模型的流量高峰是不是错开的。如果两个都满足跨模型扩缩容值得一试如果利用率本来就高或者流量高峰高度重叠那省下来的空间有限不如把精力花在推理优化上。我在实际项目里推这套方案时最大的体会是算法只是一半另一半是工程细节。论文里的决策算法可能几十行公式就写完了但把它落地成稳定的系统指标采集、优雅缩容、冷启动预热、震荡抑制这些脏活占了八成工作量。所以别被论文的优雅迷惑动手时把精力放在这些细节上效果才出得来。