长尾延迟没有对冲机制,TIMEOUT_S 一个旋钮调不出好取值
#24
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
现象
某些中转渠道会把个别请求挂住很久然后正常返回 200——不报错、不限流、不返回 429/503。
最小复现(自建中转 + MiniMax-M3,非流式,并发 1、串行、关掉重试,提示词是「用一句话说明
什么是并发。不超过二十个字。」)。连发 20 次,每次的耗时:
20 次里 5 次超过 60 秒,最长 262.9 秒,全部正常返回 200。 二十个字的提示词、单个请求、
没有并发。中位数 15.1 秒。
真实负载(同一个源,一个 seed 1073 次调用):
两次几乎一样)
——它们不是在生成,是在等
75 次。一个这么窄的峰更像某个固定时长的机制,不像负载造成的排队
我们没能把它归因到自己这一侧。 按「本次调用开始时在飞的其它调用数」分桶之后,在飞 0 条
的桶里照样有这个 90 多秒的峰——也就是说一个请求独自在飞时也会挂。但我们也不敢反过来说它与
并发无关:同一批数据里,每次调用平均被扣掉的等待时间在在飞 0 条时是最低的几档之一,方向指向
「并发确实会加剧」。样本不足以下结论,两个方向我们都不排除。
为什么现有旋钮不够
现在能拧的只有
TIMEOUT_S,而它是个两难:几十秒
单一的总时长阈值分不开「挂住了」和「正在认真生成」。
建议:对冲请求(hedged request)
发出请求后,若超过某阈值仍未收到首 token,就并发再发一个,谁先回用谁,另一个取消。
判据用 TTFT 而不是总时长,这样不会误杀慢生成——库已经在收
ttft_ms(
TransportResult.ttft_ms,middleware/retry.py:386搬进LLMResponse)。挂起的特征正是首 token 迟迟不来,而慢生成的首 token 是正常的。
配置形如:
默认关闭。开启后成本会小幅上升(被对冲掉的那次请求可能已经产生了计费),所以要在文档里
写明这个代价,并且让计费口径能把「对冲掉的那次」区分出来。
优先级
这条比另一个 issue「
LLMResponse答不出这次调用总共等了多久、试了几次」低:那一条是观测缺失,会让下游的数字不可信;这一条是性能增强,下游可以先靠降
TIMEOUT_S缓解。两条一起提是因为它们指向同一个现象。
环境
polygateway 1.3.3,Python 3.12,OpenAI 兼容 transport,非流式。
还需要注意前端需要返回真实的生成时间,所以可能涉及到添加多一个额外的参数,这个参数是排除了重试以及等待时间的裸生成时间,并且应该取对冲请求中快的那个。这样子就可以获取真实的请求时间,而不包含因为llm调用波动所带来的额外时间。然后对冲请求的相关参数中对于前端有用的部分也需要返回,如何算有用你来判断。
已在 1.3.7 解决(opt-in,默认关闭):
{SCOPE}__HEDGE__AFTER_S开启 chat 单路并发对冲:阈值到点向另一个等价源并发第二请求,谁先回用谁,输家取消收口。触发判据分流式(首 token 未至,误杀不了慢生成)与非流式(纯总时长阈值,物理上挂起与慢生成不可分,只能靠阈值取值)。CallStats新增三字段:generation_ms(裸生成时间=赢家那次 transport 墙钟,排除准入排队/退避/触发前等待/清理,与total_latency_ms的差值即波动开销)、hedges、hedge_won。HEDGE__MAX_EXTRAv1 仅单路(>1 仅 warning,梯次多路预留)。发布: https://gitea.iomgaa.online/iomgaa/PolyGateway/releases/tag/v1.3.7