loading

Loading

探索与发现

首页 AI 开发实战

推理服务批内超时淘汰:一次省下 25% 吞吐的事

字数: (2383)
阅读: (11)
0
摘要:推理服务过载时,排队最久的请求早已被客户端放弃,服务端却还在拿 GPU 算。主动在队列里淘汰超时请求,单这层就能把过载期有效吞吐拉高约四分之一,但淘汰线怎么定、分桶怎么切、恢复怎么收,才是真正花功夫的地方。

推理服务跟普通微服务最大的区别,是单个请求要占 GPU 好几秒。过载时它不是慢慢变慢,而是把最贵的算力花在注定作废的请求上。拿一个 8 卡实例举例:单请求 2 秒 GPU 时间,理论吞吐 4 QPS。流量涨到 8 QPS,队列深度每秒净增 4,几十秒后排在后面的请求等待时间早就超过客户端能容忍的阈值。它们没有被拒绝,还占着队列位和预分配的资源,GPU 满转,有效吞吐停在原地。

阿里云开发者社区那篇《推理服务的过载保护:排队、降级与熔断的次序》把这个现象说得很直接:推理过载的核心矛盾是“算最贵的资源,算注定作废的请求”。它讲的四层保护里,我认为最容易被忽略、但性价比最高的是第二层——队列超时淘汰。原因不在算法复杂度,而在心理上没人愿意主动砍自己的请求。

大家都在等客户端先放弃

这里的“批内”不只是指 batch 内部,而是指一段排队窗口里的所有请求。大多数服务端设计是“来了就入队”,出队的唯一路径是被执行或客户端断开。这条路径在同步推理里看似合理,实际上把判断责任推给了客户端。

问题在于,客户端早就先放弃了。同步 HTTP 连接在推理耗时长时超时率会陡然上升,阿里云 PAI 的异步推理材料里提到一个数据:推理耗时超过 15 秒时,同步请求超时率增至 62%。这意味着大量请求是“客户端已经断了,服务端还在算”。按原文的估算,排队 80 秒的请求,99% 的客户端早已放弃。

所以超时淘汰做的事很简单:请求在队列里待超过 N 秒(比如 30s),服务端主动把它从队里摘出来,返回 429,不等客户端发现。这层单独能把过载期的有效吞吐提升 25%左右。数字看起来不大,但要意识到,它提升的不是总量,而是在低质量请求淹进来时保住的可用吞吐。省下来的 GPU 时间直接分给了还能被用到结果的请求。

关键在“主动出队”这三个字

这里有个常见实现错误:超时检查只在出队时做。请求一直在队里排到要执行的那一刻,才发现它已经超时,撤销执行,回 429。这个顺序等于是发现浪费之后再止损,但排队期间它一直蚕食调度器的时间和排队位置。要做到真正有效,淘汰线程或者每个消费循环都必须周期性扫队头到队中,按入队时间戳主动剔出。

更细的一点是淘汰判断要和客户端 timeout 对齐,不一致会带来两种浪费。服务端设 30s,客户端设 60s,那么 35 秒时你淘汰了一个还能用的请求,用户看到的是 429,但他自己不认为这一轮应该失败。反过来,服务端设 60s,客户端设 30s,60 秒内正常排队的大量请求早就没人要了。合理的设置是按你的 P99 排队时间加一点余量取,并结合交互请求和批量请求拆桶。

不是所有渠道都该用同一个 N

推荐系统的特征拼装请求和后台批量生成,在排队超时上不该用一样的时间。交互请求(在线问答、实时补全)用户是真的在等,它的淘汰线应该紧贴客户端可忍受时间;批量请求(离线打标、后台摘要、队列里刷的合成任务)本身没有人在看进度条,淘汰线可以放很长,甚至不参与淘汰,只需要有容量上限。

这跟原文放到队列一层里讲的也是同一件事:入口限流应该按交互、批量分桶,过载时先砍批量桶。队列淘汰也要遵循同一组标签。只要不区分,就不受控地让批量流量混在交互请求队列的前后顺序里,结果可能是交互流量虽然在,但碰到前面一大段长时批请求(批量与交互混排),交互 P95 抖到不可用。给不同桶不同的淘汰线并切换不同的完成返回策略,反而是更贴近成本现实的取舍。

配额、迟到响应与恢复时的二次冲击

实现主动失效一项也要在服务层留可观测信息。从工程落地角度有几个点必须提前想清楚:

  • 408/429 选择:淘汰根因是服务端放弃,不是一个客户自己的超时,所以返回更准确的应该是带 Retry-After 的 429,也便于客户端触发退避而不是判定请求错误。
  • 重试引发补偿流。进入优雅退潮时,原请求返回 429 之后很多客户端会重试。把 retry 延迟和 jitter 控制好,否则淘汰做得越狠,重试进来的吞吐越猛。Jitter 应该跟排队优先级一起设计:对于批量任务可用较长的退避,并加到前面去,不要在还没退役完成时把它拉起来。
  • 恢复不要靠一次性升档。原文的恢复节奏是限流阈值分档上(比如 50%、80%、100% 每档观察两分钟,降到一半后每两分钟重争查一遍信号),讲得很清楚了:队列清空的瞬间恰是补偿突发出现的时候。淘汰率指标可以辅助你判断,如果你在这里就立刻把限流回到全量,补一针重试的流量极可能把排队时间直接打回淘汰线。

最后是那三个最值的监控位:队列深度、请求等待的 P95 以及超时淘汰率。淘汰率最实用。它是一个开销小但解释力很强的资源分配效果信号。淘汰率长期在 5% 以上,说明限流或淘汰线比较松懈,需要收紧;到 20% 左右就不能开,或按业务目标放行,反而应该补容量。

横向看其他实现

淘汰并不是队列范式的唯一做法。PAI 的异步推理用独立的输入/输出队列和订阅窗口大小来做背压,推理实例按自身能承受的并发度订阅,队列服务会记录实例连接;如果实例失联,它把分发给该实例的请求重新推到其他实例。它不淘汰请求,代价是需要客户端接受异步检索结果的语义,工程链更长(消息队列、输出队列、扩展也需要观察)。GKE 批推理那一侧更外围:订阅端延长 Pub/Sub 的确认窗口、用死信主题装住毒丸消息、遇到 429 退避消费。它们主要保住消息不丢,不解决排队里的死等。两者都与“批内超时淘汰”互补,而非替代。

超时淘汰真正的成本,主要不在代码量,而在你需要甘愿在每个过载周期做一轮“从队列里剔除请求”的决策:你知道那些请求如果继续排着还有极小概率成功,但还是主动放手,将剩余 GPU 时间留给排队更短、更可能被拿到结果的请求。这个取舍必须先在监控面板和压测里测清楚,再上线固化淘汰线,不能到最后变成排障时反复猜测随机参数。

转载请注明出处: 皮哈皮阿

本文的链接地址: https://blog.phpat.com/ai-dev/640.html

手机扫码阅读

微信或手机浏览器扫一扫,随时随地随心阅读与分享

您可能对以下文章感兴趣
1 / 1
预览图