夜市口有家老王面馆,每天傍晚生意最好,队伍能排到街角;下午三点却空无一人。老王一开始雇了两位固定厨师,高峰时忙得脚不沾地,闲时只能擦桌子打发时间。后来他学聪明了:在门口挂计数器数顾客、预测明天高峰时段、提前打电话叫帮厨、等客流回落再让帮厨回家,还故意让每口灶只用到七分火力。这套“看客流、算人力、忙时加人、闲时减人”的打法,正是 LLM 服务容量规划的核心逻辑。本文就用老王的生意经,带你一步步搞懂 QPS 预测、容量评估和弹性扩容,让 GPU 算力像面馆的灶火一样,该旺时旺、该省时省。
对 AI 从业者而言,学会容量规划意味着你能让每一分 GPU 成本都花在刀刃上——流量洪峰来时扛得住,夜深人静时不烧钱,这是从“能跑通”走向“能赚钱”的关键一步。
可视化演示
展示工作日QPS日周期预测曲线、水平与垂直弹性扩容对比,以及弹性伸缩将GPU利用率从30-40%提升到60%+。
本节配有交互动效(进入视口后自动加载,约 3KB)。
QPS日周期预测 低谷期高峰5-8倍周期 MAPE<12% 高峰前主动扩容准备资源避免冷启动。
QPS核心概念
老王面馆的第一次变革,是在门口挂了一个计数器。每位顾客进门他按一下,每小时清点一次,于是“晚 7 点来 300 人、下午 3 点来 20 人”这样的规律浮现出来。这个“每小时顾客数”,对应到 LLM 服务里就是 QPS——每秒有多少个请求打到你的模型上。它是容量规划最基本的一把尺子。
光看一个总数不够。老王很快发现客流有脾气:工作日晚上是高峰,周末下午也旺,下雨天没人来,商场搞活动时人潮翻倍。LLM 服务的流量同样如此——上班族白天调 AI 助手、深夜跑批任务、大促时客服机器人被挤爆。这种周期性加突发性的特征,决定了你不能按平均值备资源,必须按“波动曲线”来规划。
数据从哪来?靠采集。网关访问日志、负载均衡指标、业务埋点,把每分钟的请求数、来源、成功失败结果全部记下来。采集粒度决定了预测精度——只记“每天总客流”的老王,永远发现不了“晚 7 点高峰”这个规律;同样,只统计小时级 QPS 的团队,也扛不住分钟级的流量陡增。
1. N_requests(分子):统计窗口内进入系统的总请求数量。想象老王的计数器上,晚高峰两小时累计跳了 18000 次。
2. T_seconds(分母):统计窗口的时长,单位必须换算成秒。两小时就是 7200 秒。
3. 比值含义:总请求数除以总秒数,得到平均每秒查询数:18000 ÷ 7200 = 2.5 QPS。
数清人头只是第一步。老王把计数器换成了带时间轴的电子本子,按分钟记录,这才看清了高峰的形状。对应到工程上,就是搭建请求级的埋点与日志链路。有了准确的历史流量,下一节才能谈“预测”。
流量预测算法
老王不再满足于“昨天来了多少人”,他想知道明天会来多少。最初的直觉是“上周三和这周三应该差不多”,后来他发现最近三天的客流比上周同一天更有参考价值——邻居新开的烧烤摊、天气变化、食客口碑都在缓慢改变生意。这种“更相信近期数据”的思路,就是指数平滑法的雏形。
指数平滑(EWMA)的数学表达极其简洁:新预测值 = 平滑系数 × 最新实测值 +(1 − 平滑系数)× 上一个预测值。它不需要保留全部历史数据,只要记住上一个预测值就能持续滚动更新,特别适合在线服务实时算流量。相比之下,移动平均法取最近 N 个点的算术平均,实现简单但反应迟钝;更精细的周期趋势分解则把时间序列拆成长期趋势、周期性分量和随机噪声三部分,先分别建模再叠加,能捕捉“每天晚 7 点必涨”这类周期规律,但计算复杂度也更高。
预测准不准,要用误差说话。常见指标有 MAPE(平均绝对百分比误差)和 RMSE(均方根误差),误差越小,后续扩容计划就越有底气。
1. X_t(当前实测值):本时刻观察到的真实 QPS。好比老王今晚 7 点实到 100 位顾客。
2. S_{t-1}(上轮预测值):此前时刻预测出的值,比如昨天估计今天 7 点会来 80 位。
3. α(平滑系数):取值在 0 到 1 之间,决定新数据占多大权重。α = 0.3 表示 30% 相信实测、70% 沿用旧预测。
4. S_t(新预测值):0.3 × 100 + 0.7 × 80 = 86,预测值向实测靠拢,既追变化又防抖动。
老王后来发现,完全照搬上周客流(简单平均)往往被突发天气坑惨,而“七分老经验、三分新观察”的指数平滑反而稳。预测出明天的高峰人数后,下一个问题自然浮现:到底需要几个厨师、几口灶?
容量评估体系
知道明天高峰会来多少人,老王开始盘算:一个厨师一小时能出多少碗面?他做过实验——让一位师傅不停歇地煮面,记下极限是 40 碗/小时。对应到 LLM 服务,这就是“单实例 QPS 上限”:给一台 GPU 实例持续加压,直到它扛不住,记下它的吞吐天花板。这必须实测,不能拍脑袋,因为模型大小、显存带宽、输入输出长度都会显著影响这个数字。
光看吞吐还不够,还要看“顾客最长等多久”。面馆里最惨的那位顾客等了多少分钟,比平均等餐时间更能说明问题——这个“最惨顾客”的等待时间,对应 P99 延迟:99% 的请求延迟都小于该值,只有 1% 的请求比它更慢。平均延迟 200ms 的服务,P99 可能已经飙到 2 秒,体验天差地别。
还有一个反直觉的点:不能把灶烧到 100% 满负荷。万一来了个突发大单,炉子全忙,新客就只能干等。所以要设安全水位,比如只用单实例上限的 70%~80%,留出的余量用来吸收流量尖峰和故障转移。
1. QPS_peak(峰值流量):预测算法给出的未来高峰 QPS。老王预估明天晚高峰 600 位顾客。
2. QPS_single(单实例上限):压测得到的单机吞吐上限,相当于一位厨师一小时最多 100 碗面。
3. U_safe(安全水位):允许使用的比例,0.8 表示只用到上限的八成,留两成余量。
4. 向上取整:600 ÷ (100 × 0.8) = 7.5,但灶不能开半口,所以备 8 个实例。
老王算出来高峰需要 8 口灶,但他只常驻 5 口,剩下 3 口等高峰前再架上——机器和人一样,不用全天满编,关键是“该在的时候在”。这就引出了弹性扩缩容。
弹性扩缩容
弹性扩容的精髓,老王总结成一句话:“提前一小时叫人,而不是队伍排到 20 米才喊。” 他会在高峰前给帮厨打电话,让他们提前到店系上围裙;等客流回落,再让帮厨回家休息。对应到 Kubernetes,HPA(Horizontal Pod Autoscaler)就是这位调度员。
HPA 的工作方式是周期性采样:观察当前副本数下的指标平均值(可以是 CPU、QPS 或自定义业务指标),与目标阈值比较,按比例调整副本数。它的触发机制是“偏差比例”而非简单的开关——当前 QPS 超出目标越多,扩容的步子就越大。扩容不是瞬间完成的:新 Pod 要经历调度、拉镜像、加载模型权重、预热推理缓存(冷启动),动辄几分钟。所以老练的团队会做“预扩容”:预测模型说半小时后要涨,现在就先把实例加上去。线上还要配限流保护,像面馆满座时发号排队,先让请求在队列里等一下,也好过把系统冲垮。缩容则要格外谨慎:高峰一过立刻砍实例,流量稍微反弹就会再次扩容,一来一回“震荡”反而更费钱,所以要设冷却期和最小副本数。
1. R_current(当前副本数):此刻集群里运行的 Pod 数量。面馆现在有 4 位厨师在岗。
2. M_current(当前观测指标):最近采样窗口内指标的平均值,比如平均 QPS 已经到 80。
3. M_target(目标阈值):希望指标维持的水平,比如目标就是 60 QPS,维持体验不排队。
4. 取整与决策:4 × 80 ÷ 60 = 5.33,向上取整为 6,即需要新增 2 个实例。
老王的经验“提前叫人来、高峰后让人歇一会儿再走”,对应到系统里就是预扩容、冷却期和最小副本数。但扩容方案对不对,不能靠猜,得先做一场模拟考试——负载测试。
负载测试验证
老王不会等到中秋当晚才发现灶不够用。他会在节前请一群朋友来“试吃”,模拟高峰场景:同时进店、点单、催菜,看 8 口灶同时开火会不会排长队、出餐时间会不会爆表。这就是负载测试——用模拟流量检验系统的真实容量。
压测工具(k6、wrk、Locust 等)负责“造人”,但要造得逼真:请求频率、并发数、请求体大小、思考间隔都要贴近线上真实比例,这就是流量模型构造。压测中要盯住两个关键曲线:随着并发上升,QPS 先是稳步上升,到某个点后增速放缓甚至掉头向下,同时延迟开始急剧飙升——这个拐点就是系统真实容量。背后的理论支撑是 Little's Law:系统内平均并发请求数 L = 到达率 λ × 平均响应时间 W。测出平均响应时间 W 后,就能反推:想压到目标 QPS,需要模拟多少并发。压测还要固化为回归测试,每次模型版本升级或推理配置变更后重跑一遍,防止性能悄悄回退。
1. λ(到达率):每秒进入系统的新请求数,也就是压测要模拟的目标 QPS,比如 200。
2. W(平均响应时间):每个请求从进入系统到处理完成的平均耗时,比如 0.5 秒。
3. L(系统内请求数):任意时刻同时待在系统里(排队或处理中)的请求数:200 × 0.5 = 100 个并发。
老王请朋友试吃后才知道“8 口灶、每碗 3 分钟”的真实极限,从此备料、排班心里有底。压测拿到了真实容量,最后一步是把这套打法变成日常运营制度。
工程落地实践
老王把经验沉淀成一套“面馆运营系统”:门口装客流摄像头记录每一分钟进店人数;队尾超过 10 人自动给老板手机发提醒;墙上挂大屏实时显示客流曲线和出餐水位;月底对着账单复盘哪几天帮厨请多了、哪几天备料备少了。这套系统对应到 LLM 服务,就是监控、告警、看板和成本核算。
落地的起点是监控体系建设:埋点采集 QPS、P99 延迟、GPU 利用率、排队长度等核心指标,写入时序数据库统一存储。告警规则要分级别:水位超过 70% 提示观察、85% 触发预警、95% 拉响告急,并用预测值联动——预测明天 10 点有洪峰,今晚就自动提醒值班同学确认扩容计划。容量看板把“预测曲线、实际曲线、当前副本数”叠加在同一张图上,团队一眼就能看出预测准不准、扩容跟没跟得上。最后是成本优化:弹性缩容省下的 GPU 小时数乘以单价,就是容量规划最直观的功劳。用预测驱动预算制定,远比月底对着超额账单追认要优雅。
1. N(平均实例数):统计周期内实际运行的实例数量均值。弹性做得好,N 就贴着需求走。
2. T_hours(时长):累计运行的小时数。一个月按 30 天算就是 720 小时。
3. P_hour(单价):单台 GPU 实例每小时的价格,比如 30 元。
4. 成本合计:12 × 720 × 30 = 259,200 元。预测越准、水位越合理,N 就越接近真实需求,账就越省。
老王的面馆从此“看屏下菜”:客人多了自动加灶,客人少了关灶省气。LLM 服务团队也一样——QPS 预测是眼睛,弹性扩容是手脚,监控看板是神经,成本账本是成绩单。四者闭环,容量规划才算真正落地。
AI 应用场景
电商大促前的 LLM 客服预扩容
阶段:容量规划与弹性扩容
解决的问题:传统做法是运营盯着监控屏,看到 QPS 涨了才手动点“扩容”,从点击到新实例真正接流量往往要 10 分钟以上,大促前 10 分钟的流量洪峰早就把服务打穿了。AI 预测算法提前 30 分钟识别出“促销活动带来的流量爬坡”,触发预扩容,让实例在大促开始前就已就位。
具体案例:某电商平台每年 618 大促,智能客服 LLM 的 QPS 从日常 200 暴涨到 2500。他们用近三年的大促流量数据训练周期趋势分解模型,提前 1 小时把副本数从 20 预扩到 80,并在活动开始后由 HPA 按实时指标微调,全程 P99 延迟稳定在 800ms 以内。
多租户平台的成本分摊与配额管理
阶段:容量评估与监控体系
解决的问题:多个业务线共享一个 LLM 推理集群时,谁用了多少算力说不清,月底成本只能“大锅饭”分摊。通过按租户维度的 QPS、Token 消耗、GPU 利用率监控,结合容量评估模型,把总成本按实际用量精准拆分。
具体案例:某企业内部 AI 中台服务 12 个业务线,每条线的请求模式完全不同:搜索业务白天高峰,报表业务深夜跑批。平台按租户统计 QPS 曲线并分别做预测,夜间把算力让给跑批任务,白天还给搜索业务,整体 GPU 采购成本下降 35%。
模型版本上线前的容量回归验证
阶段:负载测试与回归迭代
解决的问题:新模型版本效果更好,但可能推理变慢、显存翻倍,直接全量上线会把线上容量打爆。用压测工具对新旧版本做 A/B 容量对比,量化评估后再决定上线策略。
具体案例:某团队把 7B 模型升级为 13B 模型,压测发现单实例 QPS 上限从 45 掉到 28,P99 延迟从 400ms 涨到 900ms。他们据此调整副本数预算、启用量化推理,确认指标达标后才灰度放量,避免了线上事故。
Python 演示
"""QPS预测与弹性扩容演示:指数平滑预测流量并计算所需实例数。"""import mathimport random def exp_smooth(history, alpha=0.3): """用指数平滑法预测下一个时刻的 QPS。""" s = history[0] for x in history: s = alpha * x + (1 - alpha) * s return s def calc_replicas(qps_peak, qps_single, safe_ratio=0.7): """根据峰值 QPS、单实例上限和安全水位计算所需实例数。""" return math.ceil(qps_peak / (qps_single * safe_ratio)) def simulate(series, qps_single, safe_ratio): """逐分钟滚动预测并生成扩容计划。""" plan = [] for i in range(len(series)): hist = series[:i + 1] pred = exp_smooth(hist) need = calc_replicas(max(pred, series[i]), qps_single, safe_ratio) plan.append(need) return plan def main(): random.seed(42) # 模拟 60 分钟流量:周期性高峰叠加随机抖动 series = [] for i in range(60): wave = 40 + 30 * math.sin(i / 8.0) noise = random.uniform(-5, 5) series.append(max(1, wave + noise)) plan = simulate(series, qps_single=40, safe_ratio=0.7) for i in (10, 20, 30, 40, 50): pred = exp_smooth(series[:i + 1]) print("t=%2dmin 实测=%5.1f 预测=%5.1f 实例=%d" % (i, series[i], pred, plan[i])) if __name__ == "__main__": main()
s = alpha * x + (1 - alpha) * s指数平滑的核心递推:每个新观测值都按 α 比例修正旧预测,相当于老王“七分经验、三分新观察”。return math.ceil(qps_peak / (qps_single * safe_ratio))实例数计算公式:峰值除以“单机上限 × 安全水位”后向上取整,保证容量有余量。max(pred, series[i])取预测值和当前实测值的较大者,宁可按眼前真实流量备资源,也不盲目乐观。random.seed(42)固定随机种子,保证每次运行生成的流量数据一致,演示结果可复现。wave = 40 + 30 * math.sin(i / 8.0)用正弦波模拟“晚高峰”的周期性规律,正如真实业务流量存在昼夜节律。
常见误区
误区把过去一年里最高的 QPS 当作设计上限,认为“按最大峰值配资源永远不会出错”。
正解历史极值往往是偶发异常造成的——大促、故障重试风暴、爬虫攻击都可能是罪魁祸首。照搬极值会让日常资源闲置大半,等于面馆因为去年中秋爆满,就一直雇 20 个厨师过到来年。正确做法是把趋势、周期、突发三类分量分开,按常规峰值配资源并留安全水位,用弹性机制去吸收偶发突发。
为何常见:人脑对“最大、最坏”这类极端数字印象最深刻,容易把一次性异常当成长期常态。
误区HPA 一触发,新实例马上就能分担流量,所以只要把扩缩容开起来就万事大吉。
正解新 Pod 要经过调度、拉取镜像、加载模型权重、预热缓存等多个阶段,冷启动动辄数分钟。高峰流量不会等你,所以必须“提前叫人”:用预测驱动预扩容,同时保留少量常驻缓冲实例。好比临时叫来的帮厨要系围裙、洗手、认灶台,不可能进门就颠勺。
为何常见:容器化“一键弹性”的宣传让人误以为扩张是即时的,忽略了 LLM 模型加载这一重型步骤。
误区高峰一过马上把实例砍到最小数量,节省的成本最多。
正解快速缩容后一旦流量反弹,会立刻触发再次扩容,实例数来回震荡,调度开销和 GPU 计费反而更高。这就是“缩容震荡”。正确做法是设置冷却期、下降梯度和最小副本数,让实例数平滑回落。好比客人刚走一半就把灶全关了,回头又来一桌,重新生火比一直开着更费气。
为何常见:成本焦虑让人只盯着“少开机器”的账,忽略了频繁启停本身的调度成本和延迟风险。
误区压测发现 QPS 上不去,第一反应是继续加实例,加到达标为止。
正解瓶颈未必在实例数量,可能在单实例内的算子效率、显存带宽、网络、乃至下游数据库。盲目加机器如同灶台太小却拼命加厨师——人再多也只能排队等锅。正确流程是先做性能剖析定位瓶颈,再决定是扩容、量化模型还是优化推理引擎。
为何常见:“加机器最简单”是运维直觉,但 GPU 资源昂贵,拿钱硬怼往往掩盖了真正可优化的技术问题。
误区监控面板显示平均延迟 200ms,就觉得用户体验没问题,不用扩容。
正解平均值会掩盖尾部灾难——可能 90% 的请求只要 100ms,但剩下 10% 的请求卡了 2 秒。排队理论告诉我们,高负载下尾部延迟会比平均延迟恶化快得多,必须盯 P99 甚至 P999。就像店里平均等餐 5 分钟,但最惨的顾客等了 40 分钟,那位顾客下次还会来吗?
为何常见:平均值计算简单、看板默认展示,人眼会对“平滑的曲线”产生虚假的安全感。
面试题
Q1. 关于 QPS 的定义,下列哪个说法正确?
- A.每秒钟系统成功处理的请求数量← 正确答案
- B.每分钟系统写入磁盘的日志条数
- C.每秒钟网络传输的数据包总数
- D.每分钟数据库事务提交的成功数
答案:A。QPS 衡量的是服务每秒成功处理的请求量,好比面馆只数真正进门点单的顾客。B 是日志写入速率,C 是网络吞吐,D 是数据库事务指标,它们都不属于面向服务的查询吞吐。
Q2. 指数平滑法的核心思想是什么?
- A.只使用最近一个时刻的观测值
- B.对历史数据按新旧加权平均← 正确答案
- C.把所有历史数据求简单算术平均
- D.用线性回归拟合所有历史点
答案:B。指数平滑对越新的数据赋予越高权重,通过 α 系数控制新旧比例,只需记住上一个预测值即可滚动更新。A 过于激进,C 是等权平均反应慢,D 是离线拟合方式,都不符合在线预测的场景。
Q3. 某实例实测 QPS 上限为 100,安全水位设为 0.8,预测峰值 QPS 为 600,所需实例数是多少?
- A.6 个实例
- B.7 个实例
- C.8 个实例← 正确答案
- D.9 个实例
答案:C。600 ÷ (100 × 0.8) = 7.5,向上取整为 8。好比高峰预估 600 位顾客、每口灶安全出 80 碗,需要 7.5 口灶,实际必须准备 8 口,不能开半口灶。
Q4. P99 延迟指标的含义是什么?
- A.99% 的请求延迟低于该数值← 正确答案
- B.99% 的请求延迟高于该数值
- C.系统每 99 秒发生一次超时
- D.系统有 99% 的时间处于空闲
答案:A。P99 表示排序后第 99 百分位的延迟,即只有 1% 的请求比它更慢。它专门用来抓“极少数顾客等太久”的尾部问题,平均延迟达标不代表最惨的用户体验也达标。
Q5. HPA 弹性扩容的核心触发依据是什么?
- A.管理员手动执行的扩容命令
- B.观测指标与目标阈值的偏差比例← 正确答案
- C.服务器 CPU 的物理核心数目
- D.每天固定时段的定时触发器
答案:B。HPA 是比例控制器,按“当前副本数 × 当前指标 ÷ 目标指标”得出目标副本数,指标超得越多扩得越猛。手动命令、CPU 核数都与其无关,定时器只是辅助而非核心触发机制。
Q6. 以下哪项不是冷启动问题的合理解决方案?
- A.提前将模型权重加载到显存
- B.预留常驻的预热实例池
- C.扩容瞬间丢弃所有排队请求← 正确答案
- D.采用模型分片和并行加载
答案:C。冷启动的解法是“提前备好食材、把火烧热”——预加载权重、预热池、分片加载都能缩短就绪时间。丢弃排队请求等于把到店的客人赶走,既损失体验又浪费流量,绝不可取。
Q7. Little's Law(L = λ × W)在压测设计中的用途是?
- A.由 QPS 和响应时间推导并发数← 正确答案
- B.由并发数直接计算服务器价格
- C.由延迟推导磁盘的读写速度
- D.由实例数推导 GPU 显存大小
答案:A。给定目标 QPS(λ)和实测平均响应时间(W),两者相乘就得到需要模拟的并发数 L。例如目标 200 QPS、平均响应 0.5 秒,压测客户端就要维持 100 个并发,这是设计流量模型的基础。
Q8. 弹性缩容时需要重点关注哪些因素?
- A.缩容后的流量回弹风险考虑← 正确答案
- B.冷却期设置防止实例震荡← 正确答案
- C.存量请求在途处理的清空← 正确答案
- D.服务器机箱外壳的颜色搭配
答案:ABC。缩容必须防回弹——流量可能短暂回落又反弹;要设冷却期——避免反复扩缩形成震荡;要等在途请求处理完——防止砍掉的工作节点把未完成请求一起丢掉。外壳配色与容量规划毫无关系。
Q9. 一个完整的容量规划流程包含哪些关键步骤?
- A.收集历史流量与业务的数据← 正确答案
- B.预测算法估算未来的峰值← 正确答案
- C.结合安全水位计算实例数← 正确答案
- D.关闭监控降低系统的开销
答案:ABC。容量规划 = 采数据(历史流量与业务事件)→ 做预测(估算峰值)→ 算容量(安全水位下求实例数)→ 持续验证(压测与监控)。关闭监控属于本末倒置,没了眼睛的容量规划寸步难行。
Q10. 压测结果不达标时,应当按怎样的顺序排查原因?
- A.单实例推理瓶颈的定位分析← 正确答案
- B.排队等待与超时机制核查← 正确答案
- C.目标指标定义的合理性← 正确答案
- D.直接翻倍实例数量解决问题
答案:ABC。正确排查顺序是“先定位瓶颈(算子、显存、网络)→ 再查排队与超时机制 → 最后复核指标定义是否合理”。直接翻倍实例属于拍脑袋式的大炮打蚊子,可能花了钱却打不中真正的瓶颈。