做 LLM 服务的人迟早会撞上一个问题压测时一切正常上线后一到高峰 P99 就爆炸。再回去看请求曲线会发现流量根本不是一个均匀的直线而是一段一段的脉冲短时间冲高然后又回落。这种“一阵一阵往上涨”的流量特征在系统领域有一个经典名字——Burstiness也就是突发性。“Burstiness is all you need for LLM serving”这个标题很明显在致敬 Transformer 那篇“Attention is all you need”。它想表达的核心主张也很直接在做 LLM 服务系统时请求到达的突发性才是设计系统时更应该优先考虑的因素而不是只盯着平均 QPS。这个方向对在线推理平台、多租户 API 服务、Agent 类工作负载、边缘侧部署都有直接影响。这篇文章会从工程视角拆解这个研究方向先讲 Burstiness 在 LLM serving 里到底是什么为什么传统方案扛不住突发流量再给一套可以落到本机的请求日志分析方法、脉冲负载压测方法以及容量规划和调度优化建议。不需要你有论文原文跟着文章里的代码和步骤就能对自己手上的推理服务做一次突发性体检。1. 核心概念速览维度说明研究方向LLM serving 系统中的请求突发性Burstiness感知与调度优化核心观点请求到达的突发性特征比平均 QPS 更能影响服务质量与资源效率问题背景恒定 QPS 压测通过线上突发流量导致延迟分位数恶化、显存 OOM关键挑战推理任务耗时长、显存资源独占性强、GPU 实例冷启动慢、弹性扩容滞后典型优化方向突发流量预测、突发感知调度、动态批处理、资源预留、容量规划适用场景在线 LLM API、多租户推理平台、Agent 循环任务、边缘端部署技术验证方式请求日志统计、脉冲负载压测、延迟分位数对比、GPU 资源利用率观测开源状态暂不确定是否配套完整代码库建议以论文原文和作者主页为准这个研究方向重点解决的问题不是单次推理有多快而是集群在“突然来一堆请求”的时候能不能稳住 P99 延迟能不能不把显存打爆能不能让扩容动作跟上流量脉冲的节奏。下面展开说。2. 先理解 BurstinessLLM 请求为什么不是匀速的Burstiness 在排队论和网络流量工程里早就有大量讨论简单来说就是事件到达时间不是均匀分布的而是会在一段时间内聚集出现。比如某办公软件的 API 网关上午 10 点整会有一批定时任务同时触发某个 Agent 平台突然上了首页推荐用户并发对话在几分钟内翻了几倍线上大促活动开始后客服机器人的请求瞬间冲高。这些场景放在传统 Web 服务里可能只是“扩容一下”的问题但放在 LLM serving 里会放大很多倍原因有三个。第一推理请求的响应时间不是毫秒级而是秒级甚至分钟级。请求一旦在队列里积压延迟会线性甚至超线性恶化。用户发了一条消息服务端排队 20 秒才开始生成体验基本不可用而且重试会进一步加剧流量脉冲。第二显存是独占性资源。一个推理实例能同时处理的并发请求数取决于显存里能放多少 KV Cache。突发流量过来队列不一定会先挂显存可能先被撑爆进程直接 OOM 重启然后所有在途请求全部失败。第三弹性扩容对 LLM 服务来说并不是瞬时的。GPU 实例冷启动、模型权重加载、预热这些动作往往需要几分钟。等扩容完成流量脉冲可能已经过去了机器加上了高峰期也结束了。所以 Burstiness 在 LLM serving 里不只是一个统计学名词它是直接影响 SLA、成本和稳定性的核心变量。用平均 QPS 做容量规划等于默认流量是均匀的这个假设在线下压测环境成立到了线上真实流量环境下基本站不住。3. 传统 LLM serving 方案为什么扛不住突发流量现在主流的开源推理框架比如 vLLM、TensorRT-LLM在单机推理效率和连续批处理Continuous Batching上做得已经很成熟了。它们能够在一个 batch 里同时处理不同进度的请求让 GPU 计算单元尽量不空闲。这是针对“请求已经在服务端”的场景做优化但并没有真正解决“请求在某个时间窗口突然大量到达”的问题。固定资源池的模式是最常见的。你评估完业务峰值申请了固定数量的 GPU 实例按平均值申请突发时会发现算力不够按峰值申请大部分时间 GPU 利用率很低成本却一直顶着。无论是哪种问题本质都是容量规划没有把突发性纳入考量。连续批处理的优化边界也需要重新看。它能把已到达请求的混批效率优化得不错但如果请求到达本身就是脉冲式的批处理能做的只是“高峰期把多个请求挤在一起处理”并不能消除排队等待本身。更麻烦的是当突发流量超过服务实例的最大并发数时请求开始排队排队的请求又占着连接资源客户端超时重试重试又变成新的流量形成雪崩。弹性扩容在 Kubernetes 这类平台上有标准做法按 CPU 或 QPS 指标扩副本。但对 LLM 服务来说扩副本之后要加载模型加载的就是好几个 GB 的权重还要做预热。等到副本 Ready最早的流量脉冲已经结束了。扩了个寂寞。再往细看FIFO 队列在突发流量下也有问题。先到的长请求排在前面后到的短请求可能要等很久。短请求本身可能只是简单问答却因为前面的长文本生成任务被堵住。如果队列没有优先级区分也没有超时和丢弃策略整体的尾延迟会很难看。这些问题的共同点是把流量当成均匀或近似均匀的输入来设计。一旦把 Burstiness 当作第一性原理来考虑设计目标的优先级就会变先保高峰期的可用性再考虑平时的资源利用率先做突发预测和资源预留再谈批处理效率。4. 突发感知的 LLM 服务系统技术方向拆解从面向“Burstiness”这个研究主张出发一个完整的突发感知 LLM 服务系统通常会把下面几个方向组合起来。这里整理的是方向性拆解不代表论文原文的实现细节但这几个方向是这一类系统设计空间里绕不开的环节。4.1 突发流量预测预测是突发感知系统的基础。常见思路是维护请求日志的时间序列按分钟或秒级粒度统计 QPS然后用自回归模型、Prophet、Transformer 时序模型等预测下一个时间窗口的流量走势。更贴近生产环境的是按租户或按业务方维度做预测因为不同业务的突发模式差别很大客服机器人是白天高、晚上低定时任务集中在整点活动流量可能完全由运营计划驱动。预测的价值在于给调度器一个提前量。哪怕只提前 1 到 2 分钟预测到流量要上涨也足够触发预热或资源预留动作而不是等队列堆积以后再做反应。4.2 突发感知调度这里主要做两件事一是调整请求进入执行阶段之前的排队策略二是动态调整批处理窗口。排队策略上可以按请求的预估长度、业务优先级、租户等级做加权队列避免长任务把短任务全部堵死。动态批处理上可以在低峰期适当拉长批处理等待窗口换取更大的 batch 和更高的吞吐在高峰期则缩小等待窗口尽可能让请求更快进入执行阶段。更进一步的设计是允许对已经在排队的请求做超时管理和优雅丢弃。对用户来说明确的“当前繁忙请稍后重试”比无限等待更容易接受。4.3 资源预留与弹性池一个比较实用的思路是“基础池 弹性池”。基础池按业务常态流量的峰值来评估容量保证大多数时间 GPU 利用率处于合理水平弹性池在预测到突发流量时再动态拉起峰值过去后及时释放。这里的关键是模型权重分发要快。如果每个新实例都要从对象存储拉好几个 GB 的模型文件弹性池根本来不及。常见的优化方法包括节点本地缓存、模型仓库预热、以及用快照或共享内存方式加载权重。4.4 Prefill 与 Decode 的解耦突发流量对 Prefill预填充阶段的冲击尤其明显。大量长 Prompt 同时到达时Pre fill 阶段的计算压力会瞬间拉高。如果在架构上把 Prefill 和 Decode 分离用不同的实例或不同的调度策略处理两个阶段就能避免某个长 Prompt 挤占所有请求的 Decode 带宽。DistServe 这一类方案已经验证了这种拆分的收益在突发场景下尤其明显。4.5 排队与降级策略最后一道防线是网关层的限流、排队和降级。可以在网关统一设置基于令牌桶的速率限制按租户分配配额同时对整体队列长度设置阈值。超过阈值直接返回 429而不是让请求堆积在推理节点上。这样既能保护下游节点也能让客户端尽快进入指数退避重试逻辑而不是反复打满连接。5. 从请求日志分析服务的突发性不管论文里用了多复杂的算法想验证自己服务是否有突发性第一步永远是分析请求日志。下面给出一套可以直接跑的 Python 脚本用来从时间戳数组里提取几个关键指标。5.1 安装依赖pip install numpy pandas matplotlib5.2 分析脚本import numpy as np import pandas as pd import matplotlib.pyplot as plt # 假设请求日志包含一列时间戳格式为 unix 秒 df pd.read_csv(requests.csv, parse_dates[arrival_time]) # 按秒统计 QPS qps_series df.set_index(arrival_time).resample(1s).size().rename(qps) # 计算基础统计量 mean_qps qps_series.mean() std_qps qps_series.std() p95_qps qps_series.quantile(0.95) p99_qps qps_series.quantile(0.99) max_qps qps_series.max() # 变异系数 CV 标准差 / 均值越大说明突发性越强 cv std_qps / mean_qps if mean_qps 0 else 0 # 峰值均值比 peak_to_mean max_qps / mean_qps if mean_qps 0 else 0 print(f平均 QPS: {mean_qps:.2f}) print(fQPS 标准差: {std_qps:.2f}) print(fP95 QPS: {p95_qps:.2f}) print(fP99 QPS: {p99_qps:.2f}) print(f最大 QPS: {max_qps:.2f}) print(f变异系数 CV: {cv:.3f}) print(f峰值均值比: {peak_to_mean:.2f}) # 到达间隔分布 df_sorted df.sort_values(arrival_time) inter_arrival df_sorted[arrival_time].diff().dt.total_seconds().dropna() print(f到达间隔均值: {inter_arrival.mean():.4f}s) print(f到达间隔 P99: {inter_arrival.quantile(0.99):.4f}s) # 画 QPS 曲线 plt.figure(figsize(12, 4)) qps_series.plot() plt.title(Request QPS over Time) plt.xlabel(Time) plt.ylabel(QPS) plt.grid(True) plt.show()5.3 怎么看结果变异系数 CV 如果大于 1说明 QPS 波动非常剧烈系统具有明显突发性。比如平均 QPS 是 20标准差也是 20CV 就是 1.0意味着流量在不同秒之间的差异非常大。峰值均值比如果超过 3说明高峰期流量是常态的 3 倍以上做容量规划的时候就不能只看平均 QPS。到达间隔分布也很有参考价值。如果到达间隔的分布接近指数分布说明请求是随机的泊松过程突发性相对温和如果大量请求在极短时间窗口内到达、间隔 P99 明显偏小说明存在明显的聚集效应。6. 脉冲负载压测评估 LLM 服务的抗突发能力只看日志还不够最好能在测试环境里复现出突发流量观察真实服务的延迟和资源表现。脉冲式压测就是专门干这个的。6.1 设计思路压测策略分为两段一段是基础负载模拟常态流量一段是脉冲负载模拟突发流量。比如基础 QPS 是 20每 90 秒触发一次 30 秒的脉冲脉冲期间 QPS 升到 80。这样既能看到平时的稳态延迟也能看到突发期间的排队情况。6.2 脉冲负载生成示例这是一个独立的 Python 示例用来生成脉冲式请求节奏。实际发送请求的时候可以把request_func替换成你自己的调用逻辑。import time import threading from dataclasses import dataclass dataclass class PulseProfile: base_qps: float 20.0 peak_qps: float 80.0 pulse_seconds: float 30.0 cycle_seconds: float 90.0 total_seconds: int 600 def send_request(request_func): try: request_func() except Exception as exc: print(f[{time.strftime(%H:%M:%S)}] request error: {exc}) def pulse_worker(profile: PulseProfile, request_func): end_time time.time() profile.total_seconds while time.time() end_time: cycle_start time.time() # 基础负载阶段持续到脉冲触发点 base_end time.time() (profile.cycle_seconds - profile.pulse_seconds) while time.time() base_end: send_request(request_func) time.sleep(1.0 / profile.base_qps) # 脉冲阶段 pulse_end time.time() profile.pulse_seconds while time.time() pulse_end: send_request(request_func) time.sleep(1.0 / profile.peak_qps) # 等待本轮循环结束 sleep_time cycle_start profile.cycle_seconds - time.time() if sleep_time 0: time.sleep(sleep_time) def main(): profile PulseProfile( base_qps20.0, peak_qps80.0, pulse_seconds30.0, cycle_seconds90.0, total_seconds600, ) def dummy_request(): # 这里替换成真实推理服务调用 time.sleep(0.1) thread threading.Thread(targetpulse_worker, args(profile, dummy_request), daemonTrue) thread.start() thread.join() if __name__ __main__: main()6.3 压测期间重点观察四类指标第一延迟分位数。P50、P95、P99 是否在突发期间明显抬升以及脉冲结束之后是否快速回落。理想状态是 P99 有轻微上行但不失控脉冲结束后延迟能回到正常范围。第二队列长度。如果服务端暴露了排队指标比如 FastAPI 服务的连接等待数、vLLM 的 waiting queue 长度重点观察突发瞬间的队列堆积深度和恢复速度。第三GPU 利用率和显存占用。突发期间 GPU 计算单元是否打满显存使用是否接近上限。如果显存持续增长而不释放后续就有 OOM 风险。第四错误率和重试率。突发期间如果出现大量超时、429、连接重置说明服务已经进入过载状态需要往前端加限流或者提早扩容。7. 容量规划与调度优化实践分析完突发性、压测出系统的抗压上限之后要把结果落到实际的容量规划和调度策略上。7.1 按 P95 甚至 P99 做容量规划容量规划的时间窗口建议按 10 分钟粒度看 P95而不是按小时粒度看平均值。比如业务高峰期 10 分钟粒度的 P95 QPS 是 60那就按 60 到 80 的冗余来评估所需的 GPU 实例数量。平均 QPS 只能用来估算总成本不能用来决定实例数量。7.2 基础池加弹性池常态流量和突发流量分开处理。基础池覆盖日常 P95稳定运行不频繁变配。弹性池给突发流量留后手通过预测触发提前 2 到 3 分钟扩容。扩容之后如果流量确实没上来及时缩容避免资源浪费。这里有一个很实际的要求模型权重必须已经缓存在节点本地否则弹性池会被冷启动拖垮。7.3 排队与超时策略推理服务内部一定要有队列长度上限达到上限后直接拒绝新请求并返回 429而不是无限排队。每个请求也要有超时时间超过比如 60 秒就主动终止避免一个异常长请求拖住整个 batch。网关侧可以再加一层速度限制比如每租户每秒最多请求数防止某个业务方把全局资源打满。7.4 客户端指数退避重试服务端做了限流之后客户端也要配合。收到 429 或者超时错误之后不要立即重试而是使用指数退避策略比如 1 秒、2 秒、4 秒、8 秒依次等待。这样即使服务端已经过载重试流量也不会形成二次脉冲。7.5 多租户配额如果是多业务共用一个推理集群一定要做租户级配额。每个租户单独设置最大并发数同时设置全局并发上限。这样任何一个租户的业务突发都不会把其他租户的请求全部挤出队列。8. 常见问题与排查方法问题现象可能原因排查方式解决方案高峰期 P99 延迟暴涨突发流量超过服务并发上限请求排队查看服务端队列长度和响应时间分布按 P95 做容量规划提前扩容增加限流GPU 利用率不高但延迟很高请求之间互相等待显存或受到排队阻塞查看实例并发数和队列深度调整最大并发数优化批处理窗口突发流量期间显存 OOM并发请求过多KV Cache 超过显存容量监测显存曲线和请求并发数降低最大并发限制单请求最大长度缩小 batch弹性扩容后延迟没有明显下降新实例冷启动慢模型加载和预热时间过长观察从扩容触发到 Ready 的耗时节点本地缓存模型权重提前预热队列里少量长请求堵住所有短请求FIFO 队列没有优先级区分查看排队请求的输入长度分布引入优先级队列长请求单独调度突发结束后延迟仍长时间不恢复客户端持续重试形成二次流量脉冲查看错误日志中的重试来源客户端启用指数退避服务端丢弃超时请求请求被无限等待用户直接超时队列没有最大长度限制查看队列配置和堆积趋势设置队列上限超限返回 4299. 最佳实践把突发性纳入日常运维9.1 建立请求级别的可观测性记录每个请求的到达时间、排队开始时间、执行开始时间、结束时间以及 Prompt 长度和输出长度。只有拿到这些维度才能区分延迟是花在排队上还是花在执行上。如果发现请求长时间停留在排队阶段说明容量不足如果请求一旦开始执行就很慢说明单机推理性能有待优化。9.2 保留一套历史流量重放工具把线上某段真实高峰期的请求记录保存下来脱敏后做成离线数据集。每次升级推理框架、调整模型版本或者修改调度策略之后用这套历史流量做回归压测对比 P99 和资源利用率。比单纯用恒定 QPS 压测更有说服力也更接近线上真实表现。9.3 设置可接受的过载保护阈值提前定义好“什么程度算过载”。比如队列长度超过 200或者单实例并发超过 32就直接拒绝新请求。不要等到延迟已经恶化到不可接受才做限制限流本身就是服务质量的一部分。9.4 多轮 Agent 场景要单独评估Agent 类应用和普通单轮问答的突发模式很不一样。一个 Agent 任务内部可能连续调用多次 LLM每次调用之间还有工具执行这类请求会在短时间内形成多倍的推理压力并且很难从传统的 HTTP 请求数简单预估。如果有 Agent 类业务建议单独统计 token 消耗速率把它作为容量规划的关键指标。9.5 涉及模型、代码、数据合规的检查清单如果这篇论文涉及的思路会被你落地到实际生产环境还要做好基础合规工作。比如请求日志里包含用户内容时记得脱敏处理。推理数据如果涉及业务敏感信息要在部署环境中限定访问范围。模型权重和推理框架要注意 License 要求尤其在商用场景里要确认使用边界。10. 总结与下一步Burstiness 不是一个新概念但把它放到 LLM serving 的场景里重新审视确实是现阶段很多线上问题的关键切入点。恒定 QPS 压测通过不代表线上能扛住真实流量因为真实流量的形状是脉冲式的不是直线式的。如果你是第一次接触这个方向建议按这样的顺序做验证先用请求日志分析脚本计算自己服务的变异系数和峰值均值比确认是否存在明显突发性再用脉冲负载压测工具给推理服务制造一次可控的高峰流量观察 P99 和显存表现最后从容量规划和限流策略入手把突发性因素正式纳入常规运维流程。最容易踩的坑是两个一个是只优化单机推理效率而忽视排队和扩容的滞后另一个是只用平均值做容量决策。先把这两个问题修正过来burstiness 带来的大部分稳定性问题都会得到明显改善。