大多数推理服务的监控面板长这样:QPS、P99 延迟、错误率三条曲线。上线头两周看着挺健康,某个周一早上 P99 突然从 800ms 跳到 6 秒,而 QPS 只涨了 20%。你去看后端,GPU 利用率 95%,vLLM 的 num_requests_waiting 已经到 140。请求一直在进来,也一直在排队,但没有任何一层指标告诉你是哪一类请求在排队、排了多久、为什么排。
这篇文章想讨论一个具体的工程问题:怎么把排队延迟拆成可以各自归因的几层,而不是盯着一个总分发呆。所有数字只来自公开可查的推理框架指标(vLLM 暴露的 Prometheus endpoint、GKE 推理最佳实践文档里的指标名)以及排队论建模,不编造任何自家线上数据。
先把计量单位换掉:请求和 Token 不是一回事
经典 Web 服务的利特尔法则 L = λW 好用的前提是“工作单位”约等于一个请求,服务时间方差可控。SQL 查询 5 到 50 毫秒,HTTP 调用 100 到 500 毫秒,规划容量时拿 RPS 一除就够。LLM 推理打破这个前提有三个口子:
- 双峰处理。每个请求先走 prefill(处理输入 prompt,可并行化),再走 decode(逐 token 顺序生成,每次前向一个 token)。两段计算特性不一样。
- 输出长度在请求完成前是未知的。一个请求生 10 个 token,另一个生 4000 个,服务时间方差跨两个数量级。你用
max_tokens能拿到上界,但没多少人会设准。 - 扩展受 KV 缓存内存约束。每多一个活动序列就多一份 KV 缓存,且随已生成 token 数增长。瓶颈经常在显存,不在算力。
把这三条摊开看就清楚了:请求层面的负载均衡器看到两个实例各 10 个并发请求,认为它们是均衡的。实际一个是 10 个各产 50 token 的短请求,另一个是 10 个各产 2000 token 的长请求,GPU 上的工作量差 40 倍。一台能以 200 token/s 稳定出字,另一台只有 40 token/s。差 4 到 5 倍。
所以第一件事不是加仪表盘,而是把主容量指标从 RPS 换成 TPS(每秒 token 数)。原来那条“QPS 没涨,延迟却崩了”的曲线,换成 TPS 之后大概率能看出入口 token 总量已经在爬坡。
四层账本:请求、排队、批次、解码
我倾向于把排队延迟显式分成可以用不同数据源、不同刷新频率去盯的四层,而不是等一个端到端 P99。这样同一个 P99 恶化,你能直接指到某一层。
input_tokens → 上游准入队列 → 后端 waiting 队 → 运行 batch → decode 输出
t0 W_admission W_backend B_batch ttft/tpot
第一层:入口到准入 = 请求到了多少人
你的负载均衡器或者是网关前面的那一层。这里没排队的话这层就是零;有的话多少是缓存命中或请求大小导致后面撑住了却仍然收进来。
第二层:上游准入队列 = 显式允许多少“待见”
这一层是代价最高的,大多数服务是不存在的——请求直接推进推理引擎,让引擎自己去排队。推过去之后,后端的队列你基本排不动。可控的队列一定要放在自己能排序的地方:调度、优先级、租户轮询都在这一层做。推进推理引擎的时候应当只有很短的内部等待队列。
怎么判断现在该拦还是该放?盯两个信号。1:后端每个序列的 token 生成速度。文章中说阈值约 7 tok/s 每序列。低于这条线的时候,说明系统已经在过载,继续接入只会更快把 KV 缓存填满,把缓存管理(逐出、重算)耗时推到比推理本身还多。这是一种直到灾难前都无事发生的失效模式,然后在半小时内全面失速。2:num_requests_waiting。多于 2 到 3 个就该在上游截下来。
第三层:后端等待对 = 内部可见但不可排序
这就是推理引擎自己的等待队列(vLLM 指标名是 vllm:num_requests_waiting)。从调度层面来看很难控制:你无法确认推理引擎按什么优先规则从里面取任务。一但看见它每秒十几二十的抖动,去查是谁猛推了长 token 成本请求进来、是否绕过了那边的准入。
第四层:batch 与 decode = 单序列的体感
这一层不是“排队”,而是两条本身就混在这段里的时延:排队进 batch 的时间,以及每步输出 token 的间距。TTFT = prefill 时间 + 等之前 batch 周转完成 + 在同一个 batch 里真正排到他 prefill 那一刻。TDOT(Time per Output Token)= 4 落到点(ttft)+ 输出长的序列在每一步拖慢整批解码结果。看这层的时候分开测量 prefill 和 decode 对整体的影响。
三个观测数据源各自能告诉你什么、不能告诉你什么
1. vLLM 的 Prometheus 指标(vllm:num_requests_waiting、vllm:num_requests_running、vllm:gpu_cache_usage_perc)。这是第四层的输入端,同时也合用到第三、二层的控制信号时。kv cache usage 是先于任何延迟峰出现的信号(GKE 推理最佳实践文档中也推荐它作为 HPA 的先行指标)。峰值往往是它先到 95%,两组后或数十秒后,实际延迟才开始出现。不能做的是替回答“下一个该谁”。也没有响应长度或属性的信息。
2. 上游准入/网关的队列指标(你这些统计:入队请求入池、释放、活跃 push)。这层别人测、不该强加“谁能得到更低延迟”的含糊实现。这一重覆盖的是第二层、能决定真控制和定制的地方都在这。延迟敏感的推理更要独立给维度去拿请求的服务说明。
3. 以返回为锚打点(对应第三层): TTFT 全开完整比首 token 到的时间更宽。测量时请分开两部分监控。
拆分再准,也不总生效
分开看是为了更准确做局部决策,但拆出来不等于都能观测能控。预判解码速度经常由输入长度较弱指示、强相关也无法进入任何进程去读真实的 decode 速度时候仍要容忍变差带来的扩大。会出错那些情况一定在现场可中断、可重放,不必再额外加重排。
拆分负责的是取舍测量手段是否合理:如果单是交付边缘业务看到队列阻塞,影响其实是并发算全线程资源 —— 因此还是要按业务优先级做队列的参与排序(高低优先级或批量/在线)。
实际最小可用配置是三件事:(1) 首层按源拆出队; (2) 后端的 token/s 和等待队列长度做串联; (3) TDOT 首 token 和时间分布可回到入口收起来。动手从前两层开始,放弃区分之前,先解决一个方向不对称 —— 也就是最初那个“看不到谁排、何时退”的问题。
建议下一步:找一条代表请求路径(请求入出口 - 上游队列 - 后端队列 - 批)依次打点,定量分辨每一级,给三个层次各自设一个“超过即准入停摆”阈值。先跑一周,再加上端指标。
转载请注明出处: 皮哈皮阿
本文的链接地址: https://blog.phpat.com/ai-engineering/626.html
微信或手机浏览器扫一扫,随时随地随心阅读与分享
-
2026 年 AI 应用评测:从单轮打分到多轮轨迹评估
一个 Agent 的评测分数从 72% 涨到 81%,团队很高兴,直到发现它学会了在失败时直接结束任务,把“没做完”算成了“无需回答”。评测设计错了,指标越好看越危险。
2026/10/19
-
推理成本下降之后:模型路由、KV Cache 与端侧推理
上个月一个团队拿着账单来找我:模型单价降了,费用却涨了三倍。看完调用链就明白了,问题不在单价,在用法。单位 token 便宜,不等于每次任务便宜。
2026/10/10
-
长度惩罚参数调优:常见参数名字容易混淆
长度惩罚在主流 API 里并不存在。真正管长度的是 max_tokens 和 stop sequences,frequency、presence、repetition 惩罚管的是用词重复和新词探索。把这些旋钮分开,能避免输出被截断、术语被替换成同义词两类故障。
4天前
-
推理模型的流式输出:thinking 块怎么流
推理模型的流式输出不只是逐字渲染:思考块、正文、工具调用参数混杂在 SSE 流里,客户端要按字段分流、独立缓冲、支持中途中断。记录接通义千问与智谱过程中的判断和踩坑。
3天前
-
动态拼批不是开个开关:线上拼批的取舍与门禁
在线服务里的动态拼批和离线批量推理是两码事。窗口时长、KV Cache 水位、批内参数对齐共同决定拼接能否成立。上线前该盯的是 p99 和显存峰值,而不只是吞吐。
1天前
-
推理服务排队延迟的分层观测:从请求数到Token账本
盯 QPS 和 P99 看不到排队延迟长在哪。把这件事拆成入口→上游准入队列→后端等待→batch 解码四层,用 vLLM 暴露的排队与 KV 缓存指标做层级归因,讨论分层之后仍然看不见的部分与取舍。
7小时前
京公网安备11011402053654 
@muffin:链接已更新,分类图像功能...
AI网址导航模版图标库更换指南
作者大大,图标库邀请链接失效了,麻烦更新...
AI网址导航模版图标库更换指南
太好看了,快点更新!
友情链接
@戴墨镜的刘备:哈喽,路过点赞。
vue.js组件之手写签名板vue-signature-simple
太好看了,快点更新!太棒了!👍真的很赞!
vue.js组件之手写签名板vue-signature-simple
你写得非常清晰明了,让我很容易理解你的观...
Emlog插件RSS订阅器发布